Skip to content

Commit

Permalink
chore: [IOPAE-1142] Added featured carousels in ServicesHomeScreen (#…
Browse files Browse the repository at this point in the history
…5793)

## Short description
This PR refactors the `ServicesHomeScreen` screen by adding the featured
services and the featured institutions carousels.

<details open><summary>Details</summary>
<p>

| New services tab  |
| - |
| <video
src="https://github.com/pagopa/io-app/assets/29163287/6602ddc9-da5a-49d1-b61c-c21dbd7cc642"
width="300"/> |

</p>
</details> 

## List of changes proposed in this pull request
- Added `CardPressableBase` component
- Updated carousels by adding the `CardPressableBase` component
- Updated `ServicesHomeScreen` screen to display the national
institutions and featured carousels
- Updated locales

## How to test
Using `io-dev-api-server`, navigate to the services tab. Check that the
screen is displayed correctly
  • Loading branch information
adelloste committed May 23, 2024
1 parent 3bfb1d2 commit ca2de78
Show file tree
Hide file tree
Showing 17 changed files with 2,610 additions and 303 deletions.
5 changes: 5 additions & 0 deletions locales/en/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2104,6 +2104,11 @@ services:
emptyListMessage: There are no services available at this time, pull down to refresh
new: New
home:
featured:
services:
title: Featured
institutions:
title: Featured Institutions
institutions:
title: National
institution:
Expand Down
5 changes: 5 additions & 0 deletions locales/it/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2104,6 +2104,11 @@ services:
emptyListMessage: Non ci sono servizi disponibili al momento, trascina in basso per aggiornare
new: Nuovo
home:
featured:
services:
title: In primo piano
institutions:
title: Enti in evidenza
institutions:
title: Nazionali
institution:
Expand Down
35 changes: 35 additions & 0 deletions ts/features/services/common/components/CardPressableBase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";
import { Pressable } from "react-native";
import Animated from "react-native-reanimated";
import { PressableBaseProps, WithTestID } from "@pagopa/io-app-design-system";
import { useSpringPressScaleAnimation } from "../../../../components/ui/utils/hooks/useSpringPressScaleAnimation";

type CardPressableBaseProps = WithTestID<PressableBaseProps>;

