ts/features/fci/screens/valid/FciSignatureFieldsScreen.tsx
import * as React from "react";
import { View, SectionList, ScrollView } from "react-native";
import { Route, StackActions, useRoute } from "@react-navigation/native";
import * as RA from "fp-ts/lib/ReadonlyArray";
import * as O from "fp-ts/lib/Option";
import { constFalse, increment, pipe } from "fp-ts/lib/function";
import {
ButtonSolidProps,
FooterWithButtons,
H2,
H4,
IconButton,
IOColors,
IOStyles,
VSpacer
} from "@pagopa/io-app-design-system";
import I18n from "../../../../i18n";
import { emptyContextualHelp } from "../../../../utils/emptyContextualHelp";
import { useIODispatch, useIOSelector } from "../../../../store/hooks";
import {
fciDocumentSignatureFieldsSelector,
fciSignatureDetailDocumentsSelector
} from "../../store/reducers/fciSignatureRequest";
import { DocumentDetailView } from "../../../../../definitions/fci/DocumentDetailView";
import { useIONavigation } from "../../../../navigation/params/AppParamsList";
import SignatureFieldItem from "../../components/SignatureFieldItem";
import { SignatureField } from "../../../../../definitions/fci/SignatureField";
import { FCI_ROUTES } from "../../navigation/routes";
import { fciDocumentSignaturesSelector } from "../../store/reducers/fciDocumentSignatures";
import {
fciEndRequest,
fciUpdateDocumentSignaturesRequest
} from "../../store/actions";
import { useFciAbortSignatureFlow } from "../../hooks/useFciAbortSignatureFlow";
import {
Clause,
TypeEnum as ClausesTypeEnum
} from "../../../../../definitions/fci/Clause";
import { DocumentToSign } from "../../../../../definitions/fci/DocumentToSign";
import {
getClauseLabel,
getRequiredSignatureFields,
getSectionListData,
orderSignatureFields
} from "../../utils/signatureFields";
import { LightModalContext } from "../../../../components/ui/LightModal";
import DocumentWithSignature from "../../components/DocumentWithSignature";
import GenericErrorComponent from "../../components/GenericErrorComponent";
import {
trackFciShowSignatureFields,
trackFciStartSignature
} from "../../analytics";
import { useFciSignatureFieldInfo } from "../../hooks/useFciSignatureFieldInfo";
import { fciEnvironmentSelector } from "../../store/reducers/fciEnvironment";
import { useHeaderSecondLevel } from "../../../../hooks/useHeaderSecondLevel";
export type FciSignatureFieldsScreenNavigationParams = Readonly<{
documentId: DocumentDetailView["id"];
currentDoc: number;
}>;
const FciSignatureFieldsScreen = () => {
const { currentDoc, documentId: docId } =
useRoute<
Route<"FCI_SIGNATURE_FIELDS", FciSignatureFieldsScreenNavigationParams>
>().params;
const documentsSelector = useIOSelector(fciSignatureDetailDocumentsSelector);
const signatureFieldsSelector = useIOSelector(
fciDocumentSignatureFieldsSelector(docId)
);
const documentsSignaturesSelector = useIOSelector(
fciDocumentSignaturesSelector
);
const fciEnvironment = useIOSelector(fciEnvironmentSelector);
const dispatch = useIODispatch();
const navigation = useIONavigation();
const [isClausesChecked, setIsClausesChecked] = React.useState(false);
const [isError, setIsError] = React.useState(false);
const { showModal, hideModal } = React.useContext(LightModalContext);
// get signatureFields for the current document
const docSignatures = React.useMemo(
() =>
pipe(
documentsSignaturesSelector,
RA.findFirst(doc => doc.document_id === docId)
),
[docId, documentsSignaturesSelector]
);
// get required signatureFields for the current document
// that user should check to sign the document
const requiredFields = React.useMemo(
() => getRequiredSignatureFields(signatureFieldsSelector),
[signatureFieldsSelector]
);
React.useEffect(() => {
// get the required signature fields for the current document,
// which the user has previously checked to sign it
const res = pipe(
requiredFields,
RA.map(signatureField =>
pipe(
docSignatures,
RA.fromOption,
RA.map(doc => doc.signature_fields),
RA.map(fields => fields.filter(f => f === signatureField)),
RA.flatten
)
),
RA.flatten
);
setIsClausesChecked(res.length >= requiredFields.length);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [docSignatures]);
const { present, bottomSheet: fciAbortSignature } =
useFciAbortSignatureFlow();
const { present: presentInfo, bottomSheet: fciSignaturefieldInfo } =
useFciSignatureFieldInfo();
const onPressDetail = (signatureField: SignatureField) => {
trackFciShowSignatureFields(fciEnvironment);
showModal(
<DocumentWithSignature
attrs={signatureField.attrs}
currentDoc={currentDoc}
onClose={hideModal}
onError={() => onError()}
testID={"FciDocumentWithSignatureTestID"}
/>
);
};
/**
* Callback which sets the isError state to true and hides the modal.
*/
const onError = () => {
setIsError(true);
hideModal();
};
const updateDocumentSignatures = (fn: (doc: DocumentToSign) => void) =>
pipe(
docSignatures,
O.chain(document => O.fromNullable(document)),
O.map(fn)
);
const onChange = (value: boolean, item: SignatureField) =>
updateDocumentSignatures(doc =>
dispatch(
fciUpdateDocumentSignaturesRequest({
...doc,
signature_fields: !value
? [...doc.signature_fields.filter(f => f !== item)]
: [...doc.signature_fields, item]
})
)
);
const renderSectionHeader = (info: { section: { title: string } }) => {
const clauseLabel = getClauseLabel(info.section.title as Clause["type"]);
return (
<View
style={{
backgroundColor: IOColors.white,
flexDirection: "row"
}}
>
<H4 color="bluegrey" style={IOStyles.flex}>
{clauseLabel}
</H4>
{/*
Show info icon and signature field info only for unfair clauses
NOTE: this could be a temporary solution, since we could have
an improved user experience.
*/}
{info.section.title === ClausesTypeEnum.UNFAIR && (
<>
<IconButton
icon={"info"}
onPress={presentInfo}
accessibilityLabel={I18n.t("global.buttons.info")}
/>
{fciSignaturefieldInfo}
</>
)}
</View>
);
};
const renderSignatureFields = () => (
<SectionList
sections={getSectionListData(
orderSignatureFields(signatureFieldsSelector)
)}
keyExtractor={(item, index) => `${item.clause.title}${index}`}
testID={"FciSignatureFieldsSectionListTestID"}
renderItem={({ item }) => (
<SignatureFieldItem
title={item.clause.title}
disabled={item.clause.type === ClausesTypeEnum.REQUIRED}
value={pipe(
documentsSignaturesSelector,
RA.findFirst(doc => doc.document_id === docId),
O.chain(document => O.fromNullable(document)),
O.map(doc => doc.signature_fields),
O.map(RA.filter(f => f === item)),
O.fold(constFalse, RA.isNonEmpty)
)}
onChange={v => onChange(v, item)}
onPressDetail={() => onPressDetail(item)}
/>
)}
renderSectionHeader={renderSectionHeader}
/>
);
const cancelButtonProps: ButtonSolidProps = {
onPress: present,
label: I18n.t("global.buttons.cancel"),
accessibilityLabel: I18n.t("global.buttons.cancel")
};
const continueButtonProps: ButtonSolidProps = {
disabled: !isClausesChecked,
onPress: () => {
if (currentDoc < documentsSelector.length - 1) {
navigation.dispatch(
StackActions.push(FCI_ROUTES.DOCUMENTS, {
attrs: undefined,
currentDoc: increment(currentDoc)
})
);
} else {
trackFciStartSignature(fciEnvironment);
navigation.navigate(FCI_ROUTES.MAIN, {
screen: FCI_ROUTES.USER_DATA_SHARE
});
}
},
accessibilityLabel:
currentDoc < documentsSelector.length - 1
? I18n.t("global.buttons.continue")
: "Firma",
label:
currentDoc < documentsSelector.length - 1
? I18n.t("global.buttons.continue")
: "Firma"
};
useHeaderSecondLevel({
title: I18n.t("features.fci.title"),
supportRequest: true,
contextualHelp: emptyContextualHelp
});
if (isError) {
return (
<GenericErrorComponent
title={I18n.t("features.fci.errors.generic.default.title")}
subTitle={I18n.t("features.fci.errors.generic.default.subTitle")}
onPress={() => dispatch(fciEndRequest())}
testID={"FciGenericErrorTestID"}
/>
);
}
return (
<View style={IOStyles.flex} testID={"FciSignatureFieldsTestID"}>
<ScrollView style={IOStyles.horizontalContentPadding}>
<H2>{I18n.t("features.fci.signatureFields.title")}</H2>
<VSpacer size={32} />
{renderSignatureFields()}
</ScrollView>
<FooterWithButtons
type={"TwoButtonsInlineThird"}
secondary={{ type: "Solid", buttonProps: continueButtonProps }}
primary={{ type: "Outline", buttonProps: cancelButtonProps }}
/>
{fciAbortSignature}
</View>
);
};
export default FciSignatureFieldsScreen;