Adding an Edit Route for Access Codes in Open Event Frontend

This blog post will illustrate how to add edit route to access code to allow organizers editing an access code. Editing access codes deals with the following API on Open Event Server.

PATCH          /v1/access-codes/{id}

First of all we need to add a route to allow editing of access codes. Our route will be /events/{event_id}/tickets/access-codes/edit/{access_code_id}. To generate a new route we need to run the command

ember g route events/view/tickets/access-codes/edit

This will generate new routes files and templates. Also this will add this route in router.js. In router.js we need to specify what we are passing as a parameter to route. For this we specify access_code_id to let ember know that this parameter will be passed with the URL. This is done so as to know which access code to update. Our route should look something like this /events/{event_id}/tickets/access-codes/edit/{access_code_id}. Final router.js file for access-codes part is given below:

//  app/router.js
 
this.route('access-codes', function() {
   this.route('list', { path: '/:access_status' });
   this.route('create');
   this.route('edit', { path: '/edit/:access_code_id' });
});

 

Next we need to pass model (data) to our edit route template. In our model we will be looking for a particular access code with an id and event tickets. After we get our event tickets, we then look for the event tickets which are already present in that access code. This is done so as to check the tickets in the template which are already present in that access code. So for this in afterModel hook we loop over all event tickets and whichever ticket is included in the access code tickets array we mark its isChecked property as true. This helps us to mark those tickets as checked in multiple select checkboxes of template.

// app/routes/events/view/tickets/access-codes/edit.js

