MadCapIdea

MADCAP IDEA PART 6 : STATIC SCREEN DESIGN

Last Time

 

Last time we looked at setting up the react router, which used some dummy screens. This time we will actually be implementing the static screen designs. The screens will be pretty much what the end application will look like, with the exception that they are not wired up to the Play framework backend, and for now might use dummy data to show what they would look like for real, when the entire app is completed and working

 

PreAmble

Just as a reminder this is part of my ongoing set of posts which I talk about here :

https://sachabarbs.wordpress.com/2017/05/01/madcap-idea/, where we will be building up to a point where we have a full app using lots of different stuff, such as these

 

  • WebPack
  • React.js
  • React Router
  • TypeScript
  • Babel.js
  • Akka
  • Scala
  • Play (Scala Http Stack)
  • MySql
  • SBT
  • Kafka
  • Kafka Streams

 

So I think the best way to cover this post is to do it screen by screen, so lets start at the beginning and end at the end.

 

The Screens

In this section we will simply go through each of the screens and examine the code and the final rendering and discuss and important code that might be contained within the screen being discussed

 

Login

This is the entire code for the login screen

import * as React from "react";
import * as ReactDOM from "react-dom";

import 'bootstrap/dist/css/bootstrap.css';
import
{
    Well,
    Grid,
    Row,
    Col,
    ButtonInput
} from "react-bootstrap";

import { Form, ValidatedInput } from 'react-bootstrap-validation';

import revalidator from 'revalidator';


let schema = {
    properties: {
        email: {
            type: 'string',
            maxLength: 255,
            format: 'email',
            required: true,
            allowEmpty: false
        },
        password: {
            type: 'string',
            minLength: 8,
            maxLength: 60,
            required: true,
            allowEmpty: false
        }
    }
};



export class Login extends React.Component<undefined, undefined> {
    render() {
        return (
            <Well className="outer-well">
                <Form
                    // Supply callbacks to both valid and invalid
                    // submit attempts
                    validateAll={this._validateForm}
                    onInvalidSubmit={this._handleInvalidSubmit}
                    onValidSubmit={this._handleValidSubmit}>
                    <Grid>
                        <Row className="show-grid">
                            <Col xs={10} md={6}>
                                <h4>ENTER YOUR LOGIN DETAILS</h4>
                            </Col>
                        </Row>
                        <Row className="show-grid">
                            <Col xs={10} md={6}>
                                <ValidatedInput type='text'
                                    label='Email'
                                    name='email'
                                    errorHelp='Email address is invalid'/>

                            </Col>
                        </Row>
                        <Row className="show-grid">
                            <Col xs={10} md={6}>
                                <ValidatedInput type='password'
                                    name='password'
                                    label='Password'
                                    errorHelp='Password is invalid'/>

                            </Col>
                        </Row>
                        <Row className="show-grid">
                            <Col xs={10} md={6}>
                                <ButtonInput
                                    id="loginBtn"
                                    type='submit'
                                    bsSize='small'
                                    bsStyle='primary'
                                    value='Register'>Login</ButtonInput>
                            </Col>
                        </Row>
                    </Grid>
                </Form>
            </Well>
        )
    }


    _validateForm = (values) => {
        let res = revalidator.validate(values, schema);

        // If the values passed validation, we return true
        if (res.valid) {
            return true;
        }

        // Otherwise we should return an object containing errors
        // e.g. { email: true, password: true }
        return res.errors.reduce((errors, error) => {
            // Set each property to either true or
            // a string error description
            errors[error.property] = true;

            return errors;
        }, {});
    }

    _handleInvalidSubmit = (errors, values) => {
        // Errors is an array containing input names
        // that failed to validate
        alert("Form has errors and may not be submitted");
    }

    _handleValidSubmit = (values) => {
        // Values is an object containing all values
        // from the inputs
        console.log("Form may be submitted");
        console.log(values);
    }
}

Where this will look like this in a browser

image

 

There are a couple of things to talk about on this screen.

 

Firstly there is the use of React-bootstrap components, which are all imported at the top of the file like this

 

import 'bootstrap/dist/css/bootstrap.css';
import
{
    Well,
    Grid,
    Row,
    Col,
    ButtonInput
} from "react-bootstrap";

 

