Skip to content

Commit

Permalink
feat(IT Wallet): [SIW-1638] Handle unknown credential status (#6576)
Browse files Browse the repository at this point in the history
## Short description
This PR handles unexpected failures of the status attestation call. When
that happens (for instance because of network problems), it is not
possibile to determine the status of a credential. This "unknown" status
is reflected in the UI and the credential detail cannot be accessed.

## List of changes proposed in this pull request
- Added new card assets for the "Not available" status
- Explicitly return `unknown` in `getCredentialStatus` (previously an
unknown status attestation would return `valid`)
- Modified `ItwCredentialCard` to display the greyed-out card
- Added the new status in the cards DS section

## How to test
The easiest way to test this PR is by mocking the result of the
`/status` endpoint so that it returns 500.

<img
src="https://github.com/user-attachments/assets/daa56de4-04f1-493a-afdb-cba22064d3b2"
width="240" />
<img
src="https://github.com/user-attachments/assets/7a4af4ff-7bbb-4c70-b07a-6767c82eba56"
width="240" />

---------

Co-authored-by: Federico Mastrini <[email protected]>
  • Loading branch information
gispada and mastro993 authored Jan 8, 2025
1 parent b490c39 commit 694b118
Show file tree
Hide file tree
Showing 25 changed files with 982 additions and 45 deletions.
Binary file added img/features/itWallet/cards/dc_na.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/features/itWallet/cards/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/features/itWallet/cards/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/features/itWallet/cards/mdl_na.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/features/itWallet/cards/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/features/itWallet/cards/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/features/itWallet/cards/ts_na.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/features/itWallet/cards/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/features/itWallet/cards/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions locales/en/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3283,6 +3283,7 @@ features:
invalid: Non valida
verificationExpiring: Verifica
verificationExpired: Verifica
unknown: Non disponibile
digital: Versione digitale
generic:
error:
Expand Down Expand Up @@ -3538,6 +3539,10 @@ features:
openPdf: "Show document"
shareButton: Save or share
fiscalCode: Your Fiscal Code
statusAttestationUnknown:
title: Non siamo riusciti a caricare {{credentialName}}
content: Chiudi e riapri l'app per riprovare.
primaryAction: Ho capito
trustmark:
cta: Mostra certificato di autenticità
description: Mostra il QR Code per attestare l’autenticità del documento quando ti viene richiesto.
Expand Down
5 changes: 5 additions & 0 deletions locales/it/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3283,6 +3283,7 @@ features:
invalid: Non valida
verificationExpiring: Verifica
verificationExpired: Verifica
unknown: Non disponibile
digital: Versione digitale
generic:
error:
Expand Down Expand Up @@ -3538,6 +3539,10 @@ features:
openPdf: "Mostra documento"
shareButton: Salva o condividi
fiscalCode: Il tuo Codice Fiscale
statusAttestationUnknown:
title: Non siamo riusciti a caricare {{credentialName}}
content: Chiudi e riapri l'app per riprovare.
primaryAction: Ho capito
trustmark:
cta: Mostra certificato di autenticità
description: Mostra il QR Code per attestare l’autenticità del documento quando ti viene richiesto.
Expand Down
18 changes: 18 additions & 0 deletions ts/features/design-system/core/DSCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,12 @@ const ItwCards = () => (
status="jwtExpiring"
/>
</DSComponentViewerBox>
<DSComponentViewerBox name="Unknown Status">
<ItwCredentialCard
credentialType={CredentialType.DRIVING_LICENSE}
status="unknown"
/>
</DSComponentViewerBox>
</VStack>
</DesignSystemSection>
<DesignSystemSection title="Skeumorphic Driving License">
Expand Down Expand Up @@ -409,6 +415,12 @@ const ItwCards = () => (
status="expiring"
/>
</DSComponentViewerBox>
<DSComponentViewerBox name="Unknown Status">
<ItwCredentialCard
credentialType={CredentialType.EUROPEAN_DISABILITY_CARD}
status="unknown"
/>
</DSComponentViewerBox>
</VStack>
</DesignSystemSection>
<DesignSystemSection title="Skeumorphic Disability Card">
Expand Down Expand Up @@ -440,6 +452,12 @@ const ItwCards = () => (
status="expiring"
/>
</DSComponentViewerBox>
<DSComponentViewerBox name="Unknown Status">
<ItwCredentialCard
credentialType={CredentialType.EUROPEAN_HEALTH_INSURANCE_CARD}
status="unknown"
/>
</DSComponentViewerBox>
</VStack>
</DesignSystemSection>
</VStack>
Expand Down
5 changes: 3 additions & 2 deletions ts/features/itwallet/analytics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,15 @@ export type ItwCed = "not_available" | "valid" | "not_valid" | "expiring";
*/
export const ID_STATUS_MAP: Record<
ItwCredentialStatus,
"valid" | "not_valid" | "expiring"
"valid" | "not_valid" | "expiring" | "unknown"
> = {
valid: "valid",
invalid: "not_valid",
expired: "not_valid",
expiring: "expiring",
jwtExpired: "not_valid",
jwtExpiring: "expiring"
jwtExpiring: "expiring",
unknown: "unknown"
};

// #region SCREEN VIEW EVENTS
Expand Down
75 changes: 61 additions & 14 deletions ts/features/itwallet/common/components/ItwCredentialCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { HStack, IOColors, IOText, Tag } from "@pagopa/io-app-design-system";
import React from "react";
import { ImageSourcePropType, StyleSheet, View } from "react-native";
import Color from "color";
import { AnimatedImage } from "../../../../components/AnimatedImage";
import {
borderColorByStatus,
Expand All @@ -11,30 +12,69 @@ import {
import { CredentialType } from "../utils/itwMocksUtils";
import { getThemeColorByCredentialType } from "../utils/itwStyleUtils";
import { ItwCredentialStatus } from "../utils/itwTypesUtils";
import { ItwDigitalVersionBadge } from "./ItwDigitalVersionBadge";
import {
ItwDigitalVersionBadge,
TagColorScheme
} from "./ItwDigitalVersionBadge";

export type ItwCredentialCard = {
credentialType: string;
status?: ItwCredentialStatus;
};

type StyleProps = {
cardBackgroundSource: ImageSourcePropType;
titleColor: string;
titleOpacity: number;
tagColorScheme: TagColorScheme;
};

const getStyleProps = (
credentialType: string,
status: ItwCredentialStatus
): StyleProps => {
const isValid = validCredentialStatuses.includes(status);

const theme = getThemeColorByCredentialType(credentialType);
const [on, off, na] = credentialCardBackgrounds[credentialType];

if (status === "unknown") {
return {
cardBackgroundSource: na,
titleColor: Color(theme.textColor).grayscale().hex(),
titleOpacity: 0.5,
tagColorScheme: "greyscale"
};
}
if (isValid) {
return {
cardBackgroundSource: on,
titleColor: theme.textColor,
titleOpacity: 1,
tagColorScheme: "default"
};
}
return {
cardBackgroundSource: off,
titleColor: theme.textColor,
titleOpacity: 0.5,
tagColorScheme: "faded"
};
};

export const ItwCredentialCard = ({
status = "valid",
credentialType
}: ItwCredentialCard) => {
const isValid = validCredentialStatuses.includes(status);
const theme = getThemeColorByCredentialType(credentialType);
const labelOpacity = isValid ? 1 : 0.5;
const styleProps = getStyleProps(credentialType, status);

const cardBackgroundSource =
credentialCardBackgrounds[credentialType][isValid ? 0 : 1];
const statusTagProps = tagPropsByStatus[status];

return (
<View style={styles.cardContainer}>
<View style={styles.card}>
<AnimatedImage
source={cardBackgroundSource}
source={styleProps.cardBackgroundSource}
style={styles.cardBackground}
/>
</View>
Expand All @@ -48,8 +88,8 @@ export const ItwCredentialCard = ({
maxFontSizeMultiplier={1.25}
style={{
letterSpacing: 0.25,
color: theme.textColor,
opacity: labelOpacity,
color: styleProps.titleColor,
opacity: styleProps.titleOpacity,
flex: 1,
flexShrink: 1
}}
Expand All @@ -61,7 +101,7 @@ export const ItwCredentialCard = ({
</View>
<ItwDigitalVersionBadge
credentialType={credentialType}
isFaded={!isValid}
colorScheme={styleProps.tagColorScheme}
/>
<View
style={[styles.border, { borderColor: borderColorByStatus[status] }]}
Expand All @@ -71,19 +111,26 @@ export const ItwCredentialCard = ({
};

const credentialCardBackgrounds: {
[type: string]: [ImageSourcePropType, ImageSourcePropType];
[type: string]: [
ImageSourcePropType,
ImageSourcePropType,
ImageSourcePropType
];
} = {
[CredentialType.EUROPEAN_DISABILITY_CARD]: [
require("../../../../../img/features/itWallet/cards/dc.png"),
require("../../../../../img/features/itWallet/cards/dc_off.png")
require("../../../../../img/features/itWallet/cards/dc_off.png"),
require("../../../../../img/features/itWallet/cards/dc_na.png")
],
[CredentialType.EUROPEAN_HEALTH_INSURANCE_CARD]: [
require("../../../../../img/features/itWallet/cards/ts.png"),
require("../../../../../img/features/itWallet/cards/ts_off.png")
require("../../../../../img/features/itWallet/cards/ts_off.png"),
require("../../../../../img/features/itWallet/cards/ts_na.png")
],
[CredentialType.DRIVING_LICENSE]: [
require("../../../../../img/features/itWallet/cards/mdl.png"),
require("../../../../../img/features/itWallet/cards/mdl_off.png")
require("../../../../../img/features/itWallet/cards/mdl_off.png"),
require("../../../../../img/features/itWallet/cards/mdl_na.png")
]
};

Expand Down
45 changes: 33 additions & 12 deletions ts/features/itwallet/common/components/ItwDigitalVersionBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,28 @@ import {
IOColors,
makeFontStyleObject
} from "@pagopa/io-app-design-system";
import Color from "color";
import React from "react";
import { Platform, StyleSheet, Text, View } from "react-native";
import I18n from "../../../../i18n";

export type TagColorScheme = "default" | "faded" | "greyscale";

type DigitalVersionBadgeProps = {
credentialType: string;
isFaded?: boolean;
colorScheme: TagColorScheme;
};

type CredentialTypesProps = {
background: string;
foreground: string;
};

const ItwDigitalVersionBadge = ({
credentialType,
isFaded = false
}: DigitalVersionBadgeProps) => {
const mapCredentialTypes: Record<
NonNullable<string>,
CredentialTypesProps
> = {
const getColorPropsByScheme = (
credentialType: string,
colorScheme: TagColorScheme
) => {
const mapCredentialTypes: Record<string, CredentialTypesProps> = {
MDL: {
foreground: "#5E303E",
background: "#FADCF5"
Expand All @@ -41,9 +41,30 @@ const ItwDigitalVersionBadge = ({
}
};

const colorProps = mapCredentialTypes[credentialType];
const baseColorProps = mapCredentialTypes[credentialType];

if (!baseColorProps) {
return;
}

if (colorScheme === "greyscale") {
return {
foreground: Color(baseColorProps.foreground).grayscale().hex(),
background: Color(baseColorProps.background).grayscale().hex()
};
}

return baseColorProps;
};

const ItwDigitalVersionBadge = ({
credentialType,
colorScheme = "default"
}: DigitalVersionBadgeProps) => {
const colorProps = getColorPropsByScheme(credentialType, colorScheme);

// If a credential does not have the color configuration means that we should not display the badge
if (!colorProps) {
// If a credential does not have the color configuration means that we should not display the badge
return null;
}

Expand All @@ -52,7 +73,7 @@ const ItwDigitalVersionBadge = ({
return (
<View style={styles.wrapper}>
<View style={[styles.badge, { backgroundColor: background }]}>
{isFaded && <View style={styles.faded} />}
{colorScheme !== "default" && <View style={styles.faded} />}
<Text
numberOfLines={1}
ellipsizeMode="tail"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ describe("ItwCredentialCard", () => {
"valid",
"expired",
"expiring",
"pending"
"pending",
"unknown"
] as ReadonlyArray<ItwCredentialStatus>)(
"should match snapshot when status is %p",
status => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
import { render } from "@testing-library/react-native";
import React from "react";
import { ItwDigitalVersionBadge } from "../ItwDigitalVersionBadge";
import {
ItwDigitalVersionBadge,
TagColorScheme
} from "../ItwDigitalVersionBadge";

describe("ItwDigitalVersionBadge", () => {
it.each([
"MDL",
"EuropeanDisabilityCard",
"EuropeanHealthInsuranceCard",
"InvalidCredentialType"
])("should render correctly %s", credentialType => {
const component = render(
<ItwDigitalVersionBadge credentialType={credentialType} />
).toJSON();
expect(component).toMatchSnapshot();
});
["MDL", "default"],
["MDL", "faded"],
["MDL", "greyscale"],
["EuropeanDisabilityCard", "default"],
["EuropeanDisabilityCard", "faded"],
["EuropeanDisabilityCard", "greyscale"],
["EuropeanHealthInsuranceCard", "default"],
["EuropeanHealthInsuranceCard", "faded"],
["EuropeanHealthInsuranceCard", "greyscale"],
["InvalidCredentialType", "default"]
])(
"should render correctly %s in state %s",
(credentialType, colorScheme) => {
const component = render(
<ItwDigitalVersionBadge
credentialType={credentialType}
colorScheme={colorScheme as TagColorScheme}
/>
).toJSON();
expect(component).toMatchSnapshot();
}
);
});
Loading

0 comments on commit 694b118

Please sign in to comment.