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.