List SUSI.AI Devices in Admin Panel

In this blog I’ll be explaining about the Devices Tab in SUSI.AI Admin Panel. Admins can now view the connected devices of the users with view, edit and delete actions. Also the admins can directly view the location of the device on the map by clicking on the device location of that user.

Implementation

List Devices

Admin device Tab

Devices tab displays device name, macId, room, email Id, date added, last active, last login IP and location of the device. loadDevices function is called on componentDidMount which calls the fetchDevices API which fetches the list of devices from /aaa/getDeviceList.json endpoint. List of all devices is stored in devices array. Each device in the array is an object with the above properties. Clicking on the device location opens a popup displaying the device location on the map.

loadDevices = () => {
   fetchDevices()
     .then(payload => {
       const { devices } = payload;
       let devicesArray = [];
       devices.forEach(device => {
         const email = device.name;
         const devices = device.devices;
         const macIdArray = Object.keys(devices);
         const lastLoginIP =
           device.lastLoginIP !== undefined ? device.lastLoginIP : '-';
         const lastActive =
           device.lastActive !== undefined
             ? new Date(device.lastActive).toDateString()
             : '-';
         macIdArray.forEach(macId => {
           const device = devices[macId];
           let deviceName = device.name !== undefined ? device.name : '-';
           deviceName =
             deviceName.length > 20
               ? deviceName.substr(0, 20) + '...'
               : deviceName;
           let location = 'Location not given';
           if (device.geolocation) {
             location = (
               
                 {device.geolocation.latitude},{device.geolocation.longitude}
               
             );
           }
           const dateAdded =
             device.deviceAddTime !== undefined
               ? new Date(device.deviceAddTime).toDateString()
               : '-';
 
           const deviceObj = {
             deviceName,
             macId,
             email,
             room: device.room,
             location,
             latitude:
               device.geolocation !== undefined
                 ? device.geolocation.latitude
                 : '-',
             longitude:
               device.geolocation !== undefined
                 ? device.geolocation.longitude
                 : '-',
             dateAdded,
             lastActive,
             lastLoginIP,
           };
           devicesArray.push(deviceObj);
         });
       });
       this.setState({
         loadingDevices: false,
         devices: devicesArray,
       });
     })
     .catch(error => {
       console.log(error);
     });
 };

View Device

User Device Page

View action redirects to users /mydevices?email<email>&macid=<macid>. This allows admin to have full control of the My devices section of the user. Admin can change device details and delete device. Also admin can see all the devices of the user from the ALL tab. To edit a device click on edit icon in the table, update the details and click on check icon. To delete a device click on the delete device which then asks for confirmation of device name and on confirmation deletes the device.

Edit Device

Edit Device Dialog

Edit actions opens up a dialog modal which allows the admin to update the device name and room. Clicking on the edit button calls the modifyUserDevices API which takes in email Id, macId, device name and room name as parameters. This calls the API endpoint /aaa/modifyUserDevices.json.

 handleChange = event => {
   this.setState({ [event.target.name]: event.target.value });
 };

 render() {
   const { macId, email, handleConfirm, handleClose } = this.props;
   const { room, deviceName } = this.state;
   return (
     <React.Fragment>
       <DialogTitle>Edit Device Details for {macId}</DialogTitle>
       <DialogContent>
         <OutlinedTextField
           value={deviceName}
           label="Device Name"
           name="deviceName"
           variant="outlined"
           onChange={this.handleChange}
           style={{ marginRight: '20px' }}
         />
         <OutlinedTextField
           value={room}
           label="Room"
           name="room"
           variant="outlined"
           onChange={this.handleChange}
         />
       </DialogContent>
       <DialogActions>
         <Button
           key={1}
           color="primary"
           onClick={() => handleConfirm(email, macId, room, deviceName)}>
           Change
         </Button>
         <Button key={2} color="primary" onClick={handleClose}>
           Cancel
         </Button>
       </DialogActions>
     </React.Fragment>
   );
 }

Delete Device

Delete Device Dialog

Delete action opens up a confirm delete dialog modal. To delete a device enter the device name and click on delete. This calls the confirmDelete function which calls the removeUserDevice API which takes in email Id and macId as parameters. This API hits the endpoint /aaa/removeUserDevices.json.

