added recovery ui

This commit is contained in:
SrGooglo 2025-02-25 23:07:37 +00:00
parent 5b6d36744c
commit be0c61a028
7 changed files with 880 additions and 729 deletions

View File

@ -9,452 +9,480 @@ import config from "@config"
import "./index.less" import "./index.less"
const stepsOnError = { const stepsOnError = {
username: "This username or email is not exist", username: "This username or email is not exist",
password: "Password is incorrect", password: "Password is incorrect",
} }
const stepsValidations = { const stepsValidations = {
username: async (state) => { username: async (state) => {
const check = await AuthModel.usernameValidation(state.username).catch((err) => { const check = await AuthModel.usernameValidation(state.username).catch(
return { (err) => {
exists: false return {
} exists: false,
}) }
},
)
return check.exists return check.exists
}, },
} }
const phasesToSteps = { const phasesToSteps = {
0: "username", 0: "username",
1: "password", 1: "password",
} }
class Login extends React.Component { class Login extends React.Component {
static pageStatement = { static pageStatement = {
bottomBarAllowed: false bottomBarAllowed: false,
} }
state = { state = {
loading: false, loading: false,
loginInputs: {}, loginInputs: {},
error: null, error: null,
phase: 0, phase: 0,
mfa_required: null, mfa_required: null,
activation: null, activation: null,
forbidden: false, forbidden: false,
} }
formRef = React.createRef() formRef = React.createRef()
handleFinish = async () => { handleFinish = async () => {
this.setState({ this.setState({
mfa_required: false, mfa_required: false,
}) })
const payload = { const payload = {
username: this.state.loginInputs.username, username: this.state.loginInputs.username,
password: this.state.loginInputs.password, password: this.state.loginInputs.password,
mfa_code: this.state.loginInputs.mfa_code, mfa_code: this.state.loginInputs.mfa_code,
} }
this.clearError() this.clearError()
this.toggleLoading(true) this.toggleLoading(true)
await AuthModel.login(payload, this.onDone).catch((error) => { await AuthModel.login(payload, this.onDone).catch((error) => {
if (error.response.data) { if (error.response.data) {
if (error.response.data.violation) { if (error.response.data.violation) {
return this.setState({ return this.setState({
forbidden: error.response.data.violation forbidden: error.response.data.violation,
}) })
} }
if (error.response.data.activation_required) { if (error.response.data.activation_required) {
return this.setState({ return this.setState({
activation: { activation: {
required: true, required: true,
user_id: error.response.data.user_id, user_id: error.response.data.user_id,
}, },
}) })
} }
} }
console.error(error, error.response) console.error(error, error.response)
this.toggleLoading(false) this.toggleLoading(false)
this.onError(error.response.data.error) this.onError(error.response.data.error)
return false return false
}) })
} }
onDone = async ({ mfa_required } = {}) => { onDone = async ({ mfa_required } = {}) => {
if (mfa_required) { if (mfa_required) {
this.setState({ this.setState({
loading: false, loading: false,
mfa_required: mfa_required, mfa_required: mfa_required,
}) })
return false return false
} }
if (typeof this.props.close === "function") { if (typeof this.props.close === "function") {
await this.props.close({ await this.props.close({
unlock: true unlock: true,
}) })
} }
if (typeof this.props.onDone === "function") { if (typeof this.props.onDone === "function") {
await this.props.onDone() await this.props.onDone()
} }
return true return true
} }
onClickActivateAccount = async () => { onClickActivateAccount = async () => {
const activationObj = this.state.activation const activationObj = this.state.activation
if (!activationObj) { if (!activationObj) {
return null return null
} }
try { try {
await AuthModel.activateAccount( await AuthModel.activateAccount(
this.state.activation.user_id, this.state.activation.user_id,
this.state.activation.code this.state.activation.code,
) )
this.handleFinish() this.handleFinish()
} catch (error) { } catch (error) {
this.setState({ this.setState({
activation: { activation: {
...this.state.activation, ...this.state.activation,
error: error error: error,
} },
}) })
console.error(error) console.error(error)
} }
} }
onClickResendActivationCode = async () => { onClickResendActivationCode = async () => {
const activationObj = this.state.activation const activationObj = this.state.activation
if (!activationObj) { if (!activationObj) {
return null return null
} }
const rensendObj = await AuthModel.resendActivationCode(activationObj.user_id) const rensendObj = await AuthModel.resendActivationCode(
.catch((error) => { activationObj.user_id,
app.message.info(`Please try again later...`) ).catch((error) => {
return null app.message.info(`Please try again later...`)
}) return null
})
if (rensendObj) {
this.setState({ if (rensendObj) {
activation: { this.setState({
...this.state.activation, activation: {
resended: rensendObj.date, ...this.state.activation,
}, resended: rensendObj.date,
}) },
} })
} }
}
onClickForgotPassword = () => {
if (this.props.locked) { onClickForgotPassword = () => {
this.props.unlock() if (this.props.locked) {
} this.props.unlock()
}
if (typeof this.props.close === "function") {
this.props.close() if (typeof this.props.close === "function") {
} this.props.close()
}
app.location.push("/apr")
} app.location.push("/auth?key=recover")
}
toggleLoading = (to) => {
if (typeof to === "undefined") { toggleLoading = (to) => {
to = !this.state.loading if (typeof to === "undefined") {
} to = !this.state.loading
}
this.setState({
loading: to this.setState({
}) loading: to,
} })
}
clearError = () => {
this.setState({ clearError = () => {
error: null this.setState({
}) error: null,
} })
}
onError = (error) => {
this.setState({ onError = (error) => {
error: error this.setState({
}) error: error,
} })
}
onUpdateInput = (input, value) => {
if (input === "username") { onUpdateInput = (input, value) => {
value = value.toLowerCase() if (input === "username") {
value = value.trim() value = value.toLowerCase()
} value = value.trim()
}
// remove error from ref
this.formRef.current.setFields([ // remove error from ref
{ this.formRef.current.setFields([
name: input, {
errors: [] name: input,
} errors: [],
]) },
])
this.setState({
loginInputs: { this.setState({
...this.state.loginInputs, loginInputs: {
[input]: value ...this.state.loginInputs,
} [input]: value,
}) },
} })
}
nextStep = async () => {
const phase = phasesToSteps[this.state.phase] nextStep = async () => {
const phase = phasesToSteps[this.state.phase]
if (typeof stepsValidations[phase] === "function") {
this.toggleLoading(true) if (typeof stepsValidations[phase] === "function") {
this.toggleLoading(true)
const result = await stepsValidations[phase](this.state.loginInputs)
const result = await stepsValidations[phase](this.state.loginInputs)
this.toggleLoading(false)
this.toggleLoading(false)
if (!result) {
this.formRef.current.setFields([ if (!result) {
{ this.formRef.current.setFields([
name: phase, {
errors: [stepsOnError[phase]] name: phase,
}, errors: [stepsOnError[phase]],
]) },
])
return false
} return false
} }
}
const to = this.state.phase + 1
const to = this.state.phase + 1
if (!phasesToSteps[to]) {
return this.handleFinish() if (!phasesToSteps[to]) {
} return this.handleFinish()
}
this.setState({
phase: to this.setState({
}) phase: to,
} })
}
prevStep = () => {
const to = this.state.phase - 1 prevStep = () => {
const to = this.state.phase - 1
if (!phasesToSteps[to]) {
console.warn("No step found for phase", to) if (!phasesToSteps[to]) {
console.warn("No step found for phase", to)
return
} return
}
this.setState({
phase: to, this.setState({
mfa_required: null, phase: to,
}) mfa_required: null,
} })
}
canNext = () => {
if (this.state.loading) { canNext = () => {
return false if (this.state.loading) {
} return false
}
const { phase } = this.state
const { phase } = this.state
const step = phasesToSteps[phase]
const step = phasesToSteps[phase]
return !!this.state.loginInputs[step]
} return !!this.state.loginInputs[step]
}
render() {
if (this.state.forbidden) { render() {
return <div className="login_wrapper"> if (this.state.forbidden) {
<div className="content"> return (
<h1>Access denied</h1> <div className="login_wrapper">
<h3>Your account has been disabled due a violation to our terms of service</h3> <div className="content">
<h1>Access denied</h1>
<p>Here is a detailed description of the violation</p> <h3>
Your account has been disabled due a violation to
<div className="field-error"> our terms of service
{this.state.forbidden.reason} </h3>
</div>
<p>Here is a detailed description of the violation</p>
<p>If you think this is an error, or you want to apeel this decision please contact our support</p>
</div> <div className="field-error">
</div> {this.state.forbidden.reason}
} </div>
if (this.state.activation) { <p>
return <div className="login_wrapper"> If you think this is an error, or you want to apeel
<div className="content"> this decision please contact our support
<h1>Activate your Account</h1> </p>
<p>We have sent you an email with a code that you need to enter below in order to activate your account.</p> </div>
</div>
<antd.Input.OTP )
length={6} }
onChange={(code) => this.setState({
activation: { if (this.state.activation) {
...this.state.activation, return (
code: code, <div className="login_wrapper">
} <div className="content">
})} <h1>Activate your Account</h1>
/> <p>
We have sent you an email with a code that you need
<div className="resend"> to enter below in order to activate your account.
{ </p>
this.state.activation.resended && <antd.Alert
message={`Mail resended`} <antd.Input.OTP
/> length={6}
} onChange={(code) =>
<a this.setState({
href="#" activation: {
onClick={this.onClickResendActivationCode} ...this.state.activation,
> code: code,
Didn't receive the email? },
</a> })
</div> }
/>
{
this.state.activation.error && <div className="field-error"> <div className="resend">
{this.state.activation.error.response.data.error} {this.state.activation.resended && (
</div> <antd.Alert message={`Mail resended`} />
} )}
<a
<antd.Button href="#"
onClick={this.onClickActivateAccount} onClick={this.onClickResendActivationCode}
> >
Activate Didn't receive the email?
</antd.Button> </a>
</div> </div>
</div>
} {this.state.activation.error && (
<div className="field-error">
return <div className="login_wrapper"> {
<div className="content"> this.state.activation.error.response.data
<div className="header"> .error
<h1> }
Sign in </div>
</h1> )}
<h3>
To continue to {config.app.siteName} <antd.Button onClick={this.onClickActivateAccount}>
</h3> Activate
</div> </antd.Button>
</div>
<antd.Form </div>
name="login" )
className="fields" }
autoCorrect="off"
autoCapitalize="none" return (
autoComplete="on" <div className="login_wrapper">
onFinish={this.handleFinish} <div className="content">
ref={this.formRef} <div className="header">
> <h1>Sign in</h1>
<antd.Form.Item <h3>To continue to {config.app.siteName}</h3>
name="username" </div>
className="field"
> <antd.Form
<span><Icons.FiMail /> Username or Email</span> name="login"
<antd.Input className="fields"
placeholder="myusername / myemail@example.com" autoCorrect="off"
onChange={(e) => this.onUpdateInput("username", e.target.value)} autoCapitalize="none"
onPressEnter={this.nextStep} autoComplete="on"
disabled={this.state.phase !== 0} onFinish={this.handleFinish}
autoFocus ref={this.formRef}
/> >
</antd.Form.Item> <antd.Form.Item name="username" className="field">
<span>
<antd.Form.Item <Icons.FiMail /> Username or Email
name="password" </span>
className={classnames( <antd.Input
"field", placeholder="myusername / myemail@example.com"
{ onChange={(e) =>
["hidden"]: this.state.phase !== 1, this.onUpdateInput(
} "username",
)} e.target.value,
> )
<span><Icons.FiLock /> Password</span> }
<antd.Input.Password onPressEnter={this.nextStep}
//placeholder="********" disabled={this.state.phase !== 0}
onChange={(e) => this.onUpdateInput("password", e.target.value)} autoFocus
onPressEnter={this.nextStep} />
/> </antd.Form.Item>
</antd.Form.Item>
<antd.Form.Item
<antd.Form.Item name="password"
name="mfa_code" className={classnames("field", {
className={classnames( ["hidden"]: this.state.phase !== 1,
"field", })}
{ >
["hidden"]: !this.state.mfa_required, <span>
} <Icons.FiLock /> Password
)} </span>
> <antd.Input.Password
<span><Icons.FiLock /> Verification Code</span> //placeholder="********"
onChange={(e) =>
{ this.onUpdateInput(
this.state.mfa_required && <> "password",
<p>We send a verification code to [{this.state.mfa_required.sended_to}]</p> e.target.value,
)
<p> }
Didn't receive the code? <a onClick={this.handleFinish}>Resend</a> onPressEnter={this.nextStep}
</p> />
</> </antd.Form.Item>
}
<antd.Form.Item
<antd.Input.OTP name="mfa_code"
length={4} className={classnames("field", {
formatter={(str) => str.toUpperCase()} ["hidden"]: !this.state.mfa_required,
onChange={(code) => this.onUpdateInput("mfa_code", code)} })}
onPressEnter={this.nextStep} >
/> <span>
</antd.Form.Item> <Icons.FiLock /> Verification Code
</antd.Form> </span>
<div className="component-row"> {this.state.mfa_required && (
{ <>
this.state.phase > 0 && <antd.Button <p>
onClick={this.prevStep} We send a verification code to [
disabled={this.state.loading} {this.state.mfa_required.sended_to}]
> </p>
Back
</antd.Button> <p>
} Didn't receive the code?{" "}
<antd.Button <a onClick={this.handleFinish}>
onClick={this.nextStep} Resend
disabled={!this.canNext() || this.state.loading} </a>
loading={this.state.loading} </p>
> </>
Continue )}
</antd.Button>
</div> <antd.Input.OTP
length={4}
{ formatter={(str) => str.toUpperCase()}
this.state.error && <div className="field-error"> onChange={(code) =>
{this.state.error} this.onUpdateInput("mfa_code", code)
</div> }
} onPressEnter={this.nextStep}
/>
<div className="field" onClick={this.onClickForgotPassword}> </antd.Form.Item>
<a>Forgot your password?</a> </antd.Form>
</div>
</div> <div className="component-row">
</div> {this.state.phase > 0 && (
} <antd.Button
onClick={this.prevStep}
disabled={this.state.loading}
>
Back
</antd.Button>
)}
<antd.Button
onClick={this.nextStep}
disabled={!this.canNext() || this.state.loading}
loading={this.state.loading}
>
Continue
</antd.Button>
</div>
{this.state.error && (
<div className="field-error">{this.state.error}</div>
)}
<div className="field" onClick={this.onClickForgotPassword}>
<a>Forgot your password?</a>
</div>
</div>
</div>
)
}
} }
const ForwardedLogin = (props) => { const ForwardedLogin = (props) => {
return <Login {...props} /> return <Login {...props} />
} }
export default ForwardedLogin export default ForwardedLogin

View File

@ -0,0 +1,149 @@
import React from "react"
import { Input } from "antd"
import FormWithSteps from "@components/FormWithSteps"
import AuthModel from "@models/auth"
const Steps = [
{
id: "email_input",
render: ({ updateState, values }) => {
function onChangeInput(e) {
updateState("account", e.target.value)
}
return (
<>
<p>
First enter your account or email address to find your
associated account.
</p>
<Input
placeholder="@username or email"
value={values.account}
onChange={onChangeInput}
autoFocus
/>
</>
)
},
validate: (values) => {
return values.account && values.account.length > 3
},
onNext: async ({ values, updateState, setError }) => {
try {
const recoverSession = await AuthModel.recoverPassword(
values.account,
)
updateState("recoverSession", recoverSession)
} catch (error) {
console.error(error.response.data)
setError(error.response.data.error)
return {
cancel: true,
}
}
},
},
{
id: "new_password",
render: ({ updateState, values }) => {
return (
<>
<p>Enter a new password for your account.</p>
<Input.Password
placeholder="New Password"
value={values.new_password}
onChange={(e) =>
updateState("new_password", e.target.value)
}
autoFocus
/>
</>
)
},
validate: (values) => {
if (!values.new_password) {
return false
}
return values.new_password.length >= 8
},
},
{
id: "otp_input",
render: ({ updateState, values }) => {
return (
<>
<p>
We've sent you a code to your email [
{values.recoverSession.email}]
</p>
<p>Expires in {values.recoverSession.expires_in} minutes</p>
<Input.OTP
length={values.recoverSession.code_length}
onChange={(value) => updateState("otp", value)}
value={values.otp}
autoFocus
/>
</>
)
},
validate: (values) => {
if (!values.otp) {
return false
}
return values.otp.length === values.recoverSession.code_length
},
},
]
const OnFinish = async ({ values, setError }) => {
try {
const result = await AuthModel.changePassword({
newPassword: values.new_password,
code: values.otp,
verificationToken: values.recoverSession.verificationToken,
})
app.message.info("Password changed successfully")
app.navigation.goAuth()
} catch (error) {
console.error(error)
setError(error.message)
return {
cancel: true,
}
}
}
const Header = () => {
return (
<div className="steped-form-header">
<h1>Account Recovery</h1>
</div>
)
}
const RecoveryPage = (props) => {
return (
<FormWithSteps
header={Header}
steps={Steps}
onCancel={() => {
props.setActiveKey("selector")
}}
onFinish={OnFinish}
cancelable
/>
)
}
export default RecoveryPage

