Working with Route Hooks in Badgeyay

Badgeyay is an open source project developed by FOSSASIA Community to generate badges for conferences and events through a simple user interface.

In this post I am going to explain about the route lifecycle hooks in ember and how we have utilized one lifecycle component to reset the controller state in badge generation form. In Ember every entity has some predefined set of methods, that it goes through the existence of the application. Route is not different from it. Our main goal is to restore the state of the controller every time we entered into a route, so that we receive a clean and new instance and not the previous state. The hook that fits in the situation is setupController(). This method is called after model() hook to set the controller state in the route. We will restore the variables in the controller here in this method to reset it to original state. This will help in removing the messages and progress on the previous state of the page on a new visit.

Procedure

  1. Open the route, where we want to override the hook, and create a method setupController() this will call the base hook and override its behaviour.
setupController(controller, model) {
  this._super(…arguments);
  set(controller, ‘defImages’, model.def_images);
  set(controller, ‘user’, model.user);
  this.set(‘controller.badgeGenerated’, false);
  this.set(‘controller.showProgress’, false);
}

 

As we can see in the method, it first initialises the super constructor and then we are writing the logic for the reset state. This will reset the badgeGenerated and showProgress variable in the controller to mentioned values.

  1. Getting the generated badge link from the last promise resolved to set the value of the variable in the controller action.
sendBadge(badgeData) {

        this.set(‘badgeGenerated’, true);

  },

 

