Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Family members scan summary #319

Merged
merged 9 commits into from
Dec 4, 2023
Binary file added src/_assets/images/check.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 src/_assets/images/[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 src/_assets/images/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/_assets/images/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-var-requires */
export * from './avatars';
export const Check = require('./check.png');
export const Counter = require('./counter.png');
export const Error = require('./error.png');
export const Family = require('./family.png');
Expand Down
12 changes: 9 additions & 3 deletions src/_components/diagonalSplitView/DiagonalSplitView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FC } from 'react';
import { ScrollView, useWindowDimensions, View } from 'react-native';
import { ScrollView, StyleProp, useWindowDimensions, View, ViewStyle } from 'react-native';

import { ThemeColor } from '../../_styles/theme';
import { getColor } from '../../_utils';
Expand All @@ -9,15 +9,19 @@ import * as Styled from './style';
type TProps = {
backgroundColor?: ThemeColor;
bottomContent?: React.ReactNode;
bottomContentStyle?: StyleProp<ViewStyle>;
diagonalContainerHeight?: number;
isScrollable?: boolean;
lineColor?: ThemeColor;
topContent: React.ReactNode;
topContentStyle?: StyleProp<ViewStyle>;
};

const DiagonalSplitView: FC<TProps> = ({
topContent,
bottomContent,
topContentStyle,
bottomContentStyle,
backgroundColor = 'secondary.600',
lineColor = 'secondary.700',
isScrollable,
Expand All @@ -29,7 +33,9 @@ const DiagonalSplitView: FC<TProps> = ({
<>
<Styled.TopSafeAreaViewContainer backgroundColor={backgroundColor} edges={['top']} isScrollable={false} />
<Styled.ViewContainer backgroundColor={backgroundColor} edges={['bottom']} isScrollable={false}>
<Styled.TopContainer backgroundColor={backgroundColor}>{topContent}</Styled.TopContainer>
<Styled.TopContainer backgroundColor={backgroundColor} style={topContentStyle}>
{topContent}
</Styled.TopContainer>

<Styled.DiagonalContainer diagonalContainerHeight={diagonalContainerHeight} lineColor={lineColor}>
<Styled.Triangle
Expand All @@ -40,7 +46,7 @@ const DiagonalSplitView: FC<TProps> = ({
<Styled.TriangleDark diagonalContainerHeight={diagonalContainerHeight} screenWidth={width} />
</Styled.DiagonalContainer>

<Styled.BottomContainer as={isScrollable ? ScrollView : View}>
<Styled.BottomContainer as={isScrollable ? ScrollView : View} style={bottomContentStyle}>
{isScrollable ? <Styled.BottomContainerContent>{bottomContent}</Styled.BottomContainerContent> : bottomContent}
</Styled.BottomContainer>
</Styled.ViewContainer>
Expand Down
42 changes: 0 additions & 42 deletions src/_components/family/familyMemberPoints/FamilyMemberPoints.tsx

This file was deleted.

51 changes: 51 additions & 0 deletions src/_components/family/familyMembersPoints/FamilyMembersPoints.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ReactElement } from 'react';
import { useTranslation } from 'react-i18next';
import { FlatList, StyleProp, View, ViewStyle } from 'react-native';

import { getAvatarByNameOrDefault } from '../../../_utils';
import { TFamilyMember } from '../../../profile/_models';
import Typography from '../../typography/Typography';
import * as Styled from './style';

type TProps = {
RightComponent?: ({ member }: { member: TFamilyMember }) => ReactElement;
Subtitle?: ({ member }: { member: TFamilyMember }) => ReactElement;
members: TFamilyMember[];
style?: StyleProp<ViewStyle>;
};

export const FamilyMembersPoints = ({ members, RightComponent, Subtitle, style }: TProps) => {
const { t } = useTranslation();

return (
<FlatList
ItemSeparatorComponent={Styled.Divider}
contentContainerStyle={style}
data={members}
keyExtractor={item => item.uitpasNumber}
renderItem={({ item: member }) => (
<Styled.Item>
<Styled.Avatar resizeMode="contain" source={getAvatarByNameOrDefault(member.icon)} />
<Styled.ItemBody>
<Typography fontStyle="bold" numberOfLines={1}>
{member.passholder.firstName}
{member.mainFamilyMember ? ` ${t('SHOP_DETAIL.WHO_CAN_REDEEM.YOU')}` : ''}
</Typography>
{Subtitle ? (
<Subtitle member={member} />
) : (
<Typography color="primary.700" fontStyle="semibold" numberOfLines={1} size="small">
{t('SHOP_DETAIL.WHO_CAN_REDEEM.POINTS', { count: member.passholder.points })}
</Typography>
)}
</Styled.ItemBody>
{RightComponent && (
<View>
<RightComponent member={member} />
</View>
)}
</Styled.Item>
)}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export const Avatar = styled.Image`

export const ItemBody = styled.View`
flex: 1;
height: 44px;
justify-content: space-around;
margin-left: 8px;
margin-right: 12px;
Expand Down
1 change: 1 addition & 0 deletions src/_components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { default as DatePicker } from './datePicker/DatePicker';
export { default as DiagonalSplitView } from './diagonalSplitView/DiagonalSplitView';
export { default as EnlargedHeader } from './enlargedHeader/EnlargedHeader';
export { default as ExternalLink } from './externalLink/ExternalLink';
export * from './family/familyMembersPoints/FamilyMembersPoints';
export { default as FakeTextInput } from './textInput/fakeTextInput/FakeTextInput';
export { default as FocusAwareStatusBar } from './statusBar/FocusAwareStatusBar';
export { default as HtmlRenderer } from './htmlRenderer/HtmlRenderer';
Expand Down
8 changes: 7 additions & 1 deletion src/_routing/_components/TRootStackParamList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { TFamilyMember } from '../../profile/_models';
import { TRedeemedReward } from '../../redeemedRewards/_models/redeemedReward';
import { TCheckInResponse } from '../../scan/_models';
import { TFamilyScanResponse } from '../../scan/familyCheckinSummary/_models';
import { TFilterRewardCategory, TFilterRewardSection } from '../../shop/_hooks/useRewardFilters';
import { TReward, TRewardType } from '../../shop/_models/reward';
import { TSearchFilters } from '../../shop/_models/searchFilters';
Expand Down Expand Up @@ -39,7 +40,12 @@ export type TRootStackParamList = {
};
FamiliesOverview: undefined;
FamilyCheckin: { checkinCode: string };
FamilyCheckinSummary: { familyMemberResponses: { member: TFamilyMember; response: TCheckInResponse }[] };
FamilyCheckinSummary: {
memberResponses: {
member: TFamilyMember;
response: TFamilyScanResponse;
}[];
};
FamilyInformation: undefined;
FamilyOnboarding: undefined;
FamilyOverview: undefined;
Expand Down
1 change: 1 addition & 0 deletions src/_translations/locales/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@
"TITLE": "Met wie ben je vandaag op stap?",
"CHECKIN": "Spaar!",
"SUMMARY": {
"DESCRIPTION": "Je hebt gespaard voor",
"CLOSE": "Sluiten"
}
}
Expand Down
10 changes: 6 additions & 4 deletions src/scan/familyCheckin/FamilyCheckin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useGetFamilyMembers } from '../../onboarding/family/_queries';
import { TFamilyMember } from '../../profile/_models';
import { TCheckInResponse } from '../_models';
import { useCheckin } from '../_queries/useCheckin';
import { TFamilyScanResponse } from '../familyCheckinSummary/_models';
import * as Styled from './style';

type TProps = {
Expand Down Expand Up @@ -42,8 +43,8 @@ const FamilyCheckin: FC = ({ navigation, route }: TProps) => {
return checkin({ body: { checkinCode }, path: `/passholders/${member.passholder.id}/checkins` });
});
const responses = await Promise.allSettled(promises);
const familyMemberResponses = mapFamilyMembersToResponses(checkedFamilyMembers, responses);
navigation.navigate('FamilyCheckinSummary', { familyMemberResponses });
const memberResponses = mapFamilyMembersToResponses(checkedFamilyMembers, responses);
navigation.navigate('FamilyCheckinSummary', { memberResponses });
};

return (
Expand Down Expand Up @@ -85,12 +86,13 @@ const FamilyCheckin: FC = ({ navigation, route }: TProps) => {
const mapFamilyMembersToResponses = (
members: TFamilyMember[],
responses: PromiseSettledResult<TCheckInResponse>[],
): { member: TFamilyMember; response: TCheckInResponse }[] => {
): { member: TFamilyMember; response: TFamilyScanResponse }[] => {
return members.map((member, index) => {
const response = responses[index];
return {
member,
response: response.status === 'fulfilled' ? response.value : response.reason,
response:
response.status === 'fulfilled' ? { type: 'success', value: response.value } : { error: response.reason, type: 'error' },
};
});
};
Expand Down
81 changes: 72 additions & 9 deletions src/scan/familyCheckinSummary/FamilyCheckinSummary.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,86 @@
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

import { SafeAreaView } from '../../_components';
import { TRootStackNavigationProp } from '../../_routing';
import { Check } from '../../_assets/images';
import { DiagonalSplitView, Typography } from '../../_components';
import { TRootStackNavigationProp, TRootStackRouteProp } from '../../_routing';
import { TFamilyMember } from '../../profile/_models';
import { ErrorIcon, PointsCollectedIcon } from './icons';
import * as Styled from './style';

type TProps = {
navigation: TRootStackNavigationProp<'FamilyCheckinSummary'>;
route: TRootStackRouteProp<'FamilyCheckinSummary'>;
};
export const FamilyCheckinSummary = ({ navigation, route }: TProps) => {
const { memberResponses } = route.params;

export const FamilyCheckinSummary = ({ navigation }: TProps) => {
const { t } = useTranslation();
const { top } = useSafeAreaInsets();

const FamilyMembersSummary = useCallback(() => {
const getResponseByMember = (member: TFamilyMember) => {
const index = memberResponses.findIndex(
({
member: {
passholder: { id },
},
}) => member.passholder.id === id,
);
return memberResponses[index].response;
};

const renderIcon = ({ member }: { member: TFamilyMember }) => {
const response = getResponseByMember(member);
if (response.type === 'success') {
return <PointsCollectedIcon count={response.value.addedPoints} />;
}
return <ErrorIcon />;
};

const renderErrorDescription = ({ member }: { member: TFamilyMember }) => {
const response = getResponseByMember(member);
if (response.type === 'error') {
return <Typography size="small">{response.error.endUserMessage.nl}</Typography>;
}
return (
<Typography color="primary.700" fontStyle="semibold" numberOfLines={1} size="small">
{t('SHOP_DETAIL.WHO_CAN_REDEEM.POINTS', { count: member.passholder.points })}
</Typography>
);
};

return (
<Styled.FamilyMembersPoints
RightComponent={renderIcon}
Subtitle={renderErrorDescription}
members={memberResponses.map(({ member }) => member)}
/>
);
}, [memberResponses, t]);

return (
<SafeAreaView backgroundColor="neutral.0" edges={['bottom']} isScrollable={false}>
<Styled.Body />
<Styled.CloseButton
label={t('SCAN.FAMILY_MEMBERS.SUMMARY.CLOSE')}
onPress={() => navigation.reset({ index: 0, routes: [{ name: 'MainNavigator', params: { screen: 'Profile' } }] })}
<>
<DiagonalSplitView
bottomContent={
<Styled.Body>
<FamilyMembersSummary />
<Styled.CloseButton
label={t('SCAN.FAMILY_MEMBERS.SUMMARY.CLOSE')}
onPress={() => navigation.reset({ index: 0, routes: [{ name: 'MainNavigator', params: { screen: 'Profile' } }] })}
/>
</Styled.Body>
}
bottomContentStyle={{ paddingLeft: 0, paddingRight: 0 }}
topContent={null}
/>
</SafeAreaView>
<Styled.Header style={{ top: top + 16 }}>
<Styled.HeaderImage source={Check} />
<Styled.HeaderTitle color="primary.700" fontStyle="bold" size="large">
{t('SCAN.FAMILY_MEMBERS.SUMMARY.DESCRIPTION')}
</Styled.HeaderTitle>
</Styled.Header>
</>
);
};
4 changes: 4 additions & 0 deletions src/scan/familyCheckinSummary/_models/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { TApiError } from '../../../_http';
import { TCheckInResponse } from '../../_models';

export type TFamilyScanResponse = { type: 'success'; value: TCheckInResponse } | { error: TApiError; type: 'error' };
11 changes: 11 additions & 0 deletions src/scan/familyCheckinSummary/icons/ErrorIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as Styled from './style';

export const ErrorIcon = () => {
return (
<Styled.Container type="error">
<Styled.Label color="error.500" fontStyle="bold" size="large">
!
</Styled.Label>
</Styled.Container>
);
};
15 changes: 15 additions & 0 deletions src/scan/familyCheckinSummary/icons/PointsCollectedIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as Styled from './style';

type TProps = {
count: number;
};

export const PointsCollectedIcon = ({ count }: TProps) => {
chennara marked this conversation as resolved.
Show resolved Hide resolved
return (
<Styled.Container type="success">
<Styled.Label color="primary.700" fontStyle="bold" size="small">
+{count}
</Styled.Label>
</Styled.Container>
);
};
2 changes: 2 additions & 0 deletions src/scan/familyCheckinSummary/icons/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './ErrorIcon';
export * from './PointsCollectedIcon';
16 changes: 16 additions & 0 deletions src/scan/familyCheckinSummary/icons/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import styled from 'styled-components/native';

import { Typography } from '../../../_components';

export const Container = styled.View<{ type: 'success' | 'error' }>`
width: 32px;
height: 32px;
border-radius: 16px;
border: ${({ type, theme }) => `4px solid ${type === 'success' ? theme.palette.primary['200'] : theme.palette.error['200']}`};
justify-content: center;
align-items: center;
`;

export const Label = styled(Typography)`
line-height: 24px; // $height - $borderWidth
chennara marked this conversation as resolved.
Show resolved Hide resolved
`;
Loading