Skip to content

Commit

Permalink
feat(ui): Standardise page layout - Create password (#272)
Browse files Browse the repository at this point in the history
* wip: first page setup

* wip: password setup

* fix: hint

* fix: input response

* fix: unit tests

* fix: PasswordValidation stylesheet

* fix: error message

* fix: margin before error msg

* fix: change button text
  • Loading branch information
sdisalvo-crd authored Nov 28, 2023
1 parent 316995d commit 154aa9b
Show file tree
Hide file tree
Showing 22 changed files with 490 additions and 452 deletions.
2 changes: 1 addition & 1 deletion src/locales/en/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@
},
"button": {
"continue": "Create Password",
"skip": "Continue without setting a password"
"skip": "Skip"
},
"alert": {
"text": "You have chosen to skip creating a password. If you change your mind later, you will still be able to create a password.",
Expand Down
10 changes: 5 additions & 5 deletions src/routes/nextRoute/nextRoute.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe("NextRoute", () => {
const result = getNextOnboardingRoute(data as DataProps);

expect(result).toEqual({
pathname: RoutePath.GENERATE_SEED_PHRASE,
pathname: RoutePath.CREATE_PASSWORD,
});
});

Expand All @@ -86,7 +86,7 @@ describe("NextRoute", () => {
const result = getNextSetPasscodeRoute(storeMock);

expect(result).toEqual({
pathname: RoutePath.GENERATE_SEED_PHRASE,
pathname: RoutePath.CREATE_PASSWORD,
});
});

Expand Down Expand Up @@ -115,7 +115,7 @@ describe("NextRoute", () => {
const result = getNextVerifySeedPhraseRoute();

expect(result).toEqual({
pathname: RoutePath.CREATE_PASSWORD,
pathname: RoutePath.TABS_MENU,
});
});
});
Expand Down Expand Up @@ -162,7 +162,7 @@ describe("getNextRoute", () => {
});

expect(result.nextPath).toEqual({
pathname: RoutePath.GENERATE_SEED_PHRASE,
pathname: RoutePath.CREATE_PASSWORD,
});