This allows us to import the actual React-bootstrap components and the CSS for bootstrap. React-bootstrap doesn’t really mind which CSS it uses, as long as it is a version of regular bootstrap CSS. I am using bootstrap v3.0 CSS here. React-bootstrap is more about the actual markup, and doing things in a more React like manner, to reduce some of the regular Bootstrap markup, which is possible thanks to using React.

 

The other important part about this screen is that it does client side validation. This is done using this library : react-bootstrap-validation. There are a few React validation libraries out there, but I like the simplicity and elegance of this one a lot. You really only have to do a couple of thing (as can be seen in full in the code above).

 

You need to import the library

 

import { Form, ValidatedInput } from 'react-bootstrap-validation';

import revalidator from 'revalidator';

 

Then you need a schema describing your form that you are trying to validate

let schema = {
    properties: {
        email: {
            type: 'string',
            maxLength: 255,
            format: 'email',
            required: true,
            allowEmpty: false
        },
        password: {
            type: 'string',
            minLength: 8,
            maxLength: 60,
            required: true,
            allowEmpty: false
        }
    }
};

 

And then you need to create the actual form (here is a stripped down one that matches the schema above)

 

<Form>
	<ValidatedInput type='text'
		label='Email'
		name='email'
		errorHelp='Email address is invalid'/>
	<ValidatedInput type='password'
		name='password'
		label='Password'
		errorHelp='Password is invalid'/>
</Form>

 

And we must also supply some validation callback hooks for the form, which are done like this

 

<Form
    // Supply callbacks to both valid and invalid
    // submit attempts
    validateAll={this._validateForm}
    onInvalidSubmit={this._handleInvalidSubmit}
    onValidSubmit={this._handleValidSubmit}>

</Form>

 

Where the code for these callbacks looks like this

 

export class Login extends React.Component<undefined, undefined> {
    render() {
        return (
          .....
        )
    }


    _validateForm = (values) => {
        let res = revalidator.validate(values, schema);

        // If the values passed validation, we return true
        if (res.valid) {
            return true;
        }

        // Otherwise we should return an object containing errors
        // e.g. { email: true, password: true }
        return res.errors.reduce((errors, error) => {
            // Set each property to either true or
            // a string error description
            errors[error.property] = true;

            return errors;
        }, {});
    }

    _handleInvalidSubmit = (errors, values) => {
        // Errors is an array containing input names
        // that failed to validate
        alert("Form has errors and may not be submitted");
    }

    _handleValidSubmit = (values) => {
        // Values is an object containing all values
        // from the inputs
        console.log("Form may be submitted");
        console.log(values);
    }
}

So that pretty much explains how the validation works, what does it look like when run? Well it looks like this when its not valid

 

image

 

And that is pretty much the static design of the Login screen, lets move on

 

 

Register

The Register screen uses the same validation stuff as above, so I will not cover it again. The only thing of note in the Register screen is that it will either need to render a Passenger registration or a Driver registration depending on what option was chosen by the user.

 

To do this conditional rendering, I opted for a set of 2 simple buttons that modify the internal state of the main Register React component, and will then either display a PassengerRegistration component of a DriverRegistration component.

 

Here is the full code for the main Register React component

 

import * as React from "react";
import * as ReactDOM from "react-dom";

import 'bootstrap/dist/css/bootstrap.css';
import
{
    Well,
    Grid,
    Row,
    Col,
    ButtonInput,
    ButtonGroup,
    Button
} from "react-bootstrap";

import { PassengerRegistration } from "./PassengerRegistration";
import { DriverRegistration } from "./DriverRegistration";


export interface RegisterState {
    option: string;
}


export class Register extends React.Component<any, RegisterState> {

    constructor(props: any) {
        super(props);
        this.state = { option: "passenger" };
    }

    render() {
        return (
            <Well className="outer-well">
                <Grid>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <h4>PLEASE ENTER YOUR REGISTRATION DETAILS</h4>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <h6>Choose your registration type </h6>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ButtonGroup>
                                <Button bsSize='small' onClick={this._onOptionChange.bind(this, 'passenger') } active={this.state.option === 'passenger'}>Passenger</Button>
                                <Button bsSize='small' onClick={this._onOptionChange.bind(this, 'driver') } active={this.state.option === 'driver'}>Driver</Button>
                            </ButtonGroup>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            {this.state.option === 'passenger' ?
                                <div><PassengerRegistration/></div> :
                                <div><DriverRegistration/></div>
                            }
                        </Col>
                    </Row>
                </Grid>
            </Well>
        )
    }