confirmDelete = () => {
   const { actions } = this.props;
   const { macId, email } = this.state;
   removeUserDevice({ macId, email })
     .then(payload => {
       actions.openSnackBar({
         snackBarMessage: payload.message,
         snackBarDuration: 2000,
       });
       actions.closeModal();
       this.setState({
         loadingDevices: true,
       });
       this.loadDevices();
     })
     .catch(error => {
       actions.openSnackBar({
         snackBarMessage: `Unable to delete device with macID ${macId}. Please try again.`,
         snackBarDuration: 2000,
       });
     });
 };

To conclude, admin can now view all the connected SUSI.AI devices along with the user details and location. They can also access users My Devices tab in Dashboard and update and delete devices.

Resources

Continue ReadingList SUSI.AI Devices in Admin Panel

CRUD operations on Config Keys in Admin Panel of SUSI.AI

SUSI.AI Admin Panel now allows the Admin to create, read, update and delete config keys present in system settings. Config keys are API keys which are used to link the application to third party services like Google Maps, Google ReCaptcha, Google Analytics, Matomo, etc. The API key is a unique identifier that is used to authenticate requests associated with the project for usage and billing purposes.

CRUD Operations

Create Config Key

To create a config key click on “Add Config Key” Button, a dialog opens up which has two field Key Name and Key Value. this.props.actions.openModal opens up the shared Dialog Modal. On clicking on “Create”, the createApiKey is called which takes in the two parameters.

handleCreate = () => {
   this.props.actions.openModal({
     modalType: 'createSystemSettings',
     type: 'Create',
     handleConfirm: this.confirmUpdate,
     keyName: this.state.keyName,
     keyValue: this.state.keyValue,
     handleClose: this.props.actions.closeModal,
   });
 };
 handleSave = () => {
   const { keyName, keyValue } = this.state;
   const { handleConfirm } = this.props;
   createApiKey({ keyName, keyValue })
     .then(() => handleConfirm())
     .catch(error => {
       console.log(error);
     });
 }; 

Read Config Key

API endpoint fetchApiKeys is called on componentDidMount and when Config Key is created, updated or deleted.

 fetchApiKeys = () => {
   fetchApiKeys()
     .then(payload => {
       let apiKeys = [];
       let i = 1;
       let keys = Object.keys(payload.keys);
       keys.forEach(j => {
         const apiKey = {
           serialNum: i,
           keyName: j,
           value: payload.keys[j],
         };
         ++i;
         apiKeys.push(apiKey);
       });
       this.setState({
         apiKeys: apiKeys,
         loading: false,
       });
     })
     .catch(error => {
       console.log(error);
     });
 };

Update Config Key

To Update a config key click on edit from the actions column, Update Config Key dialog opens up which allows you to edit the key value. On clicking on update, the createApiKey API is called.

 handleUpdate = row => {
   this.props.actions.openModal({
     modalType: 'updateSystemSettings',
     type: 'Update',
     keyName: row.keyName,
     keyValue: row.value,
     handleConfirm: this.confirmUpdate,
     handleClose: this.props.actions.closeModal,
   });
 };

Delete Config Key

To delete a config key click on delete from actions column, delete config key confirmation dialog opens up. On clicking on Delete, the deleteApiKey is called which takes in key name as parameter.

 handleDelete = row => {
   this.setState({ keyName: row.keyName });
   this.props.actions.openModal({
     modalType: 'deleteSystemSettings',
     keyName: row.keyName,
     handleConfirm: this.confirmDelete,
     handleClose: this.props.actions.closeModal,
   });
 };
 confirmDelete = () => {
   const { keyName } = this.state;
   deleteApiKey({ keyName })
     .then(this.fetchApiKeys)
     .catch(error => {
       console.log(error);
     });
   this.props.actions.closeModal();
 };

In conclusion, CRUD operations of Config Keys help admins to manage third party services. With these operations the admin can manage the API keys of various services without having to look for them in the backend.

Resources

Continue ReadingCRUD operations on Config Keys in Admin Panel of SUSI.AI

Dialog Component in SUSI.AI

Dialog Component in SUSI.AI is rendered in App.js to remove code redundancy. Redux is integrated in the Dialog component which allows us to open/close the dialog from any component by altering the modal states. This implementation allows us to get rid of the need of having dialog component in different components.

