feat: link new user form and watcha_register end point

pull/12/head
c-cal 5 years ago
parent 94f08dd69e
commit 7990259402
Signed by: watcha
GPG Key ID: 87DD78E7F7A1581D
  1. 5
      public/locales/en/common.json
  2. 2
      public/locales/en/usersTab.json
  3. 23
      public/locales/fr/common.json
  4. 2
      public/locales/fr/usersTab.json
  5. 20
      src/Alert.js
  6. 4
      src/App.css
  7. 13
      src/CollapsableRightPanel.js
  8. 309
      src/CreateUserRightPanel.js
  9. 2
      src/Login.js
  10. 47
      src/NewItemModal.js
  11. 4
      src/NewUserForm.js
  12. 43
      src/UsersTab.js

@ -15,5 +15,8 @@
"body": "<0>Should the failure happen again, please contact us at <0>{{email}}</0>.</0><1>You'll be redirected to <0>{{redirectUrl}}</0> in a few seconds...</1>"
},
"save": "Save",
"cancel": "Cancel"
"cancel": "Cancel",
"close": "Close",
"success": "Successfull transaction",
"danger": "An error has occurred"
}

@ -18,6 +18,8 @@
"emailAddress": "a valid email address"
},
"unavailableEmailAddress": "This email address is already bound to another account.",
"success": "A new user has been created",
"danger": "User creation failed",
"headers": {
"displayName": "Display name",
"emailAddress": "Email address",

@ -10,26 +10,8 @@
"Inactive Rooms": "Salons inactifs ",
"Room": "Salon ",
"Statistics for Watcha server": "Statistiques",
"Hide internal users": "Masquer les utilisateurs internes",
"Hide one-to-one conversations": "Masquer les conversations à deux",
"Hide partners": "Masquer les utilisateurs externes",
"Hide inactive": "Masquer les salons inactifs",
"Search": "Rechercher",
"Last Name": "Nom",
"First Name": "Prénom",
"This email address seems to be invalid. Please enter a valid email address.": "Cette adresse email semble invalide. Veuillez entrer une adresse email valide",
"Invalid Email": "Adresse email invalide",
"Invalid Last Name": "Nom invalide",
"First Name must contain at least two characters ": "Le Prénom doit contenir au moins deux caractères",
"Last Name must contain at least two characters ": "Le Nom doit contenir au moins deux caractères",
"Email already in use": "Email utilisé",
"Invalid First Name": "Prénom invalide",
" is already in use for a different account.": " est déjà associé à un compte.",
"User id required": "Nom d'utilisateur requis",
"Enter a valid user id": " Entrez un nom d'utilisateur valide ",
"User Creation Failed": " Échec lors de la création de l'utilisateur ",
"User Created": "Utilisateur créé",
" has been added to watcha": " a été ajouté à Watcha",
"Upgrade to internal user": "Promouvoir en utilisateur interne",
"Reset Password": " Réinitialiser le mot de passe",
"Deactivate Account": "Désactiver le compte",
@ -71,5 +53,8 @@
"loading": "Chargement..."
},
"save": "Enregistrer",
"cancel": "Annuler"
"cancel": "Annuler",
"close": "Fermer",
"success": "Opération terminée avec succès",
"danger": "Une erreur s'est produite"
}

@ -18,6 +18,8 @@
"emailAddress": "une adresse électronique valide"
},
"unavailableEmailAddress": "Cette adresse électronique est déjà liée à un autre compte",
"success": "Un nouvel utilisateur a été créé",
"danger": "La création de l'utilisateur a échoué",
"headers": {
"displayName": "Nom d'affichage",
"emailAddress": "Adresse électronique",

@ -0,0 +1,20 @@
import React from "react";
import { useTranslation } from "react-i18next";
import Alert from "react-bootstrap/Alert";
import Button from "react-bootstrap/Button";
export default ({ variant, message, onHide }) => {
const { t } = useTranslation();
return (
<Alert className="flex-fill" variant={variant}>
<Alert.Heading>{t(variant)}</Alert.Heading>
<p>{message}</p>
<hr />
<div className="d-flex justify-content-end">
<Button variant={variant} onClick={onHide}>
{t("close")}
</Button>
</div>
</Alert>
);
};

@ -46,10 +46,6 @@
align-items: center;
}
.loadingLoginText {
flex-grow: 1;
}
.searchBox {
max-width: 20em;
}