View File

@ -13,202 +13,187 @@ import PasswordStep from "./steps/password"
import EmailStep from "./steps/email" import EmailStep from "./steps/email"
import TOSStep from "./steps/tos" import TOSStep from "./steps/tos"
const steps = [ const steps = [UsernameStep, PasswordStep, EmailStep, TOSStep]
UsernameStep,
PasswordStep,
EmailStep,
TOSStep,
]
const RegisterForm = (props) => { const RegisterForm = (props) => {
const [finishing, setFinishing] = React.useState(false) const [finishing, setFinishing] = React.useState(false)
const [finishError, setFinishError] = React.useState(false) const [finishError, setFinishError] = React.useState(false)
const [finishSuccess, setFinishSuccess] = React.useState(false) const [finishSuccess, setFinishSuccess] = React.useState(false)
const [stepsValues, setStepsValues] = React.useState({}) const [stepsValues, setStepsValues] = React.useState({})
const [step, setStep] = React.useState(0) const [step, setStep] = React.useState(0)
const currentStepData = steps[step - 1] const currentStepData = steps[step - 1]
async function finish() { async function finish() {
setFinishError(null) setFinishError(null)
setFinishSuccess(false) setFinishSuccess(false)
setFinishing(true) setFinishing(true)
const result = await AuthModel.register({ const result = await AuthModel.register({
username: stepsValues.username, username: stepsValues.username,
password: stepsValues.password, password: stepsValues.password,
email: stepsValues.email, email: stepsValues.email,
tos: stepsValues.tos, tos: stepsValues.tos,
}).catch((err) => { }).catch((err) => {
setFinishSuccess(false) setFinishSuccess(false)
setFinishing(false) setFinishing(false)
setFinishError(err) setFinishError(err)
}) })
if (result) { if (result) {
setFinishing(false) setFinishing(false)
setFinishSuccess(true) setFinishSuccess(true)
} }
} }
function nextStep(to) { function nextStep(to) {
setStep((prev) => { setStep((prev) => {
if (!to) { if (!to) {
to = prev + 1 to = prev + 1
} }
if (to === steps.length + 1) { if (to === steps.length + 1) {
finish() finish()
return prev return prev
} }
return to return to
}) })
} }
function prevStep() { function prevStep() {
setStep((prev) => { setStep((prev) => {
return prev - 1 return prev - 1
}) })
} }
const updateStepValue = (value) => setStepsValues((prev) => { const updateStepValue = (value) =>
return { setStepsValues((prev) => {
...prev, return {
[currentStepData.key]: value ...prev,
} [currentStepData.key]: value,
}) }
})
function canNextStep() { function canNextStep() {
if (!currentStepData) { if (!currentStepData) {
return true return true
} }
if (!currentStepData.required) { if (!currentStepData.required) {
return true return true
} }
const currentStepValue = stepsValues[currentStepData.key] const currentStepValue = stepsValues[currentStepData.key]
if (currentStepData.required) { if (currentStepData.required) {
if (!currentStepValue) { if (!currentStepValue) {
return false return false
} }
} }
return true return true
} }
return <div return (
className={classnames( <div
"register_form", className={classnames("register_form", {
{ ["welcome_step"]: step === 0 && !finishing,
["welcome_step"]: step === 0 && !finishing })}
} >
)} <div className="register_form_header-text">
> {!finishSuccess && !finishing && step === 0 && (
<div className="register_form_header-text"> <>
{ <h1>👋 Hi! Nice to meet you</h1>
!finishSuccess && !finishing && step === 0 && <> <p>
<h1>👋 Hi! Nice to meet you</h1> Tell us some basic information to get started
<p>Tell us some basic information to get started creating your account.</p> creating your account.
</> </p>
} </>
)}
{ {!finishSuccess && !finishing && step > 0 && (
!finishSuccess && !finishing && step > 0 && <> <>
<h1> <h1>
{ {currentStepData?.icon &&
currentStepData?.icon && createIconRender(currentStepData.icon) createIconRender(currentStepData.icon)}
}
{currentStepData?.title} {currentStepData?.title}
</h1> </h1>
<p> <p>
{ {typeof currentStepData?.description === "function"
typeof currentStepData?.description === "function" ? ? currentStepData?.description()
currentStepData?.description() : currentStepData.description : currentStepData.description}
} </p>
</p> </>
</> )}
} </div>
</div>
{ {!finishSuccess &&
!finishSuccess && !finishing && step > 0 && React.createElement(currentStepData.content, { !finishing &&
onPressEnter: nextStep, step > 0 &&
currentValue: stepsValues[currentStepData.key], React.createElement(currentStepData.content, {
updateValue: updateStepValue, onPressEnter: nextStep,
}) currentValue: stepsValues[currentStepData.key],
} updateValue: updateStepValue,
})}
{ {finishing && (
finishing && <div className="register_form_creating"> <div className="register_form_creating">
<Icons.LoadingOutlined /> <Icons.LoadingOutlined />
<h1> <h1>Creating your account</h1>
Creating your account </div>
</h1> )}
</div>
}
{ {finishSuccess && (
finishSuccess && <div className="register_form_success"> <div className="register_form_success">
<Icons.CheckCircleOutlined /> <Icons.CheckCircleOutlined />
<h1> <h1>Welcome abord!</h1>
Welcome abord! <p>
</h1> One last step, we need you to login with your new
<p> account.
One last step, we need you to login with your new account. </p>
</p>
<antd.Button <antd.Button
type="primary" type="primary"
onClick={() => props.changeStage(0)} onClick={() => props.setActiveKey("selector")}
> >
Go to login Go to login
</antd.Button> </antd.Button>
</div> </div>
} )}
{ {finishError && (
finishError && <antd.Alert <antd.Alert type="error" message={finishError.message} />
type="error" )}
message={finishError.message}
/>
}
{ {!finishSuccess && !finishing && (
!finishSuccess && !finishing && <div className="register_form_actions"> <div className="register_form_actions">
{ {step === 0 && (
step === 0 && <antd.Button
<antd.Button onClick={() => props.setActiveKey("selector")}
onClick={() => props.changeStage(0)} >
> Cancel
Cancel </antd.Button>
</antd.Button> )}
} {step > 0 && (
{ <antd.Button onClick={() => prevStep()}>
step > 0 && Back
<antd.Button </antd.Button>
onClick={() => prevStep()} )}
>
Back
</antd.Button>
}
<antd.Button <antd.Button
type="primary" type="primary"
onClick={() => nextStep()} onClick={() => nextStep()}
disabled={!canNextStep()} disabled={!canNextStep()}
> >
{ {step === steps.length ? "Finish" : "Next"}
step === steps.length ? "Finish" : "Next" </antd.Button>
} </div>
</antd.Button> )}
</div> </div>
} )
</div>
} }
export default RegisterForm export default RegisterForm

