Skip to content

Commit

Permalink
feat: Add AuthentificationDialog
Browse files Browse the repository at this point in the history
  • Loading branch information
cballevre committed Jun 30, 2023
1 parent 8f9bf40 commit c0e453b
Show file tree
Hide file tree
Showing 9 changed files with 305 additions and 1 deletion.
70 changes: 70 additions & 0 deletions react/CozyDialogs/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -441,3 +441,73 @@ const Modal = () => {
)}
</BreakpointsProvider>
```

### Authentification dialog

Dialogue used to authenticate a user in the cozy system. The authentication logic is implemented in the applications.

```jsx
import { AuthentificationDialog } from 'cozy-ui/transpiled/react/CozyDialogs'
import Button from 'cozy-ui/transpiled/react/Buttons'
import DemoProvider from 'cozy-ui/docs/components/DemoProvider'
import Variants from 'cozy-ui/docs/components/Variants'
import FormControlLabel from 'cozy-ui/transpiled/react/FormControlLabel'
import RadioGroup from 'cozy-ui/transpiled/react/RadioGroup'
import Radio from 'cozy-ui/transpiled/react/Radios'
import FormControl from 'cozy-ui/transpiled/react/FormControl'
import FormLabel from 'cozy-ui/transpiled/react/FormLabel'

initialState = { showModal: false, error: '' };

const initialVariants = [{
closable: true,
loading: false,
oidc: false,
}];

