Skip to content

Commit

Permalink
feat(IT Wallet): [SIW-447] Add credentials issuing info screen (#5085)
Browse files Browse the repository at this point in the history
## Short description
This PR adds the info credentials screen that show to the user the
credentials requested by the issuer,

## List of changes proposed in this pull request
- Add Credential Issuing Info Screen
- Add a new component to render a grey box with a bullet list of
credentials
- Add a new BS to show more info about the issuing flow

NOTE: some data could be mocked

## How to test
Start and activate IT-WALLET, try to add a new credential. It should be
possibile to show a screen with a summary of requested credentials

---------

Co-authored-by: LazyAfternoons <LazyAfternoons@users.noreply.github.com>
  • Loading branch information
hevelius and LazyAfternoons committed Oct 11, 2023
1 parent 8e7b9a9 commit 9d6ac2a
Show file tree
Hide file tree
Showing 12 changed files with 360 additions and 5 deletions.
Binary file added img/features/it-wallet/interno.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions locales/en/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3422,6 +3422,18 @@ features:
title: You are adding {{credentialName}} to your Wallet
toast:
cancel: "The credential was not added to the wallet."
credentialsIssuingInfoScreen:
title: "Data needed to add the card"
subtitle: "{{authsource}} will send them to {{organization}}."
readMore: "Find out more"
dataSource: "Provided by {{authsource}}"
infoBottomSheet:
title: "Data processing"
body:
firstHeaderTitle: "Why is it necessary?"
firstBodyContent: "To obtain the issuance of the card The body authorized to issue this card needs to read your data to verify your identity."
secondHeaderTitle: "Safety of treatment"
secondBodyContent: "App IO allows the sharing of your data only with verified bodies registered on the lists that guarantee its reliability."
presentation:
credentialDetails:
buttons:
Expand Down
12 changes: 12 additions & 0 deletions locales/it/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3446,6 +3446,18 @@ features:
title: Stai aggiungendo {{credentialName}} al Portafoglio
toast:
cancel: "Tessera non aggiunta al Portafoglio."
credentialsIssuingInfoScreen:
title: "Dati necessari per aggiungere la tessera"
subtitle: "{{authsource}} li invierà a {{organization}}."
readMore: "Scopri di più"
dataSource: "Forniti da {{authsource}}"
infoBottomSheet:
title: "Trattamento dei dati"
body:
firstHeaderTitle: "Perché è necessario?"
firstBodyContent: "Per ottenere il rilascio della tessera L’ente autorizzato ad emettere questa tessera ha bisogno di leggere i tuoi dati per verificarne l’identità."
secondHeaderTitle: "Sicurezza del trattamento"
secondBodyContent: "App IO consente la condivisione dei tuoi dati solo con enti verificati e iscritti alle liste che ne garantiscono l’affidabilità."
presentation:
credentialDetails:
buttons:
Expand Down
52 changes: 52 additions & 0 deletions ts/features/it-wallet/components/ItwBulletList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as React from "react";
import { View, StyleSheet } from "react-native";
import { Body, H6, IOColors } from "@pagopa/io-app-design-system";
import * as RA from "fp-ts/lib/ReadonlyArray";
import { pipe } from "fp-ts/lib/function";

type Props = {
data: ReadonlyArray<BulletItem>;
};

export type BulletItem = {
title: string;
data: ReadonlyArray<string>;
};

const styles = StyleSheet.create({
container: {
padding: 24,
backgroundColor: IOColors.greyUltraLight,
borderRadius: 8
}
});
const BULLET_ITEM = "\u2022";

/**
* A component to render a list of bullet items
* @param data - the list of bullet items
*/
const ItwBulletList = ({ data }: Props) => (
<View style={styles.container}>
{pipe(
data,
RA.mapWithIndex((index, section) => (
<View key={`${index}-${section.title}`}>
<Body style={{ marginBottom: 8 }} weight="Regular" color="grey-700">
{section.title}
</Body>
{section.data.map((claim, index) => (
<View
style={{ marginBottom: 10, paddingLeft: 8 }}
key={`${index}-${claim}`}
>
<H6>{`${BULLET_ITEM} ${claim}`}</H6>
</View>
))}
</View>
))
)}
</View>
);

export default ItwBulletList;
3 changes: 3 additions & 0 deletions ts/features/it-wallet/components/ItwFooterInfoBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ const styles = StyleSheet.create({
* This is a component that is used in the footer of the ItwWalletScreen.
* It is used to display information about the wallet. It could have an info icon
* and a content as markdown text.
*
* NOTE: This component will be refactored in the future.
* https://pagopa.atlassian.net/browse/SIW-590
*/
const ItwFooterInfoBox = (props: Props) => (
<View style={IOStyles.horizontalContentPadding}>
Expand Down
53 changes: 53 additions & 0 deletions ts/features/it-wallet/hooks/useItwDataProcessing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as React from "react";
import { View } from "react-native";
import { Body, H6, VSpacer } from "@pagopa/io-app-design-system";
import { useIOBottomSheetModal } from "../../../utils/hooks/bottomSheet";
import { IOStyles } from "../../../components/core/variables/IOStyles";
import I18n from "../../../i18n";

/**
* A hook that returns a function to present an info bottom sheet
* related to the data processing of the credentials issuing
*/
export const useItwDataProcessing = () => {
const BottomSheetBody = () => (
<View style={IOStyles.flex}>
<H6>
{I18n.t(
"features.itWallet.issuing.credentialsIssuingInfoScreen.infoBottomSheet.body.firstHeaderTitle"
)}
</H6>
<VSpacer size={8} />
<Body>
{I18n.t(
"features.itWallet.issuing.credentialsIssuingInfoScreen.infoBottomSheet.body.firstBodyContent"
)}
</Body>
<VSpacer size={24} />
<H6>
{I18n.t(
"features.itWallet.issuing.credentialsIssuingInfoScreen.infoBottomSheet.body.secondHeaderTitle"
)}
</H6>
<VSpacer size={8} />
<Body>
{I18n.t(
"features.itWallet.issuing.credentialsIssuingInfoScreen.infoBottomSheet.body.secondBodyContent"
)}
</Body>
</View>
);
const { present, bottomSheet, dismiss } = useIOBottomSheetModal({
title: I18n.t(
"features.itWallet.issuing.credentialsIssuingInfoScreen.infoBottomSheet.title"
),
component: <BottomSheetBody />,
snapPoint: [350]
});

return {
dismiss,
present,
bottomSheet
};
};
1 change: 1 addition & 0 deletions ts/features/it-wallet/navigation/ItwParamsList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export type ItwParamsList = {
[ITW_ROUTES.CREDENTIALS.CATALOG]: undefined;
[ITW_ROUTES.CREDENTIALS
.ADD_CHECKS]: ItwAddCredentialsCheckScreenNavigationParams;
[ITW_ROUTES.CREDENTIALS.ISSUING_INFO]: undefined;
// GENERIC
[ITW_ROUTES.GENERIC.NOT_AVAILABLE]: undefined;
};
3 changes: 2 additions & 1 deletion ts/features/it-wallet/navigation/ItwRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export const ITW_ROUTES = {
},
CREDENTIALS: {
CATALOG: "ITW_CREDENTIALS_CATALOG",
ADD_CHECKS: "ITW_CREDENTIALS_ADD_CHECKS"
ADD_CHECKS: "ITW_CREDENTIALS_ADD_CHECKS",
ISSUING_INFO: "ITW_CREDENTIALS_ISSUING_INFO"
} as const,
GENERIC: {
NOT_AVAILABLE: "ITW_GENERIC_NOT_AVAILABLE"
Expand Down
5 changes: 5 additions & 0 deletions ts/features/it-wallet/navigation/ItwStackNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import ItwDiscoveryProviderInfoScreen from "../screens/discovery/ItwDiscoveryPro
import ItwCredentialsCatalogScreen from "../screens/credentials/ItwCredentialsCatalogScreen";
import ItwMissingFeatureScreen from "../screens/generic/ItwMissingFeatureScreen";
import ItwAddCredentialsCheckScreen from "../screens/credentials/ItwAddCredentialsCheckScreen";
import ItwCredentialIssuingInfoScreen from "../screens/credentials/ItwCredentialIssuingInfoScreen";
import { ItwParamsList } from "./ItwParamsList";
import { ITW_ROUTES } from "./ItwRoutes";

Expand Down Expand Up @@ -98,6 +99,10 @@ export const ItwStackNavigator = () => (
name={ITW_ROUTES.CREDENTIALS.ADD_CHECKS}
component={ItwAddCredentialsCheckScreen}
/>
<Stack.Screen
name={ITW_ROUTES.CREDENTIALS.ISSUING_INFO}
component={ItwCredentialIssuingInfoScreen}
/>
{/* COMMON */}
<Stack.Screen
name={ITW_ROUTES.GENERIC.NOT_AVAILABLE}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import BaseScreenComponent from "../../../../components/screens/BaseScreenCompon
import { emptyContextualHelp } from "../../../../utils/emptyContextualHelp";
import ItwContinueScreen from "../../components/ItwResultComponent";
import ROUTES from "../../../../navigation/routes";
import { ITW_ROUTES } from "../../navigation/ItwRoutes";

/**
* ItwAddCredentialsCheckScreen screen navigation params.
Expand Down Expand Up @@ -99,7 +100,8 @@ const ItwAddCredentialsCheckScreen = () => {
action={{
label: I18n.t("global.buttons.confirm"),
accessibilityLabel: I18n.t("global.buttons.confirm"),
onPress: () => null
onPress: () =>
navigation.navigate(ITW_ROUTES.CREDENTIALS.ISSUING_INFO)
}}
secondaryAction={{
label: I18n.t("global.buttons.cancel"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import * as React from "react";
import { View, SafeAreaView, Image, ScrollView } from "react-native";
import {
Body,
FooterWithButtons,
H1,
HSpacer,
IOStyles,
Icon,
IconContained,
LabelLink,
VSpacer
} from "@pagopa/io-app-design-system";
import { constVoid, pipe } from "fp-ts/lib/function";
import * as O from "fp-ts/lib/Option";
import { useNavigation } from "@react-navigation/native";
import { sequenceS } from "fp-ts/lib/Apply";
import { PidWithToken } from "@pagopa/io-react-native-wallet/lib/typescript/pid/sd-jwt";
import interno from "../../../../../img/features/it-wallet/interno.png";
import BaseScreenComponent from "../../../../components/screens/BaseScreenComponent";
import { emptyContextualHelp } from "../../../../utils/emptyContextualHelp";
import { useIOSelector } from "../../../../store/hooks";
import { itwDecodedPidValueSelector } from "../../store/reducers/itwPidDecodeReducer";
import ItwErrorView from "../../components/ItwErrorView";
import { cancelButtonProps } from "../../utils/itwButtonsUtils";
import { IOStackNavigationProp } from "../../../../navigation/params/AppParamsList";
import { ItwParamsList } from "../../navigation/ItwParamsList";
import ItwFooterInfoBox from "../../components/ItwFooterInfoBox";
import I18n from "../../../../i18n";
import ItwBulletList from "../../components/ItwBulletList";
import { useItwDataProcessing } from "../../hooks/useItwDataProcessing";
import { CREDENTIAL_ISSUER, CredentialCatalogItem } from "../../utils/mocks";
import { ItwCredentialsCheckCredentialSelector } from "../../store/reducers/itwCredentialsChecksReducer";

type ContentViewParams = {
decodedPid: PidWithToken;
credential: CredentialCatalogItem;
};

/**
* This screen displays the information about the credential that is going to be shared
* with the issuer.
*/
const ItwCredentialIssuingInfoScreen = () => {
const decodedPid = useIOSelector(itwDecodedPidValueSelector);
const navigation = useNavigation<IOStackNavigationProp<ItwParamsList>>();
const credential = useIOSelector(ItwCredentialsCheckCredentialSelector);
const { present, bottomSheet } = useItwDataProcessing();

const ContentView = ({ decodedPid, credential }: ContentViewParams) => (
<SafeAreaView style={IOStyles.flex}>
<ScrollView style={IOStyles.horizontalContentPadding}>
<VSpacer size={32} />
{/* SECOND HEADER */}
<View
style={{
flexDirection: "row",
alignContent: "center",
alignItems: "center"
}}
>
{/* LEFT */}
<View
style={{
flexDirection: "row",
alignItems: "center"
}}
>
<IconContained
icon={"device"}
color={"neutral"}
variant={"tonal"}
/>
<HSpacer size={8} />
<Icon name={"transactions"} color={"grey-450"} size={24} />
<HSpacer size={8} />
<IconContained
icon={"institution"}
color={"neutral"}
variant={"tonal"}
/>
</View>
{/* RIGHT */}
<Image
source={interno}
resizeMode={"contain"}
style={{ width: "100%", height: 32 }}
/>
</View>
<VSpacer size={24} />
<H1>
{I18n.t(
"features.itWallet.issuing.credentialsIssuingInfoScreen.title"
)}
</H1>
<Body>
{I18n.t(
"features.itWallet.issuing.credentialsIssuingInfoScreen.subtitle",
{
authsource:
decodedPid.pid.verification.evidence[0].record.source
.organization_name,
organization: CREDENTIAL_ISSUER
}
)}
</Body>
<VSpacer size={16} />
<LabelLink onPress={() => present()}>
{I18n.t(
"features.itWallet.issuing.credentialsIssuingInfoScreen.readMore"
)}
</LabelLink>
<VSpacer size={24} />

{/* Render a list of claims that will be shared with the credential issuer */}
<ItwBulletList data={credential.requestedClaims(decodedPid)} />

{/* ItwFooterInfoBox should be replaced with a more ligth component */}
<ItwFooterInfoBox
content={I18n.t("features.itWallet.activationScreen.tos")}
/>
<VSpacer size={48} />
</ScrollView>
<FooterWithButtons
primary={{
type: "Outline",
buttonProps: {
color: "primary",
accessibilityLabel: I18n.t("global.buttons.cancel"),
onPress: constVoid,
label: I18n.t("global.buttons.cancel")
}
}}
secondary={{
type: "Solid",
buttonProps: {
color: "primary",
accessibilityLabel: I18n.t("global.buttons.continue"),
onPress: constVoid,
label: I18n.t("global.buttons.continue")
}
}}
type="TwoButtonsInlineHalf"
/>
</SafeAreaView>
);

const DecodedPidOrErrorView = () =>
pipe(
sequenceS(O.Applicative)({ decodedPid, credential }),
O.fold(
() => (
<ItwErrorView
type="SingleButton"
leftButton={cancelButtonProps(navigation.goBack)}
/>
),
some => <ContentView {...some} />
)
);

return (
<BaseScreenComponent goBack={true} contextualHelp={emptyContextualHelp}>
<DecodedPidOrErrorView />
{bottomSheet}
</BaseScreenComponent>
);
};
export default ItwCredentialIssuingInfoScreen;
Loading

0 comments on commit 9d6ac2a

Please sign in to comment.