Skip to content

Commit

Permalink
feat(front): traduit et simplifie le formulaire de création de compte
Browse files Browse the repository at this point in the history
  • Loading branch information
ggrossetie authored and thom4parisot committed Dec 19, 2024
1 parent d699f90 commit 9a5dd74
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 120 deletions.
48 changes: 28 additions & 20 deletions front/src/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React, { useMemo } from 'react'
import { LifeBuoy } from 'react-feather'
import { useSelector } from 'react-redux'
import { Link, Route, Switch } from 'react-router-dom'
import { NavLink, Route, Switch } from 'react-router-dom'
import { useTranslation } from 'react-i18next'

import logoContent from '/images/logo.svg?inline'
import { useActiveWorkspace } from '../hooks/workspace.js'

import styles from './header.module.scss'
import LanguagesMenu from './header/LanguagesMenu.jsx'
import UserMenu from './header/UserMenu.jsx'
import LanguagesIcon from './header/LanguagesIcon.jsx'

function Header() {
const activeWorkspace = useActiveWorkspace()
Expand All @@ -18,6 +18,7 @@ function Header() {
[activeWorkspace]
)
const connected = useSelector((state) => state.loggedIn)
const { t } = useTranslation()

return (
<Switch>
Expand All @@ -26,35 +27,35 @@ function Header() {
<header className={styles.headerContainer}>
<section className={styles.header}>
<h1 className={styles.logo}>
<Link to="/">
<NavLink to="/">
<img src={logoContent} alt="Stylo" title="Stylo" />
</Link>
</NavLink>
</h1>
{connected && (
<>
<nav>
<ul className={styles.menuLinks}>
<li>
<Link
<NavLink
to={
activeWorkspaceId
? `/workspaces/${activeWorkspaceId}/articles`
: '/articles'
}
>
Articles
</Link>
</NavLink>
</li>
<li>
<Link
<NavLink
to={
activeWorkspaceId
? `/workspaces/${activeWorkspaceId}/books`
: '/books'
}
>
Corpus
</Link>
</NavLink>
</li>
</ul>
</nav>
Expand All @@ -74,18 +75,25 @@ function Header() {
</>
)}
{!connected && (
<nav>
<ul className={styles.menuLinks}>
<li>
<Link to="/">Login</Link>
</li>
<li>
<Link to="/register" className={styles.registerAction}>
Register
</Link>
</li>
</ul>
</nav>
<>
<nav>
<ul className={styles.menuLinks}>
<li>
<NavLink to="/">
{t('credentials.login.confirmButton')}
</NavLink>
</li>
<li>
<NavLink to="/register" className={styles.registerAction}>
{t('credentials.login.registerLink')}
</NavLink>
</li>
</ul>
</nav>
<nav className={styles.secondaryNav}>
<LanguagesMenu />
</nav>
</>
)}
</section>
</header>
Expand Down
147 changes: 54 additions & 93 deletions front/src/components/Register.jsx
Original file line number Diff line number Diff line change
@@ -1,159 +1,120 @@
import React, { useState } from 'react'
import React, { useCallback, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { Link, useHistory } from 'react-router-dom'

import etv from '../helpers/eventTargetValue'
import validateEmail from '../helpers/validationEmail'

import { useToasts } from '@geist-ui/core'
import { useGraphQL } from '../helpers/graphQL'
import * as queries from './Credentials.graphql'

import styles from './login.module.scss'
import Field from './Field'
import Button from './Button'
import { ArrowLeftCircle, Check } from 'react-feather'
import { fromFormData, validateSameFieldValue } from '../helpers/forms.js'

function Register() {
export default function Register() {
const { t } = useTranslation()
const { setToast } = useToasts()
const passwordRef = useRef()
const passwordConfirmationRef = useRef()
const history = useHistory()
const [email, setEmail] = useState('')
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [passwordC, setPasswordC] = useState('')
const [displayName, setDisplayName] = useState('')
const [firstName, setFirstName] = useState('')
const [lastName, setLastName] = useState('')
const [institution, setInstitution] = useState('')
const runQuery = useGraphQL()

const details = {
email,
username,
password,
passwordC,
displayName,
firstName,
lastName,
institution,
}

const createUser = async (details) => {
if (details.password !== details.passwordC) {
alert('Password and Password confirm mismatch')
return false
}
if (details.password === '') {
alert('password is empty')
return false
}
if (details.username === '') {
alert('Username is empty')
return false
}
if (details.email === '') {
alert('Email is empty')
return false
}
if (!validateEmail(details.email)) {
alert('Email appears to be malformed')
return false
}
const handleFormSubmit = useCallback(async (event) => {
event.preventDefault()
const details = fromFormData(event.target)

try {
await runQuery({ query: queries.createUser, variables: { details } })
// if no error thrown, we can navigate to /
setToast({
type: 'default',
text: t('credentials.register.successToast'),
})
history.push('/')
} catch (err) {
console.log('Unable to create a user', err)
setToast({
type: 'error',
text: t('credentials.register.errorToast', { message: err.message }),
})
}
}
}, [])

return (
<section className={styles.box}>
<form
onSubmit={(event) => {
event.preventDefault()
createUser(details)
}}
>
<h1>Create a Stylo account</h1>
<form onSubmit={handleFormSubmit} id="form-register">
<h1>{t('credentials.register.title')}</h1>

<fieldset>
<legend>Required informations</legend>
<legend>{t('credentials.register.requiredFields')}</legend>

<Field
id="email"
name="email"
type="email"
label="Email*"
autoComplete="email"
autoFocus={true}
required={true}
onChange={(e) => setEmail(etv(e))}
/>
<Field
id="username"
label="Username*"
name="username"
label={t('user.account.username')}
autoComplete="username"
required={true}
onChange={(e) => setUsername(etv(e))}
/>
<Field
id="password"
ref={passwordRef}
name="password"
type="password"
label="Password*"
label={t('credentials.password.placeholder')}
minLength={6}
autoComplete="new-password"
onChange={validateSameFieldValue(
passwordConfirmationRef,
passwordRef,
t('credentials.password.mismatch')
)}
required={true}
onChange={(e) => setPassword(etv(e))}
/>
<Field
id="passwordc"
ref={passwordConfirmationRef}
name="passwordC"
type="password"
label="Confirm Password*"
minLength={6}
label={t('credentials.confirmNewPassword.placeholder')}
autoComplete="new-password"
onChange={validateSameFieldValue(
passwordConfirmationRef,
passwordRef,
t('credentials.password.mismatch')
)}
required={true}
onChange={(e) => setPasswordC(etv(e))}
className={password === passwordC ? null : styles.beware}
/>
</fieldset>

<fieldset>
<legend>Optional details</legend>
<legend>{t('credentials.register.optionalFields')}</legend>

<Field
id="display-name"
label="Display Name"
onChange={(e) => setDisplayName(etv(e))}
/>
<Field
id="first-name"
label="First Name"
onChange={(e) => setFirstName(etv(e))}
/>
<Field
id="last-name"
label="Last Name"
onChange={(e) => setLastName(etv(e))}
/>
<Field
id="institution"
label="Organization"
onChange={(e) => setInstitution(etv(e))}
/>
<Field name="displayName" label={t('user.account.displayName')} />
<Field name="firstName" label={t('user.account.firstName')} />
<Field name="lastName" label={t('user.account.lastName')} />
<Field name="institution" label={t('user.account.institution')} />
</fieldset>

<ul className={styles.actions}>
<li>
<Link to="/">
<ArrowLeftCircle className={styles.inlineIcon} size={20} />
Go back to Login
{t('credentials.login.goBackLink')}
</Link>
</li>
<li className={styles.actionsSubmit}>
<Button primary={true} type="submit">
<Check /> Create
<Check role="presentation" />
{t('credentials.login.registerLink')}
</Button>
</li>
</ul>
</form>
</section>
)
}

export default Register
40 changes: 40 additions & 0 deletions front/src/helpers/forms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Transforms a form into a plain object
*
* formData.entries() does not handle multiple values (sic) and formData.getAll() only works at a field level
*
* @param {React.ReactHTMLElement|FormData} formElement
* @returns {Record<string, string|number|string[]|number[]>}
*/
export function fromFormData(input) {
const d = input instanceof FormData ? input : new FormData(input)

return Array.from(d.keys()).reduce(
(data, key) => ({
...data,
[key.replace('[]', '')]: key.endsWith('[]') ? d.getAll(key) : d.get(key),
}),
{}
)
}

/**
*
* @param {React.RefObject} field
* @param {React.RefObject} referenceField
* @param {string} errorMessage
* @returns {(event: React.ChangeEvent) => undefined}
*/
export function validateSameFieldValue(field, referenceField, errorMessage) {
return function onChangeEvent() {
const fieldValue = field.current.value
const referenceFieldValue = referenceField.current.value

console.log({ fieldValue, referenceFieldValue })
if (fieldValue !== referenceFieldValue) {
field.current.setCustomValidity(errorMessage)
} else {
field.current.setCustomValidity('')
}
}
}
Loading

0 comments on commit 9a5dd74

Please sign in to comment.