@heyama/pay-react-native 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/checkout-core/hooks/use-checkout-controller.js +5 -1
- package/dist/checkout-core/messages/en.d.ts +1 -0
- package/dist/checkout-core/messages/en.js +1 -0
- package/dist/checkout-core/messages/fr.js +1 -0
- package/dist/checkout-core/models/checkout.enum.d.ts +2 -0
- package/dist/checkout-core/models/checkout.enum.js +2 -0
- package/dist/checkout-core/models/checkout.interface.d.ts +5 -0
- package/dist/checkout-core/models/checkout.view.utils.js +1 -0
- package/dist/components/SheetButton.d.ts +1 -1
- package/dist/components/SheetButton.js +8 -4
- package/dist/components/SheetChosenMethod.js +3 -2
- package/dist/components/SheetCodeField.js +3 -2
- package/dist/components/SheetFailed.js +1 -1
- package/dist/components/SheetForm.js +17 -8
- package/dist/components/SheetHeader.js +9 -8
- package/dist/components/SheetIcon.js +2 -1
- package/dist/components/SheetLayout.js +5 -3
- package/dist/components/SheetMethodCard.d.ts +1 -1
- package/dist/components/SheetMethodCard.js +19 -4
- package/dist/components/SheetPhoneField.js +3 -2
- package/dist/hooks/use-shake.d.ts +3 -0
- package/dist/hooks/use-shake.js +18 -0
- package/dist/models/sheet.constants.d.ts +21 -0
- package/dist/models/sheet.constants.js +21 -0
- package/dist/models/sheet.interface.d.ts +4 -0
- package/package.json +1 -1
|
@@ -22,6 +22,7 @@ const NO_PAYMENT_UI = {
|
|
|
22
22
|
submitting: false,
|
|
23
23
|
dismissed: null,
|
|
24
24
|
redirecting: false,
|
|
25
|
+
methodMissing: 0,
|
|
25
26
|
};
|
|
26
27
|
/**
|
|
27
28
|
* The whole checkout for one payment, on any platform: the gateway's live
|
|
@@ -196,6 +197,7 @@ export function useCheckoutController(view, { clues, mutations, platform }) {
|
|
|
196
197
|
pickerOpen: false,
|
|
197
198
|
code: '',
|
|
198
199
|
cardComplete: false,
|
|
200
|
+
methodMissing: 0,
|
|
199
201
|
});
|
|
200
202
|
}
|
|
201
203
|
},
|
|
@@ -209,8 +211,10 @@ export function useCheckoutController(view, { clues, mutations, platform }) {
|
|
|
209
211
|
},
|
|
210
212
|
pay: () => {
|
|
211
213
|
const { selected } = derived;
|
|
212
|
-
if (!selected)
|
|
214
|
+
if (!selected) {
|
|
215
|
+
update({ methodMissing: ui.methodMissing + 1 });
|
|
213
216
|
return;
|
|
217
|
+
}
|
|
214
218
|
// The preview offers no card: it shows Mobile Money only.
|
|
215
219
|
if (selected.flow === MethodFlowEnum.CARD_FIELDS) {
|
|
216
220
|
// The fields page pays with its provider, then says how it went.
|
|
@@ -123,6 +123,8 @@ export declare enum TerminalKindEnum {
|
|
|
123
123
|
export declare enum CheckoutButtonVariantEnum {
|
|
124
124
|
PRIMARY = "primary",
|
|
125
125
|
SECONDARY = "secondary",
|
|
126
|
+
/** The second choice under Pay or Try again: the secondary's look, no border. */
|
|
127
|
+
PLAIN = "plain",
|
|
126
128
|
/** Gives something up: cancelling the payment. */
|
|
127
129
|
DANGER = "danger"
|
|
128
130
|
}
|
|
@@ -139,6 +139,8 @@ export var CheckoutButtonVariantEnum;
|
|
|
139
139
|
(function (CheckoutButtonVariantEnum) {
|
|
140
140
|
CheckoutButtonVariantEnum["PRIMARY"] = "primary";
|
|
141
141
|
CheckoutButtonVariantEnum["SECONDARY"] = "secondary";
|
|
142
|
+
/** The second choice under Pay or Try again: the secondary's look, no border. */
|
|
143
|
+
CheckoutButtonVariantEnum["PLAIN"] = "plain";
|
|
142
144
|
/** Gives something up: cancelling the payment. */
|
|
143
145
|
CheckoutButtonVariantEnum["DANGER"] = "danger";
|
|
144
146
|
})(CheckoutButtonVariantEnum || (CheckoutButtonVariantEnum = {}));
|
|
@@ -186,6 +186,11 @@ export interface CheckoutUi {
|
|
|
186
186
|
/** An attempt the payer walked away from: the form shows again. */
|
|
187
187
|
dismissed: string | null;
|
|
188
188
|
redirecting: boolean;
|
|
189
|
+
/**
|
|
190
|
+
* Pay pressed with no method picked, counted: the list turns red and
|
|
191
|
+
* shakes once per press. Back to 0 once a method is picked.
|
|
192
|
+
*/
|
|
193
|
+
methodMissing: number;
|
|
189
194
|
}
|
|
190
195
|
/** Everything the screens show that follows from the payment and the page, worked out once. */
|
|
191
196
|
export interface CheckoutDerived {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { SheetButtonProps } from '../models/sheet.interface.js';
|
|
2
|
-
/** The sheet's buttons: filled in the app colour for the main action, outlined for the rest. */
|
|
2
|
+
/** The sheet's buttons: filled in the app colour for the main action, outlined for the rest, plain (no border) for a quiet second choice. */
|
|
3
3
|
export declare function SheetButton({ label, onPress, variant, busy, disabled, inactive, }: SheetButtonProps): import("react").JSX.Element;
|
|
@@ -4,7 +4,8 @@ import { CheckoutButtonVariantEnum } from "../checkout-core/models/checkout.enum
|
|
|
4
4
|
import { useSheetTheme } from "../hooks/use-sheet-theme.js";
|
|
5
5
|
import { FigtreeEnum } from "../models/sheet.enum.js";
|
|
6
6
|
import { SheetText } from "./SheetText.js";
|
|
7
|
-
|
|
7
|
+
import { FIELD_HEIGHT, FIELD_RADIUS } from "../models/sheet.constants.js";
|
|
8
|
+
/** The sheet's buttons: filled in the app colour for the main action, outlined for the rest, plain (no border) for a quiet second choice. */
|
|
8
9
|
export function SheetButton({ label, onPress, variant = CheckoutButtonVariantEnum.PRIMARY, busy = false, disabled = false, inactive = false, }) {
|
|
9
10
|
const { colors, brand } = useSheetTheme();
|
|
10
11
|
const primary = variant === CheckoutButtonVariantEnum.PRIMARY;
|
|
@@ -14,14 +15,17 @@ export function SheetButton({ label, onPress, variant = CheckoutButtonVariantEnu
|
|
|
14
15
|
styles.button,
|
|
15
16
|
primary
|
|
16
17
|
? { backgroundColor: brand }
|
|
17
|
-
:
|
|
18
|
+
: variant !== CheckoutButtonVariantEnum.PLAIN && {
|
|
19
|
+
borderWidth: 1,
|
|
20
|
+
borderColor: colors.line3,
|
|
21
|
+
},
|
|
18
22
|
(disabled || inactive) && styles.disabled,
|
|
19
23
|
], children: [busy && _jsx(ActivityIndicator, { color: ink }), _jsx(SheetText, { weight: FigtreeEnum.BOLD, size: 16, color: ink, style: styles.label, children: label })] }));
|
|
20
24
|
}
|
|
21
25
|
const styles = StyleSheet.create({
|
|
22
26
|
button: {
|
|
23
|
-
minHeight:
|
|
24
|
-
borderRadius:
|
|
27
|
+
minHeight: FIELD_HEIGHT,
|
|
28
|
+
borderRadius: FIELD_RADIUS,
|
|
25
29
|
flexDirection: 'row',
|
|
26
30
|
alignItems: 'center',
|
|
27
31
|
justifyContent: 'center',
|
|
@@ -6,6 +6,7 @@ import { useSheetTheme } from "../hooks/use-sheet-theme.js";
|
|
|
6
6
|
import { FigtreeEnum } from "../models/sheet.enum.js";
|
|
7
7
|
import { SheetLogo } from "./SheetLogo.js";
|
|
8
8
|
import { SheetText } from "./SheetText.js";
|
|
9
|
+
import { FIELD_HEIGHT, FIELD_RADIUS } from "../models/sheet.constants.js";
|
|
9
10
|
/** The method picked, on one row, with Change back to the list: as on the web checkout. */
|
|
10
11
|
export function SheetChosenMethod({ logo, name, sub, onChange, }) {
|
|
11
12
|
const t = useTranslations('checkout');
|
|
@@ -23,10 +24,10 @@ const styles = StyleSheet.create({
|
|
|
23
24
|
flexDirection: 'row',
|
|
24
25
|
alignItems: 'center',
|
|
25
26
|
gap: 12,
|
|
26
|
-
height:
|
|
27
|
+
height: FIELD_HEIGHT,
|
|
27
28
|
marginTop: 12,
|
|
28
29
|
borderWidth: 1,
|
|
29
|
-
borderRadius:
|
|
30
|
+
borderRadius: FIELD_RADIUS,
|
|
30
31
|
paddingLeft: 8,
|
|
31
32
|
paddingRight: 6,
|
|
32
33
|
},
|
|
@@ -7,6 +7,7 @@ import { telHref } from "../checkout-core/models/checkout.phone.utils.js";
|
|
|
7
7
|
import { useSheetTheme } from "../hooks/use-sheet-theme.js";
|
|
8
8
|
import { FigtreeEnum } from "../models/sheet.enum.js";
|
|
9
9
|
import { SheetInput, SheetText } from "./SheetText.js";
|
|
10
|
+
import { FIELD_HEIGHT, FIELD_RADIUS } from "../models/sheet.constants.js";
|
|
10
11
|
/**
|
|
11
12
|
* OTP methods: Dial opens the network's code in the phone dialler for a
|
|
12
13
|
* one-time payment code (on Android; on iOS it says to dial it by hand);
|
|
@@ -58,9 +59,9 @@ const styles = StyleSheet.create({
|
|
|
58
59
|
section: { marginTop: 10 },
|
|
59
60
|
label: { marginBottom: 8 },
|
|
60
61
|
input: {
|
|
61
|
-
height:
|
|
62
|
+
height: FIELD_HEIGHT,
|
|
62
63
|
borderWidth: 1,
|
|
63
|
-
borderRadius:
|
|
64
|
+
borderRadius: FIELD_RADIUS,
|
|
64
65
|
paddingHorizontal: 14,
|
|
65
66
|
fontSize: 18,
|
|
66
67
|
letterSpacing: 5,
|
|
@@ -27,7 +27,7 @@ export function SheetFailed({ core }) {
|
|
|
27
27
|
? t('sending')
|
|
28
28
|
: attempt.flow === MethodFlowEnum.OTP
|
|
29
29
|
? t('retry_code')
|
|
30
|
-
: t('retry'), busy: ui.submitting, onPress: actions.retry }), _jsx(SheetButton, { variant: CheckoutButtonVariantEnum.
|
|
30
|
+
: t('retry'), busy: ui.submitting, onPress: actions.retry }), _jsx(SheetButton, { variant: CheckoutButtonVariantEnum.PLAIN, label: suggestion
|
|
31
31
|
? t('try_with', { method: suggestion.name })
|
|
32
32
|
: t('use_another'), onPress: actions.useAnother })] }), children: _jsxs(View, { style: styles.failed, children: [_jsx(SheetIcon, { kind: SheetIconEnum.CROSS, color: colors.danger, background: colors.dangerSoft }), _jsx(SheetText, { weight: FigtreeEnum.BOLD, size: 22, accessibilityRole: "header", style: styles.title, children: t(`failure.${reason}.title`, values) }), _jsx(SheetText, { paragraph: true, size: 15, color: colors.inkStrong, children: t(`failure.${reason}.body`, values) }), _jsx(SheetText, { paragraph: true, color: colors.ink2, style: styles.next, children: t(`failure.${reason}.next`, values) }), _jsxs(View, { style: [styles.rows, { borderColor: colors.line }], children: [_jsx(SheetRow, { label: t('amount'), children: formatMoney(view.amount, view.currency) }), _jsxs(SheetRow, { label: t('method'), children: [_jsx(SheetLogo, { logo: logoOf(attempt) }), _jsx(SheetText, { weight: FigtreeEnum.SEMI_BOLD, children: attempt.methodName })] }), _jsx(SheetRow, { label: t('tries_left'), children: t('tries_value', {
|
|
33
33
|
left: view.attemptsLeft,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Pressable, StyleSheet, View } from 'react-native';
|
|
2
|
+
import { Animated, Pressable, StyleSheet, View } from 'react-native';
|
|
3
3
|
import { useTranslations } from 'use-intl';
|
|
4
4
|
import { CheckoutTabEnum, MethodFlowEnum, MethodStateEnum, } from "../checkout-core/models/checkout.enum.js";
|
|
5
5
|
import { logoOf } from "../checkout-core/models/checkout.view.utils.js";
|
|
6
|
+
import { useShake } from "../hooks/use-shake.js";
|
|
6
7
|
import { useSheetTheme } from "../hooks/use-sheet-theme.js";
|
|
7
8
|
import { FigtreeEnum } from "../models/sheet.enum.js";
|
|
8
9
|
import { flagOf } from "../models/sheet.utils.js";
|
|
@@ -12,13 +13,17 @@ import { SheetCodeField } from "./SheetCodeField.js";
|
|
|
12
13
|
import { SheetMethodCard } from "./SheetMethodCard.js";
|
|
13
14
|
import { SheetPhoneField } from "./SheetPhoneField.js";
|
|
14
15
|
import { SheetText } from "./SheetText.js";
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
import { FIELD_HEIGHT, FIELD_RADIUS, TABS_INSET, } from "../models/sheet.constants.js";
|
|
17
|
+
/** Two cards a row, the last one alone when they are odd; red with a hint, and shaken, after Pay with none picked. */
|
|
18
|
+
function MethodGrid({ cards, missing }) {
|
|
19
|
+
const t = useTranslations('checkout');
|
|
20
|
+
const { colors } = useSheetTheme();
|
|
21
|
+
const shake = useShake(missing);
|
|
17
22
|
const rows = [];
|
|
18
23
|
for (let index = 0; index < cards.length; index += 2) {
|
|
19
24
|
rows.push(cards.slice(index, index + 2));
|
|
20
25
|
}
|
|
21
|
-
return (
|
|
26
|
+
return (_jsxs(Animated.View, { testID: "method-grid", accessibilityRole: "radiogroup", style: [styles.grid, { transform: [{ translateX: shake }] }], children: [rows.map((row) => (_jsxs(View, { style: styles.gridRow, children: [row.map(({ id, ...card }) => (_jsx(SheetMethodCard, { ...card, missing: missing > 0 }, id))), row.length === 1 && _jsx(View, { style: styles.gridSpacer })] }, row[0].id))), missing > 0 && (_jsx(SheetText, { accessibilityRole: "alert", weight: FigtreeEnum.MEDIUM, size: 13, color: colors.danger, children: t('method_missing') }))] }));
|
|
22
27
|
}
|
|
23
28
|
/**
|
|
24
29
|
* The form of design A: the country when there are several, the Mobile
|
|
@@ -61,7 +66,7 @@ export function SheetForm({ core, phoneRef, cardFields }) {
|
|
|
61
66
|
? { backgroundColor: colors.ink, borderColor: colors.ink }
|
|
62
67
|
: { borderColor: colors.line3 },
|
|
63
68
|
], children: [_jsx(SheetText, { size: 14, children: flagOf(country) }), _jsx(SheetText, { weight: FigtreeEnum.SEMI_BOLD, size: 13, color: on ? colors.surface : colors.ink, children: t(`countries.${country}`) })] }, country));
|
|
64
|
-
}) })), _jsxs(View, { accessibilityRole: "tablist", style: [styles.tabs, { backgroundColor: colors.fill }], children: [tab(CheckoutTabEnum.MOBILE_MONEY, t('tab_wallet')), tab(CheckoutTabEnum.CARD, t('card'))] }), derived.selected ? (_jsx(SheetChosenMethod, { logo: logoOf(derived.selected), name: derived.selected.name, sub: derived.selected.network, onChange: actions.openPicker })) : (_jsx(MethodGrid, { cards: cards })), !onCard && _jsx(SheetPhoneField, { core: core, phoneRef: phoneRef }), !onCard && derived.selected?.flow === MethodFlowEnum.OTP && (_jsx(SheetCodeField, { core: core })), onCard && derived.selected?.flow === MethodFlowEnum.CARD_FIELDS && (_jsx(SheetCardFields, { core: core, bridge: cardFields }))] }));
|
|
69
|
+
}) })), _jsxs(View, { testID: "sheet-tabs", accessibilityRole: "tablist", style: [styles.tabs, { backgroundColor: colors.fill }], children: [tab(CheckoutTabEnum.MOBILE_MONEY, t('tab_wallet')), tab(CheckoutTabEnum.CARD, t('card'))] }), derived.selected ? (_jsx(SheetChosenMethod, { logo: logoOf(derived.selected), name: derived.selected.name, sub: derived.selected.network, onChange: actions.openPicker })) : (_jsx(MethodGrid, { cards: cards, missing: ui.methodMissing })), !onCard && _jsx(SheetPhoneField, { core: core, phoneRef: phoneRef }), !onCard && derived.selected?.flow === MethodFlowEnum.OTP && (_jsx(SheetCodeField, { core: core })), onCard && derived.selected?.flow === MethodFlowEnum.CARD_FIELDS && (_jsx(SheetCardFields, { core: core, bridge: cardFields }))] }));
|
|
65
70
|
}
|
|
66
71
|
const styles = StyleSheet.create({
|
|
67
72
|
form: { paddingHorizontal: 18, paddingTop: 14, paddingBottom: 8 },
|
|
@@ -80,13 +85,17 @@ const styles = StyleSheet.create({
|
|
|
80
85
|
paddingHorizontal: 12,
|
|
81
86
|
paddingVertical: 6,
|
|
82
87
|
},
|
|
83
|
-
tabs: {
|
|
88
|
+
tabs: {
|
|
89
|
+
flexDirection: 'row',
|
|
90
|
+
height: FIELD_HEIGHT,
|
|
91
|
+
borderRadius: FIELD_RADIUS,
|
|
92
|
+
padding: TABS_INSET,
|
|
93
|
+
},
|
|
84
94
|
tab: {
|
|
85
95
|
flex: 1,
|
|
86
96
|
alignItems: 'center',
|
|
87
97
|
justifyContent: 'center',
|
|
88
|
-
|
|
89
|
-
borderRadius: 9,
|
|
98
|
+
borderRadius: FIELD_RADIUS - TABS_INSET,
|
|
90
99
|
},
|
|
91
100
|
tabOn: {
|
|
92
101
|
shadowColor: 'black',
|
|
@@ -3,6 +3,7 @@ import { Image, Pressable, StyleSheet, View } from 'react-native';
|
|
|
3
3
|
import { useTranslations } from 'use-intl';
|
|
4
4
|
import { MEDIA_LINE } from "../checkout-core/models/checkout.constants.js";
|
|
5
5
|
import { useSheetTheme } from "../hooks/use-sheet-theme.js";
|
|
6
|
+
import { SHEET_BADGE_RADIUS, SHEET_BADGE_SIZE, } from "../models/sheet.constants.js";
|
|
6
7
|
import { FigtreeEnum } from "../models/sheet.enum.js";
|
|
7
8
|
import { SheetBone } from "./SheetBone.js";
|
|
8
9
|
import { SheetText } from "./SheetText.js";
|
|
@@ -14,8 +15,8 @@ export function SheetHeader({ view, loading, onClose, dragHandlers, }) {
|
|
|
14
15
|
const t = useTranslations('checkout');
|
|
15
16
|
const { colors, brand } = useSheetTheme();
|
|
16
17
|
const app = view?.app;
|
|
17
|
-
return (_jsxs(View, { ...dragHandlers, children: [_jsx(View, { style: [styles.grabber, { backgroundColor: colors.line3 }] }), _jsxs(View, { style: styles.row, children: [loading ? (_jsxs(_Fragment, { children: [_jsx(SheetBone, { width:
|
|
18
|
-
(app.logoUrl ? (_jsx(Image, { source: { uri: app.logoUrl }, accessibilityIgnoresInvertColors: true, style: styles.logo })) : (_jsx(View, { testID: "app-initial", style: [styles.initial, { backgroundColor: brand }], children: _jsx(SheetText, { weight: FigtreeEnum.EXTRA_BOLD, size:
|
|
18
|
+
return (_jsxs(View, { ...dragHandlers, children: [_jsx(View, { style: [styles.grabber, { backgroundColor: colors.line3 }] }), _jsxs(View, { style: styles.row, children: [loading ? (_jsxs(_Fragment, { children: [_jsx(SheetBone, { width: SHEET_BADGE_SIZE, height: SHEET_BADGE_SIZE, radius: SHEET_BADGE_RADIUS }), _jsx(View, { style: styles.name, children: _jsx(SheetBone, { width: 84, height: 14 }) })] })) : (app &&
|
|
19
|
+
(app.logoUrl ? (_jsx(Image, { testID: "app-logo", source: { uri: app.logoUrl }, accessibilityIgnoresInvertColors: true, style: styles.logo })) : (_jsx(View, { testID: "app-initial", style: [styles.initial, { backgroundColor: brand }], children: _jsx(SheetText, { weight: FigtreeEnum.EXTRA_BOLD, size: 18, color: "white", style: styles.centred, children: app.name.charAt(0) }) })))), !loading && (_jsx(SheetText, { weight: FigtreeEnum.BOLD, size: 16, numberOfLines: 1, accessibilityRole: "header", style: [styles.name, styles.centred], children: app?.name ?? 'Brissoto Pay' })), !loading && view?.test && (_jsx(View, { style: [styles.badge, { backgroundColor: colors.warnSoft }], children: _jsx(SheetText, { weight: FigtreeEnum.EXTRA_BOLD, size: 10.5, color: colors.warnInk, style: [styles.badgeText, styles.centred], children: t('test_badge') }) })), _jsxs(Pressable, { onPress: onClose, accessibilityRole: "button", accessibilityLabel: t('close'), hitSlop: 8, style: [styles.close, { backgroundColor: colors.fill }], children: [_jsx(View, { style: [styles.bar, styles.barUp, { backgroundColor: colors.ink2 }] }), _jsx(View, { style: [
|
|
19
20
|
styles.bar,
|
|
20
21
|
styles.barDown,
|
|
21
22
|
{ backgroundColor: colors.ink2 },
|
|
@@ -37,16 +38,16 @@ const styles = StyleSheet.create({
|
|
|
37
38
|
paddingTop: 10,
|
|
38
39
|
},
|
|
39
40
|
logo: {
|
|
40
|
-
width:
|
|
41
|
-
height:
|
|
42
|
-
borderRadius:
|
|
41
|
+
width: SHEET_BADGE_SIZE,
|
|
42
|
+
height: SHEET_BADGE_SIZE,
|
|
43
|
+
borderRadius: SHEET_BADGE_RADIUS,
|
|
43
44
|
borderWidth: 1,
|
|
44
45
|
borderColor: MEDIA_LINE,
|
|
45
46
|
},
|
|
46
47
|
initial: {
|
|
47
|
-
width:
|
|
48
|
-
height:
|
|
49
|
-
borderRadius:
|
|
48
|
+
width: SHEET_BADGE_SIZE,
|
|
49
|
+
height: SHEET_BADGE_SIZE,
|
|
50
|
+
borderRadius: SHEET_BADGE_RADIUS,
|
|
50
51
|
alignItems: 'center',
|
|
51
52
|
justifyContent: 'center',
|
|
52
53
|
},
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { StyleSheet, View } from 'react-native';
|
|
3
|
+
import { SHEET_BADGE_SIZE } from "../models/sheet.constants.js";
|
|
3
4
|
import { SheetIconEnum } from "../models/sheet.enum.js";
|
|
4
5
|
/** A sign in a disc, drawn with views: the sheet ships no icon font or SVG. */
|
|
5
|
-
export function SheetIcon({ kind, color, background, size =
|
|
6
|
+
export function SheetIcon({ kind, color, background, size = SHEET_BADGE_SIZE, }) {
|
|
6
7
|
const scale = size / 48;
|
|
7
8
|
const ink = { backgroundColor: color };
|
|
8
9
|
const line = { borderColor: color };
|
|
@@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
3
3
|
import { Keyboard, Platform, ScrollView, StyleSheet, View } from 'react-native';
|
|
4
4
|
import { useTranslations } from 'use-intl';
|
|
5
5
|
import { useSheetTheme } from "../hooks/use-sheet-theme.js";
|
|
6
|
+
import { SHEET_FOOT_GAP, SHEET_FOOT_TOP } from "../models/sheet.constants.js";
|
|
6
7
|
import { FigtreeEnum } from "../models/sheet.enum.js";
|
|
7
8
|
import { SheetText } from "./SheetText.js";
|
|
8
9
|
/**
|
|
@@ -39,10 +40,11 @@ const styles = StyleSheet.create({
|
|
|
39
40
|
foot: {
|
|
40
41
|
borderTopWidth: 1,
|
|
41
42
|
paddingHorizontal: 18,
|
|
42
|
-
paddingTop:
|
|
43
|
+
paddingTop: SHEET_FOOT_TOP,
|
|
43
44
|
paddingBottom: 28,
|
|
44
|
-
gap:
|
|
45
|
+
gap: SHEET_FOOT_GAP,
|
|
45
46
|
},
|
|
46
47
|
footOnKeyboard: { paddingBottom: 12 },
|
|
47
|
-
|
|
48
|
+
// Android's extra font padding would push it off the middle.
|
|
49
|
+
secure: { textAlign: 'center', includeFontPadding: false },
|
|
48
50
|
});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { MethodCardProps } from '../models/sheet.interface.js';
|
|
2
2
|
/** One method of the grid: logo and radio dot, then its name and network, or why it is greyed. Picked, it becomes the chosen row. */
|
|
3
|
-
export declare function SheetMethodCard({ logo, name, sub, muted, onSelect, }: MethodCardProps): import("react").JSX.Element;
|
|
3
|
+
export declare function SheetMethodCard({ logo, name, sub, muted, missing, onSelect, }: MethodCardProps): import("react").JSX.Element;
|
|
@@ -5,12 +5,25 @@ import { useSheetTheme } from "../hooks/use-sheet-theme.js";
|
|
|
5
5
|
import { FigtreeEnum } from "../models/sheet.enum.js";
|
|
6
6
|
import { SheetLogo } from "./SheetLogo.js";
|
|
7
7
|
import { SheetText } from "./SheetText.js";
|
|
8
|
+
import { FIELD_RADIUS } from "../models/sheet.constants.js";
|
|
8
9
|
/** One method of the grid: logo and radio dot, then its name and network, or why it is greyed. Picked, it becomes the chosen row. */
|
|
9
|
-
export function SheetMethodCard({ logo, name, sub, muted, onSelect, }) {
|
|
10
|
+
export function SheetMethodCard({ logo, name, sub, muted, missing = false, onSelect, }) {
|
|
10
11
|
const { colors } = useSheetTheme();
|
|
11
|
-
|
|
12
|
+
const red = missing && !muted;
|
|
13
|
+
return (_jsxs(TouchableOpacity, { onPress: onSelect, disabled: muted, activeOpacity: 0.7, accessibilityRole: "radio", accessibilityLabel: `${name}, ${sub}`, accessibilityState: { checked: false, disabled: muted }, style: [
|
|
14
|
+
styles.card,
|
|
15
|
+
red
|
|
16
|
+
? [styles.cardMissing, { borderColor: colors.danger }]
|
|
17
|
+
: { borderColor: colors.line2 },
|
|
18
|
+
], children: [_jsxs(View, { style: styles.top, children: [_jsx(SheetLogo, { logo: logo, size: LOGO_SIZE.ROW, muted: muted }), _jsx(View, { style: [
|
|
12
19
|
styles.radio,
|
|
13
|
-
{
|
|
20
|
+
{
|
|
21
|
+
borderColor: muted
|
|
22
|
+
? colors.line2
|
|
23
|
+
: red
|
|
24
|
+
? colors.danger
|
|
25
|
+
: colors.line4,
|
|
26
|
+
},
|
|
14
27
|
] })] }), _jsx(SheetText, { weight: FigtreeEnum.BOLD, size: 15, numberOfLines: 1, color: muted ? colors.muted : colors.ink, children: name }), _jsx(SheetText, { size: 12.5, numberOfLines: 1, color: muted ? colors.warnInk : colors.muted, children: sub })] }));
|
|
15
28
|
}
|
|
16
29
|
const styles = StyleSheet.create({
|
|
@@ -18,10 +31,12 @@ const styles = StyleSheet.create({
|
|
|
18
31
|
flex: 1,
|
|
19
32
|
minWidth: 0,
|
|
20
33
|
borderWidth: 1,
|
|
21
|
-
borderRadius:
|
|
34
|
+
borderRadius: FIELD_RADIUS,
|
|
22
35
|
padding: 12,
|
|
23
36
|
gap: 2,
|
|
24
37
|
},
|
|
38
|
+
// 1.5 like the web's ring; the padding gives back the half point.
|
|
39
|
+
cardMissing: { borderWidth: 1.5, padding: 11.5 },
|
|
25
40
|
top: {
|
|
26
41
|
flexDirection: 'row',
|
|
27
42
|
alignItems: 'flex-start',
|
|
@@ -6,6 +6,7 @@ import { FigtreeEnum } from "../models/sheet.enum.js";
|
|
|
6
6
|
import { flagOf } from "../models/sheet.utils.js";
|
|
7
7
|
import { SheetLogo } from "./SheetLogo.js";
|
|
8
8
|
import { SheetInput, SheetText } from "./SheetText.js";
|
|
9
|
+
import { FIELD_HEIGHT, FIELD_RADIUS } from "../models/sheet.constants.js";
|
|
9
10
|
/** The Mobile Money number on the phone keyboard: the country's dial code, the number grouped as typed, its check. */
|
|
10
11
|
export function SheetPhoneField({ core, phoneRef }) {
|
|
11
12
|
const t = useTranslations('checkout');
|
|
@@ -32,9 +33,9 @@ const styles = StyleSheet.create({
|
|
|
32
33
|
field: {
|
|
33
34
|
flexDirection: 'row',
|
|
34
35
|
alignItems: 'center',
|
|
35
|
-
height:
|
|
36
|
+
height: FIELD_HEIGHT,
|
|
36
37
|
borderWidth: 1,
|
|
37
|
-
borderRadius:
|
|
38
|
+
borderRadius: FIELD_RADIUS,
|
|
38
39
|
overflow: 'hidden',
|
|
39
40
|
},
|
|
40
41
|
dial: {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { Animated, Easing } from 'react-native';
|
|
3
|
+
import { SHAKE_MS, SHAKE_OFFSETS } from "../models/sheet.constants.js";
|
|
4
|
+
/** A side-to-side shake each time `count` grows past 0: the offset to put in a `translateX`. */
|
|
5
|
+
export function useShake(count) {
|
|
6
|
+
const [offset] = useState(() => new Animated.Value(0));
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (count === 0)
|
|
9
|
+
return;
|
|
10
|
+
Animated.sequence(SHAKE_OFFSETS.map((toValue) => Animated.timing(offset, {
|
|
11
|
+
toValue,
|
|
12
|
+
duration: SHAKE_MS / SHAKE_OFFSETS.length,
|
|
13
|
+
easing: Easing.out(Easing.quad),
|
|
14
|
+
useNativeDriver: true,
|
|
15
|
+
}))).start();
|
|
16
|
+
}, [count, offset]);
|
|
17
|
+
return offset;
|
|
18
|
+
}
|
|
@@ -39,3 +39,24 @@ export declare const SHEET_FLICK_SPEED = 0.8;
|
|
|
39
39
|
export declare const SHEET_RESIZE: LayoutAnimationConfig;
|
|
40
40
|
/** A paragraph's line height, as a share of its size: the web checkout's 1.5, a touch tighter for Figtree on phones. */
|
|
41
41
|
export declare const PARAGRAPH_LINE_HEIGHT = 1.45;
|
|
42
|
+
/** A field the payer still has to fill, shaken once when Pay is pressed: its offsets, then back. */
|
|
43
|
+
export declare const SHAKE_MS = 300;
|
|
44
|
+
export declare const SHAKE_OFFSETS: number[];
|
|
45
|
+
/** The form's one row height and one corner, Pay's: the tabs, the method picked, the fields and the buttons line up. */
|
|
46
|
+
export declare const FIELD_HEIGHT = 50;
|
|
47
|
+
export declare const FIELD_RADIUS = 14;
|
|
48
|
+
/** The tabs' inset around the tab on: its corner follows the bar's. */
|
|
49
|
+
export declare const TABS_INSET = 3;
|
|
50
|
+
/**
|
|
51
|
+
* Over and under the secured line, between the foot's separator and Pay.
|
|
52
|
+
* Figtree leaves 2pt more room over its letters than under them at this
|
|
53
|
+
* size, so the space over is 2 less: the letters sit in the middle.
|
|
54
|
+
*/
|
|
55
|
+
export declare const SHEET_FOOT_TOP = 11;
|
|
56
|
+
export declare const SHEET_FOOT_GAP = 13;
|
|
57
|
+
/**
|
|
58
|
+
* The app's logo in the header and a screen's disc under it (failed, closed)
|
|
59
|
+
* share one size: one under the other, their centres line up.
|
|
60
|
+
*/
|
|
61
|
+
export declare const SHEET_BADGE_SIZE = 44;
|
|
62
|
+
export declare const SHEET_BADGE_RADIUS = 12;
|
|
@@ -78,3 +78,24 @@ export const SHEET_RESIZE = {
|
|
|
78
78
|
};
|
|
79
79
|
/** A paragraph's line height, as a share of its size: the web checkout's 1.5, a touch tighter for Figtree on phones. */
|
|
80
80
|
export const PARAGRAPH_LINE_HEIGHT = 1.45;
|
|
81
|
+
/** A field the payer still has to fill, shaken once when Pay is pressed: its offsets, then back. */
|
|
82
|
+
export const SHAKE_MS = 300;
|
|
83
|
+
export const SHAKE_OFFSETS = [-6, 6, -3, 0];
|
|
84
|
+
/** The form's one row height and one corner, Pay's: the tabs, the method picked, the fields and the buttons line up. */
|
|
85
|
+
export const FIELD_HEIGHT = 50;
|
|
86
|
+
export const FIELD_RADIUS = 14;
|
|
87
|
+
/** The tabs' inset around the tab on: its corner follows the bar's. */
|
|
88
|
+
export const TABS_INSET = 3;
|
|
89
|
+
/**
|
|
90
|
+
* Over and under the secured line, between the foot's separator and Pay.
|
|
91
|
+
* Figtree leaves 2pt more room over its letters than under them at this
|
|
92
|
+
* size, so the space over is 2 less: the letters sit in the middle.
|
|
93
|
+
*/
|
|
94
|
+
export const SHEET_FOOT_TOP = 11;
|
|
95
|
+
export const SHEET_FOOT_GAP = 13;
|
|
96
|
+
/**
|
|
97
|
+
* The app's logo in the header and a screen's disc under it (failed, closed)
|
|
98
|
+
* share one size: one under the other, their centres line up.
|
|
99
|
+
*/
|
|
100
|
+
export const SHEET_BADGE_SIZE = 44;
|
|
101
|
+
export const SHEET_BADGE_RADIUS = 12;
|
|
@@ -179,6 +179,8 @@ export interface MethodCardProps {
|
|
|
179
179
|
sub: string;
|
|
180
180
|
/** Greyed and not pressable: in maintenance, or not offered yet. */
|
|
181
181
|
muted: boolean;
|
|
182
|
+
/** Pay was pressed with no method picked: the card is ringed in red. */
|
|
183
|
+
missing?: boolean;
|
|
182
184
|
onSelect?: () => void;
|
|
183
185
|
}
|
|
184
186
|
export interface ChosenMethodProps {
|
|
@@ -194,6 +196,8 @@ export interface MethodGridCard extends MethodCardProps {
|
|
|
194
196
|
}
|
|
195
197
|
export interface MethodGridProps {
|
|
196
198
|
cards: Array<MethodGridCard>;
|
|
199
|
+
/** Pay presses with no method picked: red, and a shake per press. */
|
|
200
|
+
missing: number;
|
|
197
201
|
}
|
|
198
202
|
export interface SheetIconProps {
|
|
199
203
|
kind: SheetIconEnum;
|