From 7ec48b36e01240626fa644b968c358dcd282476a Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Thu, 24 Jan 2019 19:34:32 +0000 Subject: [PATCH] First pass at members list no issue - don't nest details route as it's not nested UI - implement styled list of members - add `` component that generates random background colour and initials based on member name - fixed generation of fake member details in mirage --- ghost/admin/app/components/member-avatar.js | 32 ++++++++++++++++ ghost/admin/app/controllers/member.js | 6 +++ ghost/admin/app/controllers/members.js | 31 ++++++++++++++++ ghost/admin/app/router.js | 5 +-- ghost/admin/app/routes/member.js | 9 +++++ ghost/admin/app/routes/members.js | 5 +++ ghost/admin/app/routes/members/details.js | 3 -- ghost/admin/app/routes/members/index.js | 7 ---- .../app/templates/components/gh-nav-menu.hbs | 2 +- .../templates/components/member-avatar.hbs | 3 ++ ghost/admin/app/templates/member.hbs | 13 +++++++ ghost/admin/app/templates/members.hbs | 37 ++++++++++++++++++- ghost/admin/app/templates/members/details.hbs | 0 ghost/admin/app/templates/members/index.hbs | 10 ----- ghost/admin/mirage/config.js | 4 +- ghost/admin/mirage/scenarios/default.js | 2 + .../components/member-avatar-test.js | 24 ++++++++++++ 17 files changed, 165 insertions(+), 28 deletions(-) create mode 100644 ghost/admin/app/components/member-avatar.js create mode 100644 ghost/admin/app/controllers/member.js create mode 100644 ghost/admin/app/controllers/members.js create mode 100644 ghost/admin/app/routes/member.js delete mode 100644 ghost/admin/app/routes/members/details.js delete mode 100644 ghost/admin/app/routes/members/index.js create mode 100644 ghost/admin/app/templates/components/member-avatar.hbs create mode 100644 ghost/admin/app/templates/member.hbs delete mode 100644 ghost/admin/app/templates/members/details.hbs delete mode 100644 ghost/admin/app/templates/members/index.hbs create mode 100644 ghost/admin/tests/integration/components/member-avatar-test.js diff --git a/ghost/admin/app/components/member-avatar.js b/ghost/admin/app/components/member-avatar.js new file mode 100644 index 0000000000..f363a7e4af --- /dev/null +++ b/ghost/admin/app/components/member-avatar.js @@ -0,0 +1,32 @@ +import Component from '@ember/component'; +import {computed} from '@ember/object'; +import {htmlSafe} from '@ember/string'; + +const stringToHslColor = function (str, saturation, lightness) { + var hash = 0; + for (var i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + + var h = hash % 360; + return 'hsl(' + h + ', ' + saturation + '%, ' + lightness + '%)'; +}; + +export default Component.extend({ + + member: null, + + backgroundStyle: computed('member.name', function () { + let name = this.member.name; + if (name) { + let color = stringToHslColor(name, 30, 80); + return htmlSafe(`background-color: ${color}`); + } + }), + + initials: computed('member.name', function () { + let names = this.member.name.split(' '); + let intials = [names[0][0], names[names.length - 1][0]]; + return intials.join('').toUpperCase(); + }) +}); diff --git a/ghost/admin/app/controllers/member.js b/ghost/admin/app/controllers/member.js new file mode 100644 index 0000000000..bf72a2cf5e --- /dev/null +++ b/ghost/admin/app/controllers/member.js @@ -0,0 +1,6 @@ +import Controller from '@ember/controller'; +import {alias} from '@ember/object/computed'; + +export default Controller.extend({ + member: alias('model') +}); diff --git a/ghost/admin/app/controllers/members.js b/ghost/admin/app/controllers/members.js new file mode 100644 index 0000000000..29c199de50 --- /dev/null +++ b/ghost/admin/app/controllers/members.js @@ -0,0 +1,31 @@ +import Controller from '@ember/controller'; +import {computed} from '@ember/object'; +import {task} from 'ember-concurrency'; + +/* eslint-disable ghost/ember/alias-model-in-controller */ +export default Controller.extend({ + queryParams: ['page'], + + meta: null, + members: null, + + page: computed('meta.pagination.page', function () { + let page = this.get('meta.pagination.page'); + + if (!page || page === 1) { + return null; + } + + return page; + }), + + fetchMembers: task(function* () { + let results = yield this.store.query('member', { + page: this.page || 1, + limit: 15 + }); + + this.set('meta', results.meta); + this.set('members', results); + }) +}); diff --git a/ghost/admin/app/router.js b/ghost/admin/app/router.js index 31f67e58e0..1562131bfc 100644 --- a/ghost/admin/app/router.js +++ b/ghost/admin/app/router.js @@ -64,9 +64,8 @@ Router.map(function () { this.route('settings.integrations.unsplash', {path: '/settings/integrations/unsplash'}); this.route('settings.integrations.zapier', {path: '/settings/integrations/zapier'}); - this.route('members', function () { - this.route('details', {path: ':member_id'}); - }); + this.route('members'); + this.route('member', {path: '/members/:member_id'}); this.route('subscribers', function () { this.route('new'); diff --git a/ghost/admin/app/routes/member.js b/ghost/admin/app/routes/member.js new file mode 100644 index 0000000000..5880e6ac3c --- /dev/null +++ b/ghost/admin/app/routes/member.js @@ -0,0 +1,9 @@ +import AuthenticatedRoute from 'ghost-admin/routes/authenticated'; + +export default AuthenticatedRoute.extend({ + // TODO: add model method to load member if not passed in + + titleToken() { + return this.controller.get('member.name'); + } +}); diff --git a/ghost/admin/app/routes/members.js b/ghost/admin/app/routes/members.js index 63bdc43deb..192f5c8fe9 100644 --- a/ghost/admin/app/routes/members.js +++ b/ghost/admin/app/routes/members.js @@ -22,5 +22,10 @@ export default AuthenticatedRoute.extend({ return this.transitionTo('posts'); } }); + }, + + setupController(controller) { + this._super(...arguments); + controller.fetchMembers.perform(); } }); diff --git a/ghost/admin/app/routes/members/details.js b/ghost/admin/app/routes/members/details.js deleted file mode 100644 index 7ff4a4b892..0000000000 --- a/ghost/admin/app/routes/members/details.js +++ /dev/null @@ -1,3 +0,0 @@ -import AuthenticatedRoute from 'ghost-admin/routes/authenticated'; - -export default AuthenticatedRoute.extend({}); diff --git a/ghost/admin/app/routes/members/index.js b/ghost/admin/app/routes/members/index.js deleted file mode 100644 index 6e46c48630..0000000000 --- a/ghost/admin/app/routes/members/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import AuthenticatedRoute from 'ghost-admin/routes/authenticated'; - -export default AuthenticatedRoute.extend({ - model() { - return this.store.findAll('member'); - } -}); diff --git a/ghost/admin/app/templates/components/gh-nav-menu.hbs b/ghost/admin/app/templates/components/gh-nav-menu.hbs index d919771734..46758c7126 100644 --- a/ghost/admin/app/templates/components/gh-nav-menu.hbs +++ b/ghost/admin/app/templates/components/gh-nav-menu.hbs @@ -51,7 +51,7 @@
  • {{#link-to "team" data-test-nav="team"}}{{svg-jar "account-group"}}Team{{/link-to}}
  • {{#if (and config.enableDeveloperExperiments (gh-user-can-admin session.user))}} -
  • {{#link-to "members" data-test-nav="members"}}{{svg-jar "email"}}Members{{/link-to}}
  • +
  • {{#link-to "members" current-when="members member" data-test-nav="members"}}{{svg-jar "email"}}Members{{/link-to}}
  • {{/if}} {{#if (and feature.subscribers (gh-user-can-admin session.user))}}
  • {{#link-to "subscribers" data-test-nav="subscribers"}}{{svg-jar "email"}}Subscribers{{/link-to}}
  • diff --git a/ghost/admin/app/templates/components/member-avatar.hbs b/ghost/admin/app/templates/components/member-avatar.hbs new file mode 100644 index 0000000000..82fa8673fa --- /dev/null +++ b/ghost/admin/app/templates/components/member-avatar.hbs @@ -0,0 +1,3 @@ +
    + {{this.initials}} +
    \ No newline at end of file diff --git a/ghost/admin/app/templates/member.hbs b/ghost/admin/app/templates/member.hbs new file mode 100644 index 0000000000..3cfeb61ca0 --- /dev/null +++ b/ghost/admin/app/templates/member.hbs @@ -0,0 +1,13 @@ +
    +
    +

    + {{link-to "Members" "members" data-test-link="members"}} + {{svg-jar "arrow-right"}} + {{member.name}} +

    +
    + +
    + +
    +
    \ No newline at end of file diff --git a/ghost/admin/app/templates/members.hbs b/ghost/admin/app/templates/members.hbs index c24cd68950..07e169cbfe 100644 --- a/ghost/admin/app/templates/members.hbs +++ b/ghost/admin/app/templates/members.hbs @@ -1 +1,36 @@ -{{outlet}} +
    +
    +

    Members

    +
    + {{this.meta.pagination.total}} Members +
    +
    + +
    + {{#if this.members}} + {{!-- members list, styles taken from .apps-grid --}} +
    + {{#each this.members as |member index|}} +
    + {{#link-to "member" member}} +
    +
    + +
    +

    {{member.name}}

    +

    {{member.email}}

    +
    +
    +
    {{svg-jar "arrow-right" class="w4"}}
    +
    + {{/link-to}} +
    + {{/each}} +
    + {{else}} + {{#unless this.fetchMembers.isRunning}} +

    No members found.

    + {{/unless}} + {{/if}} +
    +
    \ No newline at end of file diff --git a/ghost/admin/app/templates/members/details.hbs b/ghost/admin/app/templates/members/details.hbs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ghost/admin/app/templates/members/index.hbs b/ghost/admin/app/templates/members/index.hbs deleted file mode 100644 index c172af5bee..0000000000 --- a/ghost/admin/app/templates/members/index.hbs +++ /dev/null @@ -1,10 +0,0 @@ -
    -
    -

    Members

    -
    -
    - -
    -

    ...

    -
    -
    \ No newline at end of file diff --git a/ghost/admin/mirage/config.js b/ghost/admin/mirage/config.js index a4290dcb59..08bb1ce164 100644 --- a/ghost/admin/mirage/config.js +++ b/ghost/admin/mirage/config.js @@ -24,15 +24,13 @@ export default function () { // this.urlPrefix = ''; // make this `http://localhost:8080`, for example, if your API is on a different server this.namespace = '/ghost/api/v2/admin'; // make this `api`, for example, if your API is namespaced - this.timing = 400; // delay for each request, automatically set to 0 during testing + this.timing = 1000; // delay for each request, automatically set to 0 during testing this.logging = true; // Mock endpoints here to override real API requests during development, eg... // this.put('/posts/:id/', versionMismatchResponse); // mockTags(this); // this.loadFixtures('settings'); - - this.createList('member', 200); mockMembers(this); // keep this line, it allows all other API requests to hit the real server diff --git a/ghost/admin/mirage/scenarios/default.js b/ghost/admin/mirage/scenarios/default.js index cd650f3c09..5f584b5fe3 100644 --- a/ghost/admin/mirage/scenarios/default.js +++ b/ghost/admin/mirage/scenarios/default.js @@ -8,4 +8,6 @@ export default function (server) { server.createList('tag', 100); server.create('integration', {name: 'Demo'}); + + server.createList('member', 125); } diff --git a/ghost/admin/tests/integration/components/member-avatar-test.js b/ghost/admin/tests/integration/components/member-avatar-test.js new file mode 100644 index 0000000000..e5d4cea8d7 --- /dev/null +++ b/ghost/admin/tests/integration/components/member-avatar-test.js @@ -0,0 +1,24 @@ +import { expect } from 'chai'; +import { describe, it } from 'mocha'; +import { setupComponentTest } from 'ember-mocha'; +import hbs from 'htmlbars-inline-precompile'; + +describe('Integration | Component | member-avatar', function() { + setupComponentTest('member-avatar', { + integration: true + }); + + it('renders', function() { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.on('myAction', function(val) { ... }); + // Template block usage: + // this.render(hbs` + // {{#member-avatar}} + // template content + // {{/member-avatar}} + // `); + + this.render(hbs`{{member-avatar}}`); + expect(this.$()).to.have.length(1); + }); +});