Adding System Messages on Open Event Server

The Open Event Server enables organizers to manage events from concerts to conferences and meetups. It offers features for events with several tracks and venues. Event managers can create invitation forms for speakers and build schedules in a drag and drop interface. The event information is stored in a database. The system provides API endpoints to fetch the data, and to modify and update it.

The Open Event Server is based on JSON 1.0 Specification and hence build on top of Flask Rest Json API (for building Rest APIs) and Marshmallow (for Schema).

In this blog, we will talk about how to add API for accessing the System Messages on Open Event Server. The focus is on its Model updation and it’s Schema creation.

Model Updation

For the System Messages, we’ll make update model as follows

Now, let’s try to understand this Schema.

In this feature, we are providing Admin the rights to read email and notification formats used in Open Event application.

  1. First of all, there is the need to know that it has three columns notification_status, user_control_status and mail_status of type boolean.
  2. Next it has action attribute which is of type String.
  3. At last, we have hybrid properties email_message and notification_message which will return the format of email and notification respective to the action string.
  4. The hybrid properties depends on _email_message method and _notification_message method. These methods reads the MAILS and NOTIFS dictionaries and return there values corresponding to string of action key of corresponding record.

Schema Creation

For the System Messages, we’ll make our Schema as follows

Now, let’s try to understand this Schema.

In this feature, we are providing Admin the rights to read email and notification formats used in Open Event application.

  1. First of all, there is the need to know that it has three boolean properties notification_status, user_control_status and mail_status
  2. Next it has action attribute which is of type String and it’s value can be validated to have any one of the list provided in choices.
  3. At last, it has the String attributes email_message and notification_message which will return the action formats of email and notification concerning the action string provided.

So, we saw how System Messages Schema and Model is created / updated to allow Admin users to read it’s values.

Resources

Continue ReadingAdding System Messages on Open Event Server

Adding Dredd Tests for Image Sizes on Open Event Flask Server

In this blog, we will talk about how to add dredd hooks for testing the API of Event Image Sizes and Speaker Image Sizes in Open Event Server. The focus is on adding the factory class and dredd hooks of these APIs using factory-boy python library and Dredd API testing framework.

Factory Creation

For the Event and Speaker Image Sizes, we’ll make our factory classes EventImageSizeFactory  and SpeakerImageSizeFactory as follows

Now, let’s try to understand this class.

In this class, we are writing the sample data two records of ImageSizes Model, these records corresponds to Event and Speaker Image Sizes.

  1. First of all, we inherit class factory.alchemy.SQLAlchemyModelFactory to build our sample data which for Image Sizes.
  2. Class Meta has model and sqlalchemy_session attributes. Model tells the factory class of to which model this factory class push the data to database and sqlalchemy_session is assigned with the current database session.
  3. Next, we add the attributes according to the model and Schema of Image Sizes.

Adding Dredd Hooks

For the ImageSizes, we’ll make our dredd hooks as follows

Now, let’s try to understand these tests.

In this tests, we check the API by matching the response after adding a record in these API to one which is present at API blueprint.

  1. First of all, we use decorator @hooks.before which means we first add a record in the database and then match the response we get from API say /v1/event-image-sizes with the response mentioned at Image Size > Event Image Size Details > Get Event Image Size Details in API blueprint.
  2. We create an instance of EventImageSizeFactory which is a record of model Image Sizes.
  3. This record is then returned as a response of API /v1/event-image-sizes and matches with the blueprint at Image Size > Event Image Size Details > Get Event Image Size Details

Similarly, we have added other dredd tests for PATCH method as well.

So, we saw how factory-boy python library and Dredd API testing framework helped us in testing the REST APIs on Open Event Server.

Resources

Continue ReadingAdding Dredd Tests for Image Sizes on Open Event Flask Server

Building the API of Speaker Image Size on Open Event Server

The Open Event Server enables organizers to manage events from concerts to conferences and meetups. It offers features for events with several tracks and venues.It uses the JSON 1.0 Specification and build on top of Flask Rest Json API (for building Rest APIs) and Marshmallow (for Schema). In this blog, we will talk about how to add API for accessing and updating the Speaker Image Size on Open Event Server. The focus is on its API creation.

API Creation

For the SpeakerImageSizeDetail, we’ll make our Schema as follows

Now, let’s try to understand SpeakerImageSizeDetail.