Redux Code

There are two actions and reducers which control the dialog component. Default state of isModalOpen is false and modalType is an empty string. To open a dialog modal the action openModal is dispatched, which sets isModalOpen to true and the modalType. To close a dialog modal the action closeModal is dispatched, which sets isModalOpen to default state i.e. false.

import { handleActions } from 'redux-actions';
import actionTypes from '../actionTypes';

const defaultState = {
 modalProps: {
   isModalOpen: false,
   modalType: '',
 },
};

export default handleActions(
 {
   [actionTypes.UI_OPEN_MODAL](state, { payload }) {
     return {
       ...state,
       modalProps: {
         isModalOpen: true,
         ...payload,
       },
     };
   },
   [actionTypes.UI_CLOSE_MODAL](state) {
     return {
       ...state,
       modalProps: defaultState.modalProps,
     };
   },
 }
 defaultState,
);

Shared Dialog Component

Dialog Modal can be opened from any component by dispatching an action. 

To open a Dialog Modal: this.props.actions.openModal({modalType: [modal name]});

To close a Dialog Modal: this.props.actions.closeModal();

Shared Dialog Component has a DialogData object which contains objects with two main properties : Dialog component and Dialog size. Other props can also be passed along with these two properties such as fullScreen. Dialog Content of different Dialogs are present in their respective folders. Each Dialog Content has a Title, Content and Actions.Different Dialog types present are:

  1. Confirm Delete with Input: This dialog modal is used when a user deletes account, device and skill. 
  2. Confirm Dialog: This dialog modal is used where confirmation is required from the user/admin such as on changing skill status, on password reset,etc.
  3. Share Dialog: This dialog modal opens up when the share icon is clicked in the chat.
  4. Standard Action Dialog: This dialog modal opens up on restore skill, delete feedback, system settings and bot.
  5. Tour Dialog: This dialog modal opens up SUSI.AI tour.

To add a new Dialog to DialogSection, the steps are:

  1. Import the Dialog Content Component
  2. Add the Dialog Component to DialogData object in the following manner:
const DialogData = {
[dialog componet name]: { Component : [imported dialog component name], size : [size of the Dialog Component]},
}

Code (Reduced)

const DialogData = {
  login: { Component: Login, size: 'sm' },
}
const DialogSection = props => {
 const {
   actions,
   modalProps: { isModalOpen, modalType, ...otherProps },
   visited,
 } = props;

 const getDialog = () => {
   if (isModalOpen) {
     return DialogData[modalType];
   }
   return DialogData.noComponent;
 };

 const { size, Component, fullScreen = false } = getDialog();

return (
   <Dialog
      maxWidth={size}
      fullWidth={true}
      open={isModalOpen || !visited}
      onClose={isModalOpen ? actions.closeModal : actions.setVisited}
      fullScreen={fullScreen}
    >
     <DialogContainer>
       {Component ? <Component {...otherProps} /> : null}
     </DialogContainer>
  </Dialog>
)
};

In conclusion, having a shared dialog component reduces redundant code and allows to have a similar Dialog UI across the repo. Also having one component managing all the dialogs removes the possibility of  two dialogs being fired up at once.

Resources

Continue ReadingDialog Component in SUSI.AI

Different views for SUSI Skill Creator

SUSI Skill Creator is a service provided to easily create skills for SUSI. The skill can be written in the form of code. The coding syntax is described in SUSI skill tutorial. The problem with this is that we can’t expect everyone to be great coders and be able to understand the document and easily create a skill. Hence, we needed some robust alternatives for people who don’t want to write the code. This is where UI View and Tree View comes in.

What is UI View?

UI View or User Interface View shows the skill in form of chat bubbles. This is useful for demonstrating how the actual chat will look like.

Hello
Hi. I’m SUSI.

The following code will be shown as this in conversation view:

What is Tree View?

Tree view shows the conversation in form of a tree with its branching representing the chats.

Hello|Hi
Hi. I'm SUSI.|Hey there!

The following code will be shown as this in tree view:

How are the views synchronised?

We’re basically not making any API calls or performing any major function in any of the views. The components of different views are solely for their own function. All the API calls are made in a parent component named SkillCreator.

The main reason for creating a parent component is that we need the category, language, name and commit message options to appear on all the three views. Hence, it only makes sense to have a parent component for them and different components for the views.

