diff --git a/.depcheckrc.yml b/.depcheckrc.yml
index 5a506bc7a51..e8fe7a446ec 100644
--- a/.depcheckrc.yml
+++ b/.depcheckrc.yml
@@ -4,6 +4,8 @@ ignores:
- 'webpack-cli'
- '@react-native-community/datetimepicker'
- '@react-native-community/slider'
+ - 'patch-package'
+ - '@lavamoat/allow-scripts'
# This is used on the patch for TokenRatesController of Assets controllers, for we to be able to use the last version of it
- cockatiel
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f13a32ae47f..64bd5bea831 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,16 +5,22 @@ on:
pull_request:
merge_group:
types: [checks_requested]
-
+
jobs:
- setup:
- runs-on: ubuntu-20.04
+ check-diff:
+ runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: yarn
+ - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 #v1
+ with:
+ ruby-version: '3.1.5'
+ bundler-cache: true
+ env:
+ BUNDLE_GEMFILE: ios/Gemfile
- name: Determine whether the current PR is a draft
id: set-is-draft
if: github.event_name == 'pull_request' && github.event.pull_request.number
@@ -27,7 +33,7 @@ jobs:
run: printf '%s\n\n%s' '@metamask:registry=https://npm.pkg.github.com' "//npm.pkg.github.com/:_authToken=${PACKAGE_READ_TOKEN}" > .npmrc
env:
PACKAGE_READ_TOKEN: ${{ secrets.PACKAGE_READ_TOKEN }}
- - run: yarn setup --node
+ - run: yarn setup
- name: Require clean working directory
shell: bash
run: |
@@ -39,7 +45,6 @@ jobs:
fi
dedupe:
runs-on: ubuntu-20.04
- needs: setup
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
@@ -58,7 +63,6 @@ jobs:
fi
scripts:
runs-on: ubuntu-20.04
- needs: setup
strategy:
matrix:
scripts:
@@ -86,7 +90,6 @@ jobs:
fi
unit-tests:
runs-on: ubuntu-20.04
- needs: setup
strategy:
matrix:
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
@@ -156,7 +159,6 @@ jobs:
js-bundle-size-check:
runs-on: ubuntu-20.04
- needs: setup
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
@@ -220,6 +222,10 @@ jobs:
uses: actions/checkout@v3
- name: SonarCloud Quality Gate Status
id: sonar-status
+ env:
+ REPO: ${{ github.repository }}
+ ISSUE_NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Skip step if event is a PR
if [[ "${{ github.event_name }}" != "pull_request" ]]; then
@@ -227,31 +233,40 @@ jobs:
exit 0
fi
- sleep 30
+ # Bypass step if skip-sonar-cloud label is found
+ LABEL=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
+ "https://api.github.com/repos/$REPO/issues/$ISSUE_NUMBER/labels" | \
+ jq -r '.[] | select(.name=="skip-sonar-cloud") | .name')
- PROJECT_KEY="metamask-mobile"
- PR_NUMBER="${{ github.event.pull_request.number }}"
- SONAR_TOKEN="${{ secrets.SONAR_TOKEN }}"
+ if [[ "$LABEL" == "skip-sonar-cloud" ]]; then
+ echo "skip-sonar-cloud label found. Skipping SonarCloud Quality Gate check."
+ else
+ sleep 30
- if [ -z "$PR_NUMBER" ]; then
- echo "No pull request number found. Failing the check."
- exit 1
- fi
+ PROJECT_KEY="metamask-mobile"
+ PR_NUMBER="${{ github.event.pull_request.number }}"
+ SONAR_TOKEN="${{ secrets.SONAR_TOKEN }}"
- RESPONSE=$(curl -s -u "$SONAR_TOKEN:" \
- "https://sonarcloud.io/api/qualitygates/project_status?projectKey=$PROJECT_KEY&pullRequest=$PR_NUMBER")
- echo "SonarCloud API Response: $RESPONSE"
+ if [ -z "$PR_NUMBER" ]; then
+ echo "No pull request number found. Failing the check."
+ exit 1
+ fi
- STATUS=$(echo "$RESPONSE" | jq -r '.projectStatus.status')
+ RESPONSE=$(curl -s -u "$SONAR_TOKEN:" \
+ "https://sonarcloud.io/api/qualitygates/project_status?projectKey=$PROJECT_KEY&pullRequest=$PR_NUMBER")
+ echo "SonarCloud API Response: $RESPONSE"
- if [[ "$STATUS" == "ERROR" ]]; then
- echo "Quality Gate failed."
- exit 1
- elif [[ "$STATUS" == "OK" ]]; then
- echo "Quality Gate passed."
- else
- echo "Could not determine Quality Gate status."
- exit 1
+ STATUS=$(echo "$RESPONSE" | jq -r '.projectStatus.status')
+
+ if [[ "$STATUS" == "ERROR" ]]; then
+ echo "Quality Gate failed."
+ exit 1
+ elif [[ "$STATUS" == "OK" ]]; then
+ echo "Quality Gate passed."
+ else
+ echo "Could not determine Quality Gate status."
+ exit 1
+ fi
fi
check-workflows:
name: Check workflows
@@ -270,12 +285,11 @@ jobs:
runs-on: ubuntu-20.04
needs:
[
- setup,
+ check-diff,
dedupe,
scripts,
unit-tests,
check-workflows,
- sonar-cloud,
js-bundle-size-check,
sonar-cloud-quality-gate-status,
]
diff --git a/.github/workflows/crowdin_action.yml b/.github/workflows/crowdin_action.yml
index 2582db7e5a7..eba1ac69154 100644
--- a/.github/workflows/crowdin_action.yml
+++ b/.github/workflows/crowdin_action.yml
@@ -10,8 +10,6 @@ on:
- main
schedule:
- cron: "0 */12 * * *"
- merge_group:
- types: [checks_requested]
jobs:
synchronize-with-crowdin:
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index f577c5ba755..a95001cb1c6 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -4,21 +4,20 @@ on:
branches: main
pull_request:
-
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
- uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3
+ uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3
+ uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3
- uses: actions/checkout@v3
- name: Build and load
- uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
+ uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
with:
context: .
file: scripts/docker/Dockerfile
diff --git a/.storybook/main.js b/.storybook/main.js
index 64a3f3cf602..fd51e44c137 100644
--- a/.storybook/main.js
+++ b/.storybook/main.js
@@ -3,6 +3,7 @@ module.exports = {
'../app/component-library/components/**/*.stories.?(ts|tsx|js|jsx)',
'../app/component-library/base-components/**/*.stories.?(ts|tsx|js|jsx)',
'../app/component-library/components-temp/TagColored/**/*.stories.?(ts|tsx|js|jsx)',
+ '../app/component-library/components-temp/KeyValueRow/**/*.stories.?(ts|tsx|js|jsx)',
],
addons: ['@storybook/addon-ondevice-controls'],
framework: '@storybook/react-native',
diff --git a/.storybook/storybook.requires.js b/.storybook/storybook.requires.js
index ae2e8f2d55b..3b26602e64d 100644
--- a/.storybook/storybook.requires.js
+++ b/.storybook/storybook.requires.js
@@ -30,6 +30,13 @@ global.STORIES = [
importPathMatcher:
"^\\.[\\\\/](?:app\\/component-library\\/components-temp\\/TagColored(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?!\\.)(?=.)[^/]*?\\.stories\\.(?:ts|tsx|js|jsx)?)$",
},
+ {
+ titlePrefix: "",
+ directory: "./app/component-library/components-temp/KeyValueRow",
+ files: "**/*.stories.?(ts|tsx|js|jsx)",
+ importPathMatcher:
+ "^\\.[\\\\/](?:app\\/component-library\\/components-temp\\/KeyValueRow(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?!\\.)(?=.)[^/]*?\\.stories\\.(?:ts|tsx|js|jsx)?)$",
+ },
];
import "@storybook/addon-ondevice-controls/register";
@@ -115,8 +122,7 @@ const getStories = () => {
"./app/component-library/components/Toast/Toast.stories.tsx": require("../app/component-library/components/Toast/Toast.stories.tsx"),
"./app/component-library/base-components/TagBase/TagBase.stories.tsx": require("../app/component-library/base-components/TagBase/TagBase.stories.tsx"),
"./app/component-library/components-temp/TagColored/TagColored.stories.tsx": require("../app/component-library/components-temp/TagColored/TagColored.stories.tsx"),
- "./app/components/Views/AssetDetails/AssetDetailsActions/AssetDetailsActions.stories.tsx": require("../app/components/Views/AssetDetails/AssetDetailsActions/AssetDetailsActions.stories.tsx"),
-
+ "./app/component-library/components-temp/KeyValueRow/KeyValueRow.stories.tsx": require("../app/component-library/components-temp/KeyValueRow/KeyValueRow.stories.tsx"),
};
};
diff --git a/Gemfile b/Gemfile
deleted file mode 100644
index 3c02108f3ce..00000000000
--- a/Gemfile
+++ /dev/null
@@ -1,10 +0,0 @@
-source 'https://rubygems.org'
-
-# Recommended to use http://rbenv.org/ to install and use this version
-ruby '>= 3.1.5'
-
-# Allow minor version updates up to but excluding 2.0.0
-gem 'cocoapods', '~> 1.12'
-
-# Allow all version updates up to but excluding 7.1.0
-gem 'activesupport', '>= 6.1.7.3', '< 7.1.0'
diff --git a/app/component-library/components-temp/KeyValueRow/KeyValueLabel/KeyValueLabel.styles.tsx b/app/component-library/components-temp/KeyValueRow/KeyValueLabel/KeyValueLabel.styles.tsx
new file mode 100644
index 00000000000..ae5c4860b71
--- /dev/null
+++ b/app/component-library/components-temp/KeyValueRow/KeyValueLabel/KeyValueLabel.styles.tsx
@@ -0,0 +1,11 @@
+import { StyleSheet } from 'react-native';
+
+const styleSheet = () =>
+ StyleSheet.create({
+ labelContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ });
+
+export default styleSheet;
diff --git a/app/component-library/components-temp/KeyValueRow/KeyValueLabel/KeyValueLabel.tsx b/app/component-library/components-temp/KeyValueRow/KeyValueLabel/KeyValueLabel.tsx
new file mode 100644
index 00000000000..aefa71eecbc
--- /dev/null
+++ b/app/component-library/components-temp/KeyValueRow/KeyValueLabel/KeyValueLabel.tsx
@@ -0,0 +1,64 @@
+import ButtonIcon from '../../../../component-library/components/Buttons/ButtonIcon';
+import Label from '../../../../component-library/components/Form/Label';
+import {
+ IconColor,
+ IconName,
+} from '../../../../component-library/components/Icons/Icon';
+import {
+ TextVariant,
+ TextColor,
+} from '../../../../component-library/components/Texts/Text';
+import { useStyles } from '../../../../component-library/hooks';
+import useTooltipModal from '../../../../components/hooks/useTooltipModal';
+import React from 'react';
+import { View } from 'react-native';
+import { KeyValueRowLabelProps, TooltipSizes } from '../KeyValueRow.types';
+import styleSheet from './KeyValueLabel.styles';
+
+/**
+ * A label and tooltip component.
+ *
+ * @param {Object} props - Component props.
+ * @param {TextVariant} [props.variant] - Optional text variant. Defaults to TextVariant.BodyMDMedium.
+ * @param {TextVariant} [props.color] - Optional text color. Defaults to TextColor.Default.
+ * @param {TextVariant} [props.tooltip] - Optional tooltip to render to the right of the label text.
+ *
+ * @returns {JSX.Element} The rendered KeyValueRowLabel component.
+ */
+const KeyValueRowLabel = ({
+ label,
+ variant = TextVariant.BodyMDMedium,
+ color = TextColor.Default,
+ tooltip,
+}: KeyValueRowLabelProps) => {
+ const { styles } = useStyles(styleSheet, {});
+
+ const { openTooltipModal } = useTooltipModal();
+
+ const hasTooltip = tooltip?.title && tooltip?.text;
+
+ const onNavigateToTooltipModal = () => {
+ if (!hasTooltip) return;
+ openTooltipModal(tooltip.title, tooltip.text);
+ };
+
+ return (
+
+
+ {hasTooltip && (
+
+ )}
+
+ );
+};
+
+export default KeyValueRowLabel;
diff --git a/app/component-library/components-temp/KeyValueRow/KeyValueRoot/KeyValueRoot.styles.tsx b/app/component-library/components-temp/KeyValueRow/KeyValueRoot/KeyValueRoot.styles.tsx
new file mode 100644
index 00000000000..a3de4af351f
--- /dev/null
+++ b/app/component-library/components-temp/KeyValueRow/KeyValueRoot/KeyValueRoot.styles.tsx
@@ -0,0 +1,13 @@
+import { StyleSheet } from 'react-native';
+
+const styleSheet = () =>
+ StyleSheet.create({
+ rootContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ overflow: 'hidden',
+ },
+ });
+
+export default styleSheet;
diff --git a/app/component-library/components-temp/KeyValueRow/KeyValueRoot/KeyValueRoot.tsx b/app/component-library/components-temp/KeyValueRow/KeyValueRoot/KeyValueRoot.tsx
new file mode 100644
index 00000000000..e308488648a
--- /dev/null
+++ b/app/component-library/components-temp/KeyValueRow/KeyValueRoot/KeyValueRoot.tsx
@@ -0,0 +1,37 @@
+import { useStyles } from '../../../hooks';
+import React from 'react';
+import { View } from 'react-native';
+import { KeyValueRowRootProps } from '../KeyValueRow.types';
+import styleSheet from './KeyValueRoot.styles';
+
+/**
+ * The main container for the KeyValueRow component.
+ * When creating custom KeyValueRow components, this must be the outermost component wrapping the two components.
+ *
+ * e.g.
+ * ```
+ *
+ *
+ *
+ *
+ * ```
+ *
+ * @component
+ * @param {Object} props - Component props.
+ * @param {Array} props.children - The two children.
+ * @param {ViewProps} [props.style] - Optional styling
+ *
+ * @returns {JSX.Element} The rendered Root component.
+ */
+const KeyValueRowRoot = ({
+ children,
+ style: customStyles,
+}: KeyValueRowRootProps) => {
+ const { styles: defaultStyles } = useStyles(styleSheet, {});
+
+ const styles = [defaultStyles.rootContainer, customStyles];
+
+ return {children};
+};
+
+export default KeyValueRowRoot;
diff --git a/app/component-library/components-temp/KeyValueRow/KeyValueRow.stories.tsx b/app/component-library/components-temp/KeyValueRow/KeyValueRow.stories.tsx
new file mode 100644
index 00000000000..e5a440b3276
--- /dev/null
+++ b/app/component-library/components-temp/KeyValueRow/KeyValueRow.stories.tsx
@@ -0,0 +1,97 @@
+import React from 'react';
+import { withNavigation } from '../../../../storybook/decorators';
+import { View, StyleSheet } from 'react-native';
+import KeyValueRowComponent, {
+ KeyValueRowFieldIconSides,
+ TooltipSizes,
+} from './index';
+import Text, { TextColor, TextVariant } from '../../components/Texts/Text';
+import Title from '../../../components/Base/Title';
+import { IconColor, IconName, IconSize } from '../../components/Icons/Icon';
+
+const KeyValueRowMeta = {
+ title: 'Components Temp / KeyValueRow',
+ component: KeyValueRowComponent,
+ decorators: [withNavigation],
+};
+
+export default KeyValueRowMeta;
+
+const styles = StyleSheet.create({
+ container: {
+ padding: 16,
+ },
+ listItem: {
+ marginVertical: 16,
+ gap: 16,
+ },
+});
+
+export const KeyValueRow = {
+ render: () => (
+
+ KeyValueRow Component
+
+ Prebuilt component displayed below but KeyValueRow stubs are available
+ to create new KeyValueRow variants.
+
+
+
+
+
+
+
+
+ ),
+};
diff --git a/app/component-library/components-temp/KeyValueRow/KeyValueRow.styles.ts b/app/component-library/components-temp/KeyValueRow/KeyValueRow.styles.ts
new file mode 100644
index 00000000000..33999c44819
--- /dev/null
+++ b/app/component-library/components-temp/KeyValueRow/KeyValueRow.styles.ts
@@ -0,0 +1,12 @@
+import { StyleSheet } from 'react-native';
+
+const styleSheet = () =>
+ StyleSheet.create({
+ flexRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 8,
+ },
+ });
+
+export default styleSheet;
diff --git a/app/component-library/components-temp/KeyValueRow/KeyValueRow.test.tsx b/app/component-library/components-temp/KeyValueRow/KeyValueRow.test.tsx
new file mode 100644
index 00000000000..a9b00ece7e8
--- /dev/null
+++ b/app/component-library/components-temp/KeyValueRow/KeyValueRow.test.tsx
@@ -0,0 +1,104 @@
+import React from 'react';
+import { render } from '@testing-library/react-native';
+import KeyValueRow from './KeyValueRow';
+import { IconName } from '../../components/Icons/Icon';
+
+jest.mock('@react-navigation/native', () => {
+ const actualNav = jest.requireActual('@react-navigation/native');
+ return {
+ ...actualNav,
+ useNavigation: () => ({
+ navigate: jest.fn(),
+ }),
+ };
+});
+
+describe('KeyValueRow', () => {
+ describe('Prebuilt Component', () => {
+ describe('KeyValueRow', () => {
+ it('should render when there is only text', () => {
+ const { toJSON } = render(
+ ,
+ );
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+
+ it('should render text with tooltips', () => {
+ const { toJSON } = render(
+ ,
+ );
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+
+ it('should render text with icons', () => {
+ const { toJSON } = render(
+ ,
+ );
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+
+ it('should render text with icons and tooltips', () => {
+ const { toJSON } = render(
+ ,
+ );
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+ });
+ });
+});
diff --git a/app/component-library/components-temp/KeyValueRow/KeyValueRow.tsx b/app/component-library/components-temp/KeyValueRow/KeyValueRow.tsx
new file mode 100644
index 00000000000..18c6300b1cf
--- /dev/null
+++ b/app/component-library/components-temp/KeyValueRow/KeyValueRow.tsx
@@ -0,0 +1,93 @@
+import { useStyles } from '../../hooks';
+import React from 'react';
+import stylesheet from './KeyValueRow.styles';
+import {
+ KeyValueRowProps,
+ KeyValueRowFieldIconSides,
+ KeyValueRowSectionAlignments,
+} from './KeyValueRow.types';
+import Icon from '../../components/Icons/Icon';
+import { View } from 'react-native';
+import { areKeyValueRowPropsEqual } from './KeyValueRow.utils';
+import KeyValueSection from './KeyValueSection/KeyValueSection';
+import KeyValueRowLabel from './KeyValueLabel/KeyValueLabel';
+import KeyValueRowRoot from './KeyValueRoot/KeyValueRoot';
+
+/**
+ * Prebuilt convenience component to format and render a key/value KeyValueRowLabel pair.
+ * The KeyValueRowLabel component has props to display a tooltip and icon.
+ *
+ * Examples are in the Storybook: [StorybookLink](./KeyValueRow.stories.tsx)
+ *
+ * @param {Object} props - Component props
+ * @param {KeyValueRowField} props.field - Represents the left side of the key value row pair
+ * @param {KeyValueRowField} props.value - Represents the right side of the key value row pair
+ * @param {ViewProps} [props.style] - Optional styling
+ *
+ * @returns {JSX.Element} The rendered KeyValueRow component.
+ */
+const KeyValueRow = React.memo(({ field, value, style }: KeyValueRowProps) => {
+ const { styles } = useStyles(stylesheet, {});
+
+ // Field (left side)
+ const fieldIcon = field?.icon;
+ const shouldShowFieldIcon = fieldIcon?.name;
+
+ // Value (right side)
+ const valueIcon = value?.icon;
+ const shouldShowValueIcon = valueIcon?.name;
+
+ return (
+
+
+
+ {shouldShowFieldIcon &&
+ (fieldIcon.side === KeyValueRowFieldIconSides.LEFT ||
+ fieldIcon.side === KeyValueRowFieldIconSides.BOTH ||
+ !fieldIcon?.side) && }
+
+ {shouldShowFieldIcon &&
+ (fieldIcon?.side === KeyValueRowFieldIconSides.RIGHT ||
+ fieldIcon?.side === KeyValueRowFieldIconSides.BOTH) && (
+
+ )}
+
+
+
+
+ {shouldShowValueIcon &&
+ (valueIcon?.side === KeyValueRowFieldIconSides.LEFT ||
+ valueIcon?.side === KeyValueRowFieldIconSides.BOTH ||
+ !valueIcon?.side) && }
+
+ {shouldShowValueIcon &&
+ (valueIcon?.side === KeyValueRowFieldIconSides.RIGHT ||
+ valueIcon?.side === KeyValueRowFieldIconSides.BOTH) && (
+
+ )}
+
+
+
+ );
+}, areKeyValueRowPropsEqual);
+
+/**
+ * Exported sub-components to provide a base for new KeyValueRow variants.
+ */
+export const KeyValueRowStubs = {
+ Root: KeyValueRowRoot,
+ Section: KeyValueSection,
+ Label: KeyValueRowLabel,
+};
+
+export default KeyValueRow;
diff --git a/app/component-library/components-temp/KeyValueRow/KeyValueRow.types.ts b/app/component-library/components-temp/KeyValueRow/KeyValueRow.types.ts
new file mode 100644
index 00000000000..0e9c864d309
--- /dev/null
+++ b/app/component-library/components-temp/KeyValueRow/KeyValueRow.types.ts
@@ -0,0 +1,157 @@
+import {
+ IconProps,
+ IconSize,
+} from '../../../component-library/components/Icons/Icon';
+import { ButtonIconSizes } from '../../components/Buttons/ButtonIcon';
+import { ReactNode } from 'react';
+import { TextProps } from '../../components/Texts/Text/Text.types';
+import { ViewProps } from 'react-native';
+
+/**
+ * The optional tooltip tha can be displayed within a KeyValueRowField or KeyValueRowLabel.
+ *
+ * @see KeyValueRowField
+ * @see KeyValueRowLabel
+ */
+interface KeyValueRowTooltip {
+ /**
+ * The title displayed at the top of the tooltip.
+ */
+ title: string;
+ /**
+ * The text displayed within the tooltip body.
+ */
+ text: string;
+ /**
+ * Optional size of the tooltip icon.
+ * @default TooltipSizes.Md
+ */
+ size?: ButtonIconSizes;
+}
+
+/**
+ * Used to position icon in KeyValueRowField
+ *
+ * @see KeyValueRowField
+ */
+export enum KeyValueRowFieldIconSides {
+ LEFT = 'LEFT',
+ RIGHT = 'RIGHT',
+ BOTH = 'BOTH',
+}
+
+/**
+ * Represents a field displayed within KeyValueRowProps.
+ *
+ * @see KeyValueRowProps
+ */
+interface KeyValueRowField {
+ /**
+ * The text to display.
+ */
+ text: string;
+ /**
+ * Optional text variant.
+ * @default TextVariant.BodyMDMedium
+ */
+ variant?: TextProps['variant'];
+ /**
+ * Optional text color.
+ * @default TextColor.Default
+ */
+ color?: TextProps['color'];
+ /**
+ * Optional icon to display. If undefined, no icon is displayed.
+ */
+ icon?: IconProps & { side?: KeyValueRowFieldIconSides };
+ /**
+ * Optional tooltip to display. If undefined, no tooltip is displayed.
+ */
+ tooltip?: KeyValueRowTooltip;
+}
+
+export const IconSizes = IconSize;
+
+export const TooltipSizes = ButtonIconSizes;
+
+/**
+ * The KeyValueRowLabel prop interface.
+ *
+ * @see KeyValueRowLabel in ./KeyValueRow.tsx
+ */
+export interface KeyValueRowLabelProps {
+ /**
+ * Text to display.
+ */
+ label: string;
+ /**
+ * Optional text variant.
+ * @default TextVariant.BodyMDMedium
+ */
+ variant?: TextProps['variant'];
+ /**
+ * Optional text color.
+ * @default TextColor.Default
+ */
+ color?: TextProps['color'];
+ /**
+ * Optional tooltip. If undefined, the tooltip won't be displayed.
+ */
+ tooltip?: KeyValueRowTooltip;
+}
+
+/**
+ * Represents the main container for the KeyValueRow component.
+ */
+export interface KeyValueRowRootProps {
+ /**
+ * Must have exactly two children. Adding more will lead to an undesired outcome.
+ */
+ children: [ReactNode, ReactNode];
+ /**
+ * Optional styles. Useful for controlling padding and margins.
+ */
+ style?: ViewProps['style'];
+}
+
+/**
+ * Represents the valid KeyValueSection alignments.
+ */
+export enum KeyValueRowSectionAlignments {
+ LEFT = 'flex-start',
+ RIGHT = 'flex-end',
+}
+
+/**
+ * The KeyValueSection component props.
+ */
+export interface KeyValueSectionProps {
+ /**
+ * Child components.
+ */
+ children: ReactNode;
+ /**
+ * Optional content alignment.
+ * @default KeyValueRowSectionAlignments.RIGHT
+ */
+ align?: KeyValueRowSectionAlignments;
+}
+
+/**
+ * The KeyValueRow component props.
+ */
+export interface KeyValueRowProps {
+ /**
+ * The "key" portion of the KeyValueRow (left side).
+ * Using the variable name field because key is reserved.
+ */
+ field: KeyValueRowField;
+ /**
+ * The "value" portion of the KeyValueRow (right side).
+ */
+ value: KeyValueRowField;
+ /**
+ * Optional styles. E.g. specifying padding or margins.
+ */
+ style?: ViewProps['style'];
+}
diff --git a/app/component-library/components-temp/KeyValueRow/KeyValueRow.utils.ts b/app/component-library/components-temp/KeyValueRow/KeyValueRow.utils.ts
new file mode 100644
index 00000000000..bf4c38843e4
--- /dev/null
+++ b/app/component-library/components-temp/KeyValueRow/KeyValueRow.utils.ts
@@ -0,0 +1,8 @@
+import { KeyValueRowProps } from './KeyValueRow.types';
+
+export const areKeyValueRowPropsEqual = (
+ prevProps: KeyValueRowProps,
+ newProps: KeyValueRowProps,
+) =>
+ JSON.stringify(prevProps.field) === JSON.stringify(newProps.field) &&
+ JSON.stringify(prevProps.value) === JSON.stringify(newProps.value);
diff --git a/app/component-library/components-temp/KeyValueRow/KeyValueSection/KeyValueSection.styles.tsx b/app/component-library/components-temp/KeyValueRow/KeyValueSection/KeyValueSection.styles.tsx
new file mode 100644
index 00000000000..f8d8adf679d
--- /dev/null
+++ b/app/component-library/components-temp/KeyValueRow/KeyValueSection/KeyValueSection.styles.tsx
@@ -0,0 +1,10 @@
+import { StyleSheet } from 'react-native';
+
+const styleSheet = () =>
+ StyleSheet.create({
+ keyValueSectionContainer: {
+ flex: 1,
+ },
+ });
+
+export default styleSheet;
diff --git a/app/component-library/components-temp/KeyValueRow/KeyValueSection/KeyValueSection.tsx b/app/component-library/components-temp/KeyValueRow/KeyValueSection/KeyValueSection.tsx
new file mode 100644
index 00000000000..7c01d2b438a
--- /dev/null
+++ b/app/component-library/components-temp/KeyValueRow/KeyValueSection/KeyValueSection.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import { useStyles } from '../../../hooks';
+import { View } from 'react-native';
+import {
+ KeyValueRowSectionAlignments,
+ KeyValueSectionProps,
+} from '../KeyValueRow.types';
+import stylesSheet from './KeyValueSection.styles';
+
+/**
+ * A container representing either the left or right side of the KeyValueRow.
+ * For desired results, use only two components within the .
+ *
+ * @component
+ * @param {Object} props - Component props.
+ * @param {ReactNode} props.children - The child components.
+ * @param {KeyValueRowSectionAlignments} [props.align] - The alignment of the KeyValueSection. Defaults to KeyValueRowSectionAlignments.RIGHT
+ *
+ * @returns {JSX.Element} The rendered KeyValueSection component.
+ */
+const KeyValueSection = ({
+ children,
+ align = KeyValueRowSectionAlignments.LEFT,
+}: KeyValueSectionProps) => {
+ const { styles } = useStyles(stylesSheet, {});
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default KeyValueSection;
diff --git a/app/component-library/components-temp/KeyValueRow/__snapshots__/KeyValueRow.test.tsx.snap b/app/component-library/components-temp/KeyValueRow/__snapshots__/KeyValueRow.test.tsx.snap
new file mode 100644
index 00000000000..c83d3a4fd5f
--- /dev/null
+++ b/app/component-library/components-temp/KeyValueRow/__snapshots__/KeyValueRow.test.tsx.snap
@@ -0,0 +1,609 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`KeyValueRow Prebuilt Component KeyValueRow should render text with icons 1`] = `
+
+
+
+
+
+
+ Key Text
+
+
+
+
+
+
+
+
+
+ Value Text
+
+
+
+
+
+`;
+
+exports[`KeyValueRow Prebuilt Component KeyValueRow should render text with icons and tooltips 1`] = `
+
+
+
+
+
+
+ Key Text
+
+
+
+
+
+
+
+
+
+
+
+
+ Value Text
+
+
+
+
+
+
+
+
+`;
+
+exports[`KeyValueRow Prebuilt Component KeyValueRow should render text with tooltips 1`] = `
+
+
+
+
+
+ Key Text
+
+
+
+
+
+
+
+
+
+
+
+ Value Text
+
+
+
+
+
+
+
+
+`;
+
+exports[`KeyValueRow Prebuilt Component KeyValueRow should render when there is only text 1`] = `
+
+
+
+
+
+ Sample Key Text
+
+
+
+
+
+
+
+
+ Sample Value Text
+
+
+
+
+
+`;
diff --git a/app/component-library/components-temp/KeyValueRow/index.tsx b/app/component-library/components-temp/KeyValueRow/index.tsx
new file mode 100644
index 00000000000..ee4a0c18825
--- /dev/null
+++ b/app/component-library/components-temp/KeyValueRow/index.tsx
@@ -0,0 +1,8 @@
+export { KeyValueRowStubs, default } from './KeyValueRow';
+export {
+ KeyValueRowSectionAlignments,
+ KeyValueRowFieldIconSides,
+ TooltipSizes,
+ IconSizes,
+} from './KeyValueRow.types';
+export type * from './KeyValueRow.types';
diff --git a/app/component-library/components/Pickers/PickerAccount/PickerAccount.tsx b/app/component-library/components/Pickers/PickerAccount/PickerAccount.tsx
index 5a4c293777f..ce9a9ab9014 100644
--- a/app/component-library/components/Pickers/PickerAccount/PickerAccount.tsx
+++ b/app/component-library/components/Pickers/PickerAccount/PickerAccount.tsx
@@ -51,7 +51,7 @@ const PickerAccount: React.ForwardRefRenderFunction<
/>
{accountName}
diff --git a/app/component-library/components/Pickers/PickerAccount/__snapshots__/PickerAccount.test.tsx.snap b/app/component-library/components/Pickers/PickerAccount/__snapshots__/PickerAccount.test.tsx.snap
index f2c7d176b13..0afdb8affeb 100644
--- a/app/component-library/components/Pickers/PickerAccount/__snapshots__/PickerAccount.test.tsx.snap
+++ b/app/component-library/components/Pickers/PickerAccount/__snapshots__/PickerAccount.test.tsx.snap
@@ -178,11 +178,11 @@ exports[`PickerAccount should render correctly 1`] = `
style={
{
"color": "#141618",
- "fontFamily": "EuclidCircularB-Regular",
- "fontSize": 16,
- "fontWeight": "400",
+ "fontFamily": "EuclidCircularB-Medium",
+ "fontSize": 14,
+ "fontWeight": "500",
"letterSpacing": 0,
- "lineHeight": 24,
+ "lineHeight": 22,
}
}
testID="account-label"
@@ -207,7 +207,7 @@ exports[`PickerAccount should render correctly 1`] = `
diff --git a/app/component-library/components/Pickers/PickerBase/__snapshots__/PickerBase.test.tsx.snap b/app/component-library/components/Pickers/PickerBase/__snapshots__/PickerBase.test.tsx.snap
index 6d3ac0d242b..f93ccf2a438 100644
--- a/app/component-library/components/Pickers/PickerBase/__snapshots__/PickerBase.test.tsx.snap
+++ b/app/component-library/components/Pickers/PickerBase/__snapshots__/PickerBase.test.tsx.snap
@@ -17,7 +17,7 @@ exports[`PickerBase should render correctly 1`] = `
>
+interface SelectorButtonProps {
+ onPress: (event: GestureResponderEvent) => void;
+ disabled?: boolean;
+ children: React.ReactNode;
+}
+
+const createStyles = (colors: Theme['colors']) =>
StyleSheet.create({
container: {
backgroundColor: colors.background.alternative,
@@ -23,7 +29,7 @@ const createStyles = (colors) =>
},
});
-function SelectorButton({ onPress, disabled, children, ...props }) {
+const SelectorButton: React.FC = ({ onPress, disabled, children, ...props }) => {
const { colors } = useTheme();
const styles = createStyles(colors);
@@ -35,12 +41,6 @@ function SelectorButton({ onPress, disabled, children, ...props }) {
);
-}
-
-SelectorButton.propTypes = {
- children: PropTypes.node,
- onPress: PropTypes.func,
- disabled: PropTypes.bool,
};
export default SelectorButton;
diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js
index 1353c66b04a..d800e78ce30 100644
--- a/app/components/Nav/Main/index.js
+++ b/app/components/Nav/Main/index.js
@@ -21,14 +21,11 @@ import BackgroundTimer from 'react-native-background-timer';
import NotificationManager from '../../../core/NotificationManager';
import Engine from '../../../core/Engine';
import AppConstants from '../../../core/AppConstants';
-import notifee from '@notifee/react-native';
import I18n, { strings } from '../../../../locales/i18n';
import FadeOutOverlay from '../../UI/FadeOutOverlay';
import BackupAlert from '../../UI/BackupAlert';
import Notification from '../../UI/Notification';
import RampOrders from '../../UI/Ramp';
-import Device from '../../../util/device';
-import Routes from '../../../constants/navigation/Routes';
import {
showTransactionNotification,
hideCurrentNotification,
@@ -36,12 +33,12 @@ import {
removeNotificationById,
removeNotVisibleNotifications,
} from '../../../actions/notification';
+
import ProtectYourWalletModal from '../../UI/ProtectYourWalletModal';
import MainNavigator from './MainNavigator';
import SkipAccountSecurityModal from '../../UI/SkipAccountSecurityModal';
import { query } from '@metamask/controller-utils';
import SwapsLiveness from '../../UI/Swaps/SwapsLiveness';
-import useNotificationHandler from '../../../util/notifications/hooks';
import {
setInfuraAvailabilityBlocked,
@@ -70,6 +67,8 @@ import {
selectNetworkImageSource,
} from '../../../selectors/networkInfos';
import { selectShowIncomingTransactionNetworks } from '../../../selectors/preferencesController';
+
+import useNotificationHandler from '../../../util/notifications/hooks';
import {
DEPRECATED_NETWORKS,
NETWORKS_CHAIN_ID,
@@ -105,16 +104,18 @@ const Main = (props) => {
const [showDeprecatedAlert, setShowDeprecatedAlert] = useState(true);
const { colors } = useTheme();
const styles = createStyles(colors);
-
const backgroundMode = useRef(false);
const locale = useRef(I18n.locale);
const removeConnectionStatusListener = useRef();
const removeNotVisibleNotifications = props.removeNotVisibleNotifications;
-
+ useNotificationHandler(props.navigation);
useEnableAutomaticSecurityChecks();
useMinimumVersions();
+
+
+
useEffect(() => {
if (DEPRECATED_NETWORKS.includes(props.chainId)) {
setShowDeprecatedAlert(true);
@@ -267,23 +268,8 @@ const Main = (props) => {
initForceReload();
return;
}
- });
-
- const bootstrapAndroidInitialNotification = useCallback(async () => {
- if (Device.isAndroid()) {
- const initialNotification = await notifee.getInitialNotification();
-
- if (
- initialNotification?.data?.action === 'tx' &&
- initialNotification.data.id
- ) {
- NotificationManager.setTransactionToView(initialNotification.data.id);
- props.navigation.navigate(Routes.TRANSACTIONS_VIEW);
- }
- }
- }, [props.navigation]);
- useNotificationHandler(bootstrapAndroidInitialNotification, props.navigation);
+ });
// Remove all notifications that aren't visible
useEffect(() => {
diff --git a/app/components/UI/AddressCopy/AddressCopy.styles.ts b/app/components/UI/AddressCopy/AddressCopy.styles.ts
index 5a79e70438d..089c48d5136 100644
--- a/app/components/UI/AddressCopy/AddressCopy.styles.ts
+++ b/app/components/UI/AddressCopy/AddressCopy.styles.ts
@@ -7,14 +7,18 @@ const styleSheet = (params: { theme: Theme }) => {
const { colors } = theme;
return StyleSheet.create({
- address: { flexDirection: 'row' },
+ address: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
copyButton: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.primary.muted,
borderRadius: 20,
- paddingHorizontal: 8,
- marginLeft: 8,
+ paddingHorizontal: 12,
+ padding: 4,
+ marginLeft: 12,
},
icon: { marginLeft: 4 },
});
diff --git a/app/components/UI/AddressCopy/AddressCopy.tsx b/app/components/UI/AddressCopy/AddressCopy.tsx
index 17f8dbd1550..d295c5f75ad 100644
--- a/app/components/UI/AddressCopy/AddressCopy.tsx
+++ b/app/components/UI/AddressCopy/AddressCopy.tsx
@@ -79,7 +79,7 @@ const AddressCopy = ({ formatAddressType = 'full' }: AddressCopyProps) => {
>
{selectedInternalAccount
diff --git a/app/components/UI/ApprovalTagUrl/ApprovalTagUrl.tsx b/app/components/UI/ApprovalTagUrl/ApprovalTagUrl.tsx
index c66a2390c3d..e7980f2848d 100644
--- a/app/components/UI/ApprovalTagUrl/ApprovalTagUrl.tsx
+++ b/app/components/UI/ApprovalTagUrl/ApprovalTagUrl.tsx
@@ -4,12 +4,12 @@ import { useSelector } from 'react-redux';
import { strings } from '../../../../locales/i18n';
import TagUrl from '../../../component-library/components/Tags/TagUrl';
+import { useStyles } from '../../../component-library/hooks';
import AppConstants from '../../../core/AppConstants';
+import { selectInternalAccounts } from '../../../selectors/accountsController';
import { selectAccountsByChainId } from '../../../selectors/accountTrackerController';
import { prefixUrlWithProtocol } from '../../../util/browser';
import useFavicon from '../../hooks/useFavicon/useFavicon';
-import { selectInternalAccounts } from '../../../selectors/accountsController';
-import { useStyles } from '../../../component-library/hooks';
import stylesheet from './ApprovalTagUrl.styles';
const { ORIGIN_DEEPLINK, ORIGIN_QR_CODE } = AppConstants.DEEPLINKS;
@@ -51,12 +51,14 @@ const ApprovalTagUrl = ({
const domainTitle = useMemo(() => {
let title = '';
- if (url || currentEnsName) {
- title = prefixUrlWithProtocol(currentEnsName || url || '');
+ if (url || currentEnsName || origin) {
+ title = prefixUrlWithProtocol(currentEnsName || origin || url);
+ } else {
+ title = '';
}
return title;
- }, [currentEnsName, url]);
+ }, [currentEnsName, origin, url]);
const faviconSource = useFavicon(origin as string) as
| { uri: string }
diff --git a/app/components/UI/AssetOverview/Balance/Balance.tsx b/app/components/UI/AssetOverview/Balance/Balance.tsx
index abbaa021a1f..651ff62892d 100644
--- a/app/components/UI/AssetOverview/Balance/Balance.tsx
+++ b/app/components/UI/AssetOverview/Balance/Balance.tsx
@@ -25,6 +25,10 @@ import Text, {
TextVariant,
} from '../../../../component-library/components/Texts/Text';
import { TokenI } from '../../Tokens/types';
+import { useNavigation } from '@react-navigation/native';
+import { isPooledStakingFeatureEnabled } from '../../Stake/constants';
+import StakingBalance from '../../Stake/components/StakingBalance/StakingBalance';
+
interface BalanceProps {
asset: TokenI;
mainBalance: string;
@@ -46,6 +50,7 @@ const NetworkBadgeSource = (chainId: string, ticker: string) => {
const Balance = ({ asset, mainBalance, secondaryBalance }: BalanceProps) => {
const { styles } = useStyles(styleSheet, {});
+ const navigation = useNavigation();
const networkName = useSelector(selectNetworkName);
const chainId = useSelector(selectChainId);
@@ -58,6 +63,7 @@ const Balance = ({ asset, mainBalance, secondaryBalance }: BalanceProps) => {
asset={asset}
mainBalance={mainBalance}
balance={secondaryBalance}
+ onPress={() => !asset.isETH && navigation.navigate('AssetDetails')}
>
{
{asset.name || asset.symbol}
+ {isPooledStakingFeatureEnabled() && asset?.isETH && }
);
};
diff --git a/app/components/UI/AssetOverview/Balance/index.test.tsx b/app/components/UI/AssetOverview/Balance/index.test.tsx
index 8df5db0ed87..79d5d4c5c0d 100644
--- a/app/components/UI/AssetOverview/Balance/index.test.tsx
+++ b/app/components/UI/AssetOverview/Balance/index.test.tsx
@@ -1,15 +1,27 @@
import React from 'react';
+import { Image } from 'react-native';
import Balance from '.';
-import { render } from '@testing-library/react-native';
+import { render, fireEvent } from '@testing-library/react-native';
import { selectNetworkName } from '../../../../selectors/networkInfos';
import { selectChainId } from '../../../../selectors/networkController';
-import { useSelector } from 'react-redux';
+import { Provider, useSelector } from 'react-redux';
+import configureMockStore from 'redux-mock-store';
+import { backgroundState } from '../../../../util/test/initial-root-state';
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useSelector: jest.fn(),
}));
+const mockNavigate = jest.fn();
+
+jest.mock('@react-navigation/native', () => ({
+ ...jest.requireActual('@react-navigation/native'),
+ useNavigation: () => ({
+ navigate: mockNavigate,
+ }),
+}));
+
const mockDAI = {
address: '0x6b175474e89094c44da98b954eedeac495271d0f',
aggregators: ['Metamask', 'Coinmarketcap'],
@@ -25,7 +37,35 @@ const mockDAI = {
logo: 'image-path',
};
+const mockETH = {
+ address: '0x0000000000000000000000000000',
+ aggregators: [],
+ balanceError: null,
+ balance: '100',
+ balanceFiat: '$10000',
+ decimals: 18,
+ image:
+ 'https://static.cx.metamask.io/api/v1/tokenIcons/1/0x6b175474e89094c44da98b954eedeac495271d0f.png',
+ name: 'Ethereum',
+ symbol: 'ETH',
+ isETH: true,
+ logo: 'image-path',
+};
+
+const mockInitialState = {
+ engine: {
+ backgroundState,
+ },
+};
+
describe('Balance', () => {
+ const mockStore = configureMockStore();
+ const store = mockStore(mockInitialState);
+
+ Image.getSize = jest.fn((_uri, success) => {
+ success(100, 100); // Mock successful response for ETH native Icon Image
+ });
+
beforeEach(() => {
(useSelector as jest.Mock).mockImplementation((selector) => {
switch (selector) {
@@ -38,9 +78,11 @@ describe('Balance', () => {
}
});
});
- beforeAll(() => {
- jest.resetAllMocks();
+
+ afterEach(() => {
+ jest.clearAllMocks();
});
+
it('should render correctly with a fiat balance', () => {
const wrapper = render(
,
@@ -58,4 +100,24 @@ describe('Balance', () => {
);
expect(wrapper).toMatchSnapshot();
});
+
+ it('should fire navigation event for non native tokens', () => {
+ const { queryByTestId } = render(
+ ,
+ );
+ const assetElement = queryByTestId('asset-DAI');
+ fireEvent.press(assetElement);
+ expect(mockNavigate).toHaveBeenCalledTimes(1);
+ });
+
+ it('should not fire navigation event for native tokens', () => {
+ const { queryByTestId } = render(
+
+
+ ,
+ );
+ const assetElement = queryByTestId('asset-ETH');
+ fireEvent.press(assetElement);
+ expect(mockNavigate).toHaveBeenCalledTimes(0);
+ });
});
diff --git a/app/components/UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.tsx b/app/components/UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.tsx
index b2d26f68838..e01d3afdccb 100644
--- a/app/components/UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.tsx
+++ b/app/components/UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.tsx
@@ -26,10 +26,7 @@ import Icon, {
IconSize,
} from '../../../../component-library/components/Icons/Icon';
import Routes from '../../../../constants/navigation/Routes';
-import {
- asyncAlert,
- requestPushNotificationsPermission,
-} from '../../../../util/notifications';
+import NotificationsService from '../../../../util/notifications/services/NotificationService';
import { MetaMetricsEvents } from '../../../../core/Analytics';
import { useEnableNotifications } from '../../../../util/notifications/hooks/useNotifications';
import { useMetrics } from '../../../hooks/useMetrics';
@@ -37,7 +34,6 @@ import {
selectIsProfileSyncingEnabled,
selectIsMetamaskNotificationsEnabled,
} from '../../../../selectors/notifications';
-import { AuthorizationStatus } from '@notifee/react-native';
interface Props {
route: {
@@ -65,18 +61,11 @@ const BasicFunctionalityModal = ({ route }: Props) => {
const { enableNotifications } = useEnableNotifications();
const enableNotificationsFromModal = useCallback(async () => {
- const nativeNotificationStatus = await requestPushNotificationsPermission(
- asyncAlert,
- );
-
- if (nativeNotificationStatus?.authorizationStatus === AuthorizationStatus.AUTHORIZED) {
- /**
- * Although this is an async function, we are dispatching an action (firing & forget)
- * to emulate optimistic UI.
- *
- */
- enableNotifications();
+ const { permission } = await NotificationsService.getAllPermissions(false);
+ if (permission !== 'authorized') {
+ return;
}
+ enableNotifications();
}, [enableNotifications]);
const closeBottomSheet = async () => {
diff --git a/app/components/UI/ComponentErrorBoundary/index.js b/app/components/UI/ComponentErrorBoundary/index.js
deleted file mode 100644
index d532728cdc3..00000000000
--- a/app/components/UI/ComponentErrorBoundary/index.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import Logger from '../../../util/Logger';
-import trackErrorAsAnalytics from '../../../util/metrics/TrackError/trackErrorAsAnalytics';
-
-class ComponentErrorBoundary extends React.Component {
- state = { error: null };
-
- static propTypes = {
- /**
- * Component to be used when there is no error
- */
- children: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.node),
- PropTypes.node,
- ]),
- /**
- * Component label for logging
- */
- componentLabel: PropTypes.string.isRequired,
- /**
- * Function to be called when there is an error
- */
- onError: PropTypes.func,
- /**
- * Will not track as an error, but still log to analytics
- */
- dontTrackAsError: PropTypes.bool,
- };
-
- static getDerivedStateFromError(error) {
- return { error };
- }
-
- componentDidCatch(error, errorInfo) {
- // eslint-disable-next-line no-unused-expressions
- this.props.onError?.();
-
- const { componentLabel, dontTrackAsError } = this.props;
-
- if (dontTrackAsError) {
- return trackErrorAsAnalytics(
- `Component Error Boundary: ${componentLabel}`,
- error?.message,
- );
- }
- Logger.error(error, { View: this.props.componentLabel, ...errorInfo });
- }
-
- getErrorMessage = () =>
- `Component: ${this.props.componentLabel}\n${this.state.error.toString()}`;
-
- render() {
- return this.state.error ? null : this.props.children;
- }
-}
-
-export default ComponentErrorBoundary;
diff --git a/app/components/UI/ComponentErrorBoundary/index.tsx b/app/components/UI/ComponentErrorBoundary/index.tsx
new file mode 100644
index 00000000000..788c502297c
--- /dev/null
+++ b/app/components/UI/ComponentErrorBoundary/index.tsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import Logger from '../../../util/Logger';
+import trackErrorAsAnalytics from '../../../util/metrics/TrackError/trackErrorAsAnalytics';
+
+interface ComponentErrorBoundaryProps {
+ /**
+ * Component to be used when there is no error
+ */
+ children: React.ReactNode;
+ /**
+ * Component label for logging
+ */
+ componentLabel: string;
+ /**
+ * Function to be called when there is an error
+ */
+ onError?: () => void;
+ /**
+ * Will not track as an error, but still log to analytics
+ */
+ dontTrackAsError?: boolean;
+}
+
+interface ComponentErrorBoundaryState {
+ error: Error | null;
+}
+
+class ComponentErrorBoundary extends React.Component {
+ state: ComponentErrorBoundaryState = { error: null };
+
+ static getDerivedStateFromError(error: Error): ComponentErrorBoundaryState {
+ return { error };
+ }
+
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
+ this.props.onError?.();
+
+ const { componentLabel, dontTrackAsError } = this.props;
+
+ if (dontTrackAsError) {
+ return trackErrorAsAnalytics(
+ `Component Error Boundary: ${componentLabel}`,
+ error?.message,
+ );
+ }
+ Logger.error(error, { View: this.props.componentLabel, ...errorInfo });
+ }
+
+ getErrorMessage = (): string =>
+ `Component: ${this.props.componentLabel}\n${this.state.error?.toString()}`;
+
+ render(): React.ReactNode {
+ return this.state.error ? null : this.props.children;
+ }
+}
+
+export default ComponentErrorBoundary;
diff --git a/app/components/UI/GenericButton/index.android.js b/app/components/UI/GenericButton/index.android.tsx
similarity index 74%
rename from app/components/UI/GenericButton/index.android.js
rename to app/components/UI/GenericButton/index.android.tsx
index d715f3567a7..31c0f904e48 100644
--- a/app/components/UI/GenericButton/index.android.js
+++ b/app/components/UI/GenericButton/index.android.tsx
@@ -1,6 +1,5 @@
import React from 'react';
-import PropTypes from 'prop-types';
-import { View, ViewPropTypes, TouchableNativeFeedback } from 'react-native';
+import { View, TouchableNativeFeedback, StyleProp, ViewStyle, GestureResponderEvent } from 'react-native';
/**
* @deprecated The `` component has been deprecated in favor of the new `