In this feature, we are providing Admin the rights to Get and Update the SpeakerImageSizes

  1. kwargs[‘id’] = 2 states that Image Size model has 2 records and 1st record is used for Event Image Size and 2nd record is used for Speaker Image Size.
  2. decorators = (api.has_permission(‘is_admin’, methods=”PATCH”, id=”2″),) states that for Speaker Image Size, Update API is accessible to Admins only.
  3. methods = [‘GET’, ‘PATCH’] states that this API provides two methods i.e. GET and PATCH.
  4. schema = SpeakerImageSizeSchema states that the schema which is used to return the response is Speaker Image Size Schema.
  5. data_layer = {‘session’: db.session, ‘model’: ImageSizes} states the session and Model used to fetch the records.

Resources

Continue ReadingBuilding the API of Speaker Image Size on Open Event Server

Add More Languages to a Skill in SUSI.AI

The SUSI SKill CMS provides skills in multiple languages. Often there are similar skills in different languages. For example, there is a News skill in English and Samachar skill in Hindi. Then why not link them together and mark one as the translation of the other. This will help the user to reach and explore the desired skill in an efficient way. Moreover, it may be easier to type ‘News’ than ‘समाचार’ and find the required skill through translations. So here it has been explained how to link two SUSI skills as translations.

Server side implementation

Create a skillSupportedLanguages.json file to store the related skills together as translations and make a JSONTray object for that in src/ai/susi/DAO.java file. The JSON file contains the language name and the skill name in that language, wrapped in an array.

public static JsonTray skillSupportedLanguages;

Path skillSupportedLanguages_per = skill_status_dir.resolve("skillSupportedLanguages.json");
Path skillSupportedLanguages_vol = skill_status_dir.resolve("skillSupportedLanguages_session.json");
skillSupportedLanguages = new JsonTray(skillSupportedLanguages_per.toFile(), skillSupportedLanguages_vol.toFile(), 1000000);
OS.protectPath(skillSupportedLanguages_per);
OS.protectPath(skillSupportedLanguages_vol);

Now create an API that accepts the skill details and translation details and stores them in the JSON file. Create UpdateSupportedLanguages.java class for the API.

Endpoint: /cms/updateSupportedLanguages.json

Minimum user role: Anonymous

Params:

  • Model
  • Group
  • Language (language of the skill for which translation is to be added)
  • Skill (name of the skill for which translation is to be added)
  • New language (translation language of the skill)
  • New skill name (name of the skill in translated language)

When a new translation is added check if it already exists in the translation group stored in the skillSupportedLanguages.json. Use the DAO object and loop over the array, check is the language name and the language name already exists. If yes then simply return.

if (!alreadyExixts) {
    groupName.put(createSupportedLanguagesArray(language_name, skill_name, new_language_name, new_skill_name));
}

Otherwise, create a new object containing the new language name and the skill name in that language and add it to the translation group.

public JSONArray createSupportedLanguagesArray(String language_name, String skill_name, String new_language_name, String new_skill_name) {
    JSONArray supportedLanguages =  new JSONArray();

    JSONObject languageObject = new JSONObject();
    languageObject.put("language", language_name);
    languageObject.put("name", skill_name);
    supportedLanguages.put(languageObject);

    JSONObject newLanguageObject = new JSONObject();
    newLanguageObject.put("language", new_language_name);
    newLanguageObject.put("name", new_skill_name);
    supportedLanguages.put(newLanguageObject);

    return supportedLanguages;
}

Add this API to SusiServer.java

// Add translation to the skill
UpdateSupportedLanguages.class

Resources

 

Continue ReadingAdd More Languages to a Skill in SUSI.AI

Adding Support for Playing Audio in SUSI iOS App

SUSI.AI supports various actions like the answer, map, table, video play and many more. You can play youtube videos in the chat screen. It also supports for playing audio in the chat screen. In this post, we will see that how playing audio feature implemented in SUSI iOS.

Getting audio action from server side –

In the chat screen, when we ask SUSI to play audio, we get the audio source from the server side. For example, if we ask SUSI “open the pod bay door”, we get the following action object:

"actions": [
{
"type": "audio_play",
"identifier_type": "youtube",
"identifier": "7qnd-hdmgfk"
},
{
"language": "en",
"type": "answer",
"expression": "I'm sorry, Dave. I'm afraid I can't do that."
}
]

In the above action object, we can see that we get two actions, audio_play and answer. In audio_play action, we are getting an identifier type which tells us about the source of audio. Identifier type can be youtube or local or any other source. When the identifier is youtube, we play audio from youtube stream. In identifier, we get the audio file path. In case of youtube identifier type, we get youtube video ID and play from youtube stream. In answer action type, we get the expression which we display in chat screen after thumbnail.

