refactor!: update components

This commit is contained in:
@lukin 2023-01-15 00:00:00 +04:00
parent 17dedde7e2
commit b90506a476
69 changed files with 771 additions and 714 deletions

View file

@ -51,10 +51,11 @@ parent=keywind
### Theme ### Theme
When you do need to customize a palette, you can configure your colors under the `colors` key in the `theme` section of `tailwind.config.js` file: When you do need to customize a palette, you can configure your colors under the `colors` key in the `theme` section of Tailwind config file:
`tailwind.config.js`
```js ```js
// tailwind.config.js
module.exports = { module.exports = {
theme: { theme: {
extend: { extend: {
@ -70,18 +71,15 @@ Read more about Tailwind CSS configuration in the [documentation](https://tailwi
### Components ### Components
You can inherit Keywind components in your own theme. For example, to resize the primary button you should create a styled `theme/mytheme/components/button/primary.ftl` file: You can update Keywind components in your own child theme. For example, create a copy of the `body` component and change the background:
`theme/mytheme/login/components/atoms/body.ftl`
``` ```
<#macro kw component="button" rest...> <#macro kw>
<${component} <body class="bg-primary-100">
class="bg-primary-600 flex justify-center px-6 py-3 relative rounded-lg text-md text-white w-full focus:outline-none focus:ring-2 focus:ring-primary-600 focus:ring-offset-2 hover:bg-primary-700"
<#list rest as attrName, attrValue>
${attrName}="${attrValue}"
</#list>
>
<#nested> <#nested>
</${component}> </body>
</#macro> </#macro>
``` ```

View file

@ -0,0 +1,7 @@
<#-- https://github.com/tailwindlabs/heroicons/blob/master/src/20/solid/arrow-top-right-on-square.svg -->
<#macro kw>
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M4.25 5.5C3.83579 5.5 3.5 5.83579 3.5 6.25V14.75C3.5 15.1642 3.83579 15.5 4.25 15.5H12.75C13.1642 15.5 13.5 15.1642 13.5 14.75V10.75C13.5 10.3358 13.8358 10 14.25 10C14.6642 10 15 10.3358 15 10.75V14.75C15 15.9926 13.9926 17 12.75 17H4.25C3.00736 17 2 15.9926 2 14.75V6.25C2 5.00736 3.00736 4 4.25 4H9.25C9.66421 4 10 4.33579 10 4.75C10 5.16421 9.66421 5.5 9.25 5.5H4.25Z" fill-rule="evenodd" />
<path clip-rule="evenodd" d="M6.19385 12.7532C6.47175 13.0603 6.94603 13.0841 7.25319 12.8062L16.5 4.43999V7.25C16.5 7.66421 16.8358 8 17.25 8C17.6642 8 18 7.66421 18 7.25V2.75C18 2.33579 17.6642 2 17.25 2H12.75C12.3358 2 12 2.33579 12 2.75C12 3.16421 12.3358 3.5 12.75 3.5H15.3032L6.24682 11.6938C5.93966 11.9717 5.91595 12.446 6.19385 12.7532Z" fill-rule="evenodd" />
</svg>
</#macro>

View file

@ -0,0 +1,6 @@
<#-- https://github.com/tailwindlabs/heroicons/blob/master/src/20/solid/chevron-down.svg -->
<#macro kw>
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</#macro>

View file

@ -1,16 +1,16 @@
<#import "./provider/bitbucket.ftl" as bitbucketIcon> <#import "./bitbucket.ftl" as bitbucketIcon>
<#import "./provider/facebook.ftl" as facebookIcon> <#import "./facebook.ftl" as facebookIcon>
<#import "./provider/github.ftl" as githubIcon> <#import "./github.ftl" as githubIcon>
<#import "./provider/gitlab.ftl" as gitlabIcon> <#import "./gitlab.ftl" as gitlabIcon>
<#import "./provider/google.ftl" as googleIcon> <#import "./google.ftl" as googleIcon>
<#import "./provider/instagram.ftl" as instagramIcon> <#import "./instagram.ftl" as instagramIcon>
<#import "./provider/linkedin.ftl" as linkedinIcon> <#import "./linkedin.ftl" as linkedinIcon>
<#import "./provider/microsoft.ftl" as microsoftIcon> <#import "./microsoft.ftl" as microsoftIcon>
<#import "./provider/oidc.ftl" as oidcIcon> <#import "./oidc.ftl" as oidcIcon>
<#import "./provider/openshift.ftl" as openshiftIcon> <#import "./openshift.ftl" as openshiftIcon>
<#import "./provider/paypal.ftl" as paypalIcon> <#import "./paypal.ftl" as paypalIcon>
<#import "./provider/stackoverflow.ftl" as stackoverflowIcon> <#import "./stackoverflow.ftl" as stackoverflowIcon>
<#import "./provider/twitter.ftl" as twitterIcon> <#import "./twitter.ftl" as twitterIcon>
<#macro bitbucket> <#macro bitbucket>
<@bitbucketIcon.kw /> <@bitbucketIcon.kw />

View file

@ -0,0 +1,22 @@
<#macro kw color="">
<#switch color>
<#case "error">
<#assign colorClass="bg-red-100 text-red-600">
<#break>
<#case "info">
<#assign colorClass="bg-blue-100 text-blue-600">
<#break>
<#case "success">
<#assign colorClass="bg-green-100 text-green-600">
<#break>
<#case "warning">
<#assign colorClass="bg-orange-100 text-orange-600">
<#break>
<#default>
<#assign colorClass="bg-blue-100 text-blue-600">
</#switch>
<div class="${colorClass} p-4 rounded-lg text-sm" role="alert">
<#nested>
</div>
</#macro>

View file

@ -0,0 +1,5 @@
<#macro kw>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<#nested>
</body>
</#macro>

View file

@ -0,0 +1,5 @@
<#macro kw>
<div class="flex flex-col pt-4 space-y-2">
<#nested>
</div>
</#macro>

View file

@ -0,0 +1,33 @@
<#macro kw color="" component="button" size="" rest...>
<#switch color>
<#case "primary">
<#assign colorClass="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700">
<#break>
<#case "secondary">
<#assign colorClass="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900">
<#break>
<#default>
<#assign colorClass="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700">
</#switch>
<#switch size>
<#case "medium">
<#assign sizeClass="px-4 py-2 text-sm">
<#break>
<#case "small">
<#assign sizeClass="px-2 py-1 text-xs">
<#break>
<#default>
<#assign sizeClass="px-4 py-2 text-sm">
</#switch>
<${component}
class="${colorClass} ${sizeClass} flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2"
<#list rest as attrName, attrValue>
${attrName}="${attrValue}"
</#list>
>
<#nested>
</${component}>
</#macro>

View file

@ -0,0 +1,19 @@
<#macro kw content="" footer="" header="">
<div class="bg-white p-8 rounded-lg space-y-6">
<#if header?has_content>
<div class="space-y-4">
${header}
</div>
</#if>
<#if content?has_content>
<div class="space-y-4">
${content}
</div>
</#if>
<#if footer?has_content>
<div class="space-y-4">
${footer}
</div>
</#if>
</div>
</#macro>

View file

@ -1,17 +1,19 @@
<#macro kw name checked=false rest...> <#macro kw checked=false label="" name="" rest...>
<div class="flex items-center"> <div class="flex items-center">
<input <input
<#if checked>checked</#if> <#if checked>checked</#if>
class="border-secondary-200 h-4 rounded text-primary-600 w-4 focus:ring-primary-200 focus:ring-opacity-50" class="border-secondary-200 h-4 rounded text-primary-600 w-4 focus:ring-primary-200 focus:ring-opacity-50"
id="${name}" id="${name}"
name="${name}" name="${name}"
type="checkbox" type="checkbox"
<#list rest as attrName, attrValue> <#list rest as attrName, attrValue>
${attrName}="${attrValue}" ${attrName}="${attrValue}"
</#list> </#list>
> >
<label class="block ml-2 text-secondary-900 text-sm" for="${name}"> <label class="ml-2 text-secondary-600 text-sm" for="${name}">
<#nested> ${label}
</label> </label>
</div> </div>
</#macro> </#macro>

View file

@ -0,0 +1,5 @@
<#macro kw>
<div class="max-w-md space-y-6 w-full">
<#nested>
</div>
</#macro>

View file

@ -0,0 +1,11 @@
<#macro kw rest...>
<form
class="m-0 space-y-4"
<#list rest as attrName, attrValue>
${attrName}="${attrValue}"
</#list>
>
<#nested>
</form>
</#macro>

View file

@ -0,0 +1,5 @@
<#macro kw>
<h1 class="text-center text-xl">
<#nested>
</h1>
</#macro>

View file

@ -0,0 +1,37 @@
<#macro
kw
autofocus=false
disabled=false
invalid=false
label=""
message=""
name=""
required=true
rest...
>
<div>
<label class="sr-only" for="${name}">
${label}
</label>
<input
<#if autofocus>autofocus</#if>
<#if disabled>disabled</#if>
<#if required>required</#if>
aria-invalid="${invalid?c}"
class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm"
id="${name}"
name="${name}"
placeholder="${label}"
<#list rest as attrName, attrValue>
${attrName}="${attrValue}"
</#list>
>
<#if invalid?? && message??>
<div class="mt-2 text-red-600 text-sm">
${message?no_esc}
</div>
</#if>
</div>
</#macro>

View file

@ -0,0 +1,30 @@
<#macro kw color="" component="a" size="" rest...>
<#switch color>
<#case "primary">
<#assign colorClass="text-primary-600 hover:text-primary-500">
<#break>
<#case "secondary">
<#assign colorClass="text-secondary-600 hover:text-secondary-900">
<#break>
<#default>
<#assign colorClass="text-primary-600 hover:text-primary-500">
</#switch>
<#switch size>
<#case "small">
<#assign sizeClass="text-sm">
<#break>
<#default>
<#assign sizeClass="">
</#switch>
<${component}
class="<#compress>${colorClass} ${sizeClass} inline-flex</#compress>"
<#list rest as attrName, attrValue>
${attrName}="${attrValue}"
</#list>
>
<#nested>
</${component}>
</#macro>

View file

@ -1,5 +1,5 @@
<#macro kw> <#macro kw>
<div class="font-bold text-center text-2xl"> <div class="font-bold text-center text-2xl">
${kcSanitize(msg("loginTitleHtml", (realm.displayNameHtml!"")))?no_esc} <#nested>
</div> </div>
</#macro> </#macro>

View file

@ -1,20 +1,18 @@
<#macro kw id tabIndex checked=false rest...> <#macro kw checked=false id="" label="" rest...>
<div> <div>
<input <input
<#if checked>checked</#if> <#if checked>checked</#if>
class="border-secondary-200 focus:ring-primary-600" class="border-secondary-200 focus:ring-primary-600"
id="${id}" id="${id}"
type="radio" type="radio"
<#list rest as attrName, attrValue> <#list rest as attrName, attrValue>
${attrName}="${attrValue}" ${attrName}="${attrValue}"
</#list> </#list>
> >
<label <label class="ml-2 text-secondary-600 text-sm" for="${id}">
class="font-medium ml-2 text-sm" ${label}
for="${id}"
tabindex="${tabIndex}"
>
<#nested>
</label> </label>
</div> </div>
</#macro> </#macro>

View file

@ -1,10 +0,0 @@
<#macro kw component="button" rest...>
<${component}
class="bg-primary-600 flex justify-center px-4 py-2 relative rounded-lg text-sm text-white w-full focus:outline-none focus:ring-2 focus:ring-primary-600 focus:ring-offset-2 hover:bg-primary-700"
<#list rest as attrName, attrValue>
${attrName}="${attrValue}"
</#list>
>
<#nested>
</${component}>
</#macro>

View file

@ -1,10 +0,0 @@
<#macro kw component="button" rest...>
<${component}
class="bg-secondary-100 flex justify-center px-4 py-2 relative rounded-lg text-sm text-secondary-600 w-full focus:outline-none focus:ring-2 focus:ring-secondary-600 focus:ring-offset-2 hover:bg-secondary-200 hover:text-secondary-900"
<#list rest as attrName, attrValue>
${attrName}="${attrValue}"
</#list>
>
<#nested>
</${component}>
</#macro>

View file

@ -1,6 +0,0 @@
<#-- https://github.com/tailwindlabs/heroicons/blob/master/src/outline/chevron-down.svg -->
<#macro kw>
<svg fill="none" height="1em" stroke="currentColor" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</#macro>

View file

@ -1,6 +0,0 @@
<#-- https://github.com/tailwindlabs/heroicons/blob/master/src/outline/external-link.svg -->
<#macro kw>
<svg fill="none" height="1em" stroke="currentColor" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</#macro>

View file

@ -1,23 +0,0 @@
<#macro kw invalid name autofocus=false disabled=false message=true required=true rest...>
<label class="sr-only" for="${name}">
<#nested>
</label>
<input
<#if autofocus>autofocus</#if>
<#if disabled>disabled</#if>
<#if required>required</#if>
aria-invalid="${messagesPerField.existsError(invalid)?c}"
class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm"
id="${name}"
name="${name}"
placeholder="<#compress><#nested></#compress>"
<#list rest as attrName, attrValue>
${attrName}="${attrValue}"
</#list>
>
<#if message && messagesPerField.existsError(invalid)>
<div class="mt-2 text-red-600 text-sm">
${kcSanitize(messagesPerField.getFirstError(invalid))?no_esc}
</div>
</#if>
</#macro>

View file

@ -1,22 +0,0 @@
<#macro kw>
<#switch message.type>
<#case "error">
<#assign color="bg-red-100 text-red-600">
<#break>
<#case "info">
<#assign color="bg-blue-100 text-blue-600">
<#break>
<#case "success">
<#assign color="bg-green-100 text-green-600">
<#break>
<#case "warning">
<#assign color="bg-orange-100 text-orange-600">
<#break>
<#default>
<#assign color="bg-blue-100 text-blue-600">
</#switch>
<div class="${color} p-4 rounded-lg text-sm" role="alert">
<span>${kcSanitize(message.summary)?no_esc}</span>
</div>
</#macro>

View file

@ -1,10 +0,0 @@
<#import "../button/primary.ftl" as buttonPrimary>
<#macro kw>
<form action="${url.loginAction}" class="flex justify-center" method="post">
<input name="tryAnotherWay" type="hidden" value="on"/>
<@buttonPrimary.kw type="submit">
${msg("doTryAnotherWay")}
</@buttonPrimary.kw>
</form>
</#macro>

View file

@ -1,5 +0,0 @@
<#macro kw>
<footer class="space-y-4">
<#nested>
</footer>
</#macro>

View file

@ -1,5 +0,0 @@
<#macro kw>
<header class="space-y-4">
<#nested>
</header>
</#macro>

View file

@ -1,5 +0,0 @@
<#macro kw>
<main class="space-y-4">
<#nested>
</main>
</#macro>

View file

@ -1,5 +0,0 @@
<#macro kw>
<div class="bg-white p-8 rounded-lg space-y-6">
<#nested>
</div>
</#macro>

View file

@ -1,7 +0,0 @@
<#macro kw>
<div class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<#nested>
</div>
</div>
</#macro>

View file

@ -1,29 +0,0 @@
<#import "../icon/chevron-down.ftl" as iconChevronDown>
<#import "../link/secondary.ftl" as linkSecondary>
<#macro kw>
<div class="relative" x-data="{open: false}">
<@linkSecondary.kw component="button" type="button" @click="open = true">
<div class="flex items-center">
<span class="mr-1 text-sm">${locale.current}</span>
<@iconChevronDown.kw />
</div>
</@linkSecondary.kw>
<div
class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg"
x-cloak
x-show="open"
@click.away="open = false"
>
<#list locale.supported as locales>
<#if locale.current != locales.label>
<div class="px-4 py-2">
<@linkSecondary.kw href=locales.url>
<span class="text-sm">${locales.label}</span>
</@linkSecondary.kw>
</div>
</#if>
</#list>
</div>
</div>
</#macro>

View file

@ -1,3 +0,0 @@
<#macro kw>
<div class="text-secondary-600 text-sm">* ${msg("requiredFields")}</div>
</#macro>

View file

@ -1,3 +0,0 @@
<#macro kw>
<h1 class="text-center text-xl"><#nested "header"></h1>
</#macro>

View file

@ -1,15 +0,0 @@
<#import "../icon/external-link.ftl" as iconExternalLink>
<#import "../link/primary.ftl" as linkPrimary>
<#macro kw>
<#nested "show-username">
<div class="flex items-center justify-center mb-4 space-x-2">
<b>${auth.attemptedUsername}</b>
<@linkPrimary.kw
href="${url.loginRestartFlowUrl}"
title="${msg('restartLoginTooltip')}"
>
<@iconExternalLink.kw />
</@linkPrimary.kw>
</div>
</#macro>

View file

@ -1,10 +0,0 @@
<#macro kw component="a" rest...>
<${component}
class="inline-flex text-primary-600 hover:text-primary-500"
<#list rest as attrName, attrValue>
${attrName}="${attrValue}"
</#list>
>
<#nested>
</${component}>
</#macro>

View file

@ -1,10 +0,0 @@
<#macro kw component="a" rest...>
<${component}
class="inline-flex text-secondary-600 hover:text-secondary-900"
<#list rest as attrName, attrValue>
${attrName}="${attrValue}"
</#list>
>
<#nested>
</${component}>
</#macro>

View file

@ -0,0 +1,72 @@
<#import "/assets/providers/providers.ftl" as providerIcons>
<#macro kw providers=[]>
<div class="pt-4 separate text-secondary-600 text-sm">
${msg("identity-provider-login-label")}
</div>
<div class="gap-4 grid grid-cols-3">
<#list providers as provider>
<#switch provider.alias>
<#case "bitbucket">
<#assign colorClass="hover:bg-provider-bitbucket/10">
<#break>
<#case "facebook">
<#assign colorClass="hover:bg-provider-facebook/10">
<#break>
<#case "github">
<#assign colorClass="hover:bg-provider-github/10">
<#break>
<#case "gitlab">
<#assign colorClass="hover:bg-provider-gitlab/10">
<#break>
<#case "google">
<#assign colorClass="hover:bg-provider-google/10">
<#break>
<#case "instagram">
<#assign colorClass="hover:bg-provider-instagram/10">
<#break>
<#case "linkedin">
<#assign colorClass="hover:bg-provider-linkedin/10">
<#break>
<#case "microsoft">
<#assign colorClass="hover:bg-provider-microsoft/10">
<#break>
<#case "oidc">
<#assign colorClass="hover:bg-provider-oidc/10">
<#break>
<#case "openshift-v3">
<#assign colorClass="hover:bg-provider-openshift/10">
<#break>
<#case "openshift-v4">
<#assign colorClass="hover:bg-provider-openshift/10">
<#break>
<#case "paypal">
<#assign colorClass="hover:bg-provider-paypal/10">
<#break>
<#case "stackoverflow">
<#assign colorClass="hover:bg-provider-stackoverflow/10">
<#break>
<#case "twitter">
<#assign colorClass="hover:bg-provider-twitter/10">
<#break>
<#default>
<#assign colorClass="hover:bg-secondary-100">
</#switch>
<a
class="${colorClass} border border-secondary-200 flex justify-center py-2 rounded-lg hover:border-transparent"
data-provider="${provider.alias}"
href="${provider.loginUrl}"
type="button"
>
<#if providerIcons[provider.alias]??>
<div class="h-6 w-6">
<@providerIcons[provider.alias] />
</div>
<#else>
${provider.displayName!}
</#if>
</a>
</#list>
</div>
</#macro>

View file

@ -0,0 +1,29 @@
<#import "/assets/icons/chevron-down.ftl" as icon>
<#import "/components/atoms/link.ftl" as link>
<#macro kw currentLocale="" locales=[]>
<div class="relative" x-data="{ open: false }">
<@link.kw @click="open = true" color="secondary" component="button" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">${currentLocale}</span>
<@icon.kw />
</div>
</@link.kw>
<div
@click.away="open = false"
class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg"
x-cloak
x-show="open"
>
<#list locales as locale>
<#if currentLocale != locale.label>
<div class="px-4 py-2">
<@link.kw color="secondary" href=locale.url size="small">
${locale.label}
</@link.kw>
</div>
</#if>
</#list>
</div>
</div>
</#macro>

View file

@ -0,0 +1,15 @@
<#import "/assets/icons/arrow-top-right-on-square.ftl" as icon>
<#import "/components/atoms/link.ftl" as link>
<#macro kw linkHref="" linkTitle="" name="">
<div class="flex items-center justify-center mb-4 space-x-2">
<b>${name}</b>
<@link.kw
color="primary"
href=linkHref
title=linkTitle
>
<@icon.kw />
</@link.kw>
</div>
</#macro>

View file

@ -1,70 +0,0 @@
<#import "./icon/provider.ftl" as iconProvider>
<#macro kw>
<div class="pt-4 separate text-secondary-600 text-sm">${msg("identity-provider-login-label")}</div>
<div class="gap-4 grid grid-cols-3">
<#list social.providers as provider>
<#switch provider.alias>
<#case "bitbucket">
<#assign color="hover:bg-provider-bitbucket/10">
<#break>
<#case "facebook">
<#assign color="hover:bg-provider-facebook/10">
<#break>
<#case "github">
<#assign color="hover:bg-provider-github/10">
<#break>
<#case "gitlab">
<#assign color="hover:bg-provider-gitlab/10">
<#break>
<#case "google">
<#assign color="hover:bg-provider-google/10">
<#break>
<#case "instagram">
<#assign color="hover:bg-provider-instagram/10">
<#break>
<#case "linkedin">
<#assign color="hover:bg-provider-linkedin/10">
<#break>
<#case "microsoft">
<#assign color="hover:bg-provider-microsoft/10">
<#break>
<#case "oidc">
<#assign color="hover:bg-provider-oidc/10">
<#break>
<#case "openshift-v3">
<#assign color="hover:bg-provider-openshift/10">
<#break>
<#case "openshift-v4">
<#assign color="hover:bg-provider-openshift/10">
<#break>
<#case "paypal">
<#assign color="hover:bg-provider-paypal/10">
<#break>
<#case "stackoverflow">
<#assign color="hover:bg-provider-stackoverflow/10">
<#break>
<#case "twitter">
<#assign color="hover:bg-provider-twitter/10">
<#break>
<#default>
<#assign color="hover:bg-secondary-100">
</#switch>
<a
class="border border-secondary-200 flex justify-center py-2 rounded-lg hover:border-transparent ${color}"
data-provider="${provider.alias}"
href="${provider.loginUrl}"
type="button"
>
<#if iconProvider[provider.alias]??>
<div class="h-6 w-6">
<@iconProvider[provider.alias] />
</div>
<#else>
${provider.displayName!}
</#if>
</a>
</#list>
</div>
</#macro>

View file

@ -1,4 +1,4 @@
<#macro kw> <#macro kw script="">
<title>${msg("loginTitle", (realm.displayName!""))}</title> <title>${msg("loginTitle", (realm.displayName!""))}</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
@ -23,9 +23,13 @@
</#list> </#list>
</#if> </#if>
<#if script?has_content>
<script defer src="${url.resourcesPath}/${script}" type="module"></script>
</#if>
<#if properties.scripts?has_content> <#if properties.scripts?has_content>
<#list properties.scripts?split(" ") as script> <#list properties.scripts?split(" ") as script>
<script defer src="${url.resourcesPath}/${script}" type="text/javascript"></script> <script defer src="${url.resourcesPath}/${script}" type="module"></script>
</#list> </#list>
</#if> </#if>
</#macro> </#macro>

View file

@ -1,10 +1,14 @@
<#import "template.ftl" as layout> <#import "template.ftl" as layout>
<#import "components/button/primary.ftl" as buttonPrimary> <#import "components/atoms/button.ftl" as button>
<#import "components/button/secondary.ftl" as buttonSecondary> <#import "components/atoms/button-group.ftl" as buttonGroup>
<#import "components/input/primary.ftl" as inputPrimary> <#import "components/atoms/form.ftl" as form>
<#import "components/label/totp.ftl" as labelTotp> <#import "components/atoms/input.ftl" as input>
<#import "components/label/userdevice.ftl" as labelUserDevice> <#import "components/atoms/link.ftl" as link>
<#import "components/link/primary.ftl" as linkPrimary> <#import "features/labels/totp.ftl" as totpLabel>
<#import "features/labels/totp-device.ftl" as totpDeviceLabel>
<#assign totpLabel><@totpLabel.kw /></#assign>
<#assign totpDeviceLabel><@totpDeviceLabel.kw /></#assign>
<@layout.registrationLayout <@layout.registrationLayout
displayMessage=!messagesPerField.existsError("totp", "userLabel") displayMessage=!messagesPerField.existsError("totp", "userLabel")
@ -24,15 +28,15 @@
</#list> </#list>
</ul> </ul>
</li> </li>
<#if mode?? && mode = "manual"> <#if mode?? && mode="manual">
<li class="space-y-2"> <li>
<p>${msg("loginTotpManualStep2")}</p> <p>${msg("loginTotpManualStep2")}</p>
<p class="font-bold text-xl">${totp.totpSecretEncoded}</p> <p class="font-bold text-xl">${totp.totpSecretEncoded}</p>
</li> </li>
<li> <li>
<@linkPrimary.kw href=totp.qrUrl> <@link.kw color="primary" href=totp.qrUrl>
${msg("loginTotpScanBarcode")} ${msg("loginTotpScanBarcode")}
</@linkPrimary.kw> </@link.kw>
</li> </li>
<li class="space-y-2"> <li class="space-y-2">
<p>${msg("loginTotpManualStep3")}</p> <p>${msg("loginTotpManualStep3")}</p>
@ -40,9 +44,9 @@
<li>${msg("loginTotpType")}: ${msg("loginTotp." + totp.policy.type)}</li> <li>${msg("loginTotpType")}: ${msg("loginTotp." + totp.policy.type)}</li>
<li>${msg("loginTotpAlgorithm")}: ${totp.policy.getAlgorithmKey()}</li> <li>${msg("loginTotpAlgorithm")}: ${totp.policy.getAlgorithmKey()}</li>
<li>${msg("loginTotpDigits")}: ${totp.policy.digits}</li> <li>${msg("loginTotpDigits")}: ${totp.policy.digits}</li>
<#if totp.policy.type = "totp"> <#if totp.policy.type="totp">
<li>${msg("loginTotpInterval")}: ${totp.policy.period}</li> <li>${msg("loginTotpInterval")}: ${totp.policy.period}</li>
<#elseif totp.policy.type = "hotp"> <#elseif totp.policy.type="hotp">
<li>${msg("loginTotpCounter")}: ${totp.policy.initialCounter}</li> <li>${msg("loginTotpCounter")}: ${totp.policy.initialCounter}</li>
</#if> </#if>
</ul> </ul>
@ -55,58 +59,52 @@
class="mx-auto" class="mx-auto"
src="data:image/png;base64, ${totp.totpSecretQrCode}" src="data:image/png;base64, ${totp.totpSecretQrCode}"
> >
<@linkPrimary.kw href=totp.manualUrl> <@link.kw color="primary" href=totp.manualUrl>
${msg("loginTotpUnableToScan")} ${msg("loginTotpUnableToScan")}
</@linkPrimary.kw> </@link.kw>
</li> </li>
</#if> </#if>
<li>${msg("loginTotpStep3")}</li> <li>${msg("loginTotpStep3")}</li>
<li>${msg("loginTotpStep3DeviceName")}</li> <li>${msg("loginTotpStep3DeviceName")}</li>
</ol> </ol>
<form action="${url.loginAction}" class="m-0 space-y-4" method="post"> <@form.kw action=url.loginAction method="post">
<div> <input name="totpSecret" type="hidden" value="${totp.totpSecret}">
<@inputPrimary.kw <#if mode??>
autocomplete="off" <input name="mode" type="hidden" value="${mode}">
autofocus=true
invalid=["totp"]
name="totp"
required=false
type="text"
>
<@labelTotp.kw />
</@inputPrimary.kw>
<input name="totpSecret" type="hidden" value="${totp.totpSecret}">
<#if mode??>
<input name="mode" type="hidden" value="${mode}">
</#if>
</div>
<div>
<@inputPrimary.kw
autocomplete="off"
invalid=["userLabel"]
name="userLabel"
required=false
type="text"
>
<@labelUserDevice.kw />
</@inputPrimary.kw>
</div>
<#if isAppInitiatedAction??>
<div class="flex flex-col pt-4 space-y-2">
<@buttonPrimary.kw type="submit">
${msg("doSubmit")}
</@buttonPrimary.kw>
<@buttonSecondary.kw name="cancel-aia" type="submit">
${msg("doCancel")}
</@buttonSecondary.kw>
</div>
<#else>
<div class="pt-4">
<@buttonPrimary.kw type="submit">
${msg("doSubmit")}
</@buttonPrimary.kw>
</div>
</#if> </#if>
</form> <@input.kw
autocomplete="off"
autofocus=true
invalid=messagesPerField.existsError("totp")
label=totpLabel
message=kcSanitize(messagesPerField.get("totp"))
name="totp"
required=false
type="text"
/>
<@input.kw
autocomplete="off"
invalid=messagesPerField.existsError("userLabel")
label=totpDeviceLabel
message=kcSanitize(messagesPerField.get("userLabel"))
name="userLabel"
required=false
type="text"
/>
<@buttonGroup.kw>
<#if isAppInitiatedAction??>
<@button.kw color="primary" type="submit">
${msg("doSubmit")}
</@button.kw>
<@button.kw color="secondary" name="cancel-aia" type="submit" value="true">
${msg("doCancel")}
</@button.kw>
<#else>
<@button.kw color="primary" type="submit">
${msg("doSubmit")}
</@button.kw>
</#if>
</@buttonGroup.kw>
</@form.kw>
</#if> </#if>
</@layout.registrationLayout> </@layout.registrationLayout>

View file

@ -1,21 +1,18 @@
<#import "template.ftl" as layout> <#import "template.ftl" as layout>
<#import "components/button/primary.ftl" as buttonPrimary> <#import "components/atoms/button.ftl" as button>
<#import "components/atoms/form.ftl" as form>
<@layout.registrationLayout; section> <@layout.registrationLayout; section>
<#if section="header"> <#if section="header">
${msg("confirmLinkIdpTitle")} ${msg("confirmLinkIdpTitle")}
<#elseif section="form"> <#elseif section="form">
<form action="${url.loginAction}" class="m-0 space-y-4" method="post"> <@form.kw action=url.loginAction method="post">
<div> <@button.kw color="primary" name="submitAction" type="submit" value="updateProfile">
<@buttonPrimary.kw name="submitAction" type="submit" value="updateProfile"> ${msg("confirmLinkIdpReviewProfile")}
${msg("confirmLinkIdpReviewProfile")} </@button.kw>
</@buttonPrimary.kw> <@button.kw color="primary" name="submitAction" type="submit" value="linkAccount">
</div> ${msg("confirmLinkIdpContinue", idpDisplayName)}
<div> </@button.kw>
<@buttonPrimary.kw name="submitAction" type="submit" value="linkAccount"> </@form.kw>
${msg("confirmLinkIdpContinue", idpDisplayName)}
</@buttonPrimary.kw>
</div>
</form>
</#if> </#if>
</@layout.registrationLayout> </@layout.registrationLayout>

View file

@ -1,6 +1,7 @@
<#import "template.ftl" as layout> <#import "template.ftl" as layout>
<#import "components/button/primary.ftl" as buttonPrimary> <#import "components/atoms/button.ftl" as button>
<#import "components/button/secondary.ftl" as buttonSecondary> <#import "components/atoms/button-group.ftl" as buttonGroup>
<#import "components/atoms/form.ftl" as form>
<@layout.registrationLayout; section> <@layout.registrationLayout; section>
<#if section="header"> <#if section="header">
@ -46,16 +47,16 @@
</#if> </#if>
</h3> </h3>
</#if> </#if>
<form action="${url.oauthAction}" class="m-0 space-y-4" method="POST"> <@form.kw action=url.oauthAction method="post">
<input name="code" type="hidden" value="${oauth.code}"> <input name="code" type="hidden" value="${oauth.code}">
<div class="flex flex-col pt-4 space-y-2"> <@buttonGroup.kw>
<@buttonPrimary.kw name="accept" type="submit"> <@button.kw color="primary" name="accept" type="submit">
${msg("doYes")} ${msg("doYes")}
</@buttonPrimary.kw> </@button.kw>
<@buttonSecondary.kw name="cancel" type="submit"> <@button.kw color="secondary" name="cancel" type="submit">
${msg("doNo")} ${msg("doNo")}
</@buttonSecondary.kw> </@button.kw>
</div> </@buttonGroup.kw>
</form> </@form.kw>
</#if> </#if>
</@layout.registrationLayout> </@layout.registrationLayout>

View file

@ -1,9 +1,12 @@
<#import "template.ftl" as layout> <#import "template.ftl" as layout>
<#import "components/button/primary.ftl" as buttonPrimary> <#import "components/atoms/button.ftl" as button>
<#import "components/input/primary.ftl" as inputPrimary> <#import "components/atoms/button-group.ftl" as buttonGroup>
<#import "components/label/totp.ftl" as labelTotp> <#import "components/atoms/form.ftl" as form>
<#import "components/link/secondary.ftl" as linkSecondary> <#import "components/atoms/input.ftl" as input>
<#import "components/radio/primary.ftl" as radioPrimary> <#import "components/atoms/radio.ftl" as radio>
<#import "features/labels/totp.ftl" as totpLabel>
<#assign totpLabel><@totpLabel.kw /></#assign>
<@layout.registrationLayout <@layout.registrationLayout
displayMessage=!messagesPerField.existsError("totp") displayMessage=!messagesPerField.existsError("totp")
@ -13,45 +16,35 @@
<#if section="header"> <#if section="header">
${msg("doLogIn")} ${msg("doLogIn")}
<#elseif section="form"> <#elseif section="form">
<form <@form.kw action=url.loginAction method="post">
action="${url.loginAction}"
class="m-0 space-y-4"
method="post"
>
<#if otpLogin.userOtpCredentials?size gt 1> <#if otpLogin.userOtpCredentials?size gt 1>
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
<#list otpLogin.userOtpCredentials as otpCredential> <#list otpLogin.userOtpCredentials as otpCredential>
<@radioPrimary.kw <@radio.kw
checked=(otpCredential.id == otpLogin.selectedCredentialId) checked=(otpCredential.id == otpLogin.selectedCredentialId)
id="kw-otp-credential-${otpCredential?index}" id="kw-otp-credential-${otpCredential?index}"
label=otpCredential.userLabel
name="selectedCredentialId" name="selectedCredentialId"
tabIndex="${otpCredential?index}" tabindex=otpCredential?index
value="${otpCredential.id}" value=otpCredential.id
> />
${otpCredential.userLabel}
</@radioPrimary.kw>
</#list> </#list>
</div> </div>
</#if> </#if>
<div> <@input.kw
<@inputPrimary.kw autocomplete="off"
autocomplete="off" autofocus=true
autofocus=true invalid=messagesPerField.existsError("totp")
invalid=["totp"] label=totpLabel
name="otp" message=kcSanitize(messagesPerField.get("totp"))
type="text" name="otp"
> type="text"
<@labelTotp.kw /> />
</@inputPrimary.kw> <@buttonGroup.kw>
</div> <@button.kw color="primary" name="submitAction" type="submit">
<div class="pt-4">
<@buttonPrimary.kw
name="submitAction"
type="submit"
>
${msg("doLogIn")} ${msg("doLogIn")}
</@buttonPrimary.kw> </@button.kw>
</div> </@buttonGroup.kw>
</form> </@form.kw>
</#if> </#if>
</@layout.registrationLayout> </@layout.registrationLayout>

View file

@ -1,8 +1,12 @@
<#import "template.ftl" as layout> <#import "template.ftl" as layout>
<#import "components/button/primary.ftl" as buttonPrimary> <#import "components/atoms/button.ftl" as button>
<#import "components/input/primary.ftl" as inputPrimary> <#import "components/atoms/button-group.ftl" as buttonGroup>
<#import "components/label/username.ftl" as labelUsername> <#import "components/atoms/form.ftl" as form>
<#import "components/link/secondary.ftl" as linkSecondary> <#import "components/atoms/input.ftl" as input>
<#import "components/atoms/link.ftl" as link>
<#import "features/labels/username.ftl" as usernameLabel>
<#assign usernameLabel><@usernameLabel.kw /></#assign>
<@layout.registrationLayout <@layout.registrationLayout
displayInfo=true displayInfo=true
@ -13,30 +17,28 @@
<#if section="header"> <#if section="header">
${msg("emailForgotTitle")} ${msg("emailForgotTitle")}
<#elseif section="form"> <#elseif section="form">
<form action="${url.loginAction}" class="m-0 space-y-4" method="post"> <@form.kw action=url.loginAction method="post">
<div> <@input.kw
<@inputPrimary.kw autocomplete=realm.loginWithEmailAllowed?string("email", "username")
autocomplete=realm.loginWithEmailAllowed?string("email", "username") autofocus=true
autofocus=true invalid=messagesPerField.existsError("username")
invalid=["username"] label=usernameLabel
name="username" message=kcSanitize(messagesPerField.get("username"))
type="text" name="username"
value=(auth?has_content && auth.showUsername())?then(auth.attemptedUsername, '') type="text"
> value=(auth?has_content && auth.showUsername())?then(auth.attemptedUsername, '')
<@labelUsername.kw /> />
</@inputPrimary.kw> <@buttonGroup.kw>
</div> <@button.kw color="primary" type="submit">
<div>
<@buttonPrimary.kw type="submit">
${msg("doSubmit")} ${msg("doSubmit")}
</@buttonPrimary.kw> </@button.kw>
</div> </@buttonGroup.kw>
</form> </@form.kw>
<#elseif section="info"> <#elseif section="info">
${msg("emailInstruction")} ${msg("emailInstruction")}
<#elseif section="nav"> <#elseif section="nav">
<@linkSecondary.kw href=url.loginUrl> <@link.kw color="secondary" href=url.loginUrl size="small">
<span class="text-sm">${kcSanitize(msg("backToLogin"))?no_esc}</span> ${kcSanitize(msg("backToLogin"))?no_esc}
</@linkSecondary.kw> </@link.kw>
</#if> </#if>
</@layout.registrationLayout> </@layout.registrationLayout>

View file

@ -1,6 +1,9 @@
<#import "template.ftl" as layout> <#import "template.ftl" as layout>
<#import "components/button/primary.ftl" as buttonPrimary> <#import "components/atoms/button.ftl" as button>
<#import "components/input/primary.ftl" as inputPrimary> <#import "components/atoms/button-group.ftl" as buttonGroup>
<#import "components/atoms/checkbox.ftl" as checkbox>
<#import "components/atoms/form.ftl" as form>
<#import "components/atoms/input.ftl" as input>
<@layout.registrationLayout <@layout.registrationLayout
displayMessage=!messagesPerField.existsError("password", "password-confirm") displayMessage=!messagesPerField.existsError("password", "password-confirm")
@ -10,7 +13,7 @@
<#if section="header"> <#if section="header">
${msg("updatePasswordTitle")} ${msg("updatePasswordTitle")}
<#elseif section="form"> <#elseif section="form">
<form action="${url.loginAction}" class="m-0 space-y-4" method="post"> <@form.kw action=url.loginAction method="post">
<input <input
autocomplete="username" autocomplete="username"
name="username" name="username"
@ -18,36 +21,44 @@
value="${username}" value="${username}"
> >
<input autocomplete="current-password" name="password" type="hidden"> <input autocomplete="current-password" name="password" type="hidden">
<div> <@input.kw
<@inputPrimary.kw autocomplete="new-password"
autocomplete="new-password" autofocus=true
autofocus=true invalid=messagesPerField.existsError("password", "password-confirm")
invalid=["password", "password-confirm"] label=msg("passwordNew")
message=false name="password-new"
name="password-new" type="password"
type="password" />
> <@input.kw
${msg("passwordNew")} autocomplete="new-password"
</@inputPrimary.kw> invalid=messagesPerField.existsError("password-confirm")
</div> label=msg("passwordConfirm")
<div> message=kcSanitize(messagesPerField.get("password-confirm"))
<@inputPrimary.kw name="password-confirm"
autocomplete="new-password" type="password"
invalid=["password-confirm"] />
name="password-confirm" <#if isAppInitiatedAction??>
type="password" <@checkbox.kw
> checked=true
${msg("passwordConfirm")} label=msg("logoutOtherSessions")
</@inputPrimary.kw> name="logout-sessions"
</div> value="on"
/>
<#-- TODO isAppInitiatedAction --> </#if>
<@buttonGroup.kw>
<div> <#if isAppInitiatedAction??>
<@buttonPrimary.kw type="submit"> <@button.kw color="primary" type="submit">
${msg("doSubmit")} ${msg("doSubmit")}
</@buttonPrimary.kw> </@button.kw>
</div> <@button.kw color="secondary" name="cancel-aia" type="submit" value="true">
</form> ${msg("doCancel")}
</@button.kw>
<#else>
<@button.kw color="primary" type="submit">
${msg("doSubmit")}
</@button.kw>
</#if>
</@buttonGroup.kw>
</@form.kw>
</#if> </#if>
</@layout.registrationLayout> </@layout.registrationLayout>

View file

@ -1,6 +1,8 @@
<#import "template.ftl" as layout> <#import "template.ftl" as layout>
<#import "components/button/primary.ftl" as buttonPrimary> <#import "components/atoms/button.ftl" as button>
<#import "components/input/primary.ftl" as inputPrimary> <#import "components/atoms/button-group.ftl" as buttonGroup>
<#import "components/atoms/form.ftl" as form>
<#import "components/atoms/input.ftl" as input>
<@layout.registrationLayout <@layout.registrationLayout
displayMessage=!messagesPerField.existsError("email", "firstName", "lastName", "username") displayMessage=!messagesPerField.existsError("email", "firstName", "lastName", "username")
@ -10,62 +12,60 @@
<#if section="header"> <#if section="header">
${msg("loginProfileTitle")} ${msg("loginProfileTitle")}
<#elseif section="form"> <#elseif section="form">
<form action="${url.loginAction}" class="m-0 space-y-4" method="post"> <@form.kw action=url.loginAction method="post">
<#if user.editUsernameAllowed> <#if user.editUsernameAllowed>
<div> <@input.kw
<@inputPrimary.kw autocomplete="username"
autocomplete="username" autofocus=true
autofocus=true invalid=messagesPerField.existsError("username")
invalid=["username"] label=msg("username")
name="username" message=kcSanitize(messagesPerField.get("username"))
type="text" name="username"
value=(user.username)!'' type="text"
> value=(user.username)!''
${msg("username")} />
</@inputPrimary.kw>
</div>
</#if> </#if>
<div> <@input.kw
<@inputPrimary.kw autocomplete="email"
autocomplete="email" invalid=messagesPerField.existsError("email")
invalid=["email"] label=msg("email")
name="email" message=kcSanitize(messagesPerField.get("email"))
type="email" name="email"
value=(user.email)!'' type="email"
> value=(user.email)!''
${msg("email")} />
</@inputPrimary.kw> <@input.kw
</div> autocomplete="given-name"
<div> invalid=messagesPerField.existsError("firstName")
<@inputPrimary.kw label=msg("firstName")
autocomplete="given-name" message=kcSanitize(messagesPerField.get("firstName"))
invalid=["firstName"] name="firstName"
name="firstName" type="text"
type="text" value=(user.firstName)!''
value=(user.firstName)!'' />
> <@input.kw
${msg("firstName")} autocomplete="family-name"
</@inputPrimary.kw> invalid=messagesPerField.existsError("lastName")
</div> label=msg("lastName")
<div> message=kcSanitize(messagesPerField.get("lastName"))
<@inputPrimary.kw name="lastName"
autocomplete="family-name" type="text"
invalid=["lastName"] value=(user.lastName)!''
name="lastName" />
type="text" <@buttonGroup.kw>
value=(user.lastName)!'' <#if isAppInitiatedAction??>
> <@button.kw color="primary" type="submit">
${msg("lastName")} ${msg("doSubmit")}
</@inputPrimary.kw> </@button.kw>
</div> <@button.kw color="secondary" name="cancel-aia" type="submit" value="true">
${msg("doCancel")}
<#-- TODO isAppInitiatedAction --> </@button.kw>
<#else>
<div> <@button.kw color="primary" type="submit">
<@buttonPrimary.kw type="submit"> ${msg("doSubmit")}
${msg("doSubmit")} </@button.kw>
</@buttonPrimary.kw> </#if>
</div> </@buttonGroup.kw>
</form> </@form.kw>
</#if> </#if>
</@layout.registrationLayout> </@layout.registrationLayout>

View file

@ -1,10 +1,14 @@
<#import "template.ftl" as layout> <#import "template.ftl" as layout>
<#import "components/provider.ftl" as provider> <#import "components/atoms/button.ftl" as button>
<#import "components/button/primary.ftl" as buttonPrimary> <#import "components/atoms/button-group.ftl" as buttonGroup>
<#import "components/checkbox/primary.ftl" as checkboxPrimary> <#import "components/atoms/checkbox.ftl" as checkbox>
<#import "components/input/primary.ftl" as inputPrimary> <#import "components/atoms/form.ftl" as form>
<#import "components/label/username.ftl" as labelUsername> <#import "components/atoms/input.ftl" as input>
<#import "components/link/primary.ftl" as linkPrimary> <#import "components/atoms/link.ftl" as link>
<#import "components/molecules/identity-provider.ftl" as identityProvider>
<#import "features/labels/username.ftl" as usernameLabel>
<#assign usernameLabel><@usernameLabel.kw /></#assign>
<@layout.registrationLayout <@layout.registrationLayout
displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled?? displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled??
@ -16,9 +20,8 @@
${msg("loginAccountTitle")} ${msg("loginAccountTitle")}
<#elseif section="form"> <#elseif section="form">
<#if realm.password> <#if realm.password>
<form <@form.kw
action="${url.loginAction}" action=url.loginAction
class="m-0 space-y-4"
method="post" method="post"
onsubmit="login.disabled = true; return true;" onsubmit="login.disabled = true; return true;"
> >
@ -27,58 +30,56 @@
type="hidden" type="hidden"
value="<#if auth.selectedCredential?has_content>${auth.selectedCredential}</#if>" value="<#if auth.selectedCredential?has_content>${auth.selectedCredential}</#if>"
> >
<div> <@input.kw
<@inputPrimary.kw autocomplete=realm.loginWithEmailAllowed?string("email", "username")
autocomplete=realm.loginWithEmailAllowed?string("email", "username") autofocus=true
autofocus=true disabled=usernameEditDisabled??
disabled=usernameEditDisabled?? invalid=messagesPerField.existsError("username", "password")
invalid=["username", "password"] label=usernameLabel
name="username" message=kcSanitize(messagesPerField.getFirstError("username", "password"))
type="text" name="username"
value=(login.username)!'' type="text"
> value=(login.username)!''
<@labelUsername.kw /> />
</@inputPrimary.kw> <@input.kw
</div> invalid=messagesPerField.existsError("username", "password")
<div> label=msg("password")
<@inputPrimary.kw name="password"
invalid=["username", "password"] type="password"
message=false />
name="password" <#if realm.rememberMe && !usernameEditDisabled?? || realm.resetPasswordAllowed>
type="password" <div class="flex items-center justify-between">
> <#if realm.rememberMe && !usernameEditDisabled??>
${msg("password")} <@checkbox.kw
</@inputPrimary.kw> checked=login.rememberMe??
</div> label=msg("rememberMe")
<div class="flex items-center justify-between"> name="rememberMe"
<#if realm.rememberMe && !usernameEditDisabled??> />
<@checkboxPrimary.kw checked=login.rememberMe?? name="rememberMe"> </#if>
${msg("rememberMe")} <#if realm.resetPasswordAllowed>
</@checkboxPrimary.kw> <@link.kw color="primary" href=url.loginResetCredentialsUrl size="small">
</#if> ${msg("doForgotPassword")}
<#if realm.resetPasswordAllowed> </@link.kw>
<@linkPrimary.kw href=url.loginResetCredentialsUrl> </#if>
<span class="text-sm">${msg("doForgotPassword")}</span> </div>
</@linkPrimary.kw> </#if>
</#if> <@buttonGroup.kw>
</div> <@button.kw color="primary" name="login" type="submit">
<div class="pt-4">
<@buttonPrimary.kw name="login" type="submit">
${msg("doLogIn")} ${msg("doLogIn")}
</@buttonPrimary.kw> </@button.kw>
</div> </@buttonGroup.kw>
</form> </@form.kw>
</#if> </#if>
<#if realm.password && social.providers??> <#if realm.password && social.providers??>
<@provider.kw /> <@identityProvider.kw providers=social.providers />
</#if> </#if>
<#elseif section="info"> <#elseif section="info">
<#if realm.password && realm.registrationAllowed && !registrationDisabled??> <#if realm.password && realm.registrationAllowed && !registrationDisabled??>
<div class="text-center"> <div class="text-center">
${msg("noAccount")} ${msg("noAccount")}
<@linkPrimary.kw href=url.registrationUrl> <@link.kw color="primary" href=url.registrationUrl>
${msg("doRegister")} ${msg("doRegister")}
</@linkPrimary.kw> </@link.kw>
</div> </div>
</#if> </#if>
</#if> </#if>

View file

@ -1,23 +1,24 @@
<#import "template.ftl" as layout> <#import "template.ftl" as layout>
<#import "components/button/primary.ftl" as buttonPrimary> <#import "components/atoms/button.ftl" as button>
<#import "components/link/secondary.ftl" as linkSecondary> <#import "components/atoms/form.ftl" as form>
<#import "components/atoms/link.ftl" as link>
<@layout.registrationLayout; section> <@layout.registrationLayout; section>
<#if section = "header"> <#if section="header">
${msg("logoutConfirmTitle")} ${msg("logoutConfirmTitle")}
<#elseif section = "form"> <#elseif section="form">
<p>${msg("logoutConfirmHeader")}</p> <p>${msg("logoutConfirmHeader")}</p>
<form action="${url.logoutConfirmAction}" class="m-0 space-y-4" method="post"> <@form.kw action=url.logoutConfirmAction method="post">
<input name="session_code" type="hidden" value="${logoutConfirm.code}"> <input name="session_code" type="hidden" value="${logoutConfirm.code}">
<@buttonPrimary.kw name="confirmLogout" type="submit" value="${msg('doLogout')}"> <@button.kw color="primary" name="confirmLogout" type="submit" value=msg('doLogout')>
${msg("doLogout")} ${msg("doLogout")}
</@buttonPrimary.kw> </@button.kw>
</form> </@form.kw>
<#if !logoutConfirm.skipLink> <#if !logoutConfirm.skipLink>
<#if (client.baseUrl)?has_content> <#if (client.baseUrl)?has_content>
<@linkSecondary.kw href=client.baseUrl> <@link.kw color="secondary" href=client.baseUrl size="small">
<span class="text-sm">${kcSanitize(msg("backToApplication"))?no_esc}</span> ${kcSanitize(msg("backToApplication"))?no_esc}
</@linkSecondary.kw> </@link.kw>
</#if> </#if>
</#if> </#if>
</#if> </#if>

View file

@ -1,7 +1,9 @@
<#import "template.ftl" as layout> <#import "template.ftl" as layout>
<#import "components/button/primary.ftl" as buttonPrimary> <#import "components/atoms/button.ftl" as button>
<#import "components/input/primary.ftl" as inputPrimary> <#import "components/atoms/button-group.ftl" as buttonGroup>
<#import "components/link/secondary.ftl" as linkSecondary> <#import "components/atoms/form.ftl" as form>
<#import "components/atoms/input.ftl" as input>
<#import "components/atoms/link.ftl" as link>
<@layout.registrationLayout <@layout.registrationLayout
displayMessage=!messagesPerField.existsError("firstName", "lastName", "email", "username", "password", "password-confirm") displayMessage=!messagesPerField.existsError("firstName", "lastName", "email", "username", "password", "password-confirm")
@ -11,90 +13,76 @@
<#if section="header"> <#if section="header">
${msg("registerTitle")} ${msg("registerTitle")}
<#elseif section="form"> <#elseif section="form">
<form action="${url.registrationAction}" class="m-0 space-y-4" method="post"> <@form.kw action=url.registrationAction method="post">
<div> <@input.kw
<@inputPrimary.kw autocomplete="given-name"
autocomplete="given-name" autofocus=true
autofocus=true invalid=messagesPerField.existsError("firstName")
invalid=["firstName"] label=msg("firstName")
name="firstName" message=kcSanitize(messagesPerField.get("firstName"))
type="text" name="firstName"
value=(register.formData.firstName)!'' type="text"
> value=(register.formData.firstName)!''
${msg("firstName")} />
</@inputPrimary.kw> <@input.kw
</div> autocomplete="family-name"
<div> invalid=messagesPerField.existsError("lastName")
<@inputPrimary.kw label=msg("lastName")
autocomplete="family-name" message=kcSanitize(messagesPerField.get("lastName"))
invalid=["lastName"] name="lastName"
name="lastName" type="text"
type="text" value=(register.formData.lastName)!''
value=(register.formData.lastName)!'' />
> <@input.kw
${msg("lastName")} autocomplete="email"
</@inputPrimary.kw> invalid=messagesPerField.existsError("email")
</div> label=msg("email")
<div> message=kcSanitize(messagesPerField.get("email"))
<@inputPrimary.kw name="email"
autocomplete="email" type="email"
invalid=["email"] value=(register.formData.email)!''
name="email" />
type="email"
value=(register.formData.email)!''
>
${msg("email")}
</@inputPrimary.kw>
</div>
<#if !realm.registrationEmailAsUsername> <#if !realm.registrationEmailAsUsername>
<div> <@input.kw
<@inputPrimary.kw autocomplete="username"
autocomplete="username" invalid=messagesPerField.existsError("username")
invalid=["username"] label=msg("username")
name="username" message=kcSanitize(messagesPerField.get("username"))
type="text" name="username"
value=(register.formData.username)!'' type="text"
> value=(register.formData.username)!''
${msg("username")} />
</@inputPrimary.kw>
</div>
</#if> </#if>
<#if passwordRequired??> <#if passwordRequired??>
<div> <@input.kw
<@inputPrimary.kw autocomplete="new-password"
autocomplete="new-password" invalid=messagesPerField.existsError("password", "password-confirm")
invalid=["password", "password-confirm"] label=msg("password")
name="password" message=kcSanitize(messagesPerField.getFirstError("password", "password-confirm"))
type="password" name="password"
> type="password"
${msg("password")} />
</@inputPrimary.kw> <@input.kw
</div> autocomplete="new-password"
<div> invalid=messagesPerField.existsError("password-confirm")
<@inputPrimary.kw label=msg("passwordConfirm")
autocomplete="new-password" message=kcSanitize(messagesPerField.get("password-confirm"))
invalid=["password-confirm"] name="password-confirm"
name="password-confirm" type="password"
type="password" />
>
${msg("passwordConfirm")}
</@inputPrimary.kw>
</div>
</#if> </#if>
<#if recaptchaRequired??> <#if recaptchaRequired??>
<div> <div class="g-recaptcha" data-sitekey="${recaptchaSiteKey}" data-size="compact"></div>
<div class="g-recaptcha" data-sitekey="${recaptchaSiteKey}" data-size="compact"></div>
</div>
</#if> </#if>
<div> <@buttonGroup.kw>
<@buttonPrimary.kw type="submit"> <@button.kw color="primary" type="submit">
${msg("doRegister")} ${msg("doRegister")}
</@buttonPrimary.kw> </@button.kw>
</div> </@buttonGroup.kw>
</form> </@form.kw>
<#elseif section="nav"> <#elseif section="nav">
<@linkSecondary.kw href=url.loginUrl> <@link.kw color="secondary" href=url.loginUrl size="small">
<span class="text-sm">${kcSanitize(msg("backToLogin"))?no_esc}</span> ${kcSanitize(msg("backToLogin"))?no_esc}
</@linkSecondary.kw> </@link.kw>
</#if> </#if>
</@layout.registrationLayout> </@layout.registrationLayout>

File diff suppressed because one or more lines are too long

View file

@ -1,67 +1,83 @@
<#import "components/document.ftl" as document> <#import "document.ftl" as document>
<#import "components/layout/alerts.ftl" as alerts> <#import "components/atoms/alert.ftl" as alert>
<#import "components/layout/another-way.ftl" as anotherWay> <#import "components/atoms/body.ftl" as body>
<#import "components/layout/card.ftl" as card> <#import "components/atoms/button.ftl" as button>
<#import "components/layout/card-footer.ftl" as cardFooter> <#import "components/atoms/card.ftl" as card>
<#import "components/layout/card-header.ftl" as cardHeader> <#import "components/atoms/container.ftl" as container>
<#import "components/layout/card-main.ftl" as cardMain> <#import "components/atoms/heading.ftl" as heading>
<#import "components/layout/container.ftl" as container> <#import "components/atoms/logo.ftl" as logo>
<#import "components/layout/locales.ftl" as locales> <#import "components/atoms/nav.ftl" as nav>
<#import "components/layout/nav.ftl" as nav> <#import "components/molecules/locale-provider.ftl" as localeProvider>
<#import "components/layout/required-fields.ftl" as requiredFields> <#import "components/molecules/username.ftl" as username>
<#import "components/layout/title.ftl" as title>
<#import "components/layout/subtitle.ftl" as subtitle>
<#import "components/layout/username.ftl" as username>
<#macro <#macro
registrationLayout registrationLayout
displayInfo=false displayInfo=false
displayMessage=true displayMessage=true
displayRequiredFields=false displayRequiredFields=false
script=""
showAnotherWayIfPresent=true showAnotherWayIfPresent=true
> >
<#assign cardHeader>
<@logo.kw>
${kcSanitize(msg("loginTitleHtml", (realm.displayNameHtml!"")))?no_esc}
</@logo.kw>
<#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())>
<@heading.kw>
<#nested "header">
</@heading.kw>
<#else>
<#nested "show-username">
<@username.kw
linkHref=url.loginRestartFlowUrl
linkTitle=msg("restartLoginTooltip")
name=auth.attemptedUsername
/>
</#if>
</#assign>
<#assign cardContent>
<#if displayMessage && message?has_content && (message.type != "warning" || !isAppInitiatedAction??)>
<@alert.kw color=message.type>
${kcSanitize(message.summary)?no_esc}
</@alert.kw>
</#if>
<#nested "form">
<#if displayRequiredFields>
<p class="text-secondary-600 text-sm">
* ${msg("requiredFields")}
</p>
</#if>
<#if auth?has_content && auth.showTryAnotherWayLink() && showAnotherWayIfPresent>
<form action="${url.loginAction}" method="post">
<input name="tryAnotherWay" type="hidden" value="on" />
<@button.kw color="primary" type="submit">
${msg("doTryAnotherWay")}
</@button.kw>
</form>
</#if>
</#assign>
<#assign cardFooter>
<#if displayInfo>
<#nested "info">
</#if>
</#assign>
<html> <html>
<head> <head>
<@document.kw /> <@document.kw script=script />
</head> </head>
<body> <@body.kw>
<@container.kw> <@container.kw>
<@card.kw> <@card.kw content=cardContent footer=cardFooter header=cardHeader />
<@cardHeader.kw>
<@title.kw />
<#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())>
<@subtitle.kw>
<#nested "header">
</@subtitle.kw>
<#else>
<@username.kw />
</#if>
</@cardHeader.kw>
<@cardMain.kw>
<#if displayMessage && message?has_content && (message.type != "warning" || !isAppInitiatedAction??)>
<@alerts.kw />
</#if>
<#nested "form">
<#if displayRequiredFields>
<@requiredFields.kw />
</#if>
<#if auth?has_content && auth.showTryAnotherWayLink() && showAnotherWayIfPresent>
<@anotherWay.kw />
</#if>
</@cardMain.kw>
<#if displayInfo>
<@cardFooter.kw>
<#nested "info">
</@cardFooter.kw>
</#if>
</@card.kw>
<@nav.kw> <@nav.kw>
<#nested "nav"> <#nested "nav">
<#if realm.internationalizationEnabled && locale.supported?size gt 1> <#if realm.internationalizationEnabled && locale.supported?size gt 1>
<@locales.kw /> <@localeProvider.kw currentLocale=locale.current locales=locale.supported />
</#if> </#if>
</@nav.kw> </@nav.kw>
</@container.kw> </@container.kw>
</body> </@body.kw>
</html> </html>
</#macro> </#macro>