The default code (that appears in code editor by default) is in a state in SkillCreator. We pass this state to the code view component as a props.

<CodeView
  skillCode={this.state.code}
  sendInfoToProps={this.sendInfoToProps}
/>

Now, we need to get any change made in the code in CodeView and change the code state of SkillCreator accordingly. For that, we have a function named sendInfoToProps which is passed as props to CodeView and is then called from CodeView whenever we make some change. This will be more clear after having a look at the function sendInfoToProps:

sendInfoToProps = value => {
 if (value) {
  this.setState(
   {
     code: value.code ? value.code : this.state.code,
     expertValue: value.expertValue
       ? value.expertValue
       : this.state.expertValue,
     groupValue: value.groupValue
       ? value.groupValue
       : this.state.groupValue,
     languageValue: value.languageValue
       ? value.languageValue
       : this.state.groupValue,
   imagUrl: value.imageUrl ? value.imageUrl : this.state.imageUrl,
   },
   () => this.generateSkillData(),
  );
 }
};

You can see that we update the code in SkillCreator and other states required. Also, you can notice the we’re calling generateSkillData function here. This is done to convert the code to skill data which is sent to Conversation view and Tree view as props.
We’re calling generateSkillData as a callback function because setState is an asynchronous function.
This will demonstrate how this is passed to Conversation view:

<ConversationView
  skillData={this.state.skillData}
  handleDeleteNode={this.handleDeleteNode}
/>

Same is the case for this handleDeleteNode function. We have an option in both Conversation view and Tree view to allow the user to delete a conversation (originally written as skill in code view). On delete, handleDeleteNode is called from the props and the conversation is deleted from the code which is then send to all the views (as props) and the views are updated as well.

References:

Continue ReadingDifferent views for SUSI Skill Creator

Code view and UI View in design tab of bot wizard

Design tab is for designing your SUSI AI chatbot. You can change the background colour, add an image as background color, change colour of user text box, bot text box etc. On SUSI AI bot wizard design tab, we have two views. These views show the same thing but in a different way for different people. The code view is mainly for the developers while the UI (User Interface) view is for a non-developer user because we can not expect them to know how to code even if it is easy.

Code View

The current code view of design tab looks like this:

::bodyBackground #ffffff
::bodyBackgroundImage
::userMessageBoxBackground #0077e5
::userMessageTextColor #ffffff
::botMessageBoxBackground #f8f8f8
::botMessageTextColor #455a64
::botIconColor #000000
::botIconImage

For changing colours, you simply have to change the hex codes and for adding images as background, you need to add the url of image. You can also upload an image but that function is provided in the UI View.

UI View

The current UI view of design tab looks like this:

Here, we have colour picker for user to choose a colour for various components of the chatbot. There’s also a small box with the same colour as specified in hex code for previewing.

Switching and Synchronising between Code view and UI view

We have different components for code view and UI view. In order to synchronise both the views, we need to have the same code in state of both components. To do that, we add the code in state of the parent component i.e. design.js and then pass it to code view and UI view as states.

The following code snippet will demonstrate this:

<CodeView
    design={{
        sendInfoToProps: this.sendInfoToProps,
        updateSettings: this.updateSettings,
        code: this.state.code,
    }}
/>

You can see that we pass a whole object as props.
Using this approach, same code is passed to both the views. This solves the first problem. The next problem is that if we do a change in code view then it should happen in the code state of parent file as well. Same goes for UI view.
To do this, whenever we change the code in code view or UI view, we call a function in the parent file (design.js) which was passed as props and we pass the state of code view or UI view as arguments of this function. This function then updates the state accordingly. The name of the function is sendInfoToProps and it is as follows:

sendInfoToProps = values => {
    this.setState({ ...values });
};

You can see that it simply updates the state value as per the parameters passed to it.
So this is how synchronisation works between different views.

References:

Continue ReadingCode view and UI View in design tab of bot wizard

Modifying SUSI Skills using SUSI Skill CMS

SUSI Skill CMS is a complete solution right from creating a skill to modifying the skill. The skills in SUSI are well synced with the remote repository and can be completely modified using the Edit Skill feature of SUSI Skill CMS. Here’s how to Modify a Skill.

  1. Sign Up/Login to the website using your credentials in skills.susi.ai
  2. Choose the SKill which you want to edit and click on the pencil icon.
  3. The following screen allows editing the skill. One can change the Group, Language, Skill Name, Image and the content as well.
  4. After making the changes the commit message can be added to Save the changes.

