diff --git a/app/data/data-addons/src/main/graphql/TravelAddonBannerQuery.graphql b/app/data/data-addons/src/main/graphql/TravelAddonBannerQuery.graphql new file mode 100644 index 0000000000..e4e2d703b6 --- /dev/null +++ b/app/data/data-addons/src/main/graphql/TravelAddonBannerQuery.graphql @@ -0,0 +1,10 @@ +query TravelAddonBanner($flow: UpsellTravelAddonFlow!) { + currentMember { + upsellTravelAddonBanner (flow: $flow) { + badges + contractIds + descriptionDisplayName + titleDisplayName + } + } +} diff --git a/app/data/data-addons/src/main/kotlin/com/hedvig/android/data/addons/data/GetTravelAddonBannerInfoUseCase.kt b/app/data/data-addons/src/main/kotlin/com/hedvig/android/data/addons/data/GetTravelAddonBannerInfoUseCase.kt index ce3c9e1852..10d4441f67 100644 --- a/app/data/data-addons/src/main/kotlin/com/hedvig/android/data/addons/data/GetTravelAddonBannerInfoUseCase.kt +++ b/app/data/data-addons/src/main/kotlin/com/hedvig/android/data/addons/data/GetTravelAddonBannerInfoUseCase.kt @@ -2,9 +2,10 @@ package com.hedvig.android.data.addons.data import arrow.core.Either import arrow.core.NonEmptyList -import arrow.core.nonEmptyListOf import arrow.core.raise.either +import arrow.core.toNonEmptyListOrNull import com.apollographql.apollo.ApolloClient +import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.featureflags.FeatureManager import com.hedvig.android.featureflags.flags.Feature @@ -12,6 +13,8 @@ import com.hedvig.android.logger.LogPriority import com.hedvig.android.logger.logcat import kotlin.String import kotlinx.coroutines.flow.first +import octopus.TravelAddonBannerQuery +import octopus.type.UpsellTravelAddonFlow interface GetTravelAddonBannerInfoUseCase { suspend fun invoke(source: TravelAddonBannerSource): Either @@ -30,13 +33,39 @@ internal class GetTravelAddonBannerInfoUseCaseImpl( } null } else { - // TODO: actual impl here!!!! - // TODO: and null if eligibleInsurancesIds is empty - TravelAddonBannerInfo( - title = "Travel Plus", - description = "Extended travel insurance with extra coverage for your travels", - labels = listOf("Popular"), - eligibleInsurancesIds = nonEmptyListOf("id1"), + val mappedSource = when (source) { + TravelAddonBannerSource.TRAVEL_CERTIFICATES -> UpsellTravelAddonFlow.APP_UPSELL_UPGRADE + TravelAddonBannerSource.INSURANCES_TAB -> UpsellTravelAddonFlow.APP_ONLY_UPSALE + } + apolloClient.query(TravelAddonBannerQuery(mappedSource)).safeExecute().fold( + ifLeft = { error -> + logcat(LogPriority.ERROR) { "Error from travelAddonBannerQuery from source: $mappedSource: $error" } + raise(ErrorMessage()) + }, + ifRight = { result -> + val bannerData = result.currentMember.upsellTravelAddonBanner + if (bannerData == null) { + logcat(LogPriority.DEBUG) { "Got null response from TravelAddonBannerQuery" } + null + } else { + val nonEmptyContracts = bannerData.contractIds.toNonEmptyListOrNull() + if (nonEmptyContracts.isNullOrEmpty()) { + logcat(LogPriority.ERROR) { + "Got non null response from TravelAddonBannerQuery from source: " + + "$mappedSource, but contractIds are empty" + } + null + } else { + TravelAddonBannerInfo( + title = bannerData.titleDisplayName, + description = bannerData.descriptionDisplayName, + labels = bannerData.badges, + eligibleInsurancesIds = nonEmptyContracts, + bannerSource = mappedSource, + ) + } + } + }, ) } } @@ -48,6 +77,7 @@ data class TravelAddonBannerInfo( val description: String, val labels: List, val eligibleInsurancesIds: NonEmptyList, + val bannerSource: UpsellTravelAddonFlow, ) enum class TravelAddonBannerSource { diff --git a/app/feature/feature-addon-purchase/src/main/graphql/UpsellAddonOfferMutation.graphql b/app/feature/feature-addon-purchase/src/main/graphql/UpsellAddonOfferMutation.graphql new file mode 100644 index 0000000000..0df6686ac7 --- /dev/null +++ b/app/feature/feature-addon-purchase/src/main/graphql/UpsellAddonOfferMutation.graphql @@ -0,0 +1,33 @@ +mutation UpsellAddonOffer($contractId: ID!) { + upsellTravelAddonOffer(contractId: $contractId) { + offer { + activationDate + currentAddon { + displayItems { + displayValue + displayTitle + } + premium { + ...MoneyFragment + } + } + descriptionDisplayName + quotes { + addonId + displayItems { + displayTitle + displayValue + } + displayName + premium { + ...MoneyFragment + } + quoteId + } + titleDisplayName + } + userError { + message + } + } +} diff --git a/app/feature/feature-addon-purchase/src/main/graphql/UpsellTravelAddonActivateMutation.graphql b/app/feature/feature-addon-purchase/src/main/graphql/UpsellTravelAddonActivateMutation.graphql new file mode 100644 index 0000000000..cd970d1d67 --- /dev/null +++ b/app/feature/feature-addon-purchase/src/main/graphql/UpsellTravelAddonActivateMutation.graphql @@ -0,0 +1,7 @@ +mutation UpsellTravelAddonActivate($addonId: ID!, $quoteId: ID!) { + upsellTravelAddonActivate(addonId: $addonId, quoteId: $quoteId) { + userError { + message + } + } +} diff --git a/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/data/GetInsuranceForTravelAddonUseCase.kt b/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/data/GetInsuranceForTravelAddonUseCase.kt index 0480c5d77a..2108332c95 100644 --- a/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/data/GetInsuranceForTravelAddonUseCase.kt +++ b/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/data/GetInsuranceForTravelAddonUseCase.kt @@ -2,6 +2,7 @@ package com.hedvig.android.feature.addon.purchase.data import arrow.core.Either import arrow.core.raise.either +import arrow.core.raise.ensure import com.apollographql.apollo.ApolloClient import com.apollographql.apollo.cache.normalized.FetchPolicy import com.apollographql.apollo.cache.normalized.fetchPolicy @@ -41,21 +42,12 @@ internal class GetInsuranceForTravelAddonUseCaseImpl( } raise(ErrorMessage()) } else { - // TODO: remove mock! -// val result = memberResponse.bind().currentMember.toInsurancesForAddon(ids) -// ensure(result.isNotEmpty()) { -// { "Tried to get list of insurances for addon purchase but the list is empty!" } -// ErrorMessage() -// } -// result - listOf( - InsuranceForAddon( - id = "id", - displayName = "Rent Bas", - "Tulegatan 1", - ContractGroup.RENTAL, - ), - ) // TODO: remove mock!! + val result = memberResponse.bind().currentMember.toInsurancesForAddon(ids) + ensure(result.isNotEmpty()) { + logcat { "Tried to get list of insurances for addon purchase but the list is empty!" } + ErrorMessage() + } + result } } } diff --git a/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/data/GetTravelAddonOfferUseCase.kt b/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/data/GetTravelAddonOfferUseCase.kt index 8e149a2a76..b35548cdf5 100644 --- a/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/data/GetTravelAddonOfferUseCase.kt +++ b/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/data/GetTravelAddonOfferUseCase.kt @@ -1,16 +1,25 @@ package com.hedvig.android.feature.addon.purchase.data import arrow.core.Either +import arrow.core.NonEmptyList import arrow.core.nonEmptyListOf import arrow.core.raise.either +import arrow.core.toNonEmptyListOrNull import com.apollographql.apollo.ApolloClient +import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.uidata.UiCurrencyCode import com.hedvig.android.core.uidata.UiMoney import com.hedvig.android.data.productvariant.InsuranceVariantDocument import com.hedvig.android.feature.addon.purchase.data.Addon.TravelAddonOffer import com.hedvig.android.featureflags.FeatureManager +import com.hedvig.android.featureflags.flags.Feature +import com.hedvig.android.logger.LogPriority +import com.hedvig.android.logger.logcat +import kotlin.String +import kotlinx.coroutines.flow.first import kotlinx.datetime.LocalDate +import octopus.UpsellAddonOfferMutation internal interface GetTravelAddonOfferUseCase { suspend fun invoke(id: String): Either @@ -21,13 +30,77 @@ internal class GetTravelAddonOfferUseCaseImpl( private val featureManager: FeatureManager, ) : GetTravelAddonOfferUseCase { override suspend fun invoke(id: String): Either { - // todo: REMOVE MOCK! return either { - mockWithUpgrade + val isAddonFlagOn = featureManager.isFeatureEnabled(Feature.TRAVEL_ADDON).first() + if (!isAddonFlagOn) { + logcat(LogPriority.ERROR) { "Tried to start UpsellAddonOfferMutation but addon feature flag is off" } + raise(ErrorMessage()) + } + apolloClient.mutation(UpsellAddonOfferMutation(id)).safeExecute().fold( + ifLeft = { error -> + logcat(LogPriority.ERROR) { "Tried to start UpsellAddonOfferMutation but got error: $error" } + // not passing error message to the member here, as we want to redirect member to chat if there is a message + raise(ErrorMessage()) + }, + ifRight = { result -> + if (result.upsellTravelAddonOffer.userError != null) { + raise(ErrorMessage(result.upsellTravelAddonOffer.userError.message)) + // the only case where we want to redirect to chat + } + val data = result.upsellTravelAddonOffer.offer + if (data == null) { + logcat(LogPriority.ERROR) { "Tried to do UpsellAddonOfferMutation but got null offer" } + raise(ErrorMessage()) + } + val nonEmptyQuotes = data.quotes.toNonEmptyListOrNull() + if (nonEmptyQuotes.isNullOrEmpty()) { + logcat(LogPriority.ERROR) { "Tried to do UpsellAddonOfferMutation but got empty quotes" } + raise(ErrorMessage()) + } + + TravelAddonOffer( + addonOptions = nonEmptyQuotes.toTravelAddonQuotes(), + title = data.titleDisplayName, + description = data.descriptionDisplayName, + activationDate = data.activationDate, + currentTravelAddon = data.currentAddon.toCurrentAddon(), + ) + }, + ) } } } +private fun NonEmptyList.toTravelAddonQuotes(): NonEmptyList { + return this.map { + TravelAddonQuote( + quoteId = it.quoteId, + addonId = it.addonId, + displayName = it.displayName, + price = UiMoney.fromMoneyFragment(it.premium), + addonVariant = AddonVariant( + documents = listOf(), // todo: Addons - populate when api changes! + termsVersion = "", // todo: Addons - populate when api changes! + displayDetails = it.displayItems.map { item -> + item.displayTitle to item.displayValue + }, + ), + ) + } +} + +private fun UpsellAddonOfferMutation.Data.UpsellTravelAddonOffer.Offer.CurrentAddon?.toCurrentAddon(): CurrentTravelAddon? { + return this?.let { + CurrentTravelAddon( + price = UiMoney.fromMoneyFragment(premium), + displayDetails = displayItems.map { + it.displayTitle to it.displayValue + }, + ) + } +} + +// todo: remove mocks when not needed private val mockWithoutUpgrade = TravelAddonOffer( addonOptions = nonEmptyListOf( TravelAddonQuote( diff --git a/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/data/SubmitAddonPurchaseUseCase.kt b/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/data/SubmitAddonPurchaseUseCase.kt index edfe93b9f6..b909e1b182 100644 --- a/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/data/SubmitAddonPurchaseUseCase.kt +++ b/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/data/SubmitAddonPurchaseUseCase.kt @@ -3,22 +3,32 @@ package com.hedvig.android.feature.addon.purchase.data import arrow.core.Either import arrow.core.raise.either import com.apollographql.apollo.ApolloClient +import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage -import com.hedvig.android.featureflags.FeatureManager -import kotlinx.datetime.LocalDate +import com.hedvig.android.logger.LogPriority +import com.hedvig.android.logger.logcat +import octopus.UpsellTravelAddonActivateMutation internal interface SubmitAddonPurchaseUseCase { - suspend fun invoke(quoteId: String, addonId: String): Either + suspend fun invoke(quoteId: String, addonId: String): Either } internal class SubmitAddonPurchaseUseCaseImpl( private val apolloClient: ApolloClient, - private val featureManager: FeatureManager, ) : SubmitAddonPurchaseUseCase { - override suspend fun invoke(quoteId: String, addonId: String): Either { - // TODO: REMOVE MOCK! + override suspend fun invoke(quoteId: String, addonId: String): Either { return either { - LocalDate(2025, 1, 1) + apolloClient.mutation(UpsellTravelAddonActivateMutation(addonId = addonId, quoteId = quoteId)).safeExecute().fold( + ifLeft = { error -> + logcat(LogPriority.ERROR) { "Tried to do UpsellTravelAddonActivateMutation but got error: $error" } + raise(ErrorMessage()) + }, + ifRight = { result -> + if (result.upsellTravelAddonActivate.userError != null) { + raise(ErrorMessage(result.upsellTravelAddonActivate.userError.message)) + } + }, + ) } } } diff --git a/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/di/AddonPurchaseModule.kt b/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/di/AddonPurchaseModule.kt index 77517de224..410f7f3bd1 100644 --- a/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/di/AddonPurchaseModule.kt +++ b/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/di/AddonPurchaseModule.kt @@ -54,7 +54,6 @@ val addonPurchaseModule = module { single { SubmitAddonPurchaseUseCaseImpl( apolloClient = get(), - featureManager = get(), ) } } diff --git a/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/ui/summary/AddonSummaryViewModel.kt b/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/ui/summary/AddonSummaryViewModel.kt index 7e88df1e24..4106c8a051 100644 --- a/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/ui/summary/AddonSummaryViewModel.kt +++ b/app/feature/feature-addon-purchase/src/main/kotlin/com/hedvig/android/feature/addon/purchase/ui/summary/AddonSummaryViewModel.kt @@ -55,9 +55,12 @@ internal class AddonSummaryPresenter( ).fold( ifLeft = { currentState = initialState.copy(navigateToFailure = true) + // todo: not really passing UserError message here. Should we? Or should we maybe redirect to chat in + // the case of final failure? }, ifRight = { date -> - currentState = initialState.copy(activationDateForSuccessfullyPurchasedAddon = date) + currentState = + initialState.copy(activationDateForSuccessfullyPurchasedAddon = summaryParameters.activationDate) }, ) } diff --git a/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/insurance/InsuranceDestination.kt b/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/insurance/InsuranceDestination.kt index 72b97e0e2e..d147899728 100644 --- a/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/insurance/InsuranceDestination.kt +++ b/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/insurance/InsuranceDestination.kt @@ -83,6 +83,7 @@ import com.hedvig.android.pullrefresh.pullRefresh import com.hedvig.android.pullrefresh.rememberPullRefreshState import hedvig.resources.R import kotlinx.datetime.LocalDate +import octopus.type.UpsellTravelAddonFlow @Composable internal fun InsuranceDestination( @@ -492,6 +493,7 @@ private class InsuranceUiStateProvider : CollectionPreviewParameterProvider