    _onOptionChange(option) {
        this.setState({
            option: option
        });
    }

}

 

As you can see this code contains the state that is set to determine which type of sub registration component to actually render (see the Render() method above)

 

The sub components are as shown below (as stated above we already discussed validation, so I will not cover that again).

 

PassengerRegistration

Here is the full code for this sub component

import * as React from "react";
import * as ReactDOM from "react-dom";

import 'bootstrap/dist/css/bootstrap.css';
import
{
    Well,
    Grid,
    Row,
    Col,
    ButtonInput
} from "react-bootstrap";

import { Form, ValidatedInput } from 'react-bootstrap-validation';

import revalidator from 'revalidator';


let schema = {
    properties: {
        fullname: {
            type: 'string',
            minLength: 8,
            maxLength: 12,
            required: true,
            allowEmpty: false
        },
        email: {
            type: 'string',
            maxLength: 255,
            format: 'email',
            required: true,
            allowEmpty: false
        },
        password: {
            type: 'string',
            minLength: 8,
            maxLength: 60,
            required: true,
            allowEmpty: false
        }
    }
};



export class PassengerRegistration extends React.Component<undefined, undefined> {
    render() {
        return (
            <Form className="submittable-form-inner"
                // Supply callbacks to both valid and invalid
                // submit attempts
                validateAll={this._validateForm}
                onInvalidSubmit={this._handleInvalidSubmit}
                onValidSubmit={this._handleValidSubmit}>
                <Grid>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <h4>Passenger details</h4>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ValidatedInput type='text'
                                label='FullName'
                                name='fullname'
                                errorHelp='FullName is invalid'/>

                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ValidatedInput type='text'
                                label='Email'
                                name='email'
                                errorHelp='Email address is invalid'/>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ValidatedInput type='password'
                                label='Password'
                                name='password'
                                errorHelp='Password is invalid'/>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ButtonInput
                                id="registerBtn"
                                type='submit'
                                bsSize='small'
                                bsStyle='primary'
                                value='Register'>Register</ButtonInput>
                        </Col>
                    </Row>
                </Grid>
            </Form>
        )
    }

    _validateForm = (values) => {
        let res = revalidator.validate(values, schema);

        // If the values passed validation, we return true
        if (res.valid) {
            return true;
        }

        // Otherwise we should return an object containing errors
        // e.g. { email: true, password: true }
        return res.errors.reduce((errors, error) => {
            // Set each property to either true or
            // a string error description
            errors[error.property] = true;

            return errors;
        }, {});
    }

    _handleInvalidSubmit = (errors, values) => {
        // Errors is an array containing input names
        // that failed to validate
        alert("Form has errors and may not be submitted");
    }

    _handleValidSubmit = (values) => {
        // Values is an object containing all values
        // from the inputs
        console.log("Form may be submitted");
        console.log(values);
    }
}


 

DriverRegistration

Here is the full code for this sub component

import * as React from "react";
import * as ReactDOM from "react-dom";

import 'bootstrap/dist/css/bootstrap.css';
import
{
    Well,
    Grid,
    Row,
    Col,
    ButtonInput
} from "react-bootstrap";

import { Form, ValidatedInput } from 'react-bootstrap-validation';

import revalidator from 'revalidator';


let schema = {
    properties: {
        fullname: {
            type: 'string',
            minLength: 8,
            maxLength: 12,
            required: true,
            allowEmpty: false
        },
        email: {
            type: 'string',
            maxLength: 255,
            format: 'email',
            required: true,
            allowEmpty: false
        },
        password: {
            type: 'string',
            minLength: 8,
            maxLength: 60,
            required: true,
            allowEmpty: false
        },
        vehicleDescription: {
            type: 'string',
            minLength: 6,
            maxLength: 60,
            required: true,
            allowEmpty: false
        },
        vehicleRegistrationNumber: {
            type: 'string',
            minLength: 6,
            maxLength: 30,
            required: true,
            allowEmpty: false
        }
    }
};



