Merge pull request #17 from jedmund/auth-cleanup
Cleanup sign up and login modals
This commit is contained in:
commit
210b30ac2b
9 changed files with 340 additions and 264 deletions
|
|
@ -132,5 +132,7 @@
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,6 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const HeaderMenu = (props: Props) => {
|
const HeaderMenu = (props: Props) => {
|
||||||
const { open: signupOpen, openModal: openSignupModal, closeModal: closeSignupModal } = useSignupModal()
|
|
||||||
const { open: loginOpen, openModal: openLoginModal, closeModal: closeLoginModal } = useLoginModal()
|
|
||||||
const { open: aboutOpen, openModal: openAboutModal, closeModal: closeAboutModal } = useAboutModal()
|
|
||||||
|
|
||||||
function authItems() {
|
function authItems() {
|
||||||
return (
|
return (
|
||||||
<nav>
|
<nav>
|
||||||
|
|
@ -74,25 +70,9 @@ const HeaderMenu = (props: Props) => {
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
<div className="MenuGroup">
|
<div className="MenuGroup">
|
||||||
<li className="MenuItem" onClick={openLoginModal}>
|
<LoginModal />
|
||||||
<span>Log in</span>
|
<SignupModal />
|
||||||
</li>
|
|
||||||
{loginOpen ? (
|
|
||||||
<LoginModal
|
|
||||||
close={closeLoginModal}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<li className="MenuItem" onClick={openSignupModal}>
|
|
||||||
<span>Sign up</span>
|
|
||||||
</li>
|
|
||||||
{signupOpen ? (
|
|
||||||
<SignupModal
|
|
||||||
close={closeSignupModal}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,31 @@
|
||||||
.LoginForm {
|
.Login.Dialog form {
|
||||||
#fields {
|
display: flex;
|
||||||
display: flex;
|
flex-direction: column;
|
||||||
flex-direction: column;
|
gap: $unit / 2;
|
||||||
gap: $unit;
|
margin-bottom: $unit;
|
||||||
|
|
||||||
|
.Button {
|
||||||
|
font-size: $font-regular;
|
||||||
|
padding: ($unit * 1.5) ($unit * 2);
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&.btn-disabled {
|
||||||
|
background: $grey-90;
|
||||||
|
color: $grey-70;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.btn-disabled) {
|
||||||
|
background: $grey-90;
|
||||||
|
color: $grey-40;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $grey-80;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.fieldset {
|
|
||||||
display: flex;
|
input {
|
||||||
flex-direction: column;
|
background: $grey-90;
|
||||||
gap: 4px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,18 @@
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { withCookies, Cookies } from 'react-cookie'
|
import { useCookies } from 'react-cookie'
|
||||||
import { createPortal } from 'react-dom'
|
|
||||||
|
import * as Dialog from '@radix-ui/react-dialog'
|
||||||
|
|
||||||
import api from '~utils/api'
|
import api from '~utils/api'
|
||||||
import { accountState } from '~utils/accountState'
|
import { accountState } from '~utils/accountState'
|
||||||
|
|
||||||
import Button from '~components/Button'
|
import Button from '~components/Button'
|
||||||
import Fieldset from '~components/Fieldset'
|
import Fieldset from '~components/Fieldset'
|
||||||
import Modal from '~components/Modal'
|
|
||||||
import Overlay from '~components/Overlay'
|
|
||||||
|
|
||||||
|
import CrossIcon from '~public/icons/Cross.svg'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {}
|
||||||
cookies: Cookies
|
|
||||||
close: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ErrorMap {
|
interface ErrorMap {
|
||||||
[index: string]: string
|
[index: string]: string
|
||||||
|
|
@ -26,31 +23,40 @@ interface ErrorMap {
|
||||||
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||||
|
|
||||||
const LoginModal = (props: Props) => {
|
const LoginModal = (props: Props) => {
|
||||||
const emailInput: React.RefObject<HTMLInputElement> = React.createRef()
|
// Set up form states and error handling
|
||||||
const passwordInput: React.RefObject<HTMLInputElement> = React.createRef()
|
|
||||||
const form: React.RefObject<HTMLInputElement>[] = [emailInput, passwordInput]
|
|
||||||
|
|
||||||
const [formValid, setFormValid] = useState(false)
|
const [formValid, setFormValid] = useState(false)
|
||||||
const [errors, setErrors] = useState<ErrorMap>({
|
const [errors, setErrors] = useState<ErrorMap>({
|
||||||
email: '',
|
email: '',
|
||||||
password: ''
|
password: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
|
// Cookies
|
||||||
event.preventDefault()
|
const [cookies, setCookies] = useCookies()
|
||||||
|
|
||||||
|
// States
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
|
// Set up form refs
|
||||||
|
const emailInput: React.RefObject<HTMLInputElement> = React.createRef()
|
||||||
|
const passwordInput: React.RefObject<HTMLInputElement> = React.createRef()
|
||||||
|
const form: React.RefObject<HTMLInputElement>[] = [emailInput, passwordInput]
|
||||||
|
|
||||||
|
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
const { name, value } = event.target
|
const { name, value } = event.target
|
||||||
let newErrors = errors
|
let newErrors = {...errors}
|
||||||
|
|
||||||
switch(name) {
|
switch(name) {
|
||||||
case 'email':
|
case 'email':
|
||||||
errors.email = emailRegex.test(value)
|
if (value.length == 0)
|
||||||
? ''
|
newErrors.email = 'Please enter your email'
|
||||||
: 'That email address is not valid'
|
else if (!emailRegex.test(value))
|
||||||
|
newErrors.email = 'That email address is not valid'
|
||||||
|
else
|
||||||
|
newErrors.email = ''
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'password':
|
case 'password':
|
||||||
errors.password = value.length == 0
|
newErrors.password = value.length == 0
|
||||||
? 'Please enter your password'
|
? 'Please enter your password'
|
||||||
: ''
|
: ''
|
||||||
break
|
break
|
||||||
|
|
@ -60,10 +66,10 @@ const LoginModal = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
setErrors(newErrors)
|
setErrors(newErrors)
|
||||||
setFormValid(validateForm())
|
setFormValid(validateForm(newErrors))
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateForm() {
|
function validateForm(errors: ErrorMap) {
|
||||||
let valid = true
|
let valid = true
|
||||||
|
|
||||||
Object.values(form).forEach(
|
Object.values(form).forEach(
|
||||||
|
|
@ -77,7 +83,7 @@ const LoginModal = (props: Props) => {
|
||||||
return valid
|
return valid
|
||||||
}
|
}
|
||||||
|
|
||||||
function submit(event: React.FormEvent) {
|
function login(event: React.FormEvent) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
|
|
@ -89,65 +95,76 @@ const LoginModal = (props: Props) => {
|
||||||
if (formValid) {
|
if (formValid) {
|
||||||
api.login(body)
|
api.login(body)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
const cookies = props.cookies
|
|
||||||
|
|
||||||
const cookieObj = {
|
const cookieObj = {
|
||||||
user_id: response.data.user.id,
|
user_id: response.data.user.id,
|
||||||
username: response.data.user.username,
|
username: response.data.user.username,
|
||||||
access_token: response.data.access_token
|
access_token: response.data.access_token
|
||||||
}
|
}
|
||||||
|
|
||||||
cookies.set('user', cookieObj, { path: '/'})
|
setCookies('user', cookieObj, { path: '/'})
|
||||||
accountState.account.authorized = true
|
accountState.account.authorized = true
|
||||||
accountState.account.user = {
|
accountState.account.user = {
|
||||||
id: cookieObj.user_id,
|
id: cookieObj.user_id,
|
||||||
username: cookieObj.username
|
username: cookieObj.username
|
||||||
}
|
}
|
||||||
|
|
||||||
props.close()
|
setOpen(false)
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
function openChange(open: boolean) {
|
||||||
createPortal(
|
setOpen(open)
|
||||||
<div>
|
setErrors({
|
||||||
<Modal
|
email: '',
|
||||||
title="Log in"
|
password: ''
|
||||||
styleName="LoginForm"
|
})
|
||||||
close={ () => {} }
|
}
|
||||||
>
|
|
||||||
<form className="form" onSubmit={submit}>
|
|
||||||
<div id="fields">
|
|
||||||
<Fieldset
|
|
||||||
fieldName="email"
|
|
||||||
placeholder="Email address"
|
|
||||||
onChange={handleChange}
|
|
||||||
error={errors.email}
|
|
||||||
ref={emailInput}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Fieldset
|
return (
|
||||||
fieldName="password"
|
<Dialog.Root open={open} onOpenChange={openChange}>
|
||||||
placeholder="Password"
|
<Dialog.Trigger asChild>
|
||||||
onChange={handleChange}
|
<li className="MenuItem">
|
||||||
error={errors.password}
|
<span>Log in</span>
|
||||||
ref={passwordInput}
|
</li>
|
||||||
/>
|
</Dialog.Trigger>
|
||||||
</div>
|
<Dialog.Portal>
|
||||||
<div id="ModalBottom">
|
<Dialog.Content className="Login Dialog" onOpenAutoFocus={ (event) => event.preventDefault() }>
|
||||||
<a>Forgot your password?</a>
|
<div className="DialogHeader">
|
||||||
<Button disabled={!formValid}>Log in</Button>
|
<Dialog.Title className="DialogTitle">Log in</Dialog.Title>
|
||||||
</div>
|
<Dialog.Close className="DialogClose" asChild>
|
||||||
|
<span>
|
||||||
|
<CrossIcon />
|
||||||
|
</span>
|
||||||
|
</Dialog.Close>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form className="form" onSubmit={login}>
|
||||||
|
<Fieldset
|
||||||
|
fieldName="email"
|
||||||
|
placeholder="Email address"
|
||||||
|
onChange={handleChange}
|
||||||
|
error={errors.email}
|
||||||
|
ref={emailInput}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Fieldset
|
||||||
|
fieldName="password"
|
||||||
|
placeholder="Password"
|
||||||
|
onChange={handleChange}
|
||||||
|
error={errors.password}
|
||||||
|
ref={passwordInput}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button disabled={!formValid}>Log in</Button>
|
||||||
</form>
|
</form>
|
||||||
</Modal>
|
</Dialog.Content>
|
||||||
<Overlay onClick={props.close} />
|
<Dialog.Overlay className="Overlay" />
|
||||||
</div>,
|
</Dialog.Portal>
|
||||||
document.body
|
</Dialog.Root>
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withCookies(LoginModal)
|
export default LoginModal
|
||||||
|
|
@ -1,67 +1,47 @@
|
||||||
.SignupForm {
|
.Signup.Dialog form {
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.SignupForm form {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.SignupForm #fields {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: $unit / 2;
|
||||||
margin-bottom: 8px;
|
margin-bottom: $unit;
|
||||||
}
|
|
||||||
|
|
||||||
#ModalTop {
|
.Button {
|
||||||
display: flex;
|
font-size: $font-regular;
|
||||||
flex-direction: row;
|
padding: ($unit * 1.5) ($unit * 2);
|
||||||
align-items: center;
|
width: 100%;
|
||||||
margin-bottom: 16px;
|
|
||||||
margin-right: -8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ModalTop h1 {
|
&.btn-disabled {
|
||||||
margin: 0;
|
background: $grey-90;
|
||||||
font-size: $font-xlarge;
|
color: $grey-70;
|
||||||
text-align: left;
|
cursor: not-allowed;
|
||||||
flex-grow: 1;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#ModalTop i {
|
&:not(.btn-disabled) {
|
||||||
padding: 8px;
|
background: $grey-90;
|
||||||
}
|
color: $grey-40;
|
||||||
|
|
||||||
#ModalTop i:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
background: $grey-80;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ModalTop i:hover svg {
|
.terms {
|
||||||
color: #444;
|
color: $grey-40;
|
||||||
}
|
font-size: $font-small;
|
||||||
|
line-height: 1.2;
|
||||||
|
margin-top: $unit;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
#ModalTop svg {
|
a {
|
||||||
color: #888;
|
color: $blue;
|
||||||
height: 18px;
|
|
||||||
width: 18px;
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ModalBottom {
|
&:hover {
|
||||||
display: flex;
|
color: darken($blue, 30);
|
||||||
flex-direction: row;
|
}
|
||||||
justify-content: flex-end;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ModalBottom a {
|
input {
|
||||||
color: #666;
|
background: $grey-90;
|
||||||
font-size: $font-regular;
|
}
|
||||||
font-weight: 500;
|
|
||||||
flex-grow: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ModalBottom .Button {
|
|
||||||
min-width: 88px;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,18 @@
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { withCookies, Cookies } from 'react-cookie'
|
import { useCookies } from 'react-cookie'
|
||||||
import { createPortal } from 'react-dom'
|
|
||||||
|
import * as Dialog from '@radix-ui/react-dialog'
|
||||||
|
|
||||||
import api from '~utils/api'
|
import api from '~utils/api'
|
||||||
import { accountState } from '~utils/accountState'
|
import { accountState } from '~utils/accountState'
|
||||||
|
|
||||||
import Button from '~components/Button'
|
import Button from '~components/Button'
|
||||||
import Fieldset from '~components/Fieldset'
|
import Fieldset from '~components/Fieldset'
|
||||||
import Modal from '~components/Modal'
|
|
||||||
import Overlay from '~components/Overlay'
|
|
||||||
|
|
||||||
|
import CrossIcon from '~public/icons/Cross.svg'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {}
|
||||||
cookies: Cookies
|
|
||||||
close: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ErrorMap {
|
interface ErrorMap {
|
||||||
[index: string]: string
|
[index: string]: string
|
||||||
|
|
@ -28,6 +25,7 @@ interface ErrorMap {
|
||||||
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||||
|
|
||||||
const SignupModal = (props: Props) => {
|
const SignupModal = (props: Props) => {
|
||||||
|
// Set up form states and error handling
|
||||||
const [formValid, setFormValid] = useState(false)
|
const [formValid, setFormValid] = useState(false)
|
||||||
const [errors, setErrors] = useState<ErrorMap>({
|
const [errors, setErrors] = useState<ErrorMap>({
|
||||||
username: '',
|
username: '',
|
||||||
|
|
@ -36,33 +34,20 @@ const SignupModal = (props: Props) => {
|
||||||
passwordConfirmation: ''
|
passwordConfirmation: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Cookies
|
||||||
|
const [cookies, setCookies] = useCookies()
|
||||||
|
|
||||||
|
// States
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
|
// Set up form refs
|
||||||
const usernameInput = React.createRef<HTMLInputElement>()
|
const usernameInput = React.createRef<HTMLInputElement>()
|
||||||
const emailInput = React.createRef<HTMLInputElement>()
|
const emailInput = React.createRef<HTMLInputElement>()
|
||||||
const passwordInput = React.createRef<HTMLInputElement>()
|
const passwordInput = React.createRef<HTMLInputElement>()
|
||||||
const passwordConfirmationInput = React.createRef<HTMLInputElement>()
|
const passwordConfirmationInput = React.createRef<HTMLInputElement>()
|
||||||
const form = [usernameInput, emailInput, passwordInput, passwordConfirmationInput]
|
const form = [usernameInput, emailInput, passwordInput, passwordConfirmationInput]
|
||||||
|
|
||||||
function check(event: React.ChangeEvent<HTMLInputElement>) {
|
function register(event: React.FormEvent) {
|
||||||
const name = event.target.name
|
|
||||||
const value = event.target.value
|
|
||||||
|
|
||||||
if (value.length > 0 && errors[name].length == 0) {
|
|
||||||
const newErrors = {...errors}
|
|
||||||
|
|
||||||
api.check(name, value)
|
|
||||||
.then((response) => {
|
|
||||||
if (!response.data.available) {
|
|
||||||
newErrors[name] = `This ${name} is already in use`
|
|
||||||
}
|
|
||||||
|
|
||||||
setErrors(newErrors)
|
|
||||||
}, (error) => {
|
|
||||||
console.error(error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function process(event: React.FormEvent) {
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
|
|
@ -74,50 +59,76 @@ const SignupModal = (props: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formValid) {
|
if (formValid)
|
||||||
api.endpoints.users.create(body)
|
api.endpoints.users.create(body)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
const cookies = props.cookies
|
// Set cookies
|
||||||
cookies.set('user', response.data.user, { path: '/'})
|
setCookies('user', response.data.user, { path: '/'})
|
||||||
|
|
||||||
|
// Set states
|
||||||
accountState.account.authorized = true
|
accountState.account.authorized = true
|
||||||
accountState.account.user = {
|
accountState.account.user = {
|
||||||
id: response.data.user.id,
|
id: response.data.user.id,
|
||||||
username: response.data.user.username
|
username: response.data.user.username
|
||||||
}
|
}
|
||||||
|
|
||||||
props.close()
|
// Close the modal
|
||||||
|
setOpen(false)
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
})
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNameChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
const fieldName = event.target.name
|
||||||
|
const value = event.target.value
|
||||||
|
|
||||||
|
if (value.length >= 3) {
|
||||||
|
api.check(fieldName, value)
|
||||||
|
.then((response) => {
|
||||||
|
processNameCheck(fieldName, value, response.data.available)
|
||||||
|
}, (error) => {
|
||||||
|
console.error(error)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
validateName(fieldName, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateForm() {
|
function processNameCheck(fieldName: string, value: string, available: boolean) {
|
||||||
let valid = true
|
const newErrors = {...errors}
|
||||||
|
|
||||||
Object.values(form).forEach(
|
if (available) {
|
||||||
(input) => input.current?.value.length == 0 && (valid = false)
|
// Continue checking for errors
|
||||||
)
|
newErrors[fieldName] = ''
|
||||||
|
setErrors(newErrors)
|
||||||
|
setFormValid(true)
|
||||||
|
|
||||||
Object.values(errors).forEach(
|
validateName(fieldName, value)
|
||||||
(error) => error.length > 0 && (valid = false)
|
} else {
|
||||||
)
|
newErrors[fieldName] = `This ${fieldName} is already in use`
|
||||||
|
setErrors(newErrors)
|
||||||
return valid
|
setFormValid(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
|
function validateName(fieldName: string, value: string) {
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
const { name, value } = event.target
|
|
||||||
let newErrors = {...errors}
|
let newErrors = {...errors}
|
||||||
|
|
||||||
switch(name) {
|
switch(fieldName) {
|
||||||
case 'username':
|
case 'username':
|
||||||
newErrors.username = value.length < 3
|
if (value.length < 3)
|
||||||
? 'Username must be at least 3 characters'
|
newErrors.username = 'Username must be at least 3 characters'
|
||||||
: ''
|
else if (value.length > 20)
|
||||||
|
newErrors.username = 'Username must be less than 20 characters'
|
||||||
|
else
|
||||||
|
newErrors.username = ''
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'email':
|
case 'email':
|
||||||
|
|
@ -126,6 +137,21 @@ const SignupModal = (props: Props) => {
|
||||||
: 'That email address is not valid'
|
: 'That email address is not valid'
|
||||||
break
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
setErrors(newErrors)
|
||||||
|
setFormValid(validateForm(newErrors))
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePasswordChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
const { name, value } = event.target
|
||||||
|
let newErrors = {...errors}
|
||||||
|
|
||||||
|
switch(name) {
|
||||||
case 'password':
|
case 'password':
|
||||||
newErrors.password = passwordInput.current?.value.includes(usernameInput.current?.value!)
|
newErrors.password = passwordInput.current?.value.includes(usernameInput.current?.value!)
|
||||||
? 'Your password should not contain your username'
|
? 'Your password should not contain your username'
|
||||||
|
|
@ -149,64 +175,96 @@ const SignupModal = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
setErrors(newErrors)
|
setErrors(newErrors)
|
||||||
setFormValid(validateForm())
|
setFormValid(validateForm(newErrors))
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateForm(errors: ErrorMap) {
|
||||||
|
let valid = true
|
||||||
|
|
||||||
|
Object.values(form).forEach(
|
||||||
|
(input) => input.current?.value.length == 0 && (valid = false)
|
||||||
|
)
|
||||||
|
|
||||||
|
Object.values(errors).forEach(
|
||||||
|
(error) => error.length > 0 && (valid = false)
|
||||||
|
)
|
||||||
|
|
||||||
|
return valid
|
||||||
|
}
|
||||||
|
|
||||||
|
function openChange(open: boolean) {
|
||||||
|
setOpen(open)
|
||||||
|
setErrors({
|
||||||
|
username: '',
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
passwordConfirmation: ''
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
createPortal(
|
<Dialog.Root open={open} onOpenChange={openChange}>
|
||||||
<div>
|
<Dialog.Trigger asChild>
|
||||||
<Modal
|
<li className="MenuItem">
|
||||||
title="Sign up"
|
<span>Sign up</span>
|
||||||
styleName="SignupForm"
|
</li>
|
||||||
close={ () => {} }
|
</Dialog.Trigger>
|
||||||
>
|
<Dialog.Portal>
|
||||||
<form className="form" onSubmit={process}>
|
<Dialog.Content className="Signup Dialog" onOpenAutoFocus={ (event) => event.preventDefault() }>
|
||||||
<div id="fields">
|
<div className="DialogHeader">
|
||||||
<Fieldset
|
<Dialog.Title className="DialogTitle">Sign up</Dialog.Title>
|
||||||
fieldName="username"
|
<Dialog.Close className="DialogClose" asChild>
|
||||||
placeholder="Username"
|
<span>
|
||||||
onBlur={check}
|
<CrossIcon />
|
||||||
onChange={handleChange}
|
</span>
|
||||||
error={errors.username}
|
</Dialog.Close>
|
||||||
ref={usernameInput}
|
</div>
|
||||||
/>
|
|
||||||
|
|
||||||
<Fieldset
|
<form className="form" onSubmit={register}>
|
||||||
fieldName="email"
|
<Fieldset
|
||||||
placeholder="Email address"
|
fieldName="username"
|
||||||
onBlur={check}
|
placeholder="Username"
|
||||||
onChange={handleChange}
|
onChange={handleNameChange}
|
||||||
error={errors.email}
|
error={errors.username}
|
||||||
ref={emailInput}
|
ref={usernameInput}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Fieldset
|
<Fieldset
|
||||||
fieldName="password"
|
fieldName="email"
|
||||||
placeholder="Password"
|
placeholder="Email address"
|
||||||
onChange={handleChange}
|
onChange={handleNameChange}
|
||||||
error={errors.password}
|
error={errors.email}
|
||||||
ref={passwordInput}
|
ref={emailInput}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Fieldset
|
<Fieldset
|
||||||
fieldName="confirm_password"
|
fieldName="password"
|
||||||
placeholder="Password (again)"
|
placeholder="Password"
|
||||||
onChange={handleChange}
|
onChange={handlePasswordChange}
|
||||||
error={errors.passwordConfirmation}
|
error={errors.password}
|
||||||
ref={passwordConfirmationInput}
|
ref={passwordInput}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div id="ModalBottom">
|
<Fieldset
|
||||||
<Button disabled={!formValid}>Sign up</Button>
|
fieldName="confirm_password"
|
||||||
</div>
|
placeholder="Password (again)"
|
||||||
|
onChange={handlePasswordChange}
|
||||||
|
error={errors.passwordConfirmation}
|
||||||
|
ref={passwordConfirmationInput}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button disabled={!formValid}>Sign up</Button>
|
||||||
|
|
||||||
|
<Dialog.Description className="terms">
|
||||||
|
By signing up, I agree to the<br /><a href="#">Terms and Conditions</a> and <a href="#">Usage Guidelines</a>.
|
||||||
|
</Dialog.Description>
|
||||||
</form>
|
</form>
|
||||||
</Modal>
|
</Dialog.Content>
|
||||||
<Overlay onClick={props.close} />
|
<Dialog.Overlay className="Overlay" />
|
||||||
</div>,
|
</Dialog.Portal>
|
||||||
document.body
|
</Dialog.Root>
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default withCookies(SignupModal)
|
export default SignupModal
|
||||||
|
|
@ -5,7 +5,7 @@ import clonedeep from 'lodash.clonedeep'
|
||||||
import { useSnapshot } from 'valtio'
|
import { useSnapshot } from 'valtio'
|
||||||
|
|
||||||
import api from '~utils/api'
|
import api from '~utils/api'
|
||||||
import { accountState } from '~utils/accountState'
|
import { accountState, initialAccountState } from '~utils/accountState'
|
||||||
import { appState, initialAppState } from '~utils/appState'
|
import { appState, initialAppState } from '~utils/appState'
|
||||||
|
|
||||||
import Header from '~components/Header'
|
import Header from '~components/Header'
|
||||||
|
|
@ -51,10 +51,15 @@ const TopHeader = () => {
|
||||||
function logout() {
|
function logout() {
|
||||||
removeCookie('user')
|
removeCookie('user')
|
||||||
|
|
||||||
accountState.authorized = false
|
// Clean state
|
||||||
|
const resetState = clonedeep(initialAccountState)
|
||||||
|
Object.keys(resetState).forEach((key) => {
|
||||||
|
if (key !== 'language')
|
||||||
|
accountState[key] = resetState[key]
|
||||||
|
})
|
||||||
|
|
||||||
appState.party.editable = false
|
appState.party.editable = false
|
||||||
|
|
||||||
// TODO: How can we log out without navigating to root
|
|
||||||
router.push('/')
|
router.push('/')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,16 +90,18 @@ select {
|
||||||
}
|
}
|
||||||
|
|
||||||
.Dialog {
|
.Dialog {
|
||||||
|
$multiplier: 4;
|
||||||
|
|
||||||
animation: 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0s 1 normal none running openModal;
|
animation: 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0s 1 normal none running openModal;
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: $unit;
|
border-radius: $unit;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: $unit * 3;
|
gap: $unit * $multiplier;
|
||||||
height: auto;
|
height: auto;
|
||||||
min-width: $unit * 48;
|
min-width: $unit * 48;
|
||||||
min-height: $unit * 12;
|
min-height: $unit * 12;
|
||||||
padding: $unit * 3;
|
padding: $unit * $multiplier;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
|
|
@ -108,28 +110,43 @@ select {
|
||||||
|
|
||||||
.DialogHeader {
|
.DialogHeader {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: $unit;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
gap: $unit;
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: $grey-40;
|
||||||
|
font-size: $font-small;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.DialogClose {
|
.DialogClose {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
height: 21px;
|
|
||||||
width: 21px;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
fill: $grey-00;
|
fill: $error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
fill: $grey-40;
|
fill: $grey-40;
|
||||||
|
float: right;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.DialogTitle {
|
.DialogTitle {
|
||||||
font-size: $font-large;
|
font-size: $font-xlarge;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue