-
Notifications
You must be signed in to change notification settings - Fork 4k
Introduce free trial countdown pop-up #84049
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
Changes from all commits
106994b
f21e45e
905d611
af73253
c98fb52
ae8600c
f3794e2
25295e1
f44c7f2
a5aee0d
9ddf5ba
130e3e2
ab2b357
6d18fcf
72790af
2771a93
9b68afb
3f1c0d4
646f429
bfa0e34
7713e7f
192ad78
cbb41d6
e5aadf3
34c8614
bd248d5
a271c37
23c2589
fda035f
ccaf446
04d009e
0b8785c
162be7e
27c0359
60fe0c4
829b511
2afe1fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; | ||
| import useLocalize from '@hooks/useLocalize'; | ||
| import useResponsiveLayout from '@hooks/useResponsiveLayout'; | ||
| import useThemeStyles from '@hooks/useThemeStyles'; | ||
| import type {CountdownTime, TrialReminderVariant} from '@hooks/useTrialPaymentReminder'; | ||
|
|
||
| import colors from '@styles/theme/colors'; | ||
|
|
||
| import CONST from '@src/CONST'; | ||
|
|
||
| import React from 'react'; | ||
| import {View} from 'react-native'; | ||
|
|
||
| import Button from './Button'; | ||
| import ImageSVG from './ImageSVG'; | ||
| import Modal from './Modal'; | ||
| import Text from './Text'; | ||
|
|
||
| type TrialPaymentReminderModalProps = { | ||
| /** Whether the modal is visible */ | ||
| isVisible: boolean; | ||
|
|
||
| /** The variant of the modal to display */ | ||
| variant: TrialReminderVariant; | ||
|
|
||
| /** Number of days remaining for 'nearEnd' variant */ | ||
| daysRemaining?: number; | ||
|
|
||
| /** Countdown time for 'countdown' variant */ | ||
| countdownTime?: CountdownTime; | ||
|
|
||
| /** Called when user presses Close */ | ||
| onClose: () => void; | ||
|
|
||
| /** Called when user presses Add payment card */ | ||
| onAddPaymentCard: () => void; | ||
| }; | ||
|
|
||
| function padZero(num: number): string { | ||
| return num.toString().padStart(2, '0'); | ||
| } | ||
|
|
||
| function TrialPaymentReminderModal({isVisible, variant, daysRemaining, countdownTime, onClose, onAddPaymentCard}: TrialPaymentReminderModalProps) { | ||
| const {shouldUseNarrowLayout} = useResponsiveLayout(); | ||
| const styles = useThemeStyles(); | ||
| const illustrations = useMemoizedLazyIllustrations(['ArmWithCardPos']); | ||
| const {translate} = useLocalize(); | ||
|
|
||
| return ( | ||
| <Modal | ||
| onClose={onClose} | ||
| onBackdropPress={() => {}} | ||
| isVisible={isVisible} | ||
| type={shouldUseNarrowLayout ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.CONFIRM} | ||
| innerContainerStyle={styles.pv0} | ||
| > | ||
| <View style={[styles.alignItemsCenter, styles.wAuto, {backgroundColor: colors.blue800, height: CONST.CONFIRM_CONTENT_SVG_SIZE.HEIGHT}, styles.pb7]}> | ||
| <ImageSVG | ||
| src={illustrations.ArmWithCardPos} | ||
| contentFit="contain" | ||
| /> | ||
| </View> | ||
| <View style={[styles.m5]}> | ||
| {variant === CONST.TRIAL_REMINDER_VARIANT.NEAR_END && daysRemaining !== undefined && ( | ||
| <Text style={[styles.textSuccess, styles.textStrong, styles.mb2]}>{translate('trialPaymentReminder.trialEndsInDays', {count: daysRemaining})}</Text> | ||
| )} | ||
| {variant === CONST.TRIAL_REMINDER_VARIANT.COUNTDOWN && !!countdownTime && ( | ||
| <Text style={[styles.textSuccess, styles.textStrong, styles.mb2]}> | ||
| {translate('trialPaymentReminder.trialEndsCountdown', { | ||
| hours: padZero(countdownTime.hours), | ||
| minutes: padZero(countdownTime.minutes), | ||
| seconds: padZero(countdownTime.seconds), | ||
| })} | ||
| </Text> | ||
| )} | ||
|
|
||
| <Text style={[styles.textHeadlineH1, styles.mb3]}>{translate('trialPaymentReminder.title')}</Text> | ||
| <Text style={[styles.textSupporting]}>{translate('trialPaymentReminder.subtitle')}</Text> | ||
|
|
||
| <Button | ||
| success | ||
| style={[styles.mt5]} | ||
| onPress={onAddPaymentCard} | ||
| pressOnEnter | ||
| text={translate('trialPaymentReminder.addPaymentCardButton')} | ||
| large | ||
| /> | ||
| <Button | ||
| style={[styles.mt3]} | ||
| onPress={onClose} | ||
| text={translate('trialPaymentReminder.closeButton')} | ||
| large | ||
| /> | ||
| </View> | ||
| </Modal> | ||
| ); | ||
| } | ||
|
|
||
| export default TrialPaymentReminderModal; | ||
|
dukenv0307 marked this conversation as resolved.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import useOnyx from '@hooks/useOnyx'; | ||
| import useTrialPaymentReminder from '@hooks/useTrialPaymentReminder'; | ||
|
|
||
| import Navigation from '@libs/Navigation/Navigation'; | ||
|
|
||
| import ONYXKEYS from '@src/ONYXKEYS'; | ||
| import ROUTES from '@src/ROUTES'; | ||
|
|
||
| import React, {useCallback, useState} from 'react'; | ||
|
|
||
| import TrialPaymentReminderModal from './TrialPaymentReminderModal'; | ||
|
|
||
| function TrialPaymentReminderModalManager() { | ||
| const {isEligibleToShow, currentVariation, countdownTime, dismiss} = useTrialPaymentReminder(); | ||
| const [modal] = useOnyx(ONYXKEYS.MODAL); | ||
| const [isModalOpen, setIsModalOpen] = useState(false); | ||
|
|
||
| const isOtherModalActive = !!modal?.isVisible || !!modal?.willAlertModalBecomeVisible; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dukenv0307 i believe we can use the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NAB Any other modal or panel opened anywhere in the app makes this component re-render for nothing @dukenv0307
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you comment before we merge?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @grgia It will defer the opening to when all modals are closed to prevent race conditions in opening of modal.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you |
||
|
|
||
| if (isEligibleToShow && !isOtherModalActive && !isModalOpen) { | ||
| setIsModalOpen(true); | ||
| } | ||
| if (!isEligibleToShow && isModalOpen) { | ||
| setIsModalOpen(false); | ||
| } | ||
|
|
||
| const handleClose = useCallback(() => { | ||
| setIsModalOpen(false); | ||
| dismiss(); | ||
| }, [dismiss]); | ||
|
|
||
| const handleAddPaymentCard = useCallback(() => { | ||
| setIsModalOpen(false); | ||
| dismiss(); | ||
| Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD); | ||
| }, [dismiss]); | ||
|
|
||
| if (!currentVariation) { | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <TrialPaymentReminderModal | ||
| isVisible={isModalOpen} | ||
| variant={currentVariation.variant} | ||
| daysRemaining={currentVariation.daysRemaining} | ||
| countdownTime={countdownTime} | ||
| onClose={handleClose} | ||
| onAddPaymentCard={handleAddPaymentCard} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| export default TrialPaymentReminderModalManager; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just confirming, same color for both dark/light mode?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@grgia Yes, I think it's correct cc @shawnborton
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @Expensify/design - should the green button always be at the bottom in this case? Also this might be a good excuse to use our ghost button for the Close button and put it at the bottom?
Otherwise yes, the colors in the themes look correct to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm maybe so? Our regular "confirmation dialog" has the primary action on top, but I'm not sure if we use a different placement for these types of modals. Either of you have any other examples of this type?

I was thinking this could be nice, but I'm not sure I love how it looks in practice. What do you think?

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, this doesn't feel great to me.
But yeah, I tend to think that the green button should be above the primary button when we are showing it in a modal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shawnborton @dannymcclain Is it ok?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That looks good to me 👍