This will set the value of the variable to the received object from backend.

  1. Showing the content in frontend based on the values of the variable. When we initially visit the route it is set to as false in the setupController() hook and is changed later on through some promise resolvement in an action.
{{#if badgeGenerated}}

 . . .


{{/if}}

 

This content will be only be shown in the present state and when the user revisits the route the state will be resetted.

Pull Request for the respective issue – https://github.com/fossasia/badgeyay/pull/1313

Resources

Continue ReadingWorking with Route Hooks in Badgeyay

Adding Marketer and Sales Admin Events Relationship with User on Open Event Server

In this blog, we will talk about how to add API for adding and displaying events in with a user acts as a Marketer and/or Sales Admin on Open Event Server. The focus is on Model Updation and Schema updation of User.

Model Updation

For the Marketer and Sales Admin events, we’ll update User model as follows

Now, let’s try to understand these relationships.

In this feature, we are providing user to act as a marketer and sales admin for a event.

  1. Both the relationships will return the events in which the user is acting as a Marketer and/or Sales Admin.
  2. There are two custom system roles in model CustomSysRole which are Marketer and Sales Admin. A user can act as these custom system roles with respect to an event.
  3. In this relationship, we will return those events from UserSystemRole model in which a user is acting as Marketer Custom System Role and Sales Admin Custom System Role.
  4. We make use of Event and join UserSystemRole and CustomSysRole where we use that user where UserSystemRole.user_id == User.id , CustomSysRole.id == UserSystemRole.role_id, CustomSysRole.name == “Sales Admin” and then we return events in which Event.id == UserSystemRole.event_id
  5. Similarly, for Marketer events we make use of Event and join UserSystemRole and CustomSysRole where we use that user where UserSystemRole.user_id == User.id , CustomSysRole.id == UserSystemRole.role_id, CustomSysRole.name == “Marketer” and then we return events in which Event.id == UserSystemRole.event_id

Schema Updation

For the Marketer and Sales Admin events, we’ll update UserSchema as follows

Now, let’s try to understand this Schema.

In this feature, we are providing user to act as a marketer and sales admin for a event.

  1. For displaying marketer_events relation self_view is displayed by API v1.user_marketer_events and collection of these events is displayed by API v1.event_list
  2. These APIs will return the Events as schema=”EventSchema”. Here, many=True tells us that this is One to many relationship with Events model.

So, we saw how an user can act as a marketer and/or sales admin for many events.

Resources

Continue ReadingAdding Marketer and Sales Admin Events Relationship with User on Open Event Server

Adding Custom System Roles in Open Event Server

In this blog, we will talk about how to add different custom system roles concerning a user on Open Event Server. The focus is on its model and Schema updation.

Model Updation

For the User Table, we’ll update our User Model as follows:

Now, let’s try to understand these hybrid properties.

In this feature, we are providing Admin the rights to see whether a user is acting as a Marketer and / or  Sales Admin of any of the event or not. Here, _is__system_role method is used to check whether an user plays a system role like Marketer, Sales Admin or not. This is done by querying the record from UserSystemRole model. If the record is present then the returned value is True otherwise false.

Schema Updation

For the User Model, we’ll update our Schema as follows:

Now, let’s try to understand this Schema.

Since all the properties will return either True or false so these all properties are set to Boolean in Schema.Here dump_only means, we will return this property in the Schema.

So, we saw how User Model and Schema is updated to show custom system roles concerning a user on Open Event Server.

Resources

Continue ReadingAdding Custom System Roles in Open Event Server

Adding a Countdown to Orders Page in Open Event Frontend

This blog post will illustrate how you can add a countdown to orders page which on finishing expires the ticket in Open Event. In Open Event we allow some predefined time for users to fill in their details and once the time expires order gets expired and tickets get released. Users can order the tickets again if they want.

We start by adding a createdAt field to orders model so that we can keep track of remaining time. To calculate the time when the order should expire we add predefined time in which user should fill their details to createdAt time. In this way, we get the time when the order will expire.

So now to calculate the remaining time we just subtract the expiring time from current time. And then we render this data into the template. We define getRemainingTime property in our template and fetch the data for that property with help of javascript.

To see the template code visit this link.

The challenge here is to update the time remaining after every second. For this, we take the help of ember runloop. The run.later() function of ember runloop helps us to calculate the property after every second and set it. Code for setting the remaining time with the help of javascript is given below.

// app/components/forms/orders/order-form.js

getRemainingTime: computed('data', function() {
    let willExpireAt = this.get('data.createdAt').add(10, 'minutes');
    this.timer(willExpireAt, this.get('data.identifier'));
  }),

  timer(willExpireAt, orderIdentifier) {
    run.later(() => {
      let currentTime = moment();
      let diff = moment.duration(willExpireAt.diff(currentTime));
      this.set('getRemainingTime', moment.utc(diff.asMilliseconds()).format('mm:ss'));
      if (diff > 0) {
        this.timer(willExpireAt, orderIdentifier);
      } else {
        this.get('data').reload();
        this.get('router').transitionTo('orders.expired', orderIdentifier);
      }
    }, 1000);
  }

 

As given in the code. We pass expiring time and order’s model instance to the timer function. Timer function calculates the remaining time and sets it to getRemainingTime property of template. Timer function runs after every second with the help of run.later() function of ember runloop. To format the remaining time into MM:SS we take help of moment.js library and format the data accordingly.

Once the remaining time is less than zero (time expires) we reload the model and transition current route to expired route. We do not have to set order status as expired inside the FE. Server sets the order as expired after the predefined time. So we just reload the model from the server and we get the updated status of the order.

Resources:
Continue ReadingAdding a Countdown to Orders Page in Open Event Frontend

Adding Helper and Adding Action Buttons to Orders List in Open Event Frontend

This blog post will illustrate how to add a helper to orders list and add action buttons to orders list to delete and cancel an order in Open Event Frontend. To cancel or delete an order item we need to communicate to the server. The API endpoints to which we communicate are:

  • PATCH        /v1/orders/{order_identifier}
  • DELETE    /v1/orders/{orders_identifier}

We will define the action buttons in ui-table component of open event frontend. We will use the cell-actions file to define the cell buttons that will be present in cell-actions column. The following handlebars code will render the buttons on website.

//components/ui-table/cell/events/view/tickets/orders/cell-actions.hbs

class="ui vertical compact basic buttons"> {{#if (and (not-eq record.status 'cancelled') (can-modify-order record))}} {{#ui-popup content=(t 'Cancel order') click=(action (confirm (t 'Are you sure you would like to cancel this Order?') (action cancelOrder record))) class='ui icon button' position='left center'}} class="delete icon"> {{/ui-popup}} {{/if}} {{#if (can-modify-order record)}} {{#ui-popup content=(t 'Delete order') click=(action (confirm (t 'Are you sure you would like to delete this Order?') (action deleteOrder record))) class='ui icon button' position='left center'}} class="trash icon"> {{/ui-popup}} {{/if}} {{#ui-popup content=(t 'Resend order confirmation') class='ui icon button' position='left center'}} class="mail outline icon"> {{/ui-popup}}

 

In above code you can see two things. First is can-modify-order which is a helper. Helper is used to simplify conditional logics which cannot be easily placed in handlebars. Second thing is action. There are two actions defined: cancelOrder and deleteOrder. We will see implementation of these later. First let’s see how we define can-modify-order helper.

In can-modify-order helper we want to return true or false in case we want cancel button and delete button to display or not respectively. We write the code of can-modify-order in helpers/can-modify-order.js file. When we want to get result from this helper we call it from handlebars file and pass any parameter that we want to use in helper. Code for can-modify-order helper is given below.

// helpers/can-modify-order.js

import Helper from '@ember/component/helper';

export function canModifyOrder(params) {
 let [order] = params;
 if (order.amount !== null && order.amount > 0) {
   // returns false if order is paid and completed
   return order.status !== 'completed';
 }
 // returns true for free ticket
 return true;
}

export default Helper.helper(canModifyOrder);

 

We extract the parameter and store it in order variable. We see if it satisfies our conditions we return true else false.

Now lets see how we can define actions to perform delete and cancel action on a order. We define these actions in controllers section of app. After performing suitable operation with order we call save to update modified order and destroyRecord() to delete an order. Let see the code implementation for these actions.

actions: {
   deleteOrder(order) {
     this.set('isLoading', true);
     order.destroyRecord()
       .then(() => {
         this.get('model').reload();
         this.notify.success(this.get('l10n').t('Order has been deleted successfully.'));
       })
       .catch(() => {
         this.notify.error(this.get('l10n').t('An unexpected error has occurred.'));
       })
       .finally(() => {
         this.set('isLoading', false);
       });
   },
   cancelOrder(order) {
     this.set('isLoading', true);
     order.set('status', 'cancelled');
     order.save()
       .then(() => {
         this.notify.success(this.get('l10n').t('Order has been cancelled successfully.'));
       })
       .catch(() => {
         this.notify.error(this.get('l10n').t('An unexpected error has occurred.'));
       })
       .finally(() => {
         this.set('isLoading', false);
       });
   }

 
After defining these actions, buttons in the orders list start working. In this way, we can make use of helper to simplify the conditional logic inside templates and define proper actions.

Resources:
Continue ReadingAdding Helper and Adding Action Buttons to Orders List in Open Event Frontend

How to Make Promotional Codes Applicable on Tickets During Ordering in Open Event Frontend

This blog illustrate how to enable application of promotional codes on tickets during ordering tickets in Open Event Frontend to avail discounts and access to special tickets. Open event allows organizers to add some promotional codes on some tickets, which can be used by users to avail additional offers on tickets while ordering. Promotional codes can be of three types:

  1. Discount Codes: Allows customers to buy a ticket at discounted rates.
  2. Access Codes: Allows customers to access some hidden tickets which are accessible only to special customers.
  3. Discount + Access Code: Allows customer to access special tickets and avail discount at the same time.

Creating a discount/access code:

Organizers and admin can create an access code or a discount code from the event dashboard. They can specify the validity period of the code and can also specify the tickets on which the code will be applicable.

Validating promotional code after user enters the code:

User is allowed to enter the promotional code on events page upon selecting the tickets. IF promotional code is valid then suitable discount is provided on applicable tickets and if promotional code is an access code then hidden tickets for which the promotional code is valid are shown.

To check the validity of the promotional code we deal with the following APIs on the open event server:

  • GET             /v1/discount-codes/{Code}              (For Discount code)
  • GET             /v1/access-codes/{Code}                  (For Access code)

Code snippet to check the validity for access code is given below:

let promotionalCode = this.get('promotionalCode');
 let order = this.get('order');
   try {
     let accessCode = await this.get('store').findRecord('access-code', promotionalCode, {});
     order.set('accessCode', accessCode);
     let tickets = await accessCode.get('tickets');
     tickets.forEach(ticket => {
     ticket.set('isHidden', false);
     this.get('tickets').addObject(ticket);
     this.get('accessCodeTickets').addObject(ticket);
     this.set('invalidPromotionalCode', false);
  });
  } catch (e) {
     this.set('invalidPromotionalCode', true);
  }

 

Full code can be seen here https://github.com/fossasia/open-event-frontend/blob/development/app/components/public/ticket-list.js

Similarly for discount code we fetch the details of the discount code via the api and then validate the code. After the validation we apply the discount to the tickets applicable. Code snippet for the discount code part is given below:

try {
  let discountCode = await this.get('store').findRecord('discount-code', promotionalCode, { include: 'tickets' });
  let discountType = discountCode.get('type');
  let discountValue = discountCode.get('value');
  order.set('discountCode', discountCode);
  let tickets = await discountCode.get('tickets');
  tickets.forEach(ticket => {
     let ticketPrice = ticket.get('price');
     if (discountType === 'amount') {
       ticket.set('discount', Math.min(ticketPrice, discountValue));
       this.get('discountedTickets').addObject(ticket);
     } else {
       ticket.set('discount', ticketPrice * (discountValue / 100));
       this.get('discountedTickets').addObject(ticket);
     }
     this.set('invalidPromotionalCode', false);
  });
} catch (e) {
   if (this.get('invalidPromotionalCode')) {
      this.set('invalidPromotionalCode', true);
   }
}

 

Full code can be seen https://github.com/fossasia/open-event-frontend/blob/development/app/components/public/ticket-list.js

After promotional codes are verified we apply them to the selected tickets. In this way we apply the promotional codes to the tickets.

Resources

 

Continue ReadingHow to Make Promotional Codes Applicable on Tickets During Ordering in Open Event Frontend

Implementing Accepting and Rejecting Proposals in Open Event Frontend

This blog post will illustrate how to add buttons to accept and reject proposal and making them functional. Sessions tab in event dashboard communicates with the following APIs of Open Event Server.

  • GET                    /v1/sessions
  • PATCH              /v1/sessions
What is meant by accepting or rejecting a session in open event?

Sessions are part of event which include one or many speakers. Speakers can propose one or many sessions for a event. Now it is duty of organizer to accept some proposals and reject others. Open event provides two options to accept or reject a proposal i.e. with email or without email.

For this we need to send a key value pair which includes whether we want to send email or not along with other parameters which include current state of session and other important properties. A typical request to alter state of session looks like this.

{
  "data": {
    "attributes": {
      "title": "Micropython Session",
      "level": 1,
      "starts-at": "2017-06-01T10:00:00.500127+00:00",
      "ends-at": "2017-06-01T11:00:00.500127+00:00",
      "created-at": "2017-05-01T01:24:47.500127+00:00",
      "is-mail-sent": false,
      "send-email": true,
    },
    "type": "session",
    "id": "1"
  }
}
Implementing in frontend

We start by providing two buttons for a pending session. One to accept the session and other to reject the session.

On clicking either accept or reject button we get two options to choose i.e. with email and without email. Depending on what organizer chooses a action is fired from the template and sent to controller. Template code for these buttons looks something like this.

class=“ui vertical compact basic buttons”> {{#unless (eq record.state ‘accepted’)}} {{#ui-dropdown class=‘ui icon bottom right pointing dropdown button’}} class=“green checkmark icon”>

class=“menu”>

class=“item” {{action acceptProposal record true}}>{{t ‘With email’}}

 


class=“item” {{action acceptProposal record false}}>{{t ‘Without email’}}

 

      </div>
    {{/ui-dropdown}}
  {{/unless}}
  {{#unless (eq record.state 'rejected')}}
    {{#ui-dropdown class='ui icon bottom right pointing dropdown button'}}
      <i class="red remove icon"></i>

class=“menu”>

class=“item” {{action rejectProposal record true}}>{{t ‘With email’}}

 


class=“item” {{action rejectProposal record false}}>{{t ‘Without email’}}

 

      </div>
    {{/ui-dropdown}}
  {{/unless}}
</div>

We can see that for with email button we trigger accept proposal button with two parameters record and true. Record contains the instance of session and true signifies that we are sending email. Similar is the case with without email button. Controller for these actions looks something like this.

acceptProposal(session, sendEmail) {
      session.set('sendEmail', sendEmail);
      session.set('state', 'accepted');
      session.set('isMailSent', sendEmail);
      this.set('isLoading', true);
      session.save()
        .then(() => {
          sendEmail ? this.notify.success(this.get('l10n').t('Session has been accepted and speaker has been notified via email.'))
            : this.notify.success(this.get('l10n').t('Session has been accepted'));
        })
        .catch(() => {
          this.notify.error(this.get('l10n').t('An unexpected error has occurred.'));
        })
        .finally(() => {
          this.set('isLoading', false);
        });
    },
    rejectProposal(session, sendEmail) {
      session.set('sendEmail', sendEmail);
      session.set('state', 'rejected');
      session.set('isMailSent', sendEmail);
      this.set('isLoading', true);
      session.save()
        .then(() => {
          sendEmail ? this.notify.success(this.get('l10n').t('Session has been rejected and speaker has been notified via email.'))
            : this.notify.success(this.get('l10n').t('Session has been rejected'));
        })
        .catch(() => {
          this.notify.error(this.get('l10n').t('An unexpected error has occurred.'));
        })
        .finally(() => {
          this.set('isLoading', false);
        });
    }

For accepting with email we set sendEmail field to true and send the query to server. Similarly for reject proposal action we follow same procedure.

Conclusion

Implementing buttons like these, and defining proper actions like these we are able to change the state of session with options to send email or not.

Resources

Continue ReadingImplementing Accepting and Rejecting Proposals in Open Event Frontend

Adding Online Payment Support in Online Event Frontend via Stripe

Open Event Frontend involves ticketing system which supports both paid and free tickets. To buy a paid ticket Open Event provides several options such as debit card, credit card, cheque, bank transfer and onsite payments. So to add support for debit and credit card payments Open Event uses stripe checkout. Using stripe users can enter their card details and pay for their ticket.

Given below are some steps which are to be followed for successfully charging a user for ticket using his/her card.

  • We get publishable key for organizer’s stripe account using OAuth. See this blog.
  • We render stripe checkout button using stripe publishable key. This helps us identify which user to credit after payment.
  • After clicking checkout button user is prompted to enter his/her card details and verify payment.
  • After user’s verification stripe generates a payment token is which is used by open event frontend to charge the user for stipulated amount.
  • We send this token to open event server which processes the token and charge the user.
  • We get error or success message from open event server as per the process outcome.

To render the stripe checkout button we use ember-cli-stripe. Below is the code which helps us to understand how stripe checkout button is rendered.

// app/templates/orders/placed.hbs 

{{stripe-checkout
    locale='auto'
    name="Open Event"
    description=paymentDescription
    amount=paymentAmount
    key=model.event.stripeAuthorization.stripePublishableKey
    onToken=(action "processStripeToken")
    onClosed=(action "checkoutClosed")
    onOpened=(action "checkoutOpened")
}}

 

Full code can be seen here.

We see that we pass different parameters to stripe button which helps stripe identify how to render the button and what information to display. We have also passed the actions onToken(), onClosed() and onOpened(). All these actions are triggered at different instances based on what event occurs.

onToken(): This action is triggered when user has verified his/her purchase and we stripe has generated the payment token. Stripe passes the token back to client (open event frontend server) to process. We have handled this error via different name “processStripeToken()”. We will see cose for these actions below.

onClosed(): This action is called when checkout prompt is closed. We have not used this action in open event frontend. But this can be used to trigger some event in case your application need some action when checkout prompt is closed.

onOpened(): This action is called when checkout prompt is opened. We have not used this action in open event frontend. But this can be used to trigger some event in case your application need some action when checkout prompt is opened.

Code for these actions are given below. Full code file can be seen here.

//  app/controllers/orders/placed.js 

processStripeToken(token) {
   // Send this token to server to process payment
   let order = this.get('model');
   let chargePayload = {
     'data': {
       'attributes': {
         'stripe'            : token.id,
         'paypal_payer_id'   : null,
         'paypal_payment_id' : null
       },
       'type': 'charge'
     }
   };
   let config = {
     skipDataTransform: true
   };
   chargePayload = JSON.stringify(chargePayload);
   return this.get('loader').post(`orders/${order.identifier}/charge`, chargePayload, config)
     .then(charge => {
       if (charge.data.attributes.status) {
         this.get('notify').success(charge.data.attributes.message);
         this.transitionToRoute('orders.view', order.identifier);
       } else {
         this.get('notify').error(charge.data.attributes.message);
       }
     });
 }

 

In above code snippet for processStipeToken() we are processing the stripe payment token received from stripe after user verifies his/her payment. We pass this token to charge endpoint of open event server. After charging the user server send a response which is displayed on frontend.

In this way we achieve the functionality of adding stripe payment support in open event frontend. Please follow the links below for further clarification and detailed overview.

Resources:
Continue ReadingAdding Online Payment Support in Online Event Frontend via Stripe

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