Implementing Audio Support in App –

We use Google’s youtube Iframe API to stream audio from youtube videos. We have a VideoPlayerView that handle all the iFrame API methods and player events with help of YTPlayer HTML file.

Presenting the YouTubePlayerCell –

If the action type is audio_play, we are presenting the cell in chat screen using cellForItemAt method of UICollectionView.

if message.actionType == ActionType.audio_play.rawValue {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ControllerConstants.youtubePlayerCell, for: indexPath) as? YouTubePlayerCell {
cell.message = message
cell.delegate = self
return cell
}
}

Setting size for cell –

Using sizeForItemAt method of UICollectionView to set the size.

if message.actionType == ActionType.audio_play.rawValue {
return CGSize(width: view.frame.width, height: 158)
}

In YouTubePlayerCell, we fetch thumbnail and display in the cell with a play button. On clicking the play button, we open the player and stream music.

Final Output –

Resources –

  1. Apple’s Documentations on sizeForItemAt
  2. SUSI API Sample for Audio Play Action
  3. YouTube iFrame API for iOS
  4. Apple’s Documentations on cellForItemAt
Continue ReadingAdding Support for Playing Audio in SUSI iOS App

How webclient Settings are Implemented in SUSI.AI Accounts

The goal of accounts.susi is to implement all settings from different clients at a single place where user can change their android, iOS or webclient settings altogether and changes should be implemented directly on the susi server and hence the respective clients. This post focuses on how webclient settings are implemented on accounts.susi. SUSI.AI follows the accounting model across all clients and every settings of a user are stores at a single place in the accounting model itself. These are stored in a json object format and can be fetched directly from the ListUserSettings.json endpoint of the server.

Continue ReadingHow webclient Settings are Implemented in SUSI.AI Accounts

Snackbar Implementation in PSLab Android App

In PSLab android app we have developed the functionality of logging sensor data in CSV format. We can start and stop the data recording using the save button in the upper right corner of the menu bar and toast message was shown to notify the user for logging status whether it is started or stopped but it leads to some problem like:-

  • The user doesn’t know where the logged file has been created in the external storage.
  • If the user accidentally clicked on the save button the data logging will start the user have to manually go the storage location and delete the recently created unwanted CSV file.

What’s the solution?

The solution to both these problem is solved by implementing Snackbar instead of Toast message.

According to Material Design documentation:-

The Snackbar widget provides brief feedback about an operation through a message at the bottom of the screen. Snackbar disappears automatically, either after a timeout or after a user interaction elsewhere on the screen, and can also be swiped off the screen.

Snackbar can also offer the ability to perform an action, such as undoing an action that was just taken or retrying an action that had failed.

 

