diff --git a/README.md b/README.md
index 61dc00699..eb7685345 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,8 @@ Spec files live alongside the code they are testing. For example, in peer-review
 ## UI-Router and States
 See any *.routes.js file as an example.
 
+**Important:** Make sure the url in your routes files ends with a slash `/`
+
 ## Contributing
 
 ### Style Guide and Naming Conventions
@@ -78,9 +80,9 @@ Jade Files
 ```
     .wrapper
       h1 Lorem ipsum
-      
+
       p Sibling tag
-      
+
     .wrapper2
       p Child
 ```
@@ -99,7 +101,7 @@ SCSS Files
       height: 100px;
       width: 100px;
     }
-    
+
     .inside-box {
       font-size: 14px;
       @media screen and (min-width: 768px) {
diff --git a/app/community/members.controller.js b/app/community/members.controller.js
index f8ce7ab45..6ed34b468 100644
--- a/app/community/members.controller.js
+++ b/app/community/members.controller.js
@@ -10,7 +10,7 @@
     ctrl.notSearch = true;
     ctrl.showing = 'list';
     ctrl.domain = CONSTANTS.domain;
-    ctrl.currentMonth = 'November 2015';
+    ctrl.currentMonth = 'December 2015';
     ctrl.memberLeaderboard = [];
     ctrl.copilots = [];
     CommunityDataService.getMembersData()
diff --git a/app/directives/badges/badge-tooltip.spec.js b/app/directives/badges/badge-tooltip.spec.js
index 17ff12223..43ec2ba27 100644
--- a/app/directives/badges/badge-tooltip.spec.js
+++ b/app/directives/badges/badge-tooltip.spec.js
@@ -6,14 +6,14 @@ describe('Badge Tooltip Directive', function() {
   var spotlightChallenge = mockData.getMockSpotlightChallenges()[0];
 
   beforeEach(function() {
-    bard.appModule('tcUIComponents');
+    bard.appModule('topcoder');
     bard.inject(this, '$compile', '$rootScope');
     scope = $rootScope.$new();
   });
 
   bard.verifyNoOutstandingHttpRequests();
 
-  xdescribe('Badge Tooltip', function() {
+  describe('Badge Tooltip', function() {
     var tooltip;
 
     beforeEach(function() {
@@ -89,5 +89,18 @@ describe('Badge Tooltip Directive', function() {
       expect(dataDiv).not.to.null;
       expect(dataDiv.hasClass('ng-hide')).to.equal(true);
     });
+
+    it('should trigger mouseenter handler ', function() {
+      tooltip.trigger('mouseenter');
+      var tooltipElement = tooltip.children(0);
+      expect(tooltipElement.css('z-index')).to.equal('2000');
+      expect(tooltip.isolateScope().hide).to.equal(false);
+    });
+
+    it('should trigger mouseleave handler ', function() {
+      tooltip.trigger('mouseleave');
+      tooltipElement = tooltip.children(0);
+      expect(tooltip.isolateScope().hide).to.equal(true);
+    });
   });
 });
diff --git a/app/directives/challenge-tile/challenge-tile.directive.jade b/app/directives/challenge-tile/challenge-tile.directive.jade
index 3ec2e022d..325bd5f76 100644
--- a/app/directives/challenge-tile/challenge-tile.directive.jade
+++ b/app/directives/challenge-tile/challenge-tile.directive.jade
@@ -2,80 +2,75 @@
   .active-challenge(ng-show="challenge.status === 'ACTIVE'")
 
     header
-      .top
-        a.name(ng-href="{{challenge|challengeLinks:'detail'}}") #[span {{challenge.name}}]
+      a.name(ng-href="{{challenge|challengeLinks:'detail'}}", title="{{challenge.name}}") #[span {{challenge.name}}]
 
-        .challenge-track
-
-        p.subtrack-color {{challenge.subTrack | underscoreStrip}}
+      p.subtrack-color {{challenge.subTrack | underscoreStrip}}
 
       challenge-links(challenge="challenge", view="'tile'")
+    .challenge-card__bottom.challenge-card__bottom--active
+      .challenge-details
+        p.currentPhase {{challenge.userCurrentPhase}}
 
-    .challenge-details
-      p.currentPhase {{challenge.userCurrentPhase}}
-
-      .challenge-calendar(ng-show="challenge.userCurrentPhaseEndTime")
-        p.ends-in Ends In
-        p.time-remaining {{challenge.userCurrentPhaseEndTime[0]}}
-        p.unit-of-time {{challenge.userCurrentPhaseEndTime[1]}}
+        .challenge-calendar(ng-show="challenge.userCurrentPhaseEndTime")
+          p.ends-in Ends In
+          p.time-remaining {{challenge.userCurrentPhaseEndTime[0]}}
+          p.unit-of-time {{challenge.userCurrentPhaseEndTime[1]}}
 
-      .stalled-challenge(ng-hide="challenge.userCurrentPhaseEndTime") This challenge is currently paused.
+        .stalled-challenge(ng-hide="challenge.userCurrentPhaseEndTime") This challenge is currently paused.
 
-      .phase-action(ng-switch="challenge.userAction")
-        a.tc-btn.tc-btn-s.tc-btn-wide.tc-btn-ghost.submit(ng-switch-when="Submit", ng-href="{{challenge|challengeLinks:'detail'}}") Submit
+        .phase-action(ng-show="challenge.userAction", ng-switch="challenge.userAction")
+          a.tc-btn.tc-btn-s.tc-btn-wide.tc-btn-ghost.submit(ng-switch-when="Submit", ng-href="{{challenge|challengeLinks:'detail'}}") Submit
 
-        .submitted(ng-switch-when="Submitted") Submitted
+          .submitted(ng-switch-when="Submitted") Submitted
 
-        // TODO: Need styling and JS logic for this one
-        .registered(ng-switch-when="Registered") Registered
+          // TODO: Need styling and JS logic for this one
+          .registered(ng-switch-when="Registered") Registered
 
-    // Only show if not data science track
-    p.roles 
-      span(ng-hide="challenge.track === 'DATA_SCIENCE'") 
-        span Role:  
-        span {{challenge.userDetails.roles | listRoles}}
+      // Only show if not data science track
+      p.roles 
+        span(ng-hide="challenge.track === 'DATA_SCIENCE'") 
+          span Role:  
+          span {{challenge.userDetails.roles | listRoles}}
 
   .completed-challenge(
     ng-show="challenge.status === 'COMPLETED' || challenge.status === 'PAST'",
     ng-switch="challenge.track")
-    .challenge-track
 
     header
-      .top
-        a.name(ng-href="{{challenge|challengeLinks:'detail'}}") {{challenge.name}}
+      a.name(ng-href="{{challenge|challengeLinks:'detail'}}", title="{{challenge.name}}") {{challenge.name}}
 
-        p.subtrack-color {{challenge.subTrack | underscoreStrip}}
+      p.subtrack-color {{challenge.subTrack | underscoreStrip}}
 
       p.date-completed {{challenge.submissionEndDate | date : 'MMMM yyyy'}}
 
       .winner-ribbon(ng-show="challenge.wonFirst")
 
-    .challenge-details(ng-switch-when="DATA_SCIENCE", ng-switch="challenge.subTrack", ng-class="challenge.track")
+    .challenge-card__bottom.challenge-card__bottom--completed
+      .challenge-details(ng-switch-when="DATA_SCIENCE", ng-switch="challenge.subTrack", ng-class="challenge.track")
 
-      div
-        .marathon-score
-          p.score {{challenge.pointTotal || 0 }}
+        div
+          .marathon-score
+            p.score {{challenge.pointTotal || 0 }}
 
-          p Total Points
+            p Total Points
 
-    .challenge-details(ng-switch-when="DEVELOP")
-      dev-challenge-user-place(challenge="challenge", view="view")
+      .challenge-details(ng-switch-when="DEVELOP")
+        dev-challenge-user-place(challenge="challenge", view="view")
 
-    .challenge-details(ng-switch-when="DESIGN")
-      design-challenge-user-place(challenge="challenge", view="view")
+      .challenge-details(ng-switch-when="DESIGN")
+        design-challenge-user-place(challenge="challenge", view="view")
 
-    // Only show if not data science track
-    p.roles
-      span(ng-hide="challenge.track === 'DATA_SCIENCE'") 
-        span Role:  
-        span {{challenge.userDetails.roles | listRoles}}
+      // Only show if not data science track
+      p.roles
+        span(ng-hide="challenge.track === 'DATA_SCIENCE'") 
+          span Role:  
+          span {{challenge.userDetails.roles | listRoles}}
 
 .challenge.list-view(ng-show="view=='list'", ng-class="challenge.track")
   .active-challenge(ng-show="challenge.status === 'ACTIVE'")
-    .challenge-track
 
     header
-      a.name(ng-href="{{challenge|challengeLinks:'detail'}}") {{challenge.name}}
+      a.name(ng-href="{{challenge|challengeLinks:'detail'}}", title="{{challenge.name}}") {{challenge.name}}
       
       p.subtrack-color {{challenge.subTrack | underscoreStrip}}
 
@@ -102,10 +97,9 @@
   .completed-challenge(
     ng-show="challenge.status === 'COMPLETED' || challenge.status === 'PAST'",
     ng-switch="challenge.track")
-    .challenge-track
 
     header
-      a.name(ng-href="{{challenge|challengeLinks:'detail'}}") {{challenge.name}}
+      a.name(ng-href="{{challenge|challengeLinks:'detail'}}", title="{{challenge.name}}") {{challenge.name}}
 
       p.subtrack-color {{challenge.subTrack | underscoreStrip}}
 
@@ -113,11 +107,10 @@
 
     .challenge-details(ng-switch-when="DATA_SCIENCE", ng-switch="challenge.subTrack", ng-class="challenge.track")
 
-      div
-        .marathon-score
-          p.score {{challenge.pointTotal || 0 }}
+      .marathon-score
+        p.score {{challenge.pointTotal || 0 }}
 
-          p Total Points
+        p Total Points
 
     .challenge-details(ng-switch-when="DEVELOP")
       dev-challenge-user-place(challenge="challenge", view="view")
diff --git a/app/directives/challenge-user-place/dev-challenge-user-place.directive.jade b/app/directives/challenge-user-place/dev-challenge-user-place.directive.jade
index 99156ddc9..eeca35389 100644
--- a/app/directives/challenge-user-place/dev-challenge-user-place.directive.jade
+++ b/app/directives/challenge-user-place/dev-challenge-user-place.directive.jade
@@ -4,7 +4,7 @@
     p.place(ng-show="challenge.userStatus === 'PASSED_SCREENING'") Passed Screening
     p.place(ng-show="challenge.userStatus === 'COMPLETED'") COMPLETED
 
-  .challenge-score(ng-class="{hidden: challenge.userStatus !== 'PASSED_REVIEW'}")
+  .challenge-score(ng-hide="challenge.userStatus !== 'PASSED_REVIEW'")
     p.score {{challenge.userDetails.submissionReviewScore/100 | percentage}}
 
     p Review Score
diff --git a/app/directives/page-state-header/page-state-header.directive.js b/app/directives/page-state-header/page-state-header.directive.js
index adc3de015..37ba60297 100644
--- a/app/directives/page-state-header/page-state-header.directive.js
+++ b/app/directives/page-state-header/page-state-header.directive.js
@@ -4,7 +4,7 @@
   angular.module('tcUIComponents').directive('pageStateHeader', function() {
     return {
       restrict: 'E',
-      templateUrl: 'directives/page-state-header/page-state-header.directive.html',
+      templateUrl: 'directives/page-state-header/page-state-header.html',
       transclude: true,
       scope: {
         handle: '@',
diff --git a/app/directives/page-state-header/page-state-header.directive.jade b/app/directives/page-state-header/page-state-header.jade
similarity index 96%
rename from app/directives/page-state-header/page-state-header.directive.jade
rename to app/directives/page-state-header/page-state-header.jade
index ba80d2b90..6ed6a0e3c 100644
--- a/app/directives/page-state-header/page-state-header.directive.jade
+++ b/app/directives/page-state-header/page-state-header.jade
@@ -1,7 +1,7 @@
 .page-state-header
   header
     .page-info
-      h1 {{pageTitle}}
+      h1 {{pageTitle | track}}
 
       div(ng-transclude)
 
diff --git a/app/directives/tc-file-input/tc-file-input.directive.js b/app/directives/tc-file-input/tc-file-input.directive.js
index a7cb39c21..bc4a18554 100644
--- a/app/directives/tc-file-input/tc-file-input.directive.js
+++ b/app/directives/tc-file-input/tc-file-input.directive.js
@@ -28,8 +28,8 @@
         var fileInput = $(element[0]).find('.none');
         var fileNameInput = $(element[0]).find('input[type=text]');
 
-        fileInput.bind('change', function() {
-          var file = fileInput[0].files[0];
+        fileInput.bind('change', function(event) {
+          var file = event.target.files[0];
 
           // About 1 in 20 times, the file is undefined (must be race condition)
           // Return early in this case so no errors are thrown
diff --git a/app/directives/tc-file-input/tc-file-input.jade b/app/directives/tc-file-input/tc-file-input.jade
index c1b097480..f67de11ff 100644
--- a/app/directives/tc-file-input/tc-file-input.jade
+++ b/app/directives/tc-file-input/tc-file-input.jade
@@ -2,10 +2,11 @@
   label.tc-label {{labelText}}
     span.lowercase(ng-if="showFileType") {{ ' *(.' + fileType + ')'}}
 
-    span.tc-label__mandatory.lowercase(ng-if="mandatory") #[span *]mandatory
-
 .tc-file-field__inputs
-  input.tc-file-field__input(type="text", placeholder="{{placeholder}}", disabled)
+  .tc-label__wrapper
+    input.tc-file-field__input(type="text", placeholder="{{placeholder}}", disabled)
+
+    span.tc-label__asterisk.lowercase(ng-if="mandatory") #[span *]mandatory
 
   button.tc-btn(ng-click="selectFile()") {{buttonText}}
 
diff --git a/app/directives/tc-file-input/tc-file-input.spec.js b/app/directives/tc-file-input/tc-file-input.spec.js
index 3f2b398aa..ef1917cc8 100644
--- a/app/directives/tc-file-input/tc-file-input.spec.js
+++ b/app/directives/tc-file-input/tc-file-input.spec.js
@@ -1,19 +1,148 @@
 /* jshint -W117, -W030 */
 describe('Topcoder File Input Directive', function() {
-  var scope;
-
-  // USE AS TEMPLATE FOR DIRECTIVES
+  var scope, element, isolateScope, fileInput;
 
   beforeEach(function() {
-    bard.appModule('tcUIComponents');
+    bard.appModule('topcoder');
     bard.inject(this, '$compile', '$rootScope');
+    scope = $rootScope.$new();
+
+    var html = '' +
+      '<form>' +
+        '<tc-file-input ' +
+          'label-text="Preview Image"' +
+          'field-id="DESIGN_COVER"' +
+          'button-text="Add File"' +
+          'file-type="jpg,jpeg,png"' +
+          'placeholder="Image file as .jpg or .png"' +
+          'mandatory="true"' +
+          'set-file-reference="vm.setFileReference(file, fieldId)"' +
+          'ng-model="vm.submissionForm.submissionZip"' +
+        ' />' +
+      '</form>';
+    var form = angular.element(html);
+    element = form.find('tc-file-input');
+    var formElement = $compile(form)(scope);
+    scope.$digest();
+
+    isolateScope = element.isolateScope();
+  });
+
+  beforeEach(function() {
+    fileInput = $(element).find('.none')[0];
+  });
+
+  afterEach(function() {
+    scope.$destroy();
+    fileInput = undefined;
   });
 
   bard.verifyNoOutstandingHttpRequests();
 
-  xdescribe('', function() {
-    beforeEach(function() {});
+  describe('selectFile', function() {
+    it('triggers a click on the file input', function() {
+      var mockClick = sinon.spy(fileInput, 'click');
+
+      isolateScope.selectFile();
+      scope.$digest();
+
+      expect(mockClick).calledOnce;
+    });
+  });
+
+  describe('a change event on the file input', function() {
+    var fileNameInput, fileList, mockSetFileReference;
+
+    beforeEach(function() {
+      fileNameInput = $(element).find('input[type=text]')[0];
+      fileList = {
+        0: {
+          name: 'test.png',
+          size: 50,
+          type: 'image/png'
+        },
+        length: 1,
+        item: function (index) { return file; }
+      };
+
+      mockSetFileReference = sinon.spy(isolateScope, 'setFileReference');
+    });
+
+    afterEach(function() {
+      fileNameInput = undefined;
+      fileList = undefined;
+      mockSetFileReference = undefined;
+    });
+
+    it('sets the value of the fileNameInput with the name of the file', function() {
+      $(fileInput).triggerHandler({
+        type: 'change',
+        target: { files: fileList }
+      });
+
+      expect(fileNameInput.value).to.equal('test.png');
+    });
+
+    describe('with a valid file', function() {
+      beforeEach(function() {
+        $(fileInput).triggerHandler({
+          type: 'change',
+          target: { files: fileList }
+        });
+      });
+
+      it('calls setFileReference', function() {
+        expect(mockSetFileReference).calledOnce;
+      });
+
+      it('has ng-valid-filesize class', function() {
+        expect($(fileInput).hasClass('ng-valid-filesize')).to.be.true;
+      });
+
+      it('has ng-valid-required class', function() {
+        expect($(fileInput).hasClass('ng-valid-required')).to.be.true;
+      });
+    });
+
+    describe('with a file that\'s greater than 500MB', function() {
+      beforeEach(function() {
+        fileList[0].size = 500000001;
+
+        $(fileInput).triggerHandler({
+          type: 'change',
+          target: { files: fileList }
+        });
+      });
+
+      it('does not call setFileReference', function() {
+        expect(mockSetFileReference).not.calledOnce;
+      });
+
+      it('has ng-touched and ng-invalid-filesize classes', function() {
+        expect($(fileInput).hasClass('ng-invalid-filesize')).to.be.true;
+        expect($(fileInput).hasClass('ng-touched')).to.be.true;
+      });
+    });
+
+    describe('with a file type that\'s not in the list of fileTypes given to the directive', function() {
+      beforeEach(function() {
+        fileList[0].type = 'application/zip';
+
+        $(fileInput).triggerHandler({
+          type: 'change',
+          target: { files: fileList }
+        });
+      });
+
+      it('does not call setFileReference', function() {
+        expect(mockSetFileReference).not.calledOnce;
+      });
+
+      it('has ng-touched and ng-invalid-required classes', function() {
+        expect($(fileInput).hasClass('ng-invalid-required')).to.be.true;
+        expect($(fileInput).hasClass('ng-touched')).to.be.true;
+      });
+    });
 
-    it('', function() {});
   });
 });
diff --git a/app/directives/tc-form-fonts/tc-form-fonts.directive.js b/app/directives/tc-form-fonts/tc-form-fonts.directive.js
index 2b08d47fd..9b097a24e 100644
--- a/app/directives/tc-form-fonts/tc-form-fonts.directive.js
+++ b/app/directives/tc-form-fonts/tc-form-fonts.directive.js
@@ -42,6 +42,9 @@
           isFontSourceRequired: false
         };
 
+        // Initialize font form data
+        $scope.formFonts = { 0: _.assign({id: 0}, angular.copy(emptyFont)) };
+
         $scope.urlRegEx = new RegExp(/^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,3})+(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?$/);
 
         $scope.selectFont = function(newFont) {
diff --git a/app/directives/tc-form-fonts/tc-form-fonts.spec.js b/app/directives/tc-form-fonts/tc-form-fonts.spec.js
index 5c17c2df7..cfe6f16e6 100644
--- a/app/directives/tc-form-fonts/tc-form-fonts.spec.js
+++ b/app/directives/tc-form-fonts/tc-form-fonts.spec.js
@@ -1,19 +1,233 @@
 /* jshint -W117, -W030 */
 describe('Topcoder Form Fonts Directive', function() {
-  var scope;
-
-  // USE AS TEMPLATE FOR DIRECTIVES
+  var scope, element, isolateScope;
 
   beforeEach(function() {
-    bard.appModule('tcUIComponents');
+    bard.appModule('topcoder');
     bard.inject(this, '$compile', '$rootScope');
+    scope = $rootScope.$new();
+    scope.formFonts = [];
+
+    var form = angular.element('<form><tc-form-fonts form-fonts="formFonts" /></form>');
+    element = form.find('tc-form-fonts');
+    var formElement = $compile(form)(scope);
+    scope.$digest();
+
+    isolateScope = element.isolateScope();
+  });
+
+  afterEach(function() {
+    scope.$destroy();
   });
 
   bard.verifyNoOutstandingHttpRequests();
 
-  xdescribe('', function() {
-    beforeEach(function() {});
+  describe('is initialized with', function() {
+    it('empty font data', function() {
+      var defaultFormFont = isolateScope.formFonts[0];
+
+      expect(defaultFormFont.id).to.equal(0);
+      expect(defaultFormFont.source).to.equal('');
+      expect(defaultFormFont.name).to.equal('');
+      expect(defaultFormFont.sourceUrl).to.equal('');
+      expect(defaultFormFont.isFontUrlRequired).to.be.false;
+      expect(defaultFormFont.isFontUrlDisabled).to.be.true;
+      expect(defaultFormFont.isFontNameRequired).to.be.false;
+      expect(defaultFormFont.isFontNameDisabled).to.be.true;
+      expect(defaultFormFont.isFontSourceRequired).to.be.false;
+    });
+
+    it('a font list', function() {
+      var fontList = isolateScope.fontList0;
+
+      expect(fontList).to.be.an.array;
+      expect(fontList).to.have.length.of.at.least(1);
+    });
+
+    it('a regular expression', function() {
+      expect(isolateScope.urlRegEx).to.be.an.instanceof(RegExp);
+    });
+  });
+
+  describe('selectFont', function() {
+    var newFont, targetedFont;
+
+    beforeEach(function() {
+      newFont = {
+        id: 0,
+        value: 'FONTS_DOT_COM'
+      };
+
+      targetedFont = isolateScope.formFonts[0];
+    });
+
+    afterEach(function() {
+      newFont = undefined;
+      targetedFont = undefined;
+    });
+
+    it('updates the targeted font source with the new value', function() {
+      expect(targetedFont.source).to.equal('');
+
+      isolateScope.selectFont(newFont);
+      scope.$digest();
+
+      expect(targetedFont.source).to.equal('FONTS_DOT_COM');
+    });
+
+    it('sets disabled properties to false', function() {
+      expect(targetedFont.isFontNameDisabled).to.be.true;
+      expect(targetedFont.isFontUrlDisabled).to.be.true;
+
+      isolateScope.selectFont(newFont);
+      scope.$digest();
+
+      expect(targetedFont.isFontNameDisabled).to.be.false;
+      expect(targetedFont.isFontUrlDisabled).to.be.false;
+    });
+
+    it('sets required properties to true', function() {
+      expect(targetedFont.isFontNameRequired).to.be.false;
+      expect(targetedFont.isFontUrlRequired).to.be.false;
+
+      isolateScope.selectFont(newFont);
+      scope.$digest();
+
+      expect(targetedFont.isFontNameRequired).to.be.true;
+      expect(targetedFont.isFontUrlRequired).to.be.true;
+    });
+
+    it('sets isFontNameRequired to true and isFontUrlRequired to false when STUDIO_STANDARD_FONTS_LIST is selected', function() {
+      expect(targetedFont.isFontNameRequired).to.be.false;
+      expect(targetedFont.isFontUrlRequired).to.be.false;
+
+      isolateScope.selectFont({id: 0, value: 'STUDIO_STANDARD_FONTS_LIST'});
+      scope.$digest();
+
+      expect(targetedFont.isFontNameRequired).to.be.true;
+      expect(targetedFont.isFontUrlRequired).to.be.false;
+    });
+  });
+
+  describe('createAdditionalFontFieldset', function() {
+    it('creates a new fieldset', function() {
+      expect(Object.keys(isolateScope.formFonts).length).to.equal(1);
+
+      isolateScope.createAdditionalFontFieldset();
+      scope.$digest();
+
+      expect(Object.keys(isolateScope.formFonts).length).to.equal(2);
+    });
+
+    it('adds an incremented id to the new fieldset', function() {
+      var id = isolateScope.formFonts[0].id;
+
+      isolateScope.createAdditionalFontFieldset();
+      scope.$digest();
+
+      expect(isolateScope.formFonts[1]).to.exist;
+      expect(isolateScope.formFonts[1].id).to.equal(id + 1);
+    });
+  });
+
+  describe('deleteFontFieldset', function() {
+    it('deletes the selected font fieldset', function() {
+      isolateScope.createAdditionalFontFieldset();
+      scope.$digest();
+
+      expect(Object.keys(isolateScope.formFonts).length).to.equal(2);
+
+      isolateScope.deleteFontFieldset(1);
+      scope.$digest();
+
+      expect(Object.keys(isolateScope.formFonts).length).to.equal(1);
+    });
+
+    it('resets the font fieldset when it\'s the only one', function() {
+      var font = isolateScope.formFonts[0];
+
+      expect(font.source).to.equal('');
+
+      font.source = 'dropdown selection';
+      scope.$digest();
+
+      expect(font.source).to.equal('dropdown selection');
+
+      isolateScope.deleteFontFieldset(0);
+      scope.$digest();
+
+      expect(isolateScope.formFonts[0].source).to.equal('');
+    });
+  });
+
+  describe('isButtonDisabled', function() {
+    var button;
+
+    beforeEach(function() {
+      button = $(element).find('.fieldset__button')[0];
+    });
+
+    afterEach(function() {
+      button = undefined;
+    });
+
+    it('disables the button when no fields are filled out', function() {
+      expect(button.disabled).to.be.true;
+    });
+
+    it('disables the button when 1 field is filled out', function() {
+      isolateScope.formFonts[0].source = 'FONTS_DOT_COM';
+      scope.$digest();
+
+      expect(button.disabled).to.be.true;
+    });
+
+    it('disables the button when 2 fields are filled out', function() {
+      isolateScope.formFonts[0].source = 'FONTS_DOT_COM';
+      isolateScope.formFonts[0].sourceUrl = 'url.com';
+      scope.$digest();
+
+      expect(button.disabled).to.be.true;
+    });
+
+    it('enables the button when all fields are filled out', function() {
+      isolateScope.formFonts[0].source = 'FONTS_DOT_COM';
+      isolateScope.formFonts[0].name = 'name';
+      isolateScope.formFonts[0].sourceUrl = 'url.com';
+      scope.$digest();
+
+      expect(button.disabled).to.be.false;
+    });
+
+    it('disables the button when any field in any fieldset is empty', function() {
+      expect(button.disabled).to.be.true;
+
+      // Fill out first fieldset
+      isolateScope.formFonts[0].source = 'FONTS_DOT_COM';
+      isolateScope.formFonts[0].name = 'name';
+      isolateScope.formFonts[0].sourceUrl = 'url.com';
+      scope.$digest();
+
+      expect(button.disabled).to.be.false;
+
+      isolateScope.createAdditionalFontFieldset();
+      scope.$digest();
+
+      expect(button.disabled).to.be.true;
+
+      // Fill out second fieldset
+      isolateScope.formFonts[1].source = 'FONTS_DOT_COM2';
+      isolateScope.formFonts[1].name = 'name';
+      isolateScope.formFonts[1].sourceUrl = 'url2.com';
+      scope.$digest();
+
+      expect(button.disabled).to.be.false;
+
+      // Empty a field in the first fieldset
+      isolateScope.formFonts[0].name = '';
+      scope.$digest();
 
-    it('', function() {});
+      expect(button.disabled).to.be.true;
+    });
   });
 });
diff --git a/app/directives/tc-form-stockart/tc-form-stockart.directive.js b/app/directives/tc-form-stockart/tc-form-stockart.directive.js
index c5c3fca12..c2ae46bdb 100644
--- a/app/directives/tc-form-stockart/tc-form-stockart.directive.js
+++ b/app/directives/tc-form-stockart/tc-form-stockart.directive.js
@@ -26,6 +26,9 @@
           isFileNumberRequired: false
         };
 
+        // Initialize stockart form data
+        $scope.formStockarts = { 0: _.assign({id: 0}, angular.copy(emptyStockart)) };
+
         $scope.urlRegEx = new RegExp(/^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,3})+(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?$/);
 
         $scope.createAdditionalStockartFieldset = function() {
diff --git a/app/directives/tc-form-stockart/tc-form-stockart.jade b/app/directives/tc-form-stockart/tc-form-stockart.jade
index 3955c0eb3..de63abc07 100644
--- a/app/directives/tc-form-stockart/tc-form-stockart.jade
+++ b/app/directives/tc-form-stockart/tc-form-stockart.jade
@@ -4,6 +4,7 @@
   tc-input.fieldset__input(
     label-text="Photo Description",
     asterisk-text="Field can't be empty",
+    show-asterisk-text="true",
     placeholder="A picture of a girl",
     input-value="stockart.description",
     input-name="photoDescription{{stockartId}}",
@@ -15,6 +16,7 @@
   tc-input.fieldset__input(
     label-text="Photo URL",
     asterisk-text="Field can't be empty",
+    show-asterisk-text="true",
     placeholder="www.istockphoto.com",
     input-value="stockart.sourceUrl",
     input-name="photoURL{{stockartId}}",
@@ -30,6 +32,7 @@
   tc-input.fieldset__input(
     label-text="File Number",
     asterisk-text="Field can't be empty",
+    show-asterisk-text="true",
     placeholder="u2434312",
     input-value="stockart.fileNumber",
     input-name="fileNumber{{stockartId}}",
diff --git a/app/directives/tc-form-stockart/tc-form-stockart.spec.js b/app/directives/tc-form-stockart/tc-form-stockart.spec.js
index fe821738a..f8dfeef5d 100644
--- a/app/directives/tc-form-stockart/tc-form-stockart.spec.js
+++ b/app/directives/tc-form-stockart/tc-form-stockart.spec.js
@@ -1,19 +1,227 @@
 /* jshint -W117, -W030 */
 describe('Topcoder Form Stockart Directive', function() {
-  var scope;
-
-  // USE AS TEMPLATE FOR DIRECTIVES
+  var scope, element, isolateScope;
 
   beforeEach(function() {
-    bard.appModule('tcUIComponents');
+    bard.appModule('topcoder');
     bard.inject(this, '$compile', '$rootScope');
+    scope = $rootScope.$new();
+    scope.stockarts = [];
+
+    var form = angular.element('<form><tc-form-stockart form-stockarts="stockarts" /></form>');
+    element = form.find('tc-form-stockart');
+    var formElement = $compile(form)(scope);
+    scope.$digest();
+
+    isolateScope = element.isolateScope();
+  });
+
+  afterEach(function() {
+    scope.$destroy();
   });
 
   bard.verifyNoOutstandingHttpRequests();
 
-  xdescribe('', function() {
-    beforeEach(function() {});
+  describe('is initialized with', function() {
+    it('empty stockart data', function() {
+      var initialStockart = isolateScope.formStockarts[0];
+
+      expect(initialStockart.id).to.equal(0);
+      expect(initialStockart.description).to.equal('');
+      expect(initialStockart.sourceUrl).to.equal('');
+      expect(initialStockart.fileNumber).to.equal('');
+      expect(initialStockart.isPhotoDescriptionRequired).to.equal(false);
+      expect(initialStockart.isPhotoURLRequired).to.equal(false);
+      expect(initialStockart.isFileNumberRequired).to.equal(false);
+    });
+
+    it('a regular expression', function() {
+      expect(isolateScope.urlRegEx).to.be.an.instanceof(RegExp);
+    });
+  });
+
+  describe('createAdditionalStockartFieldset', function() {
+    it('creates a new fieldset', function() {
+      expect(Object.keys(isolateScope.formStockarts).length).to.equal(1);
+
+      isolateScope.createAdditionalStockartFieldset();
+      scope.$digest();
+
+      expect(Object.keys(isolateScope.formStockarts).length).to.equal(2);
+    });
+
+    it('adds an incremented id to the new fieldset', function() {
+      var id = isolateScope.formStockarts[0].id;
+
+      isolateScope.createAdditionalStockartFieldset();
+      scope.$digest();
+
+      expect(isolateScope.formStockarts[1]).to.exist;
+      expect(isolateScope.formStockarts[1].id).to.equal(id + 1);
+    });
+  })
+
+  describe('deleteStockartFieldset', function() {
+    it('deletes the selected stockart fieldset', function() {
+      isolateScope.createAdditionalStockartFieldset();
+      scope.$digest();
+
+      expect(Object.keys(isolateScope.formStockarts).length).to.equal(2);
+
+      isolateScope.deleteStockartFieldset(1);
+      scope.$digest();
+
+      expect(Object.keys(isolateScope.formStockarts).length).to.equal(1);
+    });
+
+    it('resets the stockart fieldset when it\'s the only one', function() {
+      var stockart = isolateScope.formStockarts[0];
+
+      expect(stockart.description).to.equal('');
+
+      stockart.description = 'a funny cat picture';
+      scope.$digest();
+
+      expect(stockart.description).to.equal('a funny cat picture');
+
+      isolateScope.deleteStockartFieldset(0);
+      scope.$digest();
+
+      expect(isolateScope.formStockarts[0].description).to.equal('');
+    });
+  });
+
+  describe('isButtonDisabled', function() {
+    var button;
+
+    beforeEach(function() {
+      button = $(element).find('.fieldset__button')[0];
+    });
+
+    afterEach(function() {
+      button = undefined;
+    });
+
+    it('disables the button when no fields are filled out', function() {
+      expect(button.disabled).to.be.true;
+    });
+
+    it('disables the button when 1 field is filled out', function() {
+      isolateScope.formStockarts[0].description = 'test description';
+      scope.$digest();
+
+      expect(button.disabled).to.be.true;
+    });
+
+    it('disables the button when 2 fields are filled out', function() {
+      isolateScope.formStockarts[0].description = 'test description';
+      isolateScope.formStockarts[0].sourceUrl = 'url';
+      scope.$digest();
+
+      expect(button.disabled).to.be.true;
+    });
+
+    it('enables the button when all fields are filled out', function() {
+      isolateScope.formStockarts[0].description = 'test description';
+      isolateScope.formStockarts[0].sourceUrl = 'url';
+      isolateScope.formStockarts[0].fileNumber = '123';
+      scope.$digest();
+
+      expect(button.disabled).to.be.false;
+    });
+
+    it('disables the button when any field in any fieldset is empty', function() {
+      expect(button.disabled).to.be.true;
+
+      // Fill out first fieldset
+      isolateScope.formStockarts[0].description = 'test description';
+      isolateScope.formStockarts[0].sourceUrl = 'url.com';
+      isolateScope.formStockarts[0].fileNumber = '123';
+      scope.$digest();
+
+      expect(button.disabled).to.be.false;
+
+      isolateScope.createAdditionalStockartFieldset();
+      scope.$digest();
+
+      expect(button.disabled).to.be.true;
+
+      // Fill out second fieldset
+      isolateScope.formStockarts[1].description = 'test description2';
+      isolateScope.formStockarts[1].sourceUrl = 'url2.com';
+      isolateScope.formStockarts[1].fileNumber = '1232';
+      scope.$digest();
+
+      expect(button.disabled).to.be.false;
+
+      // Empty a field in the first fieldset
+      isolateScope.formStockarts[0].fileNumber = '';
+      scope.$digest();
+
+      expect(button.disabled).to.be.true;
+    });
+  });
+
+  describe('showMandatoryMessage', function() {
+    describe('sets the stockart required properties to false when all fields are', function() {
+      var stockart;
+
+      beforeEach(function() {
+        stockart = isolateScope.formStockarts[0];
+        stockart.description = 'test description';
+        stockart.sourceUrl = 'url.com';
+        stockart.fileNumber = '123';
+        scope.$digest();
+      });
+
+      afterEach(function() {
+        stockart = undefined;
+      });
+
+      it('filled out', function() {
+        expect(stockart.isPhotoDescriptionRequired).to.be.false;
+        expect(stockart.isPhotoURLRequired).to.be.false;
+        expect(stockart.isFileNumberRequired).to.be.false;
+      });
+
+      it('empty', function() {
+        // Reset stockart fields
+        stockart.description = '';
+        stockart.sourceUrl = '';
+        stockart.fileNumber = '';
+        scope.$digest();
+
+        expect(stockart.isPhotoDescriptionRequired).to.be.false;
+        expect(stockart.isPhotoURLRequired).to.be.false;
+        expect(stockart.isFileNumberRequired).to.be.false;
+      });
+    });
+
+
+    describe('sets the stockart required properties to false when all fields are', function() {
+      var stockart;
+
+      beforeEach(function() {
+        stockart = isolateScope.formStockarts[0];
+        stockart.description = 'test description';
+        stockart.sourceUrl = 'url.com';
+        stockart.fileNumber = '123';
+        scope.$digest();
+      });
+
+      afterEach(function() {
+        stockart = undefined;
+      });
+
+      it('sets the stockart required properties to true if any field is blank', function() {
+        // Reset stockart fields
+        stockart.description = '';
+        scope.$digest();
 
-    it('', function() {});
+        expect(stockart.isPhotoDescriptionRequired).to.be.true;
+        expect(stockart.isPhotoURLRequired).to.be.true;
+        expect(stockart.isFileNumberRequired).to.be.true;
+      });
+    });
   });
 });
diff --git a/app/directives/tc-input/tc-input.directive.js b/app/directives/tc-input/tc-input.directive.js
index ed5a47234..4848e1b33 100644
--- a/app/directives/tc-input/tc-input.directive.js
+++ b/app/directives/tc-input/tc-input.directive.js
@@ -10,6 +10,7 @@
       scope: {
         labelText: '@',
         asteriskText: '@',
+        showAsteriskText: '@',
         placeholder: '@',
         inputValue: '=',
         inputName: '@',
diff --git a/app/directives/tc-input/tc-input.jade b/app/directives/tc-input/tc-input.jade
index 6140698dd..5208c3309 100644
--- a/app/directives/tc-input/tc-input.jade
+++ b/app/directives/tc-input/tc-input.jade
@@ -1,15 +1,16 @@
 label.tc-label {{labelText}}
 
-p.tc-label__asterisk(ng-if="inputRequired") #[span *]{{asteriskText}}
+.tc-label__wrapper
+  p.tc-label__asterisk(ng-if="inputRequired && showAsteriskText") #[span *]{{asteriskText}}
 
-input(
-  name="{{inputName}}",
-  type="{{inputType}}",
-  placeholder="{{placeholder}}",
-  ng-model="inputValue",
-  ng-pattern="inputPattern",
-  ng-required="inputRequired",
-  ng-disabled="inputDisabled",
-  maxlength="{{maxlength}}",
-  ng-change="onChange()"
-)
+  input(
+    name="{{inputName}}",
+    type="{{inputType}}",
+    placeholder="{{placeholder}}",
+    ng-model="inputValue",
+    ng-pattern="inputPattern",
+    ng-required="inputRequired",
+    ng-disabled="inputDisabled",
+    maxlength="{{maxlength}}",
+    ng-change="onChange()"
+  )
diff --git a/app/directives/tc-input/tc-input.spec.js b/app/directives/tc-input/tc-input.spec.js
new file mode 100644
index 000000000..d7e481353
--- /dev/null
+++ b/app/directives/tc-input/tc-input.spec.js
@@ -0,0 +1,73 @@
+/* jshint -W117, -W030 */
+describe('Topcoder Input Directive', function() {
+  var scope, element;
+
+  beforeEach(function() {
+    bard.appModule('topcoder');
+    bard.inject(this, '$compile', '$rootScope');
+    scope = $rootScope.$new();
+
+    element = $compile(angular.element('<tc-input />'))(scope);
+    scope.$digest();
+  });
+
+  afterEach(function() {
+    scope.$destroy();
+  });
+
+  bard.verifyNoOutstandingHttpRequests();
+
+  it('should set inputType to text if no inputType given', function() {
+    var input = element.find('input')[0];
+
+    expect(input.type).to.equal('text');
+  });
+
+  it('should set inputType to specified inputType if given', function() {
+    element = $compile(angular.element('<tc-input input-type="number"/>'))(scope);
+    scope.$digest();
+
+    var input = element.find('input')[0];
+
+    expect(input.type).to.equal('number');
+  });
+
+  it ('should set the inputValue to the result of updateValueOnBlur when blur event is triggered', function() {
+    scope.updateValueOnBlur = function(inputValue) {
+      return 'new value and ' + inputValue;
+    };
+    scope.inputValue = 'old value';
+
+    element = $compile(angular.element('<tc-input input-value="inputValue" update-value-on-blur="updateValueOnBlur(inputValue)"/>'))(scope);
+    scope.$digest();
+
+    var input = element.find('input')[0];
+
+    expect(scope.inputValue).to.equal('old value');
+
+    $(input).trigger('blur');
+
+    expect(scope.inputValue).to.equal('new value and old value')
+
+  });
+
+  it('should pass inputValue and inputName to onInputChange when inputValue changes', function() {
+    scope.inputValue = 'test input value';
+    scope.onInputChange = function(inputValue, inputName) {
+      return;
+    };
+
+    element = $compile(angular.element('<tc-input input-value="inputValue" input-name="\'test input name\'" on-input-change="onInputChange(inputValue, inputName)"/>'))(scope);
+    scope.$digest();
+
+    var input = element.find('input')[0];
+    var mockOnInputChange = sinon.spy(scope, 'onInputChange');
+
+    expect(mockOnInputChange).not.calledOnce;
+
+    scope.inputValue = 'new test input value';
+    scope.$digest();
+
+    expect(mockOnInputChange).calledOnce;
+  });
+});
diff --git a/app/filters/filters.spec.js b/app/filters/filters.spec.js
index fc6a6d662..b3e2c9fc9 100644
--- a/app/filters/filters.spec.js
+++ b/app/filters/filters.spec.js
@@ -131,7 +131,6 @@ describe('filters', function() {
       expect(ternaryFilter(true, 1, 2)).to.be.equal(1);
       expect(ternaryFilter(false, 1, 2)).to.be.equal(2);
       expect(ternaryFilter(0, 1, 2)).to.be.equal(2);
-      console.log(jstz.determine().name());
       expect(ternaryFilter(true, 'm', 'n')).to.be.equal('m');
     });
   });
diff --git a/app/filters/npad.filter.js b/app/filters/npad.filter.js
index ad4081265..b6de18af5 100644
--- a/app/filters/npad.filter.js
+++ b/app/filters/npad.filter.js
@@ -13,7 +13,7 @@
       }
       if(inputStr.length >= n)
         return inputStr
-      var zeros = "0".repeat(n);
+      var zeros = new Array( n + 1 ).join("0");
       return (zeros + inputStr).slice(-1 * n)
     };
   }
diff --git a/app/filters/track.filter.js b/app/filters/track.filter.js
index 2508a4a23..41b2e2faa 100644
--- a/app/filters/track.filter.js
+++ b/app/filters/track.filter.js
@@ -9,7 +9,7 @@
 
       var map = {
         'UI_PROTOTYPE_COMPETITION': 'UI Prototype Competition',
-        'ASSEMBLY_COMPETITION': 'ASSEMBLY',
+        'ASSEMBLY_COMPETITION': 'Assembly',
         'RIA_BUILD_COMPETITION': 'RIA Build Competition',
         'RIA_COMPONENT_COMPETITION': 'RIA Component Competition',
         'DEVELOP_MARATHON_MATCH': 'Marathon Match',
diff --git a/app/index.jade b/app/index.jade
index c9ac97625..906c3de62 100644
--- a/app/index.jade
+++ b/app/index.jade
@@ -156,8 +156,6 @@ html
     script(src='../bower_components/react/react.js')
     script(src='../bower_components/react/react-dom.js')
     script(src='../bower_components/classnames/index.js')
-    script(src='../bower_components/classnames/bind.js')
-    script(src='../bower_components/classnames/dedupe.js')
     script(src='../bower_components/react-input-autosize/dist/react-input-autosize.min.js')
     script(src='../bower_components/react-select/dist/react-select.min.js')
     script(src='../bower_components/ngReact/ngReact.js')
@@ -335,7 +333,8 @@ html
     script(src="submissions/submissions.module.js")
     script(src="submissions/submissions.controller.js")
     script(src="submissions/submissions.routes.js")
-    script(src="submissions/submit-file/submit-file.controller.js")
+    script(src="submissions/submit-design-files/submit-design-files.controller.js")
+    script(src="submissions/submit-develop-files/submit-develop-files.controller.js")
     script(src="topcoder.constants.js")
     script(src="topcoder.controller.js")
     script(src="topcoder.interceptors.js")
diff --git a/app/my-dashboard/programs/programs.controller.js b/app/my-dashboard/programs/programs.controller.js
index b6cbe3aa7..cb192afb8 100644
--- a/app/my-dashboard/programs/programs.controller.js
+++ b/app/my-dashboard/programs/programs.controller.js
@@ -49,7 +49,6 @@
 
 
     function registerUser() {
-      debugger;
       vm.loading = true;
       return MemberCertService.registerMember(userId, CONSTANTS.SWIFT_PROGRAM_ID).then(function(data) {
         if (data && data.eventId && data.userId) {
diff --git a/app/profile/subtrack/design/design-challenges.jade b/app/profile/subtrack/design/design-challenges.jade
index 15a6bf01a..dc8b7260d 100644
--- a/app/profile/subtrack/design/design-challenges.jade
+++ b/app/profile/subtrack/design/design-challenges.jade
@@ -8,4 +8,4 @@
       .no-challenges(ng-show="!vm.challenges || vm.challenges.length == 0")
         | Sorry, no successful challenges found.
 
-      tc-endless-paginator(state="vm.status.challenges", page-params="vm.pageParams")
+    tc-endless-paginator(state="vm.status.challenges", page-params="vm.pageParams")
diff --git a/app/profile/subtrack/develop/develop-challenges.jade b/app/profile/subtrack/develop/develop-challenges.jade
index 6214ffc5b..da7a5154b 100644
--- a/app/profile/subtrack/develop/develop-challenges.jade
+++ b/app/profile/subtrack/develop/develop-challenges.jade
@@ -10,4 +10,4 @@
       .no-challenges(ng-show="!vm.challenges || vm.challenges.length == 0")
         | Sorry, no successful challenges found.
 
-      tc-endless-paginator(state="vm.status.challenges", page-params="vm.pageParams")
+    tc-endless-paginator(state="vm.status.challenges", page-params="vm.pageParams")
diff --git a/app/profile/subtrack/subtrack.controller.js b/app/profile/subtrack/subtrack.controller.js
index 7433df47d..e773453ad 100644
--- a/app/profile/subtrack/subtrack.controller.js
+++ b/app/profile/subtrack/subtrack.controller.js
@@ -25,7 +25,7 @@
     vm.back = back;
     vm.subTrackStats = [];
 
-    vm.pageName = vm.subTrack.toLowerCase().replace(/_/g, ' ');
+    vm.pageName = vm.subTrack;
 
     vm.tabs = ['statistics'];
 
diff --git a/app/profile/subtrack/subtrack.jade b/app/profile/subtrack/subtrack.jade
index 9e83453a2..a3f1bd945 100644
--- a/app/profile/subtrack/subtrack.jade
+++ b/app/profile/subtrack/subtrack.jade
@@ -1,5 +1,5 @@
 .profile-subtrack-container(ng-cloak, ng-show="profileVm.status.stats === 'ready'")
-  .content 
+  .content
     .page-header
       page-state-header(handle="{{vm.userHandle}}", page-title="{{vm.pageName}}", hide-money="true", show-back-link="true", default-state="profile")
         .nav-right
@@ -11,7 +11,7 @@
             .carousel-elem
               a.flex-wrapper(ng-class="{'link': item.link, 'no-link': !item.link}", ng-href="{{item.link}}")
                 p.title(ng-hide="true") {{item.subTrack | track}}
-                
+
                 p.value(ng-show="item.label === 'rating'", style="color: {{item.val |  ratingColor}}") {{item.val | empty}}
                   span(style="background-color: {{item.val | ratingColor}}")
 
@@ -22,20 +22,20 @@
         include ./develop/develop-statistics.jade
         include ./design/design-statistics.jade
         include ./data/data-statistics.jade
-        
+
       tc-tab(heading="{{vm.tabs[1]}}")
         .subtrack-stats
           responsive-carousel(data="vm.subTrackStats", handle="{{vm.handle}}")
             .carousel-elem
               .flex-wrapper
                 p.title(ng-hide="true") {{item.subTrack | track}}
-                
+
                 p.value(ng-show="item.label === 'rating'", style="color: {{item.val |  ratingColor}}") {{item.val | empty}}
                   span(style="background-color: {{item.val | ratingColor}}")
 
                 p.value(ng-hide="item.label === 'rating'") {{item.val | empty}}
 
                 p.label {{item.label}}
-        include ./develop/develop-challenges.jade    
-        include ./design/design-challenges.jade    
+        include ./develop/develop-challenges.jade
+        include ./design/design-challenges.jade
         include ./data/data-challenges.jade
diff --git a/app/services/challenge.service.js b/app/services/challenge.service.js
index eba8c1a3b..6d86d4628 100644
--- a/app/services/challenge.service.js
+++ b/app/services/challenge.service.js
@@ -80,7 +80,7 @@
 
               // if user has role of observer
               var roles = _.get(challenge, 'userDetails.roles', []);
-              if (roles.length > 0) {
+              if (roles && roles.length > 0) {
                 var submitterRole = _.findIndex(roles, function(role) {
                   var lRole = role.toLowerCase();
                   return lRole === 'submitter';
@@ -189,7 +189,8 @@
               return submission.type === CONSTANTS.SUBMISSION_TYPE_CONTEST && submission.placement;
             }), 'placement').placement;
           }
-          if (challenge.track === 'DEVELOP' && challenge.subTrack === 'FIRST_2_FINISH') {
+          if (challenge.track === 'DEVELOP' && challenge.subTrack === 'FIRST_2_FINISH'
+            && challenge.userDetails.submissions && challenge.userDetails.submissions.length > 0) {
             challenge.highestPlacement = _.min(challenge.userDetails.submissions.filter(function(submission) {
               return submission.type === CONSTANTS.SUBMISSION_TYPE_CONTEST
               && submission.status === CONSTANTS.STATUS_ACTIVE && submission.placement;
diff --git a/app/services/challenge.service.spec.js b/app/services/challenge.service.spec.js
index 142dd8786..1de3f7ec0 100644
--- a/app/services/challenge.service.spec.js
+++ b/app/services/challenge.service.spec.js
@@ -40,357 +40,762 @@ describe('Challenge Service', function() {
     $httpBackend.flush();
   });
 
-  it('processPastChallenges should process the won DESIGN/WEB_DESIGNS challenge ', function() {
-    var challenges = [
-      {
-        id: 30041345,
-        name: 'Mock Challenge 1',
-        track: 'DESIGN',
-        subTrack: 'WEB_DESIGNS',
-        userDetails: {
-          hasUserSubmittedForReview: true,
-          roles: ['Submitter'],
-          submissions: [
-            {
-              challengeId: 30041345,
-              id: 12345,
-              placement: 1,
-              score: 98.0,
-              status: 'Active',
-              type: 'Contest Submission'
-            },
+  describe('processActiveDevDesignChallenges ', function() {
+    it('should process the active DESIGN/WEB_DESIGNS challenge with submitter role ', function() {
+      var challenges = [
+        {
+          id: 30041345,
+          name: 'Mock Challenge 1',
+          track: 'DESIGN',
+          subTrack: 'WEB_DESIGNS',
+          currentPhases: [
             {
-              challengeId: 30041345,
-              id: 12346,
-              placement: 11,
-              score: 0.0,
-              status: 'Failed Review',
-              type: 'Contest Submission'
+              "challengeId": 30052661,
+              "id": 789719,
+              "phaseType": "Registration",
+              "phaseStatus": "Open",
+              "duration": 2419200000,
+              "scheduledStartTime": moment().add(5, 'days'),
+              "scheduledEndTime": moment().add(10, 'days'),
+              "actualStartTime": moment().add(5, 'days'),
+              "actualEndTime": null,
+              "fixedStartTime": "2016-01-15T18:00Z"
             },
             {
-              challengeId: 30041345,
-              id: 12347,
-              placement: 21,
-              score: 0.0,
-              status: 'Completed Without Win',
-              type: 'Checkpoint Submission'
+              "challengeId": 30052661,
+              "id": 789720,
+              "phaseType": "Submission",
+              "phaseStatus": "Open",
+              "duration": 2418900000,
+              "scheduledStartTime": moment().add(6, 'days'),
+              "scheduledEndTime": moment().add(20, 'days'),
+              "actualStartTime": moment().add(6, 'days'),
+              "actualEndTime": null,
+              "fixedStartTime": null
             }
-          ]
+          ],
+          userDetails: {
+            roles: [
+              "Submitter"
+            ],
+            hasUserSubmittedForReview: false,
+            submissionReviewScore: null,
+            winningPlacements: null,
+            submissions: null
+          }
         }
-      }
-    ];
-    ChallengeService.processPastChallenges(challenges);
-    var challenge = challenges[0];
-    expect(challenge.highestPlacement).to.exist.to.equal(1);
-    expect(challenge.wonFirst).to.exist.to.true;
-    expect(challenge.userStatus).to.exist.to.equal('PASSED_REVIEW');
-    expect(challenge.userHasSubmitterRole).to.exist.to.true;
-  });
+      ];
+      ChallengeService.processActiveDevDesignChallenges(challenges);
+      var challenge = challenges[0];
+      expect(challenge.userAction).to.exist.to.equal("Submit");
+      expect(challenge.userCurrentPhaseEndTime).to.exist.to.have.length(3);
+      expect(challenge.userCurrentPhaseEndTime[0]).to.exist.to.equal('10');
+      expect(challenge.userCurrentPhaseEndTime[1]).to.exist.to.equal('days');
+      expect(challenge.userCurrentPhase).to.exist.to.equal('Registration');
+    });
 
-  it('processPastChallenges should process the won DEVELOP/<ANY> challenge ', function() {
-    var challenges = [
-      {
-        id: 30041345,
-        name: 'Mock Challenge 1',
-        track: 'DEVELOP',
-        subTrack: 'CODE',
-        userDetails: {
-          hasUserSubmittedForReview: true,
-          roles: ['Submitter'],
-          submissions: [
-            {
-              challengeId: 30041345,
-              id: 12345,
-              placement: 1,
-              score: 98.0,
-              status: 'Active',
-              type: 'Contest Submission'
-            },
+    it('should process the active DESIGN/WEB_DESIGNS challenge with submitter role with 1 hour difference in end date ', function() {
+      var challenges = [
+        {
+          id: 30041345,
+          name: 'Mock Challenge 1',
+          track: 'DESIGN',
+          subTrack: 'WEB_DESIGNS',
+          currentPhases: [
             {
-              challengeId: 30041345,
-              id: 12346,
-              placement: 11,
-              score: 0.0,
-              status: 'Failed Review',
-              type: 'Contest Submission'
+              "challengeId": 30052661,
+              "id": 789719,
+              "phaseType": "Registration",
+              "phaseStatus": "Open",
+              "duration": 2419200000,
+              "scheduledStartTime": moment().subtract(5, 'days'),
+              "scheduledEndTime": moment().add(1, 'hours'),
+              "actualStartTime": moment().subtract(5, 'days'),
+              "actualEndTime": null,
+              "fixedStartTime": "2016-01-15T18:00Z"
             },
             {
-              challengeId: 30041345,
-              id: 12347,
-              placement: 21,
-              score: 0.0,
-              status: 'Completed Without Win',
-              type: 'Checkpoint Submission'
+              "challengeId": 30052661,
+              "id": 789720,
+              "phaseType": "Submission",
+              "phaseStatus": "Open",
+              "duration": 2418900000,
+              "scheduledStartTime": moment().add(6, 'days'),
+              "scheduledEndTime": moment().add(20, 'days'),
+              "actualStartTime": moment().add(6, 'days'),
+              "actualEndTime": null,
+              "fixedStartTime": null
             }
           ],
-          winningPlacements: [2, 11, 1]
+          userDetails: {
+            roles: [
+              "Submitter"
+            ],
+            hasUserSubmittedForReview: false,
+            submissionReviewScore: null,
+            winningPlacements: null,
+            submissions: null
+          }
         }
+      ];
+      ChallengeService.processActiveDevDesignChallenges(challenges);
+      var challenge = challenges[0];
+      expect(challenge.userAction).to.exist.to.equal("Submit");
+      expect(challenge.userCurrentPhaseEndTime).to.exist.to.have.length(3);
+      expect(challenge.userCurrentPhaseEndTime[0]).to.exist.to.equal('1');
+      expect(challenge.userCurrentPhaseEndTime[1]).to.exist.to.equal('hour');
+      expect(challenge.userCurrentPhase).to.exist.to.equal('Registration');
+    });
 
-      }
-    ];
-    ChallengeService.processPastChallenges(challenges);
-    var challenge = challenges[0];
-    expect(challenge.highestPlacement).to.exist.to.equal(1);
-    expect(challenge.wonFirst).to.exist.to.true;
-    expect(challenge.userStatus).to.exist.to.equal('PASSED_REVIEW');
-    expect(challenge.userHasSubmitterRole).to.exist.to.true;
-  });
-
-  it('processPastChallenges should process the lost DEVELOP/<ANY> challenge ', function() {
-    var challenges = [
-      {
-        id: 30041345,
-        name: 'Mock Challenge 1',
-        track: 'DEVELOP',
-        subTrack: 'CODE',
-        userDetails: {
-          hasUserSubmittedForReview: true,
-          roles: ['Submitter'],
-          submissions: [
-            {
-              challengeId: 30041345,
-              id: 12345,
-              placement: 1,
-              score: 98.0,
-              status: 'Active',
-              type: 'Contest Submission'
-            },
+    it('should process the active DESIGN/WEB_DESIGNS challenge without null role ', function() {
+      var challenges = [
+        {
+          id: 30041345,
+          name: 'Mock Challenge 1',
+          track: 'DESIGN',
+          subTrack: 'WEB_DESIGNS',
+          currentPhases: [
             {
-              challengeId: 30041345,
-              id: 12346,
-              placement: 11,
-              score: 0.0,
-              status: 'Failed Review',
-              type: 'Contest Submission'
+              "challengeId": 30052661,
+              "id": 789719,
+              "phaseType": "Registration",
+              "phaseStatus": "Open",
+              "duration": 2419200000,
+              "scheduledStartTime": moment().add(5, 'days'),
+              "scheduledEndTime": moment().add(10, 'days'),
+              "actualStartTime": moment().add(5, 'days'),
+              "actualEndTime": null,
+              "fixedStartTime": "2016-01-15T18:00Z"
             },
             {
-              challengeId: 30041345,
-              id: 12347,
-              placement: 21,
-              score: 0.0,
-              status: 'Completed Without Win',
-              type: 'Checkpoint Submission'
+              "challengeId": 30052661,
+              "id": 789720,
+              "phaseType": "Submission",
+              "phaseStatus": "Open",
+              "duration": 2418900000,
+              "scheduledStartTime": moment().add(6, 'days'),
+              "scheduledEndTime": moment().add(20, 'days'),
+              "actualStartTime": moment().add(6, 'days'),
+              "actualEndTime": null,
+              "fixedStartTime": null
             }
           ],
-          winningPlacements: [0]
+          userDetails: {
+            roles: null,
+            hasUserSubmittedForReview: false,
+            submissionReviewScore: null,
+            winningPlacements: null,
+            submissions: null
+          }
         }
+      ];
+      ChallengeService.processActiveDevDesignChallenges(challenges);
+      var challenge = challenges[0];
+      expect(challenge.userAction).to.exist.to.equal("Submit");
+      expect(challenge.userCurrentPhaseEndTime).to.exist.to.have.length(3);
+      expect(challenge.userCurrentPhaseEndTime[0]).to.exist.to.equal('10');
+      expect(challenge.userCurrentPhaseEndTime[1]).to.exist.to.equal('days');
+      expect(challenge.userCurrentPhase).to.exist.to.equal('Registration');
+    });
 
-      }
-    ];
-    ChallengeService.processPastChallenges(challenges);
-    var challenge = challenges[0];
-    expect(challenge.highestPlacement).not.to.exist;
-    expect(challenge.wonFirst).to.exist.to.false;
-    expect(challenge.userStatus).to.exist.to.equal('PASSED_SCREENING');
-    expect(challenge.userHasSubmitterRole).to.exist.to.true;
-  });
-
-  it('processPastChallenges should process the won DEVELOP/FIRST_2_FINISH challenge ', function() {
-    var challenges = [
-      {
-        id: 30041345,
-        name: 'Mock Challenge 1',
-        track: 'DEVELOP',
-        subTrack: 'FIRST_2_FINISH',
-        userDetails: {
-          hasUserSubmittedForReview: true,
-          roles: ['Submitter'],
-          submissions: [
-            {
-              challengeId: 30041345,
-              id: 12345,
-              placement: 1,
-              score: 98.0,
-              status: 'Active',
-              type: 'Contest Submission'
-            },
+    it('should process the active DESIGN/WEB_DESIGNS challenge without undefined role ', function() {
+      var challenges = [
+        {
+          id: 30041345,
+          name: 'Mock Challenge 1',
+          track: 'DESIGN',
+          subTrack: 'WEB_DESIGNS',
+          currentPhases: [
             {
-              challengeId: 30041345,
-              id: 12346,
-              placement: 11,
-              score: 0.0,
-              status: 'Failed Review',
-              type: 'Contest Submission'
+              "challengeId": 30052661,
+              "id": 789719,
+              "phaseType": "Registration",
+              "phaseStatus": "Open",
+              "duration": 2419200000,
+              "scheduledStartTime": moment().add(5, 'days'),
+              "scheduledEndTime": moment().add(10, 'days'),
+              "actualStartTime": moment().add(5, 'days'),
+              "actualEndTime": null,
+              "fixedStartTime": "2016-01-15T18:00Z"
             },
             {
-              challengeId: 30041345,
-              id: 12347,
-              placement: 21,
-              score: 0.0,
-              status: 'Completed Without Win',
-              type: 'Checkpoint Submission'
+              "challengeId": 30052661,
+              "id": 789720,
+              "phaseType": "Submission",
+              "phaseStatus": "Open",
+              "duration": 2418900000,
+              "scheduledStartTime": moment().add(6, 'days'),
+              "scheduledEndTime": moment().add(20, 'days'),
+              "actualStartTime": moment().add(6, 'days'),
+              "actualEndTime": null,
+              "fixedStartTime": null
             }
-          ]
+          ],
+          userDetails: {
+            hasUserSubmittedForReview: false,
+            submissionReviewScore: null,
+            winningPlacements: null,
+            submissions: null
+          }
         }
-      }
-    ];
-    ChallengeService.processPastChallenges(challenges);
-    var challenge = challenges[0];
-    expect(challenge.highestPlacement).to.exist.to.equal(1);
-    expect(challenge.wonFirst).to.exist.to.true;
-    expect(challenge.userStatus).to.exist.to.equal('PASSED_REVIEW');
-    expect(challenge.userHasSubmitterRole).to.exist.to.true;
-  });
+      ];
+      ChallengeService.processActiveDevDesignChallenges(challenges);
+      var challenge = challenges[0];
+      expect(challenge.userAction).to.exist.to.equal("Submit");
+      expect(challenge.userCurrentPhaseEndTime).to.exist.to.have.length(3);
+      expect(challenge.userCurrentPhaseEndTime[0]).to.exist.to.equal('10');
+      expect(challenge.userCurrentPhaseEndTime[1]).to.exist.to.equal('days');
+      expect(challenge.userCurrentPhase).to.exist.to.equal('Registration');
+    });
 
-  it('processPastChallenges should process the lost(without placement) DEVELOP/FIRST_2_FINISH challenge ', function() {
-    var challenges = [
-      {
-        id: 30041345,
-        name: 'Mock Challenge 1',
-        track: 'DEVELOP',
-        subTrack: 'FIRST_2_FINISH',
-        userDetails: {
-          hasUserSubmittedForReview: true,
-          roles: ['Submitter'],
-          submissions: [
+    it('should process the active DESIGN/WEB_DESIGNS challenge with submitter role and already submitted ', function() {
+      var challenges = [
+        {
+          id: 30041345,
+          name: 'Mock Challenge 1',
+          track: 'DESIGN',
+          subTrack: 'WEB_DESIGNS',
+          currentPhases: [
             {
-              challengeId: 30041345,
-              id: 12345,
-              placement: null,
-              score: 34.0,
-              status: 'Active',
-              type: 'Contest Submission'
+              "challengeId": 30052661,
+              "id": 789719,
+              "phaseType": "Registration",
+              "phaseStatus": "Open",
+              "duration": 2419200000,
+              "scheduledStartTime": moment().add(5, 'days'),
+              "scheduledEndTime": moment().add(10, 'days'),
+              "actualStartTime": moment().add(5, 'days'),
+              "actualEndTime": null,
+              "fixedStartTime": "2016-01-15T18:00Z"
             },
             {
-              challengeId: 30041345,
-              id: 12346,
-              placement: null,
-              score: 0.0,
-              status: 'Failed Review',
-              type: 'Contest Submission'
+              "challengeId": 30052661,
+              "id": 789720,
+              "phaseType": "Submission",
+              "phaseStatus": "Open",
+              "duration": 2418900000,
+              "scheduledStartTime": moment().add(6, 'days'),
+              "scheduledEndTime": moment().add(20, 'days'),
+              "actualStartTime": moment().add(6, 'days'),
+              "actualEndTime": null,
+              "fixedStartTime": null
+            }
+          ],
+          userDetails: {
+            roles: [
+              "Submitter"
+            ],
+            hasUserSubmittedForReview: true,
+            submissionReviewScore: null,
+            winningPlacements: null,
+            submissions: null
+          }
+        }
+      ];
+      ChallengeService.processActiveDevDesignChallenges(challenges);
+      var challenge = challenges[0];
+      expect(challenge.userAction).to.exist.to.equal("Submitted");
+      expect(challenge.userCurrentPhaseEndTime).to.exist.to.have.length(3);
+      expect(challenge.userCurrentPhaseEndTime[0]).to.exist.to.equal('20');
+      expect(challenge.userCurrentPhaseEndTime[1]).to.exist.to.equal('days');
+      expect(challenge.userCurrentPhase).to.exist.to.equal('Submission');
+    });
+
+    it('should process the active DESIGN/WEB_DESIGNS challenge with non submitter role ', function() {
+      var challenges = [
+        {
+          id: 30041345,
+          name: 'Mock Challenge 1',
+          track: 'DESIGN',
+          subTrack: 'WEB_DESIGNS',
+          currentPhases: [
+            {
+              "challengeId": 30052661,
+              "id": 789719,
+              "phaseType": "Registration",
+              "phaseStatus": "Open",
+              "duration": 2419200000,
+              "scheduledStartTime": moment().add(5, 'days'),
+              "scheduledEndTime": moment().add(10, 'days'),
+              "actualStartTime": moment().add(5, 'days'),
+              "actualEndTime": null,
+              "fixedStartTime": "2016-01-15T18:00Z"
             },
             {
-              challengeId: 30041345,
-              id: 12347,
-              placement: 1,
-              score: 0.0,
-              status: 'Completed Without Win',
-              type: 'Checkpoint Submission'
+              "challengeId": 30052661,
+              "id": 789720,
+              "phaseType": "Submission",
+              "phaseStatus": "Open",
+              "duration": 2418900000,
+              "scheduledStartTime": moment().add(6, 'days'),
+              "scheduledEndTime": moment().add(20, 'days'),
+              "actualStartTime": moment().add(6, 'days'),
+              "actualEndTime": null,
+              "fixedStartTime": null
             }
-          ]
+          ],
+          userDetails: {
+            roles: [
+              "Observer"
+            ],
+            hasUserSubmittedForReview: false,
+            submissionReviewScore: null,
+            winningPlacements: null,
+            submissions: null
+          }
         }
-      }
-    ];
-    ChallengeService.processPastChallenges(challenges);
-    var challenge = challenges[0];
-    expect(challenge.highestPlacement).not.to.exist;
-    expect(challenge.wonFirst).to.exist.to.false;
-    expect(challenge.userStatus).to.exist.to.equal('PASSED_SCREENING');
-    expect(challenge.userHasSubmitterRole).to.exist.to.true;
-  });
+      ];
+      ChallengeService.processActiveDevDesignChallenges(challenges);
+      var challenge = challenges[0];
+      expect(challenge.userAction).not.to.exist;
+      expect(challenge.userCurrentPhaseEndTime).to.exist.to.have.length(3);
+      expect(challenge.userCurrentPhaseEndTime[0]).to.exist.to.equal('10');
+      expect(challenge.userCurrentPhaseEndTime[1]).to.exist.to.equal('days');
+      expect(challenge.userCurrentPhase).to.exist.to.equal('Registration');
+    });
 
-  it('processPastChallenges should process the lost(with placement) DEVELOP/FIRST_2_FINISH challenge ', function() {
-    var challenges = [
-      {
-        id: 30041345,
-        name: 'Mock Challenge 1',
-        track: 'DEVELOP',
-        subTrack: 'FIRST_2_FINISH',
-        userDetails: {
-          hasUserSubmittedForReview: true,
-          roles: ['Submitter'],
-          submissions: [
+    it('should process the active DESIGN/WEB_DESIGNS challenge with non submitter role ', function() {
+      var challenges = [
+        {
+          id: 30041345,
+          name: 'Mock Challenge 1',
+          track: 'DESIGN',
+          subTrack: 'WEB_DESIGNS',
+          currentPhases: [
             {
-              challengeId: 30041345,
-              id: 12345,
-              placement: 5,
-              score: 34.0,
-              status: 'Active',
-              type: 'Contest Submission'
+              "challengeId": 30052661,
+              "id": 789719,
+              "phaseType": "Registration",
+              "phaseStatus": "Open",
+              "duration": 2419200000,
+              "scheduledStartTime": moment().add(5, 'days'),
+              "scheduledEndTime": moment().add(10, 'days'),
+              "actualStartTime": moment().add(5, 'days'),
+              "actualEndTime": null,
+              "fixedStartTime": "2016-01-15T18:00Z"
             },
             {
-              challengeId: 30041345,
-              id: 12346,
-              placement: null,
-              score: 0.0,
-              status: 'Failed Review',
-              type: 'Contest Submission'
+              "challengeId": 30052661,
+              "id": 789720,
+              "phaseType": "Submission",
+              "phaseStatus": "Open",
+              "duration": 2418900000,
+              "scheduledStartTime": moment().add(6, 'days'),
+              "scheduledEndTime": moment().add(20, 'days'),
+              "actualStartTime": moment().add(6, 'days'),
+              "actualEndTime": null,
+              "fixedStartTime": null
             },
             {
-              challengeId: 30041345,
-              id: 12347,
-              placement: 1,
-              score: 0.0,
-              status: 'Completed Without Win',
-              type: 'Checkpoint Submission'
+              "challengeId": 30052661,
+              "id": 789720,
+              "phaseType": "Review",
+              "phaseStatus": "Open",
+              "duration": 2418900000,
+              "scheduledStartTime": moment().add(20, 'days'),
+              "scheduledEndTime": moment().add(22, 'days'),
+              "actualStartTime": moment().add(20, 'days'),
+              "actualEndTime": null,
+              "fixedStartTime": null
             }
-          ]
+          ],
+          userDetails: {
+            roles: [
+              "Submitter"
+            ],
+            hasUserSubmittedForReview: true,
+            submissionReviewScore: null,
+            winningPlacements: null,
+            submissions: null
+          }
         }
-      }
-    ];
-    ChallengeService.processPastChallenges(challenges);
-    var challenge = challenges[0];
-    expect(challenge.highestPlacement).to.exist.to.equal(5);
-    expect(challenge.wonFirst).to.exist.to.false;
-    expect(challenge.userStatus).to.exist.to.equal('PASSED_REVIEW');
-    expect(challenge.userHasSubmitterRole).to.exist.to.true;
+      ];
+      ChallengeService.processActiveDevDesignChallenges(challenges);
+      var challenge = challenges[0];
+      expect(challenge.userAction).not.to.exist;
+      expect(challenge.userCurrentPhaseEndTime).to.exist.to.have.length(3);
+      expect(challenge.userCurrentPhaseEndTime[0]).to.exist.to.equal('22');
+      expect(challenge.userCurrentPhaseEndTime[1]).to.exist.to.equal('days');
+      expect(challenge.userCurrentPhase).to.exist.to.equal('Review');
+    });
   });
 
-  it('processPastChallenges should process a not completed DEVELOP/FIRST_2_FINISH challenge ', function() {
-    var challenges = [
-      {
-        id: 30041345,
-        name: 'Mock Challenge 1',
-        track: 'DEVELOP',
-        subTrack: 'FIRST_2_FINISH',
-        userDetails: {
-          hasUserSubmittedForReview: false,
-          roles: ['Submitter'],
-          submissions: []
+  describe('processPastChallenges ', function() {
+    it('should process the won DESIGN/WEB_DESIGNS challenge ', function() {
+      var challenges = [
+        {
+          id: 30041345,
+          name: 'Mock Challenge 1',
+          track: 'DESIGN',
+          subTrack: 'WEB_DESIGNS',
+          userDetails: {
+            hasUserSubmittedForReview: true,
+            roles: ['Submitter'],
+            submissions: [
+              {
+                challengeId: 30041345,
+                id: 12345,
+                placement: 1,
+                score: 98.0,
+                status: 'Active',
+                type: 'Contest Submission'
+              },
+              {
+                challengeId: 30041345,
+                id: 12346,
+                placement: 11,
+                score: 0.0,
+                status: 'Failed Review',
+                type: 'Contest Submission'
+              },
+              {
+                challengeId: 30041345,
+                id: 12347,
+                placement: 21,
+                score: 0.0,
+                status: 'Completed Without Win',
+                type: 'Checkpoint Submission'
+              }
+            ]
+          }
         }
-      }
-    ];
-    ChallengeService.processPastChallenges(challenges);
-    var challenge = challenges[0];
-    expect(challenge.highestPlacement).not.to.exist;
-    expect(challenge.wonFirst).to.exist.to.false;
-    expect(challenge.userStatus).to.exist.to.equal('NOT_FINISHED');
-    expect(challenge.userHasSubmitterRole).to.exist.to.true;
-  });
+      ];
+      ChallengeService.processPastChallenges(challenges);
+      var challenge = challenges[0];
+      expect(challenge.highestPlacement).to.exist.to.equal(1);
+      expect(challenge.wonFirst).to.exist.to.true;
+      expect(challenge.userStatus).to.exist.to.equal('PASSED_REVIEW');
+      expect(challenge.userHasSubmitterRole).to.exist.to.true;
+    });
+
+    it('should process the won DEVELOP/<ANY> challenge ', function() {
+      var challenges = [
+        {
+          id: 30041345,
+          name: 'Mock Challenge 1',
+          track: 'DEVELOP',
+          subTrack: 'CODE',
+          userDetails: {
+            hasUserSubmittedForReview: true,
+            roles: ['Submitter'],
+            submissions: [
+              {
+                challengeId: 30041345,
+                id: 12345,
+                placement: 1,
+                score: 98.0,
+                status: 'Active',
+                type: 'Contest Submission'
+              },
+              {
+                challengeId: 30041345,
+                id: 12346,
+                placement: 11,
+                score: 0.0,
+                status: 'Failed Review',
+                type: 'Contest Submission'
+              },
+              {
+                challengeId: 30041345,
+                id: 12347,
+                placement: 21,
+                score: 0.0,
+                status: 'Completed Without Win',
+                type: 'Checkpoint Submission'
+              }
+            ],
+            winningPlacements: [2, 11, 1]
+          }
 
-  it('processPastChallenges should process a DEVELOP/FIRST_2_FINISH challenge for a non submitter user  ', function() {
-    var challenges = [
-      {
-        id: 30041345,
-        name: 'Mock Challenge 1',
-        track: 'DEVELOP',
-        subTrack: 'FIRST_2_FINISH',
-        userDetails: {
-          hasUserSubmittedForReview: false,
-          roles: ['Observer'],
-          submissions: []
         }
-      }
-    ];
-    ChallengeService.processPastChallenges(challenges);
-    var challenge = challenges[0];
-    expect(challenge.highestPlacement).not.to.exist;
-    expect(challenge.wonFirst).to.exist.to.false;
-    expect(challenge.userStatus).to.exist.to.equal('COMPLETED');
-    expect(challenge.userHasSubmitterRole).to.exist.to.false;
-  });
+      ];
+      ChallengeService.processPastChallenges(challenges);
+      var challenge = challenges[0];
+      expect(challenge.highestPlacement).to.exist.to.equal(1);
+      expect(challenge.wonFirst).to.exist.to.true;
+      expect(challenge.userStatus).to.exist.to.equal('PASSED_REVIEW');
+      expect(challenge.userHasSubmitterRole).to.exist.to.true;
+    });
+
+    it('should process the lost DEVELOP/<ANY> challenge ', function() {
+      var challenges = [
+        {
+          id: 30041345,
+          name: 'Mock Challenge 1',
+          track: 'DEVELOP',
+          subTrack: 'CODE',
+          userDetails: {
+            hasUserSubmittedForReview: true,
+            roles: ['Submitter'],
+            submissions: [
+              {
+                challengeId: 30041345,
+                id: 12345,
+                placement: 1,
+                score: 98.0,
+                status: 'Active',
+                type: 'Contest Submission'
+              },
+              {
+                challengeId: 30041345,
+                id: 12346,
+                placement: 11,
+                score: 0.0,
+                status: 'Failed Review',
+                type: 'Contest Submission'
+              },
+              {
+                challengeId: 30041345,
+                id: 12347,
+                placement: 21,
+                score: 0.0,
+                status: 'Completed Without Win',
+                type: 'Checkpoint Submission'
+              }
+            ],
+            winningPlacements: [0]
+          }
+
+        }
+      ];
+      ChallengeService.processPastChallenges(challenges);
+      var challenge = challenges[0];
+      expect(challenge.highestPlacement).not.to.exist;
+      expect(challenge.wonFirst).to.exist.to.false;
+      expect(challenge.userStatus).to.exist.to.equal('PASSED_SCREENING');
+      expect(challenge.userHasSubmitterRole).to.exist.to.true;
+    });
+
+    it('should process the won DEVELOP/FIRST_2_FINISH challenge ', function() {
+      var challenges = [
+        {
+          id: 30041345,
+          name: 'Mock Challenge 1',
+          track: 'DEVELOP',
+          subTrack: 'FIRST_2_FINISH',
+          userDetails: {
+            hasUserSubmittedForReview: true,
+            roles: ['Submitter'],
+            submissions: [
+              {
+                challengeId: 30041345,
+                id: 12345,
+                placement: 1,
+                score: 98.0,
+                status: 'Active',
+                type: 'Contest Submission'
+              },
+              {
+                challengeId: 30041345,
+                id: 12346,
+                placement: 11,
+                score: 0.0,
+                status: 'Failed Review',
+                type: 'Contest Submission'
+              },
+              {
+                challengeId: 30041345,
+                id: 12347,
+                placement: 21,
+                score: 0.0,
+                status: 'Completed Without Win',
+                type: 'Checkpoint Submission'
+              }
+            ]
+          }
+        }
+      ];
+      ChallengeService.processPastChallenges(challenges);
+      var challenge = challenges[0];
+      expect(challenge.highestPlacement).to.exist.to.equal(1);
+      expect(challenge.wonFirst).to.exist.to.true;
+      expect(challenge.userStatus).to.exist.to.equal('PASSED_REVIEW');
+      expect(challenge.userHasSubmitterRole).to.exist.to.true;
+    });
+
+    it('should process the lost(without placement) DEVELOP/FIRST_2_FINISH challenge ', function() {
+      var challenges = [
+        {
+          id: 30041345,
+          name: 'Mock Challenge 1',
+          track: 'DEVELOP',
+          subTrack: 'FIRST_2_FINISH',
+          userDetails: {
+            hasUserSubmittedForReview: true,
+            roles: ['Submitter'],
+            submissions: [
+              {
+                challengeId: 30041345,
+                id: 12345,
+                placement: null,
+                score: 34.0,
+                status: 'Active',
+                type: 'Contest Submission'
+              },
+              {
+                challengeId: 30041345,
+                id: 12346,
+                placement: null,
+                score: 0.0,
+                status: 'Failed Review',
+                type: 'Contest Submission'
+              },
+              {
+                challengeId: 30041345,
+                id: 12347,
+                placement: 1,
+                score: 0.0,
+                status: 'Completed Without Win',
+                type: 'Checkpoint Submission'
+              }
+            ]
+          }
+        }
+      ];
+      ChallengeService.processPastChallenges(challenges);
+      var challenge = challenges[0];
+      expect(challenge.highestPlacement).not.to.exist;
+      expect(challenge.wonFirst).to.exist.to.false;
+      expect(challenge.userStatus).to.exist.to.equal('PASSED_SCREENING');
+      expect(challenge.userHasSubmitterRole).to.exist.to.true;
+    });
 
-  it('processPastChallenges should process a DEVELOP/<ANY> challenge for a user without role  ', function() {
-    var challenges = [
-      {
-        id: 30041345,
-        name: 'Mock Challenge 1',
-        track: 'DEVELOP',
-        subTrack: 'FIRST_2_FINISH',
-        userDetails: {
-          hasUserSubmittedForReview: false,
-          roles: [],
-          submissions: [],
-          winningPlacements: [0]
+    it('should process the lost(with placement) DEVELOP/FIRST_2_FINISH challenge ', function() {
+      var challenges = [
+        {
+          id: 30041345,
+          name: 'Mock Challenge 1',
+          track: 'DEVELOP',
+          subTrack: 'FIRST_2_FINISH',
+          userDetails: {
+            hasUserSubmittedForReview: true,
+            roles: ['Submitter'],
+            submissions: [
+              {
+                challengeId: 30041345,
+                id: 12345,
+                placement: 5,
+                score: 34.0,
+                status: 'Active',
+                type: 'Contest Submission'
+              },
+              {
+                challengeId: 30041345,
+                id: 12346,
+                placement: null,
+                score: 0.0,
+                status: 'Failed Review',
+                type: 'Contest Submission'
+              },
+              {
+                challengeId: 30041345,
+                id: 12347,
+                placement: 1,
+                score: 0.0,
+                status: 'Completed Without Win',
+                type: 'Checkpoint Submission'
+              }
+            ]
+          }
+        }
+      ];
+      ChallengeService.processPastChallenges(challenges);
+      var challenge = challenges[0];
+      expect(challenge.highestPlacement).to.exist.to.equal(5);
+      expect(challenge.wonFirst).to.exist.to.false;
+      expect(challenge.userStatus).to.exist.to.equal('PASSED_REVIEW');
+      expect(challenge.userHasSubmitterRole).to.exist.to.true;
+    });
+
+    it('should process a not completed (empty submissions) DEVELOP/FIRST_2_FINISH challenge ', function() {
+      var challenges = [
+        {
+          id: 30041345,
+          name: 'Mock Challenge 1',
+          track: 'DEVELOP',
+          subTrack: 'FIRST_2_FINISH',
+          userDetails: {
+            hasUserSubmittedForReview: false,
+            roles: ['Submitter'],
+            submissions: []
+          }
+        }
+      ];
+      ChallengeService.processPastChallenges(challenges);
+      var challenge = challenges[0];
+      expect(challenge.highestPlacement).not.to.exist;
+      expect(challenge.wonFirst).to.exist.to.false;
+      expect(challenge.userStatus).to.exist.to.equal('NOT_FINISHED');
+      expect(challenge.userHasSubmitterRole).to.exist.to.true;
+    });
+
+    it('should process a not completed(null submissions) DEVELOP/FIRST_2_FINISH challenge ', function() {
+      var challenges = [
+        {
+          id: 30041345,
+          name: 'Mock Challenge 1',
+          track: 'DEVELOP',
+          subTrack: 'FIRST_2_FINISH',
+          userDetails: {
+            hasUserSubmittedForReview: false,
+            roles: ['Submitter'],
+            submissions: null
+          }
         }
-      }
-    ];
-    ChallengeService.processPastChallenges(challenges);
-    var challenge = challenges[0];
-    expect(challenge.highestPlacement).not.to.exist;
-    expect(challenge.wonFirst).to.exist.to.false;
-    expect(challenge.userStatus).to.exist.to.equal('COMPLETED');
-    expect(challenge.userHasSubmitterRole).to.exist.to.false;
+      ];
+      ChallengeService.processPastChallenges(challenges);
+      var challenge = challenges[0];
+      expect(challenge.highestPlacement).not.to.exist;
+      expect(challenge.wonFirst).to.exist.to.false;
+      expect(challenge.userStatus).to.exist.to.equal('NOT_FINISHED');
+      expect(challenge.userHasSubmitterRole).to.exist.to.true;
+    });
+
+    it('should process a DEVELOP/FIRST_2_FINISH challenge for a non submitter user  ', function() {
+      var challenges = [
+        {
+          id: 30041345,
+          name: 'Mock Challenge 1',
+          track: 'DEVELOP',
+          subTrack: 'FIRST_2_FINISH',
+          userDetails: {
+            hasUserSubmittedForReview: false,
+            roles: ['Observer'],
+            submissions: []
+          }
+        }
+      ];
+      ChallengeService.processPastChallenges(challenges);
+      var challenge = challenges[0];
+      expect(challenge.highestPlacement).not.to.exist;
+      expect(challenge.wonFirst).to.exist.to.false;
+      expect(challenge.userStatus).to.exist.to.equal('COMPLETED');
+      expect(challenge.userHasSubmitterRole).to.exist.to.false;
+    });
+
+    it('should process a DEVELOP/<ANY> challenge for a user without role  ', function() {
+      var challenges = [
+        {
+          id: 30041345,
+          name: 'Mock Challenge 1',
+          track: 'DEVELOP',
+          subTrack: 'FIRST_2_FINISH',
+          userDetails: {
+            hasUserSubmittedForReview: false,
+            roles: [],
+            submissions: [],
+            winningPlacements: [0]
+          }
+        }
+      ];
+      ChallengeService.processPastChallenges(challenges);
+      var challenge = challenges[0];
+      expect(challenge.highestPlacement).not.to.exist;
+      expect(challenge.wonFirst).to.exist.to.false;
+      expect(challenge.userStatus).to.exist.to.equal('COMPLETED');
+      expect(challenge.userHasSubmitterRole).to.exist.to.false;
+    });
+
   });
 
   it('processPastSRM should process SRM with valid placement  ', function() {
diff --git a/app/services/communityData.service.js b/app/services/communityData.service.js
index ee3dd03c6..75050d1d7 100644
--- a/app/services/communityData.service.js
+++ b/app/services/communityData.service.js
@@ -19,28 +19,28 @@
       var data = {
         "memberLeaderboard": [
         {
-          "avatar": "//www.topcoder.com/wp-content/uploads/2015/05/abedavera.jpg",
-          "name": "abedavera",
+          "avatar": "//www.topcoder.com/wp-content/uploads/2015/05/nexttopdesigns_dec2015.png",
+          "name": "nexttopdesigns",
           "contestType": "Design",
-          "description": "TCO15 Design Champion!",
+          "description": "Ten wins earning over $10K in design challenges",
           "class": "design"
         }, {
-          "avatar": "//www.topcoder.com/wp-content/uploads/2015/05/bonton.jpg",
-          "name": "bonton",
+          "avatar": "//www.topcoder.com/wp-content/uploads/2015/05/seriyvolk83_dec2015.png",
+          "name": "seriyvolk83",
           "contestType": "Development",
-          "description": "Won $4200 with 4 wins in development challenges",
+          "description": "Six wins earning over $4K in development challenges",
           "class": "develop"
         }, {
-          "avatar": "//www.topcoder.com/wp-content/uploads/2015/05/maksay.jpg",
-          "name": "maksay",
+          "avatar": "//www.topcoder.com/wp-content/uploads/2015/05/sadhwaniyash6_dec2015.png",
+          "name": "sadhwaniyash6",
           "contestType": "Data Science",
-          "description": "Only participant to see a ratings increase in all four rounds in Sept.",
+          "description": "Rating increase of 627 pts in Oct SRMs vaulting into Div 1.",
           "class": "data-science"
         }, {
-          "avatar": "https://www.topcoder.com/wp-content/uploads/2015/05/herlansyahs.jpg",
-          "name": "herlansyahs",
+          "avatar": "https://www.topcoder.com/wp-content/uploads/2015/05/alyad_dec2015.png",
+          "name": "Alyad",
           "contestType": "Design Rookie",
-          "description": "Won first and second placements within his first two months of becoming a member!",
+          "description": "Won 2 challenges within 6 weeks of becoming a member!",
           "class": "design"
         }],
         "copilots": [{
diff --git a/app/services/helpers.service.js b/app/services/helpers.service.js
index d04ace38f..04da0a983 100644
--- a/app/services/helpers.service.js
+++ b/app/services/helpers.service.js
@@ -156,7 +156,7 @@
           answer: '' + q.answer
         };
 
-        if (q.comment.length > 0) {
+        if (q.comment && q.comment.length > 0) {
           reviewItem.comments = [
             {
               content: '' + q.comment,
@@ -234,7 +234,7 @@
         });
         // now loop over all keys and replace with compiled value
         Object.keys(compiledMap).forEach(function(k) {
-          template = template.replace(k, compiledMap[k])
+          template = template.replace(k, (compiledMap[k] ? compiledMap[k] : ''));
         });
       }
       return template;
@@ -298,8 +298,8 @@
     }
 
     function setupLoginEventMetrics (usernameOrEmail) {
-      if (_kmq) {
-        _kmq.push(['identify', usernameOrEmail ]);
+      if ($window._kmq) {
+        $window._kmq.push(['identify', usernameOrEmail ]);
       }
     }
 
diff --git a/app/services/helpers.service.spec.js b/app/services/helpers.service.spec.js
index 9c797c34a..ea9e2e550 100644
--- a/app/services/helpers.service.spec.js
+++ b/app/services/helpers.service.spec.js
@@ -4,6 +4,9 @@ describe('Helper Service', function() {
   var fakeWindow = {
     location: {
       href: "/"
+    },
+    decodeURIComponent: function(param) {
+      return decodeURIComponent(param);
     }
   };
   // sinon.spy(fakeWindow.location, "href");
@@ -19,13 +22,13 @@ describe('Helper Service', function() {
   sinon.spy(fakeState, "go");
 
   beforeEach(function() {
-    module('tc.services', function($provide) {
+    module('topcoder', function($provide) {
       $provide.value('$window', fakeWindow);
       $provide.value('$state', fakeState);
       $provide.value('$location', fakeLocation);
     });
 
-    bard.inject(this, 'Helpers', '$state', '$location');
+    bard.inject(this, 'Helpers', '$rootScope', '$state', '$location', '$window', '$httpBackend');
   });
 
   describe("isEmail()", function() {
@@ -54,4 +57,358 @@ describe('Helper Service', function() {
       expect($location.url).to.have.been.calledWith("/members/test1/");
     });
   });
+
+  describe("getSocialUserData()", function() {
+    var mockProfile;
+    beforeEach(function() {
+      mockProfile = mockData.getMockAuth0Profile();
+    });
+    it("should get JSON for facebook user data ", function() {
+      mockProfile.identities[0].connection = 'facebook'; 
+      var socialData = Helpers.getSocialUserData(mockProfile, "");
+      expect(socialData).to.exist.not.null;
+      expect(socialData.socialUserId).to.exist.to.equal('123456');
+      // TODO cross check population of username for all networks
+      expect(socialData.username).to.exist.to.equal(mockProfile.first_name + '.' + mockProfile.last_name);
+      expect(socialData.firstname).to.exist.to.equal(mockProfile.first_name);
+      expect(socialData.lastname).to.exist.to.equal(mockProfile.last_name);
+      expect(socialData.email).to.exist.to.equal(mockProfile.email);
+      expect(socialData.socialProvider).to.exist.to.equal('facebook');
+      expect(socialData.accessToken).to.exist.to.equal(mockProfile.identities[0].access_token);
+      expect(socialData.accessTokenSecret).to.exist.to.equal(mockProfile.identities[0].access_token_secret);
+    });
+
+    it("should get JSON for github user data ", function() {
+      mockProfile.identities[0].connection = 'github'; 
+      var socialData = Helpers.getSocialUserData(mockProfile, "");
+      expect(socialData).to.exist.not.null;
+      expect(socialData.socialUserId).to.exist.to.equal('123456');
+      // TODO cross check population of username for all networks
+      expect(socialData.username).to.exist.to.equal(mockProfile.nickname);
+      expect(socialData.firstname).to.exist.to.equal(mockProfile.first_name);
+      expect(socialData.lastname).to.exist.to.equal(mockProfile.last_name);
+      expect(socialData.email).to.exist.to.equal(mockProfile.email);
+      expect(socialData.socialProvider).to.exist.to.equal('github');
+      expect(socialData.accessToken).to.exist.to.equal(mockProfile.identities[0].access_token);
+      expect(socialData.accessTokenSecret).to.exist.to.equal(mockProfile.identities[0].access_token_secret);
+    });
+
+    it("should get JSON for github user data without lastname ", function() {
+      mockProfile.identities[0].connection = 'github'; 
+      mockProfile.name = 'mock';
+      var socialData = Helpers.getSocialUserData(mockProfile, "");
+      expect(socialData).to.exist.not.null;
+      expect(socialData.socialUserId).to.exist.to.equal('123456');
+      // TODO cross check population of username for all networks
+      expect(socialData.username).to.exist.to.equal(mockProfile.nickname);
+      expect(socialData.firstname).to.exist.to.equal(mockProfile.first_name);
+      expect(socialData.lastname).to.exist.to.equal('');
+      expect(socialData.email).to.exist.to.equal(mockProfile.email);
+      expect(socialData.socialProvider).to.exist.to.equal('github');
+      expect(socialData.accessToken).to.exist.to.equal(mockProfile.identities[0].access_token);
+      expect(socialData.accessTokenSecret).to.exist.to.equal(mockProfile.identities[0].access_token_secret);
+    });
+
+    it("should get JSON for bitbucket user data ", function() {
+      mockProfile.identities[0].connection = 'bitbucket'; 
+      var socialData = Helpers.getSocialUserData(mockProfile, "");
+      expect(socialData).to.exist.not.null;
+      expect(socialData.socialUserId).to.exist.to.equal('123456');
+      // TODO cross check population of username for all networks
+      expect(socialData.username).to.exist.to.equal(mockProfile.username);
+      expect(socialData.firstname).to.exist.to.equal(mockProfile.first_name);
+      expect(socialData.lastname).to.exist.to.equal(mockProfile.last_name);
+      expect(socialData.email).to.exist.to.equal(mockProfile.email);
+      expect(socialData.socialProvider).to.exist.to.equal('bitbucket');
+      expect(socialData.accessToken).to.exist.to.equal(mockProfile.identities[0].access_token);
+      expect(socialData.accessTokenSecret).to.exist.to.equal(mockProfile.identities[0].access_token_secret);
+    });
+
+    it("should get JSON for stackoverflow user data ", function() {
+      mockProfile.identities[0].connection = 'stackoverflow'; 
+      var socialData = Helpers.getSocialUserData(mockProfile, "");
+      expect(socialData).to.exist.not.null;
+      expect(socialData.socialUserId).to.exist.to.equal('123456');
+      // TODO cross check population of username for all networks
+      expect(socialData.username).to.exist.to.equal('123456');
+      expect(socialData.firstname).to.exist.to.equal(mockProfile.first_name);
+      expect(socialData.lastname).to.exist.to.equal(mockProfile.last_name);
+      expect(socialData.email).to.exist.to.equal(mockProfile.email);
+      expect(socialData.socialProvider).to.exist.to.equal('stackoverflow');
+      expect(socialData.accessToken).to.exist.to.equal(mockProfile.identities[0].access_token);
+      expect(socialData.accessTokenSecret).to.exist.to.equal(mockProfile.identities[0].access_token_secret);
+    });
+
+    it("should get JSON for dribbble user data ", function() {
+      mockProfile.identities[0].connection = 'dribbble'; 
+      var socialData = Helpers.getSocialUserData(mockProfile, "");
+      expect(socialData).to.exist.not.null;
+      expect(socialData.socialUserId).to.exist.to.equal('123456');
+      // TODO cross check population of username for all networks
+      expect(socialData.username).to.exist.to.equal('123456');
+      expect(socialData.firstname).to.exist.to.equal(mockProfile.first_name);
+      expect(socialData.lastname).to.exist.to.equal(mockProfile.last_name);
+      expect(socialData.email).to.exist.to.equal(mockProfile.email);
+      expect(socialData.socialProvider).to.exist.to.equal('dribbble');
+      expect(socialData.accessToken).to.exist.to.equal(mockProfile.identities[0].access_token);
+      expect(socialData.accessTokenSecret).to.exist.to.equal(mockProfile.identities[0].access_token_secret);
+    });
+
+    it("should get JSON for twitter user data ", function() {
+      mockProfile.identities[0].connection = 'twitter'; 
+      mockProfile.screen_name = mockProfile.username;
+      var socialData = Helpers.getSocialUserData(mockProfile, "");
+      expect(socialData).to.exist.not.null;
+      expect(socialData.socialUserId).to.exist.to.equal('123456');
+      // TODO cross check population of username for all networks
+      expect(socialData.username).to.exist.to.equal(mockProfile.username);
+      expect(socialData.firstname).to.exist.to.equal(mockProfile.first_name);
+      expect(socialData.lastname).to.exist.to.equal(mockProfile.last_name);
+      // Twitter does not give email
+      expect(socialData.email).to.exist.to.equal('');
+      expect(socialData.socialProvider).to.exist.to.equal('twitter');
+      expect(socialData.accessToken).to.exist.to.equal(mockProfile.identities[0].access_token);
+      expect(socialData.accessTokenSecret).to.exist.to.equal(mockProfile.identities[0].access_token_secret);
+    });
+
+    it("should get JSON for twitter user data without lastname ", function() {
+      mockProfile.identities[0].connection = 'twitter'; 
+      mockProfile.name = 'mock';
+      mockProfile.screen_name = mockProfile.username;
+      var socialData = Helpers.getSocialUserData(mockProfile, "");
+      expect(socialData).to.exist.not.null;
+      expect(socialData.socialUserId).to.exist.to.equal('123456');
+      // TODO cross check population of username for all networks
+      expect(socialData.username).to.exist.to.equal(mockProfile.username);
+      expect(socialData.firstname).to.exist.to.equal(mockProfile.first_name);
+      expect(socialData.lastname).to.exist.to.equal('');
+      // Twitter does not give email
+      expect(socialData.email).to.exist.to.equal('');
+      expect(socialData.socialProvider).to.exist.to.equal('twitter');
+      expect(socialData.accessToken).to.exist.to.equal(mockProfile.identities[0].access_token);
+      expect(socialData.accessTokenSecret).to.exist.to.equal(mockProfile.identities[0].access_token_secret);
+    });
+
+    it("should get JSON for google-oauth2 user data ", function() {
+      mockProfile.identities[0].connection = 'google-oauth2'; 
+      var socialData = Helpers.getSocialUserData(mockProfile, "");
+      expect(socialData).to.exist.not.null;
+      expect(socialData.socialUserId).to.exist.to.equal('123456');
+      // TODO cross check population of username for all networks
+      expect(socialData.username).to.exist.to.equal(mockProfile.nickname);
+      expect(socialData.firstname).to.exist.to.equal(mockProfile.first_name);
+      expect(socialData.lastname).to.exist.to.equal(mockProfile.last_name);
+      expect(socialData.email).to.exist.to.equal(mockProfile.email);
+      expect(socialData.socialProvider).to.exist.to.equal('google-oauth2');
+      expect(socialData.accessToken).to.exist.to.equal(mockProfile.identities[0].access_token);
+      expect(socialData.accessTokenSecret).to.exist.to.equal(mockProfile.identities[0].access_token_secret);
+    });
+  });
+
+  describe("getPageTitle()", function() {
+
+    it("should get page title from state ", function() {
+      var state = { data: {title: 'Mock Page'}};
+      var title = Helpers.getPageTitle(state, null);
+      expect(title).to.exist.to.equal('Mock Page | TopCoder');
+    });
+
+    it("should get default page title when state does not have page title ", function() {
+      var state = {};
+      var title = Helpers.getPageTitle(state, null);
+      expect(title).to.exist.to.equal('TopCoder');
+    });
+
+    it("should get page title from state with dynamic data ", function() {
+      var state = { data: {title: 'Mock Page {{a.b.c}}'}};
+      var title = Helpers.getPageTitle(state, {locals : {resolve: {$$values : {a: {b : {c: 'Title'}}}}}});
+      expect(title).to.exist.to.equal('Mock Page Title | TopCoder');
+    });
+
+    it("should get static page title from state with unknown expression for dynamic data ", function() {
+      var state = { data: {title: 'Mock Page {a.b.c}'}};
+      var title = Helpers.getPageTitle(state, {locals : {resolve: {$$values : {a: {b : {c: 'Title'}}}}}});
+      expect(title).to.exist.to.equal('Mock Page {a.b.c} | TopCoder');
+    });
+
+    it("should replace dynamic data with empty value when not available in current state ", function() {
+      var state = { data: {title: 'Mock Page {{a.b.c}}'}};
+      var title = Helpers.getPageTitle(state, {locals : {resolve: {$$values : {a: {b : {}}}}}});
+      expect(title).to.exist.to.equal('Mock Page  | TopCoder');
+    });
+  });
+
+  describe("getParameterByName()", function() {
+
+    it("should get params from the URL ", function() {
+      var url = "https://topcoder.com/challenges?paramA=123&paramB=234";
+      var paramA = Helpers.getParameterByName('paramA', url);
+      var paramB = Helpers.getParameterByName('paramB', url);
+      expect(paramA).to.exist.to.equal('123');
+      expect(paramB).to.exist.to.equal('234');
+    });
+
+    it("should get params with [ or ] in from the URL ", function() {
+      var url = "https://topcoder.com/challenges?[paramA]=123&[paramB]=234";
+      var paramA = Helpers.getParameterByName('[paramA]', url);
+      var paramB = Helpers.getParameterByName('[paramB]', url);
+      expect(paramA).to.exist.to.equal('123');
+      expect(paramB).to.exist.to.equal('234');
+    });
+
+    it("should get empty value for non existing param from the URL ", function() {
+      var url = "https://topcoder.com/challenges?paramA=123&paramB=234";
+      var paramA = Helpers.getParameterByName('paramC', url);
+      expect(paramA).to.exist.to.equal('');
+    });
+  });
+
+  describe("peerReview module helpers ", function() {
+    it("should store objects by id ", function() {
+      var obj = {};
+      var questions = [{id:1}, {id: 'bcd'}, {id: 'cde'}];
+      Helpers.storeById(obj, questions);
+      expect(obj['1']).to.exist;
+      expect(obj.bcd).to.exist;
+      expect(obj.cde).to.exist;
+    });
+
+    it("should parse questions ", function() {
+      var questions = [
+        {id: 1, questionTypeId: 5, guideline: 'some guideline'},
+        {id: 'bcd', questionTypeId: 4, guideline: 'some guideline\nsecond line'},
+        {id: 'cde', questionTypeId: 5, guideline: 'some guideline\nsecond line\nthird line'}
+      ];
+      Helpers.parseQuestions(questions);
+      expect(questions[0].guidelines).to.exist.to.have.length(1);
+      expect(questions[1].guidelines).not.to.exist;
+      expect(questions[2].guidelines).to.exist.to.have.length(3);
+    });
+
+    it("should parse answers ", function() {
+      var questions = {
+        q1 : {id: 'q1', questionTypeId: 5, guideline: 'some guideline'},
+        q2 : {id: 'q2', questionTypeId: 5, guideline: 'some guideline\nsecond line'},
+        q3 : {id: 'q3', questionTypeId: 5, guideline: 'some guideline\nsecond line\nthird line'}
+      };
+      var answers = [
+        {id: 'a1', scorecardQuestionId: 'q1', answer: 3, comments:[ {content: 'perfect'}]},
+        {id: 'a2', scorecardQuestionId: 'q2', answer: 1, comments:[]},
+        {id: 'a3', scorecardQuestionId: 'q3', answer: 2, comments:[ {content: 'good'}]}
+      ];
+      Helpers.parseAnswers(questions, answers);
+      // validate q1
+      expect(questions.q1.answer).to.exist.to.equal(3);
+      expect(questions.q1.reviewItemId).to.exist.to.equal('a1');
+      expect(questions.q1.comment).to.exist.to.equal('perfect');
+      // validate q2
+      expect(questions.q2.answer).to.exist.to.equal(1);
+      expect(questions.q2.reviewItemId).to.exist.to.equal('a2');
+      expect(questions.q2.comment).not.to.exist;
+      // validate q3
+      expect(questions.q3.answer).to.exist.to.equal(2);
+      expect(questions.q3.reviewItemId).to.exist.to.equal('a3');
+      expect(questions.q3.comment).to.exist.to.equal('good');
+    });
+
+    it("should compile review items for first time creation ", function() {
+      var questions = {
+        '1' : {id: '1', questionTypeId: 5, guideline: 'some guideline'},
+        '2' : {id: '2', questionTypeId: 5, guideline: 'some guideline\nsecond line'},
+        '3' : {id: '3', questionTypeId: 5, guideline: 'some guideline\nsecond line\nthird line'}
+      };
+      var answers = [
+        {id: 'a1', scorecardQuestionId: '1', answer: 3, comments:[ {content: 'perfect'}]},
+        {id: 'a2', scorecardQuestionId: '2', answer: 1, comments:[]},
+        {id: 'a3', scorecardQuestionId: '3', answer: 2, comments:[ {content: 'good'}]}
+      ];
+      // assumes parseAnswers to be working as expected
+      Helpers.parseAnswers(questions, answers);
+
+      var review = {id: 'rev1', resourceId: 'res1', uploadId: 'u1'};
+      var reviewItems = Helpers.compileReviewItems(questions, review, false);
+      expect(reviewItems).to.exist.to.have.length(3);
+      expect(reviewItems[0].reviewId).to.exist.to.equal(review.id);
+      expect(reviewItems[0].uploadId).to.exist.to.equal(review.uploadId);
+      // expect(reviewItems[0].id).to.exist.to.equal(answers[0].id);
+      expect(reviewItems[0].answer).to.exist.to.equal(answers[0].answer.toString());
+      expect(reviewItems[0].scorecardQuestionId).to.exist.to.equal(parseInt(answers[0].scorecardQuestionId));
+      expect(reviewItems[0].comments).to.exist.to.have.length(answers[0].comments.length);
+    });
+
+    it("should compile review items for updating existing review items ", function() {
+      var questions = {
+        '1' : {id: '1', questionTypeId: 5, guideline: 'some guideline', reviewItemId: 'revItem1'},
+        '2' : {id: '2', questionTypeId: 5, guideline: 'some guideline\nsecond line', reviewItemId: 'revItem2'},
+        '3' : {id: '3', questionTypeId: 5, guideline: 'some guideline\nsecond line\nthird line', reviewItemId: 'revItem3'}
+      };
+      var answers = [
+        {id: 'a1', scorecardQuestionId: '1', answer: 3, comments:[ {content: 'perfect'}]},
+        {id: 'a2', scorecardQuestionId: '2', answer: 1, comments:[]},
+        {id: 'a3', scorecardQuestionId: '3', answer: 2, comments:[ {content: 'good'}]}
+      ];
+      // assumes parseAnswers to be working as expected
+      Helpers.parseAnswers(questions, answers);
+
+      var review = {id: 'rev1', resourceId: 'res1', uploadId: 'u1'};
+      var reviewItems = Helpers.compileReviewItems(questions, review, true);
+      expect(reviewItems).to.exist.to.have.length(3);
+      expect(reviewItems[0].uploadId).to.exist.to.equal(review.uploadId);
+      expect(reviewItems[0].id).to.exist.to.equal(answers[0].id);
+      expect(reviewItems[0].answer).to.exist.to.equal(answers[0].answer.toString());
+      expect(reviewItems[0].scorecardQuestionId).to.exist.to.equal(parseInt(answers[0].scorecardQuestionId));
+      expect(reviewItems[0].comments).to.exist.to.have.length(answers[0].comments.length);
+    });
+  });
+
+  describe("npad ", function() {
+    it("should pad string with 0 ", function() {
+      var padded = Helpers.npad("123", 5);
+      expect(padded).to.exist.to.equal('00123');
+    });
+
+    it("should pad number with 0 ", function() {
+      var padded = Helpers.npad(123, 5);
+      expect(padded).to.exist.to.equal('00123');
+    });
+
+    it("should not pad string with 0 ", function() {
+      var padded = Helpers.npad("12345", 5);
+      expect(padded).to.exist.to.equal('12345');
+    });
+  });
+
+  describe("setupLoginEventMetrics ", function() {
+    it("should add object with identify key ", function() {
+      $window._kmq = [];
+      Helpers.setupLoginEventMetrics('mockuser');
+      expect($window._kmq).to.have.length(1);
+      expect($window._kmq[0][0]).to.exist.to.equal('identify');
+      expect($window._kmq[0][1]).to.exist.to.equal('mockuser');
+    });
+  });
+
+  xdescribe("getCountyObjFromIP ", function() {
+    it("should get valid country object ", function() {
+      var mockLocation = {
+        "ip": "123.63.151.213",
+        "hostname": "No Hostname",
+        "city": "New Delhi",
+        "region": "National Capital Territory of Delhi",
+        "country": "IN",
+        "loc": "28.6000,77.2000",
+        "org": "Mock Organization"
+      };
+
+      $httpBackend
+        .when('GET', 'http://ipinfo.io')
+        .respond(200, mockLocation);
+      
+      $rootScope.$apply();
+      console.log(Helpers.getCountyObjFromIP().then(function(data) {
+        console.log(data);
+      }));
+      $rootScope.$apply();
+    });
+  });
 });
diff --git a/app/specs.html b/app/specs.html
index e560f835f..982ab2f85 100644
--- a/app/specs.html
+++ b/app/specs.html
@@ -44,6 +44,7 @@ <h1><a href="specs.html">Spec Runner</a></h1>
     </script>
 
     <!-- bower:js -->
+    <script src="../bower_components/zepto/zepto.js"></script>
     <script src="../bower_components/angular/angular.js"></script>
     <script src="../bower_components/a0-angular-storage/dist/angular-storage.js"></script>
     <script src="../bower_components/angucomplete-alt/angucomplete-alt.js"></script>
@@ -63,13 +64,24 @@ <h1><a href="specs.html">Spec Runner</a></h1>
     <script src="../bower_components/angular-animate/angular-animate.js"></script>
     <script src="../bower_components/angularjs-toaster/toaster.js"></script>
     <script src="../bower_components/appirio-tech-ng-iso-constants/dist/ng-iso-constants.js"></script>
+    <script src="../bower_components/angular-resource/angular-resource.js"></script>
+    <script src="../bower_components/moment/moment.js"></script>
+    <script src="../bower_components/angular-scroll/angular-scroll.js"></script>
+    <script src="../bower_components/react/react.js"></script>
+    <script src="../bower_components/react/react-dom.js"></script>
+    <script src="../bower_components/classnames/index.js"></script>
+    <script src="../bower_components/classnames/bind.js"></script>
+    <script src="../bower_components/classnames/dedupe.js"></script>
+    <script src="../bower_components/react-input-autosize/dist/react-input-autosize.min.js"></script>
+    <script src="../bower_components/react-select/dist/react-select.min.js"></script>
+    <script src="../bower_components/ngReact/ngReact.js"></script>
+    <script src="../bower_components/appirio-tech-ng-ui-components/dist/main.js"></script>
     <script src="../bower_components/d3/d3.js"></script>
     <script src="../bower_components/jstzdetect/jstz.min.js"></script>
-    <script src="../bower_components/moment/moment.js"></script>
+    <script src="../bower_components/lodash/lodash.js"></script>
     <script src="../bower_components/ng-busy/build/angular-busy.js"></script>
     <script src="../bower_components/ng-notifications-bar/dist/ngNotificationsBar.min.js"></script>
     <script src="../bower_components/ngDialog/js/ngDialog.js"></script>
-    <script src="../bower_components/lodash/lodash.js"></script>
     <script src="../bower_components/restangular/dist/restangular.js"></script>
     <script src="../bower_components/angular-touch/angular-touch.js"></script>
     <script src="../bower_components/angular-carousel/dist/angular-carousel.js"></script>
@@ -80,6 +92,7 @@ <h1><a href="specs.html">Spec Runner</a></h1>
     <script src="../bower_components/bardjs/dist/bard.js"></script>
     <script src="../bower_components/bardjs/dist/bard-ngRouteTester.js"></script>
     <script src="../bower_components/jquery/dist/jquery.js"></script>
+    <script src="../bower_components/bind-polyfill/index.js"></script>
     <!-- endbower -->
 
     <!-- inject:nonBowerScripts:js -->
@@ -95,6 +108,7 @@ <h1><a href="specs.html">Spec Runner</a></h1>
     <script src="/app/topcoder.controller.js"></script>
     <script src="/app/topcoder.constants.js"></script>
     <script src="/app/submissions/submissions.module.js"></script>
+    <script src="/app/submissions/submit-design-files/submit-design-files.controller.js"></script>
     <script src="/app/submissions/submissions.routes.js"></script>
     <script src="/app/submissions/submissions.controller.js"></script>
     <script src="/app/skill-picker/skill-picker.module.js"></script>
@@ -113,6 +127,7 @@ <h1><a href="specs.html">Spec Runner</a></h1>
     <script src="/app/services/user.service.js"></script>
     <script src="/app/services/tcAuth.service.js"></script>
     <script src="/app/services/tags.service.js"></script>
+    <script src="/app/services/submissions.service.js"></script>
     <script src="/app/services/statistics.service.js"></script>
     <script src="/app/services/srm.service.js"></script>
     <script src="/app/services/scorecard.service.js"></script>
@@ -184,20 +199,29 @@ <h1><a href="specs.html">Spec Runner</a></h1>
     <script src="/app/filters/empty.filter.js"></script>
     <script src="/app/filters/deadline-msg.filter.js"></script>
     <script src="/app/filters/challengeLinks.filter.js"></script>
+    <script src="/app/filters/add-beginning-space.filter.js"></script>
     <script src="/app/directives/tcui-components.module.js"></script>
     <script src="/app/directives/track-toggle/track-toggle.directive.js"></script>
     <script src="/app/directives/tc-transclude.directive.js"></script>
+    <script src="/app/directives/tc-textarea/tc-textarea.directive.js"></script>
     <script src="/app/directives/tc-tabs/tc-tabs.directive.js"></script>
     <script src="/app/directives/tc-sticky/tc-sticky.directive.js"></script>
     <script src="/app/directives/tc-section/tc-section.directive.js"></script>
     <script src="/app/directives/tc-paginator/tc-paginator.directive.js"></script>
+    <script src="/app/directives/tc-input/tc-input.directive.js"></script>
+    <script src="/app/directives/tc-form-stockart/tc-form-stockart.directive.js"></script>
+    <script src="/app/directives/tc-form-fonts/tc-form-fonts.directive.js"></script>
+    <script src="/app/directives/tc-file-input/tc-file-input.directive.js"></script>
     <script src="/app/directives/tc-endless-paginator/tc-endless-paginator.directive.js"></script>
     <script src="/app/directives/srm-tile/srm-tile.directive.js"></script>
     <script src="/app/directives/slideable.directive.js"></script>
     <script src="/app/directives/skill-tile/skill-tile.directive.js"></script>
     <script src="/app/directives/responsive-carousel/responsive-carousel.directive.js"></script>
+    <script src="/app/directives/progress-bar/progress-bar.directive.js"></script>
     <script src="/app/directives/profile-widget/profile-widget.directive.js"></script>
+    <script src="/app/directives/preventEventPropagation.directive.js"></script>
     <script src="/app/directives/page-state-header/page-state-header.directive.js"></script>
+    <script src="/app/directives/onoffswitch/onoffswitch.directive.js"></script>
     <script src="/app/directives/on-file-change.directive.js"></script>
     <script src="/app/directives/ios-card/ios-card.directive.js"></script>
     <script src="/app/directives/input-sticky-placeholder/input-sticky-placeholder.directive.js"></script>
@@ -206,6 +230,7 @@ <h1><a href="specs.html">Spec Runner</a></h1>
     <script src="/app/directives/focus-on.directive.js"></script>
     <script src="/app/directives/external-account/external-web-links.directive.js"></script>
     <script src="/app/directives/external-account/external-links-data.directive.js"></script>
+    <script src="/app/directives/external-account/external-link-deletion.controller.js"></script>
     <script src="/app/directives/external-account/external-account.directive.js"></script>
     <script src="/app/directives/empty-state-placeholder/empty-state-placeholder.directive.js"></script>
     <script src="/app/directives/distribution-graph/distribution-graph.directive.js"></script>
@@ -247,7 +272,6 @@ <h1><a href="specs.html">Spec Runner</a></h1>
     <script src="/app/my-dashboard/my-dashboard.spec.js"></script>
     <script src="/app/my-srms/my-srms.spec.js"></script>
     <script src="/app/profile/profile.controller.spec.js"></script>
-    <script src="/app/settings/settings.spec.js"></script>
     <script src="/app/services/authToken.service.spec.js"></script>
     <script src="/app/services/challenge.service.spec.js"></script>
     <script src="/app/services/externalAccounts.service.spec.js"></script>
@@ -258,29 +282,35 @@ <h1><a href="specs.html">Spec Runner</a></h1>
     <script src="/app/services/tcAuth.service.spec.js"></script>
     <script src="/app/services/user.service.spec.js"></script>
     <script src="/app/services/userStats.service.spec.js"></script>
+    <script src="/app/settings/settings.spec.js"></script>
     <script src="/app/skill-picker/skill-picker.spec.js"></script>
     <script src="/app/submissions/submissions.spec.js"></script>
     <script src="/app/account/login/login.spec.js"></script>
     <script src="/app/account/logout/logout.controller.spec.js"></script>
-    <script src="/app/account/register/register.spec.js"></script>
     <script src="/app/account/reset-password/reset-password.spec.js"></script>
+    <script src="/app/account/register/register.spec.js"></script>
     <script src="/app/blocks/exception/exception-handler.provider.spec.js"></script>
     <script src="/app/directives/badges/badge-tooltip.spec.js"></script>
     <script src="/app/directives/busy-button/busy-button.directive.spec.js"></script>
     <script src="/app/directives/challenge-tile/challenge-tile.spec.js"></script>
     <script src="/app/directives/empty-state-placeholder/empty-state-placeholder.spec.js"></script>
     <script src="/app/directives/external-account/external-account.directive.spec.js"></script>
+    <script src="/app/directives/external-account/external-link-deletion.controller.spec.js"></script>
     <script src="/app/directives/external-account/external-links-data.directive.spec.js"></script>
     <script src="/app/directives/external-account/external-web-links.directive.spec.js"></script>
     <script src="/app/directives/tc-endless-paginator/tc-endless-paginator.spec.js"></script>
+    <script src="/app/directives/tc-file-input/tc-file-input.spec.js"></script>
+    <script src="/app/directives/tc-form-fonts/tc-form-fonts.spec.js"></script>
+    <script src="/app/directives/tc-form-stockart/tc-form-stockart.spec.js"></script>
+    <script src="/app/directives/tc-input/tc-input.spec.js"></script>
     <script src="/app/directives/tc-paginator/tc-paginator.spec.js"></script>
     <script src="/app/directives/tc-tabs/tc-tabs.directive.spec.js"></script>
     <script src="/app/my-dashboard/community-updates/community-updates.spec.js"></script>
     <script src="/app/my-dashboard/header-dashboard/header-dashboard.spec.js"></script>
     <script src="/app/my-dashboard/my-challenges/my-challenges.spec.js"></script>
     <script src="/app/my-dashboard/programs/programs.spec.js"></script>
-    <script src="/app/my-dashboard/srms/srms.spec.js"></script>
     <script src="/app/my-dashboard/subtrack-stats/subtrack-stats.controller.spec.js"></script>
+    <script src="/app/my-dashboard/srms/srms.spec.js"></script>
     <script src="/app/peer-review/completed-review/completed-review.spec.js"></script>
     <script src="/app/peer-review/edit-review/edit-review.spec.js"></script>
     <script src="/app/peer-review/readOnlyScorecard/readOnlyScorecard.spec.js"></script>
@@ -291,6 +321,7 @@ <h1><a href="specs.html">Spec Runner</a></h1>
     <script src="/app/settings/account-info/account-info.spec.js"></script>
     <script src="/app/settings/edit-profile/edit-profile.spec.js"></script>
     <script src="/app/settings/preferences/preferences.spec.js"></script>
+    <script src="/app/submissions/submit-design-files/submit-design-files.spec.js"></script>
     <script src="/app/directives/account/toggle-password/toggle-password.spec.js"></script>
     <script src="/app/directives/account/toggle-password-with-tips/toggle-password-with-tips.spec.js"></script>
     <!-- endinject -->
diff --git a/app/submissions/submission-error/submission-error.jade b/app/submissions/submission-error/submission-error.jade
new file mode 100644
index 000000000..d904ccd94
--- /dev/null
+++ b/app/submissions/submission-error/submission-error.jade
@@ -0,0 +1,3 @@
+.panel-body
+  p.tc-error-messages.submissions-access-error(ng-bind="submissions.errorMessage")
+
diff --git a/app/submissions/submissions.controller.js b/app/submissions/submissions.controller.js
index 63c24e445..3f12e51cf 100644
--- a/app/submissions/submissions.controller.js
+++ b/app/submissions/submissions.controller.js
@@ -3,18 +3,34 @@
 
   angular.module('tc.submissions').controller('SubmissionsController', SubmissionsController);
 
-  SubmissionsController.$inject = ['challengeToSubmitTo'];
+  SubmissionsController.$inject = ['challengeToSubmitTo', '$state'];
 
-  function SubmissionsController(challengeToSubmitTo) {
+  function SubmissionsController(challengeToSubmitTo, $state) {
     var vm = this;
 
-    var challenge = challengeToSubmitTo.challenge;
-    vm.challengeTitle = challenge.name;
-    vm.challengeId = challenge.id;
-    vm.track = challenge.track.toLowerCase();
+    vm.error = !!challengeToSubmitTo.error;
 
-    activate();
+    if (vm.error) {
+      vm.errorType = challengeToSubmitTo.error.type;
+      vm.errorMessage = challengeToSubmitTo.error.message;
+      vm.challengeError = vm.errorType === 'challenge';
+    }
 
-    function activate() {}
+    if (challengeToSubmitTo.challenge) {
+      var challenge = challengeToSubmitTo.challenge;
+      vm.challengeTitle = challenge.name;
+      vm.challengeId = challenge.id;
+      vm.track = challenge.track.toLowerCase();
+
+      if (challengeToSubmitTo.error) {
+        $state.go('submissions.file.error');
+      } else {
+        if (challenge.track === 'DESIGN') {
+          $state.go('submissions.file.design');
+        } else if (challenge.track === 'DEVELOP') {
+          $state.go('submissions.file.develop')
+        }
+      }
+    }
   }
 })();
diff --git a/app/submissions/submissions.jade b/app/submissions/submissions.jade
index 309269929..85ea902e0 100644
--- a/app/submissions/submissions.jade
+++ b/app/submissions/submissions.jade
@@ -1,5 +1,5 @@
 .panel-page
-  .panel-header.flex.space-between
+  .panel-header.flex.space-between(ng-if="!submissions.challengeError")
     a.panel-header__back-button.flex.space-between.middle(ng-href="https://www.{{DOMAIN}}/challenge-details/{{submissions.challengeId}}/?type={{submissions.track}}")
 
       //- TODO: Replace below with svg tag
diff --git a/app/submissions/submissions.routes.js b/app/submissions/submissions.routes.js
index a80987887..d6683bf91 100644
--- a/app/submissions/submissions.routes.js
+++ b/app/submissions/submissions.routes.js
@@ -18,9 +18,7 @@
         controllerAs: 'submissions',
         data: {
           authRequired: true,
-
-          // TODO: Get title from PMs
-          title: 'Submit'
+          title: 'Challenge Submission'
         },
         resolve: {
           challengeToSubmitTo: ['ChallengeService', '$stateParams', 'UserService', function(ChallengeService, $stateParams, UserService) {
@@ -31,15 +29,19 @@
 
             var userHandle = UserService.getUserIdentity().handle;
 
+            var error = null;
+
             return ChallengeService.getUserChallenges(userHandle, params)
               .then(function(challenge) {
-                challenge = challenge[0];
+                challenge = challenge[0].plain();
 
                 if (!challenge) {
-                  // There should be a challenge, redirect?
-                  alert('User is not associated with this challenge.');
+                  setErrorMessage('challenge', 'This is not a valid challenge. Use your browser\'s back button to return.');
+                  return {
+                    error: error,
+                    challenge: null
+                  };
                 }
-
                 var phaseType;
                 var phaseId;
 
@@ -60,33 +62,65 @@
                   return false;
                 });
 
+                if (!isPhaseSubmission) {
+                  setErrorMessage('phase', 'Submission phases are not currently open for this challenge.')
+                }
+
                 var isSubmitter = _.some(challenge.userDetails.roles, function(role) {
                   return role === 'Submitter';
                 });
 
-                if (!isPhaseSubmission || !isSubmitter) {
-                // TODO: Where do we redirect if you can't submit?
-                  alert('You should not have access to this page');
+                if (!isSubmitter) {
+                  setErrorMessage('submitter', 'You do not have a submitter role for this challenge.')
                 }
 
                 return {
+                  error: error,
                   challenge: challenge,
                   phaseType: phaseType,
                   phaseId: phaseId
                 };
               })
               .catch(function(err) {
-                console.log('ERROR GETTING CHALLENGE: ', err);
-                alert('There was an error accessing this page');
-                // TODO: Where do we redirect if there is an error?
+                setErrorMessage('challenge', 'There was an error getting information for this challenge.');
+
+                return {
+                  error: error,
+                  challenge: null
+                };
               });
+
+            function setErrorMessage(type, message) {
+              // Sets the error as the first error encountered
+              if (!error) {
+                error = {
+                  type: type,
+                  message: message
+                };
+              }
+            }
           }]
         }
       },
       'submissions.file': {
-        url: '?method=file',
-        templateUrl: 'submissions/submit-file/submit-file.html',
-        controller: 'SubmitFileController',
+        url:'file/',
+        abstract: true,
+        template: '<ui-view/>'
+      },
+      'submissions.file.error': {
+        url: '',
+        templateUrl: 'submissions/submission-error/submission-error.html',
+      },
+      'submissions.file.design': {
+        url:'',
+        templateUrl: 'submissions/submit-design-files/submit-design-files.html',
+        controller: 'SubmitDesignFilesController',
+        controllerAs: 'vm',
+      },
+      'submissions.file.develop': {
+        url:'',
+        templateUrl: 'submissions/submit-develop-files/submit-develop-files.html',
+        controller: 'SubmitDevelopFilesController',
         controllerAs: 'vm',
       }
     };
diff --git a/app/submissions/submissions.spec.js b/app/submissions/submissions.spec.js
index 983dd7669..91ae5b4b9 100644
--- a/app/submissions/submissions.spec.js
+++ b/app/submissions/submissions.spec.js
@@ -1,7 +1,6 @@
 /* jshint -W117, -W030 */
 describe('Submissions Controller', function() {
-  var controller;
-  var vm;
+  var controller, vm;
 
   var mockChallenge = {
     challenge: {
@@ -11,6 +10,10 @@ describe('Submissions Controller', function() {
     }
   };
 
+  var state = {
+    go: sinon.spy()
+  }
+
   beforeEach(function() {
     bard.appModule('tc.submissions');
     bard.inject(this, '$controller');
@@ -20,12 +23,81 @@ describe('Submissions Controller', function() {
 
   beforeEach(function() {
     controller = $controller('SubmissionsController', {
-      challengeToSubmitTo: mockChallenge
+      challengeToSubmitTo: mockChallenge,
+      $state: state
     });
     vm = controller;
   });
 
-  it('should exist', function() {
+  it('exists', function() {
     expect(vm).to.exist;
   });
+
+  it('sets error properties when there is an error passed down', function() {
+    controller = $controller('SubmissionsController', {
+      challengeToSubmitTo: {
+        challenge: null,
+        error: {
+          type: 'challenge',
+          message: 'error getting challenge information'
+        }
+      },
+      $state: state
+    });
+    vm = controller;
+
+    expect(vm.errorType).to.equal('challenge');
+    expect(vm.errorMessage).to.equal('error getting challenge information');
+    expect(vm.challengeError).to.be.true;
+  });
+
+  it('sets challenge properties when there is a challenge from the routes resolve', function() {
+    expect(vm.challengeTitle).to.equal(mockChallenge.challenge.name);
+    expect(vm.challengeId).to.equal(30049240);
+    expect(vm.track).to.equal(mockChallenge.challenge.track.toLowerCase());
+  });
+
+
+  describe('routes to the correct child state for', function() {
+    it('design challenges', function() {
+
+      expect(state.go).calledWith('submissions.file.design');
+    });
+
+    it('develop challenges', function() {
+      controller = $controller('SubmissionsController', {
+        challengeToSubmitTo: {
+          challenge: {
+            name: 'Challenge Name',
+            track: 'DEVELOP',
+            id: 30049240
+          }
+        },
+        $state: state
+      });
+      vm = controller;
+
+      expect(state.go).calledWith('submissions.file.develop');
+    });
+
+    it('errors', function() {
+      controller = $controller('SubmissionsController', {
+        challengeToSubmitTo: {
+          challenge: {
+            name: 'Challenge Name',
+            track: 'DEVELOP',
+            id: 30049240
+          },
+          error: {
+            type: 'phase',
+            message: 'No open submissions phase'
+          }
+        },
+        $state: state
+      });
+      vm = controller;
+
+      expect(state.go).calledWith('submissions.file.error');
+    });
+  });
 });
diff --git a/app/submissions/submit-file/submit-file.controller.js b/app/submissions/submit-design-files/submit-design-files.controller.js
similarity index 72%
rename from app/submissions/submit-file/submit-file.controller.js
rename to app/submissions/submit-design-files/submit-design-files.controller.js
index 11dfbd604..6b8c9b70e 100644
--- a/app/submissions/submit-file/submit-file.controller.js
+++ b/app/submissions/submit-design-files/submit-design-files.controller.js
@@ -1,13 +1,13 @@
 (function () {
   'use strict';
 
-  angular.module('tc.submissions').controller('SubmitFileController', SubmitFileController);
+  angular.module('tc.submissions').controller('SubmitDesignFilesController', SubmitDesignFilesController);
 
-  SubmitFileController.$inject = ['$scope', '$stateParams', '$log', 'UserService', 'SubmissionsService', 'challengeToSubmitTo'];
+  SubmitDesignFilesController.$inject = ['$scope','$window', '$stateParams', '$log', 'UserService', 'SubmissionsService', 'challengeToSubmitTo'];
 
-  function SubmitFileController($scope, $stateParams, $log, UserService, SubmissionsService, challengeToSubmitTo) {
+  function SubmitDesignFilesController($scope, $window, $stateParams, $log, UserService, SubmissionsService, challengeToSubmitTo) {
     var vm = this;
-    $log = $log.getInstance('SubmitFileController');
+    $log = $log.getInstance('SubmitDesignFilesController');
     var files = {};
     var fileUploadProgress = {};
     vm.urlRegEx = new RegExp(/^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,3})+(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?$/);
@@ -19,30 +19,8 @@
     vm.finishing = false;
     vm.showProgress = false;
     vm.errorInUpload = false;
-    vm.formFonts = {
-      0: {
-        id: 0,
-        source: '',
-        name: '',
-        sourceUrl: '',
-        isFontUrlRequired: false,
-        isFontUrlDisabled: true,
-        isFontNameRequired: false,
-        isFontNameDisabled: true,
-        isFontSourceRequired: false
-      }
-    };
-    vm.formStockarts = {
-      0: {
-        id: 1,
-        description: '',
-        sourceUrl: '',
-        fileNumber: '',
-        isPhotoDescriptionRequired: false,
-        isPhotoURLRequired: false,
-        isFileNumberRequired: false
-      }
-    };
+    vm.formFonts = {};
+    vm.formStockarts = {};
     vm.submissionForm = {
       files: [],
 
@@ -61,8 +39,6 @@
 
     vm.submissionsBody = {
       reference: {
-
-        // type dynamic or static?
         type: 'CHALLENGE',
         id: $stateParams.challengeId,
         phaseType: challengeToSubmitTo.phaseType,
@@ -70,9 +46,7 @@
       },
       userId: userId,
       data: {
-
-        // Dynamic or static?
-        method: 'DESIGN_CHALLENGE_ZIP_FILE',
+        method: challengeToSubmitTo.challenge.track.toUpperCase() + '_CHALLENGE_ZIP_FILE',
 
         // Can delete below since they are processed and added later?
         files: [],
@@ -86,6 +60,7 @@
     vm.setRankTo1 = setRankTo1;
     vm.setFileReference = setFileReference;
     vm.uploadSubmission = uploadSubmission;
+    vm.refreshPage = refreshPage;
     vm.cancelRetry = cancelRetry;
 
     activate();
@@ -121,17 +96,20 @@
           fileObject.mediaType = file.type;
       }
 
-      // If user picks a new file, replace the that file's fileObject with a new one
-      // Or add it the list if it's not there
-      if (vm.submissionsBody.data.files.length) {
-        vm.submissionsBody.data.files.some(function(file, i, filesArray) {
-          if (file.type === fileObject.type) {
-            file = fileObject;
-          } else if (filesArray.length === i + 1) {
-            filesArray.push(fileObject);
-          }
-        });
-      } else {
+      // If user changes a file input's file, update the file details
+      var isFound = vm.submissionsBody.data.files.reduce(function(isFound, file, i, filesArray) {
+        if (isFound) { return true; }
+
+        if (file.type === fileObject.type) {
+          filesArray[i] = fileObject;
+          return true;
+        }
+
+        return false;
+      }, false);
+
+      // Add new files to the list
+      if (!isFound) {
         vm.submissionsBody.data.files.push(fileObject);
       }
     }
@@ -148,10 +126,18 @@
       vm.submissionsBody.data.submitterRank = vm.submissionForm.submitterRank;
 
       // Process stock art
-      var processedStockarts = _.map(vm.formStockarts, function(formStockart) {
+      var processedStockarts = _.reduce(vm.formStockarts, function(compiledStockarts, formStockart) {
+        if (formStockart.description) {
           delete formStockart.id;
-          return formStockart;
-      });
+          delete formStockart.isPhotoDescriptionRequired;
+          delete formStockart.isPhotoURLRequired;
+          delete formStockart.isFileNumberRequired;
+
+          compiledStockarts.push(formStockart);
+        }
+
+        return compiledStockarts;
+      }, []);
 
       vm.submissionsBody.data.stockArts = processedStockarts;
 
@@ -164,6 +150,7 @@
           delete formFont.isFontNameRequired;
           delete formFont.isFontNameDisabled;
           delete formFont.isFontSourceRequired;
+
           compiledFonts.push(formFont);
         }
 
@@ -176,10 +163,8 @@
       SubmissionsService.getPresignedURL(vm.submissionsBody, files, updateProgress);
     }
 
-    /*
-     * Callback for updating submission upload process. It looks for different phases e.g. PREPARE, UPLOAD, FINISH
-     * of the submission upload and updates the progress UI accordingly.
-     */
+    // Callback for updating submission upload process. It looks for different phases e.g. PREPARE, UPLOAD, FINISH
+    // of the submission upload and updates the progress UI accordingly.
     function updateProgress(phase, args) {
       // for PREPARE phase
       if (phase === 'PREPARE') {
@@ -204,11 +189,13 @@
             count++;
           }
           vm.uploadProgress = total / count;
+
           // initiate digest cycle because this event (xhr event) is caused outside angular
           $scope.$apply();
         } else { // typeof args === 'number', mainly used a s fallback to mark completion of the UPLOAD phase
           vm.uploadProgress = args;
         }
+
         // start next phase when UPLOAD is done
         if (vm.uploadProgress == 100) {
           $log.debug('Uploaded files.');
@@ -219,20 +206,20 @@
         // we are concerned only for completion of the phase
         if (args === 100) {
           $log.debug('Finished upload.');
-          vm.finishing = false;
-          vm.showProgress = false;
-
-          // TODO redirect to submission listing / challenge details page
         }
-      } else { // assume it to be error condition
+      } else {
+        // assume it to be error condition
         $log.debug("Error Condition: " + phase);
         vm.errorInUpload = true;
       }
     }
 
+    function refreshPage() {
+      $window.location.reload(true);
+    }
+
     function cancelRetry() {
       vm.showProgress = false;
-      // TODO redirect to submission listing / challenge details page
     }
   }
 })();
diff --git a/app/submissions/submit-file/submit-file.jade b/app/submissions/submit-design-files/submit-design-files.jade
similarity index 88%
rename from app/submissions/submit-file/submit-file.jade
rename to app/submissions/submit-design-files/submit-design-files.jade
index 0dc8534f4..4225636ec 100644
--- a/app/submissions/submit-file/submit-file.jade
+++ b/app/submissions/submit-design-files/submit-design-files.jade
@@ -10,7 +10,7 @@
 
 .panel-body
   form.form-blocks(name="submissionForm", role="form", ng-submit="submissionForm.$valid && vm.uploadSubmission()", novalidate)
-    .form-block.flex.wrap
+    .form-block.flex
       .form-block__instructions
         .form-block__title Files
 
@@ -96,7 +96,7 @@
           .tc-error-messages(ng-show="submissionForm.Submission_Rank.$dirty && submissionForm.Submission_Rank.$invalid")
             p(ng-show="submissionForm.Submission_Rank.$error.pattern") Please enter a positive integer.
 
-    .form-block.flex.wrap
+    .form-block.flex
       .form-block__instructions
         .form-block__title Notes
 
@@ -104,15 +104,16 @@
           p Type a short note about your design here. Explain revisions or other design elements that may not be clear.
 
       .form-block__fields
-        tc-textarea.tc-textarea(
-          label-text="Comments",
-          placeholder="My design tries to solve the problem with a particular idea in mind. The use of color is based on the provided brand guideline. The flows are included in the sub folder. I followed all revisions as per the directions provided.",
-          character-count="true",
-          character-count-max="500",
-          value="vm.comments"
-        )
-
-    .form-block.flex.wrap
+        .fieldset
+          tc-textarea.tc-textarea(
+            label-text="Comments",
+            placeholder="My design tries to solve the problem with a particular idea in mind. The use of color is based on the provided brand guideline.",
+            character-count="true",
+            character-count-max="500",
+            value="vm.comments"
+          )
+
+    .form-block.flex
       .form-block__instructions
         .form-block__title Did you use custom fonts?
 
@@ -127,7 +128,7 @@
         .fieldsets
           tc-form-fonts(form-fonts="vm.formFonts")
 
-    .form-block.flex.wrap
+    .form-block.flex
       .form-block__instructions
         .form-block__title Did you use stock art?
 
@@ -141,7 +142,7 @@
     .panel-footer
       p Submitting your files means you hereby agree to the #[a(ng-href="https://www.{{DOMAIN}}/community/how-it-works/terms/", target="_blank") Topcoder terms of use] and to the extent your uploaded file wins a Topcoder Competition, you hereby assign, grant, and transfer to Topcoder all rights in and title to the Winning Submission (as further described in the terms of use).
 
-      p.tc-error-messages(ng-show="vm.submissionForm.hasAgreedToTerms && submissionForm.$invalid") There are outstanding problems with this page. Fix them before you can upload your submission.
+      p.tc-error-messages(ng-show="vm.submissionForm.hasAgreedToTerms && submissionForm.$invalid") There are outstanding problems with this page. You must fix them before you can upload your submission.
 
       .checkbox.flex.center
         input(type="checkbox", ng-model="vm.submissionForm.hasAgreedToTerms", id="agree-to-terms", required)
@@ -150,7 +151,7 @@
 
       button.tc-btn.tc-btn-secondary(type="submit", ng-disabled="submissionForm.$invalid") Submit
 
-modal.transition(show="vm.showProgress", background-click-close="true", style="background-color:white;")
+modal.transition(show="vm.showProgress", background-click-close="false", style="background-color:white;")
   .upload-progress(ng-class="{'upload-progress--error': vm.errorInUpload}")
     .upload-progress__title
       p Uploading submission for
@@ -158,17 +159,24 @@ modal.transition(show="vm.showProgress", background-click-close="true", style="b
       p.upload-progress-title__challenge-name [Challenge name]
 
     img.upload-progress__image(src="/images/robot.svg", ng-hide="vm.errorInUpload")
-
     img.upload-progress__image--error(src="/images/robot-embarresed.svg", ng-show="vm.errorInUpload")
 
-    p.upload-progress__message(ng-hide="vm.errorInUpload") Hey, your work is AWESOME! Please don’t close the window while I’m working, you’ll loose all files!
+    p.upload-progress__message(ng-hide="vm.errorInUpload") Hey, your work is AWESOME! Please don’t close the window while I’m working or you’ll loose all files!
 
-    p.upload-progress__message--error(ng-show="vm.errorInUpload") Oh, that’s embarrassing! The file couldn’t be uploaded, I’m so sorry.
+    p.upload-progress__message--error(ng-show="vm.errorInUpload") Oh, that’s embarrassing! One of the files couldn’t be uploaded, I’m so sorry.
 
     progress-bar.upload-progress__progress-bar(completed="vm.uploadProgress", message="of 3 files uploaded")
 
     .upload-progress__preparing(ng-show="vm.preparing && !vm.errorInUpload") #[span Preparing...]
-    .upload-progress__finishing(ng-show="vm.finishing && !vm.errorInUpload") #[span Finishing...]
+    .upload-progress__finishing(ng-show="vm.finishing && !vm.errorInUpload")
+      p Finished!
+
+      .upload-progess__links
+        a.tc-btn.tc-btn-s(ng-href="https://www.{{DOMAIN}}/challenge-details/{{submissions.challengeId}}/?type={{submissions.track}}") Back to the challenge
+
+        a.tc-btn.tc-btn-s.tc-btn-ghost(ng-click="vm.refreshPage()") Submit another
+
+
     .upload-progress__error(ng-show="vm.errorInUpload") #[span File upload failed]
 
     .upload-progress__error-action(ng-show="vm.errorInUpload")
diff --git a/app/submissions/submit-design-files/submit-design-files.spec.js b/app/submissions/submit-design-files/submit-design-files.spec.js
new file mode 100644
index 000000000..13898bd86
--- /dev/null
+++ b/app/submissions/submit-design-files/submit-design-files.spec.js
@@ -0,0 +1,318 @@
+/* jshint -W117, -W030 */
+describe('Submit Design Files Controller', function() {
+  var controller, vm, scope;
+
+  var mockChallenge = {
+    challenge: {
+      name: 'Challenge Name',
+      track: 'DESIGN',
+      id: 30049240
+    }
+  };
+
+  var userService = {
+    getUserIdentity: function() {
+      return {
+        userId: 123456
+      };
+    }
+  };
+
+  var submissionsService = {
+    getPresignedURL: function() {}
+  };
+
+  var mockWindow = {
+    location: {
+      reload: function(val) { return val; }
+    }
+  };
+
+  beforeEach(function() {
+    bard.appModule('tc.submissions');
+    bard.inject(this, '$controller', '$rootScope');
+
+    scope = $rootScope.$new();
+  });
+
+  bard.verifyNoOutstandingHttpRequests();
+
+  beforeEach(function() {
+    controller = $controller('SubmitDesignFilesController', {
+      $scope: scope,
+      UserService: userService,
+      challengeToSubmitTo: mockChallenge,
+      SubmissionsService: submissionsService,
+      $window: mockWindow
+    });
+    vm = controller;
+  });
+
+  it('exists', function() {
+    expect(vm).to.exist;
+  });
+
+  it('sets the right track for the method', function() {
+    controller = $controller('SubmitDesignFilesController', {
+      $scope: scope,
+      UserService: userService,
+      challengeToSubmitTo: {
+        challenge: {
+          name: 'Challenge Name',
+          track: 'DEVELOP',
+          id: 30049240
+        }
+      },
+      SubmissionsService: submissionsService,
+      $window: mockWindow
+    });
+    vm = controller;
+    scope.$digest();
+
+    expect(vm.submissionsBody.data.method).to.equal('DEVELOP_CHALLENGE_ZIP_FILE');
+  });
+
+  describe('setRankTo1', function() {
+    it('returns 1 if the input is blank', function() {
+      expect(vm.setRankTo1('')).to.equal(1);
+    });
+
+    it('returns the input value if not blank', function() {
+      var inputText = 'sample input text';
+      var result = vm.setRankTo1(inputText);
+
+      expect(result).to.equal(inputText);
+    });
+  });
+
+
+  describe('setFileReference', function() {
+    var file, fieldId;
+
+    beforeEach(function() {
+      file = {
+        name: 'Dashboard 2.png',
+        size: 575548,
+        type: 'image/png'
+      };
+      fieldId = 'DESIGN_COVER';
+
+      vm.setFileReference(file, fieldId);
+      scope.$digest();
+    });
+
+    afterEach(function() {
+      file = undefined;
+      fieldId = undefined;
+    });
+
+    it('adds a file object to the submissions body', function() {
+      expect(vm.submissionsBody.data.files).to.have.length(1);
+    });
+
+    it('replaces a file object with a new one if it has the same fieldId', function() {
+      expect(vm.submissionsBody.data.files).to.have.length(1);
+
+      var newFile = {
+        name: 'different_image.png',
+        size: 4321,
+        type: 'image/png'
+      };
+
+      vm.setFileReference(newFile, fieldId);
+      scope.$digest();
+
+      expect(vm.submissionsBody.data.files).to.have.length(1);
+      expect(vm.submissionsBody.data.files[0].name).to.equal('different_image.png');
+    });
+
+    it('sets the correct mediaTypes on the fileObject', function() {
+      expect(vm.submissionsBody.data.files[0].mediaType).to.equal('image/png');
+
+      var newFile = {
+        name: 'submission.zip',
+        size: 43121,
+        type: 'application/zip'
+      };
+      var newFieldId = 'SUBMISSION_ZIP';
+
+      vm.setFileReference(newFile, newFieldId);
+      scope.$digest();
+
+      expect(vm.submissionsBody.data.files[1].mediaType).to.equal('application/octet-stream');
+
+      var newFile2 = {
+        name: 'source.zip',
+        size: 2314,
+        type: 'application/zip'
+      };
+      var newFieldId2 = 'SOURCE_ZIP';
+
+      vm.setFileReference(newFile2, newFieldId2);
+      scope.$digest();
+
+      expect(vm.submissionsBody.data.files[2].mediaType).to.equal('application/octet-stream');
+    });
+  });
+
+  describe('uploadSubmission', function() {
+    it('adds comments to the submissions body', function() {
+      vm.comments = 'test comments';
+      scope.$digest();
+
+      vm.uploadSubmission();
+      scope.$digest();
+
+      expect(vm.submissionsBody.data.submitterComments).to.equal('test comments');
+    });
+
+    it('adds the rank to the submissions body', function() {
+      vm.submissionForm.submitterRank = 3;
+      scope.$digest();
+
+      vm.uploadSubmission();
+      scope.$digest();
+
+      expect(vm.submissionsBody.data.submitterRank).to.equal(3);
+    });
+
+    it('calls the submission service', function() {
+      var mockAPICall = sinon.spy(submissionsService, 'getPresignedURL');
+
+      vm.uploadSubmission();
+      scope.$digest();
+
+      expect(mockAPICall).calledOnce;
+    });
+
+    describe('processes the stockart and', function() {
+      it('returns an empty array if no stockart given', function() {
+        vm.formStockarts = [];
+        scope.$digest();
+
+        vm.uploadSubmission();
+        scope.$digest();
+
+        expect(vm.submissionsBody.data.stockArts).to.deep.equal([]);
+      });
+
+      it('removes the required properties and id from each stockart', function() {
+        vm.formStockarts = [
+          {
+            id: 0,
+            description: 'first stockart',
+            sourceUrl: 'url.com',
+            fileNumber: '123',
+            isPhotoDescriptionRequired: false,
+            isPhotoURLRequired: false,
+            isFileNumberRequired: false
+          },
+          {
+            id: 1,
+            description: 'second stockart',
+            sourceUrl: 'url2.com',
+            fileNumber: '234',
+            isPhotoDescriptionRequired: false,
+            isPhotoURLRequired: false,
+            isFileNumberRequired: false
+          }
+        ];
+        var processedStockart = [
+          {
+            description: 'first stockart',
+            sourceUrl: 'url.com',
+            fileNumber: '123',
+          },
+          {
+            description: 'second stockart',
+            sourceUrl: 'url2.com',
+            fileNumber: '234',
+          }
+        ];
+        scope.$digest();
+
+        vm.uploadSubmission();
+        scope.$digest();
+        expect(vm.submissionsBody.data.stockArts).to.deep.equal(processedStockart);
+
+      });
+    });
+    describe('processes the fonts and', function() {
+      it('returns an empty array if no fonts given', function() {
+        vm.formFonts = [];
+        scope.$digest();
+
+        vm.uploadSubmission();
+        scope.$digest();
+
+        expect(vm.submissionsBody.data.fonts).to.deep.equal([]);
+      });
+
+      it('removes the required properties and id from each font', function() {
+        vm.formFonts = [
+          {
+            id: 0,
+            source: 'STUDIO_STANDARD_FONTS_LIST',
+            name: 'my font',
+            isFontUrlRequired: false,
+            isFontUrlDisabled: true,
+            isFontNameRequired: false,
+            isFontNameDisabled: true,
+            isFontSourceRequired: false
+          },
+          {
+            id: 1,
+            source: 'FONTS_DOT_COM',
+            name: 'my other font',
+            sourceUrl: 'fontsource.com',
+            isFontUrlRequired: false,
+            isFontUrlDisabled: true,
+            isFontNameRequired: false,
+            isFontNameDisabled: true,
+            isFontSourceRequired: false
+          }
+        ];
+        var processedFonts = [
+          {
+            source: 'STUDIO_STANDARD_FONTS_LIST',
+            name: 'my font',
+          },
+          {
+            source: 'FONTS_DOT_COM',
+            name: 'my other font',
+            sourceUrl: 'fontsource.com',
+          }
+        ];
+        scope.$digest();
+
+        vm.uploadSubmission();
+        scope.$digest();
+        expect(vm.submissionsBody.data.fonts).to.deep.equal(processedFonts);
+      });
+    });
+  });
+
+  describe('refreshPage', function() {
+    it('reloads the page', function() {
+      var mockRefresh = sinon.spy(mockWindow.location, 'reload');
+
+      vm.refreshPage();
+      scope.$digest();
+
+      expect(mockRefresh).calledWith(true);
+      expect(mockRefresh).calledOnce;
+    });
+  });
+
+  describe('cancelRetry', function() {
+    it('sets showProgress to false', function() {
+      vm.showProgress = true;
+      scope.$digest();
+      expect(vm.showProgress).to.be.true;
+
+      vm.cancelRetry();
+      scope.$digest();
+      expect(vm.showProgress).to.be.false;
+    });
+  });
+});
diff --git a/app/submissions/submit-develop-files/submit-develop-files.controller.js b/app/submissions/submit-develop-files/submit-develop-files.controller.js
new file mode 100644
index 000000000..9b40e7fc4
--- /dev/null
+++ b/app/submissions/submit-develop-files/submit-develop-files.controller.js
@@ -0,0 +1,171 @@
+(function () {
+  'use strict';
+
+  angular.module('tc.submissions').controller('SubmitDevelopFilesController', SubmitDevelopFilesController);
+
+  SubmitDevelopFilesController.$inject = ['$scope','$window', '$stateParams', '$log', 'UserService', 'SubmissionsService', 'challengeToSubmitTo'];
+
+  function SubmitDevelopFilesController($scope, $window, $stateParams, $log, UserService, SubmissionsService, challengeToSubmitTo) {
+    var vm = this;
+    $log = $log.getInstance('SubmitDevelopFilesController');
+    var files = {};
+    var fileUploadProgress = {};
+    vm.comments = '';
+    vm.uploadProgress = 0;
+    vm.uploading = false;
+    vm.preparing = false;
+    vm.finishing = false;
+    vm.showProgress = false;
+    vm.errorInUpload = false;
+    vm.submissionForm = {
+      files: [],
+
+      // use develop name
+      sourceZip: null,
+
+      submitterComments: '',
+      hasAgreedToTerms: false
+    };
+
+    var userId = parseInt(UserService.getUserIdentity().userId);
+
+    vm.submissionsBody = {
+      reference: {
+        type: 'CHALLENGE',
+        id: $stateParams.challengeId,
+        phaseType: challengeToSubmitTo.phaseType,
+        phaseId: challengeToSubmitTo.phaseId
+      },
+      userId: userId,
+      data: {
+        method: challengeToSubmitTo.challenge.track.toUpperCase() + '_CHALLENGE_ZIP_FILE',
+
+        // Can delete below since they are processed and added later?
+        files: [],
+        submitterComments: '',
+      }
+    };
+
+    vm.setFileReference = setFileReference;
+    vm.uploadSubmission = uploadSubmission;
+    vm.refreshPage = refreshPage;
+    vm.cancelRetry = cancelRetry;
+
+    activate();
+
+    function activate() {}
+
+    function setFileReference(file, fieldId) {
+      // Can clean up since fileValue on tcFileInput has file reference?
+      files[fieldId] = file;
+
+      var fileObject = {
+        name: file.name,
+        type: fieldId,
+        status: 'PENDING'
+      };
+
+      // TODO: Refactor or develop
+      switch(fieldId) {
+        case 'SUBMISSION_ZIP':
+          fileObject.mediaType = 'application/octet-stream';
+          break;
+        case 'SOURCE_ZIP':
+          fileObject.mediaType = 'application/octet-stream';
+          break;
+        default:
+          fileObject.mediaType = file.type;
+      }
+
+      // If user changes a file input's file, update the file details
+      var isFound = vm.submissionsBody.data.files.reduce(function(isFound, file, i, filesArray) {
+        if (isFound) { return true; }
+
+        if (file.type === fileObject.type) {
+          filesArray[i] = fileObject;
+          return true;
+        }
+
+        return false;
+      }, false);
+
+      // Add new files to the list
+      if (!isFound) {
+        vm.submissionsBody.data.files.push(fileObject);
+      }
+    }
+
+    function uploadSubmission() {
+      vm.errorInUpload = false;
+      vm.uploadProgress = 0;
+      vm.fileUploadProgress = {};
+      vm.showProgress = true;
+      vm.preparing = true;
+      vm.uploading = false;
+      vm.finishing = false;
+      vm.submissionsBody.data.submitterComments = vm.comments;
+
+      $log.debug('Body for request: ', vm.submissionsBody);
+      SubmissionsService.getPresignedURL(vm.submissionsBody, files, updateProgress);
+    }
+
+    // Callback for updating submission upload process. It looks for different phases e.g. PREPARE, UPLOAD, FINISH
+    // of the submission upload and updates the progress UI accordingly.
+    function updateProgress(phase, args) {
+      // for PREPARE phase
+      if (phase === 'PREPARE') {
+        // we are concerned only for completion of the phase
+        if (args === 100) {
+          vm.preparing = false;
+          vm.uploading = true;
+          $log.debug('Prepared for upload.');
+        }
+      } else if (phase === 'UPLOAD') {
+        // if args is object, this update is about XHRRequest's upload progress
+        if (typeof args === 'object') {
+          var requestId = args.file;
+          var progress = args.progress;
+          if (!fileUploadProgress[requestId] || fileUploadProgress[requestId] < progress) {
+            fileUploadProgress[requestId] = progress;
+          }
+          var total = 0, count = 0;
+          for(var requestId in fileUploadProgress) {
+            var prog = fileUploadProgress[requestId];
+            total += prog;
+            count++;
+          }
+          vm.uploadProgress = total / count;
+
+          // initiate digest cycle because this event (xhr event) is caused outside angular
+          $scope.$apply();
+        } else { // typeof args === 'number', mainly used a s fallback to mark completion of the UPLOAD phase
+          vm.uploadProgress = args;
+        }
+
+        // start next phase when UPLOAD is done
+        if (vm.uploadProgress == 100) {
+          $log.debug('Uploaded files.');
+          vm.uploading = false;
+          vm.finishing = true;
+        }
+      } else if (phase === 'FINISH') {
+        // we are concerned only for completion of the phase
+        if (args === 100) {
+          $log.debug('Finished upload.');
+        }
+      } else {
+        // assume it to be error condition
+        $log.debug("Error Condition: " + phase);
+        vm.errorInUpload = true;
+      }
+    }
+
+    function refreshPage() {
+      $window.location.reload(true);
+    }
+
+    function cancelRetry() {
+      vm.showProgress = false;
+    }
+  }
+})();
diff --git a/app/submissions/submit-develop-files/submit-develop-files.jade b/app/submissions/submit-develop-files/submit-develop-files.jade
new file mode 100644
index 000000000..9d2241fd5
--- /dev/null
+++ b/app/submissions/submit-develop-files/submit-develop-files.jade
@@ -0,0 +1,90 @@
+.mobile-redirect
+  .mobile-redirect__title File upload is not available for mobile
+
+  .mobile-redirect__body
+    p Our team is working hard on new features, and file upload currently only works on the web. Please open this page on your desktop if you want to create a submission.
+
+    a(ng-href="http://help.{{DOMAIN}}/design/submitting-to-a-design-challenge/formatting-your-submission-for-design-challenges/") TODO: same link as below for help on formatting submission
+
+    a.tc-btn.tc-btn-s(ng-href="https://www.{{DOMAIN}}/challenge-details/{{submissions.challengeId}}/?type={{submissions.track}}") Back to Challenge Details
+
+.panel-body
+  form.form-blocks(name="submissionForm", role="form", ng-submit="submissionForm.$valid && vm.uploadSubmission()", novalidate)
+    .form-block.flex
+      .form-block__instructions
+        .form-block__title Files
+
+        .form-block__text
+          p Need text from PMs.
+
+          p Need text from PMs.
+
+          p Need text from PMs.
+
+          a(ng-href="http://help.{{DOMAIN}}/design/submitting-to-a-design-challenge/formatting-your-submission-for-design-challenges/") Need link from PMs
+
+      .form-block__fields
+        .fieldset
+          tc-file-input.tc-file-field(
+            label-text="Preview Image",
+            field-id="DESIGN_COVER",
+            button-text="Add File",
+            file-type="jpg,jpeg,png"
+            placeholder="Image file as .jpg or .png",
+            mandatory="true",
+            set-file-reference="vm.setFileReference(file, fieldId)",
+            ng-model="vm.submissionForm.designCover"
+          )
+
+          .tc-error-messages(
+            ng-show="submissionForm['DESIGN_COVER'].$touched && submissionForm['DESIGN_COVER'].$invalid",
+            ng-messages="submissionForm['DESIGN_COVER'].$error"
+          )
+            p(ng-message="filesize") File size may not exceed 500MB.
+
+            p(ng-message="required") This is not the correct file format. Please select a .jpg or .png file.
+
+    .panel-footer
+      p Submitting your files means you hereby agree to the #[a(ng-href="https://www.{{DOMAIN}}/community/how-it-works/terms/", target="_blank") Topcoder terms of use] and to the extent your uploaded file wins a Topcoder Competition, you hereby assign, grant, and transfer to Topcoder all rights in and title to the Winning Submission (as further described in the terms of use).
+
+      p.tc-error-messages(ng-show="vm.submissionForm.hasAgreedToTerms && submissionForm.$invalid") There are outstanding problems with this page. You must fix them before you can upload your submission.
+
+      .checkbox.flex.center
+        input(type="checkbox", ng-model="vm.submissionForm.hasAgreedToTerms", id="agree-to-terms", required)
+
+        label(for="agree-to-terms") I understand and agree
+
+      button.tc-btn.tc-btn-secondary(type="submit", ng-disabled="submissionForm.$invalid") Submit
+
+modal.transition(show="vm.showProgress", background-click-close="false", style="background-color:white;")
+  .upload-progress(ng-class="{'upload-progress--error': vm.errorInUpload}")
+    .upload-progress__title
+      p Uploading submission for
+
+      p.upload-progress-title__challenge-name [Challenge name]
+
+    img.upload-progress__image(src="/images/robot.svg", ng-hide="vm.errorInUpload")
+    img.upload-progress__image--error(src="/images/robot-embarresed.svg", ng-show="vm.errorInUpload")
+
+    p.upload-progress__message(ng-hide="vm.errorInUpload") Hey, your work is AWESOME! Please don’t close the window while I’m working or you’ll loose all files!
+
+    p.upload-progress__message--error(ng-show="vm.errorInUpload") Oh, that’s embarrassing! One of the files couldn’t be uploaded, I’m so sorry.
+
+    progress-bar.upload-progress__progress-bar(completed="vm.uploadProgress", message="of 3 files uploaded")
+
+    .upload-progress__preparing(ng-show="vm.preparing && !vm.errorInUpload") #[span Preparing...]
+    .upload-progress__finishing(ng-show="vm.finishing && !vm.errorInUpload")
+      p Finished!
+
+      .upload-progess__links
+        a.tc-btn.tc-btn-s(ng-href="https://www.{{DOMAIN}}/challenge-details/{{submissions.challengeId}}/?type={{submissions.track}}") Back to the challenge
+
+        a.tc-btn.tc-btn-s.tc-btn-ghost(ng-click="vm.refreshPage()") Submit another
+
+
+    .upload-progress__error(ng-show="vm.errorInUpload") #[span File upload failed]
+
+    .upload-progress__error-action(ng-show="vm.errorInUpload")
+      button.tc-btn.tc-btn-s.tc-btn-ghost(type="button", ng-click="vm.cancelRetry()") Cancel
+
+      button.tc-btn.tc-btn-s.tc-btn-secondary(type="button", ng-click="submissionForm.$valid && vm.uploadSubmission()") Try Again
diff --git a/app/submissions/submit-develop-files/submit-develop-files.spec.js b/app/submissions/submit-develop-files/submit-develop-files.spec.js
new file mode 100644
index 000000000..97ca01833
--- /dev/null
+++ b/app/submissions/submit-develop-files/submit-develop-files.spec.js
@@ -0,0 +1,191 @@
+/* jshint -W117, -W030 */
+describe('Submit Develop Files Controller', function() {
+  var controller, vm, scope;
+
+  var mockChallenge = {
+    challenge: {
+      name: 'Challenge Name',
+      track: 'DEVELOP',
+      id: 30049240
+    }
+  };
+
+  var userService = {
+    getUserIdentity: function() {
+      return {
+        userId: 123456
+      };
+    }
+  };
+
+  var submissionsService = {
+    getPresignedURL: function() {}
+  };
+
+  var mockWindow = {
+    location: {
+      reload: function(val) { return val; }
+    }
+  };
+
+  beforeEach(function() {
+    bard.appModule('tc.submissions');
+    bard.inject(this, '$controller', '$rootScope');
+
+    scope = $rootScope.$new();
+  });
+
+  bard.verifyNoOutstandingHttpRequests();
+
+  beforeEach(function() {
+    controller = $controller('SubmitDevelopFilesController', {
+      $scope: scope,
+      UserService: userService,
+      challengeToSubmitTo: mockChallenge,
+      SubmissionsService: submissionsService,
+      $window: mockWindow
+    });
+    vm = controller;
+  });
+
+  it('exists', function() {
+    expect(vm).to.exist;
+  });
+
+  it('sets the right track for the method', function() {
+    controller = $controller('SubmitDevelopFilesController', {
+      $scope: scope,
+      UserService: userService,
+      challengeToSubmitTo: {
+        challenge: {
+          name: 'Challenge Name',
+          track: 'DESIGN',
+          id: 30049240
+        }
+      },
+      SubmissionsService: submissionsService,
+      $window: mockWindow
+    });
+    vm = controller;
+    scope.$digest();
+
+    expect(vm.submissionsBody.data.method).to.equal('DESIGN_CHALLENGE_ZIP_FILE');
+  });
+
+  describe('setFileReference', function() {
+    var file, fieldId;
+
+    beforeEach(function() {
+      // TODO: change to be more relevant to develop
+      file = {
+        name: 'Dashboard 2.png',
+        size: 575548,
+        type: 'image/png'
+      };
+      fieldId = 'DESIGN_COVER';
+
+      vm.setFileReference(file, fieldId);
+      scope.$digest();
+    });
+
+    afterEach(function() {
+      file = undefined;
+      fieldId = undefined;
+    });
+
+    it('adds a file object to the submissions body', function() {
+      expect(vm.submissionsBody.data.files).to.have.length(1);
+    });
+
+    it('replaces a file object with a new one if it has the same fieldId', function() {
+      expect(vm.submissionsBody.data.files).to.have.length(1);
+
+      // TODO: change to be more relevant to develop submissions
+      var newFile = {
+        name: 'different_image.png',
+        size: 4321,
+        type: 'image/png'
+      };
+
+      vm.setFileReference(newFile, fieldId);
+      scope.$digest();
+
+      expect(vm.submissionsBody.data.files).to.have.length(1);
+      expect(vm.submissionsBody.data.files[0].name).to.equal('different_image.png');
+    });
+
+    it('sets the correct mediaTypes on the fileObject', function() {
+      // TODO: change to be more relevant to develop
+      expect(vm.submissionsBody.data.files[0].mediaType).to.equal('image/png');
+
+      var newFile = {
+        name: 'submission.zip',
+        size: 43121,
+        type: 'application/zip'
+      };
+      var newFieldId = 'SUBMISSION_ZIP';
+
+      vm.setFileReference(newFile, newFieldId);
+      scope.$digest();
+
+      expect(vm.submissionsBody.data.files[1].mediaType).to.equal('application/octet-stream');
+
+      var newFile2 = {
+        name: 'source.zip',
+        size: 2314,
+        type: 'application/zip'
+      };
+      var newFieldId2 = 'SOURCE_ZIP';
+
+      vm.setFileReference(newFile2, newFieldId2);
+      scope.$digest();
+
+      expect(vm.submissionsBody.data.files[2].mediaType).to.equal('application/octet-stream');
+    });
+  });
+
+  describe('uploadSubmission', function() {
+    it('adds comments to the submissions body', function() {
+      vm.comments = 'test comments';
+      scope.$digest();
+
+      vm.uploadSubmission();
+      scope.$digest();
+
+      expect(vm.submissionsBody.data.submitterComments).to.equal('test comments');
+    });
+
+    it('calls the submission service', function() {
+      var mockAPICall = sinon.spy(submissionsService, 'getPresignedURL');
+
+      vm.uploadSubmission();
+      scope.$digest();
+
+      expect(mockAPICall).calledOnce;
+    });
+  });
+
+  describe('refreshPage', function() {
+    it('reloads the page', function() {
+      var mockRefresh = sinon.spy(mockWindow.location, 'reload');
+
+      vm.refreshPage();
+      scope.$digest();
+
+      expect(mockRefresh).calledWith(true);
+      expect(mockRefresh).calledOnce;
+    });
+  });
+
+  describe('cancelRetry', function() {
+    it('sets showProgress to false', function() {
+      vm.showProgress = true;
+      scope.$digest();
+      expect(vm.showProgress).to.be.true;
+
+      vm.cancelRetry();
+      scope.$digest();
+      expect(vm.showProgress).to.be.false;
+    });
+  });
+});
diff --git a/app/submissions/submit-file/submit-file.spec.js b/app/submissions/submit-file/submit-file.spec.js
deleted file mode 100644
index d24961aca..000000000
--- a/app/submissions/submit-file/submit-file.spec.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/* jshint -W117, -W030 */
-describe('Submit File Controller', function() {
-  var controller;
-  var vm;
-  var scope;
-
-  var mockChallenge = {
-    challenge: {
-      name: 'Challenge Name',
-      track: 'DESIGN',
-      id: 30049240
-    }
-  };
-
-  userService = {
-    getUserIdentity: function() {
-      return {
-        userId: 123456
-      };
-    }
-  };
-
-  beforeEach(function() {
-    bard.appModule('tc.submissions');
-    bard.inject(this, '$controller', '$rootScope');
-
-    scope = $rootScope.$new();
-  });
-
-  bard.verifyNoOutstandingHttpRequests();
-
-  beforeEach(function() {
-    controller = $controller('SubmitFileController', {
-      $scope: scope,
-      UserService: userService,
-      challengeToSubmitTo: mockChallenge
-    });
-    vm = controller;
-  });
-
-  it('should exist', function() {
-    expect(vm).to.exist;
-  });
-
-  describe('updateProgress ', function() {
-    it('should update PREPARE phase end ', function() {
-      
-    });
-  });
-});
diff --git a/app/topcoder.routes.js b/app/topcoder.routes.js
index e063cb6e5..2b9ca95b1 100644
--- a/app/topcoder.routes.js
+++ b/app/topcoder.routes.js
@@ -42,10 +42,8 @@
           window.location.href = CONSTANTS.MAIN_URL + '/404/';
         }]
       },
-      /**
-       * Base state that all other routes should inherit from.
-       * Child routes can override any of the specified regions
-       */
+      // Base state that all other routes should inherit from.
+      // Child routes can override any of the specified regions
       'root': {
         url: '',
         abstract: true,
@@ -70,10 +68,8 @@
         }
       },
       'home': {
-        // TODO - set new home page
         parent: 'root',
         url: '/',
-        // template: 'This is the home page',
         controller: ['$state', function($state) {
           $state.go('dashboard');
         }]
diff --git a/assets/css/directives/challenge-links.directive.scss b/assets/css/directives/challenge-links.directive.scss
index 06833d2fb..537eaf9c8 100644
--- a/assets/css/directives/challenge-links.directive.scss
+++ b/assets/css/directives/challenge-links.directive.scss
@@ -8,15 +8,13 @@
   width: 100%;
   border-bottom-right-radius: 3px;
   border-bottom-left-radius: 3px;
-  padding: 0 20px;
-  margin-bottom: 10px;
 
   a {
     text-decoration: none;
     p {
       color: $accent-gray;
       @include font-with-weight('Sofia Pro', 500);
-      font-size: 12px;
+      font-size: 10px;
       line-height: 14px;
       text-transform: uppercase;
 
diff --git a/assets/css/directives/challenge-tile.scss b/assets/css/directives/challenge-tile.scss
index 823bb3392..de968f997 100644
--- a/assets/css/directives/challenge-tile.scss
+++ b/assets/css/directives/challenge-tile.scss
@@ -3,38 +3,25 @@
 // Default Challenge Tile Stylings
 challenge-tile .challenge.tile-view {
 
-  // common css for both active and completed challenge for tile-view
-  height: 350px;
-  display: flex;
-  flex-direction: row;
-
-  .challenge-track {
-    width: 5px;
-    height: 91px;
-    position: absolute;
-    top: -1px;
-    left: -1px;
-    border-top-left-radius: 4px;
-  }
-
   header {
     width: 270px;
-    height: 91px;
     border-bottom: 1px solid #F0F0F0;
     display: flex;
     flex-direction: column;
     justify-content: space-between;
+    padding: 5px 10px;
 
     a.name {
       display: block;
-      padding: 15px 20px 0px 20px;
       @include font-with-weight('Sofia Pro', 500);
-      font-size: 14px;
-      line-height: 20px;
+      font-size: 12px;
+      line-height: 16px;
+      max-height: 48px;
+      overflow: hidden;
       color: $gray-darkest;
-      @include ellipsis;
       @include link;
       text-transform: uppercase;
+      margin-bottom: 5px;
 
       &:hover {
         text-decoration: none;
@@ -42,27 +29,34 @@ challenge-tile .challenge.tile-view {
     }
 
     p.subtrack-color {
-      padding: 0 20px;
-      margin-top: 5px;
       @include font-with-weight('Sofia Pro', 500);
-      font-size: 12px;
+      font-size: 10px;
       line-height: 14px;
       text-transform: uppercase;
+      margin-bottom: 5px;
     }
   }
 
+  .challenge-card__bottom {
+    width: 268px;/* 2px adjustment for 2 1px borders */
+    flex: 2;
+    display: flex;
+    flex-direction: column;
+  }
+
   // challenge details section
   .challenge-details {
     display: flex;
     flex-direction: column;
     align-items: center;
+    justify-content: center;
+    flex: 2;
   }
 
   // roles bar is common for both active and completed
   .roles {
-    width: 101%;
-    border: 1px solid #e0e0e0;
-    border-top: none;
+    width: 100%;
+    border-radius: 0px 0px 4px 4px;
     display: flex;
     flex-direction: row;
     justify-content: flex-start;
@@ -86,6 +80,10 @@ challenge-tile .challenge.tile-view {
   }
 
   .active-challenge {
+    height: 390px;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
     position: relative;
     width: 270px;
     border: 1px solid #E0E0E0;
@@ -95,7 +93,6 @@ challenge-tile .challenge.tile-view {
 
     .challenge-details {
       .currentPhase {
-        margin-top: 40px;
         margin-bottom: 20px;
         @include font-with-weight('Sofia Pro', 300);
         font-size: 18px;
@@ -110,7 +107,6 @@ challenge-tile .challenge.tile-view {
         align-items: center;
         width: 75px;
         height: 63px;
-        margin-bottom: 20px;
         background-image: url(/images/ico-calendar.svg);
 
         > p {
@@ -190,6 +186,7 @@ challenge-tile .challenge.tile-view {
 
 
   .completed-challenge {
+    height: 390px;
     display: flex;
     flex-direction: column;
     justify-content: space-between;
@@ -206,12 +203,11 @@ challenge-tile .challenge.tile-view {
 
       .date-completed {
         @include font-with-weight('Sofia Pro', 500);
-        font-size: 12px;
+        font-size: 10px;
+        line-height: 14px;
         text-transform: uppercase;
         color: $accent-gray;
-        padding-left: 20px;
-        padding-right: 20px;
-        margin-bottom: 10px;
+        margin-bottom: 5px;
       }
 
       .winner-ribbon {
@@ -233,12 +229,35 @@ challenge-tile .challenge.tile-view {
         justify-content: center;
       }
 
+      design-challenge-user-place {
+        display: flex;
+        flex-direction: column;
+        flex: 2;
+
+        .tile-view {
+          flex: 2;
+          justify-content: flex-end;
+        }
+      }
+
+      dev-challenge-user-place {
+        display: flex;
+        flex-direction: column;
+        flex: 2;
+
+        .tile-view {
+          flex: 2;
+        }
+      }
+
       .marathon-score {
-        margin-bottom: 70px;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
         text-align: center;
 
         .score {
-          margin-top: 28px;
           margin-bottom: 5px;
           @include font-with-weight;
           font-size: 32px;
@@ -279,16 +298,6 @@ challenge-tile .challenge.list-view {
 
   // common styles for active and completed
 
-  .challenge-track {
-    width: 5px;
-    height: 110px;
-    position: absolute;
-    top: -1px;
-    left: -1px;
-    border-top-left-radius: 6px;
-    border-bottom-left-radius: 6px;
-  }
-
   header {
     display: flex;
     flex-direction: column;
@@ -302,10 +311,11 @@ challenge-tile .challenge.list-view {
     a.name {
       display: block;
       @include font-with-weight('Sofia Pro', 500);
-      font-size: 14px;
-      line-height: 20px;
+      font-size: 12px;
+      line-height: 16px;
+      max-height: 32px;
+      overflow: hidden;
       color: $gray-darkest;
-      @include ellipsis;
       @include link;
       text-transform: uppercase;
 
@@ -315,7 +325,6 @@ challenge-tile .challenge.list-view {
     }
 
     p.subtrack-color {
-      margin-bottom: 12px;
       @include font-with-weight('Sofia Pro', 500);
       font-size: 12px;
       line-height: 14px;
@@ -449,22 +458,66 @@ challenge-tile .challenge.list-view {
 
 // Dynamic colors based on track
 .DESIGN {
-  .challenge-track { background-color: $design; }
+  &.tile-view {
+    header {
+      border-left: 3px solid $design;
+      border-radius: 3px 0 0 0;
+    }
+  }
 
-  header .subtrack-color { color: $design; }
+  &.challenge.list-view {
+    border-left: 3px solid $design;
+  }
+
+  .subtrack-color {
+    color: $design;
+  }
 }
 .DEVELOP {
-  .challenge-track { background-color: $develop; }
+  &.tile-view {
+    header {
+      border-left: 3px solid $develop;
+      border-radius: 3px 0 0 0;
+    }
+  }
 
-  header .subtrack-color { color: $develop; }
+  &.challenge.list-view {
+    border-left: 3px solid $develop;
+  }
+
+  .subtrack-color {
+    color: $develop;
+  }
 }
 .DATA_SCIENCE {
-  .challenge-track { background-color: $data_science; }
+  &.tile-view {
+    header {
+      border-left: 3px solid $data_science;
+      border-radius: 3px 0 0 0;
+    }
+  }
 
-  header .subtrack-color { color: $data_science; }
+  &.challenge.list-view {
+    border-left: 3px solid $data_science;
+  }
+
+  .subtrack-color {
+    color: $data_science;
+  }
 }
 .COPILOT {
-  .challenge-track { background-color: $copilot; }
+  &.tile-view {
+    header {
+      border-left: 3px solid $copilot;
+      border-radius: 3px 0 0 0;
+    }
+  }
+
+  &.challenge.list-view {
+    border-left: 3px solid $copilot;
+  }
 
-  header .subtrack-color { color: $copilot; }
+  .subtrack-color {
+    color: $copilot;
+  }
 }
diff --git a/assets/css/directives/design-challenge-user-place.scss b/assets/css/directives/design-challenge-user-place.scss
index 133a86446..540d71b80 100644
--- a/assets/css/directives/design-challenge-user-place.scss
+++ b/assets/css/directives/design-challenge-user-place.scss
@@ -6,15 +6,10 @@ design-challenge-user-place {
   .tile-view {
     display: flex;
     flex-direction: column;
+    justify-content: center;
     align-items: center;
 
     .place {
-      &.completed, &.passed, &.didnt {
-        margin-bottom: -36px;
-        margin-top: 52px;
-      }
-      margin-bottom: 8px;
-      margin-top: 8px;
       @include sofia-pro-regular;
       font-size: 20px;
       color: $gray-darkest;
diff --git a/assets/css/directives/dev-challenge-user-place.scss b/assets/css/directives/dev-challenge-user-place.scss
index b02ad190f..69f0ca8c5 100644
--- a/assets/css/directives/dev-challenge-user-place.scss
+++ b/assets/css/directives/dev-challenge-user-place.scss
@@ -4,8 +4,12 @@
 dev-challenge-user-place {
 
   .tile-view {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    
     .place {
-      margin-bottom: 30px;
       @include sofia-pro-light;
       font-size: 18px;
       line-height: 23px;
diff --git a/assets/css/layout/header.scss b/assets/css/layout/header.scss
index 9336d0a32..10959916a 100644
--- a/assets/css/layout/header.scss
+++ b/assets/css/layout/header.scss
@@ -539,13 +539,8 @@
         padding-right: 0;
       }
 
-      a {
-        font-weight: normal;
-        padding: 8px 15px;
-
-        &:not(:last-child) {
-          margin-right: 12px;
-        }
+      a:not(:last-child) {
+        margin-right: 12px;
       }
     }
   }
diff --git a/assets/css/my-challenges/my-challenges.scss b/assets/css/my-challenges/my-challenges.scss
index b1a24e241..1e5086822 100644
--- a/assets/css/my-challenges/my-challenges.scss
+++ b/assets/css/my-challenges/my-challenges.scss
@@ -176,7 +176,7 @@
 
         challenge-tile {
           &.tile-view {
-            margin-bottom: 20px;
+            margin-bottom: 13px;
             @media only screen and (max-width: 767px) {
               display: inline-block;
               margin-left: 15px;
diff --git a/assets/css/submissions/submissions.scss b/assets/css/submissions/submissions.scss
index b34324350..e60c2898b 100644
--- a/assets/css/submissions/submissions.scss
+++ b/assets/css/submissions/submissions.scss
@@ -2,6 +2,9 @@
 
 .mobile-redirect {
   padding: 45px 15px 30px;
+  // Not showing the mobile version of the page at all
+  // Delete the line below to show
+  display: none;
   @media screen and (min-width: 768px) {
     display: none;
   }
@@ -41,3 +44,9 @@
     }
   }
 }
+
+.submissions-access-error {
+  text-align: center;
+  margin-left: auto;
+  margin-right: auto;
+}
diff --git a/assets/css/submissions/submit-file.scss b/assets/css/submissions/submit-file.scss
index f1e801527..217e7af95 100644
--- a/assets/css/submissions/submit-file.scss
+++ b/assets/css/submissions/submit-file.scss
@@ -2,7 +2,15 @@
 
 .panel-body {
   @media screen and (max-width: 767px) {
-    display: none;
+    // Not showing the mobile version of the page at all
+    // Uncomment the line below to hide the desktop page
+    // display: none;
+  }
+}
+
+.form-blocks div:nth-last-of-type(2) {
+  .form-block__fields {
+    border-bottom: none;
   }
 }
 
@@ -40,17 +48,72 @@
   }
 }
 
+.tc-error-messages {
+  max-width: 500px;
+}
+
+tc-file-input {
+  .tc-file-field__label, .tc-file-field__inputs {
+    margin: 0 auto;
+    @media screen and (min-width: 1000px) {
+      margin: 0;
+    }
+  }
+
+  & + .tc-error-messages {
+    max-width: 407px;
+  }
+
+  .tc-label__asterisk {
+    color: $primary;
+  }
+}
+
+
 .submitterRank {
   width: 100px;
   margin-top: 10px;
 }
 
+.tc-textarea {
+  max-width: 500px;
+}
+
+tc-form-fonts, tc-form-stockart {
+  width: 500px;
+  display: block;
+  margin: 0 auto;
+  @media screen and (min-width: 1000px) {
+    margin: 0;
+    display: inline;
+  }
+  .fieldset {
+    max-width: 500px;
+    position: relative;
+  }
+}
+
+.remove-section {
+  position: absolute;
+  width: 20px;
+  height: 20px;
+  top: 15px;
+  right: -35px;
+  background-image: url(/images/x-mark-gray.svg);
+  background-size: 20px;
+  outline: 0;
+  @media screen and (min-width: 1000px) {
+    top: -5px;
+  }
+}
+
+.Select {
+  margin-bottom: 20px;
+}
+
 modal {
-  .close {
-    .icon.cross {
-      background-image: url(/images/x-mark-gray.svg);
-      background-size: 25px;
-    }
+  > button {
+    display: none;
   }
 }
 .upload-progress {
@@ -96,6 +159,7 @@ modal {
     font-size: 12px;
     line-height: 14px;
     color: $accent-gray-dark;
+    text-align: center;
   }
 
   .upload-progress__error {
@@ -115,28 +179,14 @@ modal {
   }
 }
 
-tc-form-fonts, tc-form-stockart {
-  .fieldset {
-    max-width: 500px;
-    position: relative;
-  }
-}
-
-.remove-section {
-  position: absolute;
-  width: 20px;
-  height: 20px;
-  top: -5px;
-  right: -35px;
-  background-image: url(/images/x-mark-gray.svg);
-  background-size: 20px;
-  outline: 0;
-}
+.upload-progess__links {
+  margin-top: 40px;
 
-.Select {
-  margin-bottom: 20px;
-}
+  a {
+    display: inline-block;
 
-.tc-error-messages {
-  max-width: 500px;
+    &:first-child {
+      margin-right: 10px;
+    }
+  }
 }
diff --git a/bower.json b/bower.json
index 8bedd83b4..0a55b1580 100644
--- a/bower.json
+++ b/bower.json
@@ -39,7 +39,7 @@
     "angular-ui-router": "~0.2.15",
     "angular-xml": "~2.1.1",
     "angularjs-toaster": "~0.4.15",
-    "appirio-tech-ng-iso-constants": "git@github.com:appirio-tech/ng-iso-constants#~1.0.5",
+    "appirio-tech-ng-iso-constants": "git@github.com:appirio-tech/ng-iso-constants#~1.0.6",
     "appirio-tech-ng-ui-components": "appirio-tech/ng-ui-components#bower-wiredep-fix",
     "d3": "~3.5.6",
     "fontawesome": "~4.3.0",