model(params) {
   return RSVP.hash({
     accessCode : this.store.findRecord('access-code', params.access_code_id, {}),
     tickets    : this.modelFor('events.view').query('tickets', {})
   });
 },

 async afterModel(model) {
   let tickets = await model.accessCode.tickets;
   let allTickets = model.tickets;
   allTickets.forEach(ticket => {
     if (tickets.includes(ticket)) {
       ticket.set('isChecked', true);
     } else {
       ticket.set('isChecked', false);
     }
   });

 

The information about multiple select checkboxes in frontend has been discussed in this blog post. Now after we are done setting our edit route we need to redirect user to edit page when he/she clicks on the edit button in access code list. For this we define the necessary actions in template which will be triggered when user clicks on the icon. Code for the edit icon in access code list is given below.

// templates/components/ui-table/cell/events/view/tickets/access-codes/cell-actions.hbs

class="ui vertical compact basic buttons"> {{#ui-popup content=(t 'Edit') click=(action editAccessCode record.id) class='ui icon button' position='left center'}} class="edit icon"> {{/ui-popup}}

 

The editAccessCode action looks something like this.

// controller/events/view/tickets/access-codes/edit.js

editAccessCode(id) {
     this.transitionToRoute('events.view.tickets.access-codes.edit', id);
}

 

After clicking on the edit icon user is redirected to edit route where he/she can edit access code choosen. We use the same component that we chose for creating access code. To know more about create access code template component refer to this blog. Finally when saving the edited access code we call save action. This action is defined in the controllers. The action looks something like this.

// controllers/events/view/tickets/access-codes/edit.js

export default Controller.extend({
 actions: {
   save(accessCode) {
     accessCode.save()
       .then(() => {
         this.get('notify').success(this.get('l10n').t('Access code has been successfully updated.'));
         this.transitionToRoute('events.view.tickets.access-codes');
       })
       .catch(() => {
         this.get('notify').error(this.get('l10n').t('An unexpected error has occured. Discount code cannot be created.'));
       });
   }
 }
});

 

After the access code is saved. We redirect user back to access code list.

Resources

Continue ReadingAdding an Edit Route for Access Codes in Open Event Frontend

Integrating Orders API to Allow User Select Tickets for Placing Order in Open Event Frontend

In Open Event Frontend organizer has option to sell tickets for his event. Tickets which are available to public are listed in public page of event for users to buy. In this blog post we will learn how to integrate orders API and manage multiple tickets of varying quantity under an order.

For orders we mainly interact with three API endpoints.

  1. Orders API endpoint
  2. Attendees API endpoint
  3. Tickets API endpoint

Orders and attendees have one to many relationship and similarly orders and tickets also have one to many relationship. Every attendee is related to one ticket. In simple words one attendee has one ticket and one order can contain many tickets of different quantity each meant for different attendee.

// routes/public/index.js

order: this.store.createRecord('order', {
  event     : eventDetails,
  user      : this.get('authManager.currentUser'),
  tickets   : [],
  attendees : []
})

 

We need to create instance of order model to fill in data in that record. We do this in our routes folder of public route. Code snippet is given below.

We create a empty array for tickets and attendees so that we can add their record instances as relationship to order.

As given in screenshot we have a dropdown for each ticket to select quantity of each ticket. To allow user select quantity of a ticket we use #ui-dropdown component of ember-semantic-ui in our template. The code snippet of that is given below.

// templates/components/public/ticket-list.js

{{#ui-dropdown class='compact selection' forceSelection=false onChange=(action 'updateOrder' ticket) as |execute mapper|}}
  {{input type='hidden' id=(concat ticket.id '_quantity') value=ticket.orderQuantity}}
    <i class="dropdown icon"></i>
    
class="default text">0
class="menu">
class="item" data-value="{{map-value mapper 0}}">{{0}}
{{#each (range ticket.minOrder ticket.maxOrder) as |count|}}
class="item" data-value="{{map-value mapper count}}">{{count}}
{{/each}} </div> {{/ui-dropdown}}

 

For every change in quantity of ticket selected we call a action named updateOrder. The code snippet for this action is given below.

// components/public/ticket-list.js

updateOrder(ticket, count) {
      let order = this.get('order');
      ticket.set('orderQuantity', count);
      order.set('amount', this.get('total'));
      if (count > 0) {
        order.tickets.addObject(ticket);
      } else {
        if (order.tickets.includes(ticket)) {
          order.tickets.removeObject(ticket);
        }
      }
    },

 

Here we can see that if quantity of selected ticket is more than 0 we add that ticket to our order otherwise we remove that ticket from the order if it already exists.

Once user selects his tickets for the order he/she can order the tickets. On clicking Order button we call placeOrder action. With help of this we add all the relations and finally send the order information to the server. Code snippet is given below.

// components/public/ticket-list.js
    
placeOrder() {
   let order = this.get('order');
   let event = order.get('event');
   order.tickets.forEach(ticket => {
     let attendee = ticket.orderQuantity;
     let i;
     for (i = 0; i < attendee; i++) {
       order.attendees.addObject(this.store.createRecord('attendee', {
         firstname : 'John',
         lastname  : 'Doe',
         email     : 'johndoe@example.com',
         event,
         ticket
       }));
     }
   });
   this.sendAction('save');
 }

 

Here for each ticket placed under an order we create a dummy attendee related to ticket and event. And then we call save action to save all the models. Code snippet for save is given:

actions: {
    async save() {
      try {
        this.set('isLoading', true);
        let order = this.get('model.order');
        let attendees = order.get('attendees');
        await order.save();
        attendees.forEach(async attendee => {
          await attendee.save();
        });
        await order.save()
          .then(order => {
            this.get('notify').success(this.get('l10n').t('Order created.'));
            this.transitionToRoute('orders.new', order.id);
          });
      } catch (e) {
        this.get('notify').error(this.get('l10n').t('Oops something went wrong. Please try again'));
      }
    }
  }

 

Here we finally save all the models and transition to orders page to enable user fill the attendees details.

Resources:
Continue ReadingIntegrating Orders API to Allow User Select Tickets for Placing Order in Open Event Frontend

How We Implement Custom Forms for Sessions and Speaker Form in Open Event Frontend

In this blog we will see the implementation of custom form for session and speakers form. Since every event organiser requires different fields required to be filled by speakers for their sessions, for ex. Some event organiser may need GitHub profile whereas other may need LinkedIn profile so, we implemented custom form for session and speaker form in Open Event Frontend so that every organiser can choose fields he/she requires to be filled by his speakers who are filling their proposal. Custom form allows following features:

  1. Allows organiser to choose whether he wants a particular field or not.
  2. Organiser can make a field compulsory, so that no speaker can submit his proposal without filling that field.

So, to get started we define our getCustomForm() method in mixins and it looks something like this:

import Mixin from '@ember/object/mixin';
import MutableArray from '@ember/array/mutable';

export default Mixin.create(MutableArray, {

 getCustomForm(parent) {
   return [
     this.store.createRecord('custom-form', {
       fieldIdentifier : 'title',
       form            : 'session',
       type            : 'text',
       isRequired      : true,
       isIncluded      : true,
       isFixed         : true,
       event           : parent
     }),
     this.store.createRecord('custom-form', {
       fieldIdentifier : 'subtitle',
       form            : 'session',
       type            : 'text',
       isRequired      : false,
       isIncluded      : false,
       event           : parent
     }),
     this.store.createRecord('custom-form', {
       fieldIdentifier : 'shortAbstract',
       form            : 'session',
       type            : 'text',
       isRequired      : false,
       isIncluded      : true,
       event           : parent
     }),

...

 

Here we define all the possible fields with their properties. Every field has different properties that enables us to identify whether to choose or reject them. So to enable this these fields to be chosen by organiser we need to feed them into model.

In our session-speakers-step.js in components we assign these fields to our model thorugh this:

didInsertElement() {
   if (this.get('data.event.customForms')&&!his.get('data.event.customForms.length')) {
     this.set('data.event.customForms', this.getCustomForm(this.get('data.event')));
   }
}

 

This hook gets called when our component is rendered and custom form fields gets assigned to data.event.customForms in our model.

In our handlebars template we create sliders to enable organiser to choose whether he wants to enable a field or not.

In our template logic we write a loop that renders these sliders for each fields and manipulate them as organiser slides any slider for a field. Let’s take a look at the template code block:

       <tbody>
           {{#each customForm.session as |field|}}
             <tr class="{{if field.isIncluded 'positive'}}">
               <td class="{{if device.isMobile 'center' 'right'}} aligned">
                 <label class="{{if field.isFixed 'required'}}">
                   {{field.name}}
                 </label>
               </td>
               <td class="center aligned">
                 {{ui-checkbox class='slider'
                               checked=field.isIncluded
                               disabled=field.isFixed
                               onChange=(action (mut field.isIncluded))
                               label=(if device.isMobile (t 'Include'))}}
               </td>
               <td class="center aligned">
                 {{ui-checkbox class='slider'
                               checked=field.isRequired
                               disabled=field.isFixed
                               onChange=(action (mut field.isRequired))
                               label=(if device.isMobile (t 'Require'))}}
               </td>
             </tr>
           {{/each}}
         </tbody>

 

Every slider has a action attached to it that manipulates a property of the custom for and on proceeding it saves the custom form model to server.

Later on when rendering for for speaker we fetch details from server about which fields are required or not.

Resources

Continue ReadingHow We Implement Custom Forms for Sessions and Speaker Form in Open Event Frontend

Implementing Scheduler Actions on Open Event Frontend

After the functionality to display scheduled sessions was added to Open Event Frontend, the read-only implementation of the scheduler had been completed. What was remaining now in the scheduler were the write actions, i.e., the sessions’ scheduling which event organizers do by deciding its timings, duration and venue.

First of all, these actions required the editable flag to be true for the fullcalendar plugin. This allowed the sessions displayed to be dragged and dropped. Once this was enabled, the next task was to embed data in each of the unscheduled sessions so that when they get dropped on the fullcalendar space, they get recognized by the calendar, which can place it at the appropriate location. For this functionality, they had to be jQuery UI draggables and contain an “event” data within them. This was accomplished by the following code:

this.$().draggable({
  zIndex         : 999,
  revert         : true,      // will cause the event to go back to its
  revertDuration : 0  //  original position after the drag
});

this.$().data('event', {
  title    : this.$().text().replace(/\s\s+/g, ' '), // use the element's text as the event title
  id       : this.$().attr('id'),
  serverId : this.get('session.id'),
  stick    : true, // maintain when user navigates (see docs on the renderEvent method)
  color    : this.get('session.track.color')
});

Here, “this” refers to each unscheduled session. Note that the session color is fetched via the corresponding session track. Once the unscheduled sessions contain enough relevant data and are of the right type (i.e, jQuery UI draggable type), they’re ready to be dropped on the fullcalendar space.

Now, when an unscheduled session is dropped on the fullcalendar space, fullcalendar’s eventReceive callback is triggered after its drop callback. In this callback, the code removes the session data from the unscheduled sessions’ list, so it disappears from there and gets stuck to the fullcalendar space. Then the code in the drop callback makes a PATCH request to Open Event Server with the relevant data, i.e, start and end times as well as microlocation. This updates the corresponding session on the server.

Similarly, another callback is generated when an event is resized, which means when its duration is changed. This again sends a corresponding session PATCH request to the server. Furthermore, the functionality to pop a scheduled event out of the calendar and add it back to the unscheduled sessions’ list is also implemented, just like in Eventyay version 1. For this, a cross button is implemented, which is embedded in each scheduled session. Clicking this pops the session out of the calendar and adds it back to the unscheduled sessions list. Again, a corresponding PATCH request is sent to the server.

After getting the response of such requests, a notification is displayed on the screen, which informs the users whether the action was successful or not. The main PATCH functionality is in a separate function which is called by different callbacks accordingly, so code reusability is increased:

updateSession(start, end, microlocationId, sessionId) {
    let payload = {
      data: {
        attributes: {
          'starts-at' : start ? start.toISOString() : null,
          'ends-at'   : end ? end.toISOString() : null
        },
        relationships: {
          microlocation: {
            data: {
              type : 'microlocation',
              id   : microlocationId
            }
          }
        },
        type : 'session',
        id   : sessionId
      }
    };

    let config = {
      skipDataTransform: true
    };
    return this.get('loader')
      .patch(`sessions/${sessionId}`, JSON.stringify(payload), config)
      .then(() => {
        this.get('notify').success('Changes have been made successfully');
      })
      .catch(reason => {
        this.set('error', reason);
        this.get('notify').error(`Error: ${reason}`);
      });
  },

This completes the scheduler implementation on Open Event Frontend. Here is how it looks in action:

scheduler actions.gif

Resources

Continue ReadingImplementing Scheduler Actions on Open Event Frontend

Implementing Scheduled Sessions in Open Event Scheduler

Until recently, the Open Event Frontend version 2 didn’t have the functionality to display the already scheduled sessions of an event on the sessions scheduler. Displaying the already scheduled sessions is important so that the event organizer can always use the sessions scheduler as a draft and not worry about losing progress or data about scheduled sessions’ timings. Therefore, just like a list of unscheduled sessions was implemented for the scheduler, the provision for displaying scheduled sessions also had to be implemented.

The first step towards implementing this was to fetch the scheduled sessions’ details from Open Event Server. To perform this fetch, an appropriate filter was required. This filter should ideally ask the server to send only those sessions that are “scheduled”. Thus, scheduled sessions need to be defined as sessions which have a non-null value of its starts-at and ends-at fields. Also, few more details are required to be fetched for a clean display of scheduled sessions. First, the sessions’ speaker details should be included so that the speakers’ names can be displayed alongside the sessions. Also, the microlocations’ details need to be included so that each session is displayed according to its microlocation. For example, if a session is to be delivered in a place named ‘Lecture Hall A’, it should appear under the ‘Lecture Hall A’ microlocation column. Therefore, the filter goes as follows:

let scheduledFilterOptions = [
      {
        and: [
          {
            name : 'starts-at',
            op   : 'ne',
            val  : null
          },
          {
            name : 'ends-at',
            op   : 'ne',
            val  : null
          }
        ]
      }
    ];

 

After fetching the scheduled sessions’ details, they need to be delivered to the fulllcalendar code for displaying on the session scheduler. For that, the sessions need to be converted in a format which can be parsed by the fullcalendar add-on of emberJS. For example, fullcalendar calls microlocations as ‘resources’. Here is the format which fullcalendar understands:

{
        title      : `${session.title} | ${speakerNames.join(', ')}`,
        start      : session.startsAt.format('YYYY-MM-DDTHH:mm:SS'),
        end        : session.endsAt.format('YYYY-MM-DDTHH:mm:SS'),
        resourceId : session.microlocation.get('id'),
        color      : session.track.get('color'),
        serverId   : session.get('id') // id of the session on BE
}

 

Once the sessions are in the appropriate format, their data is sent to the fullcalendar template, which renders them on the screen:

Screen Shot 2018-08-21 at 8.20.27 PM.png

This completes the implementation of displaying the scheduled sessions of an event on the Open Event Scheduler.

Resources

Continue ReadingImplementing Scheduled Sessions in Open Event Scheduler

Open Event Frontend – Events Explore Page

This blog illustrates how the events explore page is implemented in the Open Event Frontend web app. The user can land on the events explore page by clicking on Browse Events button in the top panel on the home page, shown by the mouse tip in the following picture.

Here, the user can use the various filter options provided to search for the events as per his requirements, He/she can filter according to categories, sub-categories for each category, event type, and date range. A unique feature here is that the user can pick from the start date range options such as today, tomorrow, this week, this weekend, next week and many more. If neither of these fits his needs he can use custom dates as well. The user can also filter events using event location which is autocompleted using Google Maps API. Thus, searching for events is fast, easy and fun.

Let us see how this has been implemented.

Implementation

The explore routes has a method _loadEvents(params). Here, params is the various query parameters for filtering the events. This method forms the query, sends it to the server and returns the list of events returned by the server. The server uses Flask-REST-JSONAPI. It has a very flexible filtering system. It is completely related to the data layer used by the ResourceList manager. More information about this can be found here.

So, the filters are formed using syntax specified in the link mentioned above. We form an array filterOptions which stores the various filters. The default filter is that the event should be published:

let filterOptions = [
 {
   name : 'state',
   op  : 'eq',
   val  : 'published'
 }
];

Then we check for each filter option and check if it is present or not. If yes then we add it to filterOptions. An example as follows:

if (params.category) {
 filterOptions.push({
   name : 'event-topic',
   op  : 'has',
   val  : {
     name : 'name',
     op : 'eq',
     val : params.category
   }
 });
}

This is repeated for sub_category, event_type, location and start_date and end_date. An event is considered to fulfill the date filter if it satisfies any one of the given conditions:

  • If both start_date and end_date are mentioned:
    • Event start_date is after filter start date and before filter end date.
    • Or, event end date if after filter start date and before filter end date.
    • Or, event start date is before filter start date and event end date date is after filter end date.
  • If only start_date is mentioned, then if the event start date is after filter start date or event end date is after filter start date.

The code to this can be found here. For the date ranges mentioned above(today, tomorrow etc) the start dates and end dates are calculated using the moment.js library and then passed on as params.

The filteredEvents are passed in the route model.

async model(params) {
 return {
   eventTypes     : await this.store.findAll('event-type'),
   eventTopics    : await this.store.findAll('event-topic', { include: 'event-sub-topics' }),
   filteredEvents : await this._loadEvents(params)
 };
}

The variable is set in the controller and any change to the query params is observed for. The method _loadEvents is called whenever the query params change.

setupController(controller, model) {
 this._super(...arguments);
 controller.set('filteredEvents', model.filteredEvents);
 this.set('controller', controller);
},

actions: {
 async queryParamsDidChange(change, params) {
   if (this.get('controller')) {
     this.get('controller').set('filteredEvents', await this._loadEvents(params));
   }
 }
}

The template iterates over the filteredEvents and displays each one in a card.

Resources

Continue ReadingOpen Event Frontend – Events Explore Page

Implementing User Email Verification in Open Event Frontend

Open Event Server provides the functionality of user email verification after a user registers, but it was not implemented on Open Event Frontend until recently. For users, this meant they were still not able to verify themselves, even after receiving confirmation links in their inboxes, which were sent by the server. Thus, implementing it on frontend was crucial for a complete user registration workflow.

Since the server had already exposed an endpoint to perform the registration, all that was required on the frontend to be done was to make a call to this endpoint with the necessary data. The entire process can be summarized as follows:

  1. The recently registered user clicks on the verification link she receives on her email
  2. The above step opens the link, which is of the format http://fossasia.github.io/open-event-frontend/verify?token=
  3. As soon as the frontend server receives this request, it extracts the token from the URL query parameter
  4. The token is now sent to the backend server as a patch request
  5. The response of the above request confirms whether the user verification is successful or not, and an appropriate message is displayed

In the frontend code, the above algorithm is spread across 3 files: the router, verify route and verify controller. A new route named /verify was implemented for the user verification, and was registered in the project’s main router.js file. After that, in the verify route, the beforeModel() method is used to trigger the above algorithm before the page is loaded:

// in app/routes/verify.js

beforeModel(transition) {
this.controllerFor('verify').verify(transition.queryParams.token);
}

The main algorithm above is implemented in the verify controller:

// in app/controllers/verify.js
...
queryParams : ['token'],
token       : null,
success     : false,
error       : null,

verify(tokenVal) {
let payload = {
data: {
token: tokenVal
}
};
return this.get('loader')
.post('auth/verify-email', payload)
.then(() => {
this.set('success', true);
})
.catch(reason => {
this.set('error', reason);
this.set('success', false);
});
}
});

 

A template for displaying the success or failure messages to the user was also created. It uses the value of the success boolean set above to decide the message to be displayed to the user. The user registration workflow is now complete and the user sees the following message after clicking on the verification link she receives:

Screen Shot 2018-08-21 at 7.10.43 PM

Resources

Continue ReadingImplementing User Email Verification in Open Event Frontend

Add Edit user Modal in Open Event Frontend

This blog article will illustrate how the UI for the edit user modal is implemented  and Users API has been integrated into it in Open Event Frontend.

The admin can make any user the admin, sales admin or the marketer of the app. In the route admin/users there is an ember table where all the users are listed. In the table there exists a column named ‘Action Buttons’.

When the edit action button is clicked a modal appears on the screen. Them template of the modal is as follows:

class="content">
class="ui form">

class="ui header">{{t 'Provide admin access?'}}

class="grouped inline fields">
class="field"> {{ui-radio name="isAdmin" label="Yes" value=true onChange=(action (mut isAdmin))}} {{ui-radio name="isAdmin" label="No" value=false onChange=(action (mut isAdmin))}}
</div> <h4 class="ui header">{{t 'Custom system roles'}}</h4>
class="field"> {{ui-checkbox label="Sales Admin" onChange=(action (mut checked))}} {{ui-checkbox label="Marketer" onChange=(action (mut checked))}}
<button class="ui teal right floated submit button update-changes"> {{t 'Save'}} </button> </div> </div>

For the API integration the users model is used. The attributes isAdmin, isSalesAdmin, isMarketer from the model are used to send a patch request to the server. The modal has basically to parts. The first part consists of radio buttons through which the super admin has the rights to create a user an admin of the app or to remove his role as the admin. The second part consists of checkboxes through which the user can get the custom system role to be the sales admin or the marketer. A get request is sent to the user’s model in the server and the initial values of the modal are decided.

If the admin changes some value, he clicks on the save button in the modal and a patch request is sent to the server. The save function is written in the modal’s component.

actions: {
saveRole(id) {
this.get('store').findRecord('user', id).then(function(user) {
user.save();
});
this.set('isOpen', false);
},
toggleSalesAdmin(user) {
user.toggleProperty('isSalesAdmin');
},
toggleMarketer(user) {
user.toggleProperty('isMarketer');
},
createAdmin(user, isAdmin) {
user.set('isAdmin', isAdmin);
}
}

Resources

Continue ReadingAdd Edit user Modal in Open Event Frontend

Adding the User Settings Route in Admin User Route Open Event Frontend

This blog article will illustrate how the User settings API has been integrated into the admin users route  Open Event Frontend. The admin can change the contact info of some user, details about the email preferences for different events created by the user and the third party authentication details entered by the user.

To make the settings user link in the user link column of the users table functional new sub routes are added to the app’s user route as follows:

  • /admin/users/<user_id>/settings/contact-info
  • /admin/users/<user_id>/settings/email-preferences
  • /admin/users/<user_id>/settings/applications

The template for the index route which redirects to each of the settings route is:

class="ui grid">
class="row">
class="twelve wide column"> {{#tabbed-navigation}} {{#link-to 'admin.users.view.settings.contact-info' model.user.id class='item'}} {{t 'Contact Info'}} {{/link-to}} {{#link-to 'admin.users.view.settings.email-preferences' model.user.id class='item'}} {{t 'Email-Preferences'}} {{/link-to}} {{#link-to 'admin.users.view.settings.applications' model.user.id class='item'}} {{t 'Applications'}} {{/link-to}} {{/tabbed-navigation}}
</div> {{outlet}} </div>

Interestingly, the routes admin/users/view and admin/users/list are both dynamic and expect a parameter after /users/ hence, the app cannot distinguish between them on it’s own, thus explicit handling of the dynamic parameter of the routes was implemented, differentiating them on the basis of the route’s state as follows:

beforeModel(transition) {
this._super(...arguments);
const userState = transition.params[transition.targetName].users_status;
if (!['all', 'deleted', 'active'].includes(userState)) {
this.replaceWith('admin.users.view', userState);
}
}

Thus if the dynamic portion of the route doesn’t contain the parameters all, deleted or active, then it must be referring to a user’s events or sessions and the route needs to be replaced with the desired events or sessions route accordingly.

The server is queried to fetch the details of a given user like the email,  contact, various events created by the user to get the email and notification preferences. For getting each detail the current users model is returned and the values in the model are returned to the form.

For the contact-info sub route the values like the email and the contact number are fetched and are shown in the form. There is a save button in the form too. The admin can change this information and send a patch request to the server by clicking this button.

 updateContactInfo() {
this.set('isLoading', true);
let currentUser = this.get('model.user');
currentUser.save()
.then(() => {
this.get('notify').success(this.get('l10n').t('Your Contact Info has been updated'));
})
.catch(() => {
this.get('notify').error(this.get('l10n').t('An unexpected error occurred'));
})
.finally(() => {
this.set('isLoading', false);
});
}

For the email-preferences sub route the model has attributes like sessionSchedule, nextEvent etc.and the admin has the access to change the email-notifications for any event  created by any user. The client side has checkboxes to show the data to the user. The states of the checkboxes are determined by the data that we receive from the API. We also let the admin change the preferences of the email-notifications so that he can customise the notifications and keep the ones he wants some user to receive.

{{settings/email-preferences-section preferences=model}}

The sub route for email preferences:

export default Route.extend(AuthenticatedRouteMixin, {
titleToken() {
return this.get('l10n').t('Email Preferences');
},
model() {
const currentUser = this.modelFor('admin.users.view');
return currentUser.query('emailNotifications', { include: 'event' });
}
});

So, the admin has the access to change the information and the email notifications of a user.

Resources

Continue ReadingAdding the User Settings Route in Admin User Route Open Event Frontend

Events API Integration on Admin User Route Open Event Frontend

This blog article will illustrate how the Events API has been integrated into the admin users route  Open Event Frontend, as well as how the action buttons are added to view, edit or delete the events of any user in the list by the admin.

To make the events user link in the user link column of the users table functional a new sub route is added to the app’s user route as follows:

this.route('users', function() {
     this.route('view', { path: '/:user_id' }, function() {
       this.route('events', function() {
         this.route('list', { path: '/:event_status' });
       });
     });

The newly added route further contains a dynamic sub route called list. This nested route fulfills the requirement of filtering the various events of a given user according to their states. Interestingly, the routes admin/users/view and admin/users/list are both dynamic and expect a parameter after /users/ hence, the app cannot distinguish between them on it’s own, thus explicit handling of the dynamic parameter of the routes was implemented, differentiating them on the basis of the route’s state as follows:

beforeModel(transition) {
this._super(...arguments);
const userState = transition.params[transition.targetName].users_status;
if (!['all', 'deleted', 'active'].includes(userState)) {
this.replaceWith('admin.users.view', userState);
}
}

Thus if the dynamic portion of the route doesn’t contain the parameters all, deleted or active, then it must be referring to a user’s events or sessions and the route needs to be replaced with the desired events or sessions route accordingly.

The server is queried to fetch the events of a given user by making use of the hasMany relationship a user has with his sessions. They are loaded in the route admin/users/view/events/list.js

model() {
const userDetails = this.modelFor('admin.users.view');
return this.store.findRecord('user', userDetails.id, {
include: 'events'
});

After fetching the the events from the server, a proper ember table is called in the template file of this route, and all the actions like viewing and editing an event are declared in the template.

{{events/events-table
columns=columns data=model.events
useNumericPagination=true
moveToDetails=(action 'moveToDetails')
editEvent=(action 'editEvent')
openDeleteEventModal=(action 'openDeleteEventModal')
}}

In the controller the columns of the table for events are defined and all the actions are defined.

moveToDetails(id) {
this.transitionToRoute('events.view', id);
},
editEvent(id) {
this.transitionToRoute('events.view.edit.basic-details', id);
},
deleteEvent() {
this.set('isLoading', true);
this.store.findRecord('event', this.get('eventId'), { backgroundReload: false }).then(function(event) {
event.destroyRecord();
})

So, the admin can view the list of the events of a particular user and send a patch or delete request for any event.

Resources

Continue ReadingEvents API Integration on Admin User Route Open Event Frontend