diff --git a/app/community/members.controller.js b/app/community/members.controller.js index 1b12bd21b..92c50e0d6 100644 --- a/app/community/members.controller.js +++ b/app/community/members.controller.js @@ -12,7 +12,7 @@ import angular from 'angular' ctrl.notSearch = true ctrl.showing = 'list' ctrl.domain = CONSTANTS.domain - ctrl.currentMonth = 'March 2016' + ctrl.currentMonth = 'April 2016' ctrl.memberLeaderboard = [] ctrl.copilots = [] CommunityDataService.getMembersData() diff --git a/app/community/members.jade b/app/community/members.jade index e8c6f8dc3..037e41d57 100644 --- a/app/community/members.jade +++ b/app/community/members.jade @@ -13,13 +13,13 @@ .platform-stats-container .stat p.statVal {{ctrl.platformStats.memberCount | number : 0}} - p.statLabel ACTIVE MEMBER + p.statLabel ACTIVE MEMBERS .stat p.statVal {{ctrl.platformStats.activeMembersCount | number : 0}} p.statLabel COMPETING TODAY .stat p.statVal {{ctrl.platformStats.prizePurse | currency:undefined:0}} - p.statLabel AVAILABLE PRIZE + p.statLabel AVAILABLE PRIZES .stat p.statVal {{ctrl.platformStats.activeContestsCount | number : 0}} p.statLabel ACTIVE CHALLENGES 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 75f25e7e1..2c5daf8f5 100644 --- a/app/directives/tc-form-stockart/tc-form-stockart.directive.js +++ b/app/directives/tc-form-stockart/tc-form-stockart.directive.js @@ -21,12 +21,7 @@ import _ from 'lodash' controller: ['$scope', function($scope) { var stockartId = 0 var emptyStockart = { - description: '', - sourceUrl: '', - fileNumber: '', - isPhotoDescriptionRequired: false, - isPhotoURLRequired: false, - isFileNumberRequired: false + sourceUrl: '' } // Initialize stockart form data @@ -45,9 +40,7 @@ import _ from 'lodash' // If only one stockart fieldset is there, just reset the values // so that ng-repeat doesn't refresh and there is no UI flickering if (Object.keys($scope.formStockarts).length === 1) { - $scope.submissionForm['photoDescription' + index].$setPristine() $scope.submissionForm['photoURL' + index].$setPristine() - $scope.submissionForm['fileNumber' + index].$setPristine() $scope.formStockarts[index] = angular.copy(emptyStockart) } else { @@ -57,36 +50,9 @@ import _ from 'lodash' $scope.isButtonDisabled = function() { return _.some($scope.formStockarts, function(stockart) { - return !stockart.description || !stockart.sourceUrl || !stockart.fileNumber + return !stockart.sourceUrl }) } - - $scope.showMandatoryMessage = function(inputValue, inputName) { - var id = inputName.slice(-1) - - var stockartSection = $scope.formStockarts[id] - - var stockartDescription = stockartSection.description - var stockartSourceUrl = stockartSection.sourceUrl - var stockartFileNumber = stockartSection.fileNumber - - if (!stockartDescription && !stockartSourceUrl && !stockartFileNumber) { - // All fields empty so required should be false - stockartSection.isPhotoDescriptionRequired = false - stockartSection.isPhotoURLRequired = false - stockartSection.isFileNumberRequired = false - } else if (stockartDescription && stockartSourceUrl && stockartFileNumber) { - // All fields filled out, so required should be false - stockartSection.isPhotoDescriptionRequired = false - stockartSection.isPhotoURLRequired = false - stockartSection.isFileNumberRequired = false - } else { - // Fields are not completely filled out or completely blank so setting required to true - stockartSection.isPhotoDescriptionRequired = true - stockartSection.isPhotoURLRequired = true - stockartSection.isFileNumberRequired = true - } - } }] } } diff --git a/app/directives/tc-form-stockart/tc-form-stockart.jade b/app/directives/tc-form-stockart/tc-form-stockart.jade index 7f10fb425..f561d1f15 100644 --- a/app/directives/tc-form-stockart/tc-form-stockart.jade +++ b/app/directives/tc-form-stockart/tc-form-stockart.jade @@ -1,44 +1,17 @@ .fieldset(ng-repeat="(stockartId, stockart) in formStockarts") button.clean.remove-section(type="button", ng-click="deleteStockartFieldset(stockartId)") - 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}}", - input-required="formStockarts[stockartId].isPhotoDescriptionRequired", - maxlength="100", - on-input-change="showMandatoryMessage(inputValue, inputName)" - ) - 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}}", input-required="formStockarts[stockartId].isPhotoURLRequired", input-pattern="urlRegEx", - maxlength="250", - on-input-change="showMandatoryMessage(inputValue, inputName)" + maxlength="250" ) .tc-error-messages(ng-show="submissionForm['photoURL' + stockartId].$dirty && submissionForm['photoURL' + stockartId].$invalid && submissionForm['photoURL' + stockartId].$error.pattern") p Please enter a valid url. - 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}}", - input-required="formStockarts[stockartId].isFileNumberRequired", - maxlength="50", - on-input-change="showMandatoryMessage(inputValue, inputName)" - ) - button.fieldset__button.tc-btn.tc-btn-s(type="button", ng-click="createAdditionalStockartFieldset()", ng-disabled="isButtonDisabled()") + Add Stock Photo 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 d0be7a02c..8b92a6d46 100644 --- a/app/directives/tc-form-stockart/tc-form-stockart.spec.js +++ b/app/directives/tc-form-stockart/tc-form-stockart.spec.js @@ -29,12 +29,7 @@ describe('Topcoder Form Stockart Directive', 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() { @@ -79,21 +74,21 @@ describe('Topcoder Form Stockart Directive', function() { it('resets the stockart fieldset when it\'s the only one', function() { var stockart = isolateScope.formStockarts[0] - expect(stockart.description).to.equal('') + expect(stockart.sourceUrl).to.equal('') - stockart.description = 'a funny cat picture' + stockart.sourceUrl = 'www.myURL.com' scope.$digest() - expect(stockart.description).to.equal('a funny cat picture') + expect(stockart.sourceUrl).to.equal('www.myURL.com') isolateScope.deleteStockartFieldset(0) scope.$digest() - expect(isolateScope.formStockarts[0].description).to.equal('') + expect(isolateScope.formStockarts[0].sourceUrl).to.equal('') }) }) - describe('isButtonDisabled', function() { + describe.only('isButtonDisabled', function() { var button beforeEach(function() { @@ -108,37 +103,20 @@ describe('Topcoder Form Stockart Directive', 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() - + it('enables the button when the url field is filled out', function() { 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() { + it('disables the button when any url 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 @@ -149,81 +127,16 @@ describe('Topcoder Form Stockart Directive', function() { 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 = '' + // Empty the field in the first fieldset + isolateScope.formStockarts[0].sourceUrl = '' 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() - - expect(stockart.isPhotoDescriptionRequired).to.be.true - expect(stockart.isPhotoURLRequired).to.be.true - expect(stockart.isFileNumberRequired).to.be.true - }) - }) - }) }) diff --git a/app/index.js b/app/index.js index 88d8a918b..30f039869 100644 --- a/app/index.js +++ b/app/index.js @@ -63,6 +63,7 @@ require('../assets/css/sitemap/sitemap.scss') require('../assets/css/settings/update-password.scss') require('../assets/css/settings/settings.scss') require('../assets/css/settings/preferences.scss') +require('../assets/css/settings/email.scss') require('../assets/css/settings/edit-profile.scss') require('../assets/css/settings/account-info.scss') require('../assets/css/profile/subtrack.scss') diff --git a/app/services/api.service.js b/app/services/api.service.js index 8881f4d4d..9f7735975 100644 --- a/app/services/api.service.js +++ b/app/services/api.service.js @@ -74,6 +74,8 @@ import _ from 'lodash' case 'SUBMISSIONS': case 'USER': return _getRestangularV3(CONSTANTS.AUTH_API_URL) + case 'MAILCHIMP': + return _getRestangularV3(CONSTANTS.INTERNAL_API_URL) default: return _getRestangularV3() } @@ -93,6 +95,10 @@ import _ from 'lodash' }) .setDefaultHeaders({ 'Content-Type': 'application/json' }) .addRequestInterceptor(function(element, operation, what, url) { + // for mailchimp api, don't add param field in the body + if (url.indexOf('mailchimp') > -1) { + return element + } if (url.indexOf('members') > -1 || (operation.toLowerCase() === 'post' && url.indexOf('profiles') > -1)) { return { param: element diff --git a/app/services/communityData.service.js b/app/services/communityData.service.js index 66fa59d22..9f951ca1c 100644 --- a/app/services/communityData.service.js +++ b/app/services/communityData.service.js @@ -21,29 +21,29 @@ import angular from 'angular' var data = { 'memberLeaderboard': [ { - 'avatar': '//www.topcoder.com/wp-content/uploads/2015/05/universo_march2016.png', - 'name': 'universo', + 'avatar': '//www.topcoder.com/wp-content/uploads/2015/05/toxicpixel_apr2016.png', + 'name': 'ToxicPixel', 'contestType': 'Design', - 'description': 'Won $4,500 in design challenges', + 'description': 'Won $4,200 with top prizes in LUX and Idea Gen!', 'class': 'design' }, { - 'avatar': '//www.topcoder.com/wp-content/uploads/2015/05/sdgun_march2016.png', - 'name': 'sdgun', + 'avatar': '//www.topcoder.com/wp-content/uploads/2015/05/monicamuranyi_apr2016.png', + 'name': 'MonicaMuranyi', 'contestType': 'Development', - 'description': 'Nine wins for $2,076', + 'description': 'Won $8,300 across 12 competitions', 'class': 'develop' }, { - 'avatar': '//www.topcoder.com/wp-content/uploads/2015/05/BSBandme_march2016.png', - 'name': 'BSBandme', + 'avatar': '//www.topcoder.com/wp-content/uploads/2015/05/sugina_apr2016.png', + 'name': 'Sugina', 'contestType': 'Data Science', - 'description': 'Gained 285 rating points within all 3 January SRMs', + 'description': 'Total increase of 175 points, with a jump from Div 2 to Div 1', 'class': 'data-science' }, { - 'avatar': '//www.topcoder.com/wp-content/uploads/2015/05/johan_92_march2016.png', - 'name': 'johan_92', - 'contestType': 'Design Rookie', - 'description': 'Joined end of December and has had 4 1st place wins!', - 'class': 'design' + 'avatar': '//www.topcoder.com/wp-content/uploads/2015/05/tritias_apr2016.png', + 'name': 'TiTrias', + 'contestType': 'Development Rookie', + 'description': 'Won first challenge within a month of joining!', + 'class': 'develop' } ], 'copilots': [{ diff --git a/app/services/mailchimp.service.js b/app/services/mailchimp.service.js new file mode 100644 index 000000000..ead0d06a1 --- /dev/null +++ b/app/services/mailchimp.service.js @@ -0,0 +1,74 @@ +import angular from 'angular' + +(function() { + 'use strict' + + angular.module('tc.services').factory('MailchimpService', MailchimpService) + + MailchimpService.$inject = ['$http', 'logger', 'Restangular', 'CONSTANTS', 'ApiService', '$q'] + + function MailchimpService($http, logger, Restangular, CONSTANTS, ApiService, $q) { + var mailchimpApi = ApiService.getApiServiceProvider('MAILCHIMP') + var service = { + getMemberSubscription: getMemberSubscription, + addSubscription: addSubscription + } + return service + + function getMemberSubscription(user) { + return $q(function(resolve, reject) { + mailchimpApi.one('mailchimp/lists', CONSTANTS.MAILCHIMP_LIST_ID) + .one('members', user.userId).get() + .then(function(resp) { + resolve(resp) + }) + .catch(function(err) { + if (err.status === 404) { + logger.debug('Member subscription not found') + resolve() + return + } + logger.error('Error getting member to subscription list', err) + + var errorStatus = 'FATAL_ERROR' + reject({ + status: errorStatus, + msg: err.errorMessage + }) + }) + }) + } + + + function addSubscription(user, preferences) { + var subscription = { + userId: user.userId, + firstName: user.firstName, + lastName: user.lastName, + interests: {} + } + if (!preferences) { + subscription.interests[CONSTANTS.MAILCHIMP_NL_GEN] = true + } else { + subscription.interests = preferences + } + return $q(function(resolve, reject) { + mailchimpApi.one('mailchimp/lists', CONSTANTS.MAILCHIMP_LIST_ID) + .customPUT(subscription, 'members') + .then(function(resp) { + resolve(resp) + }) + .catch(function(err) { + logger.error('Error adding member to subscription list', err) + + var errorStatus = 'FATAL_ERROR' + + reject({ + status: errorStatus, + msg: err.errorMessage + }) + }) + }) + } + } +})() diff --git a/app/settings/email/email.controller.js b/app/settings/email/email.controller.js new file mode 100644 index 000000000..443265c54 --- /dev/null +++ b/app/settings/email/email.controller.js @@ -0,0 +1,122 @@ +import angular from 'angular' + +(function () { + 'use strict' + + angular.module('tc.settings').controller('EmailSettingsController', EmailSettingsController) + + EmailSettingsController.$inject = ['$rootScope', 'userProfile', 'ProfileService', 'MailchimpService', 'logger', 'CONSTANTS', 'toaster', '$q', '$scope'] + + function EmailSettingsController($rootScope, userProfile, ProfileService, MailchimpService, logger, CONSTANTS, toaster, $q, $scope) { + var vm = this + vm.loading = false + vm.saving = false + vm.isDirty = isDirty + vm.save = save + + activate() + + function activate() { + vm.newsletters = [ + { + id: CONSTANTS.MAILCHIMP_NL_GEN, + name: 'General Newsletter', + desc: 'News summary from all tracks and programs', + enabled: false, + dirty: false + }, + { + id: CONSTANTS.MAILCHIMP_NL_DESIGN, + name: 'Design Newsletter', + desc: 'Website, mobile, and product design; UI and UX', + enabled: false, + dirty: false + }, + { + id: CONSTANTS.MAILCHIMP_NL_DEV, + name: 'Developer Newsletter', + desc: 'Software architecture, component assembly, application development, and bug hunting', + enabled: false, + dirty: false + }, + { + id: CONSTANTS.MAILCHIMP_NL_DATA, + name: 'Data Science Newsletter', + desc: 'Algorithm and data structures, statistical analysis', + enabled: false, + dirty: false + }, + { + id: CONSTANTS.MAILCHIMP_NL_IOS, + name: 'iOS Community Newsletter', + desc: 'Mobile app design and development for iOS, with Swift emphasis', + enabled: false, + dirty: false + }, + { + id: CONSTANTS.MAILCHIMP_NL_TCO, + name: 'TCO Newsletter', + desc: 'Our annual online and onsite tournament to celebrate and reward the community', + enabled: false, + dirty: false + } + ] + + vm.loading = true + return MailchimpService.getMemberSubscription(userProfile).then(function(subscription) { + vm.loading = false + if (!subscription) { + // add member to the list with empty preferences + MailchimpService.addSubscription(userProfile, {}).then(function(resp) { + logger.debug(resp) + }).catch(function(err) { + // no error to user + //TODO some error alert to community admin + logger.debug('error in adding user to member list') + }) + } else { + if (subscription.interests) { + vm.newsletters.forEach(function(newsletter) { + if (subscription.interests[newsletter.id]) { + newsletter.enabled = true + } + }) + } + } + }) + } + + function isDirty() { + var dirty = false + vm.newsletters.forEach(function(newsletter) { + if (newsletter.dirty){ + dirty = true + } + }) + return dirty + } + + function save() { + vm.saving = true + var preferences = {} + vm.newsletters.forEach(function(newsletter) { + preferences[newsletter.id] = newsletter.enabled + }) + MailchimpService.addSubscription(userProfile, preferences).then(function(resp) { + vm.loading = false + vm.saving = false + // reset dirty state for all newsletter options + vm.newsletters.forEach(function(newsletter) { + newsletter.dirty = false + }) + toaster.pop('success', 'Success!', 'Preferences updated.') + }).catch(function(err) { + logger.error('Could not update email preferences', err) + vm.loading = false + vm.saving = false + + toaster.pop('error', 'Whoops!', 'Something went wrong. Please try again later.') + }) + } + } +})() diff --git a/app/settings/email/email.jade b/app/settings/email/email.jade new file mode 100644 index 000000000..63742b87b --- /dev/null +++ b/app/settings/email/email.jade @@ -0,0 +1,30 @@ +.email-preferences-container + .settings-section.newsletters + .section-info + h2 Email Preferences + .description Choose the community newsletters that you would like to receive. They are sent about once a week and have the latest news on challenges, events, and special programs. + + .section-fields + .processing(ng-show="vm.loading") + i.fa.fa-spinner.fa-spin + .newsletters(ng-hide="vm.loading") + .newsletter(ng-repeat="newsletter in vm.newsletters") + + .content(ng-class="{ disabled: !newsletter.enabled }") + .newsletter-details + .text + span.title {{newsletter.name}} + .description + span(ng-bind="newsletter.desc") + + .onoffswitch + input.onoffswitch-checkbox(type='checkbox', name='onoffswitch', checked='', ng-model="newsletter.enabled", id="{{newsletter.name}}-onoffswitch", ng-change="newsletter.dirty = !newsletter.dirty") + label.onoffswitch-label(for='{{newsletter.name}}-onoffswitch') + span.onoffswitch-inner + span.onoffswitch-switch + .save-section + button.tc-btn.tc-btn-l.done-button( + type="button", + tc-busy-button, tc-busy-when="vm.saving", + ng-click="vm.save()", + ng-disabled="!vm.isDirty()") Save diff --git a/app/settings/preferences/preferences.jade b/app/settings/preferences/preferences.jade index 7e9c5f3b2..b836ce4dd 100644 --- a/app/settings/preferences/preferences.jade +++ b/app/settings/preferences/preferences.jade @@ -1,12 +1,5 @@ .preferences-container ul - li - a(href="http://thecloud.appirio.com/email_prefs_request.html", target="_blank") - .icon - i.fa.fa-envelope - span Email Preferences - .description Specify what kind of email you would like to receive from us - li a(href="https://apps.{{DOMAIN}}/forums/?module=Settings", target="_blank") .icon diff --git a/app/settings/settings.jade b/app/settings/settings.jade index 8c3cd2d0a..8923a13f5 100644 --- a/app/settings/settings.jade +++ b/app/settings/settings.jade @@ -10,6 +10,9 @@ li a(ui-sref="settings.account", ui-sref-active="active-tab") Account + li + a(ui-sref="settings.email", ui-sref-active="active-tab") Email + li a(ui-sref="settings.preferences", ui-sref-active="active-tab") Preferences diff --git a/app/settings/settings.routes.js b/app/settings/settings.routes.js index 0dce4be57..031faed38 100644 --- a/app/settings/settings.routes.js +++ b/app/settings/settings.routes.js @@ -47,6 +47,23 @@ import angular from 'angular' title: 'Account Info' } }, + 'settings.email': { + url: 'email/', + template: require('./email/email')(), + controller: 'EmailSettingsController', + controllerAs: 'vm', + data: { + title: 'Email Preferences' + }, + resolve: { + userIdentity: ['UserService', function(UserService) { + return UserService.getUserIdentity() + }], + userProfile: ['userIdentity', 'ProfileService', function(userIdentity, ProfileService) { + return ProfileService.getUserProfile(userIdentity.handle.toLowerCase()) + }] + } + }, 'settings.preferences': { url: 'preferences/', template: require('./preferences/preferences')(), diff --git a/app/skill-picker/skill-picker.controller.js b/app/skill-picker/skill-picker.controller.js index 75b0668d0..a564c876c 100644 --- a/app/skill-picker/skill-picker.controller.js +++ b/app/skill-picker/skill-picker.controller.js @@ -6,9 +6,9 @@ import _ from 'lodash' angular.module('tc.skill-picker').controller('SkillPickerController', SkillPickerController) - SkillPickerController.$inject = ['$scope', 'CONSTANTS', 'ProfileService', '$state', 'userProfile', 'featuredSkills', 'logger', 'toaster', 'MemberCertService', '$q'] + SkillPickerController.$inject = ['$scope', 'CONSTANTS', 'ProfileService', '$state', 'userProfile', 'featuredSkills', 'logger', 'toaster', 'MemberCertService', '$q', 'MailchimpService'] - function SkillPickerController($scope, CONSTANTS, ProfileService, $state, userProfile, featuredSkills, logger, toaster, MemberCertService, $q) { + function SkillPickerController($scope, CONSTANTS, ProfileService, $state, userProfile, featuredSkills, logger, toaster, MemberCertService, $q, MailchimpService) { var vm = this vm.ASSET_PREFIX = CONSTANTS.ASSET_PREFIX vm.IOS_PROGRAM_ID = CONSTANTS.SWIFT_PROGRAM_ID @@ -32,6 +32,7 @@ import _ from 'lodash' * Activates the controller. */ function activate() { + addToMailingList() initCommunities() checkCommunityStatus() } @@ -137,6 +138,25 @@ import _ from 'lodash' } } + function addToMailingList() { + return MailchimpService.getMemberSubscription(userProfile).then(function(subscription) { + logger.debug(subscription) + if (!subscription) { + return MailchimpService.addSubscription(userProfile).then(function(resp) { + logger.debug(resp) + }).catch(function(err) { + // no error to user + //TODO some error alert to community admin + logger.debug('error in adding user to member list') + }) + } + }).catch(function(err) { + // no error to user + //TODO some error alert to community admin + logger.debug('error in adding user to member list') + }) + } + /** * Persists the user's altered information. */ diff --git a/app/skill-picker/skill-picker.spec.js b/app/skill-picker/skill-picker.spec.js index 7a2100fc7..70dd236c1 100644 --- a/app/skill-picker/skill-picker.spec.js +++ b/app/skill-picker/skill-picker.spec.js @@ -3,12 +3,12 @@ const mockData = require('../../tests/test-helpers/mock-data') describe('Skill Picker Controller', function() { var vm - var toasterSvc, memberCertService, profileService, state + var toasterSvc, memberCertService, profileService, mailchimpService, state var mockProfile = mockData.getMockProfile() beforeEach(function() { bard.appModule('tc.skill-picker') - bard.inject(this, '$controller', '$rootScope', '$q', 'MemberCertService', 'ProfileService', 'toaster', 'CONSTANTS') + bard.inject(this, '$controller', '$rootScope', '$q', 'MemberCertService', 'ProfileService', 'MailchimpService', 'toaster', 'CONSTANTS') memberCertService = MemberCertService profileService = ProfileService @@ -52,6 +52,29 @@ describe('Skill Picker Controller', function() { return deferred.promise }) + mailchimpService = MailchimpService + sinon.stub(mailchimpService, 'getMemberSubscription', function(user) { + var deferred = $q.defer() + if (user.userId === 10336829) { + deferred.resolve() + } else if (user.userId === 12345) { + var resp = { id: 'sdku34i5kdk', email_address: user.email} + deferred.resolve(resp) + } else { + deferred.reject() + } + return deferred.promise + }) + sinon.stub(mailchimpService, 'addSubscription', function(user) { + var deferred = $q.defer() + if (user.userId === 10336829) { + deferred.resolve() + } else { + deferred.reject() + } + return deferred.promise + }) + // mocks the toaster service toasterSvc = toaster bard.mockService(toaster, { @@ -115,6 +138,39 @@ describe('Skill Picker Controller', function() { CONSTANTS.SWIFT_PROGRAM_ID = origSwiftProgId }) + it('should call mailchimp service to add subscription', function() { + expect(vm).to.exist + // getMemberSubscription should always be called + expect(mailchimpService.getMemberSubscription).to.be.calledOnce + // addSubscription should be called once if not subscribed + // getMemberSubscription service mock returns null for mockProfile.userId + expect(mailchimpService.addSubscription).to.be.calledOnce + }) + + it('should not call mailchimp service to add subscription', function() { + // reset getMemberSubscription, addSubscription spy's called count + mailchimpService.getMemberSubscription.reset() + mailchimpService.addSubscription.reset() + var scope = $rootScope.$new() + + var profile = angular.copy(mockProfile) + // update userId to return valid object in service mock + profile.userId = 12345 + vm = $controller('SkillPickerController', { + $scope: scope, + userProfile: profile, + featuredSkills: [], + $state: state + }) + $rootScope.$digest() + expect(vm).to.exist + // getMemberSubscription should always be called + expect(mailchimpService.getMemberSubscription).to.be.calledOnce + // addSubscription should not be called if already subscribed + // getMemberSubscription service mock returns valid object for userId 12345 + expect(mailchimpService.addSubscription).not.to.be.called + }) + it('should add skill ', function() { vm.toggleSkill(409) expect(vm.mySkills).to.exist.have.length(1) diff --git a/app/submissions/submit-design-files/submit-design-files.controller.js b/app/submissions/submit-design-files/submit-design-files.controller.js index 9825f3250..31c85a2c1 100644 --- a/app/submissions/submit-design-files/submit-design-files.controller.js +++ b/app/submissions/submit-design-files/submit-design-files.controller.js @@ -131,11 +131,8 @@ import _ from 'lodash' // Process stock art var processedStockarts = _.reduce(vm.formStockarts, function(compiledStockarts, formStockart) { - if (formStockart.description) { + if (formStockart.sourceUrl) { delete formStockart.id - delete formStockart.isPhotoDescriptionRequired - delete formStockart.isPhotoURLRequired - delete formStockart.isFileNumberRequired compiledStockarts.push(formStockart) } diff --git a/app/submissions/submit-design-files/submit-design-files.spec.js b/app/submissions/submit-design-files/submit-design-files.spec.js index 78ce53e30..42766baee 100644 --- a/app/submissions/submit-design-files/submit-design-files.spec.js +++ b/app/submissions/submit-design-files/submit-design-files.spec.js @@ -196,38 +196,14 @@ describe('Submit Design Files Controller', function() { expect(vm.submissionsBody.data.stockArts).to.deep.equal([]) }) - it('removes the required properties and id from each stockart', function() { + it('removes the 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 - } + { id: 0, sourceUrl: 'url.com' }, + { id: 1, sourceUrl: 'url2.com' } ] var processedStockart = [ - { - description: 'first stockart', - sourceUrl: 'url.com', - fileNumber: '123' - }, - { - description: 'second stockart', - sourceUrl: 'url2.com', - fileNumber: '234' - } + { sourceUrl: 'url.com' }, + { sourceUrl: 'url2.com' } ] scope.$digest() diff --git a/app/topcoder.constants.js b/app/topcoder.constants.js index cab991847..c3245842a 100644 --- a/app/topcoder.constants.js +++ b/app/topcoder.constants.js @@ -4,6 +4,7 @@ angular.module('CONSTANTS', []).constant('CONSTANTS', { 'API_URL' : process.env.API_URL, 'AUTH_API_URL' : process.env.AUTH_API_URL, 'API_URL_V2' : process.env.API_URL_V2, + 'INTERNAL_API_URL' : process.env.INTERNAL_API_URL, 'ASSET_PREFIX' : process.env.ASSET_PREFIX || '', 'auth0Callback' : process.env.auth0Callback, 'auth0Domain' : process.env.auth0Domain, @@ -19,6 +20,14 @@ angular.module('CONSTANTS', []).constant('CONSTANTS', { 'PHOTO_LINK_LOCATION' : process.env.PHOTO_LINK_LOCATION, 'SWIFT_PROGRAM_URL' : process.env.SWIFT_PROGRAM_URL, 'TCO16_URL' : process.env.TCO16_URL, + 'MAILCHIMP_LIST_ID' : process.env.MAILCHIMP_LIST_ID, + 'MAILCHIMP_NL_CATEGORY_ID': process.env.MAILCHIMP_NL_CATEGORY_ID, + 'MAILCHIMP_NL_GEN' : process.env.MAILCHIMP_NL_GEN, + 'MAILCHIMP_NL_TCO' : process.env.MAILCHIMP_NL_TCO, + 'MAILCHIMP_NL_IOS' : process.env.MAILCHIMP_NL_IOS, + 'MAILCHIMP_NL_DEV' : process.env.MAILCHIMP_NL_DEV, + 'MAILCHIMP_NL_DESIGN' : process.env.MAILCHIMP_NL_DESIGN, + 'MAILCHIMP_NL_DATA' : process.env.MAILCHIMP_NL_DATA, 'NEW_CHALLENGES_URL' : 'https://www.topcoder.com/challenges/develop/upcoming/', 'SWIFT_PROGRAM_ID' : 3445, diff --git a/assets/css/settings/email.scss b/assets/css/settings/email.scss new file mode 100644 index 000000000..e8d38e568 --- /dev/null +++ b/assets/css/settings/email.scss @@ -0,0 +1,91 @@ +@import 'topcoder/tc-includes'; + +.email-preferences-container { + width: 100%; + padding: 0 60px 30px; + + .processing { + display: flex; + justify-content: center; + align-items: center; + } + + .newsletters { + .newsletter { + width: 100%; + display: flex; + flex-direction: column; + align-items: left; + + .content { + display: flex; + flex-direction: row; + justify-content: space-between; + padding: 15px 0; + border-bottom: 1px solid $gray-light; + width: 100%; + transition: background-color .15s; + + &.disabled { + background-color: $gray-lightest; + } + + .newsletter-details { + display: flex; + flex-direction: row; + align-items: center; + padding-left: 10px; + + .icon { + &.disabled { + color: #b7b7b7; + } + img { + height: 32px; + width: 32px; + } + span { + @include font-with-weight('Sofia Pro', 500); + font-size: 16px; + } + } + } + } + .text { + margin-left: 15px; + .title { + font-size: 16px; + line-height: 28px; + @include sofia-pro-medium; + transition: .1s color; + &.disabled { + color: #b7b7b7; + } + } + .description { + @include merriweather-sans-regular; + font-size: 13px; + margin-top: 4px; + color: $accent-gray; + } + } + } + } + + .save-section { + width: 100%; + margin: 0 auto; + background-color: #fcfcfc; + border-top: 1px solid #f0f0f0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + button.save { + margin-bottom: 15px; + margin-top: 15px; + width: 100px; + } + } +} \ No newline at end of file diff --git a/package.json b/package.json index 97f576d82..469c5a3bb 100644 --- a/package.json +++ b/package.json @@ -3,13 +3,13 @@ "description": "Topcoder pages including login, registration, settings, dashboard, profile.", "scripts": { "build": "webpack --bail --progress --build --tc", - "start": "webpack-dev-server --history-api-fallback --dev --tc --inline --progress --port 3000", + "start": "webpack-dev-server --history-api-fallback --host 0.0.0.0 --dev --tc --inline --progress --port 3000", "lint": "eslint --ignore-path .gitignore .", "test": "karma start --tc --test" }, "devDependencies": { "angular-mocks": "^1.4.9", - "appirio-tech-webpack-config": "^0.2.0", + "appirio-tech-webpack-config": "^0.3.0", "babel-polyfill": "^6.7.2", "bardjs": "^0.1.8", "bower": "^1.6.8",