View File

@ -4,62 +4,63 @@ import config from "@config"
import { Icons } from "@components/Icons" import { Icons } from "@components/Icons"
const MainSelector = (props) => { const MainSelector = (props) => {
const { return (
onClickLogin, <>
onClickRegister, <div className="content_header">
} = props <img src={config.logo.alt} className="logo" />
</div>
return <> <div className="actions">
<div className="content_header"> {app.userData && (
<img src={config.logo.alt} className="logo" /> <antd.Button
</div> type="default"
size="large"
onClick={() => {
app.navigation.goMain()
}}
>
<antd.Avatar
size={23}
shape="square"
src={app.userData.avatar}
/>{" "}
Continue as {app.userData.username}
</antd.Button>
)}
<div className="actions"> <antd.Button
{ onClick={() => app.controls.openLoginForm()}
app.userData && <antd.Button icon={<Icons.FiLogIn />}
type="default" type="primary"
size="large" >
onClick={() => { Continue with a Comty Account
app.navigation.goMain() </antd.Button>
}}
>
<antd.Avatar size={23} shape="square" src={app.userData.avatar} /> Continue as {app.userData.username}
</antd.Button>
}
<antd.Button <antd.Button
onClick={onClickLogin} onClick={() => app.controls.openLoginForm()}
icon={<Icons.FiLogIn />} icon={<Icons.FiLogIn />}
type="primary" type="primary"
> disabled
Continue with a Comty Account >
</antd.Button> Continue with a RageStudio© ID
</antd.Button>
<antd.Button <h4>Or create a new account</h4>
onClick={onClickLogin}
icon={<Icons.FiLogIn />}
type="primary"
disabled
>
Continue with a RageStudio© ID
</antd.Button>
<h4>Or create a new account</h4> <antd.Button
onClick={() => props.setActiveKey("register")}
icon={<Icons.FiUserPlus />}
type="primary"
>
Create a Comty Account
</antd.Button>
<antd.Button <a onClick={() => props.setActiveKey("recovery")}>
onClick={onClickRegister} I need help to recover my account
icon={<Icons.FiUserPlus />} </a>
type="primary" </div>
> </>
Create a Comty Account )
</antd.Button>
<p style={{ display: "inline" }}>
<Icons.FiInfo />
Registering a new account accepts the <a onClick={() => app.location.push("/terms")}>Terms and Conditions</a> and <a onClick={() => app.location.push("/privacy")}>Privacy policy</a> for the services provided by {config.author}
</p>
</div>
</>
} }
export default MainSelector export default MainSelector