To achieve the above steps we require the following API Endpoints of the SUSI Server.

  1. http://api.susi.ai/cms/getSkillMetadata.json – This gives us the meta data which populates the various Skill Content, Image, Author etc.
  2. http://api.susi.ai/cms/getAllLanguages.json – This gives us all the languages of a Skill Group.
  3. http://api.susi.ai/cms/getGroups.json – This gives us all the list of Skill Groups whether Knowledge, Entertainment, Smalltalk etc.

Now since we have all the APIs in place we make the following AJAX calls to update the Skill Process.

  1. Since we are detecting changes in all the fields (Group Value, Skill Name, Language Value, Image Value, Commit Message, Content changes and the format of the content), the AJAX call can only be sent when there is a change in the PR and there is no null or undefined value in them. For that, we make various form validations. They are as follows.
    1. We first detect whether the User is in a logged in state.
if (!cookies.get('loggedIn')) {
            notification.open({
                message: 'Not logged In',
                description: 'Please login and then try to create/edit a skill',
                icon: <Icon type="close-circle" style={{ color: '#f44336' }} />,
            });
        }
  1. We check whether the image uploaded matches the format of the Skill image to be stored which is ::image images/imageName.png
if (!new RegExp(/images\/\w+\.\w+/g).test(this.state.imageUrl)) {
            notification.open({
                message: 'Error Processing your Request',
                description: 'image must be in format of images/imageName.jpg',
                icon: <Icon type="close-circle" style={{ color: '#f44336' }} />,
            });
        }
  1. We check if the commit message is not null and notify the user if he forgot to add a message.
if (this.state.commitMessage === null) {
            notification.open({
                message: 'Please make some changes to save the Skill',
                icon: <Icon type="close-circle" style={{ color: '#f44336' }} />,
            });
        }
  1. We also check whether the old values of the skill are completely similar to the new ones, in this case, we do not send the request.