@ -1,6 +1,5 @@
import React, { Component } from "react";
import CreateUserRightPanel from "./CreateUserRightPanel";
import RoomRightPanel from "./RoomRightPanel";
import UserRightPanel from "./UserRightPanel";
@ -36,18 +35,6 @@ class CollapsableRightPanel extends Component {
);
break;
case "createUser":
panel = (
<CreateUserRightPanel
data={this.props.data}
onClose={this.props.onClose}
refresh={this.props.refresh}
refreshRightPanel={this.props.refreshRightPanel}
isEmailAvailable={this.props.isEmailAvailable}
/>
);
break;
default:
panel = (
<UserRightPanel

@ -1,309 +0,0 @@
import React, { Component } from "react";
import { withTranslation } from "react-i18next";
import Alert from "react-bootstrap/Alert";
import Button from "react-bootstrap/Button";
import Card from "react-bootstrap/Card";
import Table from "react-bootstrap/Table";
import { MatrixClientContext } from "./contexts";
class CreateUserRightPanel extends Component {
constructor(props) {
super(props);
this.state = {
infoMessage: false,
editUserId: false,
emailValue: " ",
lastNameValue: "",
firstNameValue: "",
suggestedUserId: "",
userIdValue: "",
busy: false,
};
}
static contextType = MatrixClientContext;
onEmailChange = ev => {
this.setState({
emailValue: ev.target.value,
isEmail: this.isEmail(ev.target.value),
});
};
onFirstNameChange = ev => {
const FIRST_NAME = ev.target.value;
this.setState({
firstNameValue: FIRST_NAME,
isFirstName: this.isName(FIRST_NAME),
});
this.generateSuggestedUserId(FIRST_NAME, true);
};
onLastNameChange = ev => {
const NAME = ev.target.value;
this.setState({
lastNameValue: NAME,
isLastName: this.isName(NAME),
});
this.generateSuggestedUserId(NAME, false);
};
onUserIdChange = ev => this.setState({ userIdValue: ev.target.value });
onUserIdEdit = () => this.setState({ editUserId: !this.state.editUserId });
createUser = async () => {
const client = this.context;
const { t } = this.props;
if (!this.state.isEmail) {
this.setState({
message: {
type: "danger",
title: t("Invalid Email"),
body: t(
"This email address seems to be invalid. Please enter a valid email address."
),
},
});
this.displayInfoMessage();
} else if (!this.state.isFirstName) {
this.setState({
message: {
type: "danger",
title: t("Invalid First Name"),
body: t("First Name must contain at least two characters "),
},
});
this.displayInfoMessage();
} else if (!this.state.isLastName) {
this.setState({
message: {
type: "danger",
title: t("Invalid Last Name"),
body: t("Last Name must contain at least two characters "),
},
});
this.displayInfoMessage();
} else if (!this.props.isEmailAvailable(this.state.emailValue)) {
this.setState({
message: {
type: "danger",
title: t("Email already in use"),
body:
this.state.emailValue +
t(" is already in use for a different account."),
},
});
this.displayInfoMessage();
} else if (!this.state.userIdValue && !this.state.suggestedUserId) {
this.setState({
message: {
type: "danger",
title: t("User id required"),
body: t("Enter a valid user id"),
},
});
this.displayInfoMessage();
} else {
try {
this.setState({
busy: true,
});
const USER_ID = this.state.userIdValue
? this.state.userIdValue
: this.state.suggestedUserId;
const USER_REQUEST = await fetch(
new URL(
"_matrix/client/r0/watcha_register",
client.baseUrl
),
{
method: "POST",
headers: {
Authorization: "Bearer " + client.getAccessToken(),
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
user: USER_ID,
full_name:
this.state.firstNameValue +
" " +
this.state.lastNameValue,
email: this.state.emailValue,
admin: false,
}),
}
);
const RESPONSE = JSON.parse(await USER_REQUEST.text());
if (USER_REQUEST.ok) {
this.setState({
message: {
type: "success",
title: t("User Created"),
body: USER_ID + t(" has been added to Watcha"),
},
busy: false,
clearForm: true,
});
this.displayInfoMessage();
} else {
this.setState({
message: {
type: "danger",
title: t("User Creation Failed"),
body: RESPONSE["error"],
},
busy: false,
});
this.displayInfoMessage();
}
} catch (e) {
console.log("error: " + e);
this.setState({
busy: false,
});
return;
}
}
};
dismissInfoMessage = () => {
this.setState({ infoMessage: false });
if (this.state.clearForm) {
this.setState({
clearForm: false,
emailValue: "",
userIdValue: "",
lastNameValue: "",
firstNameValue: "",
});
}
};
displayInfoMessage = () => this.setState({ infoMessage: true });
generateSuggestedUserId = (value, firstName) => {
const suggestedUserId = firstName
? value.toLowerCase() + "." + this.state.lastNameValue.toLowerCase()
: this.state.firstNameValue.toLocaleLowerCase() +
"." +
value.toLowerCase();
this.setState({ suggestedUserId });
};
isEmail = query => /^([\w.%+-]+)@([\w-]+\.)+([\w]{2,})$/i.test(query);
isFirstName = query => query.length > 1;
isName = query => query.length > 1;
onInfoMessageValidate = () => this.dismissInfoMessage();
render() {
const { t } = this.props;
const editUserId = this.state.editUserId ? (
<td>
<input
value={this.state.userIdValue}
placeholder={this.state.suggestedUserId}
className="inputValue"
onChange={this.onUserIdChange}
/>
<Button
onClick={this.onUserIdEdit}
variant="danger"
className="editButton"
>
<i className="fas fa-times"></i>
</Button>
</td>
) : (
<td>
<input
value={this.state.suggestedUserId}
className="inputValue disabled"
readOnly
/>
<Button
onClick={this.onUserIdEdit}
variant="primary"
className="editButton"
>
<i className="fas fa-pencil-alt"></i>
</Button>
</td>
);
const bottomWell = this.state.infoMessage && (
<Alert
variant={this.state.message.type}
onClose={this.onInfoMessageValidate}
dismissible
>
<h4>{this.state.message.title}</h4>
<p>{this.state.message.body}</p>
<p>
<Button
variant={this.state.message.type}
onClick={this.onInfoMessageValidate}
>
Ok
</Button>
</p>
</Alert>
);
return (
<>
<Card.Body>
<Card body bg="light">
<Table>
<tbody>
<tr>
<td className="labelText">
{t("First Name")}:
</td>
<td>
<input
onChange={this.onFirstNameChange}
/>
</td>
</tr>
<tr>
<td className="labelText">
{t("Last Name")}:
</td>
<td>
<input
onChange={this.onLastNameChange}
/>
</td>
</tr>
<tr>
<td className="labelText">Email:</td>
<td>
<input onChange={this.onEmailChange} />
</td>
</tr>
<tr>
<td className="labelText">
{t("User Id")}:
</td>
{editUserId}
</tr>
</tbody>
</Table>
</Card>
</Card.Body>
{bottomWell}
</>
);
}
}
export default withTranslation("common", { withRef: true })(
CreateUserRightPanel
);

