admin/src/components/addressInput.jsx
import Img from "i18n-iso-countries/langs/fr.json";
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import { Row, Col, Spinner } from "reactstrap";
import { Field, useField } from "formik";
import ErrorMessage, { requiredMessage } from "./errorMessage";
import { department2region, departmentLookUp, departmentToAcademy, departmentList, regionList } from "../utils";
import InfoIcon from "./InfoIcon";
import countries from "i18n-iso-countries";
import { toastr } from "react-redux-toastr";
import { apiAdress } from "../services/api-adresse";
countries.registerLocale(Img);
const countriesList = countries.getNames("fr", { select: "official" });
// eslint-disable-next-line prettier/prettier
export default function AddressInput({
keys,
values,
handleChange,
errors,
touched,
validateField,
countryVisible = false,
onChangeCountry = () => {},
countryByDefault = "",
required = false,
departAndRegionVisible = false,
disabled = false,
}) {
const [suggestion, setSuggestion] = useState({});
const [addressInFrance, setAddressInFrance] = useState(true);
const [loading, setLoading] = useState(false);
const [addressVerified, _, addressVerifiedHelpers] = useField({
value: values[keys.addressVerified],
name: "addressVerified",
validate: (v) => v !== "true" && (values[keys.address] || values[keys.zip] || values[keys.city]) && addressInFrance && "Il est obligatoire de vérifier l'adresse",
});
useEffect(() => {
if (document.getElementsByTagName) {
const inputElements = document.getElementsByTagName("input");
for (let i = 0; inputElements[i]; i++) inputElements[i].setAttribute("autocomplete", "novalue");
}
if (!values[keys.country]) handleChange({ target: { name: keys.country, value: countryByDefault } });
}, []);
useEffect(() => {
setAddressInFrance(!values[keys.country] || values[keys.country] === "France");
if (keys.country && values[keys.country] === undefined) addressVerifiedHelpers.setValue("false");
}, [values[keys.country]]);
useEffect(() => {
const zip = values[keys.zip];
if (!zip || zip.length < 2) {
handleChange({ target: { name: keys.department, value: "" } });
handleChange({ target: { name: keys.region, value: "" } });
return;
}
if (values?.cohort === "2020") return;
let departmentCode = zip.substr(0, 2);
if (["97", "98"].includes(departmentCode)) {
departmentCode = zip.substr(0, 3);
}
//POUR LA CORSE
if (departmentCode === "20") {
if (zip === "20000" || zip.substr(0, 3) === "201") departmentCode = "2A";
else departmentCode = "2B";
}
handleChange({ target: { name: keys.department, value: departmentLookUp[departmentCode] } });
handleChange({ target: { name: keys.region, value: department2region[departmentLookUp[departmentCode]] } });
if (keys.academy) handleChange({ target: { name: keys.academy, value: departmentToAcademy[departmentLookUp[departmentCode]] } });
}, [values[keys.zip]]);
const onSuggestionSelected = () => {
let depart = suggestion.properties.postcode?.substr(0, 2) || suggestion.properties.citycode.substr(0, 2);
if (["97", "98"].includes(depart)) {
depart = suggestion.properties.postcode?.substr(0, 3) || suggestion.properties.citycode.substr(0, 3);
}
if (depart === "20") {
depart = suggestion.properties.context.substr(0, 2);
if (!["2A", "2B"].includes(depart)) depart = "2B";
}
handleChange({ target: { name: keys.city, value: suggestion.properties.city } });
handleChange({ target: { name: keys.zip, value: suggestion.properties.postcode } });
handleChange({ target: { name: keys.address, value: suggestion.properties.name } });
handleChange({ target: { name: keys.location, value: { lon: suggestion.geometry.coordinates[0], lat: suggestion.geometry.coordinates[1] } } });
if (values?.cohort !== "2020") {
handleChange({ target: { name: keys.department, value: departmentLookUp[depart] } });
handleChange({ target: { name: keys.region, value: department2region[departmentLookUp[depart]] } });
}
if (keys.cityCode) {
handleChange({ target: { name: keys.cityCode, value: suggestion.properties.citycode } });
}
if (keys.academy && depart) {
handleChange({ target: { name: keys.academy, value: departmentToAcademy[departmentLookUp[depart]] } });
}
setSuggestion({});
addressVerifiedHelpers.setValue("true");
addressVerifiedHelpers.setError("");
return;
};
const getSuggestions = async () => {
const errors = await Promise.all([validateField(keys.address), validateField(keys.city), validateField(keys.zip)]).then((arr) => arr.filter((error) => error !== false));
if (errors.length) return;
setLoading(true);
// For Nouvelle-Calédonie, we don't add the postcode to the query
const res = await apiAdress(`${values.address}, ${values.city}, ${values.zip}`, { postcode: parseInt(values.zip.substr(0, 3)) === 988 ? "" : values.zip });
const arr = res?.features;
setLoading(false);
if (arr?.length > 0) setSuggestion({ ok: true, status: "FOUND", ...arr[0] });
else {
// If no match with complete query, try with postcode only
const res = await apiAdress(values.zip, { postcode: values.zip });
const arr = res?.features.filter((e) => e.properties.type !== "municipality");
setLoading(false);
if (arr?.length > 0) setSuggestion({ ok: true, status: "FOUND", ...arr[0] });
else {
toastr.error("Aucune adresse n'a été trouvée.");
addressVerifiedHelpers.setValue("false");
}
}
};
// keys is not defined at first load ??
if (!keys) return <div />;
return (
<Wrapper>
{suggestion.status !== "FOUND" ? (
<Row>
{countryVisible && (
<Col md={12}>
<Label>Pays</Label>
<Field
disabled={disabled}
as="select"
validate={(v) => required && !v && requiredMessage}
className="form-control"
placeholder="Pays"
name={keys.country}
value={values[keys.country]}
onChange={(e) => {
const value = e.target.value;
handleChange({ target: { name: keys.country, value } });
onChangeCountry();
}}>
<option value="" label="Sélectionner un pays" disabled>
Sélectionner un pays
</option>
{Object.values(countriesList)
.sort((a, b) => a.localeCompare(b))
.map((countryName) => (
<option key={countryName} value={countryName} label={countryName}>
{countryName}
</option>
))}
</Field>
<ErrorMessage errors={errors} touched={touched} name={keys.country} />
</Col>
)}
<Col md={12} style={{ marginTop: countryVisible ? 15 : 0 }}>
<Label>Adresse</Label>
<Field
disabled={disabled}
validate={(v) => required && !v && requiredMessage}
className="form-control"
placeholder="Adresse"
name={keys.address}
value={values[keys.address]}
onChange={(e) => {
const value = e.target.value;
handleChange({ target: { name: keys.address, value } });
addressVerifiedHelpers.setValue("false");
}}
/>
<ErrorMessage errors={errors} touched={touched} name={keys.address} />
</Col>
<Col md={6} style={{ marginTop: 15 }}>
<Label>Code postal</Label>
<Field
disabled={disabled}
validate={(v) => required && !v && requiredMessage}
className="form-control"
placeholder="Code postal"
name={keys.zip}
value={values[keys.zip]}
onChange={(e) => {
const value = e.target.value;
handleChange({ target: { name: keys.zip, value } });
addressVerifiedHelpers.setValue("false");
}}
/>
<ErrorMessage errors={errors} touched={touched} name={keys.zip} />
</Col>
<Col md={6} style={{ marginTop: 15 }}>
<Label>Ville</Label>
<Field
disabled={disabled}
validate={(v) => required && !v && requiredMessage}
className="form-control"
placeholder="Ville"
name={keys.city}
value={values[keys.city]}
onChange={(e) => {
const value = e.target.value;
handleChange({ target: { name: keys.city, value } });
addressVerifiedHelpers.setValue("false");
}}
/>
<ErrorMessage errors={errors} touched={touched} name={keys.city} />
</Col>
{departAndRegionVisible ? (
<>
<Col md={6} style={{ marginTop: 15 }}>
<Label>Département</Label>
<Field
as="select"
validate={(v) => !v && requiredMessage}
disabled
className="form-control"
placeholder="Département"
name={keys.department}
value={values[keys.department]}>
<option label=""></option>
{departmentList.map((d) => (
<option key={d} value={d}>
{d}
</option>
))}
</Field>
<ErrorMessage errors={errors} touched={touched} name={keys.department} />
</Col>
<Col md={6} style={{ marginTop: 15 }}>
<Label>Région</Label>
<Field as="select" validate={(v) => !v && requiredMessage} disabled className="form-control" placeholder="Région" name={keys.region} value={values[keys.region]}>
<option label=""></option>
{regionList.map((r) => (
<option key={r} value={r}>
{r}
</option>
))}
</Field>
<ErrorMessage errors={errors} touched={touched} name={keys.region} />
</Col>
</>
) : null}
{addressInFrance ? (
<>
<Col md={12} style={{ display: "flex", alignItems: "flex-end" }}>
{addressVerified.value !== "true" ? (
<PrimaryButton style={{ marginLeft: "auto" }} onClick={() => getSuggestions(`${values[keys.address]}, ${values[keys.city]} ${values[keys.zip]}`)}>
{!loading ? "Vérifier" : <Spinner size="sm" style={{ borderWidth: "0.1em" }} />}
</PrimaryButton>
) : (
<div style={{ display: "flex", color: "#32257f", backgroundColor: "#edecfc", padding: "1rem", borderRadius: "6px", width: "100%", marginTop: "10px" }}>
<InfoIcon color="#32257F" style={{ flex: "none" }} />
<div style={{ fontSize: ".9rem", marginLeft: "5px" }}>L'adresse a été vérifiée</div>
</div>
)}
</Col>
<ErrorMessage errors={errors} touched={touched} name="addressVerified" />
</>
) : null}
</Row>
) : (
<Row>
<Col md={12} style={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
<b style={{ marginBottom: "16px" }}>Est-ce que c'est la bonne adresse ?</b>
<p>{suggestion.properties.name}</p>
<p>{`${suggestion.properties.postcode}, ${suggestion.properties.city}`}</p>
<p>France</p>
<div style={{ display: "flex" }}>
<SecondaryButton
onClick={() => {
setSuggestion({});
addressVerifiedHelpers.setValue("false");
}}>
Non
</SecondaryButton>
<PrimaryButton onClick={onSuggestionSelected}>Oui</PrimaryButton>
</div>
<ErrorMessage errors={errors} touched={touched} name="addressVerified" />
</Col>
</Row>
)}
</Wrapper>
);
}
const Wrapper = styled.div`
.react-autosuggest__container {
position: relative;
}
.react-autosuggest__suggestions-list {
position: absolute;
background-color: white;
margin: 0;
width: 100%;
z-index: 10;
left: 0px;
top: 106%;
border: 1px solid #ddd;
padding: 5px 0;
border-radius: 6px;
}
.react-autosuggest__suggestions-list li {
cursor: pointer;
padding: 7px 10px;
:hover {
background-color: #f3f3f3;
}
}
.react-autosuggest__suggestion--highlighted {
background-color: #f3f3f3;
}
`;
const PrimaryButton = styled.button`
color: #fff;
border: 0;
background-color: #5145cd;
padding: 9px 20px;
outline: 0;
border-radius: 6px;
margin-inline: 5px;
margin-top: 10px;
display: block;
outline: 0;
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 10%), 0 4px 6px -2px rgb(0 0 0 / 5%);
width: fit-content;
:hover {
opacity: 0.9;
}
`;
const SecondaryButton = styled.button`
border: solid 2px;
border-color: #e3e7ea;
background-color: #fff;
padding: 9px 20px;
outline: 0;
border-radius: 6px;
margin-inline: 5px;
margin-top: 10px;
display: block;
outline: 0;
width: fit-content;
`;
const Label = styled.div`
color: #374151;
font-size: 14px;
margin-bottom: 10px;
`;