export const CardPressableBase = ({
onPress,
testID,
accessibilityLabel,
children
}: React.PropsWithChildren<CardPressableBaseProps>) => {
const { onPressIn, onPressOut, animatedScaleStyle } =
useSpringPressScaleAnimation();

if (onPress === undefined) {
return <>{children}</>;
}

return (
<Pressable
onPress={onPress}
testID={testID}
accessible={true}
accessibilityLabel={accessibilityLabel}
onPressIn={onPressIn}
onPressOut={onPressOut}
onTouchEnd={onPressOut}
>
<Animated.View style={animatedScaleStyle}>{children}</Animated.View>
</Pressable>
);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";
import { View } from "react-native";
import {
Divider,
IOListItemStyles,
IOListItemVisualParams,
IOStyles,
Expand All @@ -25,3 +26,20 @@ export const InstitutionListItemSkeleton = () => (
</View>
</View>
);

type InstitutionListSkeletonProps = {
size?: number;
};

export const InstitutionListSkeleton = ({
size = 3
}: InstitutionListSkeletonProps) => (
<View testID="institutions-skeleton">
{Array.from({ length: size }).map((_, index) => (
<React.Fragment key={index}>
<InstitutionListItemSkeleton />
{index < size - 1 ? <Divider /> : undefined}
</React.Fragment>
))}
</View>
);
53 changes: 30 additions & 23 deletions ts/features/services/home/components/FeaturedInstitutionCard.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import React from "react";
import { Dimensions, StyleSheet, View } from "react-native";
import Placeholder from "rn-placeholder";
import {
Avatar,
H6,
Expand All @@ -7,18 +10,16 @@ import {
VSpacer
} from "@pagopa/io-app-design-system";
import { OrganizationFiscalCode } from "@pagopa/ts-commons/lib/strings";
import React from "react";
import { Dimensions, StyleSheet, View } from "react-native";
import Placeholder from "rn-placeholder";
import { WithTestID } from "../../../../types/WithTestID";
import { logoForInstitution } from "../utils";
import { CardPressableBase } from "../../common/components/CardPressableBase";

export type FeaturedInstitutionCardProps = WithTestID<{
id: string;
name: string;
accessibilityLabel?: string;
isNew?: boolean;
onPress?: (id: string) => void;
onPress?: () => void;
}>;

export const CARD_WIDTH =
Expand Down Expand Up @@ -59,28 +60,34 @@ const styles = StyleSheet.create({
});

const FeaturedInstitutionCard = (props: FeaturedInstitutionCardProps) => (
<View
style={[styles.cardContainer, props.isNew && styles.cardContainerNew]}
testID={props.testID}
<CardPressableBase
onPress={props.onPress}
testID={`${props.testID}-pressable`}
accessibilityLabel={props.accessibilityLabel}
>
<View style={styles.cardContent}>
<View style={styles.cardAvatar}>
<Avatar
logoUri={logoForInstitution({
id: "",
name: "",
fiscal_code: props.id as OrganizationFiscalCode
})}
size="small"
/>
</View>
<View style={styles.cardLabel}>
<H6 lineBreakMode="head" numberOfLines={2} color="hanPurple-850">
{props.name}
</H6>
<View
style={[styles.cardContainer, props.isNew && styles.cardContainerNew]}
testID={props.testID}
>
<View style={styles.cardContent}>
<View style={styles.cardAvatar}>
<Avatar
logoUri={logoForInstitution({
id: "",
name: "",
fiscal_code: props.id as OrganizationFiscalCode
})}
size="small"
/>
</View>
<View style={styles.cardLabel}>
<H6 lineBreakMode="head" numberOfLines={2} color="hanPurple-850">
{props.name}
</H6>
</View>
</View>
</View>
</View>
</CardPressableBase>
);

const FeaturedInstitutionCardSkeleton = ({ testID }: WithTestID<unknown>) => (
Expand Down
76 changes: 76 additions & 0 deletions ts/features/services/home/components/FeaturedInstitutionList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { useCallback, useMemo } from "react";
import { ListItemHeader, VSpacer } from "@pagopa/io-app-design-system";
import { Institution } from "../../../../../definitions/services/Institution";
import I18n from "../../../../i18n";
import { useIONavigation } from "../../../../navigation/params/AppParamsList";
import { useIODispatch, useIOSelector } from "../../../../store/hooks";
import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender";
import { SERVICES_ROUTES } from "../../common/navigation/routes";
import { featuredInstitutionsGet } from "../store/actions";
import {
featuredInstitutionsSelector,
isErrorFeaturedInstitutionsSelector,
isLoadingFeaturedInstitutionsSelector
} from "../store/reducers";
import {
FeaturedInstitutionsCarousel,
FeaturedInstitutionsCarouselSkeleton
} from "./FeaturedInstitutionsCarousel";

export const FeaturedInstitutionList = () => {
const dispatch = useIODispatch();
const navigation = useIONavigation();

const featuredInstitutions = useIOSelector(featuredInstitutionsSelector);
const isError = useIOSelector(isErrorFeaturedInstitutionsSelector);
const isLoading = useIOSelector(isLoadingFeaturedInstitutionsSelector);

useOnFirstRender(() => dispatch(featuredInstitutionsGet.request()));

const handlePress = useCallback(
({ fiscal_code, name }: Institution) => {
navigation.navigate(SERVICES_ROUTES.SERVICES_NAVIGATOR, {
screen: SERVICES_ROUTES.INSTITUTION_SERVICES,
params: {
institutionId: fiscal_code,
institutionName: name
}
});
},
[navigation]
);

const mappedFeaturedInstitutions = useMemo(
() =>
featuredInstitutions.map(props => ({
...props,
onPress: () => handlePress(props)
})),
[featuredInstitutions, handlePress]
);

const isVisible = useMemo(
() => isLoading || mappedFeaturedInstitutions.length > 0,
[isLoading, mappedFeaturedInstitutions]
);

if (isError || !isVisible) {
return null;
}

return (
<>
<ListItemHeader
label={I18n.t("services.home.featured.institutions.title")}
/>
{isLoading ? (
<FeaturedInstitutionsCarouselSkeleton />
) : (
<FeaturedInstitutionsCarousel
institutions={mappedFeaturedInstitutions}
/>
)}
<VSpacer size={24} />
</>
);
};
66 changes: 38 additions & 28 deletions ts/features/services/home/components/FeaturedServiceCard.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import React from "react";
import { StyleSheet, View } from "react-native";
import Placeholder from "rn-placeholder";
import {
Avatar,
Badge,
H4,
IOColors,
IOSpacingScale,
IOVisualCostants,
TestID,
VSpacer
} from "@pagopa/io-app-design-system";
import React from "react";
import { StyleSheet, View } from "react-native";
import Placeholder from "rn-placeholder";
import I18n from "../../../../i18n";
import { WithTestID } from "../../../../types/WithTestID";
import { CardPressableBase } from "../../common/components/CardPressableBase";
import { logoForService } from "../utils";
import OrganizationNameLabel from "./OrganizationNameLabel";

Expand Down Expand Up @@ -61,35 +63,43 @@ const styles = StyleSheet.create({
});

const FeaturedServiceCard = (props: FeaturedServiceCardProps) => (
<View
style={[styles.cardContainer, props.isNew && styles.cardContainerNew]}
testID={props.testID}
<CardPressableBase
onPress={props.onPress}
testID={`${props.testID}-pressable`}
accessibilityLabel={props.accessibilityLabel}
>
<View style={styles.cardHeader}>
<Avatar logoUri={logoForService(props.id, "")} size="small" />
{props.isNew && <Badge variant="purple" text={I18n.t("services.new")} />}
</View>
<View style={styles.cardTitle}>
<H4
lineBreakMode="head"
numberOfLines={3}
color={props.isNew ? "hanPurple-850" : "blueIO-850"}
>
{props.name}
</H4>
{props.organizationName && (
<>
<VSpacer size={4} />
<OrganizationNameLabel>
{props.organizationName}
</OrganizationNameLabel>
</>
)}
<View
style={[styles.cardContainer, props.isNew && styles.cardContainerNew]}
testID={props.testID}
>
<View style={styles.cardHeader}>
<Avatar logoUri={logoForService(props.id, "")} size="small" />
{props.isNew && (
<Badge variant="purple" text={I18n.t("services.new")} />
)}
</View>
<View style={styles.cardTitle}>
<H4
lineBreakMode="head"
numberOfLines={3}
color={props.isNew ? "hanPurple-850" : "blueIO-850"}
>
{props.name}
</H4>
{props.organizationName && (
<>
<VSpacer size={4} />
<OrganizationNameLabel>
{props.organizationName}
</OrganizationNameLabel>
</>
)}
</View>
</View>
</View>
</CardPressableBase>
);

const FeaturedServiceCardSkeleton = ({ testID }: WithTestID<unknown>) => (
const FeaturedServiceCardSkeleton = ({ testID }: TestID) => (
<View style={styles.cardContainer} testID={`${testID}-skeleton`}>
<View style={styles.cardHeader}>
<Placeholder.Box
Expand Down

0 comments on commit ca2de78

Please sign in to comment.