@ -48,7 +48,7 @@ const Login = ({ setupClient, t, i18n }) => {
block
disabled
>
<span className="loadingLoginText">{t("login.loading")}</span>
<span className="flex-grow-1">{t("login.loading")}</span>
<Spinner
as="span"
animation="border"

@ -2,23 +2,52 @@ import React from "react";
import { useTranslation } from "react-i18next";
import Button from "react-bootstrap/Button";
import Modal from "react-bootstrap/Modal";
import Spinner from "react-bootstrap/Spinner";
export default ({ show, title, onHide, onSave, children }) => {
import Alert from "./Alert";
export default props => {
const { t } = useTranslation();
const { feedback, loading, show, title, onHide, onSave, children } = props;
const footer = feedback ? (
<Alert
variant={feedback.variant}
message={feedback.message}
{...{ onHide }}
/>
) : (
<>
<Button onClick={onHide} variant="secondary">
{t("cancel")}
</Button>
{loading ? (
<Button variant="primary" disabled>
<span className="flex-grow-1">{t("save")}</span>
<Spinner
as="span"
animation="border"
size="sm"
role="status"
aria-hidden="true"
/>
</Button>
) : (
<Button variant="primary" onClick={onSave}>
{t("save")}
</Button>
)}
</>
);
return (
<Modal aria-labelledby="newItemModal" centered {...{ show, onHide }}>
<Modal.Header closeButton>
<Modal.Title id="newItemModal">{title}</Modal.Title>
</Modal.Header>
<Modal.Body>{children}</Modal.Body>
<Modal.Footer>
<Button onClick={onHide} variant="secondary">
{t("cancel")}
</Button>
<Button onClick={onSave} variant="primary">
{t("save")}
</Button>
</Modal.Footer>
<Modal.Footer>{footer}</Modal.Footer>
</Modal>
);
};

@ -8,7 +8,7 @@ import InputGroup from "react-bootstrap/InputGroup";
const namePattern = "[a-z\u00C0-\u017F]{2,}";
export default ({ userList, onSubmit, bindSubmitForm }) => {
export default ({ userList, onSubmit, bindSubmitForm, feedback }) => {
const { t } = useTranslation("usersTab");
const schema = useMemo(
@ -78,6 +78,7 @@ export default ({ userList, onSubmit, bindSubmitForm }) => {
isInvalid={
touched.fullName && !!errors.fullName
}
readOnly={feedback}
/>
<Form.Control.Feedback type="invalid">
{errors.fullName}
@ -107,6 +108,7 @@ export default ({ userList, onSubmit, bindSubmitForm }) => {
touched.emailAddress &&
!!errors.emailAddress
}
readOnly={feedback}
/>
<Form.Control.Feedback type="invalid">
{errors.emailAddress}

@ -5,7 +5,7 @@ import React, {
useRef,
useState,
} from "react";
import { useGet } from "restful-react";
import { useGet, useMutate } from "restful-react";
import { useTranslation } from "react-i18next";
import Button from "./NewItemButton";
@ -30,6 +30,7 @@ export default () => {
const [intervalId, setIntervalId] = useState(null);
const [rightPanel, setRightPanel] = useState(null);
const [modalShow, setModalShow] = useState(false);
const [feedback, setFeedback] = useState(null);
const { data, refetch } = useGet({
path: "watcha_user_list",
@ -90,22 +91,42 @@ export default () => {
submitFormRef.current = submitForm;
};
const newItemModal = useMemo(
() => (
const { mutate: post, cancel, loading } = useMutate({
verb: "POST",
path: "watcha_register",
});
const newItemModal = useMemo(() => {
const onSubmit = data => {
post({
admin: false,
email: data.emailAddress,
full_name: data.fullName,
user: data.emailAddress.replace("@", "/"),
})
.then(response =>
setFeedback({ variant: "success", message: t("success") })
)
.catch(error =>
setFeedback({ variant: "danger", message: t("danger") })
);
};
const onHide = () => {
cancel();
setModalShow(false);
setFeedback(null);
};
return (
<NewItemModal
show={modalShow}
title={t("usersTab:button")}
onHide={() => setModalShow(false)}
onSave={() => submitFormRef.current()}
{...{ feedback, loading, onHide }}
>
<NewUserForm
onSubmit={e => console.log("@@@ onSubmit", e)}
{...{ userList, bindSubmitForm }}
/>
<NewUserForm {...{ userList, onSubmit, bindSubmitForm, feedback }} />
</NewItemModal>
),
[modalShow, userList, t]
);
);
}, [modalShow, feedback, loading, userList, post, cancel, t]);
const onClose = useCallback(() => setRightPanel(), []);

Loading…
Cancel
Save