diff --git a/.version b/.version index 3ec7c5c4f..afd826259 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v1.0.17 +v1.0.18 diff --git a/app/community/members.jade b/app/community/members.jade index c3b356b47..e8c6f8dc3 100644 --- a/app/community/members.jade +++ b/app/community/members.jade @@ -28,8 +28,9 @@ small {{ctrl.currentMonth}} .members-wrapper .user-tile(ng-repeat="item in ctrl.memberLeaderboard") - .avatar-wrapper: img(ng-src="{{item.avatar}}") - .user-name {{item.name}} + a.avatar-wrapper(ui-sref="profile.about({userHandle: item.name})") + img(ng-src="{{item.avatar}}") + a.user-name(ui-sref="profile.about({userHandle: item.name})") {{item.name}} .user-tag(class="{{item.class}}") {{item.contestType}} p.user-desc {{item.description}} a(ng-href="//www.{{ctrl.domain}}/community/member-programs/topcoder-member-of-the-month/", target="_blank").user-more Read the story diff --git a/app/directives/challenge-user-place/challenge-user-place.directive.js b/app/directives/challenge-user-place/challenge-user-place.directive.js index 402ce6f8e..23add31a8 100644 --- a/app/directives/challenge-user-place/challenge-user-place.directive.js +++ b/app/directives/challenge-user-place/challenge-user-place.directive.js @@ -59,7 +59,9 @@ $scope.imageURL = $scope.challenge.userDetails.submissions[0].submissionImage && $scope.challenge.userDetails.submissions[0].submissionImage.full; $scope.selectedImage = $scope.imageURL; - $scope.challenge.highestPlacement = _.max($scope.challenge.userDetails.submissions, 'placement').placement; + $scope.challenge.highestPlacement = _.min($scope.challenge.userDetails.submissions.filter(function(submission) { + return submission.type === CONSTANTS.SUBMISSION_TYPE_CONTEST && submission.placement; + }), 'placement').placement; if ($scope.challenge.highestPlacement == 1) { $scope.challenge.wonFirst = true; diff --git a/app/directives/external-account/external-web-links.directive.js b/app/directives/external-account/external-web-links.directive.js index 3367d474b..187d76e98 100644 --- a/app/directives/external-account/external-web-links.directive.js +++ b/app/directives/external-account/external-web-links.directive.js @@ -38,6 +38,9 @@ $log.debug("Web link added: " + JSON.stringify(data)); data.data.provider = data.provider; $scope.linkedAccounts.push(data.data); + // reset the form when it is successfully added + $scope.addWebLinkFrm.$setPristine(); + $scope.url = null; toaster.pop('success', "Success", "Your link has been added. Data from your link will be visible on your profile shortly."); }) .catch(function(resp) { diff --git a/app/directives/external-account/external-web-links.directive.spec.js b/app/directives/external-account/external-web-links.directive.spec.js index 6e8997e8b..3385a7706 100644 --- a/app/directives/external-account/external-web-links.directive.spec.js +++ b/app/directives/external-account/external-web-links.directive.spec.js @@ -90,24 +90,29 @@ describe('ExternalWebLinks Directive', function() { }); expect(topcoderLink).to.exist; expect(topcoderLink.status).to.exist.to.equal('PENDING'); + expect(element.isolateScope().url).not.to.exist; expect(toasterSvc.pop).to.have.been.calledWith('success').calledOnce; }); it('should NOT add new weblink to linkedAccounts', function() { + var urlToAdd = 'https://www.topcoder.com'; element.isolateScope().userHandle = 'throwError'; - element.isolateScope().url = 'https://www.topcoder.com'; + element.isolateScope().url = urlToAdd; element.isolateScope().addWebLink(); scope.$digest(); expect(scope.linkedAccounts).to.have.length(1); + expect(element.isolateScope().url).to.exist.to.equal(urlToAdd); expect(toasterSvc.pop).to.have.been.calledWith('error').calledOnce; }); it('should NOT add new weblink to linkedAccounts', function() { + var urlToAdd = 'https://www.topcoder.com'; element.isolateScope().userHandle = 'alreadyExistsError'; - element.isolateScope().url = 'https://www.topcoder.com'; + element.isolateScope().url = urlToAdd; element.isolateScope().addWebLink(); scope.$digest(); expect(scope.linkedAccounts).to.have.length(1); + expect(element.isolateScope().url).to.exist.to.equal(urlToAdd); expect(toasterSvc.pop).to.have.been.calledWith('error').calledOnce; }); diff --git a/app/directives/srm-tile/srm-tile.directive.jade b/app/directives/srm-tile/srm-tile.directive.jade index 9709b2ab4..fc6b92c95 100644 --- a/app/directives/srm-tile/srm-tile.directive.jade +++ b/app/directives/srm-tile/srm-tile.directive.jade @@ -3,7 +3,7 @@ .challenge-track header - a(ng-href="https://community.{{DOMAIN}}/tc?module=MatchDetails&rd={{srm.rounds[0].id}}") {{srm.name}} + a(ng-href="https://community.{{DOMAIN}}/stat?c=round_overview&rd={{roundId}}") {{srm.name}} .srm-details p.starts-in Starts in #[span {{srm.codingStartAt | timeDiff:"quantity"}} {{srm.codingStartAt | timeDiff:'unit'}}] @@ -17,22 +17,22 @@ .phase-status .registered(ng-show="srm.userStatus === CONSTANTS.REGISTERED") Registered .unregistered(ng-hide="srm.currentPhase !== CONSTANTS.REGISTRATION || srm.userStatus === CONSTANTS.REGISTERED") - a.tc-btn.tc-btn-s.tc-btn-wide(href="https://community.{{DOMAIN}}/tc?module=MatchDetails&rd={{srm.rounds[0].id}}") Register + a.tc-btn.tc-btn-s.tc-btn-wide(href="https://community.{{DOMAIN}}/tc?module=MatchDetails&rd={{roundId}}") Register .past-srm(ng-show="srm.status === 'PAST'") .challenge-track header - a(ng-href="https://community.{{DOMAIN}}/tc?module=MatchDetails&rd={{srm.rounds[0].id}}") {{srm.name}} + a(ng-href="https://community.{{DOMAIN}}/stat?c=round_overview&rd={{roundId}}") {{srm.name}} .ended-on #[span {{srm.codingEndAt | localTime:"MMM DD, YYYY" }}] .member-stats p.points #[span {{srm.result.finalPoints }}] #[span Points] .ranks - .division + a.division(href="https://community.{{DOMAIN}}/stat?c=round_stats&rd={{roundId}}&dn={{division}}") p.rank {{srm.result.divisionPlacement}} p.label #[span Division {{srm.result.division}}] - .room + a.room(href="https://community.{{DOMAIN}}/stat?c=coder_room_stats&cr={{userId}}&rd={{roundId}}") p.rank #[span {{srm.result.roomPlacement}}] p.label #[span Room] p.placement Placement @@ -42,7 +42,7 @@ .challenge-track header .srm-name - a(ng-href="https://community.{{DOMAIN}}/tc?module=MatchDetails&rd={{srm.rounds[0].id}}") {{srm.name}} + a(ng-href="https://community.{{DOMAIN}}/stat?c=round_overview&rd={{roundId}}") {{srm.name}} .srm-details p.starts-in Starts in #[span {{srm.codingStartAt | timeDiff:"quantity"}} {{srm.codingStartAt | timeDiff:'unit'}}] @@ -50,13 +50,13 @@ .phase-status .registered(ng-show="srm.userStatus === CONSTANTS.REGISTERED") Registered .unregistered(ng-hide="srm.currentPhase !== CONSTANTS.REGISTRATION || srm.userStatus === CONSTANTS.REGISTERED") - a.tc-btn.tc-btn-s.tc-btn-wide(href="https://community.{{DOMAIN}}/tc?module=MatchDetails&rd={{srm.rounds[0].id}}") Register + a.tc-btn.tc-btn-s.tc-btn-wide(href="https://community.{{DOMAIN}}/tc?module=MatchDetails&rd={{roundId}}") Register .past-srm(ng-show="srm.status === 'PAST'") .challenge-track header .srm-name - a(ng-href="https://community.{{DOMAIN}}/tc?module=MatchDetails&rd={{srm.rounds[0].id}}") {{srm.name}} + a(ng-href="https://community.{{DOMAIN}}/stat?c=round_overview&rd={{roundId}}") {{srm.name}} .srm-details p.ended-on Ended {{srm.codingEndAt | timeDiff:"quantity"}} {{srm.codingEndAt | timeDiff:'unit'}} ago @@ -65,10 +65,10 @@ p.points #[span {{srm.result.finalPoints }}] Points .ranks - .division + a.division(ng-class="{noclick: !srm.result.divisionPlacement}", href="https://community.{{DOMAIN}}/stat?c=round_stats&rd={{roundId}}&dn={{division}}") p.rank {{srm.result.divisionPlacement}} p.label #[span Div {{srm.result.division}}] #[span Placement] - .room + a.room(ng-class="{noclick: !srm.result.roomPlacement}", href="https://community.{{DOMAIN}}/stat?c=coder_room_stats&cr={{userId}}&rd={{roundId}}") p.rank #[span {{srm.result.roomPlacement}}] p.label #[span Room] #[span Placement] diff --git a/app/directives/srm-tile/srm-tile.directive.js b/app/directives/srm-tile/srm-tile.directive.js index 85bf714b8..b5ab8afbc 100644 --- a/app/directives/srm-tile/srm-tile.directive.js +++ b/app/directives/srm-tile/srm-tile.directive.js @@ -8,14 +8,17 @@ srm: '=srm', view: '=', showResults: '=', - showFooter: '=' + showFooter: '=', + userId: '=' }, controller: ['$scope', '$filter', 'CONSTANTS', 'SRMService', function($scope, $filter, CONSTANTS, SRMService) { - $scope.DOMAIN = CONSTANTS.domain; - $scope.CONSTANTS = CONSTANTS; - $scope.srm.userStatus = _.get($scope.srm, 'userStatus', null); - SRMService.processSRM($scope.srm); + $scope.DOMAIN = CONSTANTS.domain; + $scope.CONSTANTS = CONSTANTS; + $scope.srm.userStatus = _.get($scope.srm, 'userStatus', null); + SRMService.processSRM($scope.srm); + $scope.roundId = $scope.srm.rounds.length && $scope.srm.rounds[0].id; + $scope.division = $scope.srm.result && $scope.srm.result.division; }] }; }); diff --git a/app/my-dashboard/srms/srms.controller.js b/app/my-dashboard/srms/srms.controller.js index 01c0ef967..3abf78fd5 100644 --- a/app/my-dashboard/srms/srms.controller.js +++ b/app/my-dashboard/srms/srms.controller.js @@ -12,6 +12,7 @@ var userId = UserService.getUserIdentity().userId; var handle = UserService.getUserIdentity().handle; + vm.userId = userId; activate(); diff --git a/app/my-dashboard/srms/srms.jade b/app/my-dashboard/srms/srms.jade index 1133ed0b7..8bf637b6a 100644 --- a/app/my-dashboard/srms/srms.jade +++ b/app/my-dashboard/srms/srms.jade @@ -6,7 +6,7 @@ header tc-section(state="vm.state") .srm-tiles - srm-tile(ng-repeat="srm in vm.srms", srm="srm", view="'tile'", ng-class="'tile-view'") + srm-tile(ng-repeat="srm in vm.srms", srm="srm", view="'tile'", ng-class="'tile-view'", user-id="vm.userId") .srm-links-card .flex-wrapper diff --git a/app/my-srms/my-srms.controller.js b/app/my-srms/my-srms.controller.js index b1422697e..965363983 100644 --- a/app/my-srms/my-srms.controller.js +++ b/app/my-srms/my-srms.controller.js @@ -28,6 +28,7 @@ var userId = UserService.getUserIdentity().userId; var userHandle = UserService.getUserIdentity().handle; + vm.userId = userId; vm.handle = userHandle; activate(); diff --git a/app/my-srms/my-srms.jade b/app/my-srms/my-srms.jade index fe042f3df..6a91b1314 100644 --- a/app/my-srms/my-srms.jade +++ b/app/my-srms/my-srms.jade @@ -23,6 +23,6 @@ .data(ng-class="vm.view + '-view'") srm-tile.srm-tile( ng-repeat="srm in vm.srms | orderBy:vm.orderBy:vm.reverseOrder", - srm="srm", view="vm.view", ng-class="vm.view + '-view'", show-results="vm.statusFilter === 'past'") + srm="srm", view="vm.view", ng-class="vm.view + '-view'", show-results="vm.statusFilter === 'past'", user-id="vm.userId") tc-endless-paginator(state="vm.loading", page-params="vm.pageParams") diff --git a/app/profile/subtrack/data/data-challenges.jade b/app/profile/subtrack/data/data-challenges.jade index 5055280de..e4f08fcc9 100644 --- a/app/profile/subtrack/data/data-challenges.jade +++ b/app/profile/subtrack/data/data-challenges.jade @@ -10,7 +10,7 @@ .challenges(ng-show="vm.subTrack == 'SRM'") .challenge.tile(ng-repeat="challenge in vm.challenges") - srm-tile(srm="challenge", domain="vm.domain", view="'tile'") + srm-tile(srm="challenge", domain="vm.domain", view="'tile'", user-id="profileVm.profile.userId") .no-challenges(ng-show="!vm.challenges || vm.challenges.length == 0") | Sorry, no rated SRMs found. diff --git a/app/services/challenge.service.js b/app/services/challenge.service.js index fddb91c48..8e4fe5c08 100644 --- a/app/services/challenge.service.js +++ b/app/services/challenge.service.js @@ -190,7 +190,7 @@ challenge.thumbnailId = challenge.userDetails.submissions[0].id; challenge.highestPlacement = _.min(challenge.userDetails.submissions.filter(function(submission) { - return submission.placement; + return submission.type === CONSTANTS.SUBMISSION_TYPE_CONTEST && submission.placement; }), 'placement').placement; if (challenge.highestPlacement == 1) { diff --git a/app/skill-picker/skill-picker.controller.js b/app/skill-picker/skill-picker.controller.js index e34eb5f2b..476fbb4b7 100644 --- a/app/skill-picker/skill-picker.controller.js +++ b/app/skill-picker/skill-picker.controller.js @@ -3,26 +3,123 @@ angular.module('tc.skill-picker').controller('SkillPickerController', SkillPickerController); - SkillPickerController.$inject = ['CONSTANTS', 'ProfileService', '$state', 'userProfile', 'featuredSkills', '$log', 'toaster']; + SkillPickerController.$inject = ['$scope', 'CONSTANTS', 'ProfileService', '$state', 'userProfile', 'featuredSkills', '$log', 'toaster', 'MemberCertService', '$q']; - function SkillPickerController(CONSTANTS, ProfileService, $state, userProfile, featuredSkills, $log, toaster) { + function SkillPickerController($scope, CONSTANTS, ProfileService, $state, userProfile, featuredSkills, $log, toaster, MemberCertService, $q) { var vm = this; $log = $log.getInstance("SkillPickerController"); vm.ASSET_PREFIX = CONSTANTS.ASSET_PREFIX; + vm.IOS_PROGRAM_ID = CONSTANTS.SWIFT_PROGRAM_ID; vm.submitSkills = submitSkills; vm.featuredSkills = featuredSkills; + vm.userId = userProfile.userId; vm.username = userProfile.handle; vm.toggleSkill = toggleSkill; vm.tracks = {}; vm.mySkills = []; vm.disableDoneButton = false; + vm.showCommunity = false; + vm.loadingCommunities = false; + vm.communities = {}; + vm.isPageDirty = isPageDirty; /////// activate(); + /** + * Activates the controller. + */ function activate() { - $log.debug("init") + $log.debug("init"); + initCommunities(); + checkCommunityStatus(); } + /** + * Verfies if the page state has been modified by the user in any way. + */ + function isPageDirty() { + return isTracksDirty() || isCommunitiesDirty(); + } + + /** + * Verfies if the tracks section state has been modified by the user in any way. + */ + function isTracksDirty() { + return vm.tracks.DESIGN || vm.tracks.DEVELOP || vm.tracks.DATA_SCIENCE; + } + + /** + * Verfies if the communities section state has been modified by the user in any way. + */ + function isCommunitiesDirty() { + var community = _.findWhere(vm.communities, {dirty: true}); + return !!community; + } + + /** + * Initializes the communities to show in the communities section. + */ + function initCommunities() { + vm.communities['ios'] = { displayName: 'iOS', programId: vm.IOS_PROGRAM_ID, status: false, dirty: false, display: true}; + _addWatchToCommunity(vm.communities['ios']); + } + + /** + * Helper method to add watch to given object. + */ + function _addWatchToCommunity(community) { + community.unregister = $scope.$watch( + function() { return community; }, + function(newValue, oldValue) { + if (oldValue && newValue.status !== oldValue.status) { + newValue.dirty = oldValue.dirty ? false : true; + } + }, + true + ); + } + + /** + * Checks registration status of each community and updates the state of each community. + */ + function checkCommunityStatus() { + var promises = []; + for (var name in vm.communities) { + var community = vm.communities[name]; + promises.push(MemberCertService.getMemberRegistration(vm.userId, community.programId)); + } + vm.loadingCommunities = true; + $q.all(promises).then(function(responses) { + vm.loadingCommunities = false; + responses.forEach(function(program) { + if (program) { + var community = _.findWhere(vm.communities, {programId: program.eventId}); + if (community) { + // set display false to avoid showing already enabled/registered program + // we expect display property to be modified after first load of the page + community.display = false; + community.status = true; + if (community.unregister){ + community.unregister(); + _addWatchToCommunity(community); + } + } + } + }); + // if there exists at least 1 community which can be displayed, set showCommunity flag to true + var community = _.findWhere(vm.communities, {display: true}); + if (community) { + vm.showCommunity = true; + } + }) + .catch(function(error) { + vm.loadingCommunities = false; + }); + } + + /** + * Toggles the given skill for the user. If it is not added, adds it and if already added, removes it. + */ function toggleSkill(tagId) { var _idx = vm.mySkills.indexOf(tagId.toString()); if (_idx > -1) { @@ -34,50 +131,56 @@ } } + /** + * Persists the user's altered information. + */ function submitSkills() { vm.saving = true; - // save tracks - userProfile.tracks = _.reduce(vm.tracks, function(result, isInterested, trackName) { - if (isInterested) { - result.push(trackName); - } - return result; - }, []); - userProfile.save().then(function(data) { - if (vm.mySkills.length > 0) { - // save skills - var data = {}; - for (var i = 0; i < vm.mySkills.length; i++) { - data[vm.mySkills[i]] = { - hidden: false - }; + var promises = []; + if (isTracksDirty()) { + // save tracks + userProfile.tracks = _.reduce(vm.tracks, function(result, isInterested, trackName) { + if (isInterested) { + result.push(trackName); + } + return result; + }, []); + promises.push(userProfile.save()); + } + if (vm.mySkills.length > 0) { + // save skills + var data = {}; + for (var i = 0; i < vm.mySkills.length; i++) { + data[vm.mySkills[i]] = { + hidden: false + }; + } + promises.push(ProfileService.updateUserSkills(vm.username, data)); + } + $log.debug('isCommunitiesDirty: ' + isCommunitiesDirty()); + if (isCommunitiesDirty()) { + for(var communityName in vm.communities) { + var community = vm.communities[communityName]; + if (community.dirty === true) { + if (community.status === true) { + promises.push(MemberCertService.registerMember(vm.userId, community.programId)); } - ProfileService.updateUserSkills(vm.username, data) - .then(function(resp) { - vm.saving = false; - toaster.pop('success', "Success!", "Your skills have been updated."); - vm.disableDoneButton = true; - $state.go('dashboard'); - }) - .catch(function(data) { - vm.saving = false; - toaster.pop('error', "Whoops", "Something went wrong. Please try again later."); - }) - } else { - vm.saving = false; - toaster.pop('success', "Success!", "Your skills have been updated."); - vm.disableDoneButton = true; - $state.go('dashboard'); } + } + } - }) - .catch(function(resp) { - vm.saving = false; - toaster.pop('error', "Whoops", "Something went wrong. Please try again later."); - }) - + $q.all(promises).then(function(responses) { + vm.saving = false; + toaster.pop('success', "Success!", "Your skills have been updated."); + vm.disableDoneButton = true; + $state.go('dashboard'); + }) + .catch(function(resp) { + vm.saving = false; + toaster.pop('error', "Whoops!", "Something went wrong. Please try again later."); + }); } } })(); diff --git a/app/skill-picker/skill-picker.jade b/app/skill-picker/skill-picker.jade index 63f450525..812f79bc5 100644 --- a/app/skill-picker/skill-picker.jade +++ b/app/skill-picker/skill-picker.jade @@ -3,6 +3,25 @@ p.instruction Hi {{vm.username}}! Your account is now active. To help other members get to know you, select the tracks in which you're interested, and specify some of your skills. You can edit this information later on your Profile. + .communities(ng-show="!vm.loadingCommunities && vm.showCommunity") + .communities__title Communities + .communities__description Topcoder regularly establishes exclusive communities to help members develop expertise and earn money in particular technologies. Join a featured community to be notified of events and challenges. + .communities__list + .community(ng-repeat="(communityKey, community) in vm.communities", ng-class="{'community--disabled': !community.status}", ng-if="community.display") + .community__details + .community__icon(ng-class="{'community__icon--disabled': !community.status}") + img(ng-if="communityKey == 'ios' && community.status", src="/images/ico-ios-community.svg") + img(ng-if="communityKey == 'ios' && !community.status", src="/images/ico-ios-community-grey.svg") + + .community__text + span.community__title(class="{{!community.status && 'disabled'}}") {{community.displayName}} + .community__description + span(ng-if="communityKey == 'ios'") Mobile app design and development for iOS, with Swift emphasis + + onoff-switch(model="community.status", unique-id="'community-' + communityKey") + + + .tracks-container .title tracks .description Topcoder's three categories of challenges… please pick at least one based on your skills and interests. @@ -51,4 +70,4 @@ type="button", tc-busy-button, tc-busy-when="vm.saving", ng-click="vm.submitSkills()", - ng-disabled="vm.disableDoneButton || (!vm.tracks.DESIGN && !vm.tracks.DEVELOP && !vm.tracks.DATA_SCIENCE)") Done + ng-disabled="vm.disableDoneButton || !vm.isPageDirty()") Done diff --git a/app/skill-picker/skill-picker.spec.js b/app/skill-picker/skill-picker.spec.js index f4e938b68..cad2670e8 100644 --- a/app/skill-picker/skill-picker.spec.js +++ b/app/skill-picker/skill-picker.spec.js @@ -1,19 +1,244 @@ /* jshint -W117, -W030 */ describe('Skill Picker Controller', function() { var vm; + var toasterSvc, memberCertService, profileService, state; + var mockProfile = mockData.getMockProfile(); - // beforeEach(function() { - // bard.appModule('tc.skill-picker'); - // bard.inject(this, '$controller', '$rootScope', '$q', 'userProfile'); + beforeEach(function() { + bard.appModule('tc.skill-picker'); + bard.inject(this, '$controller', '$rootScope', '$q', 'MemberCertService', 'ProfileService', 'toaster', 'CONSTANTS'); - // vm = $controller('SkillPickerController', { - // }); - // }); + memberCertService = MemberCertService; + profileService = ProfileService; + var swiftProgramId = CONSTANTS.SWIFT_PROGRAM_ID; - // bard.verifyNoOutstandingHttpRequests(); + // mock member cert api + sinon.stub(memberCertService, 'getMemberRegistration', function(userId, programId) { + var deferred = $q.defer(); + if (programId === swiftProgramId) { + var resp = {eventId: swiftProgramId, userId: 12345}; + deferred.resolve(resp); + } else { + deferred.resolve(); + } + return deferred.promise; + }); + + sinon.stub(memberCertService, 'registerMember', function(userId, programId) { + var deferred = $q.defer(); + var resp = {eventId: programId, userId: 12345}; + if (programId === 100) { + deferred.reject(); + } else { + deferred.resolve(resp); + } + return deferred.promise; + }); - // it('should be created successfully', function() { - // expect(vm).to.exist; - // }); + // adds spy method to mocked profile object for saving the profile + mockProfile.save = sinon.spy(); + + // mock profile service + sinon.stub(profileService, 'updateUserSkills', function(username, skills) { + var deferred = $q.defer(); + var resp = {}; + if (skills[412]) { + deferred.reject(); + } else { + deferred.resolve(resp); + } + return deferred.promise; + }); + + // mocks the toaster service + toasterSvc = toaster; + bard.mockService(toaster, { + pop: $q.when(true), + default: $q.when(true) + }); + + // mocked $state object + state = { go: sinon.spy()}; + + var scope = $rootScope.$new(); + vm = $controller('SkillPickerController', { + $scope: scope, + userProfile: mockProfile, + featuredSkills: [], + $state: state + + }); + $rootScope.$digest(); + }); + + bard.verifyNoOutstandingHttpRequests(); + + it('should be created successfully', function() { + expect(vm).to.exist; + expect(vm.showCommunity).to.exist.to.false; + }); + + it('should have empty tracks object ', function() { + expect(vm.tracks).to.exist; + expect(vm.tracks.DESIGN).not.to.exist; + expect(vm.tracks.DEVELOP).not.to.exist; + expect(vm.tracks.DATA_SCIENCE).not.to.exist; + }); + + it('should have correct userIdentity ', function() { + expect(vm.userId).to.exist.to.equal(mockProfile.userId); + expect(vm.username).to.exist.to.equal(mockProfile.handle); + }); + + it('should not have page dirty ', function() { + var dirty = vm.isPageDirty(); + expect(dirty).to.equal(false); + }); + + it('should be created successfully with showCommunity being true', function() { + // backup original swift program id, to restore after test execution + var origSwiftProgId = CONSTANTS.SWIFT_PROGRAM_ID; + // update swift program id to something which says, member is not registered + CONSTANTS.SWIFT_PROGRAM_ID = 3446; + vm = $controller('SkillPickerController', { + $scope: $rootScope.$new(), + userProfile: mockProfile, + featuredSkills: [] + }); + $rootScope.$digest(); + expect(vm).to.exist; + // showCommunity flag should be set to true because we have at least one unregistered community + expect(vm.showCommunity).to.exist.to.true; + // restores the original swift program id + CONSTANTS.SWIFT_PROGRAM_ID = origSwiftProgId; + }); + + it('should add skill ', function() { + vm.toggleSkill(409); + expect(vm.mySkills).to.exist.have.length(1); + }); + + it('should remove added skill ', function() { + vm.toggleSkill(409); + // should add the skill + expect(vm.mySkills).to.exist.have.length(1); + // next call to toggleSkill with same argument, should remove that skill + vm.toggleSkill(409); + // should remove the skill + expect(vm.mySkills).to.exist.have.length(0); + }); + + it('should not make any update call without any change ', function() { + vm.submitSkills(); + $rootScope.$digest(); + expect(mockProfile.save).not.to.be.called; + expect(profileService.updateUserSkills).not.to.be.called; + expect(memberCertService.registerMember).not.to.be.called; + // we should still go to dashboard if the function is called, + // call to the function is controlled by disabling the button + expect(state.go).to.have.been.calledWith('dashboard').calledOnce; + }); + + it('should update tracks for the member ', function() { + vm.tracks.DEVELOP = true; + vm.tracks.DESIGN = false; + vm.submitSkills(); + $rootScope.$digest(); + expect(mockProfile.save).to.be.calledOnce; + expect(profileService.updateUserSkills).not.to.be.called; + expect(memberCertService.registerMember).not.to.be.called; + expect(state.go).to.have.been.calledWith('dashboard').calledOnce; + }); + + it('should show error popup for updating tracks ', function() { + vm.tracks.DEVELOP = true; + // stubs the save method to reject the promise + mockProfile.save = function() {}; + sinon.stub(mockProfile, 'save', function() { + var deferred = $q.defer(); + deferred.reject(); + return deferred.promise; + }); + vm.submitSkills(); + $rootScope.$digest(); + expect(mockProfile.save).to.be.calledOnce; + expect(toasterSvc.pop).to.have.been.calledWith('error', "Whoops!", sinon.match('wrong')).calledOnce; + expect(profileService.updateUserSkills).not.to.be.called; + expect(memberCertService.registerMember).not.to.be.called; + expect(state.go).not.to.be.called; + }); + + it('should update skills for the member ', function() { + vm.mySkills = [409, 410]; + vm.submitSkills(); + $rootScope.$digest(); + expect(mockProfile.save).not.to.be.called; + expect(profileService.updateUserSkills).to.be.calledOnce; + expect(memberCertService.registerMember).not.to.be.called; + expect(state.go).to.have.been.calledWith('dashboard').calledOnce; + }); + + it('should show error popup for error in updating skills ', function() { + vm.mySkills = [409, 410, 412]; + vm.submitSkills(); + $rootScope.$digest(); + expect(mockProfile.save).not.to.be.called; + expect(profileService.updateUserSkills).to.be.calledOnce; + expect(toasterSvc.pop).to.have.been.calledWith('error', "Whoops!", sinon.match('wrong')).calledOnce; + expect(memberCertService.registerMember).not.to.be.called; + expect(state.go).not.to.be.called; + }); + + it('should update communities for the member ', function() { + vm.communities['ios'].status = true; + vm.communities['ios'].dirty = true; + vm.submitSkills(); + $rootScope.$digest(); + expect(mockProfile.save).not.to.be.called; + expect(profileService.updateUserSkills).not.to.be.called; + expect(memberCertService.registerMember).to.be.calledOnce; + expect(state.go).to.have.been.calledWith('dashboard').calledOnce; + }); + + // we may need to update this test case when we want to call unregister endpoint + it('should NOT update communities for the member for disabled community ', function() { + vm.communities['ios'].status = false; + vm.communities['ios'].dirty = true; + vm.submitSkills(); + $rootScope.$digest(); + expect(mockProfile.save).not.to.be.called; + expect(profileService.updateUserSkills).not.to.be.called; + expect(memberCertService.registerMember).not.to.be.called; + // we should still go to dashboard if the function is called, + // call to the function is controlled by disabling the button + expect(state.go).to.have.been.calledWith('dashboard').calledOnce; + }); + + it('should NOT update communities for the member for enabled but non dirty community ', function() { + // TODO need one more community, with dirty true, to test out the non dirty community update + vm.communities['ios'].status = true; + vm.communities['ios'].dirty = false; + vm.submitSkills(); + $rootScope.$digest(); + expect(mockProfile.save).not.to.be.called; + expect(profileService.updateUserSkills).not.to.be.called; + expect(memberCertService.registerMember).not.to.be.called; + // we should still go to dashboard if the function is called, + // call to the function is controlled by disabling the button + expect(state.go).to.have.been.calledWith('dashboard').calledOnce; + }); + + it('should show error popup for error in updating communities ', function() { + vm.communities['ios'].programId = 100;// to cause rejction of the promise + vm.communities['ios'].status = true; + vm.communities['ios'].dirty = true; + vm.submitSkills(); + $rootScope.$digest(); + expect(mockProfile.save).not.to.be.called; + expect(profileService.updateUserSkills).not.to.be.called; + expect(memberCertService.registerMember).to.be.calledOnce; + expect(toasterSvc.pop).to.have.been.calledWith('error', "Whoops!", sinon.match('wrong')).calledOnce; + expect(state.go).not.to.be.called; + }); }); diff --git a/app/topcoder.constants.js b/app/topcoder.constants.js index fddccd379..037784533 100644 --- a/app/topcoder.constants.js +++ b/app/topcoder.constants.js @@ -32,7 +32,8 @@ angular.module("CONSTANTS", []) "BUSY_PROGRESS_MESSAGE": "Processing..", "REGISTRATION": "REGISTRATION", "CODING": "CODING", - "REGISTERED": "REGISTERED" + "REGISTERED": "REGISTERED", + "SUBMISSION_TYPE_CONTEST": "Contest Submission" }) ; \ No newline at end of file diff --git a/assets/css/community/members.scss b/assets/css/community/members.scss index a8475b936..95d685da3 100644 --- a/assets/css/community/members.scss +++ b/assets/css/community/members.scss @@ -181,7 +181,7 @@ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1); text-align: center; background: $white; - .avatar-wrapper { + .avatar-wrapper img { margin: 0 auto; border-radius: 100px; border: 1px solid $gray-light; @@ -209,6 +209,10 @@ .user-more { text-transform: uppercase; } + .user-name { + color: black; + display: block; + } .user-desc { color: $gray-dark-alt; min-height: 54px; diff --git a/assets/css/directives/design-challenge-user-place.scss b/assets/css/directives/design-challenge-user-place.scss index bcba91355..133a86446 100644 --- a/assets/css/directives/design-challenge-user-place.scss +++ b/assets/css/directives/design-challenge-user-place.scss @@ -11,7 +11,7 @@ design-challenge-user-place { .place { &.completed, &.passed, &.didnt { margin-bottom: -36px; - margin-top: 40px; + margin-top: 52px; } margin-bottom: 8px; margin-top: 8px; diff --git a/assets/css/directives/srm-tile.scss b/assets/css/directives/srm-tile.scss index ca088326a..eb361c3ce 100644 --- a/assets/css/directives/srm-tile.scss +++ b/assets/css/directives/srm-tile.scss @@ -2,6 +2,9 @@ // common styles for both list and tile view .srm { + .noclick { + cursor: default; + } .phase-status { .registered { position: relative; diff --git a/assets/css/skill-picker/skill-picker.scss b/assets/css/skill-picker/skill-picker.scss index 4b18ed13e..6432cbb22 100644 --- a/assets/css/skill-picker/skill-picker.scss +++ b/assets/css/skill-picker/skill-picker.scss @@ -30,6 +30,67 @@ color: $accent-gray; } + .communities { + width: 100%; + margin-bottom: 30px; + .communities__title { + @include sofia-pro-regular; + text-transform: uppercase; + font-size: 18px; + padding-bottom: 10px; + } + .communities__description { + @include font-with-weight('Merriweather Sans'); + color: #A3A3AE; + font-size: 13px; + line-height: 18px; + } + .communities__list { + margin-top: 10px; + .community { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 15px 0; + border-bottom: 1px solid $gray-light; + + &.disabled { + background-color: $gray-lightest; + } + + .community__details { + display: flex; + flex-direction: row; + align-items: center; + padding-left: 10px; + } + + .community__text { + margin-left: 10px; + } + + .community__title { + font-size: 16px; + line-height: 28px; + @include font-with-weight('Sofia Pro', 500); + transition: .1s color; + + &.disabled { + color: $gray-dark; + } + } + + .community__description { + @include font-with-weight('Merriweather Sans', 400); + font-size: 13px; + margin-top: 4px; + color: $accent-gray; + } + } + } + } + .tracks-container { width: 100%; >.title { diff --git a/assets/images/ico-ios-community-grey.svg b/assets/images/ico-ios-community-grey.svg new file mode 100755 index 000000000..7dfad082d --- /dev/null +++ b/assets/images/ico-ios-community-grey.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/ico-ios-community.svg b/assets/images/ico-ios-community.svg new file mode 100755 index 000000000..3fa0795ca --- /dev/null +++ b/assets/images/ico-ios-community.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/skills/id-109.svg b/assets/images/skills/id-109.svg old mode 100644 new mode 100755 index df7695e7a..c5bd3faa3 --- a/assets/images/skills/id-109.svg +++ b/assets/images/skills/id-109.svg @@ -1,17 +1,13 @@ - - - icons/skills/id-109 - Created with Sketch. + - - - - - - - - + + + + + + + diff --git a/assets/images/skills/id-110.svg b/assets/images/skills/id-110.svg new file mode 100644 index 000000000..0893de9e5 --- /dev/null +++ b/assets/images/skills/id-110.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/skills/id-131.svg b/assets/images/skills/id-131.svg index 9bfc4fdb7..810524791 100644 --- a/assets/images/skills/id-131.svg +++ b/assets/images/skills/id-131.svg @@ -1,15 +1,10 @@ - - - icons/skills/id-131 - Created with Sketch. + - - - - - - + + + + \ No newline at end of file diff --git a/assets/images/skills/id-132.svg b/assets/images/skills/id-132.svg index b8f0204bb..39fca3b22 100644 --- a/assets/images/skills/id-132.svg +++ b/assets/images/skills/id-132.svg @@ -1,16 +1,11 @@ - - - icons/skills/id-132 - Created with Sketch. + - - - - - - - + + + + + \ No newline at end of file diff --git a/assets/images/skills/id-133.svg b/assets/images/skills/id-133.svg index 092024090..1363734d5 100644 --- a/assets/images/skills/id-133.svg +++ b/assets/images/skills/id-133.svg @@ -1,18 +1,11 @@ - - - icons/skills/id-133 - Created with Sketch. + - - - - - - - - - + + + + + \ No newline at end of file diff --git a/assets/images/skills/id-140.svg b/assets/images/skills/id-140.svg index 31d4124e6..4343d950d 100644 --- a/assets/images/skills/id-140.svg +++ b/assets/images/skills/id-140.svg @@ -1,13 +1,10 @@ - - - icons/skills/id-140 - Created with Sketch. + - - - - + + + + \ No newline at end of file diff --git a/assets/images/skills/id-154.svg b/assets/images/skills/id-154.svg index c3c983493..2d575d131 100644 --- a/assets/images/skills/id-154.svg +++ b/assets/images/skills/id-154.svg @@ -1,19 +1,15 @@ - - - icons/skills/id-154 - Created with Sketch. + - - - - - - - - - - + + + + + + + + + diff --git a/assets/images/skills/id-205.svg b/assets/images/skills/id-205.svg new file mode 100755 index 000000000..f1c97f3fb --- /dev/null +++ b/assets/images/skills/id-205.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/skills/id-213.svg b/assets/images/skills/id-213.svg index e76e09bca..6cf6f36bc 100644 --- a/assets/images/skills/id-213.svg +++ b/assets/images/skills/id-213.svg @@ -1,17 +1,13 @@ - - - icons/skills/id-213 - Created with Sketch. + - - - - - - - - + + + + + + + diff --git a/assets/images/skills/id-236.svg b/assets/images/skills/id-236.svg index f3f4e035b..e691ffee0 100644 --- a/assets/images/skills/id-236.svg +++ b/assets/images/skills/id-236.svg @@ -1,8 +1,5 @@ - - - icons/skills/id-236 - Created with Sketch. + @@ -10,15 +7,15 @@ - - - - + + + + - + - + diff --git a/assets/images/skills/id-355.svg b/assets/images/skills/id-355.svg index 4be94eeeb..c5007a13e 100644 --- a/assets/images/skills/id-355.svg +++ b/assets/images/skills/id-355.svg @@ -1,16 +1,13 @@ - - - icons/skills/id-355 - Created with Sketch. + - - - - - - - + + + + + + + \ No newline at end of file diff --git a/assets/images/skills/id-368.svg b/assets/images/skills/id-368.svg index c357f322b..9c627a233 100644 --- a/assets/images/skills/id-368.svg +++ b/assets/images/skills/id-368.svg @@ -1,18 +1,15 @@ - - - icons/skills/id-368 - Created with Sketch. + - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/assets/images/skills/id-401.svg b/assets/images/skills/id-401.svg index 0328b1917..35cc10d15 100644 --- a/assets/images/skills/id-401.svg +++ b/assets/images/skills/id-401.svg @@ -1,16 +1,18 @@ - - - icons/skills/id-401 - Created with Sketch. + - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/skills/id-416.svg b/assets/images/skills/id-416.svg new file mode 100644 index 000000000..3cf3b6845 --- /dev/null +++ b/assets/images/skills/id-416.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/skills/id-development.svg b/assets/images/skills/id-development.svg new file mode 100755 index 000000000..b797673e9 --- /dev/null +++ b/assets/images/skills/id-development.svg @@ -0,0 +1,20 @@ + + + + icons/skills/id-development + Created with Sketch. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config.js b/config.js index f35fc241e..a4659bbe3 100644 --- a/config.js +++ b/config.js @@ -42,7 +42,10 @@ module.exports = function() { CODING: 'CODING', // users' status - REGISTERED: 'REGISTERED' + REGISTERED: 'REGISTERED', + + // submission type + SUBMISSION_TYPE_CONTEST: 'Contest Submission' } }, @@ -88,7 +91,10 @@ module.exports = function() { CODING: 'CODING', // users' status - REGISTERED: 'REGISTERED' + REGISTERED: 'REGISTERED', + + // submission type + SUBMISSION_TYPE_CONTEST: 'Contest Submission' } }, @@ -134,7 +140,10 @@ module.exports = function() { CODING: 'CODING', // users' status - REGISTERED: 'REGISTERED' + REGISTERED: 'REGISTERED', + + // submission type + SUBMISSION_TYPE_CONTEST: 'Contest Submission' } }, @@ -180,7 +189,10 @@ module.exports = function() { CODING: 'CODING', // users' status - REGISTERED: 'REGISTERED' + REGISTERED: 'REGISTERED', + + // submission type + SUBMISSION_TYPE_CONTEST: 'Contest Submission' } }, @@ -226,7 +238,10 @@ module.exports = function() { CODING: 'CODING', // users' status - REGISTERED: 'REGISTERED' + REGISTERED: 'REGISTERED', + + // submission type + SUBMISSION_TYPE_CONTEST: 'Contest Submission' } }