CI HUBCI HUB SDK
Login Templates

Template Examples

Five production templates showing common login screen patterns. Each is a complete .ect file.

1. Server URL Form

Single URL field — user enters their portal URL, then the handler redirects to OAuth.

<% extend 'login-template.ect' %>

<p>Please enter the URL to your ExampleAdapter1 portal.</p>
<br>
<form action="<%- @action %>" method="post">
  <div class="field">
    <label class="label">ExampleAdapter1 portal URL</label>
    <div class="control">
      <input class="inputfield" type="url" name="serverUrl" value="<%= @serverUrl %>" required>
    </div>
  </div>
  <div class="field is-grouped">
    <div class="control">
      <button class="button is-link" type="submit">Login</button>
    </div>
    <!--
    <div class="control">
      <a class="button" href="<%- @action %>&canceled=true">Cancel</a>
    </div>
    -->
  </div>
</form>

Rendered with:

return render(path.join(import.meta.dirname, 'select-serverurl'), {
  title: 'Select ExampleAdapter1 Portal',
  action: urlObject.toString(),
  serverUrl: serverUrl || 'https://your-instance.example.com',
  logo,
  contact,
  error: serverUrlError ? 'Invalid Url (System is not a valid ExampleAdapter1 system).' : undefined
})

2. Credentials Form

Three credential fields — cloud name, API key, and API secret. Validates against the platform API before completing login.

<% extend 'login-template.ect' %>

<p>Please enter your credentials.</p>
<br>
<form action="<%- @action %>" method="post">
<div class="field">
    <label class="label">Cloud Name</label>
    <div class="control">
      <input class="inputfield" type="text" name="cloudName" value="<%= @cloudName %>" required>
    </div>
  </div>
  <div class="field">
    <label class="label">API-Key</label>
    <div class="control">
      <input class="inputfield" type="text" name="apiKey" value="<%= @apiKey %>" required>
    </div>
  </div>
  <div class="field">
    <label class="label">API-Secret</label>
    <div class="control">
      <input class="inputfield" type="password" name="apiSecret" value="<%= @apiSecret %>" required>
    </div>
  </div>
  <div class="field is-grouped">
    <div class="control">
      <button class="button is-link" type="submit">Login</button>
    </div>
  </div>
</form>

The handler re-renders this template with an error message on validation failure:

const render = error => res.render(path.join(import.meta.dirname, 'login'),
  Object.assign({
    title: 'ExampleAdapter2 Login',
    action: endpointUrlWithParam({ state }),
    logo,
    providerName: 'ExampleAdapter2',
    error
  }, token))

3. Tenant URL + Environment Selector

Conditional dropdown for server type (production/UAT) plus a tenant URL field. The @displayDropDown flag controls whether the environment selector appears.

<% extend 'login-template.ect' %>

<p>Please enter your ExampleAdapter3 Tenant URL.</p>
<br>
<form action="<%- @action %>" method="post">
 <% if @displayDropDown : %>
  <div class="field">
    <label class="label">Server</label>
    <div class="control">
      <select class="inputfield" name="serverType" required >
        <option value="prod" <% if @server == "prod" : %>selected<% end %> >Production</option>
        <option value="uat" <% if @server == "uat" : %>selected<% end %> >UAT</option>
        </select>
    </div>
  </div>
  <% end %>
  <div class="field">
    <label class="label">Tenant URL</label>
    <div class="control">
      <input class="inputfield" type="text" name="tenantUrl" value="<%= @tenantUrl %>" required>
    </div>
  </div>
  <div class="field is-grouped">
    <div class="control">
      <button class="button is-link" type="submit">Login</button>
    </div>
    <!--
    <div class="control">
      <a class="button" href="<%- @action %>&canceled=true">Cancel</a>
    </div>
    -->
  </div>
</form>

Key pattern: <% if @displayDropDown : %> conditionally shows form sections based on adapter configuration.

4. Organization Select

Dynamic dropdown populated from an array of organizations fetched after initial authentication.

<% extend 'login-template.ect' %>

<p>Please select your organization.</p>
<br>
<form action="<%- @action %>" method="post">
  <% if @organizations?.length : %>
  <div class="field">
    <label class="label">Organization</label>
    <div class="control">
      <select class="select" name="org" value="<%= @organization %>">
        <option value="">Please choose an organization</option>
        <% for entry in @organizations : %>
        <option value="<%= entry.id %>"><%= entry.name %></option>
        <% end %>
      </select>
    </div>
  </div>
  <% end %>
  <div class="field is-grouped">
    <div class="control">
      <button class="button is-link" type="submit">Confirm</button>
    </div>
    <!--
    <div class="control">
      <a class="button" href="<%- @action %>&canceled=true">Cancel</a>
    </div>
    -->
  </div>
</form>

The handler passes the organizations array:

return render(path.join(import.meta.dirname, 'select-org'), {
  action: urlObject.toString(),
  organizations: orgs.map(o => ({ id: o.id, name: o.name })),
  logo,
  contact
})

Key patterns:

  • @organizations?.length — safe-navigates and checks for a non-empty array
  • <% for entry in @organizations : %> — loops to generate <option> elements
  • Button says "Confirm" instead of "Login" — this is a post-auth selection step

5. Multi-Step: URL Entry → Brand Select

Single template that serves two purposes depending on @authMethod. First visit: URL input field. After OAuth: brand selection dropdown.

<% extend 'login-template.ect' %>

<p>Please enter the URL to your ExampleAdapter5 portal.</p>
<br>
<form action="<%- @action %>" method="post">
  <% if @authMethod=="brands" : %>
    <div class="field">
      <label class="label">Select a brand</label>
      <div class="control">
        <div class="select">
          <select name="brandId" required>
            <option value="">Select a brand</option>
            <% for brand in @brands : %>
              <option value="<%= brand.id %>"><%= brand.name %></option>
            <% end %>
          </select>
        </div>
      </div>
    </div>
  <% else : %>
  <div class="field">
    <label class="label">ExampleAdapter5 portal URL</label>
    <div class="control">
      <input class="inputfield" type="url" name="serverUrl" value="<%= @serverUrl %>" required>
    </div>
  </div>
  <% end %>
  <div class="field is-grouped">
    <div class="control">
      <button class="button is-link" type="submit">Login</button>
    </div>
    <!--
    <div class="control">
      <a class="button" href="<%- @action %>&canceled=true">Cancel</a>
    </div>
    -->
  </div>
</form>

The handler renders this template twice during the login flow:

Step 1 — URL entry (no authMethod set, renders the else branch):

return render(path.join(import.meta.dirname, 'select-serverurl'), {
  title: 'Select ExampleAdapter5 Portal',
  action: urlObject.toString(),
  serverUrl: serverUrl || 'https://your-instance.example.com',
  logo,
  contact
})

Step 2 — Brand selection (after OAuth, authMethod set to "brands"):

return render(path.join(import.meta.dirname, 'select-serverurl'), {
  title: 'Select Brand',
  brands: brands.data.data.brands,
  authMethod: 'brands',
  serverUrl,
  state,
  action: urlObject.toString(),
  logo,
  contact
})

Key pattern: <% if @authMethod=="brands" : %> switches the entire form between two modes using a single template file. The submit button and form action remain the same — only the input fields change.


Template Overview | Input Types | Login Flow

On this page