diff --git a/src/app/components/Editor.tsx b/src/app/components/Editor.tsx index d50f0df..dec45b7 100644 --- a/src/app/components/Editor.tsx +++ b/src/app/components/Editor.tsx @@ -1,7 +1,7 @@ import { FieldErrors, FieldPathByValue, FormProvider, Resolver, useForm } from "react-hook-form"; import PubliccodeYmlLanguages from "./PubliccodeYmlLanguages"; -import { Col, Container, notify, Row } from "design-react-kit"; +import { Col, Container, Icon, notify, Row } from "design-react-kit"; import { set } from "lodash"; import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -44,6 +44,9 @@ import { resetPubliccodeYmlLanguages, setPubliccodeYmlLanguages } from "../store import yamlSerializer from "../yaml-serializer"; import { removeDuplicate } from "../yaml-upload"; import EditorUsedBy from "./EditorUsedBy"; +import { WarningModal } from "./WarningModal"; + +const PUBLIC_CODE_EDITOR_WARNINGS = 'PUBLIC_CODE_EDITOR_WARNINGS' const validatorFn = async (values: PublicCode) => await validator({ publiccode: JSON.stringify(values), baseURL: values.url }); @@ -105,6 +108,20 @@ export default function Editor() { const [currentPublicodeYmlVersion, setCurrentPubliccodeYmlVersion] = useState(''); const [isYamlModalVisible, setYamlModalVisibility] = useState(false); const [isPublicCodeImported, setPublicCodeImported] = useState(false); + const [isWarningModalVisible, setWarningModalVisibility] = useState(false); + const [warnings, setWarnings] = useState<{ key: string; message: string; }[]>([]); + + useEffect(() => { + const warnings = localStorage.getItem(PUBLIC_CODE_EDITOR_WARNINGS); + + if (warnings) { + setWarnings(JSON.parse(warnings)) + } + }, []) + + useEffect(() => { + localStorage.setItem(PUBLIC_CODE_EDITOR_WARNINGS, JSON.stringify(warnings)) + }, [warnings]) const getNestedValue = (obj: PublicCodeWithDeprecatedFields, path: string) => { return path.split('.').reduce((acc, key) => (acc as never)?.[key], obj); @@ -222,6 +239,7 @@ export default function Editor() { reset({ ...defaultValues }); checkPubliccodeYmlVersion(getValues() as PublicCode); setPublicCodeImported(false); + setWarnings([]) }; const setFormDataAfterImport = async ( @@ -245,17 +263,19 @@ export default function Editor() { const res = await checkWarnings(values) - if (res.warnings.size) { - const body = Array - .from(res.warnings) - .reduce((p, [key, { message }]) => p + `${key}: ${message}`, '') + setWarnings(Array.from(res.warnings).map(([key, { message }]) => ({ key, message }))); + + const numberOfWarnings = res.warnings.size; - const _1_MINUTE = 60 * 1 * 1000 + if (numberOfWarnings) { + const body = `ci sono ${numberOfWarnings} warnings` + + const _5_SECONDS = 5 * 1 * 1000 notify("Warnings", body, { dismissable: true, state: 'warning', - duration: _1_MINUTE + duration: _5_SECONDS }) } } @@ -281,7 +301,16 @@ export default function Editor() {
- +
+
+ +
+ {!!warnings.length && +
+ setWarningModalVisibility(true)} />  +
+ } +
@@ -496,6 +525,11 @@ export default function Editor() { display={isYamlModalVisible} toggle={() => setYamlModalVisibility(!isYamlModalVisible)} /> + setWarningModalVisibility(!isWarningModalVisible)} + warnings={warnings} + />
); diff --git a/src/app/components/EditorFeatures.tsx b/src/app/components/EditorFeatures.tsx index 921fd59..add0f69 100644 --- a/src/app/components/EditorFeatures.tsx +++ b/src/app/components/EditorFeatures.tsx @@ -1,17 +1,22 @@ +import { Button, Icon, Input, InputGroup } from "design-react-kit"; +import { get } from "lodash"; +import { useEffect, useRef, useState } from "react"; import { useController, useFormContext } from "react-hook-form"; -import PublicCode from "../contents/publiccode"; import { useTranslation } from "react-i18next"; -import { get } from "lodash"; -import { useState } from "react"; -import { Button, Icon, Input, InputGroup } from "design-react-kit"; +import PublicCode from "../contents/publiccode"; +import flattenObject from "../flatten-object-to-record"; +import { removeDuplicate } from "../yaml-upload"; interface Props { lang: string; } export default function EditorFeatures({ lang }: Props): JSX.Element { + const formFieldName = `description.${lang}.features` as keyof PublicCode; + const { control } = useFormContext(); const { + field, field: { onChange, value }, formState: { errors }, } = useController({ @@ -22,21 +27,34 @@ export default function EditorFeatures({ lang }: Props): JSX.Element { const { t } = useTranslation(); const features: string[] = value ? (value as string[]) : []; - const [currFeat, setCurrFeat] = useState(""); + const [current, setCurrent] = useState(""); const label = t(`publiccodeyml.description.features.label`); const description = t(`publiccodeyml.description.features.description`); const errorMessage = get(errors, `description.${lang}.features.message`); - const addFeature = () => { - onChange([...features, currFeat.trim()]); - setCurrFeat(""); + const add = () => { + onChange(removeDuplicate([...features, current.trim()])); + setCurrent(""); }; - const removeFeature = (feat: string) => { + const remove = (feat: string) => { onChange(features.filter((elem) => elem !== feat)); }; + const inputRef = useRef(null); + + useEffect(() => { + const errorsRecord = flattenObject(errors as Record); + const formFieldKeys = Object.keys(errorsRecord); + const isFirstError = formFieldKeys && formFieldKeys.length && formFieldKeys[0] === formFieldName + + if (isFirstError) { + inputRef.current?.focus() + } + + }, [errors, formFieldName, inputRef]) + return (
@@ -54,7 +72,7 @@ export default function EditorFeatures({ lang }: Props): JSX.Element { diff --git a/src/app/components/EditorMultiselect.tsx b/src/app/components/EditorMultiselect.tsx index ec815f6..f514dae 100644 --- a/src/app/components/EditorMultiselect.tsx +++ b/src/app/components/EditorMultiselect.tsx @@ -1,14 +1,16 @@ +import { get } from "lodash"; +import { useEffect, useRef } from "react"; import { FieldPathByValue, useController, useFormContext, } from "react-hook-form"; -import { RequiredDeep } from "type-fest"; -import PublicCode, { PublicCodeWithDeprecatedFields } from "../contents/publiccode"; import { useTranslation } from "react-i18next"; import { Multiselect } from "react-widgets"; import { Filter } from "react-widgets/Filter"; -import { get } from "lodash"; +import { RequiredDeep } from "type-fest"; +import PublicCode, { PublicCodeWithDeprecatedFields } from "../contents/publiccode"; +import flattenObject from "../flatten-object-to-record"; type Props = { fieldName: T; @@ -37,6 +39,19 @@ export default function EditorMultiselect< const description = t(`publiccodeyml.${fieldName}.description`); const errorMessage = get(errors, `${fieldName}.message`); + const inputRef = useRef(null); + + useEffect(() => { + const errorsRecord = flattenObject(errors as Record); + const formFieldKeys = Object.keys(errorsRecord); + const isFirstError = formFieldKeys && formFieldKeys.length && formFieldKeys[0] === fieldName + + if (isFirstError) { + inputRef.current?.focus() + } + + }, [errors, fieldName, inputRef]) + return (