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"
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

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 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

View File

@ -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

View File

@ -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

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;
}