# Control Worker Activities using Worker.js: Add an Agent UI to our Project

Let's get started on our agent UI. Assuming you've followed the conventions so far in this tutorial, the UI we create will be accessible using your web browser at:

`http://localhost:8080/agents?WorkerSid=WK01234012340123401234` (substitute your Alice's WorkerSid)

We pass the WorkerSid in the URL to avoid implementing complex user management in our demo. In reality, you are likely to store a user's WorkerSid in your database alongside other User attributes.

Let's add on our to our `server.rb` file to add an endpoint to generate a page based on a template.

## server.rb

```rb
require 'rubygems'
require 'twilio-ruby'
require 'sinatra'
require 'json'

set :port, 8080

# Get your Account Sid and Auth Token from twilio.com/user/account
account_sid = '{{ account_sid }}'
auth_token = '{{ auth_token }}'
workspace_sid = '{{ workspace_sid }}'
workflow_sid = '{{ workflow_sid }}'

@client = Twilio::REST::Client.new(account_sid, auth_token)

post '/assignment_callback' do
  # Respond to assignment callbacks with accept instruction
  content_type :json
  # from must be a verified phone number from your twilio account
  {
    "instruction" => "dequeue",
    "from" => "+15556667777",
    "post_work_activity_sid" => "WA0123401234..."
  }.to_json
end

get '/create-task' do
  # Create a task
  task = @client.taskrouter.workspaces(workspace_sid)
                            .tasks
                            .create(
                                attributes: {
                                      'selected_language' => 'es'
                            }.to_json,
                                workflow_sid: workflow_sid
                            )
  task.attributes
end

get '/accept_reservation' do
  # Accept a Reservation
  task_sid = params[:task_sid]
  reservation_sid = params[:reservation_sid]

  reservation = @client.taskrouter.workspaces(workspace_sid)
                        .tasks(task_sid)
                        .reservations(reservation_sid)
                        .update(reservation_status: 'accepted')
  reservation.worker_name
end

get '/incoming_call' do
  Twilio::TwiML::VoiceResponse.new do |r|
    r.gather(action: '/enqueue_call', method: 'POST', timeout: 5, num_digits: 1) do |gather|
      gather.say('Para Español oprime el uno.', language: 'es')
      gather.say('For English, please hold or press two.', language: 'en')
    end
  end.to_s
end

post '/enqueue_call' do
  digit_pressed = params[:Digits]
  if digit_pressed == 1
    language = "es"
  else
    language = "en"
  end

  attributes = '{"selected_language":"'+language+'"}'

  Twilio::TwiML::Response.new do |r|
    r.Enqueue workflowSid: workflow_sid do |e|
      e.Task attributes
    end
  end.text
end

get '/agents' do
  worker_sid = params['WorkerSid']

  capability = Twilio::JWT::TaskRouterCapability.new(
    account_sid, auth_token,
    workspace_sid, worker_sid
  )

  allow_activity_updates = Twilio::JWT::TaskRouterCapability::Policy.new(
    Twilio::JWT::TaskRouterCapability::TaskRouterUtils
    .all_activities(workspace_sid), 'POST', true
  )
  capability.add_policy(allow_activity_updates)

  allow_reservation_updates = Twilio::JWT::TaskRouterCapability::Policy.new(
    Twilio::JWT::TaskRouterCapability::TaskRouterUtils
    .all_reservations(workspace_sid, worker_sid), 'POST', true
  )
  capability.add_policy(allow_reservation_updates)

  worker_token = capability.to_s

  erb :agent, :locals => {:worker_token => worker_token}
end
```

Now create a folder called views. Inside that folder, create an ERB template file that will be rendered when the URL is requested:

## views/agent.erb

```html
<!DOCTYPE html>
<html>
<head>
    <title>Customer Care - Voice Agent Screen</title>
    <link rel="stylesheet" href="//media.twiliocdn.com/taskrouter/quickstart/agent.css"/>
    <script src="https://sdk.twilio.com/js/taskrouter/v1.21/taskrouter.min.js" integrity="sha384-5fq+0qjayReAreRyHy38VpD3Gr9R2OYIzonwIkoGI4M9dhfKW6RWeRnZjfwSrpN8" crossorigin="anonymous"></script>
    <script type="text/javascript">
        /* Subscribe to a subset of the available TaskRouter.js events for a worker */
        function registerTaskRouterCallbacks() {
            worker.on('ready', function(worker) {
                agentActivityChanged(worker.activityName);
                logger("Successfully registered as: " + worker.friendlyName)
                logger("Current activity is: " + worker.activityName);
            });

            worker.on('activity.update', function(worker) {
                agentActivityChanged(worker.activityName);
                logger("Worker activity changed to: " + worker.activityName);
            });

            worker.on("reservation.created", function(reservation) {
                logger("-----");
                logger("You have been reserved to handle a call!");
                logger("Call from: " + reservation.task.attributes.from);
                logger("Selected language: " + reservation.task.attributes.selected_language);
                logger("-----");
            });

            worker.on("reservation.accepted", function(reservation) {
                logger("Reservation " + reservation.sid + " accepted!");
            });

            worker.on("reservation.rejected", function(reservation) {
                logger("Reservation " + reservation.sid + " rejected!");
            });

            worker.on("reservation.timeout", function(reservation) {
                logger("Reservation " + reservation.sid + " timed out!");
            });

            worker.on("reservation.canceled", function(reservation) {
                logger("Reservation " + reservation.sid + " canceled!");
            });
        }

        /* Hook up the agent Activity buttons to TaskRouter.js */

        function bindAgentActivityButtons() {
            // Fetch the full list of available Activities from TaskRouter. Store each
            // ActivitySid against the matching Friendly Name
            var activitySids = {};
            worker.activities.fetch(function(error, activityList) {
                var activities = activityList.data;
                var i = activities.length;
                while (i--) {
                    activitySids[activities[i].friendlyName] = activities[i].sid;
                }
            });

            /* For each button of class 'change-activity' in our Agent UI, look up the
             ActivitySid corresponding to the Friendly Name in the button's next-activity
             data attribute. Use Worker.js to transition the agent to that ActivitySid
             when the button is clicked.*/
            var elements = document.getElementsByClassName('change-activity');
            var i = elements.length;
            while (i--) {
                elements[i].onclick = function() {
                    var nextActivity = this.dataset.nextActivity;
                    var nextActivitySid = activitySids[nextActivity];
                    worker.update({"ActivitySid":nextActivitySid});
                }
            }
        }

        /* Update the UI to reflect a change in Activity */

        function agentActivityChanged(activity) {
            hideAgentActivities();
            showAgentActivity(activity);
        }

        function hideAgentActivities() {
            var elements = document.getElementsByClassName('agent-activity');
            var i = elements.length;
            while (i--) {
                elements[i].style.display = 'none';
            }
        }

        function showAgentActivity(activity) {
            activity = activity.toLowerCase();
            var elements = document.getElementsByClassName(('agent-activity ' + activity));
            elements.item(0).style.display = 'block';
        }

        /* Other stuff */

        function logger(message) {
            var log = document.getElementById('log');
            log.value += "\n> " + message;
            log.scrollTop = log.scrollHeight;
        }

        window.onload = function() {
            // Initialize TaskRouter.js on page load using window.workerToken -
            // a Twilio Capability token that was set from rendering the template with agents endpoint
            logger("Initializing...");
            window.worker = new Twilio.TaskRouter.Worker("{{ worker_token }}");

            registerTaskRouterCallbacks();
            bindAgentActivityButtons();
        };
    </script>
</head>
<body>
<div class="content">
    <section class="agent-activity offline">
        <p class="activity">Offline</p>
        <button class="change-activity" data-next-activity="Idle">Go Available</button>
    </section>
    <section class="agent-activity idle">
        <p class="activity"><span>Available</span></p>
        <button class="change-activity" data-next-activity="Offline">Go Offline</button>
    </section>
    <section class="agent-activity reserved">
        <p class="activity">Reserved</p>
    </section>
    <section class="agent-activity busy">
        <p class="activity">Busy</p>
    </section>
    <section class="agent-activity wrapup">
        <p class="activity">Wrap-Up</p>
        <button class="change-activity" data-next-activity="Idle">Go Available</button>
        <button class="change-activity" data-next-activity="Offline">Go Offline</button>
    </section>
    <section class="log">
      <textarea id="log" readonly="true"></textarea>
    </section>
</div>
</body>
</html>
```

You'll notice that we included two external files:

* **taskrouter.min.js** is the primary TaskRouter.js JavaScript file that communicates with TaskRouter's infrastructure on our behalf. You can use this URL to include Worker.js in your production application, but first check the [reference documentation](/docs/taskrouter/js-sdk) to ensure that you include the latest version number.
* **agent.css** is a simple CSS file created for the purpose of this Quickstart. It saves us having to type out some simple pre-defined styles.

And that's it! Open `http://localhost:8080/agents?WorkerSid=WK012340123401234` in your browser and you should see the screen below. If you make the same phone call as we made in Part 3, you should see Alice's Activity transition on screen as she is reserved and assigned to handle the Task.

If you see "Initializing..." and no progress, make sure that you have included the correct WorkerSid in the "WorkerSid" request parameter of the URL.

For more details, refer to the [TaskRouter JavaScript SDK documentation](/docs/taskrouter/js-sdk).

## Completed Agent UI

* This simple PoC has been tested in the latest version of popular browsers, including IE 11. \*

![Agent UI showing status 'Ready' with option to 'Go Offline'.](https://docs-resources.prod.twilio.com/ea5b8f6f3541c5a014adba0c1992eeb6b43939773cdf2ee8e44d8645c5055edb.png)
