mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
use new register component
This commit is contained in:
parent
843186ca33
commit
3e6de98d10
@ -388,7 +388,7 @@ class ComtyApp extends React.Component {
|
||||
"app.no_session": async () => {
|
||||
const location = window.location.pathname
|
||||
|
||||
if (location !== "/" && location !== "/login" && location !== "/register") {
|
||||
if (location !== "/" && location !== "/auth" && location !== "/register") {
|
||||
antd.notification.info({
|
||||
message: "You are not logged in, to use some features you will need to log in.",
|
||||
btn: <antd.Button type="primary" onClick={() => app.goAuth()}>Login</antd.Button>,
|
||||
|
215
packages/app/src/pages/auth/forms/register/index.jsx
Normal file
215
packages/app/src/pages/auth/forms/register/index.jsx
Normal file
@ -0,0 +1,215 @@
|
||||
import React from "react"
|
||||
import * as antd from "antd"
|
||||
|
||||
import { Icons, createIconRender } from "components/Icons"
|
||||
|
||||
import classnames from "classnames"
|
||||
|
||||
import AuthModel from "models/auth"
|
||||
|
||||
import "./index.less"
|
||||
|
||||
import UsernameStep from "./steps/username"
|
||||
import PasswordStep from "./steps/password"
|
||||
import EmailStep from "./steps/email"
|
||||
import TOSStep from "./steps/tos"
|
||||
|
||||
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 [stepsValues, setStepsValues] = React.useState({})
|
||||
const [step, setStep] = React.useState(0)
|
||||
|
||||
const currentStepData = steps[step - 1]
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
if (result) {
|
||||
setFinishing(false)
|
||||
setFinishSuccess(true)
|
||||
}
|
||||
}
|
||||
|
||||
function nextStep(to) {
|
||||
setStep((prev) => {
|
||||
if (!to) {
|
||||
to = prev + 1
|
||||
}
|
||||
|
||||
if (to === steps.length + 1) {
|
||||
finish()
|
||||
return prev
|
||||
}
|
||||
|
||||
return to
|
||||
})
|
||||
}
|
||||
|
||||
function prevStep() {
|
||||
setStep((prev) => {
|
||||
return prev - 1
|
||||
})
|
||||
}
|
||||
|
||||
const updateStepValue = (value) => setStepsValues((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
[currentStepData.key]: value
|
||||
}
|
||||
})
|
||||
|
||||
function canNextStep() {
|
||||
if (!currentStepData) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (!currentStepData.required) {
|
||||
return true
|
||||
}
|
||||
|
||||
const currentStepValue = stepsValues[currentStepData.key]
|
||||
|
||||
if (currentStepData.required) {
|
||||
if (!currentStepValue) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
</>
|
||||
}
|
||||
|
||||
{
|
||||
!finishSuccess && !finishing && step > 0 && <>
|
||||
<h1>
|
||||
{
|
||||
currentStepData?.icon && createIconRender(currentStepData.icon)
|
||||
}
|
||||
|
||||
{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,
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
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>
|
||||
|
||||
<antd.Button
|
||||
type="primary"
|
||||
onClick={() => props.changeStage(0)}
|
||||
>
|
||||
Go to login
|
||||
</antd.Button>
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
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>
|
||||
}
|
||||
|
||||
<antd.Button
|
||||
type="primary"
|
||||
onClick={() => nextStep()}
|
||||
disabled={!canNextStep()}
|
||||
>
|
||||
{
|
||||
step === steps.length ? "Finish" : "Next"
|
||||
}
|
||||
</antd.Button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default RegisterForm
|
77
packages/app/src/pages/auth/forms/register/index.less
Normal file
77
packages/app/src/pages/auth/forms/register/index.less
Normal file
@ -0,0 +1,77 @@
|
||||
.register_form {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 20px;
|
||||
|
||||
height: 100%;
|
||||
|
||||
transition: all 250ms ease-in-out;
|
||||
|
||||
&.welcome_step {
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.register_form_header-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.register_form_step_content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.register_form_creating,
|
||||
.register_form_success {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
height: 100%;
|
||||
|
||||
gap: 10px;
|
||||
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: fit-content;
|
||||
font-size: 1.5rem;
|
||||
|
||||
color: var(--text-color);
|
||||
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.register_form_success {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.register_form_actions {
|
||||
position: absolute;
|
||||
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
|
||||
justify-content: flex-end;
|
||||
|
||||
width: 100%;
|
||||
|
||||
gap: 10px;
|
||||
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
import React from "react"
|
||||
import * as antd from "antd"
|
||||
|
||||
import UserModel from "models/user"
|
||||
|
||||
const EmailStepComponent = (props) => {
|
||||
const [email, setEmail] = React.useState(props.currentValue ?? "")
|
||||
|
||||
const [loading, setLoading] = React.useState(false)
|
||||
const [validFormat, setValidFormat] = React.useState(null)
|
||||
const [emailAvailable, setEmailAvailable] = React.useState(null)
|
||||
|
||||
const isValid = () => {
|
||||
return email.length > 0 && validFormat && emailAvailable
|
||||
}
|
||||
|
||||
const checkIfIsEmail = (email) => {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
if (!isValid()) return
|
||||
|
||||
props.onPressEnter()
|
||||
}
|
||||
|
||||
const handleUpdate = (e) => {
|
||||
setEmail(e.target.value)
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (email.length === 0) {
|
||||
setEmailAvailable(null)
|
||||
setValidFormat(null)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
props.updateValue(null)
|
||||
|
||||
setLoading(true)
|
||||
|
||||
setValidFormat(checkIfIsEmail(email))
|
||||
|
||||
// check if email is available
|
||||
const timer = setTimeout(async () => {
|
||||
if (!validFormat) return
|
||||
|
||||
const request = await UserModel.checkEmailAvailability(email).catch((error) => {
|
||||
antd.message.error(`Cannot check email availability: ${error.message}`)
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
if (request) {
|
||||
setEmailAvailable(request.available)
|
||||
|
||||
if (!request.available) {
|
||||
antd.message.error("Email is already in use")
|
||||
props.updateValue(null)
|
||||
} else {
|
||||
props.updateValue(email)
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
}, 1000)
|
||||
|
||||
return () => clearTimeout(timer)
|
||||
}, [email])
|
||||
|
||||
return <div className="register_form_step_content">
|
||||
<antd.Input
|
||||
defaultValue={props.currentValue}
|
||||
placeholder="Email"
|
||||
onPressEnter={submit}
|
||||
onChange={handleUpdate}
|
||||
status={email.length == 0 ? "default" : loading ? "default" : (isValid() ? "success" : "error")}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default {
|
||||
key: "email",
|
||||
title: "Step 3",
|
||||
icon: "Mail",
|
||||
description: "Enter a email for the account, it can be used to access to your account. \n Will not be shared with anyone else and not be used for marketing purposes.",
|
||||
required: true,
|
||||
content: EmailStepComponent,
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
import React from "react"
|
||||
import * as antd from "antd"
|
||||
|
||||
import "./index.less"
|
||||
|
||||
export const PasswordStepComponent = (props) => {
|
||||
const confirmRef = React.useRef(null)
|
||||
|
||||
const [password, setPassword] = React.useState(props.currentValue ?? "")
|
||||
const [confirmedPassword, setConfirmedPassword] = React.useState(props.currentValue ?? "")
|
||||
const [passwordStrength, setPasswordStrength] = React.useState(null)
|
||||
|
||||
const passwordMinimunStrength = 3
|
||||
const passwordsMatch = password === confirmedPassword
|
||||
const passwordError = !passwordsMatch && confirmedPassword.length > 0
|
||||
|
||||
const submit = () => {
|
||||
if (!passwordError) {
|
||||
props.onPressEnter()
|
||||
}
|
||||
}
|
||||
|
||||
const passwordStrengthCalculator = (password) => {
|
||||
let strength = 0
|
||||
|
||||
if (password.length === 0 || password.length < 8) {
|
||||
return strength
|
||||
}
|
||||
|
||||
strength += 1
|
||||
|
||||
if (password.length >= 12) {
|
||||
strength += 1
|
||||
}
|
||||
|
||||
if (password.match(/[a-z]/)) {
|
||||
strength += 1
|
||||
}
|
||||
|
||||
if (password.match(/[A-Z]/)) {
|
||||
strength += 1
|
||||
}
|
||||
|
||||
if (password.match(/[0-9]/)) {
|
||||
strength += 1
|
||||
}
|
||||
|
||||
if (password.match(/[^a-zA-Z0-9]/)) {
|
||||
strength += 1
|
||||
}
|
||||
|
||||
return strength
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
const calculatedStrength = passwordStrengthCalculator(password)
|
||||
|
||||
setPasswordStrength(calculatedStrength)
|
||||
|
||||
if (password !== confirmedPassword) {
|
||||
props.updateValue(null)
|
||||
}
|
||||
|
||||
if (calculatedStrength < passwordMinimunStrength) {
|
||||
props.updateValue(null)
|
||||
}
|
||||
|
||||
if (calculatedStrength >= passwordMinimunStrength && password === confirmedPassword) {
|
||||
props.updateValue(password)
|
||||
}
|
||||
}, [password, confirmedPassword])
|
||||
|
||||
return <div className="register_form_step_content, passwords_fields">
|
||||
<antd.Input.Password
|
||||
className="password"
|
||||
placeholder="Password"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="none"
|
||||
defaultValue={props.currentValue}
|
||||
onPressEnter={() => {
|
||||
confirmRef.current.focus()
|
||||
}}
|
||||
onChange={(e) => {
|
||||
setPassword(e.target.value)
|
||||
}}
|
||||
status={passwordError ? "error" : "success"}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
<antd.Input.Password
|
||||
className="password"
|
||||
placeholder="Confirm Password"
|
||||
ref={confirmRef}
|
||||
autoCorrect="off"
|
||||
autoCapitalize="none"
|
||||
defaultValue={props.currentValue}
|
||||
onPressEnter={submit}
|
||||
status={passwordError ? "error" : "success"}
|
||||
onChange={(e) => {
|
||||
setConfirmedPassword(e.target.value)
|
||||
}}
|
||||
/>
|
||||
|
||||
<antd.Progress
|
||||
percent={passwordStrength * 20}
|
||||
status={passwordStrength < passwordMinimunStrength ? "exception" : "success"}
|
||||
showInfo={false}
|
||||
/>
|
||||
|
||||
<div className="passwordPolicy">
|
||||
<p>Password must be at least 8 characters long.</p>
|
||||
<p>Password must contain at least one number.</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
key: "password",
|
||||
title: "Step 2",
|
||||
icon: "Key",
|
||||
description: "Enter a password for the account. must comply with the password requirements policy.",
|
||||
required: true,
|
||||
content: PasswordStepComponent,
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
.passwords_fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 10px;
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
import React from "react"
|
||||
import * as antd from "antd"
|
||||
|
||||
import MarkdownReader from "components/MarkdownReader"
|
||||
import config from "config"
|
||||
|
||||
const FrameStyle = {
|
||||
"width": "60vw",
|
||||
"max-width": "60vw",
|
||||
"height": "90vh",
|
||||
"max-height": "90vh",
|
||||
"overflow": "overlay",
|
||||
"justify-content": "flex-start",
|
||||
}
|
||||
|
||||
const LegalDocumentsDecorators = {
|
||||
"terms": "Terms of Service",
|
||||
"privacy": "Privacy Policy",
|
||||
}
|
||||
|
||||
function composeConfirmationCheckboxLabel(documents) {
|
||||
let labels = [
|
||||
"I have read and accept"
|
||||
]
|
||||
|
||||
documents.forEach(([key, value], index) => {
|
||||
const isLast = index === documents.length - 1
|
||||
|
||||
labels.push(`the ${LegalDocumentsDecorators[key] ?? `document (${key})`} ${!isLast ? "and" : ""}`)
|
||||
})
|
||||
|
||||
return labels.join(" ")
|
||||
}
|
||||
|
||||
const TermsOfServiceStepComponent = (props) => {
|
||||
const legalDocuments = Object.entries(config.legal)
|
||||
|
||||
return <div className="register_form_step_content">
|
||||
{
|
||||
Object.entries(config.legal).map(([key, value]) => {
|
||||
if (!value) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <antd.Button
|
||||
key={key}
|
||||
onClick={() => {
|
||||
app.layout.modal.open(key, MarkdownReader, {
|
||||
includeCloseButton: true,
|
||||
frameContentStyle: FrameStyle,
|
||||
props: {
|
||||
url: value
|
||||
}
|
||||
})
|
||||
}}
|
||||
>
|
||||
Read {LegalDocumentsDecorators[key] ?? `document (${key})`}
|
||||
</antd.Button>
|
||||
})
|
||||
}
|
||||
|
||||
<antd.Checkbox
|
||||
defaultChecked={props.currentValue}
|
||||
onChange={(event) => {
|
||||
props.updateValue(event.target.checked)
|
||||
}}
|
||||
>
|
||||
{composeConfirmationCheckboxLabel(legalDocuments)}
|
||||
</antd.Checkbox>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default {
|
||||
key: "tos",
|
||||
title: "Step 3",
|
||||
icon: "FileDone",
|
||||
description: "Take your time to read these legal documents.",
|
||||
required: true,
|
||||
content: TermsOfServiceStepComponent,
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
import React from "react"
|
||||
import * as antd from "antd"
|
||||
|
||||
import { Icons } from "components/Icons"
|
||||
|
||||
import UserModel from "models/user"
|
||||
|
||||
export const UsernameStepComponent = (props) => {
|
||||
const [loading, setLoading] = React.useState(false)
|
||||
const [username, setUsername] = React.useState(props.currentValue ?? "")
|
||||
|
||||
const [validLength, setValidLength] = React.useState(props.currentValue ? true : null)
|
||||
const [validCharacters, setValidCharacters] = React.useState(props.currentValue ? true : null)
|
||||
const [usernameAvailable, setUsernameAvailable] = React.useState(props.currentValue ? true : null)
|
||||
|
||||
const isValid = () => {
|
||||
return username.length > 0 && validCharacters && usernameAvailable
|
||||
}
|
||||
|
||||
const hasValidCharacters = (username) => {
|
||||
return /^[a-z0-9_]+$/.test(username)
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
if (!isValid()) return
|
||||
|
||||
props.onPressEnter()
|
||||
}
|
||||
|
||||
const handleUpdate = (e) => {
|
||||
if (e.target.value === " ") {
|
||||
return
|
||||
}
|
||||
|
||||
e.target.value = e.target.value.toLowerCase()
|
||||
|
||||
setUsername(e.target.value)
|
||||
}
|
||||
|
||||
const renderIndicator = (value, label) => {
|
||||
if (loading) {
|
||||
return <>
|
||||
<Icons.LoadingOutlined
|
||||
style={{
|
||||
color: "var(--text-color)"
|
||||
}}
|
||||
/>
|
||||
<p>{label}</p>
|
||||
</>
|
||||
}
|
||||
|
||||
if (value) {
|
||||
return <>
|
||||
<Icons.CheckCircleOutlined
|
||||
style={{
|
||||
color: "#99F7AB"
|
||||
}}
|
||||
/>
|
||||
<p>{label}</p>
|
||||
</>
|
||||
}
|
||||
|
||||
return <>
|
||||
<Icons.CloseCircleOutlined
|
||||
style={{
|
||||
color: "var(--text-color)"
|
||||
}}
|
||||
/>
|
||||
<p>{label}</p>
|
||||
</>
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (username.length < 3) {
|
||||
setUsernameAvailable(null)
|
||||
setValidCharacters(null)
|
||||
setValidLength(false)
|
||||
|
||||
setLoading(false)
|
||||
|
||||
return
|
||||
} else {
|
||||
setValidLength(true)
|
||||
}
|
||||
|
||||
props.updateValue(null)
|
||||
|
||||
setLoading(true)
|
||||
|
||||
setValidCharacters(hasValidCharacters(username))
|
||||
|
||||
const timer = setTimeout(async () => {
|
||||
if (!validCharacters) {
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
const request = await UserModel.checkUsernameAvailability(username).catch((error) => {
|
||||
app.message.error(`Cannot check username availability: ${error.message}`)
|
||||
console.error(error)
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
if (request) {
|
||||
setUsernameAvailable(request.available)
|
||||
|
||||
if (!request.available) {
|
||||
props.updateValue(null)
|
||||
} else {
|
||||
props.updateValue(username)
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
}, 1000)
|
||||
|
||||
return () => clearTimeout(timer)
|
||||
}, [username])
|
||||
|
||||
return <div className="register_form_step_content">
|
||||
<antd.Input
|
||||
autoCorrect="off"
|
||||
autoCapitalize="none"
|
||||
onPressEnter={submit}
|
||||
placeholder="newuser"
|
||||
value={username}
|
||||
onChange={handleUpdate}
|
||||
status={username.length == 0 ? "default" : loading ? "default" : (isValid() ? "success" : "error")}
|
||||
maxLength={64}
|
||||
/>
|
||||
|
||||
<div className="usernameValidity">
|
||||
<div className="check">
|
||||
{
|
||||
renderIndicator(validLength, "At least 3 characters / Maximum 64 characters")
|
||||
}
|
||||
</div>
|
||||
<div className="check">
|
||||
{
|
||||
renderIndicator(usernameAvailable, "Username available")
|
||||
}
|
||||
</div>
|
||||
<div className="check">
|
||||
{
|
||||
renderIndicator(validCharacters, "Valid characters (letters, numbers, underscores)")
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default {
|
||||
key: "username",
|
||||
title: "Step 1",
|
||||
icon: "User",
|
||||
description: () => <div>
|
||||
<p>Enter your username you gonna use for your account, its used to access to your account and give a easy name to identify you.</p>
|
||||
<p>You can set a diferent public name for your account after registration.</p>
|
||||
</div>,
|
||||
required: true,
|
||||
content: UsernameStepComponent,
|
||||
}
|
76
packages/app/src/pages/auth/forms/selector/index.jsx
Normal file
76
packages/app/src/pages/auth/forms/selector/index.jsx
Normal file
@ -0,0 +1,76 @@
|
||||
import * as antd from "antd"
|
||||
import config from "config"
|
||||
|
||||
import { Icons } from "components/Icons"
|
||||
|
||||
const MainSelector = (props) => {
|
||||
const {
|
||||
onClickLogin,
|
||||
onClickRegister,
|
||||
} = props
|
||||
|
||||
return <>
|
||||
<div className="content_header">
|
||||
<img src={app.isMobile ? config.logo.alt : config.logo.full} className="logo" />
|
||||
</div>
|
||||
|
||||
{
|
||||
app.userData && <div
|
||||
className="actions"
|
||||
style={{
|
||||
marginBottom: "50px"
|
||||
}}
|
||||
>
|
||||
<antd.Button
|
||||
type="default"
|
||||
size="large"
|
||||
onClick={() => {
|
||||
app.navigation.goMain()
|
||||
}}
|
||||
>
|
||||
Continue as {app.userData.username}
|
||||
</antd.Button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className="actions">
|
||||
<antd.Button
|
||||
onClick={onClickLogin}
|
||||
size="large"
|
||||
icon={<Icons.LogIn />}
|
||||
type="primary"
|
||||
>
|
||||
Continue with a Comty™ Account
|
||||
</antd.Button>
|
||||
|
||||
<antd.Button
|
||||
onClick={onClickLogin}
|
||||
size="large"
|
||||
icon={<Icons.LogIn />}
|
||||
type="primary"
|
||||
disabled
|
||||
>
|
||||
Continue with a RageStudio© ID™
|
||||
</antd.Button>
|
||||
</div>
|
||||
|
||||
<h4>Or create a new account</h4>
|
||||
|
||||
<div className="actions">
|
||||
<antd.Button
|
||||
onClick={onClickRegister}
|
||||
icon={<Icons.UserPlus />}
|
||||
type="primary"
|
||||
>
|
||||
Create a Comty™ Account
|
||||
</antd.Button>
|
||||
|
||||
<p>
|
||||
<Icons.Info />
|
||||
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
|
80
packages/app/src/pages/auth/index.jsx
Executable file
80
packages/app/src/pages/auth/index.jsx
Executable file
@ -0,0 +1,80 @@
|
||||
import React from "react"
|
||||
|
||||
import useRandomFeaturedWallpaperUrl from "hooks/useRandomFeaturedWallpaperUrl"
|
||||
|
||||
import RegisterForm from "./forms/register"
|
||||
import MainSelector from "./forms/selector"
|
||||
|
||||
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>
|
||||
}
|
||||
|
||||
const stagesToComponents = {
|
||||
0: MainSelector,
|
||||
2: RegisterForm
|
||||
}
|
||||
|
||||
const AuthPage = (props) => {
|
||||
const [stage, setStage] = React.useState(0)
|
||||
const randomWallpaperURL = useRandomFeaturedWallpaperUrl()
|
||||
|
||||
function changeStage(nextStage) {
|
||||
setStage(nextStage)
|
||||
}
|
||||
|
||||
const onClickLogin = () => {
|
||||
app.controls.openLoginForm()
|
||||
}
|
||||
|
||||
const onClickRegister = () => {
|
||||
changeStage(2)
|
||||
}
|
||||
|
||||
return <div className="loginPage">
|
||||
<div className="background">
|
||||
<GradientSVG />
|
||||
</div>
|
||||
|
||||
<div className="wrapper">
|
||||
<div
|
||||
className="wrapper_background"
|
||||
style={{
|
||||
backgroundImage: randomWallpaperURL ? `url(${randomWallpaperURL.url})` : null,
|
||||
animation: randomWallpaperURL ? "opacityIn 1s" : null
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="content">
|
||||
{
|
||||
React.createElement(stagesToComponents[stage] ?? stagesToComponents[0], {
|
||||
onClickLogin,
|
||||
onClickRegister,
|
||||
changeStage,
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default AuthPage
|
@ -39,22 +39,33 @@
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
position: relative;
|
||||
z-index: 55;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
position: relative;
|
||||
width: 55vw;
|
||||
max-width: 800px;
|
||||
|
||||
z-index: 55;
|
||||
overflow: hidden;
|
||||
|
||||
height: 50vh;
|
||||
max-height: 500px;
|
||||
|
||||
transition: all 250ms ease-in-out;
|
||||
|
||||
background-color: var(--background-color-accent);
|
||||
outline: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
|
||||
min-height: 50vh;
|
||||
outline: 1px solid var(--border-color);
|
||||
|
||||
border-radius: 12px;
|
||||
|
||||
.wrapper_background {
|
||||
height: 100%;
|
||||
width: 300px;
|
||||
|
||||
min-width: 250px;
|
||||
width: 250px;
|
||||
|
||||
border-radius: 12px;
|
||||
|
||||
@ -70,7 +81,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
|
||||
padding: 40px;
|
||||
|
@ -1,142 +0,0 @@
|
||||
import React from "react"
|
||||
import * as antd from "antd"
|
||||
import config from "config"
|
||||
|
||||
import { Icons } from "components/Icons"
|
||||
import { Footer } from "components"
|
||||
|
||||
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>
|
||||
}
|
||||
|
||||
export default (props) => {
|
||||
const [wallpaperData, setWallpaperData] = React.useState(null)
|
||||
|
||||
const setRandomWallpaper = async () => {
|
||||
const { data: featuredWallpapers } = await app.cores.api.customRequest({
|
||||
method: "GET",
|
||||
url: "/featured_wallpapers"
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
return []
|
||||
})
|
||||
|
||||
// get random wallpaper from array
|
||||
const randomWallpaper = featuredWallpapers[Math.floor(Math.random() * featuredWallpapers.length)]
|
||||
|
||||
setWallpaperData(randomWallpaper)
|
||||
}
|
||||
|
||||
const onClickRegister = () => {
|
||||
app.controls.openRegisterForm()
|
||||
}
|
||||
|
||||
const onClickLogin = () => {
|
||||
app.controls.openLoginForm()
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
setRandomWallpaper()
|
||||
}, [])
|
||||
|
||||
return <div className="loginPage">
|
||||
<div className="background">
|
||||
<GradientSVG />
|
||||
</div>
|
||||
|
||||
<div className="wrapper">
|
||||
<div
|
||||
className="wrapper_background"
|
||||
style={{
|
||||
backgroundImage: wallpaperData ? `url(${wallpaperData.url})` : null,
|
||||
animation: wallpaperData ? "opacityIn 1s" : null
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="content">
|
||||
<div className="content_header">
|
||||
<img src={app.isMobile ? config.logo.alt : config.logo.full} className="logo" />
|
||||
</div>
|
||||
|
||||
{
|
||||
app.userData && <div
|
||||
className="actions"
|
||||
style={{
|
||||
marginBottom: "50px"
|
||||
}}
|
||||
>
|
||||
<antd.Button
|
||||
type="default"
|
||||
size="large"
|
||||
onClick={() => {
|
||||
app.navigation.goMain()
|
||||
}}
|
||||
>
|
||||
Continue as {app.userData.username}
|
||||
</antd.Button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className="actions">
|
||||
<antd.Button
|
||||
onClick={onClickLogin}
|
||||
size="large"
|
||||
icon={<Icons.LogIn />}
|
||||
type="primary"
|
||||
>
|
||||
Continue with a Comty™ Account
|
||||
</antd.Button>
|
||||
|
||||
<antd.Button
|
||||
onClick={onClickLogin}
|
||||
size="large"
|
||||
icon={<Icons.LogIn />}
|
||||
type="primary"
|
||||
disabled
|
||||
>
|
||||
Continue with a RageStudio© ID™
|
||||
</antd.Button>
|
||||
</div>
|
||||
|
||||
<h4>Or create a new account</h4>
|
||||
|
||||
<div className="actions">
|
||||
<antd.Button
|
||||
onClick={onClickRegister}
|
||||
icon={<Icons.UserPlus />}
|
||||
type="primary"
|
||||
>
|
||||
Create a Comty™ Account
|
||||
</antd.Button>
|
||||
|
||||
<p>
|
||||
<Icons.Info />
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <Footer /> */}
|
||||
</div>
|
||||
}
|
@ -2,8 +2,10 @@ import React from "react"
|
||||
import MarkdownReader from "components/MarkdownReader"
|
||||
import config from "config"
|
||||
|
||||
export default () => {
|
||||
const PrivacyReader = () => {
|
||||
return <MarkdownReader
|
||||
url={config.legal.privacy}
|
||||
/>
|
||||
}
|
||||
|
||||
export default PrivacyReader
|
Loading…
x
Reference in New Issue
Block a user