Skip to content

Commit b64ecc3

Browse files
authored
Merge pull request #676 from code0-tech/feat/#673
Custom password validation function and validation component
2 parents 401ea34 + fe9a9b9 commit b64ecc3

4 files changed

Lines changed: 77 additions & 86 deletions

File tree

src/components/form/Input.stories.tsx

Lines changed: 4 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {Card} from "../card/Card";
44
import {Button} from "../button/Button";
55
import {IconKey, IconLogin, IconMail} from "@tabler/icons-react";
66
import {Text} from "../text/Text";
7-
import {PasswordInput} from "./PasswordInput";
7+
import {PasswordInput, passwordValidation} from "./PasswordInput";
88
import {TextInput} from "./TextInput";
99
import {EmailInput, emailValidation} from "./EmailInput";
1010
import {NumberInput} from "./NumberInput";
@@ -33,17 +33,14 @@ export const Login = () => {
3333
if (!emailValidation(value)) return "Please provide a valid email"
3434
return null
3535
},
36-
password: (value) => {
37-
if (!value) return "Password is required"
38-
return null
39-
}
36+
password: passwordValidation
4037
},
4138
onSubmit: (values) => {
4239
console.log(values)
4340
}
4441
})
4542

46-
return <Card color={"primary"} maw={300}>
43+
return <Card color={"secondary"} maw={"15vw"}>
4744
<Text size={"xl"} display={"block"} hierarchy={"primary"}>Login</Text>
4845
<br/>
4946
<Text size={"sm"} display={"block"}>
@@ -73,71 +70,10 @@ export const Login = () => {
7370
justifyContent: "space-between",
7471
gap: ".75rem",
7572
}}>
76-
<Button w={"100%"} color={"secondary"} variant={"outlined"} onClick={validate}>
73+
<Button w={"100%"} color={"tertiary"} onClick={validate}>
7774
Login
7875
</Button>
7976

80-
<Button w={"100%"} color={"secondary"} variant={"outlined"} onClick={() => {
81-
const publicKeyCredentialCreationOptions: PublicKeyCredentialCreationOptions = {
82-
challenge: crypto.getRandomValues(new Uint8Array(32)),
83-
rp: {name: "Code0 Dev", id: "localhost"},
84-
user: {
85-
id: Uint8Array.from("nico", c => c.charCodeAt(0)),
86-
name: "nico@localhost",
87-
displayName: "Nico Sammito",
88-
},
89-
pubKeyCredParams: [
90-
{type: "public-key", alg: -7},
91-
{type: "public-key", alg: -257}
92-
] as const,
93-
authenticatorSelection: {
94-
userVerification: "preferred" as UserVerificationRequirement,
95-
// authenticatorAttachment: "platform" as AuthenticatorAttachment,
96-
},
97-
timeout: 60000,
98-
}
99-
100-
navigator.credentials.create({
101-
publicKey: publicKeyCredentialCreationOptions,
102-
}).then(cred => {
103-
console.log("Passkey registriert:", cred);
104-
});
105-
}}>
106-
Login with Passkeys
107-
</Button>
108-
109-
<Button
110-
w={"100%"}
111-
color={"primary"}
112-
variant={"normal"}
113-
onClick={async () => {
114-
const publicKeyCredentialRequestOptions: PublicKeyCredentialRequestOptions = {
115-
challenge: crypto.getRandomValues(new Uint8Array(32)), // Demo: sollte vom Server kommen!
116-
rpId: "localhost",
117-
userVerification: "preferred" as UserVerificationRequirement,
118-
timeout: 60000,
119-
// allowCredentials: [
120-
// {
121-
// id: new Uint8Array([/* credentialId als Uint8Array vom Server */]).buffer,
122-
// type: "public-key" as PublicKeyCredentialType,
123-
// }
124-
// ],
125-
};
126-
127-
try {
128-
await navigator.credentials.get({
129-
publicKey: publicKeyCredentialRequestOptions,
130-
}).then(value => {
131-
console.log("Passkey Login erfolgreich:", value);
132-
});
133-
} catch (err) {
134-
console.error("Login fehlgeschlagen:", err);
135-
}
136-
}}
137-
>
138-
Login with Passkeys
139-
</Button>
140-
14177
</div>
14278
</Card>
14379

src/components/form/Input.style.scss

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
box-sizing: border-box;
1313

1414
& {
15-
@include box.box(variables.$secondary, variables.$white, variables.$white);
15+
@include box.box(variables.$tertiary, variables.$white, variables.$white);
1616
@include box.boxHover(variables.$primary, variables.$white, variables.$white);
1717
@include box.boxActive(variables.$primary, variables.$white, variables.$white);
1818
@include helpers.fontStyle();
@@ -120,24 +120,24 @@
120120
}
121121

122122
&__message {
123-
padding: $padding;
123+
//padding: $padding;
124124
z-index: 0;
125125
display: flex;
126126
align-items: center;
127-
font-size: variables.$xs;
127+
font-size: variables.$sm;
128128
gap: $padding / 2;
129129
margin-top: $padding;
130130

131131
& {
132-
@include box.box(variables.$error, variables.$white, variables.$error);
132+
//@include box.box(variables.$error, variables.$white, variables.$error);
133133
@include helpers.borderRadius();
134134
@include helpers.fontStyle();
135135
box-shadow: none;
136136
}
137137

138138
> svg {
139-
width: variables.$sm;
140-
height: variables.$sm;
139+
width: variables.$md;
140+
height: variables.$md;
141141
}
142142
}
143143

