Skip to content

Commit

Permalink
New Login form design (#551)
Browse files Browse the repository at this point in the history
* New Login form design

* Finish new login form to match original functionality
  • Loading branch information
StevenWeathers committed Apr 1, 2024
1 parent 81bb063 commit f829ee0
Show file tree
Hide file tree
Showing 10 changed files with 414 additions and 386 deletions.
366 changes: 366 additions & 0 deletions ui/src/components/auth/LoginForm.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,366 @@
<script lang="ts">
import { AppConfig, appRoutes } from '../../config';
import { user } from '../../stores';
import LL from '../../i18n/i18n-svelte';
import SolidButton from '../global/SolidButton.svelte';
import TextInput from '../global/TextInput.svelte';
export let registerLink = '';
export let targetPage = appRoutes.landing;
export let router;
export let xfetch = async (url, ...options) => {};
export let eventTag = () => {};
export let notifications = () => {};
declare global {
interface Window {
setTheme: Function;
}
}
const { AllowRegistration, LdapEnabled } = AppConfig;
const authEndpoint = LdapEnabled ? '/api/auth/ldap' : '/api/auth';
let email = '';
let password = '';
let forgotPassword = false;
let resetEmail = '';
let mfaRequired = false;
let mfaUser = null;
let mfaSessionId = null;
let mfaToken = '';
function toggleForgotPassword() {
forgotPassword = !forgotPassword;
eventTag(
'forgot_password_toggle',
'engagement',
`forgot: ${forgotPassword}`,
);
}
function handleLoginSubmit(e) {
e.preventDefault();
const body = {
email,
password,
};
xfetch(authEndpoint, { body, skip401Redirect: true })
.then((res: any) => res.json())
.then(function (result) {
const u = result.data.user;
const newUser = {
id: u.id,
name: u.name,
email: u.email,
rank: u.rank,
locale: u.locale,
notificationsEnabled: u.notificationsEnabled,
subscribed: result.data.subscribed,
};
if (result.data.mfaRequired) {
mfaRequired = true;
mfaUser = newUser;
mfaSessionId = result.data.sessionId;
} else {
user.create(newUser);
if (u.theme !== 'auto') {
localStorage.setItem('theme', u.theme);
window.setTheme();
}
eventTag('login', 'engagement', 'success', () => {
// setupI18n({
// withLocale: newUser.locale,
// })
router.route(targetPage, true);
});
}
})
.catch(function () {
notifications.danger(
$LL.authError({
friendly: AppConfig.FriendlyUIVerbs,
}),
);
eventTag('login', 'engagement', 'failure');
});
}
function authMfa(e) {
e.preventDefault();
const body = {
passcode: mfaToken,
sessionId: mfaSessionId,
};
xfetch('/api/auth/mfa', { body, skip401Redirect: true })
.then(res => res.json())
.then(function () {
user.create(mfaUser);
eventTag('login_mfa', 'engagement', 'success', () => {
// setupI18n({
// withLocale: mfaUser.locale,
// })
router.route(targetPage, true);
});
})
.catch(function () {
notifications.danger($LL.mfaAuthError());
eventTag('login_mfa', 'engagement', 'failure');
});
}
function sendPasswordReset(e) {
e.preventDefault();
const body = {
email: resetEmail,
};
xfetch('/api/auth/forgot-password', { body })
.then(function () {
notifications.success(
$LL.sendResetPasswordSuccess({
email: resetEmail,
}),
2000,
);
forgotPassword = !forgotPassword;
eventTag('forgot_password', 'engagement', 'success');
})
.catch(function () {
notifications.danger($LL.sendResetPasswordError());
eventTag('forgot_password', 'engagement', 'failure');
});
}
</script>

{#if !forgotPassword && !mfaRequired}
<form
on:submit="{handleLoginSubmit}"
class="space-y-6"
name="login"
id="login"
>
<div>
<label
for="email"
class="block text-sm font-medium text-gray-700 dark:text-white"
>{$LL.email()}</label
>
<div class="mt-1">
<input
id="email"
type="text"
data-testid="username"
placeholder="{$LL.enterYourEmail()}"
required
class="block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 dark:bg-gray-800 dark:border-gray-600 dark:text-white dark:placeholder-gray-300 dark:focus:border-indigo-400 dark:focus:ring-indigo-400 sm:text-sm"
bind:value="{email}"
/>
</div>
</div>
<div>
<label
for="password"
class="block text-sm font-medium text-gray-700 dark:text-white"
>{$LL.password()}</label
>
<div class="mt-1">
<input
id="password"
name="password"
type="password"
data-testid="password"
autocomplete="current-password"
required
class="block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 dark:bg-gray-800 dark:border-gray-600 dark:text-white dark:placeholder-gray-300 dark:focus:border-indigo-400 dark:focus:ring-indigo-400 sm:text-sm"
placeholder="{$LL.passwordPlaceholder()}"
bind:value="{password}"
/>
</div>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center">
<!-- <input id="remember_me" name="remember_me" type="checkbox"-->
<!-- class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 dark:text-white dark:border-gray-600 dark:focus:ring-indigo-400 disabled:cursor-wait disabled:opacity-50">-->
<!-- <label for="remember_me" class="ml-2 block text-sm text-gray-900 dark:text-white">Remember-->
<!-- me</label>-->
</div>
<div class="text-sm">
{#if !LdapEnabled}
<a
class="font-medium text-indigo-400 hover:text-indigo-500 cursor-pointer"
on:click="{toggleForgotPassword}"
>
{$LL.forgotPasswordCheckboxLabel()}
</a>
{/if}
</div>
</div>
<div>
<button
data-testid="login"
type="submit"
class="group relative flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-indigo-700 dark:border-transparent dark:hover:bg-indigo-600 dark:focus:ring-indigo-400 dark:focus:ring-offset-2 disabled:cursor-wait disabled:opacity-50"
>
<span class="absolute inset-y-0 left-0 flex items-center pl-3">
<svg
class="h-5 w-5 text-indigo-500 group-hover:text-indigo-400"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd"></path>
</svg>
</span>
{$LL.login()}
</button>
</div>
</form>
<!-- <div class="mt-6">-->
<!-- <div class="relative">-->
<!-- <div class="absolute inset-0 flex items-center">-->
<!-- <div class="w-full border-t border-gray-300"></div>-->
<!-- </div>-->
<!-- <div class="relative flex justify-center text-sm">-->
<!-- <span-->
<!-- class="bg-white dark:bg-gray-700 px-2 text-gray-500 dark:text-white"-->
<!-- >Or continue with</span-->
<!-- >-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="mt-6 grid grid-cols-2 gap-3">-->
<!-- <button-->
<!-- class="inline-flex w-full items-center justify-center rounded-md border border-gray-300 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-500 dark:text-white shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 disabled:cursor-wait disabled:opacity-50"-->
<!-- >-->
<!-- <span class="sr-only">Sign in with Google</span>-->
<!-- <img-->
<!-- class="w-6 h-6"-->
<!-- src="https://www.svgrepo.com/show/475656/google-color.svg"-->
<!-- loading="lazy"-->
<!-- alt="google logo"-->
<!-- />-->
<!-- &lt;!&ndash; <svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">&ndash;&gt;-->
<!-- &lt;!&ndash; <clipPath id="p.0">&ndash;&gt;-->
<!-- &lt;!&ndash; <path d="m0 0l20.0 0l0 20.0l-20.0 0l0 -20.0z" clip-rule="nonzero"></path>&ndash;&gt;-->
<!-- &lt;!&ndash; </clipPath>&ndash;&gt;-->
<!-- &lt;!&ndash; <g clip-path="url(#p.0)">&ndash;&gt;-->
<!-- &lt;!&ndash; <path fill="currentColor" fill-opacity="0.0" d="m0 0l20.0 0l0 20.0l-20.0 0z"&ndash;&gt;-->
<!-- &lt;!&ndash; fill-rule="evenodd"></path>&ndash;&gt;-->
<!-- &lt;!&ndash; <path fill="currentColor"&ndash;&gt;-->
<!-- &lt;!&ndash; d="m19.850197 8.270351c0.8574047 4.880001 -1.987587 9.65214 -6.6881847 11.218641c-4.700598 1.5665016 -9.83958 -0.5449295 -12.08104 -4.963685c-2.2414603 -4.4187555 -0.909603 -9.81259 3.1310139 -12.6801605c4.040616 -2.867571 9.571754 -2.3443127 13.002944 1.2301085l-2.8127813 2.7000687l0 0c-2.0935059 -2.1808972 -5.468274 -2.500158 -7.933616 -0.75053835c-2.4653416 1.74962 -3.277961 5.040613 -1.9103565 7.7366734c1.3676047 2.6960592 4.5031037 3.9843292 7.3711267 3.0285425c2.868022 -0.95578575 4.6038647 -3.8674583 4.0807285 -6.844941z"&ndash;&gt;-->
<!-- &lt;!&ndash; fill-rule="evenodd"></path>&ndash;&gt;-->
<!-- &lt;!&ndash; <path fill="currentColor" d="m10.000263 8.268785l9.847767 0l0 3.496233l-9.847767 0z"&ndash;&gt;-->
<!-- &lt;!&ndash; fill-rule="evenodd"></path>&ndash;&gt;-->
<!-- &lt;!&ndash; </g>&ndash;&gt;-->
<!-- &lt;!&ndash; </svg>&ndash;&gt;-->
<!-- </button>-->
<!-- <button-->
<!-- class="inline-flex w-full justify-center rounded-md border border-gray-300 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-500 dark:text-white shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 disabled:cursor-wait disabled:opacity-50"-->
<!-- >-->
<!-- <span class="sr-only">Sign in with GitHub</span>-->
<!-- <svg-->
<!-- class="h-6 w-6"-->
<!-- fill="currentColor"-->
<!-- xmlns="http://www.w3.org/2000/svg"-->
<!-- width="800px"-->
<!-- height="800px"-->
<!-- viewBox="0 0 32 32"-->
<!-- version="1.1"-->
<!-- >-->
<!-- <path-->
<!-- d="M16 1.375c-8.282 0-14.996 6.714-14.996 14.996 0 6.585 4.245 12.18 10.148 14.195l0.106 0.031c0.750 0.141 1.025-0.322 1.025-0.721 0-0.356-0.012-1.3-0.019-2.549-4.171 0.905-5.051-2.012-5.051-2.012-0.288-0.925-0.878-1.685-1.653-2.184l-0.016-0.009c-1.358-0.93 0.105-0.911 0.105-0.911 0.987 0.139 1.814 0.718 2.289 1.53l0.008 0.015c0.554 0.995 1.6 1.657 2.801 1.657 0.576 0 1.116-0.152 1.582-0.419l-0.016 0.008c0.072-0.791 0.421-1.489 0.949-2.005l0.001-0.001c-3.33-0.375-6.831-1.665-6.831-7.41-0-0.027-0.001-0.058-0.001-0.089 0-1.521 0.587-2.905 1.547-3.938l-0.003 0.004c-0.203-0.542-0.321-1.168-0.321-1.821 0-0.777 0.166-1.516 0.465-2.182l-0.014 0.034s1.256-0.402 4.124 1.537c1.124-0.321 2.415-0.506 3.749-0.506s2.625 0.185 3.849 0.53l-0.1-0.024c2.849-1.939 4.105-1.537 4.105-1.537 0.285 0.642 0.451 1.39 0.451 2.177 0 0.642-0.11 1.258-0.313 1.83l0.012-0.038c0.953 1.032 1.538 2.416 1.538 3.937 0 0.031-0 0.061-0.001 0.091l0-0.005c0 5.761-3.505 7.029-6.842 7.398 0.632 0.647 1.022 1.532 1.022 2.509 0 0.093-0.004 0.186-0.011 0.278l0.001-0.012c0 2.007-0.019 3.619-0.019 4.106 0 0.394 0.262 0.862 1.031 0.712 6.028-2.029 10.292-7.629 10.292-14.226 0-8.272-6.706-14.977-14.977-14.977-0.006 0-0.013 0-0.019 0h0.001z"-->
<!-- ></path>-->
<!-- </svg>-->
<!-- </button>-->
<!-- </div>-->
<!-- </div>-->
{#if registerLink !== ''}
<div class="m-auto mt-6 w-fit md:mt-8">
<span class="m-auto dark:text-gray-400"
>{$LL.createAccountTagline()}
<a
class="font-semibold text-indigo-600 dark:text-indigo-100"
href="{registerLink}">{$LL.createAccount()}</a
>
</span>
</div>
{/if}
{/if}

{#if forgotPassword}
<form on:submit="{sendPasswordReset}" class="space-y-6" name="resetPassword">
<div>
<label
for="yourResetEmail"
class="block text-sm font-medium text-gray-700 dark:text-white"
>{$LL.email()}</label
>
<div class="mt-1">
<input
data-testid="resetemail"
bind:value="{resetEmail}"
placeholder="{$LL.enterYourEmail()}"
id="yourResetEmail"
name="yourResetEmail"
type="email"
required
class="block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 dark:bg-gray-800 dark:border-gray-600 dark:text-white dark:placeholder-gray-300 dark:focus:border-indigo-400 dark:focus:ring-indigo-400 sm:text-sm"
/>
</div>
</div>

<div class="text-right">
<button
type="button"
class="inline-block align-baseline font-bold text-sm
text-blue-500 hover:text-blue-800 me-4"
on:click="{toggleForgotPassword}"
>
{$LL.cancel()}
</button>
<SolidButton type="submit">
{$LL.sendResetEmail()}
</SolidButton>
</div>
</form>
{/if}

{#if mfaRequired}
<form on:submit="{authMfa}" class="space-y-6" name="authMfa">
<div
class="font-semibold font-rajdhani uppercase text-2xl md:text-3xl mb-2 md:mb-6
md:leading-tight text-center dark:text-white"
>
{$LL.login()}
</div>
<div class="mb-4">
<label
class="block text-gray-700 dark:text-gray-400 font-bold mb-2"
for="mfaToken"
>
{$LL.mfaTokenLabel()}
</label>
<TextInput
bind:value="{mfaToken}"
placeholder="{$LL.mfaTokenPlaceholder()}"
id="mfaToken"
name="mfaToken"
required
/>
</div>

<div class="text-right">
<SolidButton type="submit">
{$LL.login()}
</SolidButton>
</div>
</form>
{/if}
9 changes: 1 addition & 8 deletions ui/src/i18n/de/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ const de: Translation = {
conciseVotingResults: 'Concise Voting Results',
confirmDeleteRetro: 'Are you sure you want to delete this retrospective?',
createAccount: 'Konto erstellen',
createAccountTagline: `Don't have an account?`,
createAlertError: 'Error encountered creating alert',
createAlertSuccess: 'Alert created successfully',
createARetro: 'Create a Retro',
Expand Down Expand Up @@ -345,10 +346,6 @@ const de: Translation = {
promoteUserError: 'Error encountered promoting user',
questions: 'Questions',
register: 'Register',
registerForRetro:
'or {registerOpen}Register{registerClose} to join the Retro',
registerForStoryboard:
'or {registerOpen}Register{registerClose} to join the Storyboard',
reloadingRetro: 'Oops, reloading Retro...',
reloadingStoryboard: 'Oops, reloading Storyboard...',
remove: 'Entfernen',
Expand Down Expand Up @@ -599,10 +596,6 @@ const de: Translation = {
'Create an Account {optionalOpen}(optional){optionalClose}',
sendResetPasswordSuccess: 'Password reset instructions sent to {email}',
sendResetPasswordError: 'Error encountered attempting to send password reset',
registerForBattle: {
true: 'or {registerOpen}Register{registerClose} to join the Game',
false: 'or {registerOpen}Register{registerClose} to join the Battle',
},
chooseCountryPlaceholder: 'Choose your country (optional)',
locale: 'Locale',
companyPlaceholder: 'Enter your company (optional)',
Expand Down
Loading

0 comments on commit f829ee0

Please sign in to comment.