diff --git a/src/components/LogoSearchForm.jsx b/src/components/LogoSearchForm.jsx
index 1e10f514b9..1e8e8baa7e 100644
--- a/src/components/LogoSearchForm.jsx
+++ b/src/components/LogoSearchForm.jsx
@@ -9,6 +9,7 @@ import { useTranslation } from "react-i18next";
import LabelFilter from "../components/QuestionFilter/LabelFilter";
import { TYPE_WITHOUT_VALUE } from "../const";
+import TaxonomyAutoSelect from "./TaxonomyAutoSelect";
export const logoTypeOptions = [
{ value: "", labelKey: "logos.type" },
@@ -83,6 +84,16 @@ const LogoSearchForm = (props) => {
label={t("logos.value")}
size="small"
/>
+ ) : innerType === "brand" ? (
+
) : (
{
/>
)}
-
& {
+ /**
+ * The taxonly to querry
+ */
+ taxonomy: TaxonomyNames;
+ onChange: (itemId: string) => void;
+ value: string;
+ /**
+ * The the id bellow the text field
+ */
+ showKey?: boolean;
+};
+
+const isOptionEqualToValue = (option: string | TaxonomyItem, value: string) =>
+ (typeof option === "string" ? option : option.id) === value;
+
+export default function TaxonomyAutoSelect(props: TaxonomyAutoSelectProps) {
+ const { taxonomy, value, onChange, showKey, fullWidth, ...other } = props;
+ const [inputValue, setInputValue] = React.useState("");
+ const [selectedValue, setSelectedValue] = React.useState(null);
+ const [options, setOptions] = React.useState<
+ readonly (string | TaxonomyItem)[]
+ >([]);
+
+ const language = "en"; //getLang();
+
+ const fetch = React.useMemo(
+ () =>
+ debounce(
+ (
+ request: { input: string },
+ callback: (results?: readonly TaxonomyItem[]) => void,
+ ) => {
+ searchTaxonomy[taxonomy](request.input, language).then(({ data }) => {
+ callback((data?.options as TaxonomyItem[]) ?? []);
+ });
+ },
+ 400,
+ ),
+ [language],
+ );
+
+ React.useEffect(() => {
+ let active = true;
+
+ if (inputValue === "") {
+ setOptions(value ? [value] : []);
+ return undefined;
+ }
+
+ fetch({ input: inputValue }, (results?: readonly TaxonomyItem[]) => {
+ if (active) {
+ let newOptions: readonly (string | TaxonomyItem)[] = [];
+
+ // if (value) {
+ // newOptions = [];
+ // }
+
+ if (results) {
+ newOptions = [...newOptions, ...results];
+ }
+
+ setOptions(newOptions);
+ }
+ });
+
+ return () => {
+ active = false;
+ };
+ }, [value, inputValue, fetch]);
+
+ const selectedOption = options.find((option) =>
+ isOptionEqualToValue(option, value),
+ );
+ return (
+
+ typeof option === "string" ? option : option.text
+ }
+ freeSolo
+ filterOptions={(x) => x}
+ options={options}
+ autoComplete
+ includeInputInList
+ filterSelectedOptions
+ value={
+ selectedValue && selectedValue.id === value ? selectedValue : value
+ }
+ isOptionEqualToValue={isOptionEqualToValue}
+ // noOptionsText="No locations"
+ onChange={(event: any, newValue: TaxonomyItem | null | string) => {
+ console.log({ newValue });
+ if (typeof newValue === "object") {
+ onChange(newValue.id);
+ setSelectedValue(newValue);
+ return;
+ }
+ onChange(newValue);
+ }}
+ onInputChange={(event, newInputValue) => {
+ setInputValue(newInputValue);
+ }}
+ onBlur={() => {
+ console.log("blur");
+ console.log({
+ inputValue,
+ value,
+ selectedOption,
+ });
+ if (
+ inputValue === value ||
+ (selectedValue && selectedValue.text === inputValue)
+ ) {
+ return;
+ }
+ onChange(inputValue);
+ }}
+ fullWidth={fullWidth}
+ renderInput={(params) => (
+ {
+ console.log(event.key);
+ if (event.key === "Enter") {
+ event.preventDefault();
+ }
+ }}
+ helperText={
+ showKey && value
+ ? selectedValue && selectedValue.id === value
+ ? selectedValue.id
+ : `⚠️ unknown: "${value}"`
+ : ""
+ }
+ />
+ )}
+ />
+ );
+}
diff --git a/src/const.ts b/src/const.ts
index d805b3eaa5..d1e5f133f3 100644
--- a/src/const.ts
+++ b/src/const.ts
@@ -6,6 +6,8 @@ export const OFF_API_URL_V2 = `${OFF_URL}/api/v2`;
export const OFF_API_URL_V3 = `${OFF_URL}/api/v3`;
export const OFF_IMAGE_URL = `https://static.${OFF_DOMAIN}/images/products`;
export const OFF_SEARCH = `${OFF_URL}/cgi/search.pl`;
+export const OFF_SEARCH_A_LISIOUS =
+ "https://search.openfoodfacts.org/autocomplete";
export const IS_DEVELOPMENT_MODE = process.env.NODE_ENV === "development";
export const URL_ORIGINE = IS_DEVELOPMENT_MODE
? "http://localhost:5173"
diff --git a/src/offSearch.ts b/src/offSearch.ts
new file mode 100644
index 0000000000..378b179564
--- /dev/null
+++ b/src/offSearch.ts
@@ -0,0 +1,88 @@
+import axios, { AxiosResponse } from "axios";
+import { OFF_SEARCH_A_LISIOUS } from "./const";
+
+export type TaxonomyNames =
+ | "additive"
+ | "allergen"
+ | "amino_acid"
+ | "brand"
+ | "category"
+ | "country"
+ | "data_quality"
+ | "label"
+ | "food_group"
+ | "improvement"
+ | "ingredient"
+ | "ingredients_analysis"
+ | "ingredients_processing"
+ | "language"
+ | "mineral"
+ | "misc"
+ | "nova_group"
+ | "nucleotide"
+ | "nutrient"
+ | "origin"
+ | "other_nutritional_substance"
+ | "packaging_material"
+ | "packaging_recycling"
+ | "packaging_shape"
+ | "periods_after_opening"
+ | "preservation"
+ | "state"
+ | "vitamin";
+
+const taxonomy_names: TaxonomyNames[] = [
+ "additive",
+ "allergen",
+ "amino_acid",
+ "brand",
+ "category",
+ "country",
+ "data_quality",
+ "label",
+ "food_group",
+ "improvement",
+ "ingredient",
+ "ingredients_analysis",
+ "ingredients_processing",
+ "language",
+ "mineral",
+ "misc",
+ "nova_group",
+ "nucleotide",
+ "nutrient",
+ "origin",
+ "other_nutritional_substance",
+ "packaging_material",
+ "packaging_recycling",
+ "packaging_shape",
+ "periods_after_opening",
+ "preservation",
+ "state",
+ "vitamin",
+];
+
+const searchTaxonomy = {};
+
+taxonomy_names.forEach((taxonomy) => {
+ searchTaxonomy[taxonomy] = (querry: string, language?: string) =>
+ axios.get(
+ `${OFF_SEARCH_A_LISIOUS}?taxonomy_names=${taxonomy}&q=${querry}&lang=${
+ language || "en"
+ }`,
+ );
+});
+
+export type TaxonomyItem = {
+ id: string;
+ text: string;
+ taxonomy_name: string;
+};
+
+export default searchTaxonomy as Record<
+ TaxonomyNames,
+ (
+ querry: string,
+ language?: string,
+ ) => Promise>
+>;
diff --git a/src/robotoff.ts b/src/robotoff.ts
index 4c1cd90a8d..0097dc006a 100644
--- a/src/robotoff.ts
+++ b/src/robotoff.ts
@@ -106,7 +106,7 @@ const robotoff = {
},
searchLogos(barcode, value, type, count = 25, random = false) {
- const formattedValue = ["label", "category"].includes(type)
+ const formattedValue = value.test(/^[a-z][a-z]:/)
? { taxonomy_value: value }
: { value };