diff --git a/app/components/api-token-row.js b/app/components/api-token-row.js index 9c628e44148..280d1abad60 100644 --- a/app/components/api-token-row.js +++ b/app/components/api-token-row.js @@ -8,7 +8,7 @@ export default Component.extend({ didInsertElement() { let input = this.element.querySelector('input'); - if (input.focus) { + if (input && input.focus) { input.focus(); } }, diff --git a/app/components/owned-crate-row.js b/app/components/owned-crate-row.js new file mode 100644 index 00000000000..c253b87aafa --- /dev/null +++ b/app/components/owned-crate-row.js @@ -0,0 +1,19 @@ +import Component from '@ember/component'; +import { computed } from '@ember/object'; +import { alias } from '@ember/object/computed'; + +export default Component.extend({ + tagName: 'li', + + name: alias('ownedCrate.name'), + controlId: computed('ownedCrate.id', function() { + return `${this.ownedCrate.id}-email-notifications`; + }), + emailNotifications: alias('ownedCrate.email_notifications'), + + actions: { + toggleEmailNotifications() { + this.set('emailNotifications', !this.get('emailNotifications')); + }, + }, +}); diff --git a/app/controllers/me/index.js b/app/controllers/me/index.js index a3edf82124e..1d5225e75b9 100644 --- a/app/controllers/me/index.js +++ b/app/controllers/me/index.js @@ -1,6 +1,7 @@ import Controller from '@ember/controller'; -import { sort, filterBy, notEmpty } from '@ember/object/computed'; +import { alias, sort, filterBy, notEmpty } from '@ember/object/computed'; import { inject as service } from '@ember/service'; +import ajax from 'ember-fetch/ajax'; export default Controller.extend({ // eslint-disable-next-line ember/avoid-leaking-state-in-ember-objects @@ -12,10 +13,50 @@ export default Controller.extend({ isResetting: false, + ownedCrates: alias('model.ownedCrates'), + newTokens: filterBy('model.api_tokens', 'isNew', true), disableCreate: notEmpty('newTokens'), + emailNotificationsError: false, + emailNotificationsSuccess: false, + + setAllEmailNotifications(value) { + this.get('ownedCrates').forEach(c => { + c.set('email_notifications', value); + }); + }, + actions: { + async saveEmailNotifications() { + try { + await ajax(`/api/v1/me/email_notifications`, { + method: 'PUT', + body: JSON.stringify( + this.get('ownedCrates').map(c => ({ + id: parseInt(c.id, 10), + email_notifications: c.email_notifications, + })), + ), + }); + this.setProperties({ + emailNotificationsError: false, + emailNotificationsSuccess: true, + }); + } catch (err) { + console.error(err); + this.setProperties({ + emailNotificationsError: true, + emailNotificationsSuccess: false, + }); + } + }, + emailNotificationsSelectAll() { + this.setAllEmailNotifications(true); + }, + emailNotificationsSelectNone() { + this.setAllEmailNotifications(false); + }, startNewToken() { this.store.createRecord('api-token', { created_at: new Date(Date.now() + 2000), diff --git a/app/models/owned-crate.js b/app/models/owned-crate.js new file mode 100644 index 00000000000..6a708563854 --- /dev/null +++ b/app/models/owned-crate.js @@ -0,0 +1,6 @@ +import DS from 'ember-data'; + +export default DS.Model.extend({ + name: DS.attr('string'), + email_notifications: DS.attr('boolean'), +}); diff --git a/app/routes/me/index.js b/app/routes/me/index.js index 7e0fb31bcbe..b35a921b122 100644 --- a/app/routes/me/index.js +++ b/app/routes/me/index.js @@ -3,9 +3,20 @@ import Route from '@ember/routing/route'; import AuthenticatedRoute from '../../mixins/authenticated-route'; export default Route.extend(AuthenticatedRoute, { + actions: { + willTransition: function() { + this.controller + .setProperties({ + emailNotificationsSuccess: false, + emailNotificationsError: false, + }) + .clear(); + }, + }, model() { return { user: this.get('session.currentUser'), + ownedCrates: this.get('session.ownedCrates'), api_tokens: this.store.findAll('api-token'), }; }, diff --git a/app/services/session.js b/app/services/session.js index 9b9903f38b8..f37f5c47fc9 100644 --- a/app/services/session.js +++ b/app/services/session.js @@ -1,3 +1,4 @@ +import { A } from '@ember/array'; import Service, { inject as service } from '@ember/service'; import ajax from 'ember-fetch/ajax'; @@ -7,6 +8,7 @@ export default Service.extend({ isLoggedIn: false, currentUser: null, currentUserDetected: false, + ownedCrates: A(), store: service(), router: service(), @@ -66,6 +68,9 @@ export default Service.extend({ fetchUser() { return ajax('/api/v1/me').then(response => { this.set('currentUser', this.store.push(this.store.normalize('user', response.user))); + this.ownedCrates.pushObjects( + response.owned_crates.map(c => this.store.push(this.store.normalize('owned-crate', c))), + ); }); }, diff --git a/app/styles/me.scss b/app/styles/me.scss index 0f09ce629a6..0f055660741 100644 --- a/app/styles/me.scss +++ b/app/styles/me.scss @@ -116,6 +116,126 @@ } } +#me-email-notifications { + border-bottom: 5px solid $gray-border; + padding-bottom: 20px; + margin-bottom: 20px; + @include display-flex; + @include flex-direction(column); + + .row { + @include display-flex; + @include flex-direction(row); + + .error, .success { + border-top-width: 0px; + font-weight: bold; + + padding: 0px 10px 10px 20px; + } + + .error { + color: rgb(216, 0, 41); + } + + .success { + color: green; + } + } + + .button-container { + margin-right: 1rem; + } + + ul { + padding: 0; + @include flex-grow(1); + + li { + background-color: #fff; + display: block; + position: relative; + border: 1px solid #d5d3cb; + } + + label { + padding: 20px 30px; + width: 100%; + display: block; + text-align: left; + font-weight: bold; + cursor: pointer; + position: relative; + z-index: 2; + transition: color 200ms ease-in; + overflow: hidden; + + &:before { + width: 100%; + height: 10px; + border-radius: 50%; + content: ''; + background-color: $main-bg-dark; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%) scale3d(1, 1, 1); + opacity: 0; + z-index: -1; + } + + &:after { + width: 32px; + height: 32px; + content: ''; + border: 2px solid #d5d3cb; + border-radius: 50%; + z-index: 2; + position: absolute; + right: 30px; + top: 50%; + transform: translateY(-50%); + cursor: pointer; + } + } + + input:checked ~ label { + &:before { + transform: translate(-50%, -50%) scale3d(56, 56, 1); + opacity: 1; + } + + &:after { + background-color: #cfc487; + border-color: #cfc487; + background-image: url("data:image/svg+xml,%3Csvg width='32' height='32' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5.414 11L4 12.414l5.414 5.414L20.828 6.414 19.414 5l-10 10z' fill='%23383838' fill-rule='nonzero'/%3E%3C/svg%3E "); + background-repeat: no-repeat; + background-position: 3px 4px; + } + } + + input { + width: 32px; + height: 32px; + order: 1; + z-index: 2; + position: absolute; + right: 30px; + top: 50%; + transform: translateY(-50%); + cursor: pointer; + visibility: hidden; + } + } + + .right { + @include flex(2); + @include display-flex; + @include justify-content(flex-end); + @include align-self(center); + } +} + #me-api { @media only screen and (max-width: 350px) { .api { display: none; } diff --git a/app/templates/components/owned-crate-row.hbs b/app/templates/components/owned-crate-row.hbs new file mode 100644 index 00000000000..d5c1cc24e03 --- /dev/null +++ b/app/templates/components/owned-crate-row.hbs @@ -0,0 +1,11 @@ +{{input + type="checkbox" + name=controlId + id=controlId + checked=emailNotifications + change=(action "toggleEmailNotifications") + class="form-control" + }} + diff --git a/app/templates/me/index.hbs b/app/templates/me/index.hbs index 04a720f8c1f..9dd412e668c 100644 --- a/app/templates/me/index.hbs +++ b/app/templates/me/index.hbs @@ -25,6 +25,49 @@ {{email-input type='email' value=model.user.email user=model.user}} +
+