export class DriverRegistration extends React.Component<undefined, undefined> {
    render() {
        return (
            <Form className="submittable-form-inner"
                // Supply callbacks to both valid and invalid
                // submit attempts
                validateAll={this._validateForm}
                onInvalidSubmit={this._handleInvalidSubmit}
                onValidSubmit={this._handleValidSubmit}>
                <Grid>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <h4>Driver details</h4>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ValidatedInput type='text'
                                label='FullName'
                                name='fullname'
                                errorHelp='FullName is invalid'/>

                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ValidatedInput type='text'
                                label='Email'
                                name='email'
                                errorHelp='Email address is invalid'/>

                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ValidatedInput type='password'
                                name='password'
                                label='Password'
                                errorHelp='Password is invalid'/>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <h4>Vehicle details</h4>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ValidatedInput type='text'
                                label='Vehicle Description'
                                name='vehicleDescription'
                                errorHelp='Vehicle description is invalid'/>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ValidatedInput type='text'
                                label='Vehicle Registration Number'
                                name='vehicleRegistrationNumber'
                                errorHelp='Vehicle registration number is invalid'/>
                        </Col>
                    </Row>

                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ButtonInput
                                id="registerBtn"
                                type='submit'
                                bsSize='small'
                                bsStyle='primary'
                                value='Register'>Register</ButtonInput>
                        </Col>
                    </Row>
                </Grid>
            </Form>
        )
    }


    _validateForm = (values) => {
        let res = revalidator.validate(values, schema);

        // If the values passed validation, we return true
        if (res.valid) {
            return true;
        }

        // Otherwise we should return an object containing errors
        // e.g. { email: true, password: true }
        return res.errors.reduce((errors, error) => {
            // Set each property to either true or
            // a string error description
            errors[error.property] = true;

            return errors;
        }, {});
    }

    _handleInvalidSubmit = (errors, values) => {
        // Errors is an array containing input names
        // that failed to validate
        alert("Form has errors and may not be submitted");
    }

    _handleValidSubmit = (values) => {
        // Values is an object containing all values
        // from the inputs
        console.log("Form may be submitted");
        console.log(values);
    }

}


And this is the end result, with the Passenger registration option selected.

 

image

 

 

And this is the driver registration

image

 

 

CreateJob

Ok so now things are getting slightly more exotic we need to use a Google map along with React. There are many good tutorials that walk you through this, which are great to read so you can see how the Google maps API gets wrapped (typically this is what is done) and you can also see how to use the React Ref attribute to grab the ACTUAL element from the real DOM to host the map. There are also many available react google maps components. I had a hunt around and tried out a few and settled on this one : https://github.com/tomchentw/react-google-maps where there are some great demos here : https://tomchentw.github.io/react-google-maps/

 

The job of the “CreateJob” screen for me was to allow the user to set their current position on a map, and issue the job out to drivers that may be logged in at that time.

 

Remember I will be doing all this work on my laptop. So that is my laptop pretending to be many drivers/users etc etc, so I cant really use the inbuilt GPS position, everyone would always be at the same place, ie where ever my laptop is at that moment.

 

Ok so now we know what the job of the “CreateJob” screen is lets have a look at some of the code.

 

For the React-Google-Maps we need this import, otherwise its just stuff you have seen before

 

import { withGoogleMap, GoogleMap, Marker, InfoBox } from "react-google-maps";

 

The next part of interest is how we create an actual map which might have a marker on the map with a custom icon. Thanks to the great work of the React-Google-Maps code, this is as simple as this

 

const CreateJobGoogleMap = withGoogleMap(props => (
    <GoogleMap
        ref={props.onMapLoad}
        defaultZoom={16}
        defaultCenter={{ lat: 50.8202949, lng: -0.1406958 }}
        onClick={props.onMapClick}>
        <Marker
            position={props.currentPosition}
            icon='/assets/images/passenger.png'>
        </Marker>
    </GoogleMap>
));

 

It can be seen there that the Marker is using the position that is passed down as props to it from the parent React component. Lets now have a look at the parent React component

 

export interface CreateJobState {
    currentPosition: any;
}

export class CreateJob extends React.Component<undefined, CreateJobState> {

    constructor(props: any) {
        super(props);
        this.state = {
            currentPosition: { lat: 50.8202949, lng: -0.1406958 }
          };
    }