Figure 1 shows a Snackbar sample
(Source: – https://material.io/develop/android/components/snackbar/ )

 

To implement the Snackbar in our Android app I started by creating a custom snack bar class which contains all the code to create and show the Snackbar on the screen.

public class CustomSnackBar {

   public static void showSnackBar(@NonNull CoordinatorLayout holderLayout,  
                                   @NonNull String displayText,
                                   String actionText, 
                                   View.OnClickListener clickListener){
       
   Snackbar snackbar =     
              Snackbar.make(holderLayout,displayText,Snackbar.LENGTH_LONG)
              .setAction(actionText, clickListener);

  //do your customization here
}

The custom class contains a static method ‘showSnackBar()’ having parameters:

Parameter Return Type Description
holderLayout CoordinatorLayout Container layout in which the snack bar will be shown at the bottom (should not be null)
displayText String Text to be displayed in the content of Snackbar (should not be null)
actionText String Clickable text which has some action associated with it
clickListener View.OnClickListener On click listener specifying an action to be performed when actionText is clicked

 

Inside the method, I called the static make()  method provided by the Snackbar class and passed holderlayout, displayText and duration of Snackbar in this case Snackbar.LENGTH_LONG as parameters.

Then I called setAction() and passed in the actionText and the clickListener as parameters in it to set the action text. If we pass in null no action text will be generated.

Then, if we want to changes the action text color we can do that by calling setActionTextColor() and passing in the desired color.

snackbar.setActionTextColor(ContextCompat.getColor(holderLayout.getContext(), R.color.colorPrimary));

And if we want to change the content text color then we need to first get the view then we need to get the instance of TextView containing the content text using findViewById() and passing android.support.design.R.id.snackbar_text which is default ID for context TextView, and then call setTextColor() to set the desired color.

View sbView = snackbar.getView();
   TextView textView =     
             sbView.findViewById(android.support.design.R.id.snackbar_text);
       textView.setTextColor(Color.WHITE);
   }

So, now our Snackbar engine is complete now we need to call CustomSnackBar class static method showSnackbar() in our sensor data logger.

For doing this I replaced all the instances of the Toast message with the ‘CustomSnackBar’ by passing in the desired messages that were being passed in Toast message.

But I still need to find the location of our stored CSV file and a method to delete the current generated CSV file.

For that, I did below modification to the CSVLogger class in PSLab android app.

public class CSVLogger {
   private static final String CSV_DIRECTORY = "PSLab";
   public CSVLogger(String category) {
       this.category = category;
       setupPath();
   }
   /*Below methods are included at the bottom of the class */
   public String getCurrentFilePath() {
       return Environment.getExternalStorageDirectory().getAbsolutePath() +
               File.separator + CSV_DIRECTORY + File.separator + category;
   }
   public void deleteFile() {
       csvFile.delete();
   }
}

Now for passing the location of the stored file and implementing delete option, I called the below method when the CSV logging is stopped by the user:

CustomSnackBar.showSnackBar((CoordinatorLayout) parent.findViewById(R.id.cl),

                    “CSV File stored at” + " " +lux_logger.getCurrentFilePath(),
  
                    “DELETE”,

                    new View.OnClickListener() {
                              Override
                              public void onClick(View view) {
                                             lux_logger.deleteFile();    
                              });

By doing this I get a Snackbar as shown in Figure 2, clicking on the “DELETE” text deletes the current CSV file.

Figure 2 shows snackbar showing file stored location and delete option

 

So, implementing Snackbar helped to make the app interactive and keeps user notified and control the data logging.

Resources

  1. https://www.journaldev.com/10324/android-snackbar-example-tutorial – Android SnackBar example implemetation tutorial
  2. https://material.io/develop/android/components/snackbar/ – Android Material Desing implementation of Snackbar.
Continue ReadingSnackbar Implementation in PSLab Android App

Attendee Form Builder in Open Event Frontend

The Open-Event-Frontend allows the event organiser to create tickets for his or her event. Other uses can buy these tickets in order to attend the event. When buying a ticket we ask for certain information from the buyer. Ideally the event organizer should get to choose what information they want to ask from the buyer. This blog post goes over the implementation of the attendee form builder in the Open Event Frontend.

Information to Collect

The event organizer can choose what details to ask from the order buyer. In order to specify the choices, we present a table with the entries of allowed fields that the organizer can ask for. Moreover there is an option to mark the field as required and hence making it compulsory for the order buyer to add that information in order to buy the tickets.

Route

The route is mainly responsible for fetching the required custom forms.

async model() {
    let filterOptions = [{
      name : 'form',
      op   : 'eq',
      val  : 'attendee'
    }];

    let data = {
      event: this.modelFor('events.view')
    };
    data.customForms = await data.event.query('customForms', {
      filter       : filterOptions,
      sort         : 'id',
      'page[size]' : 50
    });

    return data;
  }

If they don’t exist then we create them in afterModel hook. We check if the size of the list of custom forms sent from the server is 3 or not. If it is 3 then we create the additional custom forms for the builder. Upon creating an event, the server automatically creates 3 custom forms for the builder. These 3 forms are firstName, lastName and email.

afterModel(data) {
    /**
     * Create the additional custom forms if only the compulsory forms exist.
     */
    if (data.customForms.length === 3) {
      let customForms = A();
      for (const customForm of data.customForms ? data.customForms.toArray() : []) {
        customForms.pushObject(customForm);
      }

      const createdCustomForms = this.getCustomAttendeeForm(data.event);

      for (const customForm of createdCustomForms ? createdCustomForms : []) {
        customForms.pushObject(customForm);
      }

      data.customForms = customForms;
    }
  }

Complete source code for reference can be found here.

Component Template

The component template for the form builder is supposed to show the forms and other options to the user in a presentable manner. Due to pre-existing components for handling custom forms, the template is extremely simple. We just loop over the list of custom forms and present the event organizer with a table comprising of the forms. Apart from the forms the organizer can specify the order expiry time. Lastly we present a save button in order to save the changes.

<form class="ui form {{if isLoading 'loading'}}"  {{action 'submit' data on='submit'}} autocomplete="off">
  <h3 class="ui dividing header">
    <i class="checkmark box icon"></i>
    <div class="content">
      {{t 'Information to Collect'}}
    </div>
  </h3>
  <div class="ui two column stackable grid">
    <div class="column">
      <table class="ui selectable celled table">
        <thead>
          <tr>
            {{#if device.isMobile}}
              <th class="center aligned">
                {{t 'Options'}}
              </th>
            {{else}}
              <th class="right aligned">
                {{t 'Option'}}
              </th>
              <th class="center aligned">
                {{t 'Include'}}
              </th>
              <th class="center aligned">
                {{t 'Require'}}
              </th>
            {{/if}}
          </tr>
        </thead>
        <tbody>
          {{#each data.customForms 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>
      </table>
    </div>
  </div>
  <h3 class="ui dividing header">
    <i class="options box icon"></i>
    <div class="content">
      {{t 'Registration Options'}}
    </div>
  </h3>
  <div class="field">
    <label>{{t 'REGISTRATION TIME LIMIT'}}</label>
    <div class="{{unless device.isMobile 'two wide'}} field">
      {{input type='number' id='orderExpiryTime' value=data.event.orderExpiryTime min="1" max="60" step="1"}}
    </div>
  </div>
  <div class="ui hidden divider"></div>
  <button type="submit" class="ui teal submit button" name="submit">{{t 'Save'}}</button>
</form>

 

Component

The component is responsible for saving the form. It also provides runtime validations to ensure that the entries entered in the fields of the form are valid.

import Component from '@ember/component';
import FormMixin from 'open-event-frontend/mixins/form';

export default Component.extend(FormMixin, {
  getValidationRules() {
    return {
      inline : true,
      delay  : false,
      on     : 'blur',
      fields : {
        orderExpiryTime: {
          identifier : 'orderExpiryTime',
          rules      : [
            {
              type   : 'integer[1..60]',
              prompt : this.get('l10n').t('Please enter a valid registration time limit between 1 to 60 minutes.')
            }
          ]
        }
      }
    };
  },
  actions: {
    submit(data) {
      this.onValid(() => {
        this.save(data);
      });
    }
  }
});

References

 

Continue ReadingAttendee Form Builder in Open Event Frontend

Redirecting to Previous Route in Ember

The Open-Event-Frontend allows the event organiser to create tickets for his or her event. Other uses can buy these tickets in order to attend the event. In order to make the user experience smooth, we redirect the user to their previous route when they successfully login into their account. This blog explains how we have achieved this functionality in the project.

Insight

We have two different cases to handle in order to solve this problem:

  1. The user was in route A and wanted to move to route B. Here route A doesn’t require authorization and route B requires authorization. In this case, we would like to direct the user to the login route and once they are done, redirect them back to route B.
  2. The user was in route A and directly entered the login route using the login button. In this case we want to direct them back to the route A after successful login.

We use Ember-simple-auth in order to manage authentication in the project. Not only does it make it easy to manage authentication, it also handles the case 1 for us out of the box. So now the simplified problem is to redirect the user back to the previous route if they entered the login route directly using the web address or the login button.

Approach

If we can somehow store the previous route visited by a user, then we can easily redirect them back once they are logged in.

We will add a custom property in the session service called previousRouteName which will store the URL of the previous route visited by the user. We will make use of the willTransition hook in the application.js file. This hook is called everytime the user transitions from one route to another which makes it suitable for us to update the previousRouteName.

actions: {
    willTransition(transition) {
      transition.then(() => {
        let params = this._mergeParams(transition.params);
        let url;

        // generate doesn't like empty params.
        if (isEmpty(params)) {
          url = transition.router.generate(transition.targetName);
        } else {
          url = transition.router.generate(transition.targetName, params);
        }
        // Do not save the url of the transition to login route.
        if (!url.includes('login')) {
          this.set('session.previousRouteName', url);
        }
      });
    }
  }

_mergeParams is a helper function which makes use of merge function of the Lodash library.

/**
   * Merge all params into one param.
   * @param params
   * @return {*}
   * @private
   */
  _mergeParams(params) {
    return merge({}, ...values(params));
  },

Now we’re done with saving the URL of the previous route. All that remains is to trigger the redirect once the user has successfully logged in. We will use the sessionAuthenticated hook which is triggered everytime the user logs in.

sessionAuthenticated() {
    if (this.get('session.previousRouteName')) {
      this.transitionTo(this.get('session.previousRouteName'));
    } else {
      this._super(...arguments);
    }
  },

If the previous route variable is set, we redirect to it otherwise we can the super method and let Ember-simple-auth handle case 1 mentioned earlier for us.

References

Continue ReadingRedirecting to Previous Route in Ember

My Tickets in Open Event Frontend

The Open-Event-Frontend allows the event organiser to create tickets for his or her event. Other uses can buy these tickets in order to attend the event. The My tickets section lists all the tickets that have been bought by a user. This blog post explains how it has been implemented in the project.

Route

The My-Tickets list route has three responsibilities:

  1. Showing appropriate title according to the current tab.
  2. Setting the filter options according to the tab.
  3. Fetching the data from the store according to the filter options.

The title of the route is decided by the following snippet:

titleToken() {
    switch (this.get('params.ticket_status')) {
      case 'upcoming':
        return this.get('l10n').t('Upcoming');
      case 'past':
        return this.get('l10n').t('Past');
      case 'saved':
        return this.get('l10n').t('Saved');
    }
  },

The second and the third requirement is satisfied inside the model hook. We define the filterOptions according to the current tab and then make the request to fetch the data accordingly. The following code snippet is responsible for this:

model(params) {
    this.set('params', params);
    let filterOptions = [{
      name : 'completed-at',
      op   : 'ne',
      val  : null
    }];
    if (params.ticket_status === 'upcoming') {
      filterOptions.push(
        {
          name : 'event',
          op   : 'has',
          val  : {
            name : 'starts-at',
            op   : 'ge',
            val  : moment().toISOString()
          }
        });
    } else if (params.ticket_status === 'past') {
      filterOptions.push(
        {
          name : 'event',
          op   : 'has',
          val  : {
            name : 'ends-at',
            op   : 'lt',
            val  : moment().toISOString()
          }
        }
      );
    }

    return this.get('authManager.currentUser').query('orders', {
      include : 'event',
      filter  : filterOptions
    });
  }

Template

The template of the My tickets list is extremely simple. We simply loop over all the orders and use the order-card component to display each of them. The order-card component is discussed in detail later. If there are no orders under the user, we show the appropriate message.

<div class="row">
  <div class="sixteen wide column">
    {{#if model}}
      {{#each model as |order|}}
        {{#order-card order=order}}
        {{/order-card}}
        <div class="ui hidden divider"></div>
      {{/each}}
    {{else}}
      <div class="ui disabled header">{{t 'No tickets found'}}</div>
    {{/if}}
  </div>
</div>

Order-card Component

The order card component is responsible for handling a single order and showing its details in as a card. In order to decide whether the order is a paid order or not, we have defined a computed property inside the order-card.js file.

import Component from '@ember/component';
import { computed } from '@ember/object';
import { isEqual } from '@ember/utils';

export default Component.extend({
  isFreeOrder: computed('order', function() {
    const amount = this.get('order.amount');
    return amount === null || isEqual(amount, '0');
  })
});

The template for the component contains the event logo aligned to the left in the card. We show the event details such as the name, location and start date on the right. Below the event details we show the order details such as the order amount, currency, the identifier and the date and time on which the order was completed. Below is the full code for reference:

<div class="event wide ui grid row">
  {{#unless device.isMobile}}
    <div class="ui card three wide computer six wide tablet column">
      <a class="image" href="#">
        {{widgets/safe-image src=(if order.event.originalImageUrl order.event.originalImageUrl order.event.originalImageUrl)}}
      </a>
    </div>
  {{/unless}}
  <div class="ui card thirteen wide computer ten wide tablet sixteen wide mobile column">
    <a class="main content" href="#">
      {{#smart-overflow class='header'}}
        {{order.event.name}}
      {{/smart-overflow}}
      <div class="meta">
        <span class="date">
          {{moment-format order.event.startsAt 'ddd, DD MMMM YYYY, h:mm A'}}
        </span>
      </div>
      {{#smart-overflow class='description'}}
        {{order.event.shortLocationName}}
      {{/smart-overflow}}
    </a>
    <div class="extra content small text">
      <span>
        <span>
          {{#if isFreeOrder}}
            {{t 'Free'}}
          {{else}}
            {{order.event.paymentCurrency}}{{order.amount}}
          {{/if}}
          {{t 'order'}}
        </span>
        <span>#{{order.identifier}}</span>
        <span>{{t 'on'}} {{moment-format order.completedAt 'MMMM DD, YYYY h:mm A'}}</span>
      </span>
    </div>
  </div>
</div>

References

Continue ReadingMy Tickets in Open Event Frontend