View File

@ -1,80 +1,82 @@
import React from "react" import React from "react"
import useRandomFeaturedWallpaperUrl from "@hooks/useRandomFeaturedWallpaperUrl" import useRandomFeaturedWallpaperUrl from "@hooks/useRandomFeaturedWallpaperUrl"
import useUrlQueryActiveKey from "@hooks/useUrlQueryActiveKey"
import RegisterForm from "./forms/register" import RegisterForm from "./forms/register"
import MainSelector from "./forms/selector" import MainSelector from "./forms/selector"
import RecoveryForm from "./forms/recovery"
import "./index.less" import "./index.less"
const GradientSVG = () => { const GradientSVG = () => {
return <svg height="100%" width="100%"> return (
<defs> <svg height="100%" width="100%">
<linearGradient id="0" x1="0" y1="0.5" x2="1" y2="0.5"> <defs>
<stop offset="0%" stop-color="rgba(225, 0, 209, 0.1)" /> <linearGradient id="0" x1="0" y1="0.5" x2="1" y2="0.5">
<stop offset="25%" stop-color="rgba(233, 0, 182, 0.08)" /> <stop offset="0%" stop-color="rgba(225, 0, 209, 0.1)" />
<stop offset="50%" stop-color="rgba(240, 0, 154, 0.05)" /> <stop offset="25%" stop-color="rgba(233, 0, 182, 0.08)" />
<stop offset="100%" stop-color="rgba(255, 0, 0, 0)" /> <stop offset="50%" stop-color="rgba(240, 0, 154, 0.05)" />
</linearGradient> <stop offset="100%" stop-color="rgba(255, 0, 0, 0)" />
<radialGradient id="1" gradientTransform="translate(-0.81 -0.5) scale(2, 1.2)"> </linearGradient>
<stop offset="0%" stop-color="rgba(255, 96, 100, 0.2)" /> <radialGradient
<stop offset="20%" stop-color="rgba(255, 96, 100, 0.16)" /> id="1"
<stop offset="40%" stop-color="rgba(255, 96, 100, 0.12)" /> gradientTransform="translate(-0.81 -0.5) scale(2, 1.2)"
<stop offset="60%" stop-color="rgba(255, 96, 100, 0.08)" /> >
<stop offset="100%" stop-color="rgba(255, 96, 100, 0)" /> <stop offset="0%" stop-color="rgba(255, 96, 100, 0.2)" />
</radialGradient> <stop offset="20%" stop-color="rgba(255, 96, 100, 0.16)" />
</defs> <stop offset="40%" stop-color="rgba(255, 96, 100, 0.12)" />
<rect fill="url(#0)" height="100%" width="100%" /> <stop offset="60%" stop-color="rgba(255, 96, 100, 0.08)" />
<rect fill="url(#1)" height="100%" width="100%" /> <stop offset="100%" stop-color="rgba(255, 96, 100, 0)" />
</svg> </radialGradient>
</defs>
<rect fill="url(#0)" height="100%" width="100%" />
<rect fill="url(#1)" height="100%" width="100%" />
</svg>
)
} }
const stagesToComponents = { const keyToComponents = {
0: MainSelector, selector: MainSelector,
2: RegisterForm register: RegisterForm,
recovery: RecoveryForm,
} }
const AuthPage = (props) => { const AuthPage = (props) => {
const [stage, setStage] = React.useState(0) const [activeKey, setActiveKey] = useUrlQueryActiveKey({
const randomWallpaperURL = useRandomFeaturedWallpaperUrl() defaultKey: "selector",
})
const randomWallpaperURL = useRandomFeaturedWallpaperUrl()
function changeStage(nextStage) { return (
setStage(nextStage) <div className="login-page">
} <div className="background">
<GradientSVG />
</div>
const onClickLogin = () => { <div className="wrapper">
app.controls.openLoginForm() <div
} className="wrapper_background"
style={{
backgroundImage: randomWallpaperURL
? `url(${randomWallpaperURL})`
: null,
animation: randomWallpaperURL ? "opacityIn 1s" : null,
}}
/>
const onClickRegister = () => { <div className="content">
changeStage(2) {React.createElement(
} keyToComponents[activeKey] ??
keyToComponents["selector"],
return <div className="login-page"> {
<div className="background"> setActiveKey: setActiveKey,
<GradientSVG /> },
</div> )}
</div>
<div className="wrapper"> </div>
<div </div>
className="wrapper_background" )
style={{
backgroundImage: randomWallpaperURL ? `url(${randomWallpaperURL})` : null,
animation: randomWallpaperURL ? "opacityIn 1s" : null
}}
/>
<div className="content">
{
React.createElement(stagesToComponents[stage] ?? stagesToComponents[0], {
onClickLogin,
onClickRegister,
changeStage,
})
}
</div>
</div>
</div>
} }
export default AuthPage export default AuthPage

View File

@ -1,11 +0,0 @@
import React from "react"
import "./index.less"
const AccountPasswordRecovery = () => {
return <div className="password_recover">
<h1>Account Password Recovery</h1>
</div>
}
export default AccountPasswordRecovery

View File

@ -1,3 +0,0 @@
.password_recover {
padding: 20px;
}