Skip to content

Commit

Permalink
Confirm sign up needs to tell the user where the code has gone to (#910)
Browse files Browse the repository at this point in the history
* Added new codedelivery in xstate

* Fixed authenticator

* Updated text, added in code if code delivery is empty

* Added changeset

* Fixed test with text

Co-authored-by: Erik Hanchett <ehhanche@amazon.com>
  • Loading branch information
ErikCH and Erik Hanchett authored Dec 1, 2021
1 parent d303f61 commit 96830f6
Show file tree
Hide file tree
Showing 12 changed files with 165 additions and 82 deletions.
8 changes: 8 additions & 0 deletions .changeset/eleven-wolves-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@aws-amplify/ui-angular': patch
'@aws-amplify/ui-react': patch
'@aws-amplify/ui': patch
'@aws-amplify/ui-vue': patch
---

Added text to confirm sign up page, so user knows where code was delivered.
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@
[disabled]="context.isPending"
>
<amplify-slot name="confirm-sign-up-header" [context]="context">
<h3 class="amplify-heading">{{ this.headerText }}</h3>
<h3 class="amplify-heading" style="font-size: 1.5rem">
{{ confirmSignUpHeading }}
</h3>
</amplify-slot>
<span style="margin-bottom: 1rem">
{{ subtitleText }}
</span>
<amplify-form-field
name="confirmation_code"
autocomplete="one-time-code"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,27 @@ export class ConfirmSignUpComponent {
return this.authenticator.slotContext;
}

public get confirmSignUpHeading(): string {
const { codeDeliveryDetails: { DeliveryMedium } = {} } = this.authenticator;
return DeliveryMedium === 'EMAIL'
? translate('We Emailed You')
: DeliveryMedium === 'SMS'
? translate('We Texted You')
: translate('We Sent A Code');
}

public get subtitleText(): string {
const { codeDeliveryDetails: { DeliveryMedium, Destination } = {} } =
this.authenticator;
return DeliveryMedium === 'EMAIL'
? `Your code is on the way. To log in, enter the code we emailed to ${Destination}. It may take a minute to arrive.`
: DeliveryMedium === 'SMS'
? `Your code is on the way. To log in, enter the code we texted to ${Destination}. It may take a minute to arrive.`
: translate(
`Your code is on the way. To log in, enter the code we sent you. It may take a minute to arrive.`
);
}

onInput(event: Event) {
event.preventDefault();
const { name, value } = <HTMLInputElement>event.target;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ export class AuthenticatorService implements OnDestroy {
return this._facade?.validationErrors;
}

public get codeDeliveryDetails() {
return this._facade?.codeDeliveryDetails;
}

/**
* Service facades
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Feature: Sign Up with Username
And I type my "email" with status "UNCONFIRMED"
And I type a new "preferred username"
And I click the "Create Account" button
Then I see "Confirm Sign Up"
Then I see "Your code is on the way"
And I see "Confirmation Code"

@angular @react @vue
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { translate } from '@aws-amplify/ui';

import { useAuthenticator } from '../..';
import { Button, Flex, Heading } from '../../..';
import { Button, Flex, Heading, Text } from '../../..';
import { isInputOrSelectElement, isInputElement } from '../../../helpers/utils';

import {
Expand All @@ -11,7 +11,13 @@ import {
} from '../shared';

export function ConfirmSignUp() {
const { isPending, resendCode, submitForm, updateForm } = useAuthenticator();
const {
isPending,
resendCode,
submitForm,
updateForm,
codeDeliveryDetails: { DeliveryMedium, Destination } = {},
} = useAuthenticator();

const handleChange = (event: React.FormEvent<HTMLFormElement>) => {
if (isInputOrSelectElement(event.target)) {
Expand All @@ -38,6 +44,21 @@ export function ConfirmSignUp() {
placeholder: translate('Enter your code'),
};

const confirmSignUpHeading =
DeliveryMedium === 'EMAIL'
? translate('We Emailed You')
: DeliveryMedium === 'SMS'
? translate('We Texted You')
: translate('We Sent A Code');
const subtitleText =
DeliveryMedium === 'EMAIL'
? `Your code is on the way. To log in, enter the code we emailed to ${Destination}. It may take a minute to arrive.`
: DeliveryMedium === 'SMS'
? `Your code is on the way. To log in, enter the code we texted to ${Destination}. It may take a minute to arrive.`
: translate(
`Your code is on the way. To log in, enter the code we sent you. It may take a minute to arrive.`
);

return (
// TODO Automatically add these namespaces again from `useAmplify`
<form
Expand All @@ -48,9 +69,12 @@ export function ConfirmSignUp() {
onSubmit={handleSubmit}
>
<Flex direction="column">
<Heading level={3}>{translate('Confirm Sign Up')}</Heading>
<Heading level={3} style={{ fontSize: '1.5rem' }}>
{confirmSignUpHeading}
</Heading>

<Flex direction="column">
<Text style={{ marginBottom: '1rem' }}>{subtitleText}</Text>
<ConfirmationCodeInput {...confirmationCodeInputProps} />

<RemoteErrorMessage />
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/src/helpers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ export const getServiceContextFacade = (state: AuthMachineState) => {
const actorContext = getActorContext(state) as ActorContextWithForms;
const error = actorContext?.remoteError;
const validationErrors = { ...actorContext?.validationError };
const codeDeliveryDetails = actorContext?.codeDeliveryDetails;
const hasValidationErrors = Object.keys(validationErrors).length > 0;
const isPending =
state.hasTag('pending') || getActorState(state)?.hasTag('pending');
Expand Down Expand Up @@ -265,6 +266,7 @@ export const getServiceContextFacade = (state: AuthMachineState) => {
route,
user,
validationErrors,
codeDeliveryDetails,
};
};

Expand Down
4 changes: 4 additions & 0 deletions packages/ui/src/machines/authenticator/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ export const setUsername = assign({
username: (context: ActorContextWithForms, _) => context.formValues.username,
});

export const setCodeDeliveryDetails = assign({
codeDeliveryDetails: (_, event: AuthEvent) => event.data.codeDeliveryDetails,
});

export const setUsernameAuthAttributes = assign({
authAttributes: (context: ActorContextWithForms, _) => ({
username: context.formValues.username,
Expand Down
13 changes: 11 additions & 2 deletions packages/ui/src/machines/authenticator/signUp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
setCredentials,
setFieldErrors,
setRemoteError,
setCodeDeliveryDetails,
setUser,
} from './actions';
import { defaultServices } from './defaultServices';
Expand Down Expand Up @@ -115,7 +116,11 @@ export function createSignUpMachine({ services }: SignUpMachineOptions) {
},
{
target: 'resolved',
actions: ['setUser', 'setCredentials'],
actions: [
'setUser',
'setCredentials',
'setCodeDeliveryDetails',
],
},
],
onError: {
Expand Down Expand Up @@ -179,7 +184,10 @@ export function createSignUpMachine({ services }: SignUpMachineOptions) {
entry: [sendUpdate(), 'clearError'],
invoke: {
src: 'confirmSignUp',
onDone: { target: '#signUpActor.resolved', actions: 'setUser' },
onDone: {
target: '#signUpActor.resolved',
actions: ['setUser'],
},
onError: { target: 'edit', actions: 'setRemoteError' },
},
},
Expand Down Expand Up @@ -232,6 +240,7 @@ export function createSignUpMachine({ services }: SignUpMachineOptions) {
setCredentials,
setFieldErrors,
setRemoteError,
setCodeDeliveryDetails,
setUser,
},
services: {
Expand Down
3 changes: 2 additions & 1 deletion packages/ui/src/types/authMachine.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CognitoUser } from 'amazon-cognito-identity-js';
import { CognitoUser, CodeDeliveryDetails } from 'amazon-cognito-identity-js';
import { Interpreter, State } from 'xstate';
import { ValidationError } from './validator';

Expand All @@ -23,6 +23,7 @@ interface BaseFormContext {
remoteError?: string;
user?: CognitoUserAmplify;
validationError?: ValidationError;
codeDeliveryDetails?: CodeDeliveryDetails;
country_code?: string;
}

Expand Down
150 changes: 76 additions & 74 deletions packages/vue/src/components/confirm-sign-up.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,88 @@
<script setup lang="ts">
import { computed, useAttrs, toRefs } from 'vue';
import { translate } from '@aws-amplify/ui';
import { useAuthenticator } from '../composables/useAuth';
const attrs = useAttrs();
const emit = defineEmits(['confirmSignUpSubmit', 'lostCodeClicked']);
const { isPending, error, codeDeliveryDetails } = toRefs(useAuthenticator());
const { submitForm, updateForm, resendCode } = useAuthenticator();
//computed properties
// Only two types of delivery methods is EMAIL or SMS
const confirmSignUpHeading = computed(() => {
return codeDeliveryDetails.value?.DeliveryMedium === 'EMAIL'
? translate('We Emailed You')
: codeDeliveryDetails.value?.DeliveryMedium === 'SMS'
? translate('We Texted You')
: translate('We Sent A Code');
});
const enterCode = computed(() => translate('Enter your code'));
const confirmationCodeText = computed(() => translate('Confirmation Code'));
const resendCodeText = computed(() => translate('Resend Code'));
const confirmText = computed(() => translate('Confirm'));
const subtitleText = computed(() => {
return codeDeliveryDetails.value?.DeliveryMedium === 'EMAIL'
? `Your code is on the way. To log in, enter the code we emailed to ${codeDeliveryDetails.value?.Destination}. It may take a minute to arrive.`
: codeDeliveryDetails.value?.DeliveryMedium === 'SMS'
? `Your code is on the way. To log in, enter the code we texted to ${codeDeliveryDetails.value?.Destination}. It may take a minute to arrive.`
: translate(
`Your code is on the way. To log in, enter the code we sent you. It may take a minute to arrive.`
);
});
// Methods
const onInput = (e: Event): void => {
const { name, value } = <HTMLInputElement>e.target;
updateForm({ name, value });
};
const onConfirmSignUpSubmit = (e: Event): void => {
if (attrs?.onConfirmSignUpSubmit) {
emit('confirmSignUpSubmit', e);
} else {
submit(e);
}
};
const submit = (e: Event): void => {
submitForm();
};
const onLostCodeClicked = (): void => {
if (attrs?.onLostCodeClicked) {
emit('lostCodeClicked');
} else {
resendCode();
}
};
</script>

<template>
<slot v-bind="$attrs" name="confirmSignUpSlotI">
<base-wrapper v-bind="$attrs">
<base-form @input="onInput" @submit.prevent="onConfirmSignUpSubmit">
<base-wrapper class="amplify-flex" style="flex-direction: column">
<slot name="header">
<base-heading class="amplify-heading" :level="3">
<base-heading
class="amplify-heading"
style="font-size: 1.5rem"
:level="3"
>
{{ confirmSignUpHeading }}
</base-heading>
</slot>
<base-text style="margin-bottom: 1rem">
{{ subtitleText }}
</base-text>
<base-field-set
class="amplify-flex"
style="flex-direction: column"
:disabled="actorState.matches('confirmSignUp.pending')"
:disabled="isPending"
>
<base-wrapper
class="amplify-flex amplify-field amplify-textfield"
Expand All @@ -38,8 +109,8 @@
class="amplify-flex"
style="flex-direction: column; align-items: unset"
>
<base-alert v-if="actorState?.context?.remoteError">
{{ actorState?.context?.remoteError }}
<base-alert v-if="error">
{{ error }}
</base-alert>
<amplify-button
class="amplify-field-group__control"
Expand All @@ -48,7 +119,7 @@
data-variation="primary"
type="submit"
style="font-weight: normal"
:disabled="actorState.matches('confirmSignUp.pending')"
:disabled="isPending"
>
{{ confirmText }}
</amplify-button>
Expand All @@ -74,72 +145,3 @@
</base-wrapper>
</slot>
</template>

<script setup lang="ts">
import { computed, ComputedRef, useAttrs } from 'vue';
import { getActorState, SignUpContext, translate } from '@aws-amplify/ui';
import { useAuth } from '../composables/useAuth';
const attrs = useAttrs();
const emit = defineEmits(['confirmSignUpSubmit', 'lostCodeClicked']);
const { state, send } = useAuth();
const actorState = computed(() =>
getActorState(state.value)
) as ComputedRef<any>;
const context = actorState.value.context as SignUpContext;
const username = context.user?.username ?? context.authAttributes?.username;
//computed properties
const enterCode = computed(() => translate('Enter your code'));
const confirmSignUpHeading = computed(() => translate('Confirm Sign Up'));
const confirmationCodeText = computed(() => translate('Confirmation Code'));
const resendCodeText = computed(() => translate('Resend Code'));
const confirmText = computed(() => translate('Confirm'));
// Methods
const onInput = (e: Event): void => {
const { name, value } = <HTMLInputElement>e.target;
send({
type: 'CHANGE',
//@ts-ignore
data: { name, value },
});
};
const onConfirmSignUpSubmit = (e: Event): void => {
if (attrs?.onConfirmSignUpSubmit) {
emit('confirmSignUpSubmit', e);
} else {
submit(e);
}
};
const submit = (e: Event): void => {
const formData = new FormData(<HTMLFormElement>e.target);
send({
type: 'SUBMIT',
//@ts-ignore
data: {
//@ts-ignore
...Object.fromEntries(formData),
username,
},
});
};
const onLostCodeClicked = (): void => {
// do something
if (attrs?.onLostCodeClicked) {
emit('lostCodeClicked');
} else {
send({
type: 'RESEND',
//@ts-ignore
data: { username },
});
}
};
</script>
Loading

0 comments on commit 96830f6

Please sign in to comment.