Skip to content

Commit

Permalink
Final touches for progressive offer creation
Browse files Browse the repository at this point in the history
  • Loading branch information
Samuel Tremko authored and SamTremko committed Jul 17, 2024
1 parent 07040c7 commit e9e6ace
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {pipe} from 'fp-ts/function'
import {focusAtom} from 'jotai-optics'
import {splitAtom} from 'jotai/utils'
import {Alert} from 'react-native'
import {btcOfferScreens, otherOfferScreens, productOfferScreens} from '..'
import {type CRUDOfferStackParamsList} from '../../../navigationTypes'
import {createInboxAtom} from '../../../state/chat/hooks/useCreateInbox'
import {
Expand Down Expand Up @@ -1058,7 +1059,7 @@ export const offerFormMolecule = molecule(() => {
'ListingAndOfferType'
)

const nextButtonInOfferModificationDisabledAtom = atom((get) => {
const dontAllowNavigationToNextStepAndReturnReasonAtom = atom((get) => {
const currentStepInOfferCreation = get(currentStepInOfferCreationAtom)
const offerType = get(offerTypeAtom)
const listingType = get(listingTypeAtom)
Expand All @@ -1068,38 +1069,54 @@ export const offerFormMolecule = molecule(() => {
const amountBottomLimit = get(amountBottomLimitAtom)
const currency = get(currencyAtom)

const noListingTypeOrOfferType =
currentStepInOfferCreation === 'ListingAndOfferType' &&
(!listingType || !offerType)
const noListingType =
currentStepInOfferCreation === 'ListingAndOfferType' && !listingType

if (noListingType) return 'errorListingTypeNotFilled'

const noOfferType =
currentStepInOfferCreation === 'ListingAndOfferType' && !offerType

if (noOfferType) return 'errorOfferTypeNotFilled'

const noOfferDescription =
currentStepInOfferCreation ===
'OfferDescriptionAndSpokenLanguagesScreen' &&
get(offerDescriptionAtom).trim() === ''

if (noOfferDescription) return 'errorDescriptionNotFilled'

const noLocationForInPersonOffer =
currentStepInOfferCreation === 'LocationPaymentMethodAndNetworkScreen' &&
locationState?.includes('IN_PERSON') &&
location?.length === 0

if (noLocationForInPersonOffer) return 'errorLocationNotFilled'

const priceNotFilled =
currentStepInOfferCreation === 'PriceScreen' &&
listingType !== 'BITCOIN' &&
singlePriceActive &&
amountBottomLimit === 0

if (priceNotFilled) return 'errorPriceNotFilled'

const deliveryMethodNotFilled =
currentStepInOfferCreation === 'DeliveryMethodAndNetworkScreen' &&
listingType === 'PRODUCT' &&
locationState?.length === 0

if (deliveryMethodNotFilled) return 'errorDeliveryMethodNotFilled'

const pickupLocationNotFilled =
(currentStepInOfferCreation === 'DeliveryMethodAndNetworkScreen' &&
listingType === 'PRODUCT' &&
locationState?.includes('IN_PERSON') &&
location?.length === 0) ??
false

if (pickupLocationNotFilled) return 'errorPickupLocationNotFilled'

const exceededLimit =
((currentStepInOfferCreation === 'CurrencyAndAmount' ||
currentStepInOfferCreation === 'PriceScreen') &&
Expand All @@ -1110,16 +1127,28 @@ export const offerFormMolecule = molecule(() => {
amountBottomLimit > currencies[currency].maxAmount) ??
false

return (
(noListingTypeOrOfferType ||
noOfferDescription ||
priceNotFilled ||
deliveryMethodNotFilled ||
pickupLocationNotFilled ||
exceededLimit ||
noLocationForInPersonOffer) ??
false
)
if (exceededLimit) return 'errorExceededLimits'

return undefined
})

const emitAlertBasedOnCurrentStepIfAnyAtom = atom(null, (get) => {
const {t} = get(translationAtom)
const reason = get(dontAllowNavigationToNextStepAndReturnReasonAtom)

if (reason) Alert.alert(t(`offerForm.${reason}`))
})

const screensBasedOnListingTypeAtom = atom((get) => {
const listingType = get(listingTypeAtom)

return listingType === 'BITCOIN'
? btcOfferScreens
: listingType === 'PRODUCT'
? productOfferScreens
: listingType === 'OTHER'
? otherOfferScreens
: []
})

return {
Expand Down Expand Up @@ -1170,6 +1199,8 @@ export const offerFormMolecule = molecule(() => {
updateListingTypeActionAtom,
updateBtcNetworkAtom,
currentStepInOfferCreationAtom,
nextButtonInOfferModificationDisabledAtom,
dontAllowNavigationToNextStepAndReturnReasonAtom,
emitAlertBasedOnCurrentStepIfAnyAtom,
screensBasedOnListingTypeAtom,
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,6 @@ function OfferDescriptionAndSpokenLanguagesScreen(): JSX.Element {

return (
<ScreenWrapper>
<Section
title={t('offerForm.description.description')}
image={descriptionSvg}
>
<Description
offerDescriptionAtom={offerDescriptionAtom}
listingTypeAtom={listingTypeAtom}
offerTypeAtom={offerTypeAtom}
/>
</Section>
<Section
title={t('offerForm.spokenLanguages.language')}
image={spokenLanguagesSvg}
Expand All @@ -51,6 +41,16 @@ function OfferDescriptionAndSpokenLanguagesScreen(): JSX.Element {
}
/>
</Section>
<Section
title={t('offerForm.description.description')}
image={descriptionSvg}
>
<Description
offerDescriptionAtom={offerDescriptionAtom}
listingTypeAtom={listingTypeAtom}
offerTypeAtom={offerTypeAtom}
/>
</Section>
</ScreenWrapper>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,6 @@ function SummaryScreen(): JSX.Element {
reduceDescriptionLength
offer={offer}
/>
<Text
marginTop="$4"
ff="$body500"
mb="$4"
col="$greyOnWhite"
fos={16}
>
{`* ${t('offerForm.summaryAdditionalInfo')}`}
</Text>
</Stack>
</Section>
</ScreenWrapper>
Expand Down
66 changes: 36 additions & 30 deletions apps/mobile/src/components/CRUDOfferFlow/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {createNativeStackNavigator} from '@react-navigation/native-stack'
import {useMolecule} from 'bunshi/dist/react'
import {useAtomValue, useSetAtom} from 'jotai'
import {useCallback, useMemo, useState} from 'react'
import {useAtomValue, useSetAtom, useStore} from 'jotai'
import {useCallback, useState} from 'react'
import {
type CRUDOfferStackParamsList,
type RootStackScreenProps,
Expand All @@ -22,7 +22,7 @@ import OfferDescriptionAndSpokenLanguagesScreen from './components/OfferDescript
import PriceScreen from './components/PriceScreen'
import SummaryScreen from './components/SummaryScreen'

const btcOfferScreens: Array<keyof CRUDOfferStackParamsList> = [
export const btcOfferScreens: Array<keyof CRUDOfferStackParamsList> = [
'ListingAndOfferType',
'CurrencyAndAmount',
'LocationPaymentMethodAndNetworkScreen',
Expand All @@ -31,7 +31,7 @@ const btcOfferScreens: Array<keyof CRUDOfferStackParamsList> = [
'SummaryScreen',
]

const productOfferScreens: Array<keyof CRUDOfferStackParamsList> = [
export const productOfferScreens: Array<keyof CRUDOfferStackParamsList> = [
'ListingAndOfferType',
'PriceScreen',
'DeliveryMethodAndNetworkScreen',
Expand All @@ -40,7 +40,7 @@ const productOfferScreens: Array<keyof CRUDOfferStackParamsList> = [
'SummaryScreen',
]

const otherOfferScreens: Array<keyof CRUDOfferStackParamsList> = [
export const otherOfferScreens: Array<keyof CRUDOfferStackParamsList> = [
'ListingAndOfferType',
'PriceScreen',
'LocationPaymentMethodAndNetworkScreen',
Expand All @@ -58,48 +58,52 @@ function CRUDOfferFlow({route: {params}, navigation}: Props): JSX.Element {
const [page, setPage] = useState(0)
const safeGoBack = useSafeGoBack()
const {
listingTypeAtom,
screensBasedOnListingTypeAtom,
editOfferActionAtom,
createOfferActionAtom,
nextButtonInOfferModificationDisabledAtom,
currentStepInOfferCreationAtom,
emitAlertBasedOnCurrentStepIfAnyAtom,
dontAllowNavigationToNextStepAndReturnReasonAtom,
} = useMolecule(offerFormMolecule)
const store = useStore()

const listingType = useAtomValue(listingTypeAtom)
const screensBasedOnListingType = useAtomValue(screensBasedOnListingTypeAtom)
const emitAlertBasedOnCurrentStepIfAny = useSetAtom(
emitAlertBasedOnCurrentStepIfAnyAtom
)
const createOffer = useSetAtom(createOfferActionAtom)
const editOffer = useSetAtom(editOfferActionAtom)
const setCurrentStepInOfferCreation = useSetAtom(
currentStepInOfferCreationAtom
)

const screensBasedOnListingType = useMemo(
() =>
listingType === 'BITCOIN'
? btcOfferScreens
: listingType === 'PRODUCT'
? productOfferScreens
: listingType === 'OTHER'
? otherOfferScreens
: [],
[listingType]
)

const onPageChange = useCallback(
(pageIndex: number) => {
(prevOrNextPageIndex: number) => {
const currentPage =
screensBasedOnListingType[pageIndex] ?? 'ListingAndOfferType'
setCurrentStepInOfferCreation(currentPage)
navigation.navigate('CRUDOfferFlow', {
screen: currentPage,
offerId: params.offerId,
})
setPage(pageIndex)
screensBasedOnListingType[prevOrNextPageIndex] ?? 'ListingAndOfferType'
if (
prevOrNextPageIndex < page ||
!store.get(dontAllowNavigationToNextStepAndReturnReasonAtom)
) {
setCurrentStepInOfferCreation(currentPage)
navigation.navigate('CRUDOfferFlow', {
screen: currentPage,
offerId: params.offerId,
})
setPage(prevOrNextPageIndex)
} else {
emitAlertBasedOnCurrentStepIfAny()
}
},
[
dontAllowNavigationToNextStepAndReturnReasonAtom,
emitAlertBasedOnCurrentStepIfAny,
navigation,
page,
params.offerId,
screensBasedOnListingType,
setCurrentStepInOfferCreation,
store,
]
)

Expand All @@ -119,7 +123,10 @@ function CRUDOfferFlow({route: {params}, navigation}: Props): JSX.Element {
currentPage={page}
numberOfPages={screensBasedOnListingType.length}
onPageChange={(nextPageIndex) => {
if (nextPageIndex < screensBasedOnListingType.length) {
if (
screensBasedOnListingType.length === 0 ||
nextPageIndex < screensBasedOnListingType.length
) {
onPageChange(nextPageIndex)
}
}}
Expand All @@ -145,7 +152,6 @@ function CRUDOfferFlow({route: {params}, navigation}: Props): JSX.Element {
}}
background="black"
touchableOverlayDisabled
nextButtonDisabledAtom={nextButtonInOfferModificationDisabledAtom}
>
<CRUDOfferStack.Navigator
screenOptions={{
Expand Down
25 changes: 2 additions & 23 deletions apps/mobile/src/components/OfferAuthorAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@ import {type OneOfferInState} from '@vexl-next/domain/src/general/offers'
import {useAtomValue} from 'jotai'
import {DateTime} from 'luxon'
import {useMemo} from 'react'
import {Stack, Text, XStack} from 'tamagui'
import {Stack, Text} from 'tamagui'
import {useChatForOffer} from '../state/chat/hooks/useChatForOffer'
import createImportedContactsForHashesAtom from '../state/contacts/atom/createImportedContactsForHashesAtom'
import {userDataRealOrAnonymizedAtom} from '../state/session'
import {useTranslation} from '../utils/localization/I18nProvider'
import randomName from '../utils/randomName'
import {setTimezoneOfUser} from '../utils/unixMillisecondsToLocaleDateTime'
import {AnonymousAvatarFromSeed} from './AnonymousAvatar'
import friendsSvg from './ChatDetailScreen/images/friendsSvg'
import ContactTypeAndCommonNumber from './ContactTypeAndCommonNumber'
import Image from './Image'
import UserAvatar from './UserAvatar'
import UserNameWithSellingBuying from './UserNameWithSellingBuying'

Expand Down Expand Up @@ -61,33 +59,14 @@ function OfferAuthorAvatar({
height={48}
seed={offerInfo.offerId}
/>
<Stack f={1} ml="$2">
<Stack flex={1} marginLeft="$2">
<UserNameWithSellingBuying
offerInfo={offerInfo}
userName={
chatForOffer?.otherSide?.realLifeInfo?.userName ??
randomName(offerInfo.offerId)
}
/>
<XStack
flexWrap="wrap"
space="$1"
justifyContent="flex-start"
alignItems="center"
>
<Text color="$greyOnBlack">
{t('offerForm.summaryFriendLevelInfo')}
</Text>
<Text color="$greyOnBlack"></Text>
<XStack alignItems="center" space="$1">
<Stack width={14} height={14}>
<Image source={friendsSvg} />
</Stack>
<Text color="$greyOnBlack">
{t('offerForm.summaryNumberOfCommonFriends')}
</Text>
</XStack>
</XStack>
</Stack>
</>
)
Expand Down
Loading

0 comments on commit e9e6ace

Please sign in to comment.