if (toldValues===newValues {
            notification.open({
                message: 'Please make some changes to save the Skill',
                icon: <Icon type="close-circle" style={{ color: '#f44336' }} />,
            });
        }

To check out the complete code, go to this link.

  1. Next, if the above validations are successful, we send a POST request to the server and show the notification to the user accordingly, whether the changes to the Skill Data have been updated or not. Here’s the AJAX call snippet.
// create a form object
let form = new FormData();       
/* Append the following fields from the Skill Component:- OldModel, OldLanguage, OldSkill, NewModel, NewGroup, NewLanguage, NewSkill, changelog, content, imageChanged, old_image_name, new_image_name, image_name_changed, access_token */  
if (image_name_changed) {
            file = this.state.file;
            // append file to image
        }

        let settings = {
            "async": true,
            "crossDomain": true,
            "url": "http://api.susi.ai/cms/modifySkill.json",
            "method": "POST",
            "processData": false,
            "contentType": false,
            "mimeType": "multipart/form-data",
            "data": form
        };
        $.ajax(settings)..done(function (response) {
         //show success
        }.
        .fail(function(response){
         // show failure
        }
  1. To verify all this we head to the commits section of the SUSI Skill Data repo and see the changes we made. The changes can be seen here https://github.com/fossasia/susi_skill_data/commits/master 

Resources

  1. AJAX POST Request – https://api.jquery.com/jquery.post/ 
  2. Material UI – http://material-ui.com 
  3. Notifications – http://www.material-ui.com/#/components/notification 
Continue ReadingModifying SUSI Skills using SUSI Skill CMS

Reset SUSI.AI User Password & Parameter extraction from Link

In this blog I will discuss how does Accounts handle the incoming request to reset the password. If a user forgets his/her password, They can use forgot password button on http://accounts.susi.ai and It’s implementation is quite straightforward. As soon as a user enter his/her e-mail id and hits RESET button, an ajax call is made which first checks if the user email id is registered with SUSI.AI or not. If not, a failure message is thrown to user. But if the user is found to be registered in the database, An email is sent to him/her which contains a 30 characters long token. On the server token is hashed against the user’s email id and a validity of 7 days is set to it.
Let us have a look at the Reset Password link one receives.

http://accounts.susi.ai/?token={30 characters long token}

On clicking this link, what it does is that user is redirected to http://accounts.susi.ai with token as a request parameter. At the client side, A search is made which evaluates whether the URL has a token parameter or not. This was the overview. Since, http://accounts.susi.ai is based on ReactJS framework, it is not easy alike the native php functionality, but much more logical and systematic. Let us now take a closer look at how this parameter is searched for, token extracted and validated.

As you can see http://accounts.susi.ai and http://accounts.susi.ai/?token={token}, Both redirect the user to the same URL. So the first task that needs to be accomplished is check if a parameter is passed in the URL or not.
First import addUrlProps and UrlQueryParamTypes from react-url-query package and PropTypes from prop-types package. These will be required in further steps.
Have a look at the code and then we will understand it’s working.

const urlPropsQueryConfig = {
  token: { type: UrlQueryParamTypes.string },
};

class Login extends Component {
	static propTypes = {
    // URL props are automatically decoded and passed in based on the config
    token: PropTypes.string,
    // change handlers are automatically generated when given a config.
    // By default they update that single query parameter and maintain existing
    // values in the other parameters.
    onChangeToken: PropTypes.func,
  }

  static defaultProps = {
    token: "null",
  }

Above in the first step, we have defined by what parameter should the Reset Password triggered. It means, if and only if the incoming parameter in the URL is token, we move to next step, otherwise normal http://accounts.susi.ai page should be rendered. Also we have defined the data type of the token parameter to be UrlQueryParamTypes.string.
PropTypes are attributes in ReactJS similar to tags in HTML. So again, we have defined the data type. onChangeToken is a custom attribute which is fired whenever token parameter is modified. To declare default values, we have used defaultProps function. If token is not passed in the URL, by default it makes it null. This is still not the last step. Till now we have not yet checked if token is there or not. This is done in the componentDidMount function. componentDidMount is a function which gets called before the client renders the page. In this function, we will check that if the value of token is not equal to null, then a value must have been passed to it. If the value of token is not equal to null, it extracts the token from the URL and redirects user to reset password application. Below is the code snippet for better understanding.

componentDidMount() {
		const {
      token
    } = this.props;
		console.log(token)
		if(token !== "null") {
			this.props.history.push('/resetpass/?token='+token);
		}
		if(cookies.get('loggedIn')) {
			this.props.history.push('/userhome', { showLogin: false });
		}
	}

With this the first step of the process has been achieved. Again in the second step, we extract the token from the URL in the similar above fashion followed by an ajax request to the server which will validate the token and sends response to client accordingly {token might be invalid or expired}. Success is encoded in a JSON object and sent to client. Error is thrown as an error only and client shows an error of “invalid token”. If it is a success, it sends email id of the user against which the token was hashed and the client displays it on the page. Now user has to enter new password and confirm it. Client also tests whether both the fields have same values or not. If they are same, a simple ajax call is made to server with new password and the email id and token. If no error is caused in between (like connection timeout, server might be down etc), client is sent a JSON response again with “accepted” flag = true and message as “Your password has been changed!”.

Additional Resources:

(npmjs – official documentation of create-react-apps)

(Fortnox developer’s blog post)

(Dave Ceddia’s blog post for new ReactJS developers)

Continue ReadingReset SUSI.AI User Password & Parameter extraction from Link

Implementing Login Functionality in SUSI Web Chat

SUSI Web Chat is fully equipped with all the accounting features which are being provided by the SUSI.AI API. This blog discloses all the API features one needs to know to embed the Login functionality in SUSI Web Chat.

  1. To embed the Login feature, first we create a form using material-ui.com components with the followng fields
    1. Email
    2. Password
    3. Note: We can also chose a Custom Server while logging in, here I have used the Standard Server ie. http://api.susi.ai to make the user Login

The form can be made with the help of the following fields

  • TextField for Email, props to be passed
    • Name – email
    • Value – this.state.email which gets the value of the current email
    • floatingLabelText is Email,
    • errorText is the message which we want to show when the email does not match the regex or its empty.

Code Snippet –

<TextField name="email" value={this.state.email} onChange={this.handleChange} errorText={this.emailErrorMessage}    floatingLabelText="Email" />
  • PasswordField for Password
    • Name – password
    • Value – this.state.password which gets the value of the current email
    • floatingLabelText is Password,
    • errorText is the message which we want to show when the password is not filled.

Code Snippet-

<PasswordField name='password' value={this.state.password} onChange={this.handleChange} errorText={this.passwordErrorMessage}   floatingLabelText='Password' />
  • The next elements are RadioButton groups taken from material-ui.com. This ensures the user signs into a standard server or even to a custom server. This is not compulsory as of now.
  • And lastly we need a submit button, which is disabled until all the fields are filled.

Code Snippet –

<RaisedButton label="Login" type="submit" labelColor="#fff" disable={!this.state.validForm} />

For the full form, check out this file at Login.react.js

  1. A Sample UI could be as shown in the image
  2. Next after creating the Login Screen, we make the onSubmit prop which is to be hooked up with another function called handleSubmit. An example code snippet from Login.react.js
 handleSubmit = (e) => {
        e.preventDefault();
        // Get the trimmed values from the fields
        var email = this.state.email.trim();
        var password = this.state.password.trim();
        // Set the default server to login
        let BASE_URL = defaults.Server;
            // handle all the details of the chosen server
        let serverUrl = this.state.serverUrl;
        if(serverUrl.slice(-1) === '/'){
            serverUrl = serverUrl.slice(0,-1);
        }
        if(serverUrl !== ''){
            BASE_URL = serverUrl;
        }
// if email and password is filled return true
        if (!email || !password) { return this.state.isFilled; }
// Check the regex of email
        let validEmail = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(email); 
// Pass the parameters to the loginEndPoint
        let loginEndPoint =
            BASE_URL+'/aaa/login.json?type=access-token&login=' +
            this.state.email + '&password=' + this.state.password;
        // If email and password is filled and valid call AJAX
        if (email && validEmail) {
            // AJAX Calls
        }
    }
    1. Then we make the Ajax Calls and store the created token from hitting the URL at http://api.susi.ai/aaa/login.json?type=access-token&login=EMAIL&password=PASSWORD. We store the cookie in browser and generate a session for the user using a package ‘universal-cookies’.
$.ajax({
    url: loginEndPoint,
    dataType: 'jsonp',
    jsonpCallback: 'p',
    jsonp: 'callback',
    crossDomain: true,
    success: function (response) {
        cookies.set('serverUrl', BASE_URL, { path: '/' });
        let accessToken = response.access_token;
        let state = this.state;// Adding the current State
        let time = response.valid_seconds; // Get the valid time of the cookie
        state.isFilled = true; // Set isFilled to true
        state.accessToken = accessToken; // Get the token
        state.success = true; // Set Success to true
        state.msg = response.message; // Get the server message
        state.time = time; // Get the time in the state
        this.setState(state); // Set the  state with the values
/* Pass the token to the binding function handleOnSubmit passing the arguments - token and the valid time */
        this.handleOnSubmit(accessToken, time);
    }.bind(this),
    error: function (errorThrown) {
        let msg = 'Login Failed. Try Again';
        let state = this.state;
        state.msg = msg;
        this.setState(state);
    }.bind(this)
});

 

    1. We then fire up the handleOnSubmit(accessToken, time) which saves the token for the given expiration time from the server.

Here’s the sample code

handleOnSubmit = (loggedIn, time) => {
        let state = this.state;
        if (state.success) {
            cookies.set('loggedIn', loggedIn, { path: '/', maxAge: time }); // set the cookie in the browser to maintain the loggedIn state
            this.props.history.push('/', { showLogin: false });
            window.location.reload();// reload after the loggedIn cookie creation
        }
        else {
            this.setState({
                error: true,
                accessToken: '',
                success: false
            });
        }
    }
  1. We then check the access token and redirect him based on his login state. This is handled in MessageSection.react.js
import Cookies from 'universal-cookie';
const cookies = new Cookies();
if (cookies.get('loggedIn')) {
    //Show all functionalities of loggedIn state
}
else {
//Redirect user to login page
}

 

To have a look at the full project, visit https://github.com/fossasia/chat.susi.ai and feel free to contribute. To test the project visit http://chat.susi.ai

Resources

Continue ReadingImplementing Login Functionality in SUSI Web Chat