    render() {
        return (
            <Well className="outer-well">
                <Grid>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <h4>SET YOUR CURRENT LOCATION</h4>
                            <h6>Click the map to set your current location</h6>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <CreateJobGoogleMap
                                containerElement={
                                    <div style={{
                                        position: 'relative',
                                        top: 0,
                                        left: 0,
                                        right: 0,
                                        bottom: 0,
                                        width: 600,
                                        height: 600,
                                        justifyContent: 'flex-end',
                                        alignItems: 'center',
                                        marginTop: 20,
                                        marginLeft: 0,
                                        marginRight: 0,
                                        marginBottom: 20
                                    }} />
                                }
                                mapElement={
                                    <div style={{
                                        position: 'relative',
                                        top: 0,
                                        left: 0,
                                        right: 0,
                                        bottom: 0,
                                        width: 600,
                                        height: 600,
                                        marginTop: 20,
                                        marginLeft: 0,
                                        marginRight: 0,
                                        marginBottom: 20
                                    }} />
                                }
                                onMapLoad={this._handleMapLoad}
                                onMapClick={this._handleMapClick}
                                currentPosition={this.state.currentPosition}
                                />
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <ButtonInput
                            id="createJobBtn"
                            type='submit'
                            bsSize='small'
                            bsStyle='primary'
                            value='Register'>Create Job</ButtonInput>
                    </Row>
                </Grid>
            </Well>
        );
    }

    _handleMapLoad = (map) => {
        if (map) {
            console.log(map.getZoom());
        }
    }

    _handleMapClick = (event) => {
        this.setState({
            currentPosition: event.latLng
        });
    }
}

 

If you cut through all the bootstrap code to render things nicely it really boils down to what happens when the map gets clicked?

 

Well what happens is that the map getting clicked will fire the _handleMapClick function, which will set the internal currentPosition state value, which was handed down to the map sub component as props, so the Marker will be shown at the clicked location.

 

This is what the final rendering looks like

 

image

 

ViewJob

This is by far the most complex of all the screens, and here is why

  • It will have to show the incoming movements (geo-coords wise) of both driver(s) and passenger (icon distinguishes them from each other, as well as metadata)
  • A passenger should be able to accept a driver for a job
  • It will need to allow a job to be cancelled, with confirmation
  • It will need to allow a job to be completed
  • On completion of a job the driver should be able to rate a passenger
  • On completion of a job the passenger should be able to rate a driver

 

So as you can see it WILL do quite a lot, obviously a lot of this will come later once the streaming aspects of the code are completed. It is however quite a lot to do statically.

 

In fact it is so much that I have had to create 3 utility components to help me out

 

YesNoDialog

This represents a generic re-usable yes/no dialog that can be triggered, here is the code for this one. The important part is that the various prop values can be controlled via the parent component state values

 

import * as React from "react";
import * as ReactDOM from "react-dom";
import * as _ from "lodash";

import 'bootstrap/dist/css/bootstrap.css';
import
{
    Button, 
    Modal
} from "react-bootstrap";


//TODO : Fix this
export interface YesNoDialogProps {
    headerText: string;
    theId: string;
    launchButtonText: string;
    yesCallBack: any;
    noCallBack: any;
}

export interface YesNoDialogState {
    showModal: boolean;
}


export class YesNoDialog extends React.Component<YesNoDialogProps, YesNoDialogState> {

    constructor(props) {
        super(props);
        console.log(this.props);
        //set initial state
        this.state = {
            showModal: false
        };
    }

    _yesClicked = () => {
        this.setState({ showModal: false });
        this.props.yesCallBack();
    }

    _noClicked = () => {
        this.setState({ showModal: false });
        this.props.noCallBack();
    }

    _close = () => {
        this.setState({ showModal: false });
    }

    _open = () => {
        this.setState({ showModal: true });
    }

    render() {
        return (
            <div className="leftFloat">

                <Button
                    id={this.props.theId}
                    type='button'
                    bsSize='small'
                    bsStyle='primary'
                    onClick={this._open}>{this.props.launchButtonText}</Button>

                <Modal show={this.state.showModal} onHide={this._close}>
                    <Modal.Header closeButton>
                        <Modal.Title>{ this.props.headerText }</Modal.Title>
                    </Modal.Header>
                    <Modal.Body>
                        <h4>Are you sure?</h4>
                    </Modal.Body>
                    <Modal.Footer>
                        <Button
                            type='button'
                            bsSize='small'
                            bsStyle='primary'
                            onClick={this._yesClicked}>Yes</Button>
                        <Button
                            type='button'
                            bsSize='small'
                            bsStyle='danger'
                            onClick={this._noClicked}>Cancel</Button>
                    </Modal.Footer>
                </Modal>
            </div>
        );
    }
}

