mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
added recovery ui
This commit is contained in:
parent
5b6d36744c
commit
be0c61a028
@ -9,452 +9,480 @@ import config from "@config"
|
||||
import "./index.less"
|
||||
|
||||
const stepsOnError = {
|
||||
username: "This username or email is not exist",
|
||||
password: "Password is incorrect",
|
||||
username: "This username or email is not exist",
|
||||
password: "Password is incorrect",
|
||||
}
|
||||
|
||||
const stepsValidations = {
|
||||
username: async (state) => {
|
||||
const check = await AuthModel.usernameValidation(state.username).catch((err) => {
|
||||
return {
|
||||
exists: false
|
||||
}
|
||||
})
|
||||
username: async (state) => {
|
||||
const check = await AuthModel.usernameValidation(state.username).catch(
|
||||
(err) => {
|
||||
return {
|
||||
exists: false,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
return check.exists
|
||||
},
|
||||
return check.exists
|
||||
},
|
||||
}
|
||||
|
||||
const phasesToSteps = {
|
||||
0: "username",
|
||||
1: "password",
|
||||
0: "username",
|
||||
1: "password",
|
||||
}
|
||||
|
||||
class Login extends React.Component {
|
||||
static pageStatement = {
|
||||
bottomBarAllowed: false
|
||||
}
|
||||
|
||||
state = {
|
||||
loading: false,
|
||||
loginInputs: {},
|
||||
error: null,
|
||||
phase: 0,
|
||||
|
||||
mfa_required: null,
|
||||
activation: null,
|
||||
forbidden: false,
|
||||
}
|
||||
|
||||
formRef = React.createRef()
|
||||
|
||||
handleFinish = async () => {
|
||||
this.setState({
|
||||
mfa_required: false,
|
||||
})
|
||||
|
||||
const payload = {
|
||||
username: this.state.loginInputs.username,
|
||||
password: this.state.loginInputs.password,
|
||||
mfa_code: this.state.loginInputs.mfa_code,
|
||||
}
|
||||
|
||||
this.clearError()
|
||||
this.toggleLoading(true)
|
||||
|
||||
await AuthModel.login(payload, this.onDone).catch((error) => {
|
||||
if (error.response.data) {
|
||||
if (error.response.data.violation) {
|
||||
return this.setState({
|
||||
forbidden: error.response.data.violation
|
||||
})
|
||||
}
|
||||
|
||||
if (error.response.data.activation_required) {
|
||||
return this.setState({
|
||||
activation: {
|
||||
required: true,
|
||||
user_id: error.response.data.user_id,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
console.error(error, error.response)
|
||||
|
||||
this.toggleLoading(false)
|
||||
this.onError(error.response.data.error)
|
||||
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
onDone = async ({ mfa_required } = {}) => {
|
||||
if (mfa_required) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
mfa_required: mfa_required,
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof this.props.close === "function") {
|
||||
await this.props.close({
|
||||
unlock: true
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof this.props.onDone === "function") {
|
||||
await this.props.onDone()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
onClickActivateAccount = async () => {
|
||||
const activationObj = this.state.activation
|
||||
|
||||
if (!activationObj) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
await AuthModel.activateAccount(
|
||||
this.state.activation.user_id,
|
||||
this.state.activation.code
|
||||
)
|
||||
|
||||
this.handleFinish()
|
||||
} catch (error) {
|
||||
this.setState({
|
||||
activation: {
|
||||
...this.state.activation,
|
||||
error: error
|
||||
}
|
||||
})
|
||||
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
onClickResendActivationCode = async () => {
|
||||
const activationObj = this.state.activation
|
||||
|
||||
if (!activationObj) {
|
||||
return null
|
||||
}
|
||||
|
||||
const rensendObj = await AuthModel.resendActivationCode(activationObj.user_id)
|
||||
.catch((error) => {
|
||||
app.message.info(`Please try again later...`)
|
||||
return null
|
||||
})
|
||||
|
||||
if (rensendObj) {
|
||||
this.setState({
|
||||
activation: {
|
||||
...this.state.activation,
|
||||
resended: rensendObj.date,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onClickForgotPassword = () => {
|
||||
if (this.props.locked) {
|
||||
this.props.unlock()
|
||||
}
|
||||
|
||||
if (typeof this.props.close === "function") {
|
||||
this.props.close()
|
||||
}
|
||||
|
||||
app.location.push("/apr")
|
||||
}
|
||||
|
||||
toggleLoading = (to) => {
|
||||
if (typeof to === "undefined") {
|
||||
to = !this.state.loading
|
||||
}
|
||||
|
||||
this.setState({
|
||||
loading: to
|
||||
})
|
||||
}
|
||||
|
||||
clearError = () => {
|
||||
this.setState({
|
||||
error: null
|
||||
})
|
||||
}
|
||||
|
||||
onError = (error) => {
|
||||
this.setState({
|
||||
error: error
|
||||
})
|
||||
}
|
||||
|
||||
onUpdateInput = (input, value) => {
|
||||
if (input === "username") {
|
||||
value = value.toLowerCase()
|
||||
value = value.trim()
|
||||
}
|
||||
|
||||
// remove error from ref
|
||||
this.formRef.current.setFields([
|
||||
{
|
||||
name: input,
|
||||
errors: []
|
||||
}
|
||||
])
|
||||
|
||||
this.setState({
|
||||
loginInputs: {
|
||||
...this.state.loginInputs,
|
||||
[input]: value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
nextStep = async () => {
|
||||
const phase = phasesToSteps[this.state.phase]
|
||||
|
||||
if (typeof stepsValidations[phase] === "function") {
|
||||
this.toggleLoading(true)
|
||||
|
||||
const result = await stepsValidations[phase](this.state.loginInputs)
|
||||
|
||||
this.toggleLoading(false)
|
||||
|
||||
if (!result) {
|
||||
this.formRef.current.setFields([
|
||||
{
|
||||
name: phase,
|
||||
errors: [stepsOnError[phase]]
|
||||
},
|
||||
])
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const to = this.state.phase + 1
|
||||
|
||||
if (!phasesToSteps[to]) {
|
||||
return this.handleFinish()
|
||||
}
|
||||
|
||||
this.setState({
|
||||
phase: to
|
||||
})
|
||||
}
|
||||
|
||||
prevStep = () => {
|
||||
const to = this.state.phase - 1
|
||||
|
||||
if (!phasesToSteps[to]) {
|
||||
console.warn("No step found for phase", to)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.setState({
|
||||
phase: to,
|
||||
mfa_required: null,
|
||||
})
|
||||
}
|
||||
|
||||
canNext = () => {
|
||||
if (this.state.loading) {
|
||||
return false
|
||||
}
|
||||
|
||||
const { phase } = this.state
|
||||
|
||||
const step = phasesToSteps[phase]
|
||||
|
||||
return !!this.state.loginInputs[step]
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.forbidden) {
|
||||
return <div className="login_wrapper">
|
||||
<div className="content">
|
||||
<h1>Access denied</h1>
|
||||
<h3>Your account has been disabled due a violation to our terms of service</h3>
|
||||
|
||||
<p>Here is a detailed description of the violation</p>
|
||||
|
||||
<div className="field-error">
|
||||
{this.state.forbidden.reason}
|
||||
</div>
|
||||
|
||||
<p>If you think this is an error, or you want to apeel this decision please contact our support</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
if (this.state.activation) {
|
||||
return <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 to enter below in order to activate your account.</p>
|
||||
|
||||
<antd.Input.OTP
|
||||
length={6}
|
||||
onChange={(code) => this.setState({
|
||||
activation: {
|
||||
...this.state.activation,
|
||||
code: code,
|
||||
}
|
||||
})}
|
||||
/>
|
||||
|
||||
<div className="resend">
|
||||
{
|
||||
this.state.activation.resended && <antd.Alert
|
||||
message={`Mail resended`}
|
||||
/>
|
||||
}
|
||||
<a
|
||||
href="#"
|
||||
onClick={this.onClickResendActivationCode}
|
||||
>
|
||||
Didn't receive the email?
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{
|
||||
this.state.activation.error && <div className="field-error">
|
||||
{this.state.activation.error.response.data.error}
|
||||
</div>
|
||||
}
|
||||
|
||||
<antd.Button
|
||||
onClick={this.onClickActivateAccount}
|
||||
>
|
||||
Activate
|
||||
</antd.Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
return <div className="login_wrapper">
|
||||
<div className="content">
|
||||
<div className="header">
|
||||
<h1>
|
||||
Sign in
|
||||
</h1>
|
||||
<h3>
|
||||
To continue to {config.app.siteName}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<antd.Form
|
||||
name="login"
|
||||
className="fields"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="none"
|
||||
autoComplete="on"
|
||||
onFinish={this.handleFinish}
|
||||
ref={this.formRef}
|
||||
>
|
||||
<antd.Form.Item
|
||||
name="username"
|
||||
className="field"
|
||||
>
|
||||
<span><Icons.FiMail /> Username or Email</span>
|
||||
<antd.Input
|
||||
placeholder="myusername / myemail@example.com"
|
||||
onChange={(e) => this.onUpdateInput("username", e.target.value)}
|
||||
onPressEnter={this.nextStep}
|
||||
disabled={this.state.phase !== 0}
|
||||
autoFocus
|
||||
/>
|
||||
</antd.Form.Item>
|
||||
|
||||
<antd.Form.Item
|
||||
name="password"
|
||||
className={classnames(
|
||||
"field",
|
||||
{
|
||||
["hidden"]: this.state.phase !== 1,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<span><Icons.FiLock /> Password</span>
|
||||
<antd.Input.Password
|
||||
//placeholder="********"
|
||||
onChange={(e) => this.onUpdateInput("password", e.target.value)}
|
||||
onPressEnter={this.nextStep}
|
||||
/>
|
||||
</antd.Form.Item>
|
||||
|
||||
<antd.Form.Item
|
||||
name="mfa_code"
|
||||
className={classnames(
|
||||
"field",
|
||||
{
|
||||
["hidden"]: !this.state.mfa_required,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<span><Icons.FiLock /> Verification Code</span>
|
||||
|
||||
{
|
||||
this.state.mfa_required && <>
|
||||
<p>We send a verification code to [{this.state.mfa_required.sended_to}]</p>
|
||||
|
||||
<p>
|
||||
Didn't receive the code? <a onClick={this.handleFinish}>Resend</a>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
|
||||
<antd.Input.OTP
|
||||
length={4}
|
||||
formatter={(str) => str.toUpperCase()}
|
||||
onChange={(code) => this.onUpdateInput("mfa_code", code)}
|
||||
onPressEnter={this.nextStep}
|
||||
/>
|
||||
</antd.Form.Item>
|
||||
</antd.Form>
|
||||
|
||||
<div className="component-row">
|
||||
{
|
||||
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>
|
||||
}
|
||||
static pageStatement = {
|
||||
bottomBarAllowed: false,
|
||||
}
|
||||
|
||||
state = {
|
||||
loading: false,
|
||||
loginInputs: {},
|
||||
error: null,
|
||||
phase: 0,
|
||||
|
||||
mfa_required: null,
|
||||
activation: null,
|
||||
forbidden: false,
|
||||
}
|
||||
|
||||
formRef = React.createRef()
|
||||
|
||||
handleFinish = async () => {
|
||||
this.setState({
|
||||
mfa_required: false,
|
||||
})
|
||||
|
||||
const payload = {
|
||||
username: this.state.loginInputs.username,
|
||||
password: this.state.loginInputs.password,
|
||||
mfa_code: this.state.loginInputs.mfa_code,
|
||||
}
|
||||
|
||||
this.clearError()
|
||||
this.toggleLoading(true)
|
||||
|
||||
await AuthModel.login(payload, this.onDone).catch((error) => {
|
||||
if (error.response.data) {
|
||||
if (error.response.data.violation) {
|
||||
return this.setState({
|
||||
forbidden: error.response.data.violation,
|
||||
})
|
||||
}
|
||||
|
||||
if (error.response.data.activation_required) {
|
||||
return this.setState({
|
||||
activation: {
|
||||
required: true,
|
||||
user_id: error.response.data.user_id,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
console.error(error, error.response)
|
||||
|
||||
this.toggleLoading(false)
|
||||
this.onError(error.response.data.error)
|
||||
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
onDone = async ({ mfa_required } = {}) => {
|
||||
if (mfa_required) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
mfa_required: mfa_required,
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof this.props.close === "function") {
|
||||
await this.props.close({
|
||||
unlock: true,
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof this.props.onDone === "function") {
|
||||
await this.props.onDone()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
onClickActivateAccount = async () => {
|
||||
const activationObj = this.state.activation
|
||||
|
||||
if (!activationObj) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
await AuthModel.activateAccount(
|
||||
this.state.activation.user_id,
|
||||
this.state.activation.code,
|
||||
)
|
||||
|
||||
this.handleFinish()
|
||||
} catch (error) {
|
||||
this.setState({
|
||||
activation: {
|
||||
...this.state.activation,
|
||||
error: error,
|
||||
},
|
||||
})
|
||||
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
onClickResendActivationCode = async () => {
|
||||
const activationObj = this.state.activation
|
||||
|
||||
if (!activationObj) {
|
||||
return null
|
||||
}
|
||||
|
||||
const rensendObj = await AuthModel.resendActivationCode(
|
||||
activationObj.user_id,
|
||||
).catch((error) => {
|
||||
app.message.info(`Please try again later...`)
|
||||
return null
|
||||
})
|
||||
|
||||
if (rensendObj) {
|
||||
this.setState({
|
||||
activation: {
|
||||
...this.state.activation,
|
||||
resended: rensendObj.date,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onClickForgotPassword = () => {
|
||||
if (this.props.locked) {
|
||||
this.props.unlock()
|
||||
}
|
||||
|
||||
if (typeof this.props.close === "function") {
|
||||
this.props.close()
|
||||
}
|
||||
|
||||
app.location.push("/auth?key=recover")
|
||||
}
|
||||
|
||||
toggleLoading = (to) => {
|
||||
if (typeof to === "undefined") {
|
||||
to = !this.state.loading
|
||||
}
|
||||
|
||||
this.setState({
|
||||
loading: to,
|
||||
})
|
||||
}
|
||||
|
||||
clearError = () => {
|
||||
this.setState({
|
||||
error: null,
|
||||
})
|
||||
}
|
||||
|
||||
onError = (error) => {
|
||||
this.setState({
|
||||
error: error,
|
||||
})
|
||||
}
|
||||
|
||||
onUpdateInput = (input, value) => {
|
||||
if (input === "username") {
|
||||
value = value.toLowerCase()
|
||||
value = value.trim()
|
||||
}
|
||||
|
||||
// remove error from ref
|
||||
this.formRef.current.setFields([
|
||||
{
|
||||
name: input,
|
||||
errors: [],
|
||||
},
|
||||
])
|
||||
|
||||
this.setState({
|
||||
loginInputs: {
|
||||
...this.state.loginInputs,
|
||||
[input]: value,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
nextStep = async () => {
|
||||
const phase = phasesToSteps[this.state.phase]
|
||||
|
||||
if (typeof stepsValidations[phase] === "function") {
|
||||
this.toggleLoading(true)
|
||||
|
||||
const result = await stepsValidations[phase](this.state.loginInputs)
|
||||
|
||||
this.toggleLoading(false)
|
||||
|
||||
if (!result) {
|
||||
this.formRef.current.setFields([
|
||||
{
|
||||
name: phase,
|
||||
errors: [stepsOnError[phase]],
|
||||
},
|
||||
])
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const to = this.state.phase + 1
|
||||
|
||||
if (!phasesToSteps[to]) {
|
||||
return this.handleFinish()
|
||||
}
|
||||
|
||||
this.setState({
|
||||
phase: to,
|
||||
})
|
||||
}
|
||||
|
||||
prevStep = () => {
|
||||
const to = this.state.phase - 1
|
||||
|
||||
if (!phasesToSteps[to]) {
|
||||
console.warn("No step found for phase", to)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.setState({
|
||||
phase: to,
|
||||
mfa_required: null,
|
||||
})
|
||||
}
|
||||
|
||||
canNext = () => {
|
||||
if (this.state.loading) {
|
||||
return false
|
||||
}
|
||||
|
||||
const { phase } = this.state
|
||||
|
||||
const step = phasesToSteps[phase]
|
||||
|
||||
return !!this.state.loginInputs[step]
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.forbidden) {
|
||||
return (
|
||||
<div className="login_wrapper">
|
||||
<div className="content">
|
||||
<h1>Access denied</h1>
|
||||
<h3>
|
||||
Your account has been disabled due a violation to
|
||||
our terms of service
|
||||
</h3>
|
||||
|
||||
<p>Here is a detailed description of the violation</p>
|
||||
|
||||
<div className="field-error">
|
||||
{this.state.forbidden.reason}
|
||||
</div>
|
||||
|
||||
<p>
|
||||
If you think this is an error, or you want to apeel
|
||||
this decision please contact our support
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (this.state.activation) {
|
||||
return (
|
||||
<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
|
||||
to enter below in order to activate your account.
|
||||
</p>
|
||||
|
||||
<antd.Input.OTP
|
||||
length={6}
|
||||
onChange={(code) =>
|
||||
this.setState({
|
||||
activation: {
|
||||
...this.state.activation,
|
||||
code: code,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="resend">
|
||||
{this.state.activation.resended && (
|
||||
<antd.Alert message={`Mail resended`} />
|
||||
)}
|
||||
<a
|
||||
href="#"
|
||||
onClick={this.onClickResendActivationCode}
|
||||
>
|
||||
Didn't receive the email?
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{this.state.activation.error && (
|
||||
<div className="field-error">
|
||||
{
|
||||
this.state.activation.error.response.data
|
||||
.error
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<antd.Button onClick={this.onClickActivateAccount}>
|
||||
Activate
|
||||
</antd.Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="login_wrapper">
|
||||
<div className="content">
|
||||
<div className="header">
|
||||
<h1>Sign in</h1>
|
||||
<h3>To continue to {config.app.siteName}</h3>
|
||||
</div>
|
||||
|
||||
<antd.Form
|
||||
name="login"
|
||||
className="fields"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="none"
|
||||
autoComplete="on"
|
||||
onFinish={this.handleFinish}
|
||||
ref={this.formRef}
|
||||
>
|
||||
<antd.Form.Item name="username" className="field">
|
||||
<span>
|
||||
<Icons.FiMail /> Username or Email
|
||||
</span>
|
||||
<antd.Input
|
||||
placeholder="myusername / myemail@example.com"
|
||||
onChange={(e) =>
|
||||
this.onUpdateInput(
|
||||
"username",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
onPressEnter={this.nextStep}
|
||||
disabled={this.state.phase !== 0}
|
||||
autoFocus
|
||||
/>
|
||||
</antd.Form.Item>
|
||||
|
||||
<antd.Form.Item
|
||||
name="password"
|
||||
className={classnames("field", {
|
||||
["hidden"]: this.state.phase !== 1,
|
||||
})}
|
||||
>
|
||||
<span>
|
||||
<Icons.FiLock /> Password
|
||||
</span>
|
||||
<antd.Input.Password
|
||||
//placeholder="********"
|
||||
onChange={(e) =>
|
||||
this.onUpdateInput(
|
||||
"password",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
onPressEnter={this.nextStep}
|
||||
/>
|
||||
</antd.Form.Item>
|
||||
|
||||
<antd.Form.Item
|
||||
name="mfa_code"
|
||||
className={classnames("field", {
|
||||
["hidden"]: !this.state.mfa_required,
|
||||
})}
|
||||
>
|
||||
<span>
|
||||
<Icons.FiLock /> Verification Code
|
||||
</span>
|
||||
|
||||
{this.state.mfa_required && (
|
||||
<>
|
||||
<p>
|
||||
We send a verification code to [
|
||||
{this.state.mfa_required.sended_to}]
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Didn't receive the code?{" "}
|
||||
<a onClick={this.handleFinish}>
|
||||
Resend
|
||||
</a>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
<antd.Input.OTP
|
||||
length={4}
|
||||
formatter={(str) => str.toUpperCase()}
|
||||
onChange={(code) =>
|
||||
this.onUpdateInput("mfa_code", code)
|
||||
}
|
||||
onPressEnter={this.nextStep}
|
||||
/>
|
||||
</antd.Form.Item>
|
||||
</antd.Form>
|
||||
|
||||
<div className="component-row">
|
||||
{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) => {
|
||||
return <Login {...props} />
|
||||
return <Login {...props} />
|
||||
}
|
||||
|
||||
export default ForwardedLogin
|
||||
export default ForwardedLogin
|
||||
|
149
packages/app/src/pages/auth/forms/recovery/index.jsx
Normal file
149
packages/app/src/pages/auth/forms/recovery/index.jsx
Normal 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
|
@ -13,202 +13,187 @@ import PasswordStep from "./steps/password"
|
||||
import EmailStep from "./steps/email"
|
||||
import TOSStep from "./steps/tos"
|
||||
|
||||
const steps = [
|
||||
UsernameStep,
|
||||
PasswordStep,
|
||||
EmailStep,
|
||||
TOSStep,
|
||||
]
|
||||
const steps = [UsernameStep, PasswordStep, EmailStep, TOSStep]
|
||||
|
||||
const RegisterForm = (props) => {
|
||||
const [finishing, setFinishing] = React.useState(false)
|
||||
const [finishError, setFinishError] = React.useState(false)
|
||||
const [finishSuccess, setFinishSuccess] = React.useState(false)
|
||||
const [finishing, setFinishing] = React.useState(false)
|
||||
const [finishError, setFinishError] = React.useState(false)
|
||||
const [finishSuccess, setFinishSuccess] = React.useState(false)
|
||||
|
||||
const [stepsValues, setStepsValues] = React.useState({})
|
||||
const [step, setStep] = React.useState(0)
|
||||
const [stepsValues, setStepsValues] = React.useState({})
|
||||
const [step, setStep] = React.useState(0)
|
||||
|
||||
const currentStepData = steps[step - 1]
|
||||
const currentStepData = steps[step - 1]
|
||||
|
||||
async function finish() {
|
||||
setFinishError(null)
|
||||
setFinishSuccess(false)
|
||||
setFinishing(true)
|
||||
async function finish() {
|
||||
setFinishError(null)
|
||||
setFinishSuccess(false)
|
||||
setFinishing(true)
|
||||
|
||||
const result = await AuthModel.register({
|
||||
username: stepsValues.username,
|
||||
password: stepsValues.password,
|
||||
email: stepsValues.email,
|
||||
tos: stepsValues.tos,
|
||||
}).catch((err) => {
|
||||
setFinishSuccess(false)
|
||||
setFinishing(false)
|
||||
setFinishError(err)
|
||||
})
|
||||
const result = await AuthModel.register({
|
||||
username: stepsValues.username,
|
||||
password: stepsValues.password,
|
||||
email: stepsValues.email,
|
||||
tos: stepsValues.tos,
|
||||
}).catch((err) => {
|
||||
setFinishSuccess(false)
|
||||
setFinishing(false)
|
||||
setFinishError(err)
|
||||
})
|
||||
|
||||
if (result) {
|
||||
setFinishing(false)
|
||||
setFinishSuccess(true)
|
||||
}
|
||||
}
|
||||
if (result) {
|
||||
setFinishing(false)
|
||||
setFinishSuccess(true)
|
||||
}
|
||||
}
|
||||
|
||||
function nextStep(to) {
|
||||
setStep((prev) => {
|
||||
if (!to) {
|
||||
to = prev + 1
|
||||
}
|
||||
function nextStep(to) {
|
||||
setStep((prev) => {
|
||||
if (!to) {
|
||||
to = prev + 1
|
||||
}
|
||||
|
||||
if (to === steps.length + 1) {
|
||||
finish()
|
||||
return prev
|
||||
}
|
||||
if (to === steps.length + 1) {
|
||||
finish()
|
||||
return prev
|
||||
}
|
||||
|
||||
return to
|
||||
})
|
||||
}
|
||||
return to
|
||||
})
|
||||
}
|
||||
|
||||
function prevStep() {
|
||||
setStep((prev) => {
|
||||
return prev - 1
|
||||
})
|
||||
}
|
||||
function prevStep() {
|
||||
setStep((prev) => {
|
||||
return prev - 1
|
||||
})
|
||||
}
|
||||
|
||||
const updateStepValue = (value) => setStepsValues((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
[currentStepData.key]: value
|
||||
}
|
||||
})
|
||||
const updateStepValue = (value) =>
|
||||
setStepsValues((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
[currentStepData.key]: value,
|
||||
}
|
||||
})
|
||||
|
||||
function canNextStep() {
|
||||
if (!currentStepData) {
|
||||
return true
|
||||
}
|
||||
function canNextStep() {
|
||||
if (!currentStepData) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (!currentStepData.required) {
|
||||
return true
|
||||
}
|
||||
if (!currentStepData.required) {
|
||||
return true
|
||||
}
|
||||
|
||||
const currentStepValue = stepsValues[currentStepData.key]
|
||||
const currentStepValue = stepsValues[currentStepData.key]
|
||||
|
||||
if (currentStepData.required) {
|
||||
if (!currentStepValue) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (currentStepData.required) {
|
||||
if (!currentStepValue) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return <div
|
||||
className={classnames(
|
||||
"register_form",
|
||||
{
|
||||
["welcome_step"]: step === 0 && !finishing
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className="register_form_header-text">
|
||||
{
|
||||
!finishSuccess && !finishing && step === 0 && <>
|
||||
<h1>👋 Hi! Nice to meet you</h1>
|
||||
<p>Tell us some basic information to get started creating your account.</p>
|
||||
</>
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={classnames("register_form", {
|
||||
["welcome_step"]: step === 0 && !finishing,
|
||||
})}
|
||||
>
|
||||
<div className="register_form_header-text">
|
||||
{!finishSuccess && !finishing && step === 0 && (
|
||||
<>
|
||||
<h1>👋 Hi! Nice to meet you</h1>
|
||||
<p>
|
||||
Tell us some basic information to get started
|
||||
creating your account.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
{
|
||||
!finishSuccess && !finishing && step > 0 && <>
|
||||
<h1>
|
||||
{
|
||||
currentStepData?.icon && createIconRender(currentStepData.icon)
|
||||
}
|
||||
{!finishSuccess && !finishing && step > 0 && (
|
||||
<>
|
||||
<h1>
|
||||
{currentStepData?.icon &&
|
||||
createIconRender(currentStepData.icon)}
|
||||
|
||||
{currentStepData?.title}
|
||||
</h1>
|
||||
<p>
|
||||
{
|
||||
typeof currentStepData?.description === "function" ?
|
||||
currentStepData?.description() : currentStepData.description
|
||||
}
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
{currentStepData?.title}
|
||||
</h1>
|
||||
<p>
|
||||
{typeof currentStepData?.description === "function"
|
||||
? currentStepData?.description()
|
||||
: currentStepData.description}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{
|
||||
!finishSuccess && !finishing && step > 0 && React.createElement(currentStepData.content, {
|
||||
onPressEnter: nextStep,
|
||||
currentValue: stepsValues[currentStepData.key],
|
||||
updateValue: updateStepValue,
|
||||
})
|
||||
}
|
||||
{!finishSuccess &&
|
||||
!finishing &&
|
||||
step > 0 &&
|
||||
React.createElement(currentStepData.content, {
|
||||
onPressEnter: nextStep,
|
||||
currentValue: stepsValues[currentStepData.key],
|
||||
updateValue: updateStepValue,
|
||||
})}
|
||||
|
||||
{
|
||||
finishing && <div className="register_form_creating">
|
||||
<Icons.LoadingOutlined />
|
||||
<h1>
|
||||
Creating your account
|
||||
</h1>
|
||||
</div>
|
||||
}
|
||||
{finishing && (
|
||||
<div className="register_form_creating">
|
||||
<Icons.LoadingOutlined />
|
||||
<h1>Creating your account</h1>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{
|
||||
finishSuccess && <div className="register_form_success">
|
||||
<Icons.CheckCircleOutlined />
|
||||
<h1>
|
||||
Welcome abord!
|
||||
</h1>
|
||||
<p>
|
||||
One last step, we need you to login with your new account.
|
||||
</p>
|
||||
{finishSuccess && (
|
||||
<div className="register_form_success">
|
||||
<Icons.CheckCircleOutlined />
|
||||
<h1>Welcome abord!</h1>
|
||||
<p>
|
||||
One last step, we need you to login with your new
|
||||
account.
|
||||
</p>
|
||||
|
||||
<antd.Button
|
||||
type="primary"
|
||||
onClick={() => props.changeStage(0)}
|
||||
>
|
||||
Go to login
|
||||
</antd.Button>
|
||||
</div>
|
||||
}
|
||||
<antd.Button
|
||||
type="primary"
|
||||
onClick={() => props.setActiveKey("selector")}
|
||||
>
|
||||
Go to login
|
||||
</antd.Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{
|
||||
finishError && <antd.Alert
|
||||
type="error"
|
||||
message={finishError.message}
|
||||
/>
|
||||
}
|
||||
{finishError && (
|
||||
<antd.Alert type="error" message={finishError.message} />
|
||||
)}
|
||||
|
||||
{
|
||||
!finishSuccess && !finishing && <div className="register_form_actions">
|
||||
{
|
||||
step === 0 &&
|
||||
<antd.Button
|
||||
onClick={() => props.changeStage(0)}
|
||||
>
|
||||
Cancel
|
||||
</antd.Button>
|
||||
}
|
||||
{
|
||||
step > 0 &&
|
||||
<antd.Button
|
||||
onClick={() => prevStep()}
|
||||
>
|
||||
Back
|
||||
</antd.Button>
|
||||
}
|
||||
{!finishSuccess && !finishing && (
|
||||
<div className="register_form_actions">
|
||||
{step === 0 && (
|
||||
<antd.Button
|
||||
onClick={() => props.setActiveKey("selector")}
|
||||
>
|
||||
Cancel
|
||||
</antd.Button>
|
||||
)}
|
||||
{step > 0 && (
|
||||
<antd.Button onClick={() => prevStep()}>
|
||||
Back
|
||||
</antd.Button>
|
||||
)}
|
||||
|
||||
<antd.Button
|
||||
type="primary"
|
||||
onClick={() => nextStep()}
|
||||
disabled={!canNextStep()}
|
||||
>
|
||||
{
|
||||
step === steps.length ? "Finish" : "Next"
|
||||
}
|
||||
</antd.Button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<antd.Button
|
||||
type="primary"
|
||||
onClick={() => nextStep()}
|
||||
disabled={!canNextStep()}
|
||||
>
|
||||
{step === steps.length ? "Finish" : "Next"}
|
||||
</antd.Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RegisterForm
|
||||
export default RegisterForm
|
||||
|
@ -4,62 +4,63 @@ import config from "@config"
|
||||
import { Icons } from "@components/Icons"
|
||||
|
||||
const MainSelector = (props) => {
|
||||
const {
|
||||
onClickLogin,
|
||||
onClickRegister,
|
||||
} = props
|
||||
return (
|
||||
<>
|
||||
<div className="content_header">
|
||||
<img src={config.logo.alt} className="logo" />
|
||||
</div>
|
||||
|
||||
return <>
|
||||
<div className="content_header">
|
||||
<img src={config.logo.alt} className="logo" />
|
||||
</div>
|
||||
<div className="actions">
|
||||
{app.userData && (
|
||||
<antd.Button
|
||||
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">
|
||||
{
|
||||
app.userData && <antd.Button
|
||||
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>
|
||||
}
|
||||
<antd.Button
|
||||
onClick={() => app.controls.openLoginForm()}
|
||||
icon={<Icons.FiLogIn />}
|
||||
type="primary"
|
||||
>
|
||||
Continue with a Comty™ Account
|
||||
</antd.Button>
|
||||
|
||||
<antd.Button
|
||||
onClick={onClickLogin}
|
||||
icon={<Icons.FiLogIn />}
|
||||
type="primary"
|
||||
>
|
||||
Continue with a Comty™ Account
|
||||
</antd.Button>
|
||||
<antd.Button
|
||||
onClick={() => app.controls.openLoginForm()}
|
||||
icon={<Icons.FiLogIn />}
|
||||
type="primary"
|
||||
disabled
|
||||
>
|
||||
Continue with a RageStudio© ID™
|
||||
</antd.Button>
|
||||
|
||||
<antd.Button
|
||||
onClick={onClickLogin}
|
||||
icon={<Icons.FiLogIn />}
|
||||
type="primary"
|
||||
disabled
|
||||
>
|
||||
Continue with a RageStudio© ID™
|
||||
</antd.Button>
|
||||
<h4>Or create a new account</h4>
|
||||
|
||||
<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
|
||||
onClick={onClickRegister}
|
||||
icon={<Icons.FiUserPlus />}
|
||||
type="primary"
|
||||
>
|
||||
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>
|
||||
</>
|
||||
<a onClick={() => props.setActiveKey("recovery")}>
|
||||
I need help to recover my account
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MainSelector
|
||||
export default MainSelector
|
||||
|
@ -1,80 +1,82 @@
|
||||
import React from "react"
|
||||
|
||||
import useRandomFeaturedWallpaperUrl from "@hooks/useRandomFeaturedWallpaperUrl"
|
||||
import useUrlQueryActiveKey from "@hooks/useUrlQueryActiveKey"
|
||||
|
||||
import RegisterForm from "./forms/register"
|
||||
import MainSelector from "./forms/selector"
|
||||
import RecoveryForm from "./forms/recovery"
|
||||
|
||||
import "./index.less"
|
||||
|
||||
const GradientSVG = () => {
|
||||
return <svg height="100%" width="100%">
|
||||
<defs>
|
||||
<linearGradient id="0" x1="0" y1="0.5" x2="1" y2="0.5">
|
||||
<stop offset="0%" stop-color="rgba(225, 0, 209, 0.1)" />
|
||||
<stop offset="25%" stop-color="rgba(233, 0, 182, 0.08)" />
|
||||
<stop offset="50%" stop-color="rgba(240, 0, 154, 0.05)" />
|
||||
<stop offset="100%" stop-color="rgba(255, 0, 0, 0)" />
|
||||
</linearGradient>
|
||||
<radialGradient id="1" gradientTransform="translate(-0.81 -0.5) scale(2, 1.2)">
|
||||
<stop offset="0%" stop-color="rgba(255, 96, 100, 0.2)" />
|
||||
<stop offset="20%" stop-color="rgba(255, 96, 100, 0.16)" />
|
||||
<stop offset="40%" stop-color="rgba(255, 96, 100, 0.12)" />
|
||||
<stop offset="60%" stop-color="rgba(255, 96, 100, 0.08)" />
|
||||
<stop offset="100%" stop-color="rgba(255, 96, 100, 0)" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<rect fill="url(#0)" height="100%" width="100%" />
|
||||
<rect fill="url(#1)" height="100%" width="100%" />
|
||||
</svg>
|
||||
return (
|
||||
<svg height="100%" width="100%">
|
||||
<defs>
|
||||
<linearGradient id="0" x1="0" y1="0.5" x2="1" y2="0.5">
|
||||
<stop offset="0%" stop-color="rgba(225, 0, 209, 0.1)" />
|
||||
<stop offset="25%" stop-color="rgba(233, 0, 182, 0.08)" />
|
||||
<stop offset="50%" stop-color="rgba(240, 0, 154, 0.05)" />
|
||||
<stop offset="100%" stop-color="rgba(255, 0, 0, 0)" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="1"
|
||||
gradientTransform="translate(-0.81 -0.5) scale(2, 1.2)"
|
||||
>
|
||||
<stop offset="0%" stop-color="rgba(255, 96, 100, 0.2)" />
|
||||
<stop offset="20%" stop-color="rgba(255, 96, 100, 0.16)" />
|
||||
<stop offset="40%" stop-color="rgba(255, 96, 100, 0.12)" />
|
||||
<stop offset="60%" stop-color="rgba(255, 96, 100, 0.08)" />
|
||||
<stop offset="100%" stop-color="rgba(255, 96, 100, 0)" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<rect fill="url(#0)" height="100%" width="100%" />
|
||||
<rect fill="url(#1)" height="100%" width="100%" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
const stagesToComponents = {
|
||||
0: MainSelector,
|
||||
2: RegisterForm
|
||||
const keyToComponents = {
|
||||
selector: MainSelector,
|
||||
register: RegisterForm,
|
||||
recovery: RecoveryForm,
|
||||
}
|
||||
|
||||
const AuthPage = (props) => {
|
||||
const [stage, setStage] = React.useState(0)
|
||||
const randomWallpaperURL = useRandomFeaturedWallpaperUrl()
|
||||
const [activeKey, setActiveKey] = useUrlQueryActiveKey({
|
||||
defaultKey: "selector",
|
||||
})
|
||||
const randomWallpaperURL = useRandomFeaturedWallpaperUrl()
|
||||
|
||||
function changeStage(nextStage) {
|
||||
setStage(nextStage)
|
||||
}
|
||||
return (
|
||||
<div className="login-page">
|
||||
<div className="background">
|
||||
<GradientSVG />
|
||||
</div>
|
||||
|
||||
const onClickLogin = () => {
|
||||
app.controls.openLoginForm()
|
||||
}
|
||||
<div className="wrapper">
|
||||
<div
|
||||
className="wrapper_background"
|
||||
style={{
|
||||
backgroundImage: randomWallpaperURL
|
||||
? `url(${randomWallpaperURL})`
|
||||
: null,
|
||||
animation: randomWallpaperURL ? "opacityIn 1s" : null,
|
||||
}}
|
||||
/>
|
||||
|
||||
const onClickRegister = () => {
|
||||
changeStage(2)
|
||||
}
|
||||
|
||||
return <div className="login-page">
|
||||
<div className="background">
|
||||
<GradientSVG />
|
||||
</div>
|
||||
|
||||
<div className="wrapper">
|
||||
<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>
|
||||
<div className="content">
|
||||
{React.createElement(
|
||||
keyToComponents[activeKey] ??
|
||||
keyToComponents["selector"],
|
||||
{
|
||||
setActiveKey: setActiveKey,
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AuthPage
|
||||
export default AuthPage
|
||||
|
@ -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
|
@ -1,3 +0,0 @@
|
||||
.password_recover {
|
||||
padding: 20px;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user