diff --git a/apps/native/app/src/messages/en.ts b/apps/native/app/src/messages/en.ts index 734168b2c9bb..17260fc7d745 100644 --- a/apps/native/app/src/messages/en.ts +++ b/apps/native/app/src/messages/en.ts @@ -594,6 +594,9 @@ export const en: TranslatedMessages = { 'problem.offline.title': 'No internet connection', 'problem.offline.message': 'An error occurred while communicating with the service provider', + 'problem.thirdParty.title': 'No connection', + 'problem.thirdParty.message': + 'An error occurred while communicating with the service provider', // passkeys 'passkeys.headingTitle': 'Sign in with Island.is app', diff --git a/apps/native/app/src/messages/is.ts b/apps/native/app/src/messages/is.ts index 0135ba4a659e..bd6967d93b48 100644 --- a/apps/native/app/src/messages/is.ts +++ b/apps/native/app/src/messages/is.ts @@ -595,6 +595,8 @@ export const is = { 'Ef þú telur þig eiga gögn sem ættu að birtast hér, vinsamlegast hafðu samband við þjónustuaðila.', 'problem.offline.title': 'Samband næst ekki', 'problem.offline.message': 'Villa kom upp í samskiptum við þjónustuaðila', + 'problem.thirdParty.title': 'Samband næst ekki', + 'problem.thirdParty.message': 'Villa kom upp í samskiptum við þjónustuaðila', // passkeys 'passkeys.headingTitle': 'Innskrá með Ísland.is appinu', diff --git a/apps/native/app/src/screens/health/health-overview.tsx b/apps/native/app/src/screens/health/health-overview.tsx index 25fce0d06d70..2c6778c7fb34 100644 --- a/apps/native/app/src/screens/health/health-overview.tsx +++ b/apps/native/app/src/screens/health/health-overview.tsx @@ -1,4 +1,12 @@ -import { Alert, Button, Heading, Input, InputRow, Typography } from '@ui' +import { + Alert, + Button, + Heading, + Input, + InputRow, + Problem, + Typography, +} from '@ui' import React, { useCallback, useMemo, useState } from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { @@ -12,6 +20,7 @@ import { } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import styled, { useTheme } from 'styled-components/native' +import { ApolloError } from '@apollo/client' import { useGetHealthCenterQuery, @@ -77,6 +86,10 @@ const HeadingSection: React.FC = ({ title, onPress }) => { ) } +const showErrorComponent = (error: ApolloError) => { + return +} + const { getNavigationOptions, useNavigationOptions } = createNavigationOptionHooks((theme, intl) => ({ topBar: { @@ -247,49 +260,57 @@ export const HealthOverviewScreen: NavigationFunctionComponent = ({ ) } /> - - - - - - + {(healthCenterRes.data || healthCenterRes.loading) && ( + <> + + + + + + + + )} + {healthCenterRes.error && + !healthCenterRes.data && + showErrorComponent(healthCenterRes.error)} openBrowser(`${origin}/minarsidur/heilsa/yfirlit`, componentId) } /> - {healthInsuranceData?.isInsured || healthInsuranceRes.loading ? ( + {(healthInsuranceRes.data && healthInsuranceData?.isInsured) || + healthInsuranceRes.loading ? ( ) : ( - + !healthInsuranceRes.error && + healthInsuranceRes.data && ( + + ) )} + {healthInsuranceRes.error && + !healthInsuranceRes.data && + showErrorComponent(healthInsuranceRes.error)} - - - - - - - - - - + {(paymentOverviewRes.loading || paymentOverviewRes.data) && ( + <> + + + + + + + + + + + + )} + {paymentOverviewRes.error && + !paymentOverviewRes.data && + paymentStatusRes.error && + !paymentStatusRes.data && + showErrorComponent(paymentOverviewRes.error)} - - - - - - + {(medicinePurchaseRes.loading || medicinePurchaseRes.data) && ( + <> + + + + + + + + )} + {medicinePurchaseRes.error && + !medicinePurchaseRes.data && + showErrorComponent(medicinePurchaseRes.error)} diff --git a/apps/native/app/src/stores/organizations-store.ts b/apps/native/app/src/stores/organizations-store.ts index 2d3580092443..3b9cb837e73a 100644 --- a/apps/native/app/src/stores/organizations-store.ts +++ b/apps/native/app/src/stores/organizations-store.ts @@ -30,6 +30,7 @@ interface Organization { interface OrganizationsStore extends State { organizations: Organization[] getOrganizationLogoUrl(forName: string, size?: number): ImageSourcePropType + getOrganizationNameBySlug(slug: string): string actions: any } @@ -72,6 +73,10 @@ export const organizationsStore = create( const uri = `${url}?w=${size}&h=${size}&fit=pad&fm=png` return { uri } }, + getOrganizationNameBySlug(slug: string) { + const org = get().organizations.find((o) => o.slug === slug) + return org?.title ?? '' + }, actions: { updateOriganizations: async () => { const client = await getApolloClientAsync() diff --git a/apps/native/app/src/ui/lib/problem/problem-template.tsx b/apps/native/app/src/ui/lib/problem/problem-template.tsx index b15358fbdc05..1ebe261c581f 100644 --- a/apps/native/app/src/ui/lib/problem/problem-template.tsx +++ b/apps/native/app/src/ui/lib/problem/problem-template.tsx @@ -10,6 +10,7 @@ export type ProblemTemplateBaseProps = { title: string message: string | ReactNode withContainer?: boolean + size?: 'small' | 'large' } interface WithIconProps extends ProblemTemplateBaseProps { @@ -68,6 +69,7 @@ const getColorsByVariant = ( const Host = styled.View<{ borderColor: Colors noContainer?: boolean + size: 'small' | 'large' }>` border-color: ${({ borderColor, theme }) => theme.color[borderColor]}; border-width: 1px; @@ -76,11 +78,12 @@ const Host = styled.View<{ justify-content: center; align-items: center; flex: 1; - row-gap: ${({ theme }) => theme.spacing[3]}px; + row-gap: ${({ theme, size }) => + size === 'small' ? theme.spacing[2] : theme.spacing[3]}px; padding: ${({ theme }) => theme.spacing[2]}px; ${({ noContainer, theme }) => noContainer && `margin: ${theme.spacing[2]}px;`} - min-height: 280px; + min-height: ${({ size }) => (size === 'large' ? '280' : '142')}px; ` const Tag = styled(View)<{ @@ -114,12 +117,13 @@ export const ProblemTemplate = ({ showIcon, tag, withContainer, + size = 'large', }: ProblemTemplateProps) => { const { borderColor, tagColor, tagBackgroundColor } = getColorsByVariant(variant) return ( - + {tag && ( @@ -129,10 +133,18 @@ export const ProblemTemplate = ({ )} {showIcon && } - + {title} - {message} + + {message} + ) diff --git a/apps/native/app/src/ui/lib/problem/problem.tsx b/apps/native/app/src/ui/lib/problem/problem.tsx index 40963f69836c..4fa3d4e984d1 100644 --- a/apps/native/app/src/ui/lib/problem/problem.tsx +++ b/apps/native/app/src/ui/lib/problem/problem.tsx @@ -1,7 +1,10 @@ import { useEffect } from 'react' + import { useTranslate } from '../../../hooks/use-translate' import { useOfflineStore } from '../../../stores/offline-store' import { ProblemTemplate, ProblemTemplateBaseProps } from './problem-template' +import { getOrganizationSlugFromError } from '../../../utils/get-organization-slug-from-error' +import { ThirdPartyServiceError } from './third-party-service-error' enum ProblemTypes { error = 'error', @@ -20,7 +23,7 @@ type ProblemBaseProps = { title?: string message?: string logError?: boolean -} & Pick +} & Pick interface ErrorProps extends ProblemBaseProps { type?: 'error' @@ -61,6 +64,7 @@ export const Problem = ({ logError = false, withContainer, showIcon, + size = 'large', }: ProblemProps) => { const t = useTranslate() const { isConnected } = useOfflineStore() @@ -73,6 +77,7 @@ export const Problem = ({ message: message ?? t('problem.error.message'), tag: tag ?? t('problem.error.tag'), variant: 'error', + size: size ?? 'large', } as const useEffect(() => { @@ -90,6 +95,7 @@ export const Problem = ({ variant="warning" title={title ?? t('problem.offline.title')} message={message ?? t('problem.offline.message')} + size={size} /> ) } @@ -99,6 +105,18 @@ export const Problem = ({ switch (type) { case ProblemTypes.error: + if (error) { + const organizationSlug = getOrganizationSlugFromError(error) + + if (organizationSlug) { + return ( + + ) + } + } return case ProblemTypes.noData: @@ -109,6 +127,7 @@ export const Problem = ({ variant="info" title={title ?? t('problem.noData.title')} message={message ?? t('problem.noData.message')} + size={size} /> ) diff --git a/apps/native/app/src/ui/lib/problem/third-party-service-error.tsx b/apps/native/app/src/ui/lib/problem/third-party-service-error.tsx new file mode 100644 index 000000000000..d2a714c18694 --- /dev/null +++ b/apps/native/app/src/ui/lib/problem/third-party-service-error.tsx @@ -0,0 +1,30 @@ +import React from 'react' +import { useIntl } from 'react-intl' + +import { ProblemTemplate } from './problem-template' +import { useOrganizationsStore } from '../../../stores/organizations-store' + +type ThirdPartyServiceErrorProps = { + organizationSlug: string + size: 'small' | 'large' +} + +export const ThirdPartyServiceError = ({ + organizationSlug, + size, +}: ThirdPartyServiceErrorProps) => { + const intl = useIntl() + + const { getOrganizationNameBySlug } = useOrganizationsStore() + const organizationName = getOrganizationNameBySlug(organizationSlug) + + return ( + + ) +} diff --git a/apps/native/app/src/utils/get-organization-slug-from-error.ts b/apps/native/app/src/utils/get-organization-slug-from-error.ts new file mode 100644 index 000000000000..1463c87cb8db --- /dev/null +++ b/apps/native/app/src/utils/get-organization-slug-from-error.ts @@ -0,0 +1,36 @@ +import { ApolloError } from '@apollo/client' + +type PartialProblem = { + organizationSlug?: string +} + +type CustomExtension = { + code: string + problem?: PartialProblem + exception?: { + problem?: PartialProblem + } +} + +/** + * Extracts the organization slug from the Apollo error, if it exists. + */ +export const getOrganizationSlugFromError = (error: ApolloError | unknown) => { + const graphQLErrors = (error as ApolloError)?.graphQLErrors + + if (graphQLErrors) { + for (const graphQLError of graphQLErrors) { + const extensions = graphQLError.extensions as CustomExtension + + const organizationSlug = + extensions?.problem?.organizationSlug ?? + extensions?.exception?.problem?.organizationSlug + + if (organizationSlug) { + return organizationSlug + } + } + } + + return undefined +}