This looks like this when rendered

image

 

 

OkDialog

This represents a generic re-usable ok dialog that can be triggered, here is the code for this one. The important part is that the various prop values can be controlled via the parent component state values

import * as React from "react";
import * as ReactDOM from "react-dom";
import * as _ from "lodash";

import 'bootstrap/dist/css/bootstrap.css';
import
{
    Button, 
    Modal
} from "react-bootstrap";


//TODO : Fix this
export interface OkDialogProps {
    headerText: string;
    bodyText: string;
    open: boolean;
    okCallBack: any;
}

export interface OkDialogState {
    showModal: boolean;
}


export class OkDialog extends React.Component<OkDialogProps, OkDialogState> {

    constructor(props) {
        super(props);
        console.log(this.props);
        //set initial state
        this.state = {
            showModal: false
        };
    }

    componentDidMount() {
        if (this.props.open === true) {
            this.setState({ showModal: true });
        }
    }

    _okClicked = () => {
        this.setState({ showModal: false });
        this.props.okCallBack();
    }

    _close = () => {
        this.setState({ showModal: false });
        this.props.okCallBack();
    }

    _open = () => {
        this.setState({ showModal: true });
    }

    render() {
        return (
            <div className="leftFloat">

                <Modal show={this.state.showModal} onHide={this._close}>
                    <Modal.Header closeButton>
                        <Modal.Title>{ this.props.headerText }</Modal.Title>
                    </Modal.Header>
                    <Modal.Body>
                        <h4>{this.props.bodyText}</h4>
                    </Modal.Body>
                    <Modal.Footer>
                        <Button
                            type='button'
                            bsSize='small'
                            bsStyle='primary'
                            onClick={this._okClicked}>Ok</Button>
                    </Modal.Footer>
                </Modal>
            </div>
        );
    }
}

This looks like this when rendered

 

image

 

 

RatingDialog

This represents a generic re-usable rating control where rating can be from 1-5, here is the code for this one. The important part is that the various prop values can be controlled via the parent component state values

import * as React from "react";
import * as ReactDOM from "react-dom";
import * as _ from "lodash";

import 'bootstrap/dist/css/bootstrap.css';
import
{
    Button, 
    Modal
} from "react-bootstrap";


import ReactStars from 'react-stars';


export interface RatingDialogProps {
    headerText: string;
    theId: string;
    okCallBack: any;
}

export interface RatingDialogState {
    showModal: boolean;
    rating: number;
}


export class RatingDialog extends React.Component<RatingDialogProps, RatingDialogState> {

    constructor(props) {
        super(props);
        console.log(this.props); 
        //set initial state
        this.state = {
            showModal: false,
            rating:0
        };
    }

    _close = () => {
        this.setState(
            {
                showModal: false,
                rating:0
            }
        );
    }

    _open = () => {
        this.setState(
            {
                showModal: true,
                rating: 0
            }
        );
    }

    _ratingChanged = (newRating) => {
        console.log(newRating)
        this.setState(
            {
                rating: newRating
            }
        );
    }

    _okClicked = () => {
        this._close();
        this.props.okCallBack();
    }

    render() {
        return (
            <div className="leftFloat">

                <Button
                    id={this.props.theId}
                    type='button'
                    bsSize='small'
                    bsStyle='primary'
                    onClick={this._open}>Complete</Button>

                <Modal show={this.state.showModal} onHide={this._close}>
                    <Modal.Header closeButton>
                        <Modal.Title>{ this.props.headerText }</Modal.Title>
                    </Modal.Header>
                    <Modal.Body>
                        <h4>Give your rating between 1-5</h4>
                        <ReactStars count={5}
                                    onChange={this._ratingChanged}
                                    size={24}
                                    color2={'#ffd700'} />
                    </Modal.Body>
                    <Modal.Footer>
                        <Button
                            type='submit'
                            bsSize='small'
                            bsStyle='primary'
                            onClick={this._okClicked}>Ok</Button>
                    </Modal.Footer>
                </Modal>
            </div>
        );
    }
}