src/components/form/InputMessage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from "react";
2-
import {IconExclamationCircle} from "@tabler/icons-react";
2+
import {IconX} from "@tabler/icons-react";
33

44
export interface InputMessageProps {
55
children: string
@@ -10,7 +10,7 @@ export const InputMessage: React.FC<InputMessageProps> = (props) => {
1010
const {children} = props
1111

1212
return <span className={"input__message"}>
13-
<IconExclamationCircle size={16}/>
13+
<IconX size={16} color={"#D90429"}/>
1414
{children}
1515
</span>
1616

src/components/form/PasswordInput.tsx

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import React, {RefObject} from "react";
22
import {Input, InputProps} from "./Input";
3-
import {IconEye, IconX} from "@tabler/icons-react";
3+
import {IconCheck, IconEye, IconX} from "@tabler/icons-react";
44
import {Button} from "../button/Button";
55
import {clearInputElement} from "./Input.utils";
6+
import {Flex} from "../flex/Flex";
7+
import {Text} from "../text/Text";
68

79
interface PasswordInputProps extends Omit<InputProps<string | null>, "wrapperComponent" | "type"> {
810
clearable?: boolean,
@@ -18,6 +20,7 @@ export const PasswordInput: React.ForwardRefExoticComponent<PasswordInputProps>
1820
clearable = true,
1921
visible = true,
2022
right,
23+
formValidation,
2124
...rest
2225
} = props
2326

@@ -39,16 +42,68 @@ export const PasswordInput: React.ForwardRefExoticComponent<PasswordInputProps>
3942
}
4043

4144
const rightAction = [right]
42-
visible && rightAction.push(<Button variant={"none"} onClick={(event) => toVisible(event)}><IconEye size={13}/></Button>)
43-
clearable && rightAction.push(<Button variant={"none"} onClick={(event) => toClearable(event)}><IconX size={13}/></Button>)
45+
visible && rightAction.push(<Button variant={"none"} onClick={(event) => toVisible(event)}><IconEye
46+
size={13}/></Button>)
47+
clearable && rightAction.push(<Button variant={"none"} onClick={(event) => toClearable(event)}><IconX
48+
size={13}/></Button>)
4449

4550

46-
return <Input
47-
right={rightAction}
48-
rightType={"action"}
49-
type={"password"}
50-
ref={ref as RefObject<HTMLInputElement>}
51-
{...rest}
52-
/>
51+
return <>
52+
<Input
53+
right={rightAction}
54+
rightType={"action"}
55+
type={"password"}
56+
ref={ref as RefObject<HTMLInputElement>}
57+
{...(formValidation ? {
58+
formValidation: {
59+
setValue: formValidation.setValue,
60+
valid: formValidation.valid,
61+
}
62+
} : {})}
63+
{...rest}
64+
/>
65+
{
66+
!formValidation?.valid && (
67+
<Flex mt={0.7} style={{flexDirection: "column"}}>
68+
<Flex align={"center"} style={{gap: "0.35rem"}}>
69+
{formValidation?.notValidMessage?.includes("1") ? <IconX color={"#D90429"} size={16}/> :
70+
<IconCheck color={"#29BF12"} size={16}/>}
71+
<Text>Must be at least 8 characters</Text>
72+
</Flex>
73+
<Flex align={"center"} style={{gap: "0.35rem"}}>
74+
{formValidation?.notValidMessage?.includes("2") ? <IconX color={"#D90429"} size={16}/> :
75+
<IconCheck color={"#29BF12"} size={16}/>}
76+
<Text>Must include a lowercase letter</Text>
77+
</Flex>
78+
<Flex align={"center"} style={{gap: "0.35rem"}}>
79+
{formValidation?.notValidMessage?.includes("3") ? <IconX color={"#D90429"} size={16}/> :
80+
<IconCheck color={"#29BF12"} size={16}/>}
81+
<Text>Must include an uppercase letter</Text>
82+
</Flex>
83+
<Flex align={"center"} style={{gap: "0.35rem"}}>
84+
{formValidation?.notValidMessage?.includes("4") ? <IconX color={"#D90429"} size={16}/> :
85+
<IconCheck color={"#29BF12"} size={16}/>}
86+
<Text>Must include a number</Text>
87+
</Flex>
88+
<Flex align={"center"} style={{gap: "0.35rem"}}>
89+
{formValidation?.notValidMessage?.includes("5") ? <IconX color={"#D90429"} size={16}/> :
90+
<IconCheck color={"#29BF12"} size={16}/>}
91+
<Text>Must include a special character</Text>
92+
</Flex>
93+
</Flex>
94+
)
95+
}
96+
</>
5397

5498
})
99+
100+
export const passwordValidation = (value: string | null): string | null => {
101+
if (!value) return "12345"
102+
let message: null | string = null
103+
if (value.length < 8) message = (message ?? "") + "1"
104+
if (!/[a-z]/.test(value)) message = (message ?? "") + "2"
105+
if (!/[A-Z]/.test(value)) message = (message ?? "") + "3"
106+
if (!/[0-9]/.test(value)) message = (message ?? "") + "4"
107+
if (!/[^A-Za-z0-9]/.test(value)) message = (message ?? "") + "5"
108+
return message
109+
}

0 commit comments

Comments
 (0)