This commit is contained in:
SrGooglo 2025-03-13 23:35:31 +00:00
parent 9c057d6017
commit b395a5f062

View File

@ -8,362 +8,415 @@ import { Icons } from "@components/Icons"
import "./index.less" import "./index.less"
const steps = [ const steps = [
{ {
key: "username", key: "username",
title: "Step 1", title: "Step 1",
icon: "FiUser", icon: "FiUser",
description: () => <div> description: () => (
<p>Enter your username you gonna use for your account, its used to access to your account.</p> <div>
<p>It must be unique, on lower case, and contain only accepted characters as letters, numbers, underscores.</p> <p>
</div>, Enter your username you gonna use for your account, its used
required: true, to access to your account.
content: (props) => { </p>
const [loading, setLoading] = React.useState(false) <p>
const [username, setUsername] = React.useState("") It must be unique, on lower case, and contain only accepted
const [validCharacters, setValidCharacters] = React.useState(null) characters as letters, numbers, underscores.
const [usernameAvailable, setUsernameAvailable] = React.useState(null) </p>
</div>
),
required: true,
content: (props) => {
const [loading, setLoading] = React.useState(false)
const [username, setUsername] = React.useState("")
const [validCharacters, setValidCharacters] = React.useState(null)
const [usernameAvailable, setUsernameAvailable] =
React.useState(null)
const isValid = () => { const isValid = () => {
return username.length > 0 && validCharacters && usernameAvailable return (
} username.length > 0 && validCharacters && usernameAvailable
)
}
const hasValidCharacters = (username) => { const hasValidCharacters = (username) => {
return /^[a-z0-9_]+$/.test(username) return /^[a-z0-9_]+$/.test(username)
} }
const submit = () => { const submit = () => {
if (!isValid()) return if (!isValid()) return
props.onPressEnter() props.onPressEnter()
} }
const handleUpdate = (e) => { const handleUpdate = (e) => {
if (e.target.value === " ") { if (e.target.value === " ") {
return return
} }
e.target.value = e.target.value.toLowerCase() e.target.value = e.target.value.toLowerCase()
setUsername(e.target.value) setUsername(e.target.value)
} }
const renderIndicator = (value, label) => { const renderIndicator = (value, label) => {
if (loading) { if (loading) {
return <> return (
<Icons.LoadingOutlined /> <>
<p>{label}</p> <Icons.LoadingOutlined />
</> <p>{label}</p>
} </>
)
}
if (value) { if (value) {
return <> return (
<Icons.CheckCircleOutlined /> <>
<p>{label}</p> <Icons.CheckCircleOutlined />
</> <p>{label}</p>
} </>
)
}
return <> return (
<Icons.CloseCircleOutlined /> <>
<p>{label}</p> <Icons.CloseCircleOutlined />
</> <p>{label}</p>
} </>
)
}
React.useEffect(() => { React.useEffect(() => {
if (username.length === 0) { if (username.length === 0) {
setUsernameAvailable(null) setUsernameAvailable(null)
setValidCharacters(null) setValidCharacters(null)
setLoading(false) setLoading(false)
return return
} }
props.handleUpdate(null) props.handleUpdate(null)
setLoading(true) setLoading(true)
setValidCharacters(hasValidCharacters(username)) setValidCharacters(hasValidCharacters(username))
const timer = setTimeout(async () => { const timer = setTimeout(async () => {
if (!validCharacters) { if (!validCharacters) {
setLoading(false) setLoading(false)
return return
} }
const request = await AuthModel.availability({ username }).catch((error) => { const request = await AuthModel.availability({
antd.message.error(`Cannot check username availability: ${error.message}`) username,
console.error(error) }).catch((error) => {
antd.message.error(
`Cannot check username availability: ${error.message}`,
)
console.error(error)
return false return false
}) })
if (request) { if (request) {
setUsernameAvailable(request.available) setUsernameAvailable(request.available)
if (!request.available) { if (!request.available) {
props.handleUpdate(null) props.handleUpdate(null)
} else { } else {
props.handleUpdate(username) props.handleUpdate(username)
} }
} }
setLoading(false) setLoading(false)
}, 1000) }, 1000)
return () => clearTimeout(timer) return () => clearTimeout(timer)
}, [username]) }, [username])
return <div className="steps step content"> return (
<antd.Input <div className="steps step content">
autoCorrect="off" <antd.Input
autoCapitalize="none" autoCorrect="off"
onPressEnter={submit} autoCapitalize="none"
placeholder="newuser" onPressEnter={submit}
value={username} placeholder="newuser"
onChange={handleUpdate} value={username}
status={username.length == 0 ? "default" : loading ? "default" : (isValid() ? "success" : "error")} onChange={handleUpdate}
/> status={
username.length == 0
? "default"
: loading
? "default"
: isValid()
? "success"
: "error"
}
/>
<div className="usernameValidity"> <div className="usernameValidity">
<div className="check"> <div className="check">
{ {renderIndicator(
renderIndicator(usernameAvailable, "Username available") usernameAvailable,
} "Username available",
</div> )}
<div className="check"> </div>
{ <div className="check">
renderIndicator(validCharacters, "Valid characters (letters, numbers, underscores)") {renderIndicator(
} validCharacters,
</div> "Valid characters (letters, numbers, underscores)",
</div> )}
</div> </div>
}, </div>
}, </div>
{ )
key: "password", },
title: "Step 2", },
icon: "FiKey", {
description: "Enter a password for the account. must comply with the password requirements policy.", key: "password",
required: true, title: "Step 2",
content: (props) => { icon: "FiKey",
const confirmRef = React.useRef(null) description:
"Enter a password for the account. must comply with the password requirements policy.",
required: true,
content: (props) => {
const confirmRef = React.useRef(null)
const [password, setPassword] = React.useState("") const [password, setPassword] = React.useState("")
const [confirmedPassword, setConfirmedPassword] = React.useState("") const [confirmedPassword, setConfirmedPassword] = React.useState("")
const [passwordStrength, setPasswordStrength] = React.useState(null) const [passwordStrength, setPasswordStrength] = React.useState(null)
const passwordMinimunStrength = 3 const passwordMinimunStrength = 3
const passwordsMatch = password === confirmedPassword const passwordsMatch = password === confirmedPassword
const passwordError = !passwordsMatch && confirmedPassword.length > 0 const passwordError =
!passwordsMatch && confirmedPassword.length > 0
const submit = () => { const submit = () => {
if (!passwordError) { if (!passwordError) {
props.onPressEnter() props.onPressEnter()
} }
} }
const passwordStrengthCalculator = (password) => { const passwordStrengthCalculator = (password) => {
let strength = 0 let strength = 0
if (password.length === 0 || password.length < 8) { if (password.length === 0 || password.length < 8) {
return strength return strength
} }
strength += 1 strength += 1
if (password.length >= 12) { if (password.length >= 12) {
strength += 1 strength += 1
} }
if (password.match(/[a-z]/)) { if (password.match(/[a-z]/)) {
strength += 1 strength += 1
} }
if (password.match(/[A-Z]/)) { if (password.match(/[A-Z]/)) {
strength += 1 strength += 1
} }
if (password.match(/[0-9]/)) { if (password.match(/[0-9]/)) {
strength += 1 strength += 1
} }
if (password.match(/[^a-zA-Z0-9]/)) { if (password.match(/[^a-zA-Z0-9]/)) {
strength += 1 strength += 1
} }
return strength return strength
} }
React.useEffect(() => { React.useEffect(() => {
const calculatedStrength = passwordStrengthCalculator(password) const calculatedStrength = passwordStrengthCalculator(password)
setPasswordStrength(calculatedStrength) setPasswordStrength(calculatedStrength)
if (password !== confirmedPassword) { if (password !== confirmedPassword) {
props.handleUpdate(null) props.handleUpdate(null)
} }
if (calculatedStrength < passwordMinimunStrength) { if (calculatedStrength < passwordMinimunStrength) {
props.handleUpdate(null) props.handleUpdate(null)
} }
if (calculatedStrength >= passwordMinimunStrength && password === confirmedPassword) { if (
props.handleUpdate(password) calculatedStrength >= passwordMinimunStrength &&
} password === confirmedPassword
}, [password, confirmedPassword]) ) {
props.handleUpdate(password)
}
}, [password, confirmedPassword])
return <div className="steps step content passwordsInput"> return (
<antd.Input.Password <div className="steps step content passwordsInput">
className="password" <antd.Input.Password
placeholder="Password" className="password"
autoCorrect="off" placeholder="Password"
autoCapitalize="none" autoCorrect="off"
onPressEnter={() => { autoCapitalize="none"
confirmRef.current.focus() onPressEnter={() => {
}} confirmRef.current.focus()
onChange={(e) => { }}
setPassword(e.target.value) onChange={(e) => {
}} setPassword(e.target.value)
status={passwordError ? "error" : "success"} }}
autoFocus status={passwordError ? "error" : "success"}
/> autoFocus
/>
<antd.Input.Password <antd.Input.Password
className="password" className="password"
placeholder="Confirm Password" placeholder="Confirm Password"
ref={confirmRef} ref={confirmRef}
autoCorrect="off" autoCorrect="off"
autoCapitalize="none" autoCapitalize="none"
onPressEnter={submit} onPressEnter={submit}
status={passwordError ? "error" : "success"} status={passwordError ? "error" : "success"}
onChange={(e) => { onChange={(e) => {
setConfirmedPassword(e.target.value) setConfirmedPassword(e.target.value)
}} }}
/> />
<antd.Progress <antd.Progress
percent={passwordStrength * 20} percent={passwordStrength * 20}
status={passwordStrength < passwordMinimunStrength ? "exception" : "success"} status={
showInfo={false} passwordStrength < passwordMinimunStrength
/> ? "exception"
: "success"
}
showInfo={false}
/>
<div className="passwordPolicy"> <div className="passwordPolicy">
<p>Password must be at least 8 characters long.</p> <p>Password must be at least 8 characters long.</p>
<p>Password must contain at least one number.</p> <p>Password must contain at least one number.</p>
</div> </div>
</div> </div>
}, )
}, },
{ },
key: "email", {
title: "Step 3", key: "email",
icon: "FiMail", title: "Step 3",
description: "Enter a email for the account", icon: "FiMail",
required: true, description: "Enter a email for the account",
content: (props) => { required: true,
const [email, setEmail] = React.useState("") content: (props) => {
const [email, setEmail] = React.useState("")
const [loading, setLoading] = React.useState(false) const [loading, setLoading] = React.useState(false)
const [validFormat, setValidFormat] = React.useState(null) const [validFormat, setValidFormat] = React.useState(null)
const [emailAvailable, setEmailAvailable] = React.useState(null) const [emailAvailable, setEmailAvailable] = React.useState(null)
const isValid = () => { const isValid = () => {
return email.length > 0 && validFormat && emailAvailable return email.length > 0 && validFormat && emailAvailable
} }
const checkIfIsEmail = (email) => { const checkIfIsEmail = (email) => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
} }
const submit = () => { const submit = () => {
if (!isValid()) return if (!isValid()) return
props.onPressEnter() props.onPressEnter()
} }
const handleUpdate = (e) => { const handleUpdate = (e) => {
setEmail(e.target.value) setEmail(e.target.value)
} }
React.useEffect(() => { React.useEffect(() => {
if (email.length === 0) { if (email.length === 0) {
setEmailAvailable(null) setEmailAvailable(null)
setValidFormat(null) setValidFormat(null)
return return
} }
props.handleUpdate(null) props.handleUpdate(null)
setLoading(true) setLoading(true)
setValidFormat(checkIfIsEmail(email)) setValidFormat(checkIfIsEmail(email))
// check if email is available // check if email is available
const timer = setTimeout(async () => { const timer = setTimeout(async () => {
if (!validFormat) return if (!validFormat) return
const request = await AuthModel.availability({ email }).catch((error) => { const request = await AuthModel.availability({
antd.message.error(`Cannot check email availability: ${error.message}`) email,
}).catch((error) => {
antd.message.error(
`Cannot check email availability: ${error.message}`,
)
return false return false
}) })
if (request) { if (request) {
setEmailAvailable(request.available) setEmailAvailable(request.available)
if (!request.available) { if (!request.available) {
antd.message.error("Email is already in use") antd.message.error("Email is already in use")
props.handleUpdate(null) props.handleUpdate(null)
} else { } else {
props.handleUpdate(email) props.handleUpdate(email)
} }
} }
setLoading(false) setLoading(false)
}, 1000) }, 1000)
return () => clearTimeout(timer) return () => clearTimeout(timer)
}, [email]) }, [email])
return <div className="steps step content"> return (
<antd.Input <div className="steps step content">
placeholder="Email" <antd.Input
onPressEnter={submit} placeholder="Email"
onChange={handleUpdate} onPressEnter={submit}
status={email.length == 0 ? "default" : loading ? "default" : (isValid() ? "success" : "error")} onChange={handleUpdate}
/> status={
</div> email.length == 0
}, ? "default"
}, : loading
? "default"
: isValid()
? "success"
: "error"
}
/>
</div>
)
},
},
] ]
export default (props) => { export default (props) => {
const onSubmit = async (values) => { const onSubmit = async (values) => {
const result = await AuthModel.register(values).catch((error) => { const result = await AuthModel.register(values).catch((error) => {
throw new Error(`Failed to register user: ${error.message}`) throw new Error(`Failed to register user: ${error.message}`)
}) })
if (result) { if (result) {
antd.message.success("User registered successfully.") antd.message.success("User registered successfully.")
} }
if (props.locked) { if (props.locked) {
props.unlock() props.unlock()
} }
if (typeof props.close === "function") { if (typeof props.close === "function") {
props.close() props.close()
} }
if (app.isMobile) { if (app.isMobile) {
app.controls.openLoginForm({ app.auth.login()
defaultLocked: props.locked, }
}) }
}
}
return <StepsForm return <StepsForm steps={steps} onSubmit={onSubmit} />
steps={steps}
onSubmit={onSubmit}
/>
} }