<Variants initialVariants={initialVariants}>
{variant => (
<>
<FormControl component="fieldset" fullWidth>
<FormLabel component="legend">Possible error codes</FormLabel>
<RadioGroup
aria-label="radio"
name="error"
row
value={state.error}
onChange={event => { setState({ error: event.target.value }) }}
>
<FormControlLabel
value=""
label="Empty"
control={<Radio />}
/>
<FormControlLabel
value="invalid_password"
label="Invalid password (invalid_password)"
control={<Radio />}
/>
<FormControlLabel
value="server"
label="Server error (server)"
control={<Radio />}
/>
</RadioGroup>
</FormControl>
<DemoProvider>
<Button label="Open modal" onClick={() => setState({ showModal: true })}/>
{state.showModal && (
<AuthentificationDialog
onAuthentification={() => alert('authentification')}
onDismiss={() => setState({ showModal: false })}
closable={variant.closable}
error={state.error}
isLoading={variant.loading}
isOIDC={variant.oidc}
/>
)}
</DemoProvider>
</>
)}
</Variants>
```
3 changes: 3 additions & 0 deletions react/CozyDialogs/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ export { default as IllustrationDialog } from './IllustrationDialog'
export { default as PermissionDialog } from './PermissionDialog'
export { default as useCozyDialog } from './useCozyDialog'
export { default as TopAnchoredDialog } from './TopAnchoredDialog'
export {
default as AuthentificationDialog
} from './specific/AuthentificationDialog'
114 changes: 114 additions & 0 deletions react/CozyDialogs/specific/AuthentificationDialog.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React, { useCallback, useMemo, useState } from 'react'
import PropTypes from 'prop-types'

import PermissionDialog from '../PermissionDialog'
import Buttons from '../../Buttons'
import { useI18n } from '../../I18n'
import Typography from '../../Typography'
import { useClient } from 'cozy-client'
import CozyAuthentificationIcon from '../../Icons/CozyAuthentification'

import PasswordField from './PasswordField'
import withSpecificDialogLocales from './locales/withSpecificDialogLocales'

/**
* Dialog used to authenticate a user in the cozy system.
* The authentication logic is implemented in the applications.
*/
const AuthentificationDialog = ({
closable,
onDismiss,
onAuthentification,
isLoading,
isOIDC,
error
}) => {
const { t } = useI18n()
const client = useClient()
const [password, setPassword] = useState('')

const handleAuthentification = useCallback(
e => {
e.preventDefault()
onAuthentification(password)
},
[onAuthentification, password]
)

const onPasswordChange = e => {
setPassword(e.currentTarget.value)
}

const getPassphraseResetUrlCb = useMemo(() => {
const url = new URL('/auth/passphrase_reset', client.getStackClient().uri)
return url.href
}, [client])

return (
<PermissionDialog
size="small"
open
onClose={closable && onDismiss}
title={t(`authentification.${isOIDC ? 'title-oidc' : 'title'}`)}
icon={CozyAuthentificationIcon}
content={
<form onSubmit={handleAuthentification}>
<Typography variant="body1" className="u-ta-center">
{t('authentification.subtitle')}
</Typography>
<PasswordField
id="authentification-password-field"
disabled={isLoading}
value={password}
onChange={onPasswordChange}
className="u-mv-1"
label={t(`authentification.${isOIDC ? 'label-oidc' : 'label'}`)}
variant="outlined"
fullWidth
error={Boolean(error) || error !== ''}
helperText={error && t(`authentification.errors.${error}`)}
/>
<Typography
variant="body1"
component="a"
color="primary"
href={getPassphraseResetUrlCb}
className="u-link"
>
{t('authentification.forgotten-password')}
</Typography>
</form>
}
actions={
<Buttons
busy={isLoading}
disabled={isLoading}
onClick={handleAuthentification}
label={t('authentification.unlock')}
fullWidth
/>
}
/>
)
}

PermissionDialog.defaultProps = {
isOIDC: false
}

PermissionDialog.propTypes = {
/** Show closing button */
closable: PropTypes.bool,
/** A function call on clicking the close button */
onDismiss: PropTypes.func,
/** A function call on submitting the form with the password entered */
onAuthentification: PropTypes.func,
/** Waiting status, e.g. processing of form submission */
isLoading: PropTypes.bool,
/** Show specific wording for OIDC */
isOIDC: PropTypes.bool,
/** Error key to display a message */
error: PropTypes.string
}

export default withSpecificDialogLocales(AuthentificationDialog)
37 changes: 37 additions & 0 deletions react/CozyDialogs/specific/EyeAdornment.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react'
import PropTypes from 'prop-types'

import IconButton from '../../IconButton'
import Icon from '../../Icon'
import { useI18n } from '../../I18n'
import InputAdornment from '../../InputAdornment'
import EyeIcon from '../../Icons/Eye'
import EyeClosedIcon from '../../Icons/EyeClosed'
import withSpecificDialogLocales from './locales/withSpecificDialogLocales'

/**
* Icon button for hiding or showing something
*/
const EyeAdornment = ({ hidden, ...rest }) => {
const { t } = useI18n()

return (
<InputAdornment position="end">
<IconButton
className="u-ph-half u-mh-0 u-miw-auto"
size="small"
label={hidden ? t('eye-adornment.show') : t('eye-adornment.hide')}
{...rest}
>
<Icon icon={hidden ? EyeIcon : EyeClosedIcon} color="white" />
</IconButton>
</InputAdornment>
)
}

EyeAdornment.propTypes = {
/** The state of the element to be displayed */
hidden: PropTypes.bool
}

export default withSpecificDialogLocales(EyeAdornment)
30 changes: 30 additions & 0 deletions react/CozyDialogs/specific/PasswordField.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React, { useState } from 'react'

import TextField from '../../MuiCozyTheme/TextField'
import EyeAdornment from './EyeAdornment'

/**
* Password field with the option of hiding or displaying it
*/
const PasswordField = props => {
const [hidden, setHidden] = useState(true)

return (
<TextField
{...props}
variant="outlined"
type={hidden ? 'password' : 'text'}
InputProps={{
endAdornment: (
<EyeAdornment
onClick={() => setHidden(!hidden)}
hidden={hidden}
disabled={props.disabled}
/>
)
}}
/>
)
}

export default PasswordField
20 changes: 20 additions & 0 deletions react/CozyDialogs/specific/locales/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"authentification": {
"title": "Login",
"title-oidc": "Cozy Pass",
"subtitle": "For security, please confirm your identity",
"label": "Cozy password",
"label-oidc": "Cozy Pass password",
"abort": "Leave",
"unlock": "Unlock",
"forgotten-password": "I forgot my password",
"errors": {
"invalid_password": "Incorrect password, try again.",
"server": "Something went wrong with the server. Please, reload the page."
}
},
"eye-adornment": {
"show": "show",
"hide": "hide"
}
}
20 changes: 20 additions & 0 deletions react/CozyDialogs/specific/locales/fr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"authentification": {
"title": "Authentification",
"title-oidc": "Cozy Pass",
"subtitle": "Par sécurité, merci de confirmer votre identité",
"label": "Mot de passe Cozy",
"label-oidc": "Mot de passe Cozy Pass",
"abort": "Quitter",
"unlock": "Déverrouiller",
"forgotten-password": "J'ai oublié mon mot de passe",
"errors": {
"invalid_password": "Mot de passe incorrect, essayer à nouveau.",
"server": "Une erreur s'est produite. Merci de recharger la page."
}
},
"eye-adornment": {
"show": "afficher",
"hide": "masquer"
}
}
11 changes: 11 additions & 0 deletions react/CozyDialogs/specific/locales/withSpecificDialogLocales.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import withOnlyLocales from '../../../I18n/withOnlyLocales'

import en from './en.json'
import fr from './fr.json'

export const locales = {
en,
fr
}

export default withOnlyLocales(locales)
1 change: 0 additions & 1 deletion react/Icons/EyeClosed.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ function SvgEyeClosed(props) {
<g fillRule="evenodd">
<path d="M1 10s3-6 9-6 9 6 9 6-3 6-9 6-9-6-9-6zm9 4a4 4 0 100-8 4 4 0 000 8zm0-2a2 2 0 100-4 2 2 0 000 4z" />
<path
stroke="#FFF"
strokeWidth={4}
d="M3 17L17 4"
strokeLinecap="round"
Expand Down

0 comments on commit c0e453b

Please sign in to comment.