storeMock.stateCache.authentication.passcodeIsSet = false;
Expand Down Expand Up @@ -195,7 +195,7 @@ describe("getNextRoute", () => {

const result = getNextSetPasscodeRoute(storeMock);
expect(result).toEqual({
pathname: RoutePath.GENERATE_SEED_PHRASE,
pathname: RoutePath.CREATE_PASSWORD,
});
});
});
12 changes: 7 additions & 5 deletions src/routes/nextRoute/nextRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ const getNextRootRoute = (store: StoreState) => {
const getNextOnboardingRoute = (data: DataProps) => {
let path;
if (data.store.stateCache.authentication.passcodeIsSet) {
path = RoutePath.GENERATE_SEED_PHRASE;
path = data.store.stateCache.authentication.passwordIsSet
? RoutePath.GENERATE_SEED_PHRASE
: RoutePath.CREATE_PASSWORD;
} else {
path = RoutePath.SET_PASSCODE;
}
Expand Down Expand Up @@ -74,7 +76,7 @@ const getNextSetPasscodeRoute = (store: StoreState) => {

const nextPath: string = seedPhraseIsSet
? RoutePath.TABS_MENU
: RoutePath.GENERATE_SEED_PHRASE;
: RoutePath.CREATE_PASSWORD;

return { pathname: nextPath };
};
Expand All @@ -99,7 +101,7 @@ const getNextGenerateSeedPhraseRoute = () => {
};

const getNextVerifySeedPhraseRoute = () => {
const nextPath = RoutePath.CREATE_PASSWORD;
const nextPath = RoutePath.TABS_MENU;
return { pathname: nextPath };
};

Expand All @@ -115,7 +117,7 @@ const updateStoreCurrentRoute = (data: DataProps) => {
};

const getNextCreatePasswordRoute = () => {
return { pathname: RoutePath.TABS_MENU };
return { pathname: RoutePath.GENERATE_SEED_PHRASE };
};
const updateStoreAfterCreatePassword = (data: DataProps) => {
const skipped = data.state?.skipped;
Expand Down Expand Up @@ -173,7 +175,7 @@ const nextRoute: Record<string, any> = {
updateRedux: [updateStoreSetSeedPhrase],
},
[RoutePath.VERIFY_SEED_PHRASE]: {
nextPath: (data: DataProps) => getNextVerifySeedPhraseRoute(),
nextPath: () => getNextVerifySeedPhraseRoute(),
updateRedux: [updateStoreAfterVerifySeedPhraseRoute, clearSeedPhraseCache],
},
[RoutePath.CREATE_PASSWORD]: {
Expand Down
6 changes: 3 additions & 3 deletions src/ui/components/CreateIdentifier/CreateIdentifier.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,19 @@
}

.type-input-title,
ion-item.input-item ion-label[position="stacked"] {
ion-item.custom-input ion-label[position="stacked"] {
font-size: 1rem;
font-weight: 500;
line-height: 1.375rem;
margin-top: 0;
transform: none;
}

ion-item.input-item .input-line ion-input input::placeholder {
ion-item.custom-input .input-line ion-input input::placeholder {
font-size: 1rem;
}

.hide-title ion-item.input-item ion-label[position="stacked"] {
.hide-title ion-item.custom-input ion-label[position="stacked"] {
opacity: 0;
}

Expand Down
59 changes: 49 additions & 10 deletions src/ui/components/CustomInput/CustomInput.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ion-item.input-item {
ion-item.custom-input {
--background: var(--ion-color-light);
--border-width: 0;
--inner-border-width: 0;
Expand All @@ -7,27 +7,38 @@ ion-item.input-item {

ion-label[position="stacked"] {
margin-bottom: 0.625rem;
.input-item-optional {

.custom-input-optional {
margin-left: 0.25rem;
opacity: 50%;
}
}

&.error .input-line {
border-color: var(--ion-color-danger);
}

.input-line {
display: flex;
width: 100%;
border: 1px solid var(--ion-color-dark-grey);
border-radius: 8px;

&:focus-within {
border-color: var(--ion-color-secondary);
}

.input-wrapper {
justify-content: center;
}

ion-input {
input {
--padding-top: 0.875rem;
--padding-bottom: 0.875rem;
--padding-start: 1.25rem;
}
--padding-top: 0.875rem;
--padding-bottom: 0.875rem;
--padding-start: 1.25rem;

&.has-focus {
border-color: var(--ion-color-secondary);
.label-text-wrapper {
display: none;
}
}

Expand All @@ -43,8 +54,36 @@ ion-item.input-item {
&::part(native) {
padding: 0;
}

ion-icon {
font-size: 1.85em !important;
font-size: 1.85em;
}
}
}

@media screen and (min-width: 250px) and (max-width: 370px) {
ion-label[position="stacked"] {
font-size: 0.8rem;
line-height: 1rem;
}

.input-line {
height: 2.725rem;

ion-input {
--padding-top: 0.35rem;
--padding-bottom: 0.35rem;
--padding-start: 0.8rem;
min-height: 100%;
font-size: 0.8rem;

.native-wrapper {
flex-grow: 0;
}
}

ion-button ion-icon {
font-size: 1rem;
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions src/ui/components/CustomInput/CustomInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const CustomInput = ({
onChangeFocus,
optional,
value,
error,
}: CustomInputProps) => {
const [hidden, setHidden] = useState(hiddenInput);

Expand All @@ -24,11 +25,11 @@ const CustomInput = ({
}
};
return (
<IonItem className="input-item">
<IonItem className={`custom-input ${error ? "error" : ""}`}>
<IonLabel position="stacked">
{title}
{optional && (
<span className="input-item-optional">
<span className="custom-input-optional">
{i18n.t("custominput.optional")}
</span>
)}
Expand All @@ -37,10 +38,12 @@ const CustomInput = ({
<IonInput
id={dataTestId}
data-testid={dataTestId}
label={title}
labelPlacement="stacked"
type={hidden ? "password" : "text"}
autofocus={autofocus}
placeholder={placeholder}
onIonChange={(e) => onChangeInput(`${e.target.value ?? ""}`)}
onIonInput={(e) => onChangeInput(e.target.value as string)}
onIonFocus={() => handleFocus(true)}
onIonBlur={() => handleFocus(false)}
value={value}
Expand Down
3 changes: 2 additions & 1 deletion src/ui/components/CustomInput/CustomInput.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ interface CustomInputProps {
title?: string;
autofocus?: boolean;
placeholder?: string;
hiddenInput: boolean;
hiddenInput?: boolean;
value: string;
onChangeInput: (text: string) => void;
onChangeFocus?: Dispatch<SetStateAction<boolean>>;
optional?: boolean;
error?: boolean;
}

export type { CustomInputProps };
2 changes: 1 addition & 1 deletion src/ui/components/ErrorMessage/ErrorMessage.types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
interface ErrorMessageProps {
message: string | undefined;
timeout: boolean;
timeout?: boolean;
}

export type { ErrorMessageProps };
2 changes: 1 addition & 1 deletion src/ui/components/IdentifierOptions/IdentifierOptions.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
color: var(--ion-color-primary);
}

.input-item {
.custom-input {
ion-label,
.theme-input-title {
font-size: 1rem;
Expand Down
39 changes: 39 additions & 0 deletions src/ui/components/PasswordValidation/PasswordValidation.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
.password-validation {
padding-bottom: 0;
background-color: var(--ion-color-light);

ion-item {
--background: var(--ion-color-light);
.password-criteria-icon {
font-size: 1.1rem;
padding: 0.2rem;
margin-right: 0.5rem;
border-radius: 1.5rem;
&.fails {
color: var(--ion-color-secondary);
background: var(--ion-color-light-grey);
}
&.pass {
color: white;
background: var(--ion-color-green);
}
}

&::part(native) {
padding: 0;
}
}

@media screen and (min-width: 250px) and (max-width: 370px) {
ion-label {
font-size: 0.8rem;
line-height: 1rem;
}

ion-icon,
ion-label {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
}
}
74 changes: 74 additions & 0 deletions src/ui/components/PasswordValidation/PasswordValidation.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { render } from "@testing-library/react";
import { PasswordValidation } from "../../components/PasswordValidation";

describe("Create Password Page", () => {
test("validates password correctly", () => {
const { container } = render(<PasswordValidation password="Abc123!@" />);
const regexConditions = container.getElementsByClassName(
"password-criteria-icon"
);
for (let i = 0; i < regexConditions.length; i++) {
expect(regexConditions[i]).toHaveClass("pass");
}
});

test("validates password length correctly", () => {
const { container } = render(<PasswordValidation password="Abc123!@" />);
const regexConditions = container.getElementsByClassName(
"password-criteria-icon"
);
expect(regexConditions[0]).toHaveClass("pass");
});

test("validates password is too short", () => {
const { container } = render(<PasswordValidation password="Ac123@" />);
const regexConditions = container.getElementsByClassName(
"password-criteria-icon"
);
expect(regexConditions[0]).toHaveClass("fails");
});

test("validates password is too long", () => {
const { container } = render(
<PasswordValidation password="Abc123456789012345678901234567890123456789012345678901234567890123@" />
);
const regexConditions = container.getElementsByClassName(
"password-criteria-icon"
);
expect(regexConditions[0]).toHaveClass("fails");
});

test("validates password doesn't have uppercase", () => {
const { container } = render(<PasswordValidation password="abc123!@" />);
const regexConditions = container.getElementsByClassName(
"password-criteria-icon"
);
expect(regexConditions[1]).toHaveClass("fails");
});

test("validates password doesn't have lowercase", () => {
const { container } = render(<PasswordValidation password="ABCD123!@" />);
const regexConditions = container.getElementsByClassName(
"password-criteria-icon"
);
expect(regexConditions[2]).toHaveClass("fails");
});

test("validates password doesn't have number", () => {
const { container } = render(<PasswordValidation password="ABCDabcde!@" />);
const regexConditions = container.getElementsByClassName(
"password-criteria-icon"
);
expect(regexConditions[3]).toHaveClass("fails");
});

test("validates password doesn't have symbol", () => {
const { container } = render(
<PasswordValidation password="ABCDabcde123" />
);
const regexConditions = container.getElementsByClassName(
"password-criteria-icon"
);
expect(regexConditions[4]).toHaveClass("fails");
});
});
Loading

0 comments on commit 154aa9b

Please sign in to comment.