This looks like this when rendered

 

image

 

For the rating component I make use of this React library : https://www.npmjs.com/package/react-stars

 

 

So this just leaves the main component that represents the page, the full code is as follows:

 

import * as React from "react";
import * as ReactDOM from "react-dom";
import * as _ from "lodash";

import { RatingDialog } from "./components/RatingDialog";
import { YesNoDialog } from "./components/YesNoDialog";
import { OkDialog } from "./components/OkDialog";


import 'bootstrap/dist/css/bootstrap.css';
import
{
    Well,
    Grid,
    Row,
    Col,
    ButtonInput,
    ButtonGroup,
    Button, 
    Modal,
    Popover,
    Tooltip,
    OverlayTrigger
} from "react-bootstrap";

import { withGoogleMap, GoogleMap, Marker, OverlayView } from "react-google-maps";

const STYLES = {
    overlayView: {
        background: `white`,
        border: `1px solid #ccc`,
        padding: 15,
    }
}


const GetPixelPositionOffset = (width, height) => {
    return { x: -(width / 2), y: -(height / 2) };
}



const ViewJobGoogleMap = withGoogleMap(props => (
    <GoogleMap
        ref={props.onMapLoad}
        defaultZoom={14}
        defaultCenter={{ lat: 50.8202949, lng: -0.1406958 }}>


        {props.markers.map((marker, index) => (
            <OverlayView
                key={marker.key}
                mapPaneName={OverlayView.OVERLAY_MOUSE_TARGET}
                position={marker.position}
                getPixelPositionOffset={GetPixelPositionOffset}>
                <div style={STYLES.overlayView}>
                    <img src={marker.icon} />
                    <strong>{marker.key}</strong>
                    <br/>
                    <Button
                        type='button'
                        bsSize='xsmall'
                        bsStyle='primary'
                        onClick={() => props.onMarkerClick(marker) }
                        value='Accept'>Accept</Button>
                </div>
            </OverlayView>
        )) }
    </GoogleMap>
));

export interface ViewJobState {
    markers: any;
    okDialogOpen: boolean;
    okDialogKey: any;
    okDialogHeaderText: string;
    okDialogBodyText: string;
}

export class ViewJob extends React.Component<undefined, ViewJobState> {

    constructor(props: any) {
        super(props);
        this.state = {
            markers: [{
                    position: {
                        lat: 50.8202949,
                        lng: -0.1406958
                    },
                    key: 'driver_1',
                    icon: '/assets/images/driver.png'
                },
                {
                    position: {
                        lat: 50.8128187,
                        lng: -0.1361418
                    },
                    key: 'driver_2',
                    icon: '/assets/images/driver.png'
                }
            ],
            okDialogHeaderText: '',
            okDialogBodyText: '',
            okDialogOpen: false,
            okDialogKey:0
        };
    }


    render() {
        return (
            <Well className="outer-well">
                <Grid>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <h4>CURRENT JOB</h4>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <Col xs={10} md={6}>
                            <ViewJobGoogleMap
                                containerElement={
                                    <div style={{
                                        position: 'relative',
                                        top: 0,
                                        left: 0,
                                        right: 0,
                                        bottom: 0,
                                        width: 600,
                                        height: 600,
                                        justifyContent: 'flex-end',
                                        alignItems: 'center',
                                        marginTop: 20,
                                        marginLeft: 0,
                                        marginRight: 0,
                                        marginBottom: 20
                                    }} />
                                }
                                mapElement={
                                    <div style={{
                                        position: 'relative',
                                        top: 0,
                                        left: 0,
                                        right: 0,
                                        bottom: 0,
                                        width: 600,
                                        height: 600,
                                        marginTop: 20,
                                        marginLeft: 0,
                                        marginRight: 0,
                                        marginBottom: 20
                                    }} />
                                }
                                markers={this.state.markers}
                                onMarkerClick={this._handleClick}/>
                        </Col>
                    </Row>
                    <Row className="show-grid">
                        <span>
                            <RatingDialog
                                theId="viewJobCompleteBtn"
                                headerText="Rate your driver/passenger"
                                okCallBack= {this._ratingsDialogOkCallBack}/>

                            <YesNoDialog
                                theId="viewJobCancelBtn"
                                launchButtonText="Cancel"
                                yesCallBack={this._jobCancelledCallBack}
                                noCallBack={this._jobNotCancelledCallBack}
                                headerText="Cancel the job"/>

                            <OkDialog
                                open= {this.state.okDialogOpen}
                                okCallBack= {this._okDialogCallBack}
                                headerText={this.state.okDialogHeaderText}
                                bodyText={this.state.okDialogBodyText}
                                key={this.state.okDialogKey}/>
                        </span>
                    </Row>
                </Grid>
            </Well>
        );
    }

    _handleClick = (targetMarker) => {
        console.log('button on overlay clicked:' + targetMarker.key);
    }

    _ratingsDialogOkCallBack = () => {
        console.log('RATINGS OK CLICKED');
        this.setState(
            {
                okDialogHeaderText: 'Ratings',
                okDialogBodyText: 'Rating successfully recorded',
                okDialogOpen: true,
                okDialogKey: Math.random()
            });
    }

    _jobCancelledCallBack = () => {
        console.log('YES CLICKED');
        this.setState(
            {
                okDialogHeaderText: 'Job Cancellaton',
                okDialogBodyText: 'Job successfully cancelled',
                okDialogOpen: true,
                okDialogKey: Math.random() 
            });
    }

    _jobNotCancelledCallBack = () => {
        console.log('NO CLICKED');
        this.setState(
            {
                okDialogHeaderText: 'Job Cancellaton',
                okDialogBodyText: 'Job remains open',
                okDialogOpen: true,
                okDialogKey: Math.random() 
            });
    }

    _okDialogCallBack = () => {
        console.log('OK on OkDialog CLICKED');
        this.setState(
            {
                okDialogOpen: false
            });
    }
}

 

Which makes use of some of the React-Google-Maps goodness we saw before, but instead of Marker objects we are now using Overlay objects to represent the driver/passenger info, obviously we make use of an array of items this time, as many driver(s) might bid for a job. Also shown here is how you can create the dialogs on demand and have their content tailored to the specific scenario in hand.

 

Right now when this component renders it looks like this

 

image

 

ViewRating

The Rating screen is perhaps the easiest of all the screens it simply allows you view your overall rating and who made the rating for you so far. This would like be an active query over a Kafka streams KTable.

 

Here is the full code for this screen, there is not much to say on this one

import * as React from "react";
import * as ReactDOM from "react-dom";

import 'bootstrap/dist/css/bootstrap.css';
import
{
    Well,
    Grid,
    Row,
    Col,
    Label
} from "react-bootstrap";


export class ViewRating extends React.Component<undefined, undefined> {
    render() {
        return (
            <Well className="outer-well">
                    <Grid>
                        <Row className="show-grid">
                            <Col xs={6} md={6}>
                                <div>
                                    <h4>YOUR RANKING <Label>4.2</Label></h4>
                                </div>
                            </Col>
                        </Row>
                        <Row className="show-grid">
                            <Col xs={10} md={6}>
                                <h6>The finer details of your ranking are shown below</h6>
                            </Col>
                        </Row>
                        <Row className="show-grid">
                            <Col xs={10} md={6}>
                                <div className="table-responsive">
                                    <table className="table table-striped table-bordered table-condensed factTable">
                                        <thead>
                                            <tr>
                                                <th>Ranked By</th>
                                                <th>Rank Given</th>
                                            </tr>
                                        </thead>
                                        <tbody>
                                            <tr>
                                                <td>John Doe</td>
                                                <td>4.2</td>
                                            </tr>
                                            <tr>
                                                <td>Mary Moe</td>
                                                <td>4.7</td>
                                            </tr>
                                            <tr>
                                                <td>July Dooley</td>
                                                <td>4.5</td>
                                            </tr>
                                        </tbody>
                                    </table>
                                </div>
                            </Col>
                        </Row>
                    </Grid>
            </Well>
        )
    }
}


 

When rendered it looks like this

 

image

 

 

Conclusion

I am no web designed, but I am fairly pleased with the outcome of the static screen designs, and have found using React-bootstrap/react/the map components and the React-validation to be quite simple and they all seemed to play together quite nicely.

Leave a comment