From 84eab1a1113600fa9e461722dab0f921a7c56b8a Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Sun, 5 Nov 2023 10:42:34 +0700 Subject: [PATCH 001/647] Add CurrencySelectionList component and use it in IOU and WS currency settings Signed-off-by: Tsaqif --- src/components/CurrencySelectionList.js | 92 +++++++++++++++++++ src/pages/iou/IOUCurrencySelection.js | 74 ++------------- .../WorkspaceSettingsCurrencyPage.js | 67 ++------------ 3 files changed, 106 insertions(+), 127 deletions(-) create mode 100644 src/components/CurrencySelectionList.js diff --git a/src/components/CurrencySelectionList.js b/src/components/CurrencySelectionList.js new file mode 100644 index 000000000000..1c0733ae9927 --- /dev/null +++ b/src/components/CurrencySelectionList.js @@ -0,0 +1,92 @@ +import Str from 'expensify-common/lib/str'; +import PropTypes from 'prop-types'; +import React, {useMemo, useState} from 'react'; +import {withOnyx} from 'react-native-onyx'; +import _ from 'underscore'; +import compose from '@libs/compose'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; +import SelectionList from './SelectionList'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; + +const propTypes = { + /** Label for the search text input */ + textInputLabel: PropTypes.string.isRequired, + + /** Callback to fire when a currency is selected */ + onSelect: PropTypes.func.isRequired, + + /** Currency item to be selected initially */ + initiallySelectedCurrencyCode: PropTypes.string.isRequired, + + // The curency list constant object from Onyx + currencyList: PropTypes.objectOf( + PropTypes.shape({ + // Symbol for the currency + symbol: PropTypes.string, + + // Name of the currency + name: PropTypes.string, + + // ISO4217 Code for the currency + ISO4217: PropTypes.string, + }), + ), + + ...withLocalizePropTypes, +}; + +const defaultProps = { + currencyList: {}, +}; + +function CurrencySelectionList(props) { + const [searchValue, setSearchValue] = useState(''); + const {translate, currencyList} = props; + + const {sections, headerMessage} = useMemo(() => { + const currencyOptions = _.map(currencyList, (currencyInfo, currencyCode) => { + const isSelectedCurrency = currencyCode === props.initiallySelectedCurrencyCode; + return { + currencyName: currencyInfo.name, + text: `${currencyCode} - ${CurrencyUtils.getLocalizedCurrencySymbol(currencyCode)}`, + currencyCode, + keyForList: currencyCode, + isSelected: isSelectedCurrency, + }; + }); + + const searchRegex = new RegExp(Str.escapeForRegExp(searchValue.trim()), 'i'); + const filteredCurrencies = _.filter(currencyOptions, (currencyOption) => searchRegex.test(currencyOption.text) || searchRegex.test(currencyOption.currencyName)); + const isEmpty = searchValue.trim() && !filteredCurrencies.length; + + return { + sections: isEmpty ? [] : [{data: filteredCurrencies, indexOffset: 0}], + headerMessage: isEmpty ? translate('common.noResultsFound') : '', + }; + }, [currencyList, searchValue, translate, props.initiallySelectedCurrencyCode]); + + return ( + + ); +} + +CurrencySelectionList.displayName = 'CurrencySelectionList'; +CurrencySelectionList.propTypes = propTypes; +CurrencySelectionList.defaultProps = defaultProps; + +export default compose( + withLocalize, + withOnyx({ + currencyList: {key: ONYXKEYS.CURRENCY_LIST}, + }), +)(CurrencySelectionList); diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index c7b5885865df..a18929061f1f 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -1,17 +1,15 @@ -import Str from 'expensify-common/lib/str'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useEffect} from 'react'; import {Keyboard} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; +import CurrencySelectionList from '@components/CurrencySelectionList'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {withNetwork} from '@components/OnyxProvider'; import ScreenWrapper from '@components/ScreenWrapper'; -import SelectionList from '@components/SelectionList'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import compose from '@libs/compose'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -42,20 +40,6 @@ const propTypes = { }), }).isRequired, - // The currency list constant object from Onyx - currencyList: PropTypes.objectOf( - PropTypes.shape({ - // Symbol for the currency - symbol: PropTypes.string, - - // Name of the currency - name: PropTypes.string, - - // ISO4217 Code for the currency - ISO4217: PropTypes.string, - }), - ), - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ iou: iouPropTypes, @@ -63,13 +47,10 @@ const propTypes = { }; const defaultProps = { - currencyList: {}, iou: iouDefaultProps, }; function IOUCurrencySelection(props) { - const [searchValue, setSearchValue] = useState(''); - const optionsSelectorRef = useRef(); const selectedCurrencyCode = (lodashGet(props.route, 'params.currency', props.iou.currency) || CONST.CURRENCY.USD).toUpperCase(); const iouType = lodashGet(props.route, 'params.iouType', CONST.IOU.TYPE.REQUEST); const reportID = lodashGet(props.route, 'params.reportID', ''); @@ -113,59 +94,19 @@ function IOUCurrencySelection(props) { [props.route, props.navigation], ); - const {translate, currencyList} = props; - const {sections, headerMessage, initiallyFocusedOptionKey} = useMemo(() => { - const currencyOptions = _.map(currencyList, (currencyInfo, currencyCode) => { - const isSelectedCurrency = currencyCode === selectedCurrencyCode; - return { - currencyName: currencyInfo.name, - text: `${currencyCode} - ${CurrencyUtils.getLocalizedCurrencySymbol(currencyCode)}`, - currencyCode, - keyForList: currencyCode, - isSelected: isSelectedCurrency, - }; - }); - - const searchRegex = new RegExp(Str.escapeForRegExp(searchValue.trim()), 'i'); - const filteredCurrencies = _.filter(currencyOptions, (currencyOption) => searchRegex.test(currencyOption.text) || searchRegex.test(currencyOption.currencyName)); - const isEmpty = searchValue.trim() && !filteredCurrencies.length; - - return { - initiallyFocusedOptionKey: _.get( - _.find(filteredCurrencies, (currency) => currency.currencyCode === selectedCurrencyCode), - 'keyForList', - ), - sections: isEmpty - ? [] - : [ - { - data: filteredCurrencies, - indexOffset: 0, - }, - ], - headerMessage: isEmpty ? translate('common.noResultsFound') : '', - }; - }, [currencyList, searchValue, selectedCurrencyCode, translate]); - return ( optionsSelectorRef.current && optionsSelectorRef.current.focus()} testID={IOUCurrencySelection.displayName} > Navigation.goBack(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID))} /> - ); @@ -178,7 +119,6 @@ IOUCurrencySelection.defaultProps = defaultProps; export default compose( withLocalize, withOnyx({ - currencyList: {key: ONYXKEYS.CURRENCY_LIST}, iou: {key: ONYXKEYS.IOU}, }), withNetwork(), diff --git a/src/pages/workspace/WorkspaceSettingsCurrencyPage.js b/src/pages/workspace/WorkspaceSettingsCurrencyPage.js index ce1e1d7b8966..a20e8e272e4e 100644 --- a/src/pages/workspace/WorkspaceSettingsCurrencyPage.js +++ b/src/pages/workspace/WorkspaceSettingsCurrencyPage.js @@ -1,73 +1,30 @@ import PropTypes from 'prop-types'; -import React, {useCallback, useState} from 'react'; -import {withOnyx} from 'react-native-onyx'; +import React, {useCallback} from 'react'; import _ from 'underscore'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import CurrencySelectionList from '@components/CurrencySelectionList'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; -import SelectionList from '@components/SelectionList'; import useLocalize from '@hooks/useLocalize'; -import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as Policy from '@userActions/Policy'; -import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import {policyDefaultProps, policyPropTypes} from './withPolicy'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; const propTypes = { - /** Constant, list of available currencies */ - currencyList: PropTypes.objectOf( - PropTypes.shape({ - /** Symbol of the currency */ - symbol: PropTypes.string.isRequired, - }), - ), isLoadingReportData: PropTypes.bool, ...policyPropTypes, }; const defaultProps = { - currencyList: {}, isLoadingReportData: true, ...policyDefaultProps, }; -const getDisplayText = (currencyCode, currencySymbol) => `${currencyCode} - ${currencySymbol}`; - -function WorkspaceSettingsCurrencyPage({currencyList, policy, isLoadingReportData}) { +function WorkspaceSettingsCurrencyPage({policy, isLoadingReportData}) { const {translate} = useLocalize(); - const [searchText, setSearchText] = useState(''); - const trimmedText = searchText.trim().toLowerCase(); - const currencyListKeys = _.keys(currencyList); - - const filteredItems = _.filter(currencyListKeys, (currencyCode) => { - const currency = currencyList[currencyCode]; - return getDisplayText(currencyCode, currency.symbol).toLowerCase().includes(trimmedText); - }); - - let initiallyFocusedOptionKey; - - const currencyItems = _.map(filteredItems, (currencyCode) => { - const currency = currencyList[currencyCode]; - const isSelected = policy.outputCurrency === currencyCode; - - if (isSelected) { - initiallyFocusedOptionKey = currencyCode; - } - - return { - text: getDisplayText(currencyCode, currency.symbol), - keyForList: currencyCode, - isSelected, - }; - }); - - const sections = [{data: currencyItems, indexOffset: 0}]; - - const headerMessage = searchText.trim() && !currencyItems.length ? translate('common.noResultsFound') : ''; - const onBackButtonPress = useCallback(() => Navigation.goBack(ROUTES.WORKSPACE_SETTINGS.getRoute(policy.id)), [policy.id]); const onSelectCurrency = (item) => { @@ -90,15 +47,10 @@ function WorkspaceSettingsCurrencyPage({currencyList, policy, isLoadingReportDat onBackButtonPress={onBackButtonPress} /> - @@ -109,9 +61,4 @@ WorkspaceSettingsCurrencyPage.displayName = 'WorkspaceSettingsCurrencyPage'; WorkspaceSettingsCurrencyPage.propTypes = propTypes; WorkspaceSettingsCurrencyPage.defaultProps = defaultProps; -export default compose( - withPolicyAndFullscreenLoading, - withOnyx({ - currencyList: {key: ONYXKEYS.CURRENCY_LIST}, - }), -)(WorkspaceSettingsCurrencyPage); +export default withPolicyAndFullscreenLoading(WorkspaceSettingsCurrencyPage); From 87c644c9f65a5d9845a92aa0a6531ace1855ddb4 Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Wed, 8 Nov 2023 20:19:09 +0700 Subject: [PATCH 002/647] Add currency url parameter for WorkspaceSettingsCurrencyPage Signed-off-by: Tsaqif --- src/ROUTES.ts | 2 +- src/components/CurrencySelectionList.js | 5 ++++- src/pages/iou/IOUCurrencySelection.js | 1 + .../workspace/WorkspaceSettingsCurrencyPage.js | 18 +++++++++++++++++- src/pages/workspace/WorkspaceSettingsPage.js | 2 +- 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index bcc4685368cb..0902309514fc 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -328,7 +328,7 @@ export default { }, WORKSPACE_SETTINGS_CURRENCY: { route: 'workspace/:policyID/settings/currency', - getRoute: (policyID: string) => `workspace/${policyID}/settings/currency`, + getRoute: (policyID: string, currency: string) => `workspace/${policyID}/settings/currency?currency=${currency}`, }, WORKSPACE_CARD: { route: 'workspace/:policyID/card', diff --git a/src/components/CurrencySelectionList.js b/src/components/CurrencySelectionList.js index 795e8477a523..ba3cc9f7d7ed 100644 --- a/src/components/CurrencySelectionList.js +++ b/src/components/CurrencySelectionList.js @@ -20,6 +20,9 @@ const propTypes = { /** Currency item to be selected initially */ initiallySelectedCurrencyCode: PropTypes.string.isRequired, + /** Currency item to be focused initially */ + initiallyFocusedCurrencyCode: PropTypes.string.isRequired, + // The curency list constant object from Onyx currencyList: PropTypes.objectOf( PropTypes.shape({ @@ -79,7 +82,7 @@ function CurrencySelectionList(props) { onChangeText={setSearchValue} onSelectRow={props.onSelect} headerMessage={headerMessage} - initiallyFocusedOptionKey={props.initiallySelectedCurrencyCode} + initiallyFocusedOptionKey={props.initiallyFocusedCurrencyCode} showScrollIndicator /> ); diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index a18929061f1f..3fce092abda0 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -106,6 +106,7 @@ function IOUCurrencySelection(props) { diff --git a/src/pages/workspace/WorkspaceSettingsCurrencyPage.js b/src/pages/workspace/WorkspaceSettingsCurrencyPage.js index a20e8e272e4e..2bb2a283acbc 100644 --- a/src/pages/workspace/WorkspaceSettingsCurrencyPage.js +++ b/src/pages/workspace/WorkspaceSettingsCurrencyPage.js @@ -1,3 +1,4 @@ +import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback} from 'react'; import _ from 'underscore'; @@ -6,6 +7,7 @@ import CurrencySelectionList from '@components/CurrencySelectionList'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as Policy from '@userActions/Policy'; @@ -19,12 +21,25 @@ const propTypes = { }; const defaultProps = { + /** Route from navigation */ + route: PropTypes.shape({ + /** Params from the route */ + params: PropTypes.shape({ + /** Focused currency code */ + currency: PropTypes.string, + + /** ID of a policy */ + policyID: PropTypes.string, + }), + }).isRequired, isLoadingReportData: true, ...policyDefaultProps, }; -function WorkspaceSettingsCurrencyPage({policy, isLoadingReportData}) { +function WorkspaceSettingsCurrencyPage({route, policy, isLoadingReportData}) { const {translate} = useLocalize(); + const currencyParam = lodashGet(route, 'params.currency', '').toUpperCase(); + const focusedCurrencyCode = CurrencyUtils.isValidCurrencyCode(currencyParam) ? currencyParam : policy.outputCurrency; const onBackButtonPress = useCallback(() => Navigation.goBack(ROUTES.WORKSPACE_SETTINGS.getRoute(policy.id)), [policy.id]); const onSelectCurrency = (item) => { @@ -50,6 +65,7 @@ function WorkspaceSettingsCurrencyPage({policy, isLoadingReportData}) { diff --git a/src/pages/workspace/WorkspaceSettingsPage.js b/src/pages/workspace/WorkspaceSettingsPage.js index d913ae26c170..287f31305164 100644 --- a/src/pages/workspace/WorkspaceSettingsPage.js +++ b/src/pages/workspace/WorkspaceSettingsPage.js @@ -87,7 +87,7 @@ function WorkspaceSettingsPage({policy, currencyList, windowWidth, route}) { return errors; }, []); - const onPressCurrency = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_SETTINGS_CURRENCY.getRoute(policy.id)), [policy.id]); + const onPressCurrency = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_SETTINGS_CURRENCY.getRoute(policy.id, policy.outputCurrency)), [policy.id, policy.outputCurrency]); const policyName = lodashGet(policy, 'name', ''); From 1e508c14374e7d0809770031c7fd455eb593edf9 Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Fri, 24 Nov 2023 07:45:11 +0700 Subject: [PATCH 003/647] Don't automatically save ws default currency when selecting currency in selection list Signed-off-by: Tsaqif --- src/ROUTES.ts | 2 +- src/components/CurrencySelectionList.js | 5 +---- .../workspace/WorkspaceSettingsCurrencyPage.js | 11 ++++------- src/pages/workspace/WorkspaceSettingsPage.js | 16 +++++++++++----- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 0902309514fc..9545a7ab5008 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -324,7 +324,7 @@ export default { }, WORKSPACE_SETTINGS: { route: 'workspace/:policyID/settings', - getRoute: (policyID: string) => `workspace/${policyID}/settings`, + getRoute: (policyID: string, currency: string) => `workspace/${policyID}/settings?currency=${currency}`, }, WORKSPACE_SETTINGS_CURRENCY: { route: 'workspace/:policyID/settings/currency', diff --git a/src/components/CurrencySelectionList.js b/src/components/CurrencySelectionList.js index ba3cc9f7d7ed..795e8477a523 100644 --- a/src/components/CurrencySelectionList.js +++ b/src/components/CurrencySelectionList.js @@ -20,9 +20,6 @@ const propTypes = { /** Currency item to be selected initially */ initiallySelectedCurrencyCode: PropTypes.string.isRequired, - /** Currency item to be focused initially */ - initiallyFocusedCurrencyCode: PropTypes.string.isRequired, - // The curency list constant object from Onyx currencyList: PropTypes.objectOf( PropTypes.shape({ @@ -82,7 +79,7 @@ function CurrencySelectionList(props) { onChangeText={setSearchValue} onSelectRow={props.onSelect} headerMessage={headerMessage} - initiallyFocusedOptionKey={props.initiallyFocusedCurrencyCode} + initiallyFocusedOptionKey={props.initiallySelectedCurrencyCode} showScrollIndicator /> ); diff --git a/src/pages/workspace/WorkspaceSettingsCurrencyPage.js b/src/pages/workspace/WorkspaceSettingsCurrencyPage.js index 2bb2a283acbc..b86ee9e34404 100644 --- a/src/pages/workspace/WorkspaceSettingsCurrencyPage.js +++ b/src/pages/workspace/WorkspaceSettingsCurrencyPage.js @@ -10,7 +10,6 @@ import useLocalize from '@hooks/useLocalize'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; -import * as Policy from '@userActions/Policy'; import ROUTES from '@src/ROUTES'; import {policyDefaultProps, policyPropTypes} from './withPolicy'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; @@ -39,12 +38,11 @@ const defaultProps = { function WorkspaceSettingsCurrencyPage({route, policy, isLoadingReportData}) { const {translate} = useLocalize(); const currencyParam = lodashGet(route, 'params.currency', '').toUpperCase(); - const focusedCurrencyCode = CurrencyUtils.isValidCurrencyCode(currencyParam) ? currencyParam : policy.outputCurrency; - const onBackButtonPress = useCallback(() => Navigation.goBack(ROUTES.WORKSPACE_SETTINGS.getRoute(policy.id)), [policy.id]); + const selectedCurrencyCode = CurrencyUtils.isValidCurrencyCode(currencyParam) ? currencyParam : policy.outputCurrency; + const onBackButtonPress = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_SETTINGS.getRoute(policy.id, selectedCurrencyCode)), [policy.id, selectedCurrencyCode]); const onSelectCurrency = (item) => { - Policy.updateGeneralSettings(policy.id, policy.name, item.keyForList); - Navigation.goBack(ROUTES.WORKSPACE_SETTINGS.getRoute(policy.id)); + Navigation.navigate(ROUTES.WORKSPACE_SETTINGS.getRoute(policy.id, item.currencyCode)); }; return ( @@ -65,8 +63,7 @@ function WorkspaceSettingsCurrencyPage({route, policy, isLoadingReportData}) { diff --git a/src/pages/workspace/WorkspaceSettingsPage.js b/src/pages/workspace/WorkspaceSettingsPage.js index 287f31305164..df43145d6ffd 100644 --- a/src/pages/workspace/WorkspaceSettingsPage.js +++ b/src/pages/workspace/WorkspaceSettingsPage.js @@ -16,6 +16,7 @@ import TextInput from '@components/TextInput'; import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import * as UserUtils from '@libs/UserUtils'; @@ -42,6 +43,9 @@ const propTypes = { params: PropTypes.shape({ /** The policyID that is being configured */ policyID: PropTypes.string.isRequired, + + /** Selected currency code */ + currency: PropTypes.string, }).isRequired, }).isRequired, @@ -56,8 +60,10 @@ const defaultProps = { function WorkspaceSettingsPage({policy, currencyList, windowWidth, route}) { const {translate} = useLocalize(); + const currencyParam = lodashGet(route, 'params.currency', '').toUpperCase(); + const currencyCode = CurrencyUtils.isValidCurrencyCode(currencyParam) ? currencyParam : policy.outputCurrency; - const formattedCurrency = !_.isEmpty(policy) && !_.isEmpty(currencyList) ? `${policy.outputCurrency} - ${currencyList[policy.outputCurrency].symbol}` : ''; + const formattedCurrency = !_.isEmpty(policy) && !_.isEmpty(currencyList) ? `${currencyCode} - ${currencyList[currencyCode].symbol}` : ''; const submit = useCallback( (values) => { @@ -65,11 +71,11 @@ function WorkspaceSettingsPage({policy, currencyList, windowWidth, route}) { return; } - Policy.updateGeneralSettings(policy.id, values.name.trim(), policy.outputCurrency); + Policy.updateGeneralSettings(policy.id, values.name.trim(), currencyCode); Keyboard.dismiss(); - Navigation.goBack(ROUTES.WORKSPACE_INITIAL.getRoute(policy.id)); + Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policy.id)); }, - [policy.id, policy.isPolicyUpdating, policy.outputCurrency], + [policy.id, policy.isPolicyUpdating, currencyCode], ); const validate = useCallback((values) => { @@ -87,7 +93,7 @@ function WorkspaceSettingsPage({policy, currencyList, windowWidth, route}) { return errors; }, []); - const onPressCurrency = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_SETTINGS_CURRENCY.getRoute(policy.id, policy.outputCurrency)), [policy.id, policy.outputCurrency]); + const onPressCurrency = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_SETTINGS_CURRENCY.getRoute(policy.id, currencyCode)), [policy.id, currencyCode]); const policyName = lodashGet(policy, 'name', ''); From 81dc4ec8a8ef6feb66e8e685857ad738b1eab33f Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Fri, 24 Nov 2023 09:43:02 +0700 Subject: [PATCH 004/647] Fix adding currency parameter to worksapace_settings get route calls Signed-off-by: Tsaqif --- src/pages/workspace/WorkspaceInitialPage.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/workspace/WorkspaceInitialPage.js b/src/pages/workspace/WorkspaceInitialPage.js index 77e831e62b63..1e27c89b405c 100644 --- a/src/pages/workspace/WorkspaceInitialPage.js +++ b/src/pages/workspace/WorkspaceInitialPage.js @@ -53,10 +53,10 @@ const defaultProps = { }; /** - * @param {string} policyID + * @param {Object} policy */ -function openEditor(policyID) { - Navigation.navigate(ROUTES.WORKSPACE_SETTINGS.getRoute(policyID)); +function openEditor(policy) { + Navigation.navigate(ROUTES.WORKSPACE_SETTINGS.getRoute(policy.id, policy.outputCurrency)); } /** @@ -152,7 +152,7 @@ function WorkspaceInitialPage(props) { { translationKey: 'workspace.common.settings', icon: Expensicons.Gear, - action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_SETTINGS.getRoute(policy.id)))), + action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_SETTINGS.getRoute(policy.id, policy.outputCurrency)))), brickRoadIndicator: hasGeneralSettingsError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : '', }, { @@ -269,7 +269,7 @@ function WorkspaceInitialPage(props) { openEditor(policy.id)))} + onPress={singleExecution(waitForNavigate(() => openEditor(policy)))} accessibilityLabel={translate('workspace.common.settings')} role={CONST.ACCESSIBILITY_ROLE.BUTTON} > @@ -289,7 +289,7 @@ function WorkspaceInitialPage(props) { openEditor(policy.id)))} + onPress={singleExecution(waitForNavigate(() => openEditor(policy)))} accessibilityLabel={translate('workspace.common.settings')} role={CONST.ACCESSIBILITY_ROLE.BUTTON} > From 6c4c5b0cc041e57c020cf1c0efaa5976cd0f5e78 Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Sun, 3 Dec 2023 15:54:49 +0700 Subject: [PATCH 005/647] Change display text format of currency selection list Signed-off-by: Tsaqif --- src/components/CurrencySelectionList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CurrencySelectionList.js b/src/components/CurrencySelectionList.js index 795e8477a523..7d43b09c8d66 100644 --- a/src/components/CurrencySelectionList.js +++ b/src/components/CurrencySelectionList.js @@ -50,7 +50,7 @@ function CurrencySelectionList(props) { const isSelectedCurrency = currencyCode === props.initiallySelectedCurrencyCode; return { currencyName: currencyInfo.name, - text: `${currencyCode} - ${CurrencyUtils.getLocalizedCurrencySymbol(currencyCode)}`, + text: `${currencyCode} - ${CurrencyUtils.getCurrencySymbol(currencyCode)}`, currencyCode, keyForList: currencyCode, isSelected: isSelectedCurrency, From 06e5faa05332dd6969f949dcbb12bdc77b3cbe3f Mon Sep 17 00:00:00 2001 From: Sheena Trepanier Date: Wed, 31 Jan 2024 16:33:41 -0800 Subject: [PATCH 006/647] Create unlimited-virtual-cards.md This is a new help doc for the imminent release of Unlimited Virtual Cards. --- .../expensify-card/unlimited-virtual-cards.md | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 docs/articles/expensify-classic/expensify-card/unlimited-virtual-cards.md diff --git a/docs/articles/expensify-classic/expensify-card/unlimited-virtual-cards.md b/docs/articles/expensify-classic/expensify-card/unlimited-virtual-cards.md new file mode 100644 index 000000000000..c5578249289a --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/unlimited-virtual-cards.md @@ -0,0 +1,86 @@ +--- +title: Unlimited Virtual Cards +description: Learn more about virtual cards and how they can help your business gain efficiency and insight into company spending. +--- + +# Overview + +For admins to issue virtual cards, your company **must upgrade to Expensify’s new Expensify Visa® Commercial Card.** + +Once upgraded to the new Expensify Card, admins can issue an unlimited number of virtual cards with a fixed or monthly limit for specific company purchases or recurring subscription payments _(e.g., Marketing purchases, Advertising, Travel, Amazon Web Services, etc.)._ + +This feature supports businesses that require tighter controls on company spending, allowing customers to set fixed or monthly spending limits for each virtual card. + +Use virtual cards if your company needs or wants: + +- To use one card per vendor or subscription, +- To issue cards for one-time purchases with a fixed amount, +- To issue cards for events or trips, +- To issue cards with a low limit that renews monthly, + +Admins can also name each virtual card, making it easy to categorize and assign them to specific accounts upon creation. Naming the card ensures a clear and organized overview of expenses within the Expensify platform. + +# How to set up virtual cards + +After adopting the new Expensify Card, domain admins can issue virtual cards to any employee using an email matching your domain. Once created and assigned, the card will be visible under the name given to the card. + +**To assign a virtual card:** + +1. Head to **Settings** > **Domains** > [**Company Cards**](https://www.expensify.com/domain_companycards). +2. Click the **Issue Virtual Cards** button. +3. Enter a card name (i.e., "Google Ads"). +4. Select a domain member to assign the card to. +5. Enter a card limit. +6. Select a **Limit Type** of _Fixed_ or _Monthly_. +7. Click **Issue Card**. + +![The Issue Virtual Cards modal is open in the middle of the screen. There are four options to set; Card Name, Assignee, Card Limit, and Limit type. A cancel (left) and save (right) button are at the bottom right of the modal.]({{site.url}}/assets/images/AdminissuedVirtualCards.png){:width="100%"} + +# How to edit virtual cards + +Domain admin can update the details of a virtual card on the [Company Cards](https://www.expensify.com/domain_companycards) page. + +**To edit a virtual card:** + +1. Click the **Edit** button to the right of the card. +2. Change the editable details. +3. Click **Edit Card** to save the changes. + +# How to terminate a virtual card + +Domain admin can also terminate a virtual card on the [Company Cards](https://www.expensify.com/domain_companycards) page by setting the limit to $0. + +**To terminate a virtual card:** + +1. Click the **Edit** button to the right of the card. +2. Set the limit to $0. +3. Click **Save**. +4. Refresh your web page, and the card will be removed from the list. + +{% include faq-begin.md %} + +**What is the difference between a fixed limit and a monthly limit?** + +There are two different limit types that are best suited for their intended purpose. + +- _Fixed limit_ spend cards are ideal for one-time expenses or providing employees access to a card for a designated purchase. +- _Monthly_ limit spend cards are perfect for managing recurring expenses such as subscriptions and memberships. + +**Where can employees see their virtual cards?** + +Employees can see their assigned virtual cards by navigating to **Settings** > **Account** > [**Credit Cards Import**](https://www.expensify.com/settings?param=%7B%22section%22:%22creditcards%22%7D) in their account. + +On this page, employees can see the remaining card limit, the type of card it is (i.e., fixed or monthly), and view the name given to the card. + +When the employee needs to use the card, they’ll click the **Show Details** button to expose the card details for making purchases. + +_Note: If the employee doesn’t have Two-Factor Authentication (2FA) enabled when they display the card details, they’ll be prompted to enable it. Enabling 2FA for their account provides the best protection from fraud and is **required** to dispute virtual card expenses._ + +**What do I do when there is fraud on one of our virtual cards?** + +If you or an employee loses their virtual card, experiences fraud, or suspects the card details are no longer secure, please [request a new card](https://help.expensify.com/articles/expensify-classic/expensify-card/Dispute-A-Transaction) immediately. A domain admin can also set the limit for the card to $0 to terminate the specific card immediately if the employee cannot take action. + +When the employee requests a new card, the compromised card will be terminated immediately. This is best practice for any Expensify Card and if fraud is suspected, action should be taken as soon as possible to reduce financial impact on the company. + +{% include faq-end.md %} + From 93072a93a9f792e0a63a5660b6bb8bbfb83429d6 Mon Sep 17 00:00:00 2001 From: Sheena Trepanier Date: Wed, 31 Jan 2024 16:37:45 -0800 Subject: [PATCH 007/647] Add files via upload --- docs/assets/images/AdminissuedVirtualCards.png | Bin 0 -> 157289 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/assets/images/AdminissuedVirtualCards.png diff --git a/docs/assets/images/AdminissuedVirtualCards.png b/docs/assets/images/AdminissuedVirtualCards.png new file mode 100644 index 0000000000000000000000000000000000000000..88df9b2f3fecef011db3e71c7eb12c02847cc97b GIT binary patch literal 157289 zcmeFZ_dnZT|36NRwp6uR6h$c=W~)Y9d$iQ3QL$%HGoc7#wv-mNi=tvwtt4h8Bu4Gp zdkZy#kk~KDtCR|@ zqq2@N6;*j0e>;lMbpD^VU;k56 z`kxzk{7>!w-R|#R`+uGJuYUM{rt-Hr{Qq{|EwRG~G`AV-)krggrPa~FS;XL8CVOvv$QIw_s37Y(m$t3>6~Q}#M;xllE9mz4;wihs_BeYYUgvpUx)d|*c(+mA$SBwDlxJ~^E+@}~UY31>M z`6hUbh<3xqh@+IoSxo1y6fO0U%X1H?!havpBjoD3m?o~6czz}%>{gJ}Z6)iN{lP!S z+4|;JzZFFOmovkMsy#!8ubRkT=r;}%gV1?)A~Ben-h}_ToR)}&;v%v^Kc=4Z;JBH5 ztW(x$GIE$EOAwW3l)Y&5SuFFZRxk+qe=kIZ4{iEp@gxwRna6kY3vd3*>+q+vE3p2D z8N^%X<0duFDKyx${+HYQg}(FX_AS}chVoD9i(MBRE*deF+9W>)PhlNOuW~W|b6xM@ zXJqa>zNd%Ujm66E>KUdtKYq6-H{PcG=a7`-Dz6Yyr#Y=KFZGe;n`GR{Q}ES)noX>@ zb_u$IM?y;P>RGsJ2T}~7|11wCd~lYN$>viL69}a{66+%362sJil*R2or7ZVbEx!J! zD$ZudwOV0h*N@J7)jDYJ3lak+21<^&JK(mx}_dmtPq0)$86cfw(k$4Di{GP!Yq{QzHU6+4^zMJpEIeb;6itaXmLvG6-ui_t+{&#ll@;y z`@aiZ$S|kWgmy*vf>+jm^#?bcfm@iKa>j$&0Akf^f>+!LZbUpQ_%KMcA#mn^D9`iH z1C;rc3GGrIUR)!~Iv&YQk4W>%!rYq1vzF2=90|_&J%ApcEkA*DpR&6iTQx+F8>_t@ zC=71>XXt*VJD2Kbbg^FxX3hJ^u*+&=Lb9s3Q~qZa+Teypk73C9gAz4$^U+1yQ5uu&3Wd zw#@!)m@Dg+&XgL{Krq%JKFd)U>;F5CN?0wc*D_D{g|V`~hRV<|syZ4Dij49KG5SS^ ztzcnn6_rD^mv?%@4E})tWpBpc)Jb`*tyW#U=#+lsYgPCCu>}mw*!$?wbRZ5GIdQzK zpWvh>z_R=Ekd;Zw3RQQ%PpXV`w6KA#3vT%5)+Q&T#79c;JcE6bhm6cPZ&=_NrpsSvYWzy#LQ_7ZS&Uo z|LfIX645TbERoLyZ$?)WubtVBMB(08vHzGjBTEVFtfo3TAm3TNKl%bcB}}-=p4*iK zi{AS((wLDWKB^P}qjzr%-Epc-pI)e&jN9$&AuK46AYFDL3zUh=}C0UEzj(3}0eP zbMy-&2zO=h9nQGj-*t!{t9&DKfQ0lX@KBy*iN3-futOg>-{o{$F7DF@RUTs?p{j&X zpJE)@R-1^GH?Ezqx|l*IXsACcag)?0+Z3?p$vA!6wr~0tz*3bQ5#RFq z=0CV^l4Ve-fZsy+$DB)1)F|Vqh|wXh^ZErnUd9@nQ;uPcUfu6hVZqbDjy(r-)|h1Y z6G-UpmF$8dt>E_`RQOD#X^l%}O1? z9f>At%v2*xVmICQ^br#BHh>NP>i+gml1}kZzt7jD#nF+HA5tVD|Ic855p_*yCI&UC z7<*m6E1Uf+b$|=MlMew5JiWiWq){%eyFFTVtFF%_BiJ9S{0>?I@Uz%C;+jIrSkuC; zF#B6zd_vx6r|fg?IbU#}tJyk`QX{4dR)KBnvdVsrft!Q|1#RM3z4*}Ll`&W3*`h!G zGddn{0J;0m>!Q!2*S?i5!COyrJ(BVK{jJuwu;;>EwLcL|W%FIEzhBKRaAn;HXY(UV zm?ME>V+Cp|K$MYgomKY&dz^0AzS0o6>~Q!|l!TxO=RoTIAoI9%78-z17ya4ysmkdZ zak8eJ6Q_2*EH;|=k<-(^-Nrkq#>7`E0vaYsEqU5uqiJ&n7foet;g8^~XWu;13|rRn zLbBB=J_5E<1-PQ7I8z z9mn5O7iO@wIx2|Ef87(@_rw&=W8_@_z(UYV#Q~%MglD+iW|F9~&Dmx0PPys+%OL>9 zsE*mCbfz<)ma3NXxjA`%SrR|`p!Q^Qy5+UiMeokU&&Qvot)*tPnS3ixSc)(E7BXN> z`a#LWfwG8$nS8{8=&gOaW0sP*5-(>n)d%;I-gL)TM0qj|G{@YAq}0c1=3wpnaK%HKZUM#^>L zI$*74?0{(ZEv{7v*R-RT+jMDV zzKfJTj}h+|Q6oWG3#=11(SUFe>oQv|1`MV<&>>N0fvz{q-nDWM><V)lyzLu|yVv*#8uU5bDn42GA*+N z9HFzMLZA^V`m5hdPeP|_9nf{$s%*)rHOf!!2*=;Ilm#aOT6uimLTV&GOlydd(((ER zi8m$xUQ5b4A)J`j{BNn6P|Y2Yi$m9NV}%;+#r@gJYjC}^53jo+rvb0nnL59D)VSgZ zmP}#buQc*Ov@5G-k>u>UIdiVvW%%S&MtR=kRH&#Y|QRheCKBsZA-@qM>b(vx8X z12%L~!yD@#cH!WWKzu4+X0)u-#r{gE$3zQ50gfZ`GTMhALL(`XCFd2p;H!!GOJGX1|SY!y-o!Gg?N%FdNrQhLRqV@^xx_{|&>wKpHwu0qdZlOtdPeO&P@XT^OYkBfi&g>Qr_ z!!hCWqLU@+^Srboj#ZkwPAv;}Zug0N?vvpuK?!X-C24vvwunAvPB|~Nvaj(RI#w0o zepW6I>LnD4spc6GZ{O5GM!WAF%^WPWc|+u8^2c^u{;*#}WVo|XhYY30_YtGxyxs6e zoS~Oj$E?HlwbLYApI7+%j@ClNZfy)9Fdk0$$5U^b&tLXTE>j5Lwl{l+0=YHBM|_Fg zoIbh9;A~@h{~2AlaNVFxRk7S*P_B76Ogm1rrZ8D^H(zunq4W9a2pyL1L+w9l)XN2{vkgcOuVmAF zrV=xPvtq20`aF$1W&04}|IUowAlirxQ^iQ)BhC?7d#8rh@3IFruNJK+VKNXl&(1+5$Pq?GfW}m#|P?S@tgkDGHSb43MDJz4&{< zx@h)puw%1-ZX`zI@a|}CtE}2ncD4>Ux<>4SSa+AN=y-G26$N$vtzVO;F=sbT^QQ`6 ztqbJ&*^hPqjxKFRUVND82+dzq^jDa?re$^av}*Aic>X3x`@%rEC#_Uo8gH>l5#3i4 zSrcIr1$^x8vUL@=Ko0*}pn!rKA2=VQ3MHzZw8?8NbC8ZUw8B2e)3YaYHjO@ON##f$ z&S+KPggQD`LovH$qK1e$2*l)d(GygLwS4|uZBSGH75UdA8;H_MmpX(d8 zixUsN`v@1OAv1ZBV>T9`4mUq7G1|YFojJj}wvGqc;}*M$S$;=p5UozU zXW?<;ak_C=(TYd4us8nyhEOR5ujWO~f&C4ckIHw(K~}qMuflQ&dzg1Y#UQRw5@xW zK&McHs?=4PGsv1N|6q0_1Zo}lM92ExKsKN#z(T4<=JsjhuO5gM*9h|kE8ma-54m-p z?>m7!GJWVim0hR#un#}Wr;n=BSN*Q-bV^nS(+A+SJf!Efg4_jhWtOH|KRVR><~sH_n-QZ7EqM zCR*x&LNJbE7^XwN$#F<~=q34#BjvkbFnfLHBEylwn%Zs?BnvJWMq=9=$PB^Rh)Y*B6m4cQ~pQRB`RmvY*l5HP;W>|hp8XLLh zg_%YsD2SWZ&?V;BHjFVHZEdNy2@QUCv^LE|`w&I{hB~e+vT<*o@k;l6W1W&*dD44y zG}-1=2=xP?Gl@Q|Yg<#*Az!a?3&(G`vnH6iM8)S7ef*;ClGoC(7M1Ph;+Xf5N**YT z>lvPsE35n?~!DkL2g5||$Q`24|N~wBX@mLkV?VXe;#wh#c+M13m z(d#9n@hAu@7!fg&65hX*Iy5dC ziw2&7x7CarzjZF#BR3(9^9CG>K^5feG$EGHzng4|3`Y&S^6_!r)DQ=7)#tRwGxAo& z+2M6;gqOu^aWn2DF-vnxcgFOBm7{L!+TY=$NO7|p(^apan7Z)!sy*?zXs_PAsr%l&0hEf+4BV+fC8ul&m-@aSIIQZdvLPtSdc z>#<4mTuSNQS~}<93t~gg!#`rZ;_=hd7Nk1BPh*PV*^O0a0mc)=3a7Z039Uelb4Xf~P3?wROpX5rFmS$FiS)tplxho1nuNO$~Tjixq-^ z>$^o>N$R(^6eLfljjTQqjCo|nYK#>%s{1X&4wa0Qwx8lw&oefB=@E`YRVVlfx>!C# zeAyK)-*(z^Jj}}x$(4QiGjl1D@TjR!bT4*&Gf~R9D6((YkT_K8ZmVBY-n?EO*z|6<#JCFh?D9}+98(_ z8}P+DS&qqAbXs%_Z{F{^UhmkQAso(m&(Tqe$yD^~sY4Sj(~0o(NBsaebsbrgl*nRv zw*j*Oui3Gm|6D{kVjTfUxDW8J4t-arKo!|Vw9g)o%%59ZLZ9zU#>i|-UY0H!Fe+IJ z68LtU8NNA$>-Dq^M)g`s{_69ZHC|gt1{ev@kSN&xhx!=E0IuvKgsB6bXY51J{CcMl zlxLg@iUEvoITQ`q1P^}ZFfcPE2vy@mtpqZoqEd^JG2tt8Xl~)x$)H8IH6<#TT4`ai zXIFh~^2%0C_jd^|f`!c2=E*IL^7Z>W-}(md4vsy^Odn(7q4H$STaPjn`Tw?=vbM@| z@t9}t1DyDm&~BA-PF11~%oDt;82zo%bi?O2NZ&{WbI zQx!<6_UTRBE>cMcWT52LkA&nRN?AoTdVX)pTm=$9I_ZxU5rlixXxtUclMzP!fezGqR$hcC2kwp2Rq5?~7Eg0+jT zWKv=(m;}#L8@-An`C#53+xW$c9mU?RTOh9s;i0w@Hg!1$U$U|+*vtu8?I+8Vd0em~ zO33Gq&1{A)DJn5pppgp`7i(-pgT}aU+60k|UWsh-G@m#{cu;WQz+AE$+Ab|c4Zg|?FL_)yN{SCUXd0&CmhCaPTT(4Z<~eDbMxK!M#TSGlsj96}@~H#-hOS|5 zsur^?Pu-O7A$+QK%$C2S zPfFyTmu0|#U_;DVy~45WrOKy#p1li@7t%-ZXJZQ zyEQ>O0x%f8*-t zNSJy)&*pYzUYXW=5okf9o!tY_#_Vmr(%`TCVc%yjf1;Kt@E6(J8=rI?>LQGO0|=XI zlOP%QT?MnFA>Fg;x$RH0YpfR8L;^iiXu2 z*7ec1HhQzcI%e%tU|DunsSHxB5!)BhQT)E|4yr&*D`m#f+OmF_`Rfu|S1L6ld82q9 zu^p_J6-4!{YA+81PXJ)Pk2RB!{dR2B`W0yHN-m^7S ziqS14vl417*pP8wT<$5lo=&&b^eiAs+~V8Xnq}f+B4Xl8GG)8|yjQg+BYcOiH0b@u z780!cVBobu)e5g|i+&Dp>+J!-R@a{Vcb^~f-cQljP))RPYrR`>SnsyFN?b%AmpFGe zS4n)gE$;^1OCHoqmQ-slk*IUbHbFaHuY$}3f-ky`OBV%0wTrUE0SQk{0qaf%*UZ6) zlgNcZbvplaqu%I-WHy4dVl--t$-JTOUT^;uEN|)K(#x&FL#hf}uGJfT+ikPcQ+^?& zRbpMrllV#zsn(uT4q^s(?GmZ zK8#I<&0pOu(||{@>f|$^$(-y3OC&)~V%%S9r3hv#pS`mRvH%Caxpk|nYc%J9qIL7O z=0%w)*St=Igzk+hilD`Pqy7E^ zGUj?q!E5~Gtq+YO3E+YODK&RD-|5<9*)kD+PUuGg5#jWW7)zQ%0}jMUKMIMh==F2( zkAQh2y=tpUzn&)0dSIv_FC?iCdBnVD0M36j##BM&d3rpGC7)3S0}VRsZrO> zoGi#Y#WOaE!MR)*k6kg&FRGD$o+@-u~A>ew{d3;Z2q>Nwo;bYh9x@;RvxEnm48 z?8f1``<6wt->dfh!Ny)N@GxB)pKcNobP3Pm`aU>J;d$X(n=pJC{LJ)OHTZYTirtiS zInYLHAUoAO9L;pj$!Y0yNS~LfqqzB@h@}9(b5zM^eJ5a#*UZ;5$cg^N6#CZGB4vn% z^-6RoXF9%1L7fH|a@;nTUg#HO6}d*-IEC|Ln?PFAh@}S_-WJpzO;>Chxxelx1quw4A9S8}YKuPNQy`RCD-1IH%3jyYS<07BVKkkXgpW zQeP5J1;0r=u+K}!4t8otA<60R+!y#^lbkQwx%VNLEHEURu^so;sC^oW6LvttSA60Fh_Oxn?mxQqYrlW-fB(T4X>dmPs|ApX}`YN)PNc) zSXpvQKNDzyruQe7h?l7S(`#XKJwp{69)PfM`1pH%CS0Ju%>_xu?%f;QE9o_P$(R%L z+XIV25rH`uCahQL^c~JvW4QSzLf2lkzHgfyJHA}hQS} zsqpz1GNT4LdXG()zQw0zZk&Q`o$Na@5Mxv2K?pC~a#+O6ZgY@Vk~v+KILEDkbo(JT zCzLTOkaY(7MfoZn1o&~*$`>0e>IeqLM4*;R{4vU@4+|ShFwv^;M;xI(1mo<{fEr7h+>Jmn$8MUX=@XE2HTSNSl&ux0V z%a7+M%bm?>o=4*1eN{)qd#+mU$@qsdV}XGRd$V_5dN))SAND*4zVy5NFX!_P9Gkf# zBtqbKh{j6Jw;18SC#8(4Y0!nt~AQrxH(n!g+%BmP9a>@QxFG`(OFBy(AxxZ$4Cq;;{0Ew0ripG)5Dpe(Shb;Tc}1fJs9>Zv!2 zaeOo@3BpZMlf0cg6`t|tFHyh}eQYxgl1+SGvYdF2F3(o)4!h1W>#7smdsBDI= z{h|@VBHsE>J*s$F!@fhKViFz8o&D$>2Pk&Sp3Ao`Yk#H^;Zl3jk69&3Ogb-ZI4>Rb zn3lP-Zc8f$@C%@#d`Nc?GKk1gmolsLn^a}%Pxo`XH>-Fri32@8({-z!zvQV*ZW=Tg z6I0xp`+Zu4>#OJN3^On?<=bHwPuV+PikcRk-cZ1Eaps}(_co`^P&j)>--CK>0k9Rx zUZj0%VW{XbFN^kJn9O%Rin37`FvH_(a=9CA+hv$h=IQKLg*iDcj$GOY+z$x zj(B@!G|D-6!#}ziK#;&>b$Vbu!|T~p)H$tRown6{kR>x(_<``RlV{mLj;=O!Rz|w) z7E{}%f{<;6-ThZw%NQiiXNua?5{vhEPru+5>L8}X3&nPY?~Y2(ob%2I3kDT-S6PWy z8j<{Yjp*FNF2DyB^pj_1Jo^~Le5gZWL4CIVXZ3Al>#NjHYbGYc|Kk7sCG|1ukAJkM zJGIF*e@r=!R(Rawjh>z5b1<%=k69`GZltsf{o zg(;9$!N-vLJHSyggwLRE;ANIo+<-$%0Jph_uAc7vJW4>Y{Os?qk zu&Q}OYQ8FnBFqf{j%CI!DrGa@WaOcP&ZXsX5h&6%J`4b(MilVr9 zNtmhNOl@uoC@qLN{Kz#CbjPgSoURT?(=gVu4vya0xQQ85e^-AeO84b-p3dj? zYeQjSU42rEu{BTnHyl9XevGK^bDs_*7S3mC#8kZ+Q#Fho3ty_&ww9X)1p=Rk`Dgq| z#Gvf7%7lW=Wn|Fq9SicxI^MzdUYXsb=t1Gx~ z4Ue~U@C>EH)}eiLk!GkX)0dAUFH9^Wk!uUbwY5IPF))|r#31j_0r4yD?(oK~Q*)Q(s=uAAvRJP-_V^P%r$Q&CU zL4y&6C5a4Vsu>d2bV{a2#2ZTztEFTS<3H)9*IWq1~qx)|PAJ9ucuxwKu3Y_L%nrhLRU3 zmCtCDv)~@xG-qa_E~OP#6}ukCoVZnuB+mRwyd}pLi2{)vAn6})7gOrmymgDRti>nl zDq0rYIdjPp>+cc1jELjsCDg=;-Wa~ zc9q^k&idu0Wh9weERPygRxX8NH1XQWkmoI5vRd{QExG5*y4+@#d0L zf7E0Pd&+P0;Kn+Vrn5iBL@@cLP9Txs3US7tvaT1nM{%~3-zJVM`rZOP)_sykq`G!Z zwvZbLV<^sfJQG2Ex{XjhR$!2NXPrk*&Y-b8(<^(|5HQAbobc~Z5ex<7INIB5K6avW zg|=OhKb7r`vo(e6DZ)NoF3qbcapea~D4K|!RG@#3D~56U-R-?)p6m;;+p7jZaR=v0 z$(Dt#Fi4$N>`o=HO5W7EhwIYjzM16rYs|JzF#$D4kMX-BB))f)U zTDpomzb(SUV_R^459v6QohU=Ux%bJZk4po08eR^HO!0owEEBnxmMPJ4+O?i?e@44B zqn+Q7BSmi_ai50=%NWXPD=foqonMWY#C2_JR^Jh-dbB2hDTr8MOAlD+x?aSD=^RmG zXLNl?1R~A_abN1c-J{YI`+2RrrUEBoa_f<#q0YTd@h=oIQVKFuNjN93iPms_X1ZOE z6SMeZ2F3lnmOkq82l~}m{;!<=Q?QF?Ll47nYVotNQ=bwp>ob!zI-Ov5TaWEvd8iRd z$_}qLrZ>A7EPM2Z+tu}=IozAf#h0?}2dXnD;fYkkZc91+R&<|JSv4{kTs!Z=I7HrC zxgXutm4`;=pDel7&>9$z-C_l>iUubY%305B$^p)xb-NFReo&+c%L=IX++pXLQ~6Jo z{vPUIPy24ahr1QQid>fRZN#(mdOHiekxOhGl|5$ z6ooWFmJ6)~aT`}x7FCuEn39E?zyvGJ=48(7)N0KgGo~4?*L=pj&bU87s(0ZVtaGuf zq2P{t-d?5V&jOTq?`|4A!t<9ykkry6D`m-10v zYB%k?(yM%(+T zu(&v6HK;P#?5_Fq4=Osd7k~sj@|*$V-LRbOZm6O-2h{wE3m>WMeVF7W;6wzbuuO^K zNR#GZ*x*TcnnZO-$i;(4W)`zTl*B8Qi)gca%ucinv+2SkTD4ax!jqsvSJ94FAzYD2 zZhQ3%`M#dgjHEhyQ+RIu&sqygIGFySkB{lBpbv^&^fA)6Hn%1spCy6I+drupjEV{Y z=@te|@>ct_IQp-~s%6L-toa|FSQ!U_nyK@9(w2~Km94B`d}yrD!}E0~A;F;bnKIR! zGQ*Ld$NlkNcLEQE`!Y&UqPj$oy!~1?2S_6}NTZ_n-$Kb>MCIqte`WIO{B(C0_0L7f zK5Y^z6TMOrHQLoFDPt%$F>|S7OY}*cE8ngN6P)a?Iel|ENCHz0YDH?9S1ZVvMjG8T z4K5zFn(UOpe`=+J{!HSEJfB@O9p@8>|Ko~XmS;YHTkLw@z0I1@jhra@Na+@fu6;v~2~^mo zF#j3HZ7MSpJM7g@YEOc*7+;FML*k7oX{-5)!8^$#nG8|(8fVm;B95pd4Ym^IwISW$ zvB)0er83iycaw_Z9`FlNzsay#(pfp$|gHKn5=6JlCJJt6?6|~QyLo? z!6?mSp!-_q`A|JLZmo+=uwqvM!<#-{KMN{0Yx9xF7Y~3mm)uT4uFizXaNKgV?ks%$ z;f88K&qol+&I>d};YczoZ7BKF4Z}jdx6c;P<9h|Y>GwI%m8{FS_S9Fr`O0HYs*15H zj^?S~;Olqr^UHC%&Z)#vRbH5vegcd|*P&}9yDQTvlL#us^D=LGaT3RFKRcuYO^uI@ z-@oQyL#!YC@e0Srn}-uOTM=5*@$rfMa@{vEwyg2Wz#Ez?EOQWZa+kyqxGHFP|5|`f z-&a}lG5NLpi+_6-s$gcf%ezMU39%0L0a_&yd5nTVdz`PQzTpx~S^#bZ6=jf!h$RZ! zXe`eI?6xYFs?IV|G=56nv2^uz?E+D>3wdT?iyqtiRsMAoIR?f+tE;AiPr$Je>l5|w z0@iIh_c9n=wG|W%=Y$B?MC9E2mwqmlKN-&`P-Yv{bv3ycKF8UydFP8ODAzUs zVBb-5AaPPB>UwW!OVl0(yz2GdPj0FVj5OLj{iIvmv^6Du$q4Or$OfGP!@y7AEcL&$ z@W>pXmU#(<)9@{kf2i?m*+0%5U&uIrjpUAZO5Phu*gdai?=P|I3V2>2`BpdeF8&W< zBFfosKQG!6uo<;nUh}0g-RQ>DLkX7cl_lI+Dl&={^KQiYrBs1Fm`O^LV ze&4!a8mUcES02ZZ!+tHKqwy{X%EuL+L*goD2(GJk@3S(9&$w%MS63VPT~`E0k7x4Y zJ1RwsN#AR<3MsBiFf@ zxaLM-x4#3z8silTmO31eQ;W-sYsN3lzFS_K5}HX-7=0`g5EviYqwn6=L%lwH zhlXxTUkdM7!CVcD?K8OQb+f%H%r`U(+ADbO@(P&*8&U`ahYdWZi)mT1*)!LsXxp=IU}v!jP(Fl5rlb21HUYd&vuUH zNtp?AV@taozpR3$_bB;Xyox!Og$>|{%?9Uy)10-c%cF2!Q?n75ZwS14)91lgU-fvk zcuM}XnulG$JlXG7NM_x7Lk(vNhcnhDd6#>@Mq;X6z@ z^c`Q?Ms;FH!ZBfNEQp=xJ;?e{T`KG=iimKj)1*1+Tq|C*8^@28xQDSE?X6d7CodrD zws#owkjOS*`%TN)A52J%j~l#59_b736#v>+NhzD;A%C)ujp_LG)U$fG`!U{bm$#Ay zN5|`fbgpYH-xXdx9V=r+eb;0UX)I>!+jw|C^CcJSinIT6UbI$7a z;&t7_aTMOaPV~H!2^kFghI&uM*5c^kfHcblb~%syF{0*iLI!t$BcD%mmB57DFRi_r z*s)=qn`;~0nz;;DFDws7V}cj_56sepTSsqi9Zw#aXp!_D&p(Gv{-VZTiRUm$_R9EK zieT_2%M3pDDDXYw`**x}Y}F#%Uq=YM!@8nj{4CEXpO*bjfkP|Nyk5pU!XBIo=!iAO z>gJr2=QhkPZOqNlh^E2{_0{pkFa;mU9m!tl2((H-bUCjk}czT5JM02b0ft# zSaw>}^GrN=7}XcNtnhRc*Pi_PY8<24hG$&q(pSa+-m;7Xvm%AKaQ+H0t&>0YeCTjE zH|R>)P52R}Fk2}lb&L+f&Fn3D^RoA?uW_>|R5M@@d5PQf@)Yz*{kk+ysU1G?MoXCp zeqaFj#a|xwz}j{vsr0o@DEM#N`Q!V+%g!W$VK>kRDl^nWjG2|mitxuuFgO4fG*T#9 zW~r~?BW!O1j&)$Z@|*@A!=$%G@9?;jNpo9jg4HI>gEL?4eL9E9gTKnPl!#-c)IpTY z7!UB10A}ceP}5P+W1ilr;~NPFa%=V48A;dz+gf^J`Da0#Bxw#DC_2DDtueyb2>-)<{H4v zyzhLB$SGcR7UrL~S&A-d!5^39VLwM~D+KK$sxTV0LNW%t$$F*_o;09gLd-R3-&(U4 zhNevLzNKo#xh#<{w#&cUz6Gq#813g5M&L@)|Ff~GthcDJ_{+nF6MjnMB0$+|raHzY z6cn)M3^r^3QNBv&*bU{oc2_qMjefd1gTP$keX70fwL@o;Vd6B|YE01yIE2pLZ8#kp zSyo5|@V=Qc?9pKUOGrwHb)TyY@9#0M2{?IpQKp3k1Um-BIV|%r^*`F}%EG&UL;YCV zYf9#eY|ZVEs@TM4u_hJvC|lZYOw_R;;xwN7NY+f2w=sZ2-nas>6lJ6Q-{VMSgMDTB zN^~~xET-H1fjGnz08SoWS!(`x$vXdvd@e4hUFYWAhGXgHDs%A|cm>)(NJ!W|b;H-^ zmHatZMwkkb^Im+hQ(f>t=Lx9|dd9;Mo`}bfe?bW!Z{4R*5f#kTs*i*r*VRQ7*!`mi z_|ZG$M++mf1N{bsW;<5=)uJqb@SfLNUN9)LMMVeJL=oLqKP}9Gbh*5B#gz=ysI7_o zwq}8v2aq)Qf4MKU-e3wZQusH_e+M3}4d0-}>scDzOneM%t zDHMvbsHBKBt53Le4iR&4tc{%K;*hr~5pz`+{hqKa-aeybe@(0nw5r zE`QQm@l=00oidNBK7qe|{~FOuGJSK2c_z7F#&4o7b-ns9#dBXjgrK(x{N97-TwChVRs15O zcxFhE;?E;(^CO0^Pg8$qE1WFGWA_t9)ngOk-6KP6_vfGmyh(k*2faZdiwfi}+W>;_ zza|JA78Na9L%o>O*0@+CqIMR%OB%f57Qgo~)~V`BwpCw%cKXv7!Q5x&8FMw*l4_^D z2z{le+kt6=v#c3vz^NaTWX;h%A~ut-gIVnmq>^k^6fNJXcw*g2v2#M%)BIV z<560D28*HR@9K>rMK2CyplaWyMlzy+R-U?&Gd@_>JJO73d4I^svDw9x)+r?UcmUbF zV{$CavR_SwU6v&w-&&I6uNB>uJO6Hf9leL#?lsPGj^|D;ZU3C&?DeMe*UU+}ML}`? zbA{vOuEpQ@I^>MiQ+8Gi1D=VVIhg`1I|=6QcOg%>&yy{*ZIk{^V(mo%6|5@>5(UoL8%K#nf-4=tLGU~IdKyPHdV zF=JyDE`8^`LT=qLvT@2mwdX%`zchNyG8pk1TSCbJRQ8D@P=P3;jnQ!V`L~(lqMv;M zIN)`LJIl^-SD`dY803tH-KvF&0Ar`7lw6i5MW*V0HM`HcR->Ma5)0)h23008EF<~5g=5G*C!%w& zG5h-Rl2$1Q`@_0)2EmigmYUkpvij;rZH1=y2L0nQ;RDdpJdzSh~v;H_vX%*?g zTgTW^h1AI-f5>5&?_#zk$;mS_uHmG!CkTWz3sI%W1Z@fAs5K#`NY%j~g(jN9oj=sG zVT=Rp&Nmtge-2v~xV~VQaXi|6Qb}Q6jYjb@N)+F6ix|9RnzR|Xz8Fbhp0={YXdz3; z!xN9Z13qB?h+sry?%su1VoK5IgKcl(WYse;O1}4?D9M2$B@TEQ1vaOM4%IcEf)l;9 z)1O$L?o1D{TneA=8-u>BKi>QB3hQ~-O(Iv1fH|V?=;%%)TODLgfk+BFeiS%-s>e&& zE=Ev~-aBm?tSQtxT9xqGn6ByAEB5BEpKj zq$iBac;a&P;RFUOl#oMn{%`)KLH)gw5^~yG>m&n9T$Xf4uwd!y&yJ`1Bz#YA`igx= zY);enGq1?#A*b!59mII3M%mJ4F3F>e1uS3F8E_OOQ+BTmKT~Ci3NO;MRyEatu6D#* zSSv-6qs>fqYIE4 zutB5w|55ekaY?P~`@iMRPTS1(Zl~o~S=r#wV45jn^I&R|^MK`)15nP2K;~8(B{Ma1 zsw^$%Awe7T^DgOc@X?1Zea`yfy~9;tUkYXB@>;TINu=+-5sy#Zd zw%vr98=JpzZlID8+1b&VsT@7VST{2*t0dl8b9YxfPHCl9f08>S)B1~F+t$-TLrECa zu&*GTUAYQY8j4F_^~y3nPvvLhyt5kfEfOK4b9Ou~V4cC3@Ug7DUq5{N} zfMOF|qq9CYy2K+Bor``LS~sx|zaq@>?|Qe;K^Be#NT(uA6>Q_h?n+`kAAX#3N<5h_ z-HR59G0aXV2DA_SL}p(SZ2Vd@6aK` z;-z=JSq3N7C>-CNA0Pj{%zSM&Ls^)MSj-w~DWp6Ffz64rSi>EbJ=X)x^VbvgoKrN7T;x2U{6R^8|jCb^ZTs^qMq`rNdEdoEU7 zD56u$CC3kLzO&>-qnd6Zk?CJmr}FJ8?>e>cgT5U!Z?$;*YUajH`xJw&~n7%lIiJCy=B1u4f4q z&q9QxI^-RzVwlHTsiF;ga(M@+Y-9ID()9iQuN{%G^Fx|`ow=hXXY@xJG1?J_5G~dp zLx&p}6~KrU-n^3-g0ZQ+o3|zF{j0tetLq*(KL%JaVP(1ae93QmHRdapI(auCGRQw^ zoN2wioN~2559CMc#yw{(2xj!tnf&^W>HgEo3)s*jx87Yw$=Wos6i0x%Vzyt^kG{MI zT~7wr~MIXKkXi!z+)E`F;ghbd|I61W=bB5ndN0* zksqFhAiV8z`)AR*l1J|7N$VEyX9&%bi26r#`8T~m^y|>-*=YdCa&IX0cK{Z!9lE*f zhiL$y89j)Rc`pRGdWg1K*zAf`X!TYuKhsls(VgU9m|=ev_2$X8ru@=&UM)pl;V*QY zM1`Y0x3XA`oXdGu2V*@#KEjsrbI1;$>-V2dM2Nmc@yU~qfyGgQVQM%N{%QOWUhvk= zi&cZ4US2Y5*Ecvi-JPa6j_`0eG4*q561UgX@O;#G=)u^fT8x)KZ{EeWnLXxABXl zT?InR&$9T|lPMhz9$!i)9Brol6}$A<5lEe9cja19p~@kUg3eapB3ILM3pu_^c$$@* zm@luqoVSHZjT(=zi(Qs@M2JE)RS0&-uIG^!r zmGBIWq(Z-u&nJVr^8~Z%Onl3kZzUNN!UDX5>6Wv6X5G?q zY_0$BYAjb}2CK84+mJ$E98o?J-FHeH+j?9qq(b~?Pd3I1($(paCp7=+c!S^>kb}Rx z-U-yOBz)ia!SuRr1#SBRzO|es`($}EGKa`@=N3DM({v`fJzAsx?%q(aP3PubO0Eg{ zfD5jLRh-KrKs>!owiMZ?xq#ywcS+)%Zn(_)emVcig*tTMqnr@!8<_Z;_MN-47XxQd zSU*dYjx=OZdVLdU*Y^AzFD(5WR=4hB)6W*Gf4=X}e6M6V6rQ&*ZTizF^1$FK-MV8V zfb-db>`DMYPym&o!|DytoOzAfli7DaLh9WXA*yT~k(+AhuiyN5{q(JtSR<61^}$;e zzbTb^fiGk0RS11n9_g@#YkpTUYZqB;6rod*w2 zl^}Q*j#h>fGUcRCL*lduu;q+wp$=y z4GvPtIAASfDCb^_SLFz!RoqOiS&(W@oq7 zvWby#y0jACr>1+U{G227FpN6^<;_pWMS5M!Yf_{(c2-OE2Z!I1tJWm?2Q}$c>U^Sk zFP5l$5w8D1YDhK5#2wNJL+|6&h@GEjSI>u|SbnC6(xbHDhtzOOxi7cK1#G5|Bck4fk9lgDUs)1hp^l=e+=~!$? z&8w&JxZb94--EHL0|!e=-04N1YW~f;S^?EA8G%#97x&s{4^C_*^?ZF&U^Xt}HcvcA z3yS>Xw|mUSZR=)&3;+;g*uG^^)4} z*L($veF$0QbV>&s9DguU0FZe=;$DNP`9g(}KuvA2Glk!IH7nork7f#q8FP_`|Di=a zRrc<~adFI+fXf-(7CJ0KfjOec=zwWR^<{%&?T#;N?-1r2zM4PJ(=9k^we9x~DFj*c zCakdZ}tTTj!ySvD1x^^Ipe1j$B|JZvG1cStD)%`+tnYGhuPq& zgPWaA3oOyF#WP%<)A2L@zPcc1-yqEb~@{3635 zObo(ZG}Fj1IA%>0mMy(MRB0O*&EIdZUOjS6!@4XX>BerIOjUUE)qt)X?sPXn&`S0r z>PAOyWze3C?MCJKTxp9bkS02ow=Q1WKG;|Qq;cyz=p)eC(efPChsL%z zy`938x7!c`jbhb@#p0hW3G$g;qTwo*KV92^04U_RDogoXu(+- zV=pgS-CFu-a1d8!Tlt~lMaipR5%ToD-TqryFb4@br1D^Op z?3IwwyG!L`tBE7-eFfpm1^!Q9{I78e=2KmR^N^^7dBWG>n)k^<^ZFl4`ot_YIL2!9 z8u~G?CIfTPRZ>F58}H8GA5K4Tr3fY0O%Zn-0n^jii0T@jv$f#VC}x0_m_2~$d@))()#n0vR*9BhOx~ok zFI>~;S;5}aaO%O~&xnCP4Bp!n_ta3$X=GwP;@WgpMrSsf=&lz(v3>u6eJ}1_-;2l2 z3)hc%(|F(A6Wrv=ppwq9-$bo6wvzuZ1^vD?)+d8ast{irCT@ zE-oGZBD#qJHN8PrXzH=IJ3o6P5i{^JYUEymuDfH%AE@sRjbt(GxHvlqfWH>r-4gXEbco%zhK!{oKREA>Gh2-TIw z+dsYCt{Z@#NS#s%%keG=HqJPaj#@1j+kO6(h|@@-5~BEWzNHr|=BKwBx706J{s#;a&B4MeT(T;{xZBxwmER78)4(bg6}9W=Dc&(f z8#&+$MP0AnNQW-FOLi^Rlwi$i@Z}Z!spd@gg&^C8 zpqJiGE}StGakUR&V%^=*+5bAu0>3~f)^o=6L(z1kzNM|{L6d!VvfPqtk~oI?5idiD z3ww<-Npe>!g`;7+TA=s$X(`sSE70U6pU0FRns=vP1Qw{rEk%x`ke{@Lg&+hBR~WH> z{>3UjItwOQ$?16H{FWRzM1Oc^v=v&)bBxa%izSz{22W!yW3jl%8ubm0Q2bLJJey$5 zgWpypge6pz*5%;1V)DBUi_`3a3&(ZRq?JwMPAL{R64t7HQ%{IR!t5%-0b6XyMvr@q>9BlXwbH1Zd(`Jc*(HE@ks%^TT9JG~ z>_4LHAggX}Qk^Kiv^VHVbA3}+(pwoqkO@5pJjy7RS_#FCt=xB6G zp4Syxu9E}WQgU9DGI=k*LCt)8&?!Y>4HWE$gf7u}0DXxJ%vsvpqJ5kY%uk)AuBN$ZmR0Y;U@ z{}ix~+?AMh^s%bg5maMbh9hcaxGtB{xv|}wHox!ex^+haTjFEcWC|S$z4JL=OM(li zt{?@*N%c|M22Ks?PkKdfo3W!OOE|%-(gJiCbu?`19BjwG{<)Pn2dtuxo}^&ni`m*^ zzfd)=%YHyGBEP0({-^{2ct&(+U)&C;tq=9hq^vaGJHqzJEYVa zLrosX;vp@CJ_s4VODPDhq0`N{9ETdOp)LuK{d zQy{nUFxcTgA5=m)j{V+TF6nCx4FklE{Ku8K2}5i#iMwL z{SVv(_;ZEhpty@SQ3Jzs&apP3%{OaUdp#}$?C5=j7$SRuJ^bkN^J9!1ooo=9j(r$>jeYS_>Syb>n}a<*v$yWD&ob zL}%9hKU^fhH$7=~ZUNo^9RA|)@B8MTlW>hN!eiwYcaAkQ?ST8B(Eo?hCm^QV$s)^WelYBJ$%^@lTs)hnERT7CS& zfA@KmR`W;1te5%POX7Im>Psr8&$RO3YrDVtuG+2lfle%30q2?gx}296ed=n}4|!KQ zDjDUGDZbTH#QYDsU(GH=E8wKs2miSMqM;s$I4_80#7<97h@UIv87X}pYnj|*>-fuu z`vDA59!c4@NyV1rx}gC%0yjE~QMX;F4~~$w-K)_0_c>OYtCwzR9eu|@O44H7XR-3{rUq{ zlGvGv{9hc(>Mj5I27sfk%3t=$?SX3?8`=|&>$IJF!zWIUw!;UJIp@7Nq%TIpm z)5%|F`}a?k%7f=#MZczlw`t+={~iT^y{O$;zd{C@iT`uZCl5Gj(Y6i$JCI8M4CH^E zF27E&(-yL3+t>fO6v5+fGx@)UBI5sl-MuX!Bx^Q-rHCS zuw9JBjoC}n?SrGW?-1-ix6^UxG1Al*cGG1fqNec zpt-yImz;zJ@i0dMC*s}ykjs`Qd9m?g8P4B6g7=hK&q8o&G3i-(cV%07H4ww6nq}4+ z(+tUs>`1(VF-1FExNfC6`*1W?CxZ9!$oy-NU*ja^S&m*4)yv!4X@3?o^6@egaej2+ zZv$36*)$^SJ!c3r9!>)2deX*!2Db|PdM(KQ*Zg))`DGnZRq;RR7L-x}zxmTnzsZ;> zU#!)sk&f4ls%Zb-zm%5@r!)y(%{L60*HELZRPP-tKt>gS(1aIGP91M+!dhOj+u(#3 zI2+joOgXJnkH*{KTAh!SM#gdgVNV9=yp(cW79b#vdD%pD`r$dVq<0?04mA|4RY#G* zxl7J_xBhd}SDC+Ew)jPByk+{EQkly7`I?lCT<{f%XpZrQlVb2ktfJJEHIb!oH1|U9 z{>6iiYdww^ETrr?`0V1nO=WxHZ3;XQ!|N8=%A3+A5{J{bmhas50_Z3_Yi(M)eWbY3 zXB25Q9f%h^OnLt^MLo_PMVfXE zlCR@3k~U${V}dQ@n;7kl>(ZqTfM@MkHh6rvunSZy0Dyc4f#ndl5@!?DY?9c~0TaKf zW<5S*P;sQjK$_c}32+bTgxo@kqoEgjc*F)udw;3G&f`3SFdzA%(;I@tj9C+~k>$;I z>v*a_6e7Z{mTnI1NnN+Kfj=6u2TVgyr4myghRIL*8WMlOJjx-FVCZebTJYoS~X)@K&6OfaW!&a`j*}x4wCMB zZizkY?Wh))yFZwXuR|8y5S1riNhF+S;U^c|EIf@Xk-|5g;hm#l->#DC@VfJnx6wUg zhdYMJ!mEf%j^~Y;;p%dBn=oO@#(i-47^mEp~ZM;$JBiYt@h~Ig;ehf7RFr|$H4+UOlZ@2f=FLd%XDyXq||~yHLstH zeor&C;+)tmeD69bt*~eLFljQpZz%|F7ppK6#^0Dj!0e&TRy|X7VL5~B(R?&5b9)2D zFVY9bTM2yrdvxP1nowSZ)<2^SzW%O){r1tdX2nKaJ_^l2JO0f;-IyM4Q;h_7lD&k! zXXYfv2Qbmj`>H1qo#B})UDvNeN;%)d$-XqJ@02^lTFTYE7Duh*jNsO z*ASJ#|GIP)MUZy&jW2ZQ)!xYJt8jTug>#O3yIom~D!$j}V9yQzubS=AO{OF_kg_o7T-j1K$pYTR! z4Ypq1Vl~k~YF*kOSb7c?slK@I&;p`Vb|UIe$tchYmqKFB1@z=_*Wz!1Vu@{gbI3!1 zxswN^Pu7`Jn#3j6KYtuG(GkRf;#%ung<}EhEfphMyf*vDrJbsaBV>i4OI}>+JhnTk zg9Ny&nsQ^?oPJeCo%U?m*CdUoe$;f6WtiwXrbEh%pFOpvv0y^BzC*zF+@XvRdCV@vw@tMd<1;jHjKl)j2U6`7I+5%;P$s@JoYeNb*F5_`ZL0G+C38))q3$~t4d#rd;oi1{mu~`|%K{`I{Y-0_pnns>X=blyvDV{V*I#Nvh>b6MQR5reN zoC@lQRC0A+u?*<{4L?a#@5llxX700f)XauCR7lV9)or6l4)=Kh*r-0<1xLzrwBB`; zO^JMS0pL5bK7sZ2tmoA`qHyLH+MKr7yNx!P{iN@!7 zwZz=msRFaGr5V%-2bhANxHwtQ^z<)WhdV!OZo|Ty$DX*bTdw26;VcGcgV6a+NT#p- zeRnH!9aNv&wc-ipfCZDKqWUQ?*+!kshG zi;_9eT6q#F>4eDROG0r$tH*AVl0Ebgw&N?y2KXQEjqz-K zr2HmC`pFaN0S@VCkN^^GR84(UPK5FwsJz#`$qCunFAyp0Y~=C~oI*3i7XS5u8tW2+ ziHMl(=W(Ig(9Qk!>hkmB(rHytzSBh@F)oSX@5#!Kc{Tq@?94@F<^fo5&oEN=t4PGu zRN|&AG)Ve4j~33795sEGhog@f)7CdYfY#k<7#N3`qcs=jzU9PxuRxe>_y*Xx=H!cI zO{)`)HMXbSRPRY6I402pN3BafIYJidj6!-4q(m7Qm43oKK(}}XL1TmW5yyrXz6+&qSVN7SaM%FaI zOqovM(hAT&O2YKf+&v&~`H%Z8CTH_JkT9ZD5D2^mQoIa*M=*ZrP8*T3Znq{|hkj(J z!b|u;UaFRPs~3N&)HE&jv1W%Z8vnKlty<20Zv!L~#j}kfC=n!iz65k8@@q)i&5`JdwaChbQ#Nv*wkX#cum~)^wQQ8&N{H(Wl#tT$|+^;#uUWZ_Y+GX?D7{W4oaTNh4H`Sbl zT{#C38S$PNWh)rge=)`;=-UlT(R9drG=`cXS_H~I`w>uBULoyC^$-H^9cE|A~o?9jD?h2^%yYE4Y+R%yI4h0 zE>Qd|r&sVvzkE1+k~r=O=rZ_dZsb%o%=X*?RX%)>qHl^?N!5PfqinosEYtdhe7vjNK3^a9TrY7G!Y*Jalt^vpOwg&IAcx zbD!isCW%*Q5?8M&2n z9sI}q`im+dy>(eC_o6OicdYQW&W&v7JLm`|IXT)>ZH4RAewKwD$;i7S_db0ew#Uo! z0=#UI`=!(*O`RaU6g3uMHwqgJ(7N1{MqQqo`we6%?`w0!ZC~B4N;f44&c!ZvyZF(r z=CotA;nSyzaKx1x4y&|1{l$pr(YV2)^6}wQjXbSa#J6kM>mlgAcXe~>#MHj@9)5>@ zG{f%n1K#4BnF-kJHM^x>Jz3GdfWrDfhiBqk8G3$s<5vKdYH@9Kp z%f*=uaX(OixL6@EcgjvK(`ewo{Ii7iIzM#?X4@wO&&FbAU>96(#|QvR8t93fu^Nn8$BqT-L9rJ?e4@HfH&>ZWT4WMoszV! zgmN4{qsu7ba;<*%dI9cePh?AONTIcV&5Mwj+Z>Ca=?KjSd*8Nljy-7bP`*wh8I4p` zO(?5bf~6CUn~W=+&P4TXkz>2(ZJ>yN#*(X;(h3mk6`2J>ZnEgVa+|DnzN7W)<74r$ z zCqJKw#M0a#%2&mT~DSx!`0q7AHgp3?}2S<;3o?Zn^oMevzblI97 z%;E|Y9^TUA^xZ9l#w>Dprb%j>B zSVGWHRLOc^_rCVii(2yc*4D68wDHddyfuLtsWChBK1b*6$tT$#rPMy4kvmyzA1Rdp z-Ecn%eY62;(Cy_i(chR?;FOzGYv|({20TTo~~ZStIp~rbgqZ z&hb#jUrxw7iS2=4lxbn|#&qN5Fx7#cX4Z+0p7s~1Rcu*3+2oliO5UgY7|>uMk|r42|5Vz*~K0trsIy9A??;s z9@tTfUvlp}Mw_FuJM_?PkY;NmS2~kuV$Goz7SC7BYVaZ#gqj62NXS0SImJ#iJp?S0 zRGs`obN22+HP!pYPNbw`x`7eEsUFV9fW70fO%jUKUmN87N`Kih|3jsVs^;}kU)sWl zl9Cg#*=pEoG7PoiZE>Lx=w1DQTUQ)oS|B*#FXOsuX`^A;U&5sVQ*g(uh3D+B@8krh z(Ra>Ji*O7LBh7|aiDnJ!McK;H3@=Ovi0-ekGV1eI6$3td>Cu=T{U#>>13oFjSCRm8 zQ;5ZOLV{`oaZuD?poptz67y5i`CGC2KonAC4fg}hvKm8>kDfo`giG+Es(7mD9&*h` zD|C_Yc*wa;+w;q+3amqttwp>XnEw2mHTAls+y&nT9dk`#GDmHI1cBIy<=SemNe!Zt zR`Q0Ht6El#C%6JKTz@D64qk!bi0?r?6)aHy%M}0v<0<8$@p&oLoqT z?MQBBj<2>WOXz=f#G+5Mcom;yM$6;a;3-7}ngJ`cXDF>hIM%henY;R5DX^)Tl`K#| z-QcAW_WcA9qA?*^=su>%z3#=%lSPfVsR(%of&BEoO0*02 zd3i)6=U)`ZFN?yaCrIs1#d(wW*Qk!`p#|%JB?ZSbWA3b0>ZizSqX*p3I-=6h(w#pi zE$BO&j~!AE+9&R!Rd#G}f&4hY9Q(cZsgtFRTw=Faq>>}$eJWKgT?_JcIN?B6L448bO_JAG+23#qLhFWWogzC$ z*BEU%VWwH-x*0J7>FGfw%+>N#@Vs@uLhkqZvs#13-MZ-JOM0=hZg^YKvCx%GS+DdN z%~QF^5Z1&bek2(@I%<*~G}*21ieH&)EuAeVrFUfiIqVCFKo2Td*1R`yzoMOb)6# zp5tH}rO!*v_A#$6E`@o0zYCb)8}BWG0jo^Pd_ytKeArubLG5zEpltM%T4@cmXZWVR zTD$&0B$C%`;Z+6aW*A>a2+OJT7i&o~aS>DM2}TS>!65W_*v~7XB_zdWr{)~zdk|80 z_u6IkWqi%O#VUpx5CKYqM8Th+}=Ft17p-eKjwiAut=YeKwcZX1792#AJL$?N5NbQgM6DeZw;8)dAK zbQT?~%OC4(^ewarjJ6THRLypfOD^ zCgyA+D+{nJ3J2MXkOt$1j2HDMO*B5|A(!faz@*+Y(sF&@@C!9N-^x);A^Bv0`aS#u4I37^Evq8rAF>!BLB2 zDAFAXQfJH%!Xy%ZP=Kc+q`E9;wHPkG?)NF2;CNgOjEDwg4eKy&T*X%Wx+>v#^v?=J zhq0#TkCGj`-b)U)2R?FI5!^P0qyvlV8!Jf=xL0Yo4QZhQXwgg7f#|#>LC!! zvFm}|&xta&i3RAZ0d;&A4O)|pkQW18rEG`t6u$^ji2UB|7X6b>iCr5@I*^$QKDY6JA!2)r;Ghn^4 zaH$Se_6_~sHUMwtsYL5EDt7LtgkMPP6E6;<_0lGkRd8-Mp`lFVge=-Gs~ek%sBGw2 zM|2K?PDl%Y(iV8d+fbXosQjqI<~t|B`Nq-$WQ~=IWB%dO$!a?LhK?QM9ru|KU$8;-WJO5rZ7;IxhMu}Q351ioQm^}5y2~PW&L?EZKj(#HOQ%ac3N<^oQ6@m9TGD3 zO#E1*Q%zk?)6LN*0D%m$2CZInRlCYZ*)~6BX{+T0h_cmfrFF#1;fTGFrdWz<)O_u0<_Y6v&iHkC^pY3M zAwn7-*dp#R)Z4F!mk?{1sg3wehyHobm_IzbUvBS0GITtnJzQ~BMm>J?rwynTWYg9Nk6#fldF1Rd-tH)7K8VhR zjysgTt%*Gs*j2#EtTT?)5o$Xx>J5%dKX(Zdxn}i*1AdnAv!XF9WsVqIt%b+Wr9*C zj2@P4V=`04g+g*>kT!8L<1o_KEIYJXBXmbgU7*(v%G^Z(&{G-)IbxA1>h6vzqFK}M z<-enE=}rGuy8J0~a`?wczGLIlx@TD1v3%c5tPQE z5#gt(I&%w2j@IRf{d2p~Hs5__uhBb_wnQ?bM&8*Xr(c-M3IZQ2wL- zOjcj!8y6}MCWnL|(PMy|c-?}r-!S;fR@ZMWdp`>4!B&=jxMr_I!Q>OZJWm%E{%gCM zzXh@)ls3W{%BRYXy)5n9!8ouwR#R;Q0d~3Se>P`DeKp?YRb^|VE6cOe2sMj1lak5t zVK_Ul3C6%7>-l=>-Pc6}Tnk1&cfj1|+n6EE2D+xpSvxh1>2WL~eE3%EyX3m40*xp9 z&`LN%4k)A%L|lS0A1S~u_g>*P`8q_DPaD6hm3@B!*4qih5Fv9l9#^DvD|kc6Xn3l) zR`oPAw(R-vT1VRYvd*EkGOVlZa&MhQhZnlJbDix&{QcplY4FO&sV-e%UO#Ei z<7O8k?bHUhOqA-nMAL$`U05M4Uguq3B87jD@V`fSm|IoM*S49|u!m_J>6i$1Ck=4G z>_g^_@1*9%60|>m2TFnJNUSe?C1`;}yNn6cR@XKG&n`qPmaU*dxLw9d*pf*kmifMe z9WRxSD62c- z`{xv5^Lk$Snvc4~vR9y?*ZIwi9fxTtTnVT#Dx=(jKBA^lRT6D=ZiKVH@h)$+tLvT> zb}TB0ERtBZxG(dYMxDbx_cY=;V@GJ8Kd73$s5@z8{_{YF+8xVV6!7ia%}EF-mg@9P z=!GC2msJ@EkVOSF;(TQ2^=+;$4=5;!30~&m;n+wpeym#v#76zEif={h(lUpQ{ix-* zA!=6QlS zgs`!=s~j_b^l`K4cACxhr*G=j?j4K@A0{yi|D;*Eh}MbnhkF9|sj|y=#cMKn2srNg zB69FPPg>D*64v=lT}|0VysX6+p4ava;@p%rl=CY*V1=a58^vd{ z7$Xt;ow*7ULl~xfe17FNztH(Tp@2vF1^2Byr`Yl98GDbZf1q~&D}3^!eQ3*8^8#A` z+QmD&=S!?kcRM&JeJ%;>Z#iF&4^@bSA&aY0I2o(`^f?1Je$p`tzSm=>LwLq+;IYH; zp>9^}ECbisjTtxf2zgmusnVnovyr;_u3L(-8>g4Ke5QWgpVIu;xAxK;zl8gB9IQsy zum*iCc6ek1v-}(sfR@$8;>sWGcXS+=c#$J8R%uXZ+LkpZ!!-`LS;bjZ!@{4-gA}Jd z-xoUx@-nYo9m$6Z{=U1TYW3!AgwZoj5&7p=43lgIN`q#6i+Ua}Y)#^c9(I27ziAw4 zd>q&uFeW|NqOr>X1y8?LO-xFDH4sR7u4opQba{R1brNZ6YLI_CI-GR@UTV{*<>#T^m!CN*ls^`pg%)YE6rw3e4U(@VSU^>3` z=h01|OT0MA;nDdSWyUIz7JQ`^a0GXx=D=r9SsZORtQ52IKJeI%xaJPzjlnPLV_lCG zbccXFL~S-&mq%OGEdl}d&>T2xAu7h4v(_hCgQFjXJT&bHQ z);w?mc)Zga#vyG4A_I+6MH#)2+85vozG2dV+)8WRB>#b0ghx?S=!M@-JdPyR`E=TB zadX1f#mj@2F%_LLH%D(-)PK%5pg}_Q;XNwQcXNJ;7tLne==BPZ+kso#u=*QAh1N9B zQSFUd^j)5zJ=0pI<&VHZjq`fFQ_<%CB4wfzP~IM%&SUQ+b+chxJEC%6JDKt$?s`Bb zI=eCEjg{Vz*`~GbgDun$QIA&y8rINlI47*GP_h6=RMKTr2KK1Howe(vzy_+l>=I(K zlP}}@LjZ0H4+E)U%Yx0k%x#P6QnkvtId-)Zy*KSqFge}*L0}~dpn_x<^Ve&b7+!v9 z-YxU*Eey}v1j=m2>@M=XQ1y(09Z=P8;-=meB2-n3dq0V5n6kO0DVr1H)4EwP2Sd`g zx4+n;e&X`@%IcQb)eOyJG0YLCOBkweS+^adE_}dtV0we;#q718vFXhgqfvI;-M117 zxfW`1P36HlBmUu0H0JMcq%iHt2pYFnqzxxlw8#gGT58nh=K4cB6Dsm9(PbmchGgF{ z`slTy%Jio|+NsCvqtQ!X&EqRTGV}~0^+!>)#wQ{ag@U8W41V(I^JZf0PL+4i4>twC zrB9xqymNN9fHgo1oIGbw0(sy zUl{>yEKDXfPX($aKu59TG3gp)xGsD?ng}T9x|F8ztw&LE+_y=9y}7-+;fJec_8G(M z8>gkB=rxh|)mm$3cTCT5_T9C{`hIHuR+f&5gjW0Js@=JBJ!NLPvaBTd@a+%cKs^sV z?Iu4OHMMZVu;u60xDYtA(Sx!QH-kN@-t_YcXF0v(8YbO5=2m~%_J0goA=;QM$!x^0 zZLGK-hj}v_ws;r-yPzvM8>r^#5f`Fj1(PC{IC}gAWcduY7#z=cUsDj@tIz6Br$oFN z*{n9C*EnGwJJF0|<(b-~=^uM1UXjAR)(s?)a(5W&g~hD?jkkJNr}NX#@TNi9p2VcG zQ~f17%ICO0K_cj}XVvMAZrm=%xTjcdrr@Oa~+#ou3jimPL{j)}%{adPLe z4C#@~=5pkK|C8Vk`Vryxa9G9e%=bkVScbG(xI%-~WUEZV@%X`lU%iZi7kCKx=T~C6Zd}Qx(N0z9F8Y(Q-M|}!`LMeiqjojwJppAc)m0(A8arHg( z>z1aPaw;-_m!mh%scW4zwxGVn1lknf;T&Mz456vd3w${fOLd;kMEEQjj)Bq+fe)Tf z9YH9ZD}5&d173551FDxaJIXQE-;0tqTPW@(pNSaNn&bGpeMuZDN(>AA`H1VNacvV9 z{9%YD2;C@zqN~=NAs;EWScv(hm3t3PE@dbh26WlwCF3gg z4EdL~28j9{rxo3s;pKs!YD_(FegB@~gzMw|)O(Hvrvc8_q28jqw|k~j z^yrP1@yjxcBS8`~F%Y(4Xw4T_woNa-a+*SDDZsVWIcE{T=5BPN!Li%SqNhO@v9$VG z)*2TEEkgf-$Z-=QZv=7rZTR_;Cr|FtQq#VPB!j*+kyA4d;?%0R&F|~DX)kvbiC<3+ z57&x!9{TGMGr(2RQsb+AQ+`2h2Ku`;C--LFkyl`B$Pn)#-!Q5yi0k^SzO0!_S-dcx z!>uF2E50t&XNZ#GK<11OB$?FA8kc7<07^OR(hRveAW^Cuwpju)@_I0bn4I5RGVdk| zk~@IrwNW(3AJiC;2Es9H2zpORE8GZutizh}(YJSd02bL#tDESJ9QJ1E##?C87`tO{ zf&0vZjXQ3tVR~=%vG~g=$A9hR4dTBVHW|ts#=Y3Va9B|R5K>iIqxbYnZR>>n#X52C za;^`o4a+HB%v9Lywu0lIhT=M=d2z!Pg9H;R@kVOqsC)LfWaV_l0c5mVZh0rwA>Z9q zZ=(e$p$R~~YY0T8G@Mu7ubV%8Zs3Y(@SZr=QgPSb|EP%yXY&hyw1^ z?*Y9(Evn8}c}xqfuUW*U{RfWSF&gCn9db#O2+lLxfmtQlwnv~hF@hH1HP0#B`_!Y<^v?*HageXqL*8x%%LLX`-guFAlqll4uc3T=0BjLHuPHe&)#be)PS@ zin@!$#ZTd5O{S_GZfuQZLisN1NE^6nW@vtB=>=@8zwy4B7Htjl(fF>9Mz>-1*1KU1 zc>s{3@5)h1v)lwM?qa>7#ZC3ztl@sJrz^cuCrC~~JoM}Dgpsf2M6B4HSj2VZVZig# zX*Q0KrDD!upx+^psE8@Gj-3K^nMY=dQ}I|7HfS5rh&RvXkJ|)x2m5*y)i{qu&6kvX zjJmGrXL8=E76mSGAZYFR!e1Pm-s&0R2cfR*b0^7Uv zt7MyL61bESy)%Y~MQ6_1(Na@f)pYai=dR_ZK-5Rv&#`Z{qlIVHQ`2c zIiJ>r2yz4lNkC{{wYlP9_lEcmH)nL+4}&@&g)H^j=G-6ckgiD89njq3j>mJ3h(H$R z0n3Qw;Nhj6ty7OVohOvu=96b=$m`9OVh-$dyN`m->_CvD!r&$Nx(&6IyjEzA ztmwkdmu!PFP?a0b5KSl_BjJ2tCC(B$(OvGSd4aa6Tgs(P6<}MCB0doOo zQ)#X_eE$@N83~clk8e3~Vz_1WraXIHb}enmW{JJKNR7Y#Z~x)=?!>Qbxel#>^L1A`AebvoXO~K?2^bJZ0Vv3OIC6Zf#e~_`8Y_g=}Bp zoyYvFc(+b1yA1S2{I;$|Zyt=o!H}cPK5SW>$5HXV$mdtyuww)6u(r8?{#dq_D$A3f zBll(48eL(K@G+x92da)r~!&_-nDet4k!utbS{hu9x# z!5YyqAz_j_f(Gv8?!R0h+mVPhdOF9WB4cZ^EY^9AC;gR`(Z2tr=6D~bLij?7v&hg2RKAlnRLu?60GA)l*zTaODqV>cLSGTNrQ)jPXkrbZKO0mx> z3eGiBM>IY&H{sccfa@^l+#v)aIi|iCY8cig{NOq<@JK4Ae~e{3FM%)z<6|{#W*bsG zthp5sN;cKH3g~m((OIC($@$f+$>g7S{z*~7Uk}aX5NY4)O#c#IZ|PebJNRjDB#ANdopwkP`~Y$lxT#G=0N^Gf)zNGGvdjhIF6D#g`St=&phh};vr zsatkSFj8@!Sjm7ODh;c;@ix^JB^Y0cX>H6mqH4X1}yJ;5}=m8F9t0eD$nd z!${D#FJ4`2J|sc*%C27-6U>R|Hd_)+E8zgn@fvIw!kMsI0(uyZpF4X;;Qi_nRv);3 zfSj3`6cCb(7Rwz)l9drolOBz&bi1@qUVve@*XL@Vb836N*xo9~w~tuuYFfdc@T!PH02!Je6KJBlAYfBEct?HHG;DMiIP&SQYn zXMQVQ!sTl1gWuY`^2$5{^h4|je72ydi4X`8eP;$j!zW%8tWf4h|+E>`F}b)7{LH)S~IO7bk_LcU3htBZpe;0L*5FY13<4! z+jim<9U(^;2Cs=q^uB#`3v?0XpaSoh^Ad`NOG}TG8G`VvR}i$Lc0Ent2;dF_NVoJSGKS zIp;JodVR@nxAV1ysK@^3S7TSBpy~HWHhhzH?fFYZw(AJqz)_+E|)MuJ4`j-N*}v%^h-^G$rwae~0(mkc-K z$L=hXQwIU%>P3(Ku#SaK_0`)oOSn{ZL41}>a1UCJH%HamCPMol#0VIWO>lbP+KNBbzyuQ8OHk=ie31@NrB2C!N1W^a2le=U(h63mAJ7ppPMH!L; zAqTC^a&{*{+ehvrZrn|;X;6-jB|`R|Ob3q*uB`1XEelXr@xE+$&N>62$Q37-Jzagl z=R4RpluVTHE1R9~%BY~`(L2vFHw3ols^wBWgtcT{*JNgo{XZt;UQOVj7hIWG0q%Lf zzv~w?p7i6i8*Pr+^4`;dfalfaDcoLinZMwP^$FrbgD7WjBAV?VEQUx4xqMF4GzN4x)_llv|T z7lf5m-EKK~zcI#fDfuoEgO|ZKFx{aWTRKTRW4sDHq^+9YdBCBpa>XjMg2w!5v|6}-Wg}{D$vDc#d*W`kCj5=s1h(7L$=HWpJ%i0$Bl9)Gu`Az>*jCEXijZ76i zk&Upg+`#ZPR2ftdVKgs0Vc%*`6ad!EMx^adRz^fJey|^OSUt;Av zV1tkwVnibyqS9;2-Nn|j%0->Bc<^C9c#mHD7q=7JNa7J~+hG=LpevU52#+%HiXiXY zo6OH7@-|Jp&mG)hNjT8k>Z88wE)N zj(;d0{o2SCz7I$M3+BiMK&1Ekuli`XSvUBY1T4Or`0X3I&jtCYD#r~cHUv04Bt(*% z!!{@hO7itBTamkNA>zHdWWk5sP(J4N<&%#lq0X<sV(rRUZ^v2 zI=Lj9w~3MX7AG^r>mV-=v*_f40+Mo9QClVYg<5+#M+(g7$j9mqT zKZhIl=`jJH5D`m!F6UUwrneDAOss9FC~NO4CZZ8aYj)%#;huYe2G+~Hhh8j+&2ynJ zJ_%U?h@(CPunj=hepR*Xn5Z2bITCP=d6mUpqH*VA_CTqx|L3R-*eKqC)XCjq-MDu9GY=FK@~Q@WM`1Me`vq z-H$@He*)tH)6H%5!723*Hu3AVPoX_oNcE#(i}U`ELqC~_B?hyjCmCBrez&tUK|z90 zYL%Fe#|()hy}q1b(A68kXXR)L@8JvVCQ-m~aO69i6~rZZQDe0X1MnrwKxQ<$yAS8( zjI7|X2n~skUq!asuzkuan))WoZxb;ckJ9o` zHbp_Md!pa2v3j&n%>gq~JzQElvny+W?XWhVdH)3)b)#ke7ubgVO&QuIiFM#`SID6k zv79ug1buiC@Q=C`rh({(VEWF^4hcc-2oBo*J^iZ*pd111UJdmAUZE+c{1zlusff3M zudlgWL0K_Z8|1jD&_{%)PUz)ux$MVlSXABZt@WE|_|}xeN8NLxaH%)9`m6#rOFqK_ zCgwOZgob*?ZHzW~Bx0wVjWTyA$4Z(Ln}d#O-wS(!gZj$f z#O_mNFu!;KC>Q>1`0Qh`X5#sIAj%z<$_kKlFPKen=pT6Wk0%Z_pP3zfL+pe3dS=E7 z+(RCdq^I|eoBAP@dnRjirN{I^H+LUei@mH08-)L@S3}+{j#+2Q^hXHdp zJiMeFPs}S3cF7T<+$}O5AEs-Ur9grMCXGjI0X zOu7xN;yqBCYu`$<9J@JXQZwl;TNq&(3+9JxnpC_8qYZC56P$zV`^C^)5Eu2~pb4IN z!tr>JE`DbcWOp!&KJWLwT~BCm^d=J45_I;ga8Bu!)i0<{ou+~9*`U;-ZQ4n`DI<)r z#ShH;H#sM_deGY(VqL1r=b5*~T&W_fL(9In< zqCJh9c`nNUrq1K31|=1=gmYmnvakJX`Qni)=8_DISaHgQJb$z$(Qq*`1L7aft(p;( zvblm_)lk5RrqY@|Y*A(1ESLrwM(usO0eR~<81-Z63ousYED)I{gpM-PQ+G(-AeQcE zRaK#s!wJl#SJQ6nUdCKgZGy@PRgid*<}2!9yafz7$s>$=Xmh}U2Jo5V+0oVhPrfdS*r8ep{ih6`_FDisV=va8aC|&CnFIfAb39wpww);l_0IA?>c%fW zr7o$$UHz3+?J`6s5UpxcVn@<&Dt)BXv64d^u>PBRbTgchFe>Gr_0nsHLDZmQo&x6q zJXngPZp_Ll|VPC>a&xHOl8;@tdei*i@~MOpq+BfPAb)*aD+OWcuT zgi5(5>=--ZnL!DgZM)@_Nnf%>@HPi6>SK%Vm2zm>MhzUs>RpmC8)WgVCwISu?6c zJ9Qs)o^mQ^yW;eqp%UwWM#{QO5q_Io3|LmjbTXcx-Rumng6oe@kIi10k=iq*p-Tez zH;QwkGOpAucj_m(S}RyXv@zyDrWp*kXr7qBiH*Znnj?BHUf|fj{<-DW5#umE>Gz~M zb3)Zv91B$21wFGjWoAXpQ;#7(2+i3=zdoCvDXZyF=lvA%oDRMOkv*95HvpU~4#}B% zWBdcA?R#r}lQwrQ5X*vp1j^VpWCbnPy2H&ut|3AOSib-y7~XKvRK0x#K~4*d9n%qG z%jLKF4k?UULWnT{FL>of;;h{(!ob2okFg-Pkr216l=dN6Ug4+hmH;adgpr2DyJa)D zKRv7tuieN>IKUWY6MnQ(t?R4 z$~BG#c|je1C5>Thxw8A;s8T~GHc&Bi)>j@HKjNZkQ=>sm(?Rj7HXp5-eOAnuE4vfi zlB=pzsE4`R&f2Ui|GrKngW{yO}U!`V(qKqVj0oelf=H{ z6ZzS%>NZ>6oQZ>vzW`LEPl~I3)sW~s+PZc;w z^Ec3-7t*ZnIO%~l(!4=SQb5D`lX@5@z*_?$nZLfZ;b99)`F@3*59)w>C2uT+ zlrT(u;7{Sj3zy(Gg|AamvLBi;JTMStx@;)od%4wh8QQ^T9afY&ta_7^J?iN%eW@50 zytAPx$KX4mj59f;ln0!{@TZNrik@FvU}WJ)l9Uff*k{ON9-qfURb{l7wP#RD@)~Q{ z;NO!`$x8RO+OTP6mXBc^n!fgh@D_krN~sdJX8VM`XNx&$kp;RrMy-YZWdd^0t)5!k z33z;Ogv8VskpW!a+89Y`qN$I&=0SMiG1ng$V5?J}x57E|kb?BJgBYs10wjV*l@cgO z7t_u|@mco{4o2vxb{X^M4&z%1p6yEkkvle=MYeyNZGL0bl$4_J6;fAE&98bAY~Yuz z+Ai~AW>-J4#evl31%RSI60eXghv!2ken`!tCFYHY=O%2hcUF9c(VgTa=OToVwSaYhTPzk z7ChMH!IOVi!N%wKZX=Cek&!iKBA0e>$dI{IEf2$H#LO*_n z7q7tHeX{(=DB397dE2gImUZGUV(_smSLR6PuU|AgZw>=0ZZY9w5c*)g3B++m-XnnL zVwyZB#dU|-kf);0xYI2P%j>j1t9QhKNo!Yu0WO-(QguyU^I|(QWx7V4q2|>QugI`E zpQ_$;Ton4>>bx*0unq9MN={8^x~kz?GX!Wn4@|%*AM|{Fn?nlcv9n68qf-|3xBik)U$3klTgb8q-Kb!tqxoe-Zgq%g50MM`{?f zedLTWX)$OK;6aP48_?VMKM$oqN^Jrp3u9{;gTAT3}F9{B;h8G7z-QE7!FpkN~A2L+drQiWSp@I zG9p`#A_yUUel%&Q1{G5FQCOq5O(pM=0{uS3u)ZQiOo`5d>M9jTg9Dpp`O3=58Hv3} zOek{ELSxNbqae@8tlCk*b)e@)Q?(#N3l@3GNo4o_xZle{~!++`r`EFNXD-CzAf{B9P1;7WJy@Rhf^rCn4kQrdV( z;#3$X8W}ICL`Bad4Qe|P^xKO9aOPaGcu8c<(D!dvzES#j34g4#@v*C8^(n9GjQW<# zx|Y8`Dkx;H_vGU#A*@p-GV5m8UFp#u-E1!_E}SHx7N?ff+&>uwIfk7@*&(`vktB@Q zc(42ENHRddRn)o;E38T6sz=Ay=%%98hU2jl>8^~|B#rV*J~pMSP9BR)qEyo<``svw ze)(i#7mjZZ-Ob}rdYhK;>S6NC!%!Z|6#GzCCRRU_#zC)|A*pb8X+EBYd!!d!PD3}& zz+9#CWAnDxkAgi+ii)7s`ike;AzJ;a@8Pp-2FfhDJ5wQPaJNRQ*`?$D7=+%Y=Zl6` zIPQGaoNV()XX*6n!d(>pEU;PcnbCYVLOL6O6dk5kh?NlWAUpU9}XcnAKwcI=fww}*l519fk2-sC~-FK~qac})kJacGSf z<8SDv$i#ZAcpDIp*yt58|weP~Sly5zXxCx+`Dl^5S0&n>yF+4b)ZF1%y+eFHn%Zes<$ zX(JZEKbR1^A8t5sb0#29-J5NyT6aE9#H%UY_a}fiM>xNRK9QEwy=vgD8X?du{g&@J zkvh7^f0<^5^8oBe>7$sC{hxuEBELXow^crxwq82H=VhAbPk$)bus3t&F$U|SgCBjP z-mD`{rmIx`QtljyzgZe^*2R?ul6XiY0Ke60Yp*aR%CYoOn?`F)5GT={qN!Z4ww*$N zbV(J=hrVm?lO&*OmmK@7vOeA$_sB!aE?7ZcT0;60xjs*}0J6wL!=Uy(H&^7~v6r_G zB9sy*wlfR57HA`ZGJN`9@8^FXJjG!Rk2<~9$;hMQJ0rjAh!pfeXR`x!!34LR;Zv zL!qMCoFB(i?!c+aq0tBIXKnE`K`$c@?J@D29sn7y2BB(7T?bCl@Z-XTQSbexekap} zxmj8T+jgYbHgI!rDdmQ$7Xh+8S459lc&BuBMzDeB%#BXY<;y7^`MtU`Jh@+{=4`61 z_O4X4Q|{Q%@hAFQJXI4e_MNgOs7Kmp$mi(zTO+H&g&ou>scKck6E;`ZnEyeKc>2TN zaDTDpVgI+=|8ogLOr#FKLB=%mi!NW)xM)@x@!iyzPp zY!8e4M#ErKz&TSs=t~!e5fJIk){-uHYkAh-Rh4YYWPrfV_u^*qs{6`CVl&-VK|671 zXrD9P;@adLxwH1)6UoVo2fpAgrVK|cch;lNqQ_GqnchyQ&FyT$@p|eFbnI^6#HbCU zSt?n0GjfFSYMPfn4Too#w8*{PU{<|ZnPf)y-DS4+joDO_I-Y7$ioY#k-y-JMpp^D_ zOR=UjbUyODN$()-ey9rhHBjrj{}~k};pe3%8a6){Jqv$Mal#d5zTx7{t|zUMlWn9O zhT1$baYj}?zun`#zIi7~wYdFWc04PoXQnNhab3^Y!D&LLwf3(>vk3+ITYV5rhO;v- ztMOCO*3Q=V99MJp^FRJeM-w8=-r@r0I^ zTbS9ov}^>ov4tCf_sb-lUj)#~QV74@ZS+8Ex1>lZZ12|zR;|AsHZe2eiT>z#jLFcg zur?2X9e(a^+MRPE&9SPV_L+*FKi4LI-EOYe(9qu&@2ygFk84>&RaLR@cDgH5@-izR z^MKHYl{nN~+~icXA+!aJ%8BRg*xiKJ4zkQ;M_~kJk57tTSkq3*voZecJo)Y&aWh%J zx9&}>NH6#Q?$(07*EC^wj0f&if*V%9N%NHtQPHmnr#gJkd7rTammwH68y7!UChW=pabd|rQ}tMAXu?98~zK{WfPu3^u!b>k+00U%r}@^dpE#1BNF z<|>}a?9ycqkl8M~)A!q2wWW3U;+{*~cq78v-&#F1XS_Y3^l7d=*ZuiwI^)of~+3(9&+5dxwWqOxCXUT1M)_ zElwX0cjs6`TJFVk?<9J10U;7L3c<(^Z69pCx=1Fq_x$1J9;`0ClQVm)^)9m?PbAgp zvA;F12B8g|A;p8DcAmCoMn);1|2PW7u_=i9go02Ty^N?;yq{>PMteiB*~u+9FaeRt zD)q3$ySccXb^Mmeqa381q+ZCk=12Ftm%sf8QDGj_iQNAF$ERI;eZBU2P)_H}P(*E7 z64~|UNsrPN?P3KGSV@muZCK@FpFn|jKo=lGt{|}$#Nt~uQXV8N9+PFCz27JQj_DC| z(za`^gjsY0SaMQFPTSW!eL(g#;wTh3lv$`4l1mf7t0peM-l}^|^MCh>(y@i~Bhuxu znW5~3)0WRxz0c#P4P#`fb=R|Ytk_5o{m9$xE`D62d$_7FRF**>Ef)7qm`9Oo>uDWT z_FXMMzUkR;5(6rhs_FkdA-{Zit0?6Yjm}4;Yee5iLy!Ppppel8E(c<@zOH6W>%$)K zFb}DCqL?636l4%>Wc&_Ic=H;tb>9$|6PI#xQCqoQIz` zv1o8`aXIPP|1QBNj1)8Guwr%+b?kn&a{GP0LF$Z`hSK@ zW_CW?rvFm6+X1H%odRrc0+E9KA^p*Xv8Yqqu-=iL^#kg|+S%*>8x!HXRZoU~fk}6C zIxlSMZ!EOFrSk6XWQ)O z()ZI%X5b`qqL*&}zxya>KBH}SvjHoElQ=5+nPZQAGWE!7&FVBmOaF0n{_B@!)IW)c zm|@IXrO`=1C$0e6Lfx+4V4*?DL!TpVdH&yX3nT#MgliJc)9#Nno<9BY{)=MPJs$pC z6yT{MPjuezB({#0W9cy&e@E4q69cf6J^!NQ1FS%-e*L+3>S8KY^?Vj~4c)pu4 z`P_H2bo+>KepdhKCND>X;;R4iJ>OYv#%DhYQ~GY?(X5S3#AxXHN@l6`YdO8kqA5ut zi&*qlADr2$r=O%{`z=7nCJNUaWhM>Y3y{8do80!-Ut#7)ckaihCMQkJqaJx=N!ATb z9|gqsy4@0mFIZeOnBE?ac}po7IJ#NI8)&|INOkvruR+cH{abELv!f#u&jV(T0ij+C z8+KpEBtkgulM#6>TdA%g?UV{1qqJW~E*?#QM`y-`pBQ|*{niFO-+J%S*9Sb%yJ=?^ z!G=2e{6sU0OD7INB?aA3ihScmUH574=iLMe>u{7h41&;-<{DgGd%?gK6q+-bD#i+& zOzTrqce4)gN!^-l*Ozuq$nslMIgpf3xJ}*1>M?LgeLL-O>w4PW>8~Qt2jZ#d`J~4) zhWoS4MT)wl;X;xwT*c3!Y$`wT+3E%&s_&XLEvaY02b0wV7chFB&FueXE`!OjqQVBG z_#NhjbgmjOt#=1Ea+MCSzIwf0pDE^eqj@Z*f8!n<35-)#x{`fmo0QP-=4^EJ!GTBg zXI!Q?ZaCzEwVDhO&h^{#-T18LK;!}%j~jUfo+c|@>D1nMf9TjtoT@KKllI-lhzS3y zoAcNT$KQO^@(BgkWuV6*FshxMtyc?IvN&b<-?_HlP^Z~8b=aN1Q#>N@Z)i!| z&r5Y)JA5i>T0ts`iL62%H<~%cnGaw95*8L>7{!hH|6Pbpjs-Om43PKiw|Z^ey^uZZ zmY@uY3VL*pF1iQ$#$Ni2u*XVkUbqAJeX+pD9Df(Xaa%KUm}z3+kw+69TIi2eo}iZ| zTDG+fwKVeGkIRn`N%H=IIp;uCpRgH>9 znVE*p1;!?)p94*C{JGMzZ+Clnu{By3LXY<_8}V?dMioL` zu$zKVToJMZ{}7W@4%oNknN;4PjP?pBf1&Vkbo0CvC6K$i+cMY^FK)Xlxp4X^WNE+L{lI^6G96We zX-S!zA!h;f@Pk~&t*#q;bubHAdfbleSM*1N+F0jiElA*c5x&wncN4(K$Vt+X(qG!o z-TY?n@$jY=H#aNX`R79sIoW<&@3RfUmY`+DLoK$Xqtj!B_`90RMmtNAJ@AO4kXMcn ziOQ<6R9>T(N4gYJqJPs2+k9)AFg1FGKF7xou6wFA%O?joiGvcBgy3%!b>kv=cn;ST zR}E&-^TmmhMgu$4&CmWenvB#b;cq~|-RGB_BYhpSkm9=c#YWyawt@AIrg(|3#P@Z) z#Q4|n|M4h^Ke;HTXF~P#@s1C(gxNXoU3)PL5L$R#UR{M4hA>aea90@=U2_{J8)*dk zG|KtJIe|7I$XN)6Y@D0dysXoKCwbDV0q^6r9>ngXZ1XDIk8BmrYg!C&;`sji;thFp z15k8RZR9-@`c>A$N#I-XE9dCp)(@N3p1hlXhrfUR{Q97ArN|~)+K#NCq)ImUw+(#} zr;*<`@Tiy*<_SHgjkci~2%wdELdx}w?**B+S$(!HeX`^u5<+)5>oz5R!=vj$KQosN z5$Ynz@lo2mp&X8lT+5ZRWo|;vMGj}Lf**B6Ms#`Kem7;ZbGW2=2RiL@5plN_m_Ru# zH1i2llqhsZYssLaHVQ?~^K+s~ zwr6M`{RfYM-v{v9kh=*R?#=Mq!ve2KT`Tp{Q9;6<9O8C^&pG(I1=`%;qSVK3TNw-{ zA<>fl%_}+{fw~z$`6GX*1mCX7lV)allm76g6|X%QIB18J8g+Np$_1R+VUC|kVwJ3a zC6DxZT;s*?@)8_JoHXg1|Bs2YDyE}VC@5-weW{fN$Qb{m@vZJlp5VI=3kHwAQ#Rct z|J?tzd!rIWYsOTbQc5}H0|h$_$8Y}Ct*T;lXI>VIIIzLy2hW;uue1HA*qZd(%XfOHiIB!kREoOWaVb&vW zkgPzZ%Ipt=f|m}teyaX^kD%5|vGBd`)0Y}_Nd+rn$8Q`S=MkuCf{1Pmv$0Sj_ObMz zU`5@N`62IYrO;jGYShnFTcBG)44ukc$%q12_OYi+xl7WSR`Tr(6fsUPch+3^K47}w zfy}%=#o3{Vy@UO@7BeEV`0!k9ywOL^6cSp2d^awMnX|0*Y>N zmNl&PjUj8Xf=(z)1-mw7aq#?wN3_N|lWN(rQsLv47H`m%E}dXsy(X%TK5gh2g(&wA zw8f+YWh~-tBG(MeENcCNdl$Xg#PgCbgefa8{%aO!Sfl>W(o#BLRDiFZk*kwwUBt;7 zbgOzi;;A8(&E3MQ>FHOfCq$wM-Af?DCVwR=-^krFA-b=HQILQvuerP7C)HR1Vr>PG*Ztr6)rLZ7h_31BbSL=+Zj@3ukP=0+eZZikOzM7l`{fem-cBkS`|Pw;lqb-#|yO!X_R`cQDDqi z`fT%-K}B;JUm>pN?pLcgYz^f&frFIsq_FNGs^S@MYWzIkM(iOrV_k7C&5V_i2zOV@ zEbDq#jr!<0AWj9u2A!hlF|c8fT=u9auIG-gG4{9AKK%OSn~LDbsqQ`#?y$V3*>pKu zSCg}#rch~7W;VBw*9Xl3v&ztIX|8CJX4*VlfWzNmn(NuEGCyC*;r&`9?~+w}>FZZ} zrXvy=Q|zenC3p}*h2g$xTD8X$ua2Xe;<+&kdArPCb6%)_8?47I{jg<9Q20NStl-qC zrPoUk;Af})(16*#=)m&1bFBPu`7PylW+n@n8fyw3C5R8D*C%&?kzefdN6jg#ay$i6y(2%)TEG^kF*K}|xH z)gKwh@Hg9O63yK4V5A$`_ep*-u8{pPrY@j_RmcFqf)1NJJbC|1+XzTZqhuOW1u<)& zI<00hlnP!4jL7y{53u&Bd2xwp!q{v4AjIXeR$6Bf195$%>gSN&MtSf5jFpjEk%zIW z;-l4J>qO(hbiu?m4%~D1LCQ*nO8}vJW}XG5ct_&9`A(CtR>8zJU9G~j4o_8b*Tx6k z-3K~!sOa*ec4CC?)xt?|3{8(PGqp0g^#O?GUM&0|`F9gGALnx$J%@flkdpjo+$JB` z!{>kszS!FLs;X285w}T`k8g$tP`G0)iSZR>Qwh0a5839VSH!MSFuFgz7CK#|q)k>_ zqEF`*%r18J1NkDo&qA%pu>)8Ey10xbE>wSe!8-V10+>R3BMFj*{N^!`C&`?B^SjNz z7~(Or{TQ(8={H%rQkSVV-Gt~+zgDb*?o4yLTt;T#h`~qh3gc8q^jTlqvH1L}4QG(&oOY0k^%8KA+{xkR^ zFQdwFyaC>-9b^&q+409SH*aP-Iv%avWV!qO2}zLUn&_UtJ1??UPuR#$Y;v6WSfJ%p zZ|f#h%n8kv|5ea%rsd>28EM!;!RNQs;?bY2>QIsSEHZP9%iLj*fX!}S7|R#9|r>rIzffeF#ET_L>Rtu5b5SLGbMUF1R=ub z)u#=8A4=L@Ek7r2XruKzX>uW%y&g~&#-s!aRQJ;hh}SBlUCvkH`r!)T&5(b6?^m$G ze@-FcOkZ%pBxleMlQ$AZhWy6kPju<&&ss5SiOI3XHWid=cLkr6DrtS;*`8c|P^BZ6 zwW;-vry1eJ;@)6JOHMrEx9-zm^Kx4p!CM>RdGyQOT)uB6!{E2cN$Yya^5ahP9^B~# z8I3xIkBL@^|2d}5$)xncRsaToo63=yCp-Q7?$elwd`zJSsQsA*6q&!UGcqrV7YVR> z+m5MDe0fIh4!!h&$vs;#2VUy@VWHW~? zIk$k|TDHO7qhEFFN-jbVBLhV4AX!XlOzBwQ4q%C#y=JR@CDou-CyZ2O=ap8ADYS%7 zhnJ;wE_#lylB)ezt>I`{!{K2xCv(iY&H9&Nu{)FfhhKYQ- z*CIhTvS0{!w$Z-&BC)TkJ@SG0BU27;US3sws=Ff%^17#_=7^x4EZ{Y)ijMtxZ{1zAQlKhq~p-!@m(D$=aqn=p!D`d5)=PLb8OCZxY zgfZ-N)l_Ol-lv2zLDeRXl5NS>g!Q$^Hbv*bx}%ZK_NT#hrQjCUYvsCzIRrm#RO4IM ztpn!foQ8SXbeKlRj+7I^>D%4KJ=gX!>-V`-IVC6@YyBugRl|q;cwD!vTayP`Z2rjY zs_B;3h}S%~5h>x_!k)=lqcTIN?V*=?jOoY|`EKnUx9de`F5Sb%EeZUQ z1bww++5gp~-apHK+2gkL=qT;`#9GR$>9LyWPbGXT-?8rB$AO)-ackMVYk(fY@9-t+ zY27D3#&eehM$?nx0Wgy$jDXZGk!HV}mUSDO#&oI%kAVSy{hdms-tjLQ9eA(LKn}RHSd^`??FY3693~{s+W}jLSi6}XYw!%0+vHMiMJIGxUng$oj1}7_;?TSc z0&+eDa1=agti;#$YLsP(x|=)Y?dVOP3bFg#T8k zeQ)u4lW+uO5dEL!gt*En)O-Q326R3f-jPO2(Ru)AEfR+t=hy-idsEWhwkM<)@x~SvH@`iGLMe%(^WmDpfs~xkMdzu#o_&Jo zUg!i@y$`~{e~w2KPU1u5q%s*4N*tD42QbIDR}(}RT`9}Va;)}z>FXr(LfEBZ z+c4vA?FTO>9x&fT)>hezjg6s7gf_0fsrM#-au-M+j(tmBXjBBkC#$FuJ!_zd^y#BF zMXRy`^!*N(jgQ~K&mP>Ij|d9|DgF!3{!0FfX;fRriwy?)%KA%b{kUwhX}aS2j}?}| z9xOy|{=ujmQ7VYckhE`=p`Yj;GEA{sE{6p94?K3s--tj3%SI18(zsn4N53wbZ`xBE zt_3SB!G1?nC?rQ+y^T$10J#P3ruG`P%fR9yr4lndJUVi zL`ycwoBPf?oN(LrdY#?Xd<1^ucpY=Fxasrns0!6`izf*ZRmgME{mt?2f1dlcci>T$ zCT}W2rl<<)7EbECAqs1IXl{*#>^02=6EGX*z<+N(yt$w2#Y>5m6_8vH|8dWtIlj+J!JZMJVevS zY-44khsViwDM)nF1-Zpr1v6bkArs|*#9%d1=GkV zjm9x8BdlkWXBk?Ji&r>g!s?pnDfy7`p%bHBU0K?*vMMy$FXZ0!>~M^~$Ec@9NQN z%!>6Ywka=FUZoP_c(^I%$P*6PdA5CP9*I>OE7SO0DG5j3!v;!X^vTvJt_?_>T4EBs z;076PIQJxfz_3(_R_BXJNd5ne?AFf;MsIaL?6CmrGyUwB&p)pp+>U35qU-be>kLDk z*@$8NzRvFN#3<4Oo=P9$Y5;W~q)`E&bpM$d-e+9n-Y|9F?Vpc+(2isa9r+65m}yE_ z6}KETl>fz|3_)k=$|Sg5tG%?_J?`eUz9wi)iMC3gwJp9j5=1sNv~?eP`R?h@W;X3Y zNp`iDFZLEJ>v!%6(rd4JsXU!l4D6I|d{Kw71?i-YgUR4n~5 zgm~tDq+wO0f+ms>$1{O`-FxrhuR<*|J)StOFV3Y^$zS)~_xz=~lvsbt0RKO#-a0JG z?Ry^1O%izhi(K^q`L&^ZWuxsdguX$t^r4Kr~zhpAI~|T z-}ic-KWDCI&)$31UU9E`t$kG#$o!;kud9a+u-hqSd}2k3imo#V{=IAgvN%;AkE_B| z2oFEBOi1quKyOmk6ZMzTv2y|^R4o3tJMlPNDY0j}dSw#S{$nBQeb)=~7q0XhFLFrK z;SC!!$KKTbbODS%&uVTju{Amu7pw^fcB!b4;5C-~if4Z?xirNB!hqI~nj2bug*eR^ zC4cop=_!n$^cDJcKND(h{HgHg3YZFj@CtIZb2YN0yWa_EUAm|RavQ%@UxvX5#(`8A z#Wa3@2ex~U=|(2D$@--6bJGn6aKZfepMY9u#SWr{laPF({YM2{mJ>x~(2{dX^|foW zHUED?`BhHOk@!7Js%L&*Bdu;$1w&v>oFgiHthPiE_S*zg`(`bbVw5ZLA%xzSXp3E} z+D_;#C2%;8QojD_WjfDY;T6vNLcjCp8}_xOLQwo^cR^^VK&EQU6up~Fj_%MKmHSS3 z7d}rVo(1Hn!#I@6yujD)wg`0 zJA}QA0+i}rIZZzFb0{Rvf=azrY<8~uA9Z*1tng=O7ktliu$s`4n~-9;(ZhX*t`;7) z{AhvXmGE3eW$ENN6^^fWuBrO5>VtB@%4)Udi2Z2uXyU$mN*q0oVZCopi7&T9u8{kC zGq;wJeK~g`vHs5(6E7DGh|7;4R5+3^ZNmnd$8uYsHbSK-EX@EE0T15$R8ls>aTWb* z#A5aGF@?cKE5XeMTNlI%Kh%%HKLfI}zejOhxrSzsptwx^>E7zhV0Xob)3ruF&Mey$ z7iut{;OsxQNOHxVsFs#1!x49SDq)NB4wJ1`q;zLz4I8i&NLwfCGMTAnPP46JX zFtaeGK8_`C;2}3-DnEg?>?kBmwlqO_p<&u={CUw6<%5Q zp~h^?Gs54q#>jCPLM1qUKtg-FeJF)E#hZhg01zYkBYFGo6DdZa$6joSGih4VYcd_} z{b5_({?4(tnv1K{rA!jEwCU*=)id7dvF+DmUZ-xPK~XyU+eZpw;zoP>Xaex3wUU!ntbAT! zTh3lTeK6zPnOCXCj>}9hJ3epQD6By+4he8IR7Q#%=y0A6)w&Y5x)|28;@2DD zC%{=oPvbNgN+)wR8m-W$^NDEWXM9-2GhUdPA$&aJsvjKPyGXJoW6%T8!HJ7;Dxehz z%L?uIYC3q(i^YTUU64hHTuHu0)9E-6;OX1yueGdWPdi6Za#7iEUK!%a8sd4v%T%)) zq#Mfnp2Lbu&a8~w&Vj4sX#n5Et*qN2ok3im?7uCPeu_XS2+58j8lXz@LH~1H6@*}X z+0}sa*H{xS`Ie!)AWM~1xPIpB_qLzWHf=#VUAJh+C#_P$#YaI**TYdBzU3Z%F}zcd zp-9LhZ}tdQdfIU^8c02*nl-7LzXjcr8Y~L9bHz_eFM%O{LM|GzA+U~S|K>|u+Mc)@ z`Z$pP&Ys2HX&V;P>uX~)vN^WL(@viu3^6xy9vz~rJ*T7E_o+u{|8^=6w0&V0Yn1QlD&aj2`uwfzCYkoUqbAj zQ%OZif2*%UU;@*J4$t~g=tuFEh^Xx}i<&bZo0@^+`&+3#SdstyHNbUJsig%b7K_vB zKr}cYQ;w7-i+-)nJ1gv^QX4W_l1;FO=c%`U{AZ`0+d0i#DoQh&%DI#q*>$%2k9z zksst+vup%c7RPfdP;Pc}DB>7ketYM`pN})ndvpU?&JSvOZvCqz{)#*e`;;ksu4jo2 zpL2Zo*)P?LEMk@iV+~wN%-KT0<%TfPDz17ir7d?rub-TfzSdPw(1~KPR~mLMKR14_ z!{M2?CBD!_n*MHK!_|&&kzzk_SHDM+liavTT2s#&wAm}Lyd4p%xgTlqJ*&XA8M}|x*@V3Oq%>?p zHex1SjaT3yIV|s#y5jDg0-o*7pS4SmnHjeF2Gi#z1{xglHO(g(Uu5X_VTVom?MA(; zKO>xJDGvg2OyLa)tTr!Qiv{gN&Pt|rw3R)cJzeV1Cm!|dH7A~zr=$#jhtZR}f4T53 z$^Fb-MaiqlBm<Kq-4zZAEz?qNBFk8&3sTl1;qr4I2~gCY=NN_CGJoCf>}`mI+|p zWtIyRu>?6cd%Q;t+^>mmLA!2(6??6;*%TCGowXuaQaoB0AGI5{v(-D;ItrW%3e;Cr zRDS)fx)64ZA2<`*9mM5e%YXj0O?tmLuTjUaY{QA;wtrY}ALaU7L~HsZOo83qUg3`! zrJS56tyC%K`Sl+^T*|6k;6E-e38`fc)6GE2ZfU(^u}LY(%C>xB>z+YWsJ`ofoJI$w^B`3Ti^k#S zJm&+jh$tJAIj|ZPm(+RPGT;IJ_qgFCs$9|JQj9X!t9VHyT*F9k+NSP6`et?`uNSwn zPc|s=f43mT8(c4)Vb9|cO3M1t(gw|J(JbsE@>dB_-T} z4EV$*8wqC>T>ojPWPCWGPnLV|e>Aq1p6>nmMwhSuP)^eIxVY}?_~Xd_jX3{#(%ou} zaF;~>(m1H_0YMyfyWbKgZrJ=^$U#0q-*d6}HyAtO`#*j!@MgY6LJ{~cyWzip<^N49 z>f_yyDW1R=|9^rK@Jc#ms__EAwqn!C1xBA}*PY?`--ap(U0Kl@FZBO=l-a0C9{xMK zzi(maxn4Ala$Da2XHpaG)SoEl!D>-9AJW|4OafS-8p`c9`NZwWe5T^z3R9lqVWTdA ze;8ExEg?YN1Ms{p{}fF&F;_cZUYMvHO?;}tku<=2v}vjKQlY+USBL|}_;vK7YSjLN z9I#e9Hb{*8$3Lro%c8amRG9oX1o~l`=UY1ko(qUPFK6TR?XS8Zi%qMhK-H(qyb6$) zB8gw;%)q}QpRAu!uA`Mk zmeuR>cLRh#^qDhGbkMoKC(7o-Fl?wEr5lxrzKpJ-F!0~;*vCG>b(dh%`1zga%**bs zukx#gi)&T@wAM1i<2JTgaqN`p46=N~$tp3~m?>|8rVhXeAFV9!>8bg~rGAAI8F zx|!@Wnhr+jkO^ntK6me#+{+kq5Xu1w=ubD)HYRPy)1sGB!w;K2|1sAObI9!bi_oBq zwC(m|qqO>oD*N(5=)?AyHE{qRp;~B8Nnc$AYH5Xi*|Hu0qVO}MS~hWX$C{FhNucOF zb8yK?@gOCSew3RfKvX~z-NxTmbL(FZmpBwyB2`eL37f+A(B!?Jo7F(Mc`RfDh{E#+ zyDkx|TiTZgW`$6qO`c3SBjC>$Pu(e_Jk@lT^{yHcz|He{+4e@Y`=CFw%WBKs;{HEU zEFpDght&V=kI{JiS27PCqog5Tt;< zozR*ZfQPLpEww&+?4L~0VS}A`MKE#@;g;RxVfuRJY${yy@3D&7In*hqB-FY$@sc`$ zIA(ZjZJ|Ly!F_s>v8pmwMoNqs7=7whnM0kDu5`A!w;F0;;>K}=QjOiO8Q%AwEF;$G z!fhV+rrQoGsKTVYTIWV$qoU%oW#4h^0`mK_5HS01sU5%ph2~V- zhy)V4XA$I>7#jH)5Pt551W^5Owfp*PWfeqAY$w3csO{Z#-L1{obvFQ(_+!a3^koF1 ze*7@C*oE)KU)(8w(1KEe+pLAkN#8{b%^wpL^~Z%-TIoF=zC(1B4c?A*+VghC3IA@3 z?Ec=bOn*;mx>Oi~_p<(fAtinH=Px$)s1q+1QT@a}W2DZfng9>Ffi${_j&q^p75f z_lpn!AJo6dOSp9*?f?%NtL?%BY1q+T5JUk z#ymK@veHHhh#zcDZJto5eM@V!g?P4Xtl@8F3KtKwe57OhggR~W+yB0Vzp&XF%bREw zoATKwi^1=FfN3AB#3PNO7vE~82>UB^f1i)>p?Ck8nXL#@Cu-dFK}|77=bMng>#P$} z#Xh76!dk8PS7aoBV=4oMmnlQ|E0;V!xp-63j#pdRH-Frmi=%(ZwyWcwL@yrKF%2&P zH?cz7tL3l=iyO8r)}lxwM0ghb1lxHWoWTE&r2O;WoBvsL1nfWjzSl*$ zNaOFu3+0u`aIaTAM3)gxSeU1wepCyCyiBXwuT7V^k&X7>5|gXG2X-Dixb{qLt-G{s zrj7CrBskt=`LA8ABs*Ka=sf98hy`+X^S`YFTa0J3ed62qwyFR%_3xAZ3UChrbBB6Z z+CM?^aAvd8Mj-^7>)m03(bu*rQizBs9J#YWFpaxiHWaoUZK*M&GF<c&FMaSqsY%Z6h zJx{#~_-vAMD}uA?o9@zy3_6c+5~}Z3+P+o<%W&f_9#M3pxV%A}cRq*knm$KlOL-&` zZ5NE~^X%^LF`}$x0NzP@hQ|kF6#T?}cucy@l39Sch}CLR&y+VdgX%5%s2bey_Q^zv z(6sP~4Gi~xOO;W>r*Llr2-+4Gr!U}Vr7`1C!~8FFjrm=_@YuXOHp|^<+)gRWFIT;h z95x}I-ax+|l_|`kl=l3b`?Z@mHfBA=se5o~fntyEDeQs>e;Ioe@@kp8HEI z@V8>I@Fo%7Zv`$GR^5TB{T6vF>ZbEC_~yV#{SxE`ARHBKp1&o2&rl}Semr%FtPeg& zP^_nTAi-)bup>y9lOpHVHWe>x`wP9hkGZm(8gr;^FVaqK;F^ujD=;_Ra}D#;3BTg|kQzZ=P{mEXD zHc5s#NRZ~BYieh%YQrY|Kfic8n#fHW{;!kR?{x56Bs32#j2JZ&$kG(yCj zAbjChZhqLhmc6aX=uT)A;C9xb1sodS&=(OVS{)QPAo{g(PthDVt&VO(}5 zE*Nsh^nNusq{ z#*NNjgMLfjaJ4PKH%UnsIYn6VF)=PKoIHL3s zIwue#+Oif?b`L?_bp1!^xTY%bmCR+M*Dy$3iY-9~tj2quwt7`}fTs3oA^f&-Qj@Vs zNKwAgejA}y)q34`9H;KDQ>yyR$!8sXdYop6HQe{!#97K2x4(_N@xQ^}dJ9`(7Gzyq zsk$)<=a7%3vf3r3?H1b_rb z?3lQ^O(C~r z3iw454=4FRA`FH!W{|L=a>vPOd+PnFK(iMq1c%-v7|n+a^wS_N2vcWTf})I==xzRqR%5PEzEoEDLYPjz-JL zh?{WgO)sw3FgLhWJN{uZ27Mmbs=RzQm@sgIJudzTS4)l^6Z2a{IxW{2V+W;V{5QF8 zn5`?tUo{@gbDN?8+akDf=_G#IykXV1K1<%z(zkeURH5>A74vwPfnfLsbA~t$KL}I_ zxGr89bQWM0?O5Xm;IL#S%v#fNG5$f~@Ij~ivP-YsNjs3{&6ho%x%sA)W6WL!nhI>`!UkK|DYm^xlnrSXJAsdvvc0V1XD zje~>4aq@0Lx!)T#gT|NGbBY@t_Da2?6l2%f7T0|`r!S_<=wkWXC%Do}$I)J$x-!>i zfn4>R;5{d=LE{8zF2D|Qpt}*BPrF|@?qz$mug|!S(K&wE8UI56GZrY7U%t6)8i;hl zhWKx1+;D@@LpM|gPam9I zZLzay1^x;fJqXxBZr&FKDO@ibt@?+|Csf_QGE|*!jM4i;+8RKO+mtPkj0Ex=-_`S_ zedjVM9Oi3`P3u&~+DZ-=%uTcGvym%IehLecaI4|73fE&o_Cd?s4;>JCKe5r$ic)g$ zitN6J=I;0GSdugg9K_Ept1XM{^R;W@C0}Q0dL`{`dY6+&`$ucdPah~_THt%L$~bB% znkm%PtF?@7%dx28dv$>vBO7tBtpQ)gRDTUkZPw0fWHek6zhH9KK|cuw1LZni1jh)jvdIe)9g%OjBumuiuA_y&)?Ae%rK{-ww5k_HBwia;?jPx3>c zQg>fQ$WI-E6wDSOe$3|P<#MY#^mK;g}k2|Qj zmOsM&!dNh^(WLHL5&h%y#Vc6ajQc0Qp?7VW+8yV2%M^q*)}VHS)+PP-dk}QUIwOvx z3FQU7R%(XPYIU;r#Ke#h>*Vt94?!g9_j;qlsBZ3#BJRT;@~|sTk@ivltvG>ik8#B- z2;@HS~6v(^XlZBX3<1LS4A36n< zsP(Q^3&#@3GX3hxq-`Hb?XLIs*K@YXmmmxcYc}Qz^dm~W8yZX=8bL#*&>*@@b`*gW zz|Ev_mlSi36h$^y5;O9JMtNKGTbR8dW9O;?xL?`$%Z1X3G~ea@+!tl>(1&`nkE&=c zkjMkrJrANDiy&tqJ7$42CRNfq*DQFB6lhV;Oud~(*3r`uI;rIy7fdtlNPm#XS<(su zAckN5x_xzr{h5oKteALLQ1Q&0bLKkJ4=tF8y|Zo8l+e&5eOj8R80f*wF{pltdyw?0 zMG*BBJ@jrOzSMRjD~@d7L6ptf3qvn|xdWcB7^K3&Y(^jq(U}l#Euu%-5L51xHlWNa z*=(#?BBE7s^CU=8L-w*~^ioDsn^u77JIiHfu+C;)?L8lJm?vSzi;CJmW-s9HG&v7^ zM(LEoECYQG13D7CI(~bEAJQ9S7;cWjmYGd2C%iUa`;1)8%YxUqk4p$kZ=7$WTNy8l zm;E+UVW1^p-b6c|PE|?hs*%tm+HV@D6?PuON?9f0OB>)?Rh?e9EIV&B<= zw@FxynzIQ%Sq<2UR@y|r<+YLYe#URU_nbZR8FjWtcEm6hJvH3x;5&FZuqr#n+|Q-b zXEA2RbuWpRCzEu2ap$KsY_LFG`Z4Hhb>V1N+_1x0jW|!0P}N@PwH@KP^mVQjdQqa~ z?6}NwWA<&W8Tpx*PBKt&41YrZ1!0!d;uUGf_UHt1!Z{ghXmn-|*}d{Z`+$ zEo>yyB-P$5>KQL*k{eXaa7@pwbU)N|5i2?0;FaLGA_$e`%xH8HhMa!z**gUqgHJ9u zf{s@y5{AZ>{6;`eg$Akw)=uw!^iz{|8BK*>FK)-xTCa5Wh%oKgq)3ip=e=^@HuZj$ zg~}}1_p&04-w~WZYC-2b=$V`VUiMT>v|DeSp$M`?vIk9}%fL&W&Y2-!Nzi&hX*=(=Q#uKxsSiZV`{%=Rx(WP=Vxh9!jI*hHMWa`%^Sn%N5_j7A~JBj ztA{I*SNKmQ9wy;g8=GHeZ%3f7+xRtUa^YdJPu%X_?(M!RUe46loSKN?cSRbynhwj((h}9tIDU6#M~ML`zRIn!IoI8qP!qAltESiw@`^H1eT7p>XZnWzlZS}uFRq^ zAw6gyFg}%*GHv*LKH$d)dL3{Wx$-8G@Zk}oCgZcO3|Hqnej;P!F*DjayW$~t5x1@+ zB%j(W>*{jr`wm!C>eVX5Q;ku}Vo47Yx!Hm`wqQ*|tk&6r+i3pfDbt-rwx)FEqZ(qU zZ3C}Ns@K`c4m1|#*#vNFKVQhKqL3|b5(%?nS2+2flOCYOnD`G$eCKuQq{VVx`uT*F zP_^SjMz$W;kwVf7MQ7eI#JNSp$CIG_yZ$cu<03j@NLJV>r3~H+rUy*ZvDWBS??)rmET5 zVeL$8n=x?vxm!H!sa?FJ98^8qf1lRpPa3;b-=vibi$!vN#Ore@?;J^} zxFNR&t#c-D(x{DH_1Ao^k4eFajc2&ELN`)RK9V`~&Qfpko-Y?6z=(_uhndMOiMjwt zw(+*tkC(|A$nYV~`zqAeTgz=$P%OwbpKvF^5a@T2gvHcEZk>B!l{uRY4Gl=J1PE`t{9{jBhzFXs@5ol zoSr(l_RS)^I?D7)t)@d{vX>h;Fjg6eIKsvXr_cdklmst7bZv18I=iv+M-w`J3xic+6uPqiDUPU01WTU;DU9U8?y_kia;g9E zb19S9p7Ju*^@;JxK+Qk+^7bc=TBp~3dyrOjsE@mWNOhK)@aB(MD;>GO>HooQAmAu< zL6C6d<4$KdJ#N!a@0vq-l$zT08M+uu*qS!+=X;v`{FXg{;QwfO=x$q7L3mXE!8{+C z)~%Wl8`)nIv@vM+@=#7N=o7Kb#_0W-Ap$3to*+5(`uJZd&~_hlVvS+vC(k`>*lqFp z`SjwyG?vlfIF7fYg>CwLQ)o*RJ>DmY<8(7KRz@DuYcDov&$JTbeynb4E7ol)$oIcy zriX{%bg&6_#SJ-z4cZJc(#6CU`#;d$*?!7y8FJuA5w+2%y$$xe!WW|H>OveI4hWA= z?S4K9>y;pj*7qVfsBw~LZ7qcVVJ9Lm04EYu@n6haW{axPzuTLhqMnu0tl#4jhV`8t zI60*R2StG7AumrbL8|jglOOW#oJ1O~4R{l&BZjcB6eBg3B z5@Yy&x|A|BpK^=xGPRjIgq-M$g(RP*K$bERv*D`xx$DJqAnxETPCX=H+CKmt^P?km zJJeR(AjheM(=wR2s-ffLzT+ZNkkQcSRS$Ui`W1R@51go}%fySQ5PvI6Zy`V{qY9yj zY;53U5`Io}5GrQ}>nvX8D@94;IzdTSZ4-)E(CoekPB=qO;pZ#0?U~neG7vNQD$P{dB1>!g1I% z6(aFYjclZBj#8x?JJoHfH81u8Fufj%qi%%`(qMHP>`B{oa56+6U@%C}m&FW3P@8Z32@=}%ynXLPjp(C)0FhzI3gSWNy@Iqa-O^sI;Q7^dpd)P!P zRfz0f_$Hz`0m38zckT#K<5=E?8Q;9Iwf!`Ez;BL2ad&8FXtVY{@7IOjMkytI+lW9k zd_YOm8fKLRZDkoW;ii0P@zZ{(Mt+PhkghtR+KE@@N~#4@DY?0fe*inXDH)Lk zjXrn1RA&FDR(`^0?Z}&ScZ_m?w_8 zE{Sdp^}H9=63yy+#?RW|us^ci6{PppYR0^5`v^aUvKsQO_xDJ)9SdVZnwc206|0qj zgJVTYRJT-ljvjsP#-049;4{MIIX+1u?N{Cf4R{LV zq1=`s&m~0|TE(-Uvzfdn@aNZy`&6mGAwFMRTA_Jl#P|4C9RYu;i3OfJf3@_Z(&qP8 zk!j&2i~Z$de>m&&`_i*ggK4;YZP;2+kSwG#{(RWn;Iy2NI_PB{)F=VGZ*bwR(TF@_Dv%T zN6y%%lwHJ^KJqn6tQZGz14O>ikDIV)5g%yTiXOoo?$pdvCIHQ4 z^xh_HIc|@2XU^>*{>a^-p zDGDh=crdfj$FU<3`{lxlHmEGhzT%DIjF80a=f3pjC{)bR7jvh$8-CiWiiu+LWcuBWpUG}_QaBCz{os-CHEt3clpGrnh za*LKBH>cV)39m2tftxD$D5+{9;hk8Z%5U)kd6=P6R9q0lc`RzFLb3^o$#FO{JVNQU z*taZRjGeE}|E>YcCl`I6Ao4H z3&Wv~%`*LtplunKrHLh7{^j_b4L7_o82^uhE>{4FFqXf0pR;y2PrL+Pk8TdYn{HTg z0W&(8(~;R&^hmZ=IHR@fj1cE~@$)(%cL|&epj2dvYzSb#(X`AEq`lefG1Q z)E8tk%jQj%kdBG*$xrJ6lI;-s3`n=Lzh`d-!}3VE=%e=C^|)H^J@PIn->nEyk#>E3 zvJgXF-pWvqv7>FH>xg|%1NMh4L##yiE=N6%wrQBgDuzFG^ZpZG!ecjZo z**ceYu3m^OEzked-Vw`tMrYk`w2%JLaC}d$vG0@8H%anC#DT72y+}hg1t16qx74cN z5hnXp;;&SP7R*XQlyMpH*k9now?2+~dc`#}npA!7&12`9E%1(2^S^}zEBe3RMKX<7 zGgOh{&GEYggL43kl!A?a`68le(3A=ETIPJu@C0-YstQa*!*ep;cG}&@pcm9TIlZK= zy~M5hID9a|u-|?!mDE!L27ZM#-=h#%?;t)926rIUKr5N$DaHXw{>Ucq_nS*>lhvL+ zV&~Dgx2(K(Jf!}<`A+0pF~2548qouWmh6^Bz5}3%Q}0vpcG8~UO0^c`QKY3r`({sr zD;)6en*uR{`hI6V8Yxv5HyGgJ6!sg$ukbu!&^svz3@^bqJYn`z;2@J&F6~?~2>qEsJhDh7JZ77<#jq@rW1N1sg(@5?g1_ zx*ZgYv#ywYG$j07hwqyoS}_OG#|sD7F8n#4;Jy=z4YFTo)W)?XnqK`(nDwQ4>@l9Y zE5uKOoio0n!<$jeEAum>+oJiqKk--lv=g?^ym(Tkak9Pry?Y$`#aFGUjS7cZ^JXxY z#8A6a8H4tm!2WqQf~uV9#yct@EMsq7u#wz7Ezbi!cFS~$OO4HFqT|WE$L8W0{5*&2 z=yG^7@h!gLnj}t|?MB%y5zNA}y@LNN)~O^EbMhFJ8Rz_iZEfRnt%Xsn8i^!ILLt^s zWBZI4BXgIXLQSwIZNu2nk^ZeE-K)!Nf0WwPq%lJy%&xjeWHe`ZDj%}~10 z=PLzk4ga$jYXE&VrY$0zgTsA(Z2idmVP0M$r$nW}q5iELk0Df_>4ntzJiKCQ;LyjV z-NizRDrT4uEj~W|RBaGs^6P9zApcbs(wRE-S&(w?9HVM=|F>EllJWVeiW`yX+206a zF61NOHL2v#2EgVpwk|5767je${Npemw9Ap5B27xoraA^6uvccH!cx>>7(Lga?uJ&| z1G4Pcy34t4&$Gc7eZ^<|Gzg73VxZ2~ z>sY#Gk>nyLvdR8$r2dZNs7={OWl)9f8&gbQ zkw8OI>z^w(n-(m~ZN-zErY8@4TsPc~!QjiEnU_OlTMsDQpHv6B_tt5=H{iIr_$~Tb zj~%O5&Gy)UyNlfS$(Q{hQoYX+R@S$01vc*Ph~0Gi;J02{vWe%BBZSA4iS+CjT3^9j zAYEE+@^#tZH6uKgqtEO#G(Kvvh?v=R8)$s)qfpLqYdk+b<%v?6F1IcbE5S1vyIqC% zC|X3HUDcPEm>BuMc&kuFV;SF!nn0=yi>rPwY{8#}Hp6Uq{rk2D(wIdFhrS!&(zd~H z>m1%l!R7Q*@3BbLA;wegGdhFcvQ~^$KRe%-zD)YqmI)L{B}yoP+`H@&k}q}cUUlL+ zNL)ay{%Ij%Q`sx-m_V5QICo|?-ai*!mZ)TRJe<2;*y@yP%6|P}cVr>`5ku9hyYA{v zIasf(79w+ju_k_g%EYhyJam)s#`?Sa!*Q^DTwB{t7LMDO%bfwcJNuJI#$FQ|%{2Gp ziAdg*)V^dVvG~k@;}1sre^y3Z+*YOP7kO+`S}mJgIFVRxYfj5Zv5%gw8gCTU*h1l#$5>m zi;YpMa;klrurmHYzF0W*xRLvHFeUlzsBvwQcm;(+zG&RT{}Mjlr< z|A|Tieeo-RSXeel5RzIGpCBc6YMMzH4MXQ>Hb)zhpy7WuxKo0H)iUjK1sK>_OGbJ} zelfafP>Hhp78Oa^GCxVLT6SIHZ0NWcmBnUDUH_5T6=jk*GOuf}{n~E7mvp=g>wNzk zWygj4a@Gm7jyg_a!sZYL!iJ|ZLvh3hil=^W~F(Yp~=~^bvc_=BQV2%Dj z@Zc&)XjWt(0efNSGqR}rN;&O%m~NTlZ|h~?I+Yu+az#bs01??L|6*m$iW{%=m6Q*R z(}V?ljWqpbIz74-2kSJF&Ak~4D+ae-kz@%SuA_Lr4Ub8v%UsHuj*&WVe!u(Xg#UKI zmb)r9$NNO~hPu}x|CkP!!A8?Wr=DosQtIMY!NUIa7H`1MN>F@oQ9bR&1!@J$ zksu>{W^h&j)p$=Hf7+Zum%cdz>o77L+^~QCC~lxB;287Ln)ObH4YJeifN@tCS1Rt6 z_Odr4-AHFw7tEHul9r!O@N%TA@`sM+{(Qv}UA$>csk8W5aihmWC37;nc8C6rSEDlW zN+pykDZ{m_Dh)s>;5h<(6DM_fqS?xGy+akRX?woh(OM~O&;>q<&_ya}WZLEXbwja- z8S<40%EWF*Q0CQi#G!eDq;tT6stiFd0R ztXN-8F(+n8_JQjtfdgFYw#%Gprg?;)1XdS>_-^#8DREM@zc<^aHYm~xe;JNrL3Hw= zS`S8Wu2?U?U>6p~g7N*6^j2;>h5~Pq+Mq!GNtzu-e2Q-sDXn^I|J?Vq@V2?&Lez(C z`sLIK0X+6MDpcI*wZgx8G3FHpJx}kd>b#(%PmpX z>tWGU=ZVW|>@jEMx4{c6qEl!MdSuk;J*9M7M#G3ii3aEa>01EWwGJmlmZ1)Es#-2v z$zhmc(N1l!qTj?_>S4ESyZVh}?i4WSd{&+ZB7;d01*slw$1BfCo-} z?X%KL`ld=;RKLbOjSFzHy!oY?wg9VaPT?twx%L%zId$2-=J)E1GQ&1X-V_oZ9dYUm zeSP^Xw)^Ws{staG&=0=A24d3d#9M11;YfoqAq>Q*mm2Mgv+v`h7$Od(#@Dfbb2}{R z(&pK2u`;BYN)F4N+5wnixO>mqd~vo(vkaREX}a6?2T0nuKYT3fcur9L(*^xVQz&F( z$z^3}Vd%&TKwX^CdVFbXa9MP)PI3GGrS5LyhR3hgEwLYbhoN%SyF}w|6%13YG7@Z5 zx6b2rci{b%cw`K`_Th*&H>19-*?qX!&J9w_}M>1ztOwT z4j(-#=)dnU&+oE$X`eDsr5+D78u#J<+_Yiz$YS7vKJ`-j`k&m_56UJabUL?h)ogX7f4$yF>zhVikX$ zSQBY&I3^UdEwm=xC=W|WVL^t)7~wW?3+Gr>94>|s5?y?zVTw;^rf*|u5`uMOcE)(? zK3P#@?x@s}|22b?ydroJMjyXG3{i`&D09C@3*pw8{vcT1{(1H&79}PC8d)ugS%|ZKWy5 z%%`84S`Bo$`uJ!W_5N|(r(GaS7!>Wi36uy5KEDRxpCOsY7ErvC4G1pw3K_XZ`xQ1` z`z0<1{Ds22P7^5g_>(G$hu?;In>VW|ZY6U&CJ94wO6)uW5wlFnK(Tw)GxYWL+mB|p zdX=4jUMU%YNQQxyV(+lf^Tp>tR>1^*SmlQmXShz|(sDF=cb9?y>wTW3p=r$O2Q&;{ zx;kTYWsD5EES!N1|8Ow82eo*{C>VS}f&qCCvg$sRi47k?3ykHFRjyoKncAg#$p?-9cEZj9P4XTu)R5u$3iOjVosv zj(}EMa$5gv+R6sn2|)#Kzgwpdlxwr)2=HzY>HU3B1|HySOt8*D3zO zZCng_ZlS26DfTqy^;3_*>F(~lmkz8E*GG#+l?8N4O|x<5pX*Ey{WX7nMN`B;j0$Qa z7Jr|%9Jz6GSOmyyHLLuk7f%@(;4+Ng`^9c_wXY|a^xmzvo9er_byvncj?p7L!PzjP zc4g7&ncY7;{%aBv@)b6t?-I}l{geK>|3cCwqW7paHZ z3&3L&AGgzP1dKoV;XO_%h|8b4<3T;TE(q%d#Vw0y1#kT7GpPDeUaw%v9@yDOVvzem zvZH_z*GVpQG3ZQijwI9H2elVa4%+0B+8%)QSyWD0o+U1WTPj!rX&Dpi_JDt3n^e`) ze{b02G1T5$hY@tCEItmm};l8Nq+{bG{Vg0O7jia@YutG5< zxo6nply)t4Lq?MQg9T1gF7CIZ$A=UNgQyIVRSgHiz`+)TomG=ZXYjkmo<;Vts+hIK z()@g*H!-ug;Prf!r+N>*TfgPIX^Lc;_83x^w2jk|UAY>^u#~csjc+WvoQRXL;k0jC zi4I^Aq@F?@mZP#=7vfeg%PMD2JFZwT;V5}2QYnPB=^S%H8YmL6#+NWZJ@}t#P-w%8DcurG&2;i2w=f4mE-&D07vwk$?`ev*TG*==0l!VMmS+w8fe-C|;Z;zE*2OQ{IcYRNAvp)v5UPz@UH! zVjRx#a$5uw4kN7EQu!O1iVndh+1VQWX*tM`pqEspE zb98Lh=gzf|#%Vdc$yuuuxs&EkQ@L?ULk*3ISSrZ7E$NlBofzb1Nj|ft=vi^^uw2I^ zfhfL$Z@$$@lJ7^{nA-*Q#s#Cl2;X^J!_XCezH5Css}^LDceh>!zbUb1!@euCTPE@* z0yWnRk1)mdMhe}@aNvsyrJ4+W@5HCm7EkC{f^?4WBrY}K3$+t@d4K3fz6W`|n4GYK{5;MWfe~OR^BJ-iL3a_8BS|C)HS zku;PrFA>iH-m9GEf|pV<<5i@C#9~kPiy^TwKv19}BG~Q%ji{8tOd5-9|aS zOnJ?1g4SFBxb7vCer6)Zfv$90 zGD}s(A84<-?`2V1h+@bmtr%nqjav!<>;BsrXG4n&FzkUihqy9ed^ZO4}_!U6lWyqz%41FFWtGcBb1wV4rzr_ ztll0+4TANkwA+4My505O37VN-Vp(FlRaN0?qxU)gN!YrcE2VKAnbbP}HQQ6gXUoRAF&@zZzG`+IMv%it*j zw4{~D^Z2Hx+B#KngSS)Zfdu$Z%gVdh9aR-X^y^9irpKM4$SIJBPRZx}vZC&wi1mT= z>W`9@$#1L*p?7wgjeT}Uc;Jc~W`FBT|K3=VS3u^(k*HtbJaGM$OE9q7Sgq0bzqTlU z8SeS>U5|^3iXMoV?*V%T~b9OV(+j&#lyne4(xVTB5C0I82HCnR`v`>@A>o@Z?r{yrRBsi=!Wy zaQ&zXZ>Lqk#-OZeBG_Jvr%6+aC*hZB#MI1Ef%fV?zl5gwq+m838#(>X&O@QqH*DV| zo?ncs8IWW}1s%5cF0${FUb&Tz*-HFus?}I$v-{1o15+kyCb}X``lF|IPbFl6>x)&0 zBlSNp$PK^-kCN^A*{hX{-;RB6C;h)FyuZTowkmg}syh|{b8mBxO{5W4RBP^jUj1Y< zu{=4=1;RCVn2+b99yZthp7g2B^?r{@y#_BoP8|=Y)$s$%$CSax{=%o-rO4bC%=!<- zU14*{(*LW*{FDV+51fx`W|L33RBvNzjBdqHJ50#dM_WU11-460F$Hh z(Hh-wVR)rNaVx}0>fHN-9@F5x`6GW9>Hm+c^A3lr>;67b6Ey^hnh+#JP1Gm}Q6g&e zP7tE^-b)bCqxTlQm(fLw-ou13MhT;L27`Ie$o<^UbG^Uw$6V%^nRE6&d#}Cs+UxsS zniEz_M}Rw}T7@x4CA@bzF1Acj?A2-$Y>(#Pvah*qmr89vY4wyKX)}}Fso3k(y7$|! zh9)P{+;aQ{En-B(a}e0^MwRnd>yX=2G>lzsMqY<0flgEI_Z~{^+$GNvxZ8wAXb;*)KvHiZo+9@a zh*Ku~`FR-8ROJW6+ctZ8J-GtK;GwriJuWKZD@FsjSaMEUp zf2x+!x6HF4S18|@$)*PosnNWu{~jZ@0Nv=XkI>5rd-aU%Gv!_$bKKkidG>%MXkzZB z+(mQ9)6x}3mv2g?^MpIXi&G(&8L$%ro?ON*H;X|9zD#2@jGr?qZY6RapTJsulj@FR@LbpZd=H8tHSx3R2{D54|^c^pc^=H1?HJ^VpZVDU~WTrr| zTHNp5&^9^lx4%lcE&3r&{wJW^T(>ageGpD^5&>>4wEqiNPqloYcmV>?#rprCizx}C z!K?;;;XRDTO(=po0~Bl07br&T;_drqjR13X7U#xJ9zlyM(?YmtY-`QrtwRTP3T#yebTwW zBjDkZzlNN4%4YR^W+Z!7N)|oFb@RYqyhJPMC~o2v-w;GYR$^-gbz;jTb6MU*OCgyv zL155(ka+gWJ&Ke0%&wB_N#~*Ek=W8*bT7-@ z=$SMG1lo`u%D-Ykf~4+^Z?E2_oB{GJ=Fd)s#gD<#tc*H~ zsa#Waf|1jiC>FkJz0USU*Eaf~V^1MuJpr_!(1zmTS9V3k@fWFcJbp8Dy9rl2cxbhs z#lkD2>q{HPF{rDBvKYNy<=7>pVRZf!fyX7|-|k)BuWd)EU9EA`9?>t9Y|cYWUVx^H zk~lP<_rb>QMxv=O-?GesyB_-AYa(h1;`{aURM%c5K>R!c4^S&Ur24-=L~H)iA#StY zVwCW3#{(zr3e>YN>$JQ{nocf)HM}W-zpOvMo0;t5orOg{jm>C+ab0slfNs-JY_*ih z-{}E+rA0Q7|2v?bXrIp@0#_gT@a4h9j$)zHJftOq6A&h@EcmuUgze{+dR)z#Tt$E$ zA;GKPd9FqJ9kp%Kq_Jn-CiQ2T=0X17@ogfQ_tSHhESI-4;TX_jllA-XhM#i*@9vPt za;!y%4fSfdRxGjuDPXZX4WeZdIng;;lDDnw0Q3)QL|UpDkN{?`r6s?a_p$mKJ2l<6 zl9HIl;WRI2d*wB6@$5Oh4z0kdAe z+dt0<>(h|2QeRCJ)zve^m<}$OVgju%b(J&~&`E5NjLOtXc-4gidXTPvNR+uejcu58 zh@XwkLwpAQqu3c?+i#;cUy14iqC~WcNCBGmy>BNLNW}EkoJ7Npsb_b0XW~_}0Bxct zxkM=5MZx3yzes(+?V>t6k%qV(V}-^inLb|3R{W*y-xc@l5Z6Ar1w;*IaqEhA z0}yG}-yUTjVlU_pKCjlApI2(Cd%-_=3cXFf*yp0l$dV3*c(!@MfXo~lykn8m=nj7r zqY>DlZwe%im)EA)z(0U_ag|Q|ONcNS8r3qqI7gF1RPSG}K?DRNZXMTQ)v7o?^0D*N zl9LOZocxual+*PxMOaXmIb$?=zeS(@r_0bV;$2dmWmVK2*UH;%5P2>7p~_-0(g!hz z0H>yo1Ahy7;Y9Jl7NlFkUfQ$c3^$(>atZ&e##^veLy~6CP(JU_R`Mn0HW=?QApHVK zv0o@lg?vl;X6e5>Jm%WDoobJY(dad=y~F{qgQ-bT>eJ_MNb~*9kM;P3=bHbWdyPl} z00JP>+9`EjVSZe@;uk*MS2WsR*AYknL0mnw}uEEzNF^y1Q53sVqwidD0kR+r*)UoBi* zMl$}KCe>1~DD_Xg%g)@Lw-H0DlPKH0&2hGq)rb+B5tHDNhERq6(M>17%6Y6I1Xgby z&*BG&HISef|HBLf%3@ygbpgmqXFH|F5Ny$x`Jk}=*32U=j;9m+J7OJ5>_d1al6Za< zug^LY52<6){HX()hUQsfvl&Sj?>*aCu#XbjR{cG`RENP>3Qaa=Udco_*V((h)MsihJB1f8zt#tN z?*&}(0@*77O;R~GC|k9k!AY5qHrXL#uHvesU^p-^jkXh+PAwVzG!yn^Nx_tHLUExi%-Hl~e;nB#3@P(XL z0^PM&A%gg+RW$1dmKWmL0moIh+pDF8bc#UypyU{l$NFueo}y|Mnu(g0+>w?iuO2>u ziUN-MriN{wt1Cba$96eyck4LQ-Y!SZoALIMJ3V;yOxJdi&1vEkg@&Ybxlaw~cn6WILc==F&T{*RQ6fH{n;A{UW z)Zr-~n%KVPkV_U$EXS6i8;`bbfJA5cC>pR19S#$lAUq*`_*%c{|~wdv_6+89N5(?U|(zBJfokg41XMl z2d0wQr+x4>>P#rt0(+@9=R7PcEt^)HW(!{IET>ybC7B&j+2!*7X}K?h8&ZB827w8z zjoG7o@^<}<2jzh5v7Z5ok{;YVND(4oei12@?fi0szZY|;sx$Ud?N6!`&9X?@<}k^HrMEe7@Nj_mIOB))t;{80=;KSfNy!#Yu3b+uak957T`kPxf;(Lt{M-qh zseYuM=Ap)y`Q2$nNaV3)zaAt5XatP^i**F%5-)%@%>Ob_61xa%6myg_4=PndkAoV$k1TreQ2#sr}W0)t=f zIHaJYtDsOYd-amB_OIVX?=bJd%x%;Y45$Jw14b1cn$oHNFs=95+}+Q!@r!KAeD3FG z(IHM6o{Ad9Cl>G-u5rA*!S5siunGemhRoI?Ii4NiW)jRu`oY?6W`;MjLrtFIYIM?H zAl`ItGUNQbEM#9OMJ~k%!jb76WAoa~<9ktwh@18t_8gv{RQJ`@GRc`jvVQs2I1~2X z?hX&w;jOBAkf8iRl*@EC78Z4nX38t1?4xXw)IN)nN-#2vkl0dd*R|#N+Bx_0x!8St zi+(#s=URO%vZ!R6Bd>zTXbfZ>h7olCT&uk8s$K7;TZ7|&d=j}rU#zl$X_8N-5y8$Z zN#$J$$4e;nRUbQ9q@3SlULc3`>~u~^7s57L7f9;>M2=o`8QXvslvT~Utg;L!_s`J~ zd}FVoT#2ZAY~xnLCY}J4V8Q%as^wJHSy^|e@Te~Zyz^AAkmcTF;xT@yG>Y+6-h4gIvVP#445MS!lTyD*R!Ukgt4` zS7KS8HvxCUEZFyz=f#C{+c6pWsh24IBY=B~K{0paQEp%7*yr>wEGRXo<}0TSFcwjf zNTfavh8R>rf*~7vuMDuKp+xod7wXzN-;+FAnb@0E=nM!SU253~(w@ioILjJ7d)dh! z_1R^l_RNsK)T$`&4!zAEhmio;H^6MKpsiH*Gx=@cq8`Nm5(B+;VE>}PC<*Zh#?hB0 zhddmQ#m5bttIw|N@EbP4OIQHrYl3u&`sS^_*DW9`4psyRXJOIhLsPN-pJOMk8cz(4 z-HVTZPj3{f$#pwMR6sK2H%_y8=^>%7+5lKWDh;)nifX*tx1_g(R(Uc^K;t~3oxwN6 z@8m807m5`|`3w+lJ;&!5mrO{|DQ~OX@qdGd$83CS^9}D)VES6;;+mXA^v*>+!F_)B z_FidOojmXC*9H}tmeX&yC#w@ZP3>jb=kz&~KEoRsuvaA4uJ1Nj{TAisUi0~I<$U?< zQ$@7gIiUJ&>Ue#SvUA$prsL3V1vA(n3gwfRk@6hRBDkmpG+2)qQxP8>cbxn}WLKzT zpBrQ$bzF^iYk&Tw5 zDw}F|`S-^e;`R|1^`pW1Hw9X%>TjOkz*zI94?obxTiYs!YKDygXUWCo$qQfvNt-lM zE}dX)CJ8>|Q-4I%TE6wwEeq&k-9FZuyCqC}ZInY~kW4z_kgIG9Gqw^Xd`M-O!L04eOpFt=4J4V{^1wD4dcPu|uzx(MN9aAEQevD#Szd{iBn;)9O;+XOA{@W8910o$2K zk=r!7WH0xY8%zg}hE8A~%zdC9cWdx{U|C#=VS>(&}Gnq!~ z-kkWz6ubuTA!VmhgOYlYQohGO^}-Zi454X1h416|P>0=YcPF|&9?g5+W-%?Xz2e~dqKfK8)M4Lnc_@M z<(LB$nwLp>%}+73ao*qnN3M6}B!xC(<;Dl{qRss$n(7A;e1YCzIMU00|MAy@O7f;% zai<*!N0Fe6`{(Ys$>1Q&cniRk1{l znLI9xi*vxMc-vm2U{g|JbVIJ$Xq(g&fh0aNFQ{Uo3<{B9SPvUJT4q!E=b@bBSQ2qzRFVU z8ss=fW7gih2+_K<0xU?HJ0ai!OWkgeC>WZiJyMvfgSss*&F~k^sV(tc( zeZn7#gk=a20Sm-$S(cZN@POGSU@O?FgIavj*DU`gGD0^3C&M?7yQ_;dNIfsDA;A60 z^z9sc>4PVoE&BiUg$gwQl+k~fTMKWw$a6!8r{UpC$?iU{9h&}&DMMrSZZKtlVPIZ^ zpE~^NW5FYl1e%%-K54f2K3@u@5;PSFxIK7N>Tu~S+TH;U&(NvWyP;Fe#ikN^w$Xnt zM}f}I0-*ZMJno~5DN*&IT-HD55r=)sSJRQCA#8e}DqM|(NJAy-{z7blG981_UXdI0 zU@p$`Uy|CNS!@2h9Dr3AOGlg!|0w@7&CD1V-Q^U@=bKn&(cRZ~92h%taj-Cjfe|@4 z-r^reghj)LzobgRh#L+aVKBT*;@_U1S`Xj}(QWa@gPe;KqK`Dm> z{rxWK{{qi|;ymlQoCmN{t_L@$v5>Vm8mYue3Ut4}Ia2liLZ11}Qf}af635MZ)d~MK zCS~2&P=Nf2zMAu&5}nz>{~E_cW1MDm=Gzq&bjKvPr_*HUoi`o}amrS>KK{|>2=4Yv zN+F}a9|H&ISXv^SfA1J*R^nzg+nF}#ou!%^r?JJOmg|qP0LSu$2Y@wcKPlB+H4|vp zwLSacmETgS`CXSKK6Vd!%HDJcwMz`m^mu>MMQ>V{HF`s?hU6Ad%N)-CK+@u;W+6-+ zZ9tSM5p87epHR@1=NzkJQ zCgFvXh>ge{zrEO!L<}X0w*1)y0#;{!vYfBL_To9oOO!w~74FrS7wn+^6xIr%Z5P$z zZz5;ERt_i9Iv$Q$&!Y#5o8yImAkdA{bo(jkgPk8QcXv-RgnupK?SAyf!RzrN;@uB; zma1ydc%bV;>e$0cUWX<tBVe;;A{9&5c9vNtegTWKhvfANy%GA`2y_x1}o7B9S%41h+0;8X8HwU%v z(Sy8003O!lm%|Sh6YVB@wzJl|edeRAQM%1~sarJC>7~Sk(kCL17mvb+wrCuXz+Q}p zF;WA_9=qDJT51aY9(g!A<}H8f(lAtDo>96NA-W_cngrxMJ-%|kBA_`D1DEM=MAn~} zWKtCl^V_{Zv{YXCKLhd=u_ryFL!@KE?~MWBMgXBM-SzVAg`f!dpR5{;!n}Whc&;PM zCgV1^tapd$Ib%jlYvN=d*QG85pi%+`fx+m-iSh_VIPsUE;A%y9XXlEArz|kh&#!y@ z#D$M<4L-#n&5%;ov)nD4&}$y(Yqb%Wv(#t{f~>~YOp>M8cCQKc9+c>sMyUit?gw)% z7y_F79$(*z&&xay_+QHa@#nvffvjn96&H139q<2C0-FONW}|7#Gsj!}qFh@7cHipF z(>V$=?BiMwn8AZ~drhjgNC?#NAO-%laTiE`y9z@gtPb16FB`beiQ#TZzzQxg+D9Xi z>(lfQX_eplJJh3&mc8As6A@cb>(ZvxsyOPrds11qFv)LK2A1H5It8zeqOJm3*DoWE zJ>BiDK)UU0u=6Xf0!VA&iU{eRd-7M;xlQsvV8aUrA*+$%HP7xtR7&63Q4nGPNXSln zKB&~eMJL)+az6R$)dC__V4$M|g_`Qa5m{ikc1`l$+!7e7g@Ep5PNlB0uMRO>H0pUQ zNQDfMF2D|t7>1}iP#oceL=>5DB27JJ)ZX{#!R4PHQzA}xAgp6z$Zg|F@cjINK~$b@$4rh0bP`po|^C|AiJfA`(RNc(6-;8xkk1fb8Bd4oivx-U!e zpMQF8G*?QYf-lWJI8?wj$u&!8UL>DXGx@$Y>u4+b;{mm)=|i%K!P51`7`1OB74>g< zD5Jmd$zuR(;IV`en%7x|r4Z!}e!l4VX*oW^g_gb|MXRDxa;q6R5z3^|OoOM-u~HK# zRztRi+XwIfn>5ZWkeYvN#t3U1&d+NxRkX^S!?@a&VTi?o>K`hx*mH6{O?Wrf^h%<@ z>=03y731pUA*o=q1cY~NaUFI;{8%;9T{2tOn7O8vksvo%@gx4gebo7xdHypkM#J+Z z)BpqOTEJtKf-6ze;G8U+PShFIY{Zy|W+ljYi_!x{C1!H#AlZA9;reZqVY>vsHG~m%F*3HwAa>;7v5(ggBSjQ> zv4A?!^?QsufT9xOH7nx49e;$20kE<7f?xFI?n2As^^+mQ^;O53DYNHfx-J4pKil4S z{WD)ZxN0HSRtVqRJZ*GT@c}LEH>n89@ILPft)hME1`z0-lvHmR@p~Y9V|GM@;={Ba zFQRM8MJs34`+r9iLtuZ4ABlUkpHrDzH&5uQP(cXZu%{GzvJ3+j8K|ADJ#*%Z7Y^D` z%tMj$pL;lyvhf=R;?r`4cQ%fxG>Z1 z7~$i^GubqxLgD+6&Rbi;_@!6As4(P2))JyC*6JW?Z{oY_uwlYwgRB@g2?=5H3g@$g zRSF<($D@B*R7zd^t>*i^DKlKoF9@>(AJ!3{6*xJO?VpCbNSM$@r^j}Z^P_b)Ct<3) z6I$bgSMe|3(4zwSGqN&TUSFtZ#T5r4q8ZX&wbqpBRkWYIZ!4>5)idxtkN6{ixKz)a z-!E%DjvFEX`$~8hQdMJVZGvw#QxqOWXaj$-H1Vc%Q)sDL%Nt znx3De{~>T&3FcV)CU?RCROps%*zO0h%daSrzD6=-#XSfADajraUv;*5iu!%DwVDDD zawy;oWt2)OPI2*?Wr>E~=UXgBWAy=j=L1xJ`+kQYR{D5=k0t|S=(w2L8}Pqv)Nwtb zG(=PBLkc+VDkIN^OGdI*uj3;`x#V$=760x5&*!=SOY@^S@w@BP=>ZJsA#Uyf$ArY|p##)cYfAb0T~(%=0kZZk-2Y}1 zZ`SP|Blj=my&|Ff$E3ed64~K*TvC0DuKR=`_@z!Yyw9hHniHdmo@lo$5l;Do9_<5znCxHP$OZ>NBRS%v-CzBx8IsN-2d&zNk%L8=Pejw7o zv{t?(gaDAjM+ex1nwz8u!l?oiUN?){mQzO%6Ul*Q#mZ)>332O6HeYEb1rPfyJB&|b zKvob)`G@q>)%-%f^!&z6C*F52@&*eh2`3Y0i+Us?+|-T`GPrjVuz!oMeoSkXqEHKe z^WL-Dhm2;l{(GOPzjD$0=&J)jU~1~WU@zD~;sgi*=_MZdC;(GrLh}xF(_Woey!MZA z3k+X+;L5s)2)khg>}1Ahk)v~hP4?x_bnCQW0!t-FT(O2wm(a`I-Hs^yan0oM z`+FOxJpeLlVJ)k{rOh_ta&kCL;wKl!(l31?l-{~2s0}iUzl6J?G;KYnAO*z@iaONP zf6}rpFN5P-$1{bk7q}dogz|@f+}{=Ttj5! zB_yomXc+C9sejZ_>9YO2u%D!qo5QrwC|6myhe^4Ooq}G^81MNJ$)$kpbL&wPym^CdBCLv6a8e%SP-u3PMo>w(QBIFhWk zi6tB9D}uJ7Dz-tswl*F0D0}ULEA26ypODPutq-up)8CwosYoEKn@!ivdb)#L)ThmH zHwB(NHqj|rK_!qC%HVUTG7D}n6esw(Ea^Bam2|U&ov{96)cqLXWkUD}%pKM-X6~u= zFv|8|kk1sD4s~r%fjfKZu-{=_P=DC3sV$UYpU^38P%kQR&AX-Uzz66%=zAi=6gei#4&r(oI!|HRMY*@r?Qg_tjK zJ$YW;=f)Ilz{0|59M;!@sl8|%`x8w<$hLe<+Gw)hI%!Rk2?o+@dnhV?DjFdgHEEkx zJQ69}e-KUcV3=qeZ{!N{iQ>!KTlh^_YrcU2dPUK$wfFsj?8{H&Mb=+n9Xr&La0g&u z75tXAR<&`dcg46*P!Z(w+2_ku*bK5Lx$j8I%L5d#vl1TmGnn+^BF!n_xBaaJ?*gmb74&}EP{tVP$?2SMiuXCfJtBj?Yuy|n={ zM6n6MzfJvwvN(Ge7ZVQD0%?qy8`ZBOqXf73?S9>MaLFmT3%Z?*_#w&p9Dds%T9dV4 z>6|FFs-wJAQYjv`U!5kMo|vmT>fhvty^6Kg<~KA-=Xt(&eX7~Cl7p0)uT<=L?z#u# zI@0bCiq-|$W~iyF54>f~P{X&c=2F3l8ayFa6nq`H*66;LVxC`9^S)%{eDG%^O}&%K zkMzA~2CZ7`kcg#L>%J(u8Y?hod1B9rGDY-OBo*1p<$eGjJrLKjzn&Lu`8#^2>(n@n zm{#y%ntg6Bd7Qcp@`+rA295L$E>qtXJiYW7wkAys%eAT{ z{Gv{`&pciLa~9LlcN28k&R?Bgw-URHR&m{}TjA^U0g^g=9*fjSs&6>u_t@U(TMeuo z10(nd#0bPeOG2|*BxA-kbf}w^Rum3@5;}vazLUM^PbQVrN^6N%FLl9QSrND<)j{!O z!{YdEKxQx8T8%A7HDwcw>f)Rn(nKcG1#{e62c9oBJr{*+$un2H*xjX{v_c3;PlJ)p zk%w=1sXpX2xBMg|)Cu~sbd9;Oxii82o4T>*V~)CTm)n*neiILOcqdBuxzS4%Gh*Al zBhUO2S!BJ-;StwW^nBxPIj{sK8H9mFpZZ-OeGcQrp)7W7r?^e$Oj(l%`!X(H zu$wO%iTF|e<#Lqxzy$|af2Dl#b)NXO;sR<;U?@rK#VPf=ApGn{+QK97!WpMWFWv$> z8fXascy+EV1GkZx-tr61BSWiYe1%^diG&;6rPW`)lpdJ}jQE=9Z|2e@&iJu5gyBy; z%YqlA-z>=&bj7`<_`pLa0Q18{yWKr}*c|`o-9^T1sUMM~RHPr79?Q7!b=jp;?ZP?? zPg9SW(~};__B9#0xq-Iln^e1&s?uF%v^}FjvVi0y+=f=8ZsXy2(Fgn8Bv#x$oj6oq zg^cgQpGyiK@E}rwjXrNHZTflGl#+Nk>dHdPuX?5A%R$I4*d=KUqZDbeka-yei)e z3(;jV*hXCW?=!@U>+0&Bcs7b}(_BVCjF8mBLTs4?4G+MeOoCGZ7TkxX9hZkgCkGs& zqN1Mq)&i$Afo}5HjZ2?tr1hf1)-oxEG-RXCiPVoK z)ARJxY_*ll(sOSg{kC++<$)X5!3!&gS#9*7zz>sH)sr{4YzY~sWVm-%z9s|tOE#Hg zX{Nd0H@sL)dY-fJGepBhpd^pyHI}*&eg{X1pzQ{lJyrNU!l;AWL=4Su0Tw||DqtCy>?<*Ty#~8cqk)G#8mnQ>0 zN_4H1#njFZ@R-P2Y(;UcLz!#*GGrm1M|&4Q5ZV4P?pBUQEFTxS(1sZv$m)*h;r4SK zkL%{rir6%H3(*Uol@u!ee!TWekMq3S{e}*!@8r1+*mPa@##gmRg;t$~;D4s_K^=xO zb_-7!&L-ijGcRqE(tzcuHndqjbGGjF-d^tWvDhP}my$_-+8yUO9%FEEX4o~9>reci z2N22ejjl$)9R-6pywdHg)T>eRbsD3a%cTTgf!t8Ft?+ugZ;RMgol$+!oAU)j%pZ5H zwBMDc*Ey;$nFF4M?YR!kG2Os5pR?dq_LIZw<aJ&R(YGCg(NXX59?;HFo)9$wg#8 zQ?$hsE1Y%w*t_$&%`~rhZ#llgIR;j=&#Y$3Ca%KnxU%<1C%8H})qWnpFV(EjdupEL z(}25=ADiWKs_n59LRI(Pt8DX=^$>-o-Xjvx;~0$!L-(Z+YU-8K-;{tr_T(I%Rhh)} zOGcfao&n(l3>{G~C&O;6ayTAG{x98W1Fkt>O)bZ-U( zU_vwDnO=&l_ZJ;h9z{ECOewxphDy5mh=TE{HNc$4fKU&>i|0hc;+WyFnG#5n+j6=y zL)w;CD@pHhcvtA8N7Z!bl%r;<)lYZHLsc?9-Q^+$z~laP+SO7V-9bk!@^=t-eLJrT zb1BXURh#@!{)!dr_-SB3$6?K6*JXt4ixTzKiOxqhe&OP_MQ^%8k4@WsigC88Bd=FN zPh@2bzi)|rxRO0e{jO?KSsK1q&Q=ga+dR`umj^DUZL`6zW10vPnz?XkbD1c(g?sFY z37e>7u25f`oxgtanOh(6l!feG`Kpj-xsx@hUSmzG3VQM}=H6BhB5kM7Dws_~?NT$D zs~y0Z(J`xiU$GiF;bmC5+~@6;OP|4m4*);iA7O?1lS3#BCAaH$$TQpQm6Yx{ zfBW!M$@fje4wQq$8wGdl`l2p8v77sRbcU0C%nks5#AUx6CPE!Xzqh=FfOK5bX;0L= zVF%w8#v2jprI+ubF>i+_>O)qYVAsv9%ZH_Cz)GEm-&PVPxhYr^EURuNyY1dfp*diV z+M_uXC13R02x8z$5Ar@r5>G%~&yuXFYqq`-Ln<-EL@2CxZ4XU6Z07~=uJUv)bk z+^|iC0DiXGy~Y*d@((hyU;P|(ga4elJrRFE-pLzi5j<-GAStLMVr&$KdrY+ApQ)tK zMfCpp`h4^WFafQo_OYOd-aK30p(dJ!X-El5SnH7lyc~5oaL2EzhBnedJ|C%iKf;%p z7L>43OJ5P87OyNVokBGxNgl{*Jh|Ii!ri59XcEbvvD~fiv7P6QbLfYpYDR%Jkx#kP~(#krXMxI1E zE@;D}nxy}R0$az8ROQv_1FqjRKRjbo3uOz>M~f$v_0M~$^DUO>AAFHBlS*kY?K65L zRzwA2)HErR$r=PrKcm;{HF|6tD#wYvd=?bGx&7e*IfjXHN28|?3!{;mV;;^kwk^Ee zruwmJqIz}p23#iRs;O8ALocbbi6P_mPYl)yfikQ|8+WlXf1Cdft#Du={j=N6bTaov ztds*fK}~N}VKUBlU+|Dk6xdWj0pLWoF>Bw~r8Qh)Tj@NNL1t10o)ZN+#njL0Ak1rjB%;7AmL zZFSmlM9Qilw@!jy8&ziw18Ev~P4m51(eSn3NM;1`Ny8w!K9_gTe}Oy7-Zfz8)FdS( zrf~aKr~6D6hpd`K^|TnAc%DxMA%-;Vu5#uPJ$a6oH3>B?2;y1>{DbsTtLzTq(;xs@5mc|?y)r2_JsSLksZ(RI;aPt2skizH->=70Ul z53uD4ZZZHiyxqzzKdEV8m4NSF%=;jqCSsW`ZA<~ko^$Gwh6rQbGRn>M@#hBqQIy-! zqNm9k8F_6YEVqo7F-n$HFr!=#8bz(@1?wgdL_bwcVp?A~b?%LJ}r ziOgk^{KO@&njlH*d`)QJDPpUi_bYHKM^vZ;#A6V~H#Tvry|qpa&~=0JDjiI@OQw18 z_nmCc@vvna7P+mW;KW3s8nsN$u3s&ITsjwBSJWTDshtLOiG3=ou1)pREp&~dyP9BO z5?p5@jqT{p-!Zmc4`HeFNJp%GR zZ)>Vyd?3S^5toV3Z~f?w-l#H}wGLc~xg7#Ko7`gcMz!uvvj0K(BHr1Yh~0}GNULaj z-Q8b#^#^(Q=O+=1pr4}LC8o#KyNU#owkznIC*1;~S>T<%OC(^LK$ZmV&mscNbQi27 zHc2{G7zhxCp8}6(zDtGJXw6JA?)i++tj2@9)!UIsLk8Yr!hjV3%JHb#3j*VALG4amLN76v~pSYBnzAKvq@+3ytZbO^w0 zbHUdgmwtvxsLN4~8V5N4+x++MjYo}bn=fqUzWe69Qs-kESJ{X(qQvAW$&{&7w{@9% z;PXHW+-nt%lJ;^e4Fp1GFnoE-Fg(ioGUda*emNN8Y>|f)n>b>y+{xZ~$u>DLBN}69 zi64(Q?&@1+Q`hCSo!5c7+Vk`YUc_ISm@`P)aA3H&>vn85(-4)ZcSqw)>_c(RfVT#K z&mf5}7!y2^zuIo3N?1SdaN}DO-5H)5HFdw)balcP??z`4`!4Ugi#K?mwB%W7jM_uK zrIXK}O_*kuzZZv)tSFjm>Ap@(4p`@XZZBdQcmUw09^3(>EmF!+ujSO@D7x`30eCNY zHlA}F)|^%DW^3y+(85#PGWKCV#AzsVf}GWOx-2s@J%EA<=&rEaB#pkkboYl_Y7Fcp z+YfQF``>;grhoiF*4fYCIzx>^<0|PXRh45ZphVWc_gNy5?&m)X5lD-i! zBFkh9Etz{(LvDx@xo5HC*_cR52xUnbsZ9Ga1CpEXTO1H^qX9F1C?LX}jhrjt@3YFM z!x51YBUJ!=>)q^?tvVgV@ce{QGq~X{gTri^LfCb4k{)YD8}yu#?RAMnPiw!Th* zp%`)ml_)Oeo^`oo=a&FBxZ*uO3>D)vq&H~+XZlo#Ak#W%9fjD|Iu4hV`r&q$*>!0M zGkMA~{zsSV+0%PR1I*?bd4__T8_x}i>Rr}+yo_-uEC{0HKGjjloCuyyY1z!Eh(;dK z$bYGGc6MIxNWPC>wEmNNO;Etn+WMIu3EZe@AX!H2)7{R~L=N5K(?%tBW$6%>Q9}nk zN;_2^5zdCY>dWqm8dm3pS-v`@6BD=Scjg_u)M8DLLx=EceZ4K=q>{4Yw)aGh^j863Nl^)8Q6NoqrCU6vcH>xO;Q8@4Al& zM#W&>4Qr`?jh5SR==Px&5IPp`l&Qw}uu4SFA{>($cm338oNEygsr@PPj&<;SQL~)+ z`zDR0kNY%1{nhSW+o-R&jrd_#dBC<>0{|D78ChhMtKeDJ6wOsHkOWir8EyDK@czpB z+2vLCPeR39snXgf+&_P?uz_SGr*(K!xD7ud55aED>B2vZ%2T3{zu<8C^Gju(=&eW3 z`Y_B29;{nG$4b1yij~kJDms?FA4$1!?SN~<(U>82TI3V%6L2-p@L(KM-`d2bpRkKk z-EfI#oQ=vVg$Y9`#b@ov)45GcNu^%Z%%kA3>L(k_=xYM}rMt3548*IDa}OSsoYu`E zNk3rMyt~s&-bugPxDx(+&v=NEBXPj@~?jfS#x4?+3VjzDh zU^`!wBQRr(h@P6phx;L5ejN)WB_j^H=>i`hWvedx$)%c=iFgd&${-p9yB^@eqkuILNDq2zO{0~!! zV4v>~-%fsGA}{X_-z!g_%<}T`<{hrq{lE&4*i1e?W2*i>XuI|KSi)r7d+p}N1awwB zAj(*%OdJ#)PBK9#u?U&xKE-Hkkmd_;ylsrQ^=VC#i zKzNH$?`P8VN2lW-4|S@dY==DUb<=f-zV8el2>{O9w-e2<`}gR!&W$-#y)iQhO4@k( zqw6&yvdGyckzK~&$*)(san_4c8A=`L$KIBXj8L6w+dBH2e1)&Xu^pY|Si8>qd=AG1 zyrWDrNirw2?PZBsD{U;m=vA9Bwe;)307B$COwVIC#PQ;`)$tVw)=C0Ht_L~xF_DD9 zwg4Qxkb=d>hZud$3q{<0bBiV+jTQq@a{E_EgjIi#|AT;+bD6qZ1$luqm@j5%fdv?+ z%s!yFd@XqEH2k63PZJbs8;WO??&gC!7*p8c81jNRoYI@d1jpb!dW?-ER#IxO1^CE94~3PZ0^?H%!^ZxdVT2WH$m(j%PR z#zYbGP^ZEcI9P=E6AeY~IEBvn}6G1Dk+CVxhcFhYT zNCP-gbi1lsI^D~go8XM25hUkeHDFt|?7Q#tVR{uN@dsde=J1|OuK^x?SZUf23fOh@S8DsTJ{`;MQ9;IS?S$+8mkkgX%4Eg{)^CM?NJNZ}hL-g^f49 zRbQnjq0wj)Q<v1<_hGPYqaGjY|U7hOnpT7R`zv7>$MxjMu{HVi^DzLzv4D1 z;B2T>&bhrP_B~~c#49C5t#bKoNUkQeZJSB%dJ%Zowe0XSTNHm~4Tz9{4`cE%d?NG=#TTA?SU_^IOvOed~5M3`Y|m*evGSWVN|fBaQqJ zQ`eBm4p9vIhqJJ6TCG|I6jM$YWEjb^{N;w~$p-yOJZkQ+dDL|iN|Zg|o_}@?CmxTa zp@OuB&F%d-lS;$quoF*X)B(|Jqsxh&PI6bfIcwshT$Q!7^sw#7wXYY#6tjBU1Nm7; z(BElO)QFI(dINJY2Mi2i!ZhO5e5UAAJX&unaK*_^zZ;miEh__N__Xci=|ZUmiR#MN zk9DtRXPN}SqGXWWw%N=!?a>Ap`4d@4m!F_l2@RuTe}VPO1l{zx&-Z+Hr91^6U1)w6+wdNFUld5Txi(Rnu;@7@OiY~Vyy)}fDT zk=9E`f-%6=e|9&imhIC^*cx4-%j27m-=~AoU`zSaD`(zuMxQ_q2;a$Y9DI6R_0r7D zOc;o+K>rHxJ6@(quR}s!6}`f{d5?bKzWXPf6N~A8&iFrl_y*I{2zwoKqKod|&tZJW zy>;v_@#x~=e@_XXJ-H%^!Vt~&&V-Wwd-_MG%z>Rf_PXlb6tRC#a_{_c!VY;2l!^a9 z+TqXPxkK^Su~kQOOaIP?I?W=<#qj5ma6tdc&-*y*=)&_Kw2KU_)od!T@FhNrQ93@o z1N<|&LgLyGKrd@xU|_2}@$N@72^I+;%sBt)ClljXVCLi?> zZzCN44YsVY^Q!qVx7};$eAU~%5rb+v+B=0<=m!7u9jB7(gL{~N?*xx#LSIj(!rh^m zOq5RS&EC#V!+XgCZ3>`nSS50*7$=h1EUE8BQm7E2t5W_J_IkiUC%Qo=kCx4~mbL1f z*T|VoeoD$RPZ*k-q{I0BoGJq;cVz#fBuqV>JRP{*MU15kbD)I=sOX?e^QwK2o05wR z_zS4NP6=?{Vwc3L?ckgDH!sBPB1eNb(m0;4df>nb%=)r;4JfmX@8nf`JCJ9BrM`lu zS#N}~IA5p4MI27gG~y82)JGYGt{C7@&fMqazJ>0XwC#cq&VvVKtK4#02dlJnJ7}w;64_1@-VBNb?C4K z+9i*f`l=gDS@8FsNiW-sZacf^B~&8Wx3f?$+m|w7KbZ< zw4e;J48_v6T78Hs&@$2{I>{V6eP7UOuq2s?;Q&vv`QXl|%m?EOg}rBWp5hyKHioTk zPyRJa+&<%7qTIKR!$(7Y4VcQMf%qt*nk7HIEw;3>oZoxIcbehL{f#ooVsv6wL!&5}+1M50hPRLqU&<3wgh3 z&0L!8rN*kbWr3eB37kY<#klDbzYr6HTceEZQhY4d_Bs@cA(I3~Tq=v&j_ca{TRlJS zB{J>Z{(9&5ZNICR6_h^wUY8@mh?Rcwg0i?|wT1kKybeUywaBB7SX}91`*TXWrD%$L z$xjaRPFFxe@x44PVSVRh>j@{D>kwa2ZkZlsE#zDSnz^oU7Rz>IKIVjJH7dH{2*}=4 zt*p{Mj-+pZ>n#>;xwyKvobZ*@lu6V=H|{e03H`M&yTtxWmGg=8>*y9cT?k};Y$0IE znp?s}Dx`$1bbh62>qn3Q;^%oVne86Xzhz0A zUi;3%ton2$g>Q&Mb6E*iXeDj9X_>pJ41Gc1w@HSGSU7T$PR zq5A`!KXhDQ>P5{_PEA$)e8}$%ef?|q1cGw&k>ujU1`QWyx02jCz2}yjzqSJBMl)?0 z{2_-LX0166@p~`Fm38-S9cf=T;;cC7_1l80jh*rwn_4$5OZeva&Z)%4{(p+q1D z?CI#}#6<8gjOMj^n{d|2V{tC7C=Mv||LK;hM0B~pxJP1r33>SK9t9BVDHXzGTNvH< zp*;h+R<0n{4s4A>)wYX+NzpKg}hX|z<~a7)aK*2QL$s&h&F$&&7t4;yK;yv zihYp1&J(KR!hC0_Q14Z{_XJr&QbKWF?NnCcfN7n>&_}~1+igd_>FLe&jbpItJ+4Dw zS@Rww2e+llFVxdYqbg^-aB*>CPZ}vU{z;gSv|C12=@<=8+=2Ysy=CjQog3RfO3>6& znOFL*y8YEWuA&x|e=OTHhdU^y9V+_K#kS-Au%g}8_Wsa#1sw)AXPNp%w}53=?dDnH z$)s*O)87Ye#5Mr$_YY)5ms-X_Yt=zlrUeBJoW4UUsq>N;$oRR7#hI2U>+2ip78V>) zHJLqcS8u9!kTH&OY#HPD5k+VfPwx<&qn@eja&9j=U?A>pEUjNYx$%``Ty?jdlaq77 zY&I_LgScRBc^ee(SV~ID2b`h$uC#uFs5rOm*Z-sHyTh8;y01|$;+3n|0F`FBHjtty zC{ep&nM?`tmT_8oSudl7GB}5JoqKtwa%c{dkcT{G@j?ts8>A!Lt-;kmRk1UVJ zw}w_L4|ew#=%OXhNsUx&^g>mr69MnjJYY3tOj&`~my#vGCWh)6(LelDxS^vf@28~} zsnnH-f8A0y&90FVZNWiD&+eV=zlSZYQ6nTJ_sy%(UTmJIe~8IN5BQ4}Yclppf8h!% zafqLZyQH*;9)hmw!0(*fS?s_x|Bpn2y3D`Mw?&=@{#8>`6LRh;NCFmo+_wbH&6tFfvz!AC4=oiqDAhJQD^yHebZ zr<=_g;ncfLs`dSn4cp}c>=f(FN;_$ZO9mh*PEAly?_3iDOwuAqWW6=KLQ(nKh#+{U zCzP%WcL?-uvvV~auXpSYup;luy47e9E;<;VrqM7h2=)i=YOgsF4Tbj5MHEBg854oL zTY&-}&l@^lM^`S?X`e?79gw|VRzDg!dRQz&4oa9t5F}+hhHLsvB?e3e)(bX^dB3AQ zNYgV{_@eHKI@&?MK{2ZiINz_Hk(KVGc|`9FE-7==rxns!X-`O>-Vc7bG-$ntrK2g|DwNORcA%1|DN^w_q9EkfXtH)eHipPON)dm`ur_zj$1X~mq19G( z8D?fEc$g~mg>@opUz5O`s=?n znkz>V*!?S$R^g+4%1_tbzPf5zk~=4dn(BsYoiOI*;wnPPM2zFI!?-W=321kbd zj@OAGDbS`Ow_D*AHnI1b#HG=d^snK8-4xrtO(8iCdTcoNtlX_LpEW_w>neOuRAHO= z5VCYcVc)UpQ=$LLr3Hb>7WEW`%s-K53np;w5SH+zE0G|(2DD_Oi^SCUV~%je@f&XJ z)c}6a%g=umC%EOQn`X!i1lHyKzAUhSB!YL-keUB9}(}$P3rRpFb}@0 zP(Jx;y^>l>;DL6oj^AcExhYqoeE1Xxg(-1%sytl6r|Nx~x!3K@B^dce=6wInZE5v!m)7gJvmUBu zo>XG5k#T-j-p{BRlNgoE%Kj)yaBDR*bnUZyb;j-Zxk@QT*uRGb7i5ihaW#~UZH)_E zpISqEN#_gvc}tnkGCV{P_f`r`NB<&kC1YV zwm#$mjC{Yg>3wMjDINK09E%^=>;J0TK|u+(OZavJCvW0bC(Op`T;Jt))569#Mr-V5 z#&4ba&$n%Oa+l%(+*)weV41PMNMbckQU<#YRJ^kHRNx)TJ!j<~#Ip4#TR|cwIcY_X z?Fx>DtoIjg^F@Og*ZwZ>66Kz!rd3oY#RXHhSE&glw#0)KtJ#Y6d0uPnk~>9%OA?I$ zgluKD?_~;bXSMerwhO&{*VTg$;&>DpnNprui^^U43MNFQ!l@ejy|ZHz_ITC)*I3#Y%RTwt*a?F&q6u4BKjJH% zpv+}xPQ zAgAufg=N&W?*7=bznGauihcC6w0} zi7v4XyRS`kkPH|~x<)HqrQd1%&ms`^>-hw!VPDst#}|N;W@trLh>=Jp)cH%RdAkm% zRQ1(H&X~ljq+0az{wchHb{kX!KWH1`>^a@%%1dAtjx{KZBYQN`E^l!>OK^l;xy#E4TltsMXC9+` zBWRJ2{Y259s57B=ErNb@;-%OjUr!P(YQVLeKRwmAFH!>N*cU$HxzV> zApHdpkFzb1$nUS3=ljMyH>`mYhrm$09Xyh%%!|9?%oS3Mu69!nTJMiK3w(1a;eRB) zIeFO{0dE~>ld7$1nfFCayqqJWHCwm5Iq?CmoLZ~Yc~biSH4X4vm;a7Q_Ew!&TShM^ zxkHDr4Ep;7$^2t7{J)7&=DZz8l~eZDPEN)9M*o;l;VS16X>hNaiG`j1*yiw2lfFLl zfpu9DMdzP8y?3V~5`V1UJtje3PMK1v8dcLg;WW-*>7sA(eo!lmmWA04hm**Iuh{J z4}tuVA9HtuuAuGf9ot>wXI(bY&{LfUxi~#+~qInnS*dB93Cr#yMHX!xp z`?`4)$B~iEPSmZ8-kRGDPd@0biO0_ym_yq9xE|+L6+RqtyQfTq7QF1R)!hlb;g5jG zUALIbMYrdHUT}BdWhmDPl3P6;I>y4z)n2m)2 z((OFbNE$i98Q$7@AK9FB?T{q+2&SCh=T7=#*@?6`hGw$AENWvuILpIYJK*huU*eC; z=CV*9vg7z=R0`+o^0L(OpTx#k9OhJ&wtUFUAz{gpKVuLd#y#s#O80VHO(ir=-%0cS zXjGQ&SySPHw#A^W8=l1Lp5uS@Z7ZaM>xW)lo*6O!`PHE}8A_y`l4Pr`boagBQkJ<_ zz6S)vY=?#(KB)NUgx_U%dm}0MFYR-meNY+8k9Hl0$my$ZynFij4YNOji!)psXh-(r z!;~&kZ-WmDDY7X3FjfN2f&bOLzidMyi3#fcB{B)Q^PJJxK#Ri}YzZ9$vBz>xFtG8@ zxo1rx587gXvqIWK{@U}ydOHN(9?qFyY!%Mb{PV`cWB25qo1lxGzpRwI|G&v0u~_Z& z1S!3WXV2r~ppe9SdH<~xT;~fz-p&n>iGZ2N1vU-_*z2id-F~%Q;veuZps3T!6*`^< zrw{pY8eI}IrP!13HsJ`gsebNh$kKcT`13bEpEGfp~@gIe__HFtqypy|jeZB=vEC2{VRg3*1Y|8-a zrWMR|PYCww)7qrLw{umtRUrdrF1`cIo9ND1;u0!oI$LnBF5HeHPF(9^4c9!bJy;H$ z1;QfM61z1?(aeeaGu<})20=9a-!q^EYf4S1NOD%K$07Pc_L}Qj4j%+TO%=t=0p()n z0OmHYV~6j|?}|{ZO!sxHko6&^p!I6C-u!lbK!ihYuS==IlkP{>+o@Rm`!>FAvx36B zE+&aBZ-KE(IcQzyGe%3>Vw=Dy8l-^%6+es=cImppyF&hLbm-<;=x~2lT&$9&w~zyG z!|yolbMdWDj{U7+WMNg;G9~LiG-{+d?im2^R!(hdivsK9$~IUd6Z@rDnqOm>&GP#* z$+Po4Yow~%io*ojxWs6-p0zhpGum&jRFsr?j^SGc6I386zJ}Z#MI$D|bNH;hviTg& zXU<1!Vzx!#h!TOAyFSZ9sF5j}i9goe!_`>acWnBDhqEglM_o@54IaO#ywz?fb)VcP z$eyuOSnS2GC6(c_4f)TXA|k01&e9 zE^56u)PI4v%FZFNEF=a{zG&5)qkM{*@U`K#Yh^_?oZ9d#j$GG0^Cy`3*XpP3_>*ZL z}e^gvq;-c^KhCPzCQ1D+IU--)Pa<& znPdmkXj;-2f3qc16(Q29<4+_ltGZ0Jk^psuwbDs)9bFhwpucNgTW@t-Yf9>?+0^k6 z+xOkX!)kFx2{KS+pfw}i`$|Iux!l#=mk+QP0*}E=n%{H-oz-aVe@&pTEmqY4O=1Wt z)3;xqQ_2rc3oz<->WzG;F(03s`qiv_spR{!A|^3Vr=z20 z?2W9k&BFpQ5?zQ$Z@{p(m}x=ULm|4}4(+`X5WnH71GfSesvUhgW>EMzi8MxRHb7wm zN!X1tv3a=1#^=%HB&{{QMJ7D1<~3JNtVUXhRjkt5bE)99?@2c^?RuvQCpO&~R>8En z3q-PEQ=dYMae`=3mQnVby>WbFDwf@BwV%1su-^UDfhk^Qj#u)Fhqz zJ*`U2$AWD3*6^DzL88JmCgLq#4DWk z9+bVnX zyQyA4R|tj<2#pq+UCgnxIz=1W-sD4CVLQ=6l`=u2?dA%h-^Wk7x?1UPQ*m3Fx~kLb z;UH`0VgU~R@Ic>ng^;z8lr7Rx2GcV;Qs%=}2R$lln@zd5Oy&F7dT0s~Ag@k8)YEca2?)hm5p~64Di59qKWeo?Kl<6n{aWS zV6f-bx6GPJ9DD_3F6-E?U&4!%zvkY%$o7fi&9C*P&pC=;oxM3+4n>6eGCeBXC--)^ zBu+sVldSKvig^o@EiGs-UfUpsuZVtpibrC5Og(B0qNFS`__mrge1A55AFhKTikO?G zG>Gc&YkLHbZR6#!1$uDSi*POsDa~3&nZ!Mt7Q`TJxX|wZJ3j+~ zlV(tcKO$}ALw<4-T2j+Rq@HgtFTyEJnTvC`f`Znx3cOEmqEXw9l3$cfN$e>-+FII{ zz6Wzq)v5bS;AU*BO>W#lRSUg(+B5bzL=_F zm{H~n^xT((lmhd$19zTPmpmCC789+9L`m8dph6dd%x%^qEIpQu8H*uAA?ugylOjQL zft$Z5*nt+(nhMBD{&(X2ryE~HL$|IdudkmS?(Uy>w)AE(tVGc=8BOlQZVhn{Ua#4z zKn-jSd2h|R_M`AIs+b21rQfMXscTRZ2zRiSC4%jPBdX-V31q2(X!|x;6^IJRVYkw# z%-5f!t&6y&Uds26vZy?Z_!=D{-`3>|a}%plG*rAm&-T)E+8j6IWYG5G*x4r2FNzL0 z%3G=wjO-|1J>yhePYaJFZ{cymH#|~#m!!9sNOJ_$?Glx(fkn~0PuoKjEdI4n|J*i) zlCs%*ebLkl!rVL>dqXq1Qf^L@*};b9SxJ*2VoGg;B#Lt>)T`bVE% zrdc1BQ_ARJpS=rzJTneXt|@?_QkC46ud*_EaU{kk|Gcl2x@AHqeb$mm)R|$}?+wS0 zu^@RDZ>^7f#0^Kylt5VK=sL(<1 zN6a@hq^w@ZXTH&n_id(ChZZi2U?z&aTuGw@f@PHmHLb#1)P3$*abU|wGbchxNg-0}|K5h~9`Y456 z_hlF7vU&@C3Rw@j;f9(&@li7Dn9S1eTKxXFffx`-m0Qa9X=RPhrZyXh)GWNSuCKb; z%RRsBq)nEC>U)&RoU&i-KQGo~Nv7A>L5C%0(M3m)6bf~tKc}QLkYI9-SSecJ7RlGZ zBk^>6Js<2c1yKb{!K1>M5JWHrDTV6#s`crB`KbG(m&8HC5o`C3oR~ko{ngE`2!Ih? zyn7b-31QYCM5kBuesnA4mq-@GGv!B;XW$NB)hSOzSpGMggb%;6G&MCtYFEd_B_xdJJIlhQ=vE*l%KXAS z%{$dJ=1^yXKmyp0rwRLG?cbm+XDBq%c4S&pY*hKo$yYA-V3=*98m*+X#JOzyCJwXr ztx?LA;2+xC-Y`hmX!+w>zLA;h?ZPF^>UXR$e4`*r{V(WwTsIO|?k0p^;R(`@^SMwi zy>CD1@GzJBJT_InLG7%olsRPkk1%|2yxZvws=E7rdUr}Pz#-8_=wKYt>+ z3V$-Wa(0!pkyA-^8nqXRAnZ>*BbYN#a|oe0geXc)wf;mJExp6@@L%DRQ?%mqT2Uph zaS8*t_ggVy+YPvzSWJbwG}H`*Q1wrNyO99xKL|JFvFeT46PKc6`K~p~_@R~3#DlNQn@Mg|kr$up}rx&F!i7YFCfLpu& z8#`Xcr?+ayRRhL4ZR24P+DfAapx3t|<%-Hpw4(6l_Z`6rC--4n-A4rwJ-@u2`Hs1%EGtJ^tkqZ>>fl|Gb zaj-XE0@k~we3Ug}F-wJP7L#P)$S9Khl?;qVL&&y2bYwUyLCim+RZFYF4}XuiHh4#Y z`mUK4LsmhwNr%MPz^RcnG>}PmKjr;Di|?K^((GO*thX`nl^S6*U4W($w%_7~PDq=bCy9xl!{*q#+_VT5cRt$!+*xKLOyVxwAk z_S^k4UyD&cD_gyp0bg)4AzfC-%Q0lK(NF(`eG)UD+NSoWAh`vS? zWc0wt<$DBAn26Xydj0%-5pD2wv0CX1;JlD`?)-CpX1)our5Nf=T6Vv0l78XJNYm_~wz%Dv%WSmn7S$=r# z17@>(F*LIlI3djW<(V{N|HYzlPiL=JyyS%WTL^0M9aea*sDC59B)a8wc>uNUFllvf zykBE_$KO!z8=3O0 zPBYv{ym$*3hOnfI;^OqP3z=u%%u0u>qosKZ)lMzcf6QA#sw5KyD~3kCef9u^8|~Bm z{ltSg_9?yV&$T2{eN@vf3+t7O4B8S4CSq&uDy)1+Azc4qQf0dloVH;Kn{9)1c~}*1 z5`x)F6I&FFQnx#UQt|ImBwhBWLyZM<-`?;rPj3&D_#TWYM8>9Xl7uY8%3;!ym$k+{I6G&aJ$pgTp(N=!(t9@D}wtC>pCwk@Kh{uRJ-P%K# z%9cU3FOBq3$|iz6igX>78gM<` zrvYRkGgajE4kEnko?w&o7iB#`%+}@jnsjmkys86NTYrHQmGJrT_l20CP?!#qbTCyz%#i#Um>*@c~LXkeZg}0$1D(_FZ_$jbN(<<@NTO zpeAqapmWr<$CbRvLPYWpS&1|}uoQ3p-ZFL_C_o^zG(>``;3@>iwwu&ytPABN(u(!r z6CYXAF4yW|6iY@>ADn9fI}uIHS{3X3G0_&~4D@UUEDR*~~LfvzA88m`bh(TuxUT zpXTs}@{ntgkimpq=P=*doPhS51H9qVuqkep!Uwgtlor&U^0sR5h9D%w*WPN4rptr% z7s7IGEbLIhsMJcwI9|nft)dn~jf+*dccJ>-=;-SA$0gxFw>$Tg660S-ze+*I zvMI(V>tSMb6Rp;t%EFUPZRk0N2bT8>rW3xV9r$YyBxFQg+3sG^gH*f2yFyDxVEEsgSCuX1UU1Q7se%mjgyfk~KesbuFl-Xk11y*A7 z!GXtms$W}DgC1wEf;=;U_|kLK`*!N2*B-eBit^4LVUEp?pM`ZO15EIq&MnA-bU zS`p)6v-XA>I2#HH5B(C%D##z%-tSYZJOHP(Szw$a>NiWsePiy;mNvd69A|V!F=9}d z3Zhs`BDag!?H5?rPMAX0zwKEDwwvLBQI{nL$yJ-s$&BQF+cBtlvg&4i z5dKmHP_cb$e%tH2$PR`?{+SewokFlepAYWq%DsUQczN$0Jj1Nwt#KY9!~<|8Jt4=k zK0b}oKA&rLd0|fg^FvcX3a1V3JEjCgi`LJXZvI+?BWp!rW@ys3pzpaLANnh4{FNx- z0tAUB*JnJiKk-fBk=hE3F;qlkxOZ@Nj}Mu>l=E5|AG?C>YBiR2!B5}A7W%#m_@3`L ztL=YWDd79wc&ngs*R9nt;P&^WiB%1xx$g;otGa1s9YT#%IVQH?$ruS_lBEYw?%OG% z0bB2zf<~~_Wsc+wQ=75f4DS6+IY#U4*B1wdf5a$Mc(m?MMzDkiV7jQFjiJqDz>5J1 zZdwwA^V>SBZ=n)>{gpWk?hdS?Wr#a>8|6heLlllz|2)n zL+e-SF1*(V8-H38iEG_jMmVaqHHQ+mCJGO_!^!u3BLU!rOrqYm^{e zjDI#yt2`r zChPyx3sG8`KC<_*v~^X$&p`gEDRyy4_o$AZE&i3HO>vhP{RQloEW+-S>YIf3_3M>* zO)F2EtD42CA3&&KxM0UlZnzuQ7DwKptJ{~|wD%5FXFLk(aQauF%cXQ%D^eR`EDr7u z9NjTSQW@luZNaQA&1ovhIOzMFw5j!h2sKWRw=Mk&xyNB`M4n%FPwdQsx!z1Bhj@Vb zC++@u9t)KnG(4xW^ut5zphorjBPnv(oXf)IUOuSFxxtGiC=gh6KbYd($@>0!XwFYu z_=w19IYEVSfaYX6 zi%<8ciB9S)Ta5I)1%eOx4MEE7Z_D; z8VQmE5`RwN{7uvaka)7vYSYlmxY6I#2ru&BXdhnok_CR}b#_i1bYA&4;iw=0DB~oY zbhGib%Bl%%UBf#Lh|I<^k^{SjIbS?B(X6k};n7s4$izR8kZm2AHi3;MYXX;zt6`8= z<4kCSc-V6MqR=tXgUj(dKHuDhwA?ly{g9-8Ts}}D=vmIr9%85O;+S-*NJj{tv;2yd&r2if==>~q()aBH=#azduBBIi1r ziRB{|{{HI>;<(J?&*94-G|wTezwmZF-U)Ebo##kBLR^n_JsIUG8!EgC<7x5+mjP@x z(?H!SJz-%kE9In&?m~6^!kqqPlW(_nZi_zxx%n3$kJ!d}NS6l^UO!s4)?s-@Joc(--C#uS*`(j^7gPGR(nvecz&`Ih|z(fX3egr_%XK$rBG<_|0 zz$ND2fAahN`m53BmFP+iQK4Z_;u6A0p$}0+rxX;X{u1tJIIcI{(eIn{^b>cltniVFFXJ5L9&ab)4a}*>Tlu{yr+q-z;=RcNjy7ogDT9r;B*VYyTU!L+2P9 zdwKV9L0;wsbV3ymca9Ibo}WjL_OC58yiEESz%(4 z3+L`R$|{1~93}uofYZwsV)btn_N=(@^Cw8eYhFz8i7N85FFUd8xb=-=CO1y{HU_@e zY!x5DJ&J5@*WU>WzF4>l@SCt5{D$k)%a^>nwKDj{myv)O&pGH~L-wBkbRT?auv42m z-*KIPCOl-%(f?eD{=dH8aQD{J6X3o6KVgK-^+SM-Aqf3H@eh~wUmv&k?nrL_6b1-7 zTxEJZ;2n+!pZ?!hAdW1ZkQ?mB86{IgfB$(F&X2a5FZYK(pNjzh9@1`8aPG72nZ7_= zB=fc$g+3(Ld~NHy{6VuI+k)Xuz0xD>pBU^m&f)ZMjv&X_T^y!j28SDBn`UmyZkO!A zqZdwojQ#P+)mK$sbzCZVwG%IS3ldc6)1#PW@Ax122xy9qZSTDYYFQCHVS`O+!Ah;i zMd1ZT$s>NUBq?5$?S5JtJ7y@Qull-mhvfl0S4C}A5l|ia3!G=ImA7f!xPMt2JC_n^ zvUtJzbV{J)?f(eAVHg2{-6s|WsAcm)*8tEJ6Zs+I5fJQEm>RF1W_a9#rcAeg!4T}- ztWP@_g0eD8y0*%+vIuPFt>)Fn%{xp~+F9k0OwKEU!`1`TxLp0X*MkSIG^4ey3p^@G zY}IT#c98PDnd)xxo_C~8Q&Z!>$mDV&-}>w52{6i!H;FfH<0A*;x0}NcbZQI(1qAvK zDRifMfF35JP{nDT!A1hFzv+fZPf`3hAUayqdZmS!_ zkx0f}nh2GaM(N8Xcfb84u$J>7^t0!U1xu8mAiqQ!4uQm2rzl&q{Shjtg1p{OP#tzv zeo@{-d-dwt6Q}L3I`^3vBeIdO`!s0V$Ant@k+tE?nxGmGnJWZzYZWIHqR7~w@V?@( z#WCL&$KHOXzH)+hoBy#-IN1?y@T866oflm0L*ti#3P3PK^G|&?7)?$n{q^nm^K>r%6ou0`Ug-VfZo)}A89-y&yL9XkwwuX$C<3@v)#^|bU-i;ltN zq8Bwmk#{0pycJ>8!lC+yN@;DIo+@#_@|=U?YD2ziV#>43Kx%2~)rZx*V+m4bSpnCm z(vy~rwd9xTne0QcKgvC+A%Z_mFUINCdMX#HQ>(ZaKr5dtHkph2+C-RsjGgn?vA95& zyLs*9K@b;gI2dB@dp@r36Vx((mSPO{S#gkW_qpx=N-nX*+V3H)PQXxI8RNNFy!WVIJabf=99T-L+vUz@i`Y_nBVzu~Y_O>+aVhEJ@N1@d>PY=VXvi!3@TEmag54?3>?L*_*2>KhXF}0D=JK zwXvy5K~BxRHL3Q|%1m>>+yZwIQ@CWVt*1&buF zwWfy`S~^blONNT@Pw|HmW#{KBDgtXQhAn_@D(94I6niNqsl4zACQGx1Cc#->IR_X5 z7q5k%Kl1Y4nF3UiuU&wNkes|r$ENQ*MW^Oat@}Xaozwh+{LIxmc8p-J_ZC(^>;3RA z!W%;LHk)alkncfC0;{V9{vd8Zsxm;x7aCrTr||=Fw(b7XjZVp1X$uQ}+ApdJe}-L{ za(ZO2Cx;@Jd>?vf1n77&)GDrDub)s%XsfjyQCs;Y_1a?4#>QfL=IY}796fhPvLvwm z=+HMC>lE%a`oD#UBJa~dW|&_#eI|YD^T1k?6DzX3Pr~ERf?EAvUYLAx9Mp)UhRvBi zsXvN2E*W#3yoIM9I5wM7XXcF$QXh2>)VzIiHCtOw*>hxC6&=0kBbNzi{yI9A*UIW| z_r>?Ix88Q@UPn`xu!O7DV939U^B(sPHjVOFwXrdgBJCZG9Ft23at%gQUYBQX^zq>0 zYzN{!?Lw|vC*ns6+LA5()*I@|zeO&mhzwWR$DN#atU871m&W&xQj4(apl+f!Hg7)6 z&@p^L6iDV{Vc^I9w1teh0enXdhYlq4hm*XkzbMw*|J=YxeH$$f#5XKwsn#33_xvV? z4*bOg@cm{)NqSvY-XQmsCDEA(qYO^XTn1AUjemh)nG|J}z`YcK0`9&1bq{={2!Yo- z8*ykFMVKv8JpvFOq8#uIF8!E3fZk8bcUZHIG@&}${LkRTAN2-pH1N$W!nSc^Y$@ah z@1T~JW`(!lPl=N;>;2SxAB*aRQ67>>x5|2GF-dmlGRHJFeD6VIJL{PDx5C7Y`}gd; zldXl_K^hRj{9stOyl38loWJ@#ISKCu9|y79`~7_iX~Ughl+t2Z#TfeSG{ z{0X*mam77&`$bwtyelH)a&r|s0%~@|gfh1O_Tycf*^r&d!`6jNo$K!ZMEMhY zS5iNF7zr{Cf?Xx98}}s{t3FO>D=$Yo6W%&}&$uF)Bu_eL5{QJ3IH_Jkf4xnywss-U z9$!-+ySiMsxQrHT?c4CZA>0Gt|K4%xc0b(@jQHxwGU8*4m(4XJk1;?X zrk-iQxY1!G9`_JWr_AG=q5Y#78IQo4mtR^KlHeRdP*g0(WUDLxdJYU`aS8G3wM8~1 zW^Oc!H=-EU+HY!VJ&CHtAPj*bP-IQ+Q~XDl(L!@7eZhjq_3Kk_ON0es_~U(g!9`J1 zU;FrzBkW#mo#bOQ0_i=Ya{SeE;|2QiUNpuJYjz9cFsWdFHIwfzv#aoMh2WYBSj)k_ zfqs{JI+TXMoBkqzfW%~im}*wlH>gH7>22tcn~7zm$11MmJ2mi#KVcZ9*qy0kiOvJ* zv@@z#dHSpEE#t6!_|I>!sDd_$YmPa!(uofPQIW^}4*`nvtsSzbVQPiD>TwDI>04`@ zE_RuPuJwDr(?<=#Qm}FwlFzFv*q%gb?nHKU%zN!3nFxLusM0SUOc}sE!_1gq^X___ zzETZzxW#UgH~=#m=C8eL>=bqS!d@89C31m-Z4{ivssiq`F^xg~j@V z&*noqBQXTD3vBY9NTpBpd_e)Lw3eX4TO6ynaQ~cPK9MSBER~o>-c1{S__;h4vi6~} zmhjG>99sqJt88(<12v90Xj!VAeePUnu%*Iq`A}qcGjgcPrcFSqr|^Vq#dq$pUr(O) zQW6z|D-|~B_Q5tZo>_rVlvV9LNeYr(bB4+;hQTql4=P9bUw^tN`g&uvvSMM05!j@R zduvr0Vk>h8Rcj-3SAV*4V7E`LUMi$a6UTg22T4bi80yoATQ5B>#= zX0^a}1R- zk^~Zo zactc3iW2?$VIpOZq+8eb74Fxt6Am7w11S8n8DFWFkgM>o97+hJ^Vt*L3Q8%j18#NT z@arG`ztY@Dgz~`GyjjV{6aAPcqr2ndKNYl4UIXHQ5rVhOw02FnI^_Giwjr+A`4w^d zr{-v+q)NICw%VYg?|LW`uzIczed(`h%dx(N4H=&ZkKXLLy+>oOM@x-Pa>zv+1B>ZJ zz)nXPk-7%+kR60$-m%Tm-?gHwioO%|Vk{-fJv)_RvdVJXND1`y%hts|w-UL-noL zG>;Ql=ZN_N7o$=C&y8+Em%Yov|LUJtnRd^$%v=<_yYkmC1sS(_2i31Yj@2B*{R7qQ+VCj_?u*1SO{%gJxr z0&a@;rrOHeqE3U?Qb-rV?ngpChxbP^Tva@h9hIisz3t5-#U)<&$y}6{T)kagax3)O zcu2j>^%7_lUHVCd=BoC2@*Q_qtV&|!Y`=Bma4X>{Yaz6JwY2{pyx9U`uJR?4E|6qd zu`!8L%`_J3chzuxEf(-}@WWIRUdC`xQ4YiPY`hN;_PxN0d#T7b|E8snet97$g$0-Gri-VYtKONfQi6Le);#?^nxYZ}3KyM-r-hZka+3;Kop^^Y zWCcv6&y(j{HTz5k3c7LQBEEM1&F}gttjmd&^zqY?z{&Xf>21@kP+o+g)OzO=S(TTu z>i)=s(sKw0y&C-+lg}nY1&&~=94b7FZ23w3n1(awc%LzH)duNzc(Jc1u8_{aWGvGn zhX}?_f~sbYj=5*#4mA*8OjpW5x}kl{em@x8?41X};Gt1@8>M*i`*-#ESt<=V$5U3h zAYEjQ1Oj#F;K=Nr^~Qo$307ECf>nb%BVMGxdd(aicA{d(ofIQfx?5r${B^6|s6{ShCT6X#PF41T~aedx&gJpVUW&Q0dZ4dJ)q zN4z1Pg7JAjMVb!Rzt|J@>!)}mAK3ax53~LSl_z;gYOSw0BI2R2@VVtjs#u0?U$gi( zf`USbjQizVl_m=&e=94!te9WUg@o9RRrv~h^vS22o07>Rb~9eQ zSjIwZf&@Lh_4jix|+XA3Io7#rssSGev%WqhF2%)Bx7{mQF%fGvbdiqxVtjc;t<+%O1$o-W2|4tyWf|=zzWVk~GZ^8w$N|$; z$}zPa&Nkz8bLJO|hgMnht>Pqc7~CA;Tbm*&HCS0mi--syy!B&@6t701{pJ0zQmJTt zSGgj?xLVQ*^~yXt5Lh?|WQmjd5KF}H{K@U(a_Gw0U-7v7X6|{pfKw;OSLvz$)I&<( z`#6TD=aM?w6o^WjQ-vH)Yd307I?JN32IEv6lJOLbI5t)HU*G#i6PNp=vx zaHKXbPVox3KCZhjRC$eKtn}4-R#rJVW&@KaIH4UP^XP_a->fWozQO??gd8pIN9&_J z$198>gc^(C;gD0xx%GBGrxq4r&X9c`gs$GY4*x5I!==`|4jp~<;?fd^zQz@m=xabV zQlao@=(M|KbjUeIfv+4Y0F}l|9Pa0Hz#nQaO8Z*!aI&(osHK2DP(#4>55(I9L}}m-grwl zfqV|F;!S6~s91)`1Q!ik+q&^D@F{I10yeqWf`yqIRyqZHpMNS7E>})TRQArP=`ZT; zpLPHYAlEdBeCWm<0ZEe#`SHq3V|d{2+oU38d;H6(rz*XwPr4t%t*gBXZ&ea%A1yyr zu64-gw%2IyIs~nwwYJ+NoEq& z*zC&H^V9u~f1mUL)(o!~b=_T^Q)r(7Lxn&Kpr&V(9&PJAwQKYv|{fYge zq04pL?0P!c7st(nt%s1myC(z3cc7}<6eVOi{c}oUL_wa}@?-iTU+s*!y+Ncv=V!!; z3g4ztyTsd`Unkmh?T@Q*saCJt31$Bytn`0?<_hdAZVMoq?_UB$^H{loGu@j#!M{7~ zv`X!ht?Aw9LtB}8;_qVLcyl)-e3sv@I7I5APfJEuNvnw$%yV-!)N9sAFVrep8ksBN zqvk=7+yfwsDi_gXglEh85Knhi<{lk!NH99Xvj#V$OFNvEt%aB3d(Pw zQ3v+TdQ__jECiE3@^zh!u5LC8WCMU;+j|yyVycq6>X#q9BZwjNoYnFK%tKs*7~s)g zyniluBw^3Zg1x)An36#Ga{pSihk8JS*}$kF3%n=^*LS_$eH(JHf7x{`kaG z0<`q)LAe`YjS@GGg9Wwq_3ltke5mj8k+Ax17DhRmKfX#@^U-dQvuRzLsMY%M!MTrZ z_8dd(&AGiIf3-Xp+PV916;3r@ zaeLR$^&V+>j&DF48U~P1xybxT=Il^AOcAnv2NT>X6 zgVH30136E~IkepZLG+IkvVFGiH~^;{4$Nq4*h$FNkSeKY zDgHlceAp|AKUvsrcY}7}(iZ%`;L%aR?j6nYe?JJM8~))hfXCgxlgO=Mv}Eav_~)^shzReki#%!(|Xxell54q#*JLW^qa?wfb{_nI0x(i$BiXUinIN+ z^g&)wM?M_wr7ZeBV)4ii8x1}5Fww60Q^!fISEE^#=!$}R2PZ}tian+XG=@<{fo%|H z_vS(aX^6E!{!ufo~ngBzY1r*H8^ z??^yJ{zx@*Mx*1*`24R0LC($-+ViOphH`HCC$+ zy#&lP%$JkOO$$GKfRY8<+)XzDn~pNZPXy)JPSV}n>K06%D=12`gtvAH%xcaWWw7du z<=uy!L6NjMdJ6_p4lxZYB?0U^h+wAn2qvXppEVC5cko)4JQvUCUJ^rZIbfKJnn5!k zRj5Zl;Rvl;-Rf(W%(?inML7g@7pFhtOcm1JOB~Won+{;vKs=R%c>vY$!mx>!wzj?X z^8R{!$lq5eS%Vhp=l*Cr_#$jO8@$4KHM(etG~e(`{-dzT94jU*HV7Nu)~3Ic0t_qI zlq#_cDP43fregrh(u1{zBP931ACQ=g{9)|K41~d)UytyuS7Dk2V3f-F%vmu&fAm~m z4Y03#Oy!ZQ26yhiD?94pdjm+RP7H&^{-*`h^_>~V^iezue*%DS{z^^n!&B6R6WNpD z9X@o)5|dK%>=d?dK9;$pzex9h8%eLO7D|?_+0f;Q3Zaab2&2K~AIm;tnpDCEBs4Dr z3on`-NaUR)_>tyfmyJlw6PPF6vS)|?(xs}=5<#kjIvuLE!950&3%(cxygdTXJ*bUl z>`fytjJW{S>F+d*H9a}msPbk4z!GtGGuGG*^n+|(2Sp%VEi1)}hEnV@HsX7r7_|l> z!D>_wJY%CUY>vJ~U=4P&19<_|7n(z8nXLI>c8OKJHGR;hSIi4~PR#F(JB!L5JfZGc zGkR5FSxStd&4~v-?sGCQsG3&3=S5)C#N3C{sJLuhdhHj%(UWEO(YrmNi%!2xZT$ybBRLI7CZNe*u2q%eh*bd2xaoaK7v$9up{g*i2w7`9aKACq5 zom!IxF)&ti??2rKDf+sLkGRYFO2ydHH%ou{3Wt&hmv6@J<+^M3?$I=&_tmz`a5+K` zIhT2(jMUh>2cS9ja%S0QJ3s9Vv&6U((nCuveMY553{1+-@Cwy=GF0a`0y!nr2zD)c z;0OI!G9(y)Uq~TF@d@tt8HoX9Akd&4Hp} zEP$Cw+8~eo!$L{91Y=$U`zW-=z7IHgJ~)p1#3@6C?A; zOay#q2KlAe)!9{m>+ZfK-&;jv#US~uhGzLu$xc1VfPObG&shs3O8(v&UQ#GN%zUQ5 ze-01kA}N>64N8)!ISCHQ(j>u|DG`5AMx&TVgr6lG5#~4T?9vt`Ak-1Qqo5JARi8^ClgErw4Slx8uj^raY_R0XQr2h!}%(J+Wyd95}^NWzWa=EWQRq ztVpHPx=aGH9)s}TDN+6K!~r3W*a!)7?Z^4ZOZ6bKFmB280C)?|g`#=|2?N znvPAIJ!bS|lf_{vnXxRBj9j1)nR9V>rF5%NW_38K%AnbHVaEDqDa*CTi;4?yXDz)Y zuxKLakp}@2*|O-{=bBcp^gu|yOuQa$3Kq}sU~in0_L>Tq&sSe)%MmA_HmUF(Fe*gv z4(n>kJGlf3=*8(~^6psGWy6y&7%Yet9`8bOGSSb{2AvLyS~}l=h~oU6X9N;sBR}ZZgd;g`)3Rjqo{G=cZr04xU`l#l$OE80CpD0x>C4)s zL&35L6o~eFW^SJ&4}Zr8)c+VmZ0xaGWmllk*>r_G&oBlAf~nk~lrV6;ezcw~m;#WfG#`rcy3DhXwGVuEn=-N}FvCivRT13a`y^H466Cy7Oi;MjY2TsIy zJB(b)zTxw@kAqdYbO@lw{%AY-Uy;vCWrA6Pr=4OG-dAz?-JZ#K>&KDN9JfOzo|m}9an&>urd-6y^34OD+zD09hk0X z;VZ*=L&xFcgAR1Y4y19+XGrv}V+Q#ObcPrq3#Xzf9x^XjUs2d1Sr!S&NzsX+HeAGd z{~7~@4YCOK6quqGr^eV-8JbvkWQ_-e*x%;6iVo@M%V0q=ShH`t<%frt6WFW+peSSv zanDrMF};*N?boo1MN!ffn~iiAWQP??cT#28Ej!f>UUBN{MC#IbTg+O5h{XWBqoR|5 zE@eLha;wlYjkM7|#4inYzAt0uG&05Nsz-Wn@We4XlEjR4N8$S#7NimL`nb`-EWpi} z3J^#aK8&>2UCOQ~~twQ158+0_nE=>#NRC&$K*c-b|Riw{}26M{mRrg8$QmA9 zopl&>n_MKmrYKVu=X`rFS-- z8O=4;(?5d=(Riz0vSVl_)2G^FyfP4(5Zqm4Su{9%7!xuk;zC+k%f~Rk?V0Sa*7CSU z(g3L2Ke^7vegDoQ4Q+g+rR4>4a~oce)^ouQWR18Q?|k2^_gOfY4&wjTt(i}MmrUH` zOmi*nsf@9~$|`7-|8sJG^aGESv(a*8(trJNR8zJgYX zP#7Z`I%66-caEJua$=0|MEIkqTyZXyRmtyGrnX~&nQdLyw)h!3y8ql#U2_zECOyPC zx0FskXYprn#bZMg!WK7ih#x_fN&~F>pm5%IJ>J6C(9Ds9#RiY*Petm%X;|t4`av|W z1N-MkW`;RD59Iuu?ppQRjSq`-Vvu0V)05_&gi4d>;OLS@T>&I2|P_8EH zUW@ju3UPdVU0Bdl-u?_TW|o4-RG|Z}@PSlC#AvE`H%Jt3-{We-|BZ_S^iDn{%lpO) zh%NFKam!nGp=ZchTde4uRfwfNs&lj2PJb=73G;HSk#Q0&4WRvo#>Or)44PR@P_#n* zLCn7NT)AUCDJt4>jtc*F?LV~%dy~y%iXMR!S3i@prKj;-iFJLQYu9R=N!t3ObP(aO-A0K9^1L-@de&1OfZ?d9lji2wwV|HOGu+Fs$M zbg&odK{RMmMrwi>;dtMeDemLTguPsGMt*)ZAElvL@~&gF0UM$8K0=BmC&8EKbe_e5 zsh4-v3*0KwJ>q)4<`5U_$u-gY-gYSOrek1^dc(^*SR#Sp6Uu>vWbe@Mk-~ZA{T?%c zcWOGmi@z2k;H_wR{4ceu%FBYb{qU9rb1~PxNAxo3`YH==H6tt*UgbtC>f>0M3%!X- zzsM7E$9;!{(S?JvvrmT>u3lZ#W^Wkv6qlT+T%iFdiaDAEYby7TZk>7Ct3)$gjZTu` zV$sj3&v8HA;+{TzAxfB5xI`~>fj*%pWy=Koh-xFV&~$T9vtQz2)9lL%5oxxsU+5Dx zGlI=*XtNe1q5j0ZW;HfVK%qN2TfgaB%*%8fQFO|xe9LYqVp*Crt(PeKt`A~lREpeR zf)oyBb?Q?(_Uqfu3V;qdk*rWuSv-yW#&3I3-2fjEDyqz`PAK}aYKLwn@Wa{D=R&B0F24m8OueDO;4?mQ>xT+?X5fs9s4a&a^s{ZZqDE8Q3>%<(^?< z6T*ss{=~d5uWLkxrG=sDY^9K=7;4rNF)rLr^v*vSx+0noJR9z4aj>zMn*xHh*wsof zWvoT0UGRFkF%WTbfj_S68Gu6A=zo;8Cp@%NTa0Lh91 z;HSzbprKkjre30iA`2XLGK)EatE{Uka{ac%i~liHe-3>YS3fUIUqOWSa5Kw}*mRLY3Gxr1=>=KF^KmYBEfUd_~2bJA8)Cxkp+~OUS(5C6&p==2(NOxLl1H`I>mfe zm0$OgJqE)aQ)~1I*X#!li+x2<$Iu4KX`^FhJJT(J;2k_epGZSG>&@L_QrZ(%M?P`N z!@=T0;Wybl$UwmN++X!vs_OZz?5pph>ITiN_>P_CUk<0Z8X8+5Cyb3SwJRi(P&Rd(~&sd zB3nkQE2~R%a*NsAPR+FXU^<@Bw{RpXhPC%&2KVI@oZm6wRBEaFeHpd&!u}Gv!{g8< zt`$?ZMLsKE<(Ib-W|7_Gh`)}p~;tZ}KV+Vh1Fd0_!E zT?#>kZEt{}#lu*&8v>@FKz~a_uP~$OHuw9U=h*5{6o|GNICqU3t&^Nwjs8A)KbN`tzaMbzuI%f3yjawAzD#&APycFmq7O!>X9BX)V3sWeL3NUWw@djAnBuo(3ol)9 z4TXrp`U!=uM?UmuL#CPf>Ukq63d;=v(5$4IzkE4SYp$QyC3<-|MhuKU^x~`3EAN?ZEUiqwn`CmT2!d_fE>r!ReTu?j-lpjXDLk^J(ETPSD>bcl2KX5f6vd|HSqxt~ z=U9Lx85?u#%R21VbpgL~Kk}<|d9EiQG>$U4g)gX+C@=3($mmo z;&b19rN6)Y`wsy=>1hrJ!%3p&se8#}cFmcn(GN$SG4H2~wYB-g={kIHSvFQZY@BmZ z!Jg*ne7F5|5tF1)78GP2TZy^z?B7T|Ep54)=9ZurLS+s5eDTb_4|A-4jMyBnF$5Nx_v{M#*Thmsk8`r96qQA{l1ms zpYAjh==V-S9<^_=-q$$kxBRduKuX* zTGn`~ceu*K?-ctZF?EdTa3UB*HBSG?On1H`S#??*^p15y)voG5YI?B5fgBJhQl~Y` z%F}{(TLU8-kWL7H&Qv!pwr!e64YuK=Un~iSeBZn5ct44c>*1fIOPuG%WjCUK-R4Pe z=&9E$L6%@iQ&)PkaOySXW?&)b4b3zd5B>ANg_NH2$D{WH07iTto@Y;GtIe*KgL8tZ zv0q!sDOgh{3Vcv-Flp>C0QjE*bNPlq^?_%h^(I&IA7nndd86GWDjumogf)d<7X^n* zeA)W0yjA8}U(S0VeGR(~kd*9u7Y;}Ba_xKM1fPOLBRthD5KbPjS+JmLB|D!@=(~`L zB<*Oz-^K+~aa0UiM1y6pc=HTfMdap|34GR%qUD+Slx_3ur@SObQFC_r-cgy?7cA6o zSuKZaMvZ)woEnf~V>!12&OK9*RgpN@^#X8Lx3BYXje9H^4kCI^Mj!gL$H3~$HV))B z25*d5y>l05$sLj0y zw7swXzz?;4I)zC;7oM%E$hQyVmtxfKKQz3iUdsgNPCXX}6bf){ zfxLlHR=%cI?tIAD?oS3ZX6_t6{E2$v-lbZ-TG)7acq{9tWnY!d6aD;$M8^)1e0s4b z`qwnB!D5E>4Zp+J>T7UR{{GSHmz^Mq1!<}ISFkpml$f=^W@N@{*ZJh03r8l7K?fS#M4UVT&L17xoEYM2J+0 z@Y7%N@9yThD>f=rmtSRch)O+eX(7oqG~L6#CZQv)Yl{gmS$ zDYkxQ>wT#bSR(njiLmrx=w&yq#>$NiuM(SKk{=06cE5!g4 zol^st&-Na%I^C`oukJy4FO`@Uk-H3LV5`$5O{!bpcH0-#_2kHXcc@C1@i#smCuZ#{ zl*8|PwxzDy3&P!@Liu!d8TSX(_Jh0LcH5th(T$krtd@C;{&Wl6Zw~`at*ra;COrm@ zQwn~y3Psgf$x5KW8RcMQYes!NA6IF_IgPwMa-2YTx#Pg2yMiNf3=$O9 zqvKn{pG~xhVHJp!0jo=CscZ=fRvAq^|k0u*Ar*%fMq}UH}&UQCAtqM89 zV>rhleh~nZL0$INz07Fuyi2f7!=$+JR{fOkQ6YRc9*H;X2=i&Z@M<~o+a^c;4*T}6 zA=6w1|MN#)AGsCjkeJQcR*jpC3YFv(YiOOx{hsqaT=Q(t^di@#o^XrXhrMh9odauZ zDPUxqKnG3zf69LY=2Jh<{tr8D^Qn*<>CIx~6IVH{?oP7YOA!fR6F@h|X4 zxmV2XV%Gos;w8-a89D8es2kR^=z4~WG0w;d9~1f(JP;SEuV=jQo@>O^vJ90=3?FY` zwZ{=kqMMi7F~6mIfoFIA`7)3gvxgsRyfbv-DcoT-*E8PCc*0YqUP!C1F0C4Y zU^dqN!5wPcFNfp`V`5Ho;&qVc=ZUBTN!ou}+oSL;!(w%ksagO0%`417^H0+)rUA^K` zkmn=S0l`++=>6sTw)EN8S(Qrj6#8Vfc0q6NXI~wuu@cJD)yom0d9zO}=DxI!cRj#d`zZLuAGO&w znx>PlZK06TLu*@utxr{@c$OFCYpXRy-tR+NS7cQip3!PQo$fKR4zjgTKp|9GU^p&~ z8y#6fRuwi*uL@{Q3a4Q(@JlU3fIyGzwAGloeAXWr(6T_(`njzb=LzRDvbo>bc3&tf zM(nn>MK!e>x30C+E9+$ONU#C3;Na+5sq1h>r*3VeFk8-85G=CnC3BVXC04w%~{7H?36`m$jfXq14t`()N&5xD~9vC>KxT~%A3$kJu)A5MRwT}^e^zWx?ryKs_ zES^<=yO?n<_!I7F;!b0oj=*<0iDwOIJVN-ceE*8FQpkLnK zAHBCJ)8MAS#G`z%8G}b>)+HbrG<7S5lUqh}!6%9)H*(wCqh9!zP-01%N8N5TfXS}v zm>|_C(p<*gXkO?|=QO-Rkx(^dY5d^IELWJ5?=0nXxdBd+wpuGxKP93P=A5+}@D=~X zll@z1%h9O()Akj!syd*nyY6{WxGb=$E@lbFql@>DsqVwZ$*loNWC(9nV@ z#0H$36YaaJJSR#>HCJfR9chB*m))jzFJR`}zj%9pa+%)ui2U-s`b~b)MPx-3$S^?^D+KuyOO7C4Kw5pr)YhTUgG<#oI3ebsPiF)vPASC)3&onvOwm`G z%QX?D4)`hhszQgwZOd^s#;<|VGmi`5L+&711era?5og}(I?_j7g3M2@WouMNnWC8e ziJcr+Qp_61WJDScS~dTGf>Z&?5p8}AlHG9YPCy#WU4vl4tWg~VIbn31!K{&4|jcFhqhlb;}%K;Oc^(nQoliVF)205h!2ozI>AhecD@ zuY%1mb8D3c^5~i$NNW|vMei+?NVmO@dX03{>SLl;z{EoBD3Iy@$Q|7K&A*wy1Jjq- zefwfW#ap=NPu0V`}6ieZdoiAL(Elz>9gE;!Y_3~AzsE&}_2I-;r#S4aQ zD8k7Q^j1y~b9F6#BjdfKNaXkW)tAN`n{j7&whQc^v)Q0q)EFXEU{BRL9N#Rt1v{&e}jvcS^^DHPP_0! zfJCevd~b_W4!u^iEfX*iBW;H_KhqRG;h$10(u9V&r(85Z-r)HtFmdFMrmheC9k7&$ z;x_2MhO-d7YgvRWl@yt++bY)zP$y8aXP z))9E@_Q583%!~&bVrf>}?XW^GE>tzVJr5N+LPg`Do>)j(U}>3hGdeeT9Qj>#V^@&> zBL_63tl%5u-f7G&wrV-TA1YI%Qy+$F;K{K#=;1SFJ`$M3OOHUJxzEb&Chsr!W?3-J zs~6xAJF4JL&JJ4HJ7Bp8Vnp1xi=XZ{f`j_S>fg`WQnOsL zKk&%@&=tN&o*JkQO+lx^62iB)G^VGfif$H;;r`q5C%!iubDlm>b{$FU!EL%acOBEz z2lbLmV`30bo1pF>Ju~hZSHhCvSN9Q&@fQ;Q?|{G??9{oA*6C`GY|&$XX2I(1aj8qs zO;4>9WuKlA_sNU>+ur8M#oEq!`p3~H1pe|iRN&9h8rIK`6u9Tpnj%~qtVX2Hqb9h3 zB3Aw5(-~>RWQxe3wB0VW!|nY5)-~sp^iI&t3`I+h;<1VsJsQ69^xap^BszRe^_@?B zF|J-0I?fSY)l?&k0FsBGsOYj;TN19+zZrf=SL&mTk)A_5;!7n&bu`azK6!-I?%u@+ zhldKi6!RHB#G&>9{?+!7+mVq&-6l?y4vNm-L4Lo)r9cr|X}*a%39kvfSVV}*CE<-d z**2>ND;oqw;oZ#SWf$ob^tXt`a9A%*_(!~}G40*$*BJl4M}z1PsV5~7>H-c{<ivq5xJ3GShaA!r+*2+V2)Xo=^aJ2vOve(DwD0SA2?G7hS$J5ORti4q8 zZcD6qzezQNy4(09nalfmSViD4Ji?PnQ_wiiz(}So=1DecLZRC}dCFY=!8JFnw+_S& zs9L0kd~>&1ac)pPZyqel%JrRk$l7&B{RDnSiLKgzxswvfyjJ_P`fX&EF6tf%)?QIJ zD3glxJ4U$ZMxFb4j|;Wf{e}@1fqg;RXOQUHG^HPCJwmR{1RpP?U!aqkDa zNN#z)oli{hM|01XR%OnY6@9)BbYX~!YK4_}nc-UDS?MX{=RyGJeM0&BaPu+>l|Kt>;UAt+^qk2LFUMb(Jsr}>;KjwTMv5}P<`{& z)^$AGd^>dOK5#BN7Q_NpJI(UFyXZ&e+eWkMWDNn;%M=$4c~q?pq1jJ#Aj~(Fz?|=%^iHnPP_^ zN58c0RL&lTG?nUUSuSlPZxLk>G}et>{%F&H*~;eLR+L>p#r%z0P%Kov^J8DkBPVxi z60B%mmpSGky{$Xz6}`VOf6tr@XM0lMHNIa;r`yQPY`;C!UfZ%R2FbZKhh?n9%Vt$E%J!Wufgv#OQ?c(fdHSFj<%rk4LBP$tlm}joI znz8Uff4z7IhwrG()6Yez%TYXHLEzoVDVq)cd;UzjfD+;`6{SlfE`xxKc9r6eH_@VrTO-s$^t-J{)$m zR^R-f71VNO^AzM~%R)f_vWl%e_V1~Ye4Xq=&s<*YGGU6?T5tnA>-lkYEz)*3<=(n2+s5!t9QXDqoa-D1(i9K zEva%QX+*u3P);!(rN=)-j4U!N}aU&OpF#54)U+)ej%y_iy`T}-K0AtMuNf7U=+L@m=WVvo(aro|ex zSKw2cHm&xt&`G^4Fa-qfJAD>RCKUjWF%u*}HK`%AzaD}#%x4WtA^U!_V z|8oy7^)>a#57f!d!a;2ar1+crLOgO)_q9KUq}|Zh!Lv>q|Hgf^;Ms1F@93DBa1*bJ zUjFQjsE$?rWdNw7%|^GhoRj#=02MMMH$c}sMRMVT^HOU#T+|oXfBM8vSu8alR!!MN zj2EY&dR{lfI{k6Wr}6mxk=shZ5tS0^FlAFRVLoYw`Lb}Z3k7j^W^1JPgd06@%O-z3 zvhZSJaB-?|{O9?i$9?M{NASCaF$4M(x7?Ln3GlC4DzU;Q&K7_gvh@RrOTRgiY3!GZ z!&pr0w0Q47@#d!rwt-^S4}-Or(1Sd~+^O&OF8Zfisi&+T(_}j*X06m8%JUAoc!bCG z&>w)hG-vSC{V8pkLI@%F@D3D7@p`xwpLy9+QRzPvBz6xl{#G+u?^x9z%uNbh-95lApT^ZqxrfOD5&7xL@O#^reXG~=ZI5N%T+ z-WSf&el5{vEOwkySN5JSc3^rkvHw*_=RFw+-^~0~%`o*F!1#1Ku%l!{RrEC~O%1QP z=o{pSdVg1@2#h*Tnsz_>9BnoY9;pbpX1iZBu&Sq@X@`CjC4x}+D9oa)M^CSL9%WQG z)gTtK0m!OY>}b~LIe^qVI;wMqz$&poO(L}`Xg#o$VzpPWIe+2B9h$X{&Q-*MTaUX_ z|8e29GEi8TCo%#$e zWH_)I8eQ9k*M}T+Lv4Qi5PyG=rDTF^DHwTD1hc%h!w3w(r%kIEnN}tenFq{|hiY+ndYoo`k};nG zOy?#78RR0~r}X7u-p+({nY6Xh%!G=ykVdI~{K?aAWKQx(sYX7Z(Ga9>M_po5s@tdW ziB2GhngW;ZLJ44k;J`{Q;bY-7%U_n`8qI@^MfoXFKL4D7>!>~zt{Fr#(k$wiz9c4^ zOs(1?LITS=LD>jERiuzoI?$o?~@eYLcKNOVvWu{_cRK=A$UhxO*5AL*(fiR;G zoyXM?T(}RYGQ0H) z%`Xd$CtP|5q(TSv-T%FlmR*iU^N^-I&qv-b4Idae5r96w#a*2K)O%3nMJ9c!R(~O| zPkXLtX;XYzfkJyz`jm_-?ogq?*GpA-r+EZf+E3utmzcf)X8CmeCMT57X?HcxjDQlrKkECZ_u}6zl8YFBQNpwC^%f57 zCtgZjJ#B-lhF-h0F+K7Wr1Spy3NVce7!-W4Dxqi6JUh*eAIhE$(Qk#jlS8bu8vY#0 zuWH6?CI!! z)ZRd=Vg4M=qM4+4*N+(+^vY%Pc}#^AXYJU2@4rxGqPrV$^TLBr(P%-M&crJb2^+Df zsr9rQ^?!b+>K4e}J#%j_-}!XUrvobD=Q$pAzgvs1?Fjyyl{i<+SAoYZ`v1sBfB%m) z>4=FgiJ8sCCGao?Xumk;QvaWG$;hNCmrU)lmds+Qg6+m{-PS)x9D3Rl4CmM*-?b1o zs{>fd-CaW*@fxRHa~fRwa3!gFqy8dpMC71&c$6e{k=o>6du z73!VvcGf$~F8WO9nAawho{oKg9Fe(hAk5<$Sgg}|q;Ke5?xvCG1(4ast;5fAQnCNQ z4Z%51K2j3?cuoLth=Ss~ABR6blQTPe7_bs04=-r z6r%t~)JA<{RFv-4lFAUoxA_x$jI5%T;x2GD{Y1<)Qxak#Q2D6arFOMQ>7d7(()2O6 zSnPeD%2^wU16<<*@4J5!(>=y?jD2epL%eQ>QUfUcBsHYo9)*R!h5 ze8mA^U|H4apor>c2$*{eO}g~~{0rq{c!X0W%5_vjBw#6?PCczZdUGIAZ*gdfot5z3 zd@r~@m;UDy*d)sbn{$US6M;iicN z*}p`pZ1Co$TcMT}tXaEjI-T1IXa_r1hQNFA?<<+uvavHPbduXjGTu-_(a^5A|L6Zo463aUyj?hvIj=5{i=zAk*$&mWSEdc&Y>CjT;4d+#z}=*L`l zTrSQ#pzPoI)m_p33++He#xIS{hr>d$gZAa>DgAMg(Pj?}!4%fP3&<~{QRh2PY(I~x z|L@?X7u%)c9-NJP@J{EJDv4$v*Wi6e&j_||+cZ1kF-D2Sl(=SuJ`(6^*l>a9qiT|o zF0`)2_4ETlP>R=Xt~YP^h@9c!L&h`M%YUA!ut*7Hit*3HI)4-2;lw3lm#BdF>Vr(C ztP0K~!2B)zM@)%w?R<+-j=6!m(Y!(ejUMHqc3<&fK#$ocKO(iu-6#*r1x#ua|KOBS zhK>J6?6fYx_(e~@>prih#}jw4g+-8)73%2yXki9EJW#JxSAOtM4pQ^q7PkNCml1=g}jy|MSWHr!oX~ zgGWg6tLlFf7C7HoE+DG~18w#n|62>x@rMrMiNHx6>8=9SK`L^vB&^Y|c;bx06-Pr=*-PvM|g6HPp-Fn#POU zZG0wPjkn__?E2J-A~F!8Y*unYMs7WDx)&4?C>tPrPmmIgT23;of)CTOypzoqv~_$R zFG-}CD_i+w8M$TE(5XJ7?n! zuARIubJBQJb7wk!VEh+rO8x!6AlGnO(Z6EeyDq@WwMfCOAEa-l z8s@(2jEtll^=7>(Jf7w~e7|9V;xT$hTXY`nnehgAAvDnk`8~*Ju?qX84+r}BWnKCs z9;cTLdcK1XZoPnGY46`Zw3qCy+fk=K8*HZ=V3w3C+}`Q5u0P$xF1pWc`9`>}hTOop z1>WXAgIv0pNEB?gv$Z*A96z$h&3CconC2QV{089=~=`D9d0tip(PT$RA^IH@px zve68L693(eS(};H!=tgAG=0K^d=b-YMx2A_% zJQ3Xx#LwJ_$G1uKN9KT%Cu=@HlYMCaOX8m=;F3u073WY&p4j%9>hiOyv9!<_(fW1m zpv4`?Xeh4f4nJ<&4jMvFimKZ*F15oayX^ls>7@PDm7J7>-&s>l>R#+DiCtb4 zW*hlf(W4%t$qvwl1vL3)-!1e?WG*jd;#iqM;H%@xfa`p8P2eO#?cd4st~phGs{qKc znpo6_vFgSdD7slsahcdP1o1}_ojiib7ZF))U?=61TdMR4_3}l&Wbp|4ra+fv38wlE zkOtfba=D_sN*#_aBb!g@rdEYU@M<;hG?1_sm8c{O#&!ZxiuNK9Zu;k!+}AR#zo$HV zTRK_J1Q6za25r(B{i~5nxfB9r_N5;jz6C^cbp+^))K6~YJ0u0^@4qgF!X=T4FnusuO%N3w3J6B4S%Z7i!TWTjmC z@3+f^rjSLF?x~HUwD`}N4=3x==VPLbmOjjyHh)`gz8RO^=es@Rcq_`+vP)GEwMN%M zvQTm6+_jnCU~Tu;mD3hv6weu#boiza{Rp9>P{bAVPSPaKFffr%3Ru>lk%TkQA+kyh z8TPtfzT%FW9(t4{)MQ@aX;^mNDw8fkU1V2L&v|}aafbe6el_(e`7!A(9k3Ld`Q`CD z_pOrq1_!kc3BNpcD8cKb;z<#F8rA6j{Uf*9*9F@TZJ+{Bel`uJl)O7kXYoVSJfD8d+H#z4%pITWbA+b8u|_Jlb)+t-|21 zIeR+^EJjU`l&oqMeieeIy98PGaD`=SgjuFk{pKS7ymUO~);ftr*eSZ)m){_;4hiW> zXcw4j-PG$#RS{!%n;`TUKgw9%)yX|ao&^-qcy(W|o3IBUuSm>DvDdjH@g?&rCB}?Q z+?L9mK7vAeO|$E?g~6noXH=S{;S47GOmCX1U?(vMi)exZR4;#(tQZn8GiiF1L3UL6}bHIH%tP?_`{~@HZ>MgyqA?ErJ}Ax^N1v zx$`JwY65rI>g}^%kNpWZ!$%s$*jo!_9i-rStM;1eX%D@pTZ*a7!NvS;#B4l51nobE zFPz6ttBc}%iQRJ+j_S=!dS3_PZg;}H{DX9a%DVGc^W<`4Ot*Mo(?q3ZbvZ&vr* zP#*4R3CxE2-AS4?-`dwn3Z_KZZoXsB46&hp#Z4aT&o7UqzR^z%QdX~{>oh9roqN#F zDsjez%&c@8iHADBt)<~VX1uPg+hu+6S2%IPk0w?k!~J_|J*m?KmrW0*%}c&WPfjc- ztR)%CY|=95M3+fJEZ&?o0f|H~+8_qu(H`*Sr0pCB3+>$w@5ftS^kOpOZJndmLyx#d=k zA320Rj&o9n_FyHNv7$YTpndd+BGFvg+94J^LkRLY_CvajhgkQZ&FSV7Y28F3-|rZ$Al2_p}fz>2S;;c&wzy zKmzH!v%*1T9RafNYpfrP{0<-u&gJRDrD*&T=H^|$aN#CAnCB%@sb z8pv)TW3$};)A*ss*wosZLrozEbuT!{>Yv$uIfNE(enJ!&B>eqaw=KoXulz_=PvjF5 zvn?SlM=tI=+*JCTrI}ETHx#$e8MA^w9~;p-OTJ`$8W#U zf8r?5o|j6P&6aIC7?|EkWsb*woj;+NHR95qv5jFv8JlPpayEk*k3M8YpU5zm-WilhSNt~a#5&U!7)`b2vqh^~a8*0TUOF(2bu+DUC7dJ&CyJ(n&#l0Lv(h8EEDEMBnvo4ZMOy>f*AHZZQ4s!b;7cZ$332G zENw4Y(dJzy6gk8BCW&5{xLAsO!|s4=`K=pSkVm zu{m7LsXF=k#mFpldMyFX`d;?c+=}}0Nviab?7C}phddTjUj^J%6U2oQksbWfkQ3-~ ze{prngh%x2sohJC!6tKZj;|N5nystpGt;*fUyDVt-+$bB78|PFc-1U&Y~%A%r(a*k z`Y8PgS?t8cj+AqK#t)b3XEapfG-&zCo6bA;+PS$ENp)3SEDhFJy`Cbb&<<7mKkHy=XXw z_Fne8j58zIucev5ufXl++m|;si^@81jT_C|Q+RY^;Un&_wtHcgLv<#f5~fV{;pO5U z4y@iYw7ZINP06>d`0vlJ)A$Z zyii>>^w)gCtUjV!(F3|B?0Oflznf|Ej0-ky1}~qVjnvOC$+}Nrj$>#30KEA^X0J!6=Vp8R{uJ zqfE#;A-gdZM%J-}7!1ZX7>uzF!|y$e>ht}*|C{%`-}im)x#zsjx#ygF?*zM*QfMm? zuyCW4lar&V`npEWEkt<+u3X6k5wncOamj2sY?kDL%n`9czP>?Q{+TL9;KcYEzmBx2b?qJn2O|cz!&AR0VY}7d(Q3D8DjvKYMW4u50P(mpSJF?794nS&hFoxGn}M z2HBVMyV{?M1o@BZlT^A~!>s{j?0jkxty3xRA`5ltT6f$4N4o{toak=#Wv0I&7j2{0 zhtCKs9Q3pY6^vm%%?*}`25SQA(*pM|RpQnzIAw}f$A~7!G={s-zc9YwHznbZ=Dn_U zdpX&bUW1i7vF}7QH2)URfz!_iSOowel+1lw2Ispy#nn*NwM=>EOh;5-=ygf^6PxFJ9WA{rG<=!B z`_Tn!7WCbi7u%wpZzLh({13^rEUT_K?~M#LE^VY$M>pd}&BM^`d7XD#y@AW~gk@QL zir+%dl4c*5G>8whQ8*tZgM?ZK^tB|9zCNE8uArTAZ)7E4lb&s7CB5-wEmH_21nR5e z$C5+gf@vy@%C!ixLVZ=1g{bS_PwD5gY?{bUp69m4GSXh?xzE&&OqAj+t|M%h`(}M? znwRxBCYyc8#N_>SV9$>d^D?TPp)aWKT^=WVyR(=YH%C?+N(u)Bjha*EErQ6=<3n*S z8!+ANxdaCe^QE?Kk6j;))r!v{G(pj3HQdBaV1LT( zbv}4O(rUKoHkIn`8lWGrP!Us)-<*~)qy=qE)|?L-Hr)yeAc<~Qu92{lxmF&ii<^#m z&#A6K^u*~!c8j1hK`Tb^ivH+M5aXTb3kASLMR;P#G}8QLG5k-@u3~DY=L`=`6t+CR z_P#90EMTMxgi301Sl(W7#F`Ak%Pzt5iIEMBS?@ot%_He4Ym!;2a-rWaYZ&y_ec((0 zEZ;UMl6Wc-)h%&*YqTOp*V0pL>kAnl<9dyuM5T&HZ6!r+pHtmJ5qZ-Xv&4JvX#S4N zcx2G66x}j&fdUJehC^^ij`Z_aRl@>T>t4(DCiGL+@XIm{4uz1`z^j9+pv+6#7Scf% zg1pFLRNRC?0C`NG8agQFp5wlnROVVue@(qPf7g4xd16QaJ{i7Rm)u!tA}(Q{Nj}m^ z+IoE+Tf~8H{`JrP{W+h&{7 zo}&3TXN%~Yx|?(8f}EOWORkODWuIQ`+HBZp;5G$oNJ`&E!Pgn?WO|8Te$xm)ym>MJ z63`p7`A7;~fJ@r|Z5sxxPX?(i9b&9$f)=*x9waIBrM|##*6Bu*8ynXa@o?SER_;uT za-WM^bqd?vYZ0rPxM{g&_>#=F`sR1^rd3LU3NLoOShdV#wJ?#K->}{@zm4SX(KVcb z3NKmlV@Y*YqyU(zsw?HDwmCg~Eqf@#FUQ*{_sHjx5f7=%c^cqzstl>FKjz6J@-OE` zEctrr)Q@RA3XnUv04QY9GOR`qywy!h`!ki>Ivz~0fQ@#g!%UBF)hs1|sumvve3>;R z$vt4(=E4U)++1t5$i_Y1K9Hq)U8lsB6mMt|jSVDW2bYxs{K}7SFVHtgGxz{^FqNUH z6R1;CGf7uSPrSKl0dl{Je9{V5x4;&ycxrSu7e(+?w1nkl!8%*LR;A%k~>@ z8uz;>Y8fB13`|w*sj(IdLL>`D!zvCBUkmB}l%1@)x^WUavx>{ACXs;idxx9hcAKM` zs=}a^E|36-u0PY2S(L=UZ>?IK6kUD3?7X$6P@@!`b%Q}4ZC&FMU)cWY8%Vc4`F6YM zk?a<=WVInpm4Wor;z-)=kh3rc+m~Ip+=@29l&wTN}yoXmShVUg5cbhU!&wd#%gNsLbz_~f*%QGVz4R5(83e1F4e z?>*&*_Xv!ZMNJ?|v4$cm5kC}QWkSg^E9kneRDJQOt$!RO0`q*8dVnI0D9+VQZw}ni%JcT_N}R>tvsam^kP(3ZlU_3=IX`@${1neG4)srsPszn$yTzT`Q+{D zglDs>84c@=-SIg%5~zf3Ro!02-=Ke~CT`X0FN;W{2j4bHTB1={KWf9MrXdooD!LxK zRpRHf9Ls&!*V>pfCui|UZKxl-l7Y6M7p_IubKQoM#$K$VtNBxx7!s1*1<`S%c7~7_ zSdS6S^uF2mGmg~x__WAXbU_SwYrsuBXlw#qZ(tJivh)LJI%^nOn6$dB6|)^;j~$SK zh>Naf8CgWHX1xqhq2s$~Q((d>O;V;|+j}{X>K(u39H4lxe;wD)8>zSXp#QmNYa)Nf zWbq|4#Hw_lqRONwLZ7nkC+}Rl(N(j~Wlw;SA!!PX=YFb`c#AoVAgFxFsU$x+$lL8= z&_$IXfl5AmO#GopdvnnU*UHVIoY0NX4v)=bFGl=^krlSOMo!eC_{6|`Z1R!boL+NO zir_;5gOB4^<0@Sh$d+3ZTtOBK{Wd4DBsqh{-USSN0gro>1`mec z&o`80oDRI)PhmJqeFUbUy?nel+s)M&7|nEa^Q7JWaxCR36pAy2?!ON$+*Geo4yoLv zF9{9pY&s-uXcU)2){_8jn;4trsM=juQm`m-!X_{J&Im4ZLviZ67-0c{jIKi+s?<6ZY$U17z^s-cy5 zX-3r=ox5D*O+}u7UD9jt;Q+bN0lo)p=B3xLgW3@v@TKSUhX=E7PDk2H)J_p%Nik{8 zURFgN5tCt(;%+tDlbH=&MY@?cU>6Ve%fuL3m-wxn&0oCkThoU)*_v%EvOJpc2|1)9 z0z2ner=L&eUQy`uiK9%525iy@C0(9DfhI-*o!Et7JQN+UwKPM*ptlbc#zXwK#JU~d zJ=AH%`6mnkl?G2$^xvoPEh1Lwfn4of9BqsktehX^lN;JOpba!t+P@)!jBk>S<27xT zYT)?&TZsH}>g*Kj^bx#l|{nHgS{8 zjW?MWzoxo-S;PV(TNAJDSMs0sn3p4B4vqJb;;&;q!FL}Cu4)v$T15!a4h%Syrds{S zl)R0#7yk;KvFy&h(&u(1LQj=N(6A18X62^ZwB>(qfNs(mz{wmTKU{MibHOiIsfvM2 zPJHt;&Dk3*QOm`(&;MRMKjNV3U75Pr?(QGDHo4*T;~AO{$gHP00R(XW_edbcmZ&JW_^_^wOhwPG%Jw()%F>EUn? z#1<W8^r2?Ya z2i$F8 z$TyT@Hb1N<8$CJVg`OvnHLh1C#$-aB+uoDdhd=YEgPkD%M1haAHZA%MuW5kw|%w6*$LkY8P71jYw2IrefNxC6}|t- zmGs2SMejF!&?pW2rZDg~RTU+k)`?Wmn2y)A!kyIM!jst*0Ui>%yAl?=U|MAdsB}26 z|0+49ku}4cy23UsttPOL&@?(yOVi9+@Bt^YF=QWXH4sIQGlR z=B{vSj&@K!Ok!)f>Bv4V(y9DDzuu)8>v@~&+k;KG(y+&WiVsT{!5|1t#@4Iw1qZoy z?R#NLcZRN^T&(+5vBC-rdTVI82g4*UufSyxVwhiM+E%qQlpPl$RVoL=hZm;2D#;r@ zt1-xAaw$4#do1C~v1?rKluc?LuQF!>Mf(yHS-I%TpyFLO1FFkb9i$NcvQcOKO;$ix`^RUl3(9uQ;!I78 zsqB+P9<0sWDfhGHhxplHCeAf!)bu}?TIC#ciCH`qteM^M#Gt!v{EF}~t2Z~70l&3>_fP|&k530*Ig$Uc8_lUqCR`T2}*{PDnCo}$C26oox%)4oq| z|5F(y#wNV44+ncVp>mYjhlUp0gFroPaNzQbA*<@w-C6rFS9zwGA7yhZ4^IgW9Hor6 z>j5GASGvKduNyl{k7F{V{xJKzwJ_vyA^Qe8`~K5o!gu@V#GH%ZLiza~@9kV>P0E7e z1)s6W@kwjK`QYB!;GvsE+6*uAmD8WC$6P>5M%d%GA?6}SHFqeD2?;O=;w|k+rb=7J zw;?{?QW`Gcg}!26KHNU@Ao!Rt&qEa5=54PjlVUbM;k^9(Jib(egXf-kyggK_CM>Xj zDe05jzGJA?vVzo$**$1!1oE-Wx4a@-l z`*?qJ(wDcBm>oul$>3>A4~?;vQf~W2?($ljy3hUhE!{-z;zMgV*H@onp02L04*Rm) z8@USnb&HD+qC7}^Y|C%9!{cPv>HjHJ_r`R*;Llu5-0FAG*1oq>Tqg|1bjR`^+vsq( z>N@lN1IMW!^=F`__fH$8v0*Y9%7+R0KGoF3Uhp>5btzh0RMybYa5Uox8uv)?g?rDP zzXu-so~4UN39UMZHvG+#LR=Xt>HCWLeupoP*Y$Q40b$z(-`SI0SOwK_A+F2r5u){@ zG6CbzRX*ibcd9SynO6q z^2j>HV&Acor*espcXEMROsrv8%XA-4q|M^a5tt?l(yFVj~IU%MsUN0<7h z>jas@ra1eYT)Y^X8{e5V1_i$@?W@6@kz$YLaJ4J>`!>!%z{8^4>x@aOMha|HTN%3I zlvj{nzqoi;O6sCP_c(YBe8?P;ivGumkm!+d`FPrt4lWXy%xOvH%Eo_P&T3N;$a>HY zDSavvPkH|qV`e8%ISDSAFGP6!!fzRW*f-Eqh780oRxZK7VN$T&=6dES3A@{5TX zX48Quz08Ed>{oDY&TO^6O6X+BmdO=`uQUy7!X&xM;N1b?t3w#3l9$56EFYfQ+LV;Z zj|o(jJ<+8vXP*?YzP?`WL2)qie<9nBmNC;ESqB1*6P;F41Jx&r?E{SjjdG#tuZ+}I zAC-%ESX2aEoQP=!^N<-!x75MltxlSKa*r$4Y*01LCGGE-8ogG@16$4FH}DEMCe6SR zvrUH710zaNC4^f`PMD0_S0Wbk1|Vh?3mz@0il+ecH{D{kGbU0^(P&3WEq5BO#0$!g zkP4k7%M~o@7vw+tMBwORJ0`g+hX%709C3r|tFIP(wvSr?Mjapq1YRxhC7jUtt za%Vj40lSdg6bGPzmm($%#0|R_1!@ zrcF6g$avqevPHhoX)Qg5jA`mq(<_}ayr}Y^@tfMM!jaar^EtqFO2Bn#t_fe#G#ns* zaEjbyI1XKI^t3wBG%gS}|@bfU2tB+5wIe1K!R`83SN`>)LNSveBz zMIP0g=pdN-ldM6EwNNuJ#gvlNl{R-fD~CfSWbr|9Uyg|9%l#YE{u~WWIkX^-Ihegf z?r?JcL=OcqL8pWS!Drk#MwEoF5gKk113;4Kd@Po``#)F6s}VW*uyt=6`!kH9LjErC zJ83D%d-DednKTEx%keMe=Fl?gLoakrPS2#F>}Fyo`vxWPy)qnW!MC&_M#*fLQijsu zuv$H-jO~HR=vHt%AFr#SrLV%lkt|(gGz4wLV7vd^YTrCqv&$@dHg}KK z5MAHY3Y?{KZa3}aGKko`W5qb=pyDLxsCC_R76R8{vPx_~7VIKF8G3fQaq zJ|Jfh+B{>qMLwC8gVwSS^c|EFnn9){yzt2lY`D6QWGSgg+)A6CcvxO|0?GIAikL3^ z!AE3P?^df&x6En8Qa>^wAzH^b{aWF~jW+~J6mygE;06d%RZONq!NS};y5rGtREJwG zTmE!qxt3o|YR)~jyZe{=S0+KD?fgUOp}3D$0ZMqsGRMuDt$F6Rako2TGof zm6+v2*7^}3;We1ljR_fn1ArR&zBI(hlK zglXmcrAp7lEg(wiYoW`5W5-Mlj;~z4B$G8b;JneqOU=cUupP^iP$X6h6B@L2EsCvj z<#740!;279aFOPp@8Si5ic6vxs-m?Bj&K5p)_5SNfHb~p`EBZ$7wl6Den^gZy zYO1mTvb9MDs{3P#Pf-R^yALrN+4pDCfscGZ0EGuQV^@NG<;dNMcroTnY!}w6t7|mw zM~1Mud>4`z!Up}yJD1B`Nvd={Rk_(61A_x#u?E0l=>aF8kjg#OP$>AvEk~OxWam5 zc$fz&@?Go(`rghl2f(-776dzq{j*<_So{NeW&F~-2VjV}jKN-cIlHdUL#EfNF(LJ{ zp}(b>fcBWW2wcMZ)C&$_fDisoO=;lEhkBqqu+Z>q3_|bI;wKE1GS2 zk~yhs5@yw@vuQqK+pE{U`ID^wK28KG-hk&MI(Iy70OmvD_==i=k=|{G?fCvBmA6Nk zvtQtHTljB0v(;ZqPyBWT?e(7A09=yYVahoF9%pa7TPgc(Y+j0QUz|c{KZC^Y(LQ|m+cHy9fAaMMh$-mFwJbI%`X%ztl`uQ>No|*A zHeB#*5it1oeo6!G>HocfK4Yi%BvufRSozN;er?Tx#5&!uDPR(+S z+z-5@ba;16gA3m>83~Yq+S6+G&|RsW#*4rWR(uMg!ax{tpv7_nIf&AiFY4UxY(hEaAUT zHwED3+{Je!amO4PbaL@9^g* ztU%uZnDINTD02LcDT;nOcHrmZJKFnGm$-qSfEws$o?x{Pj6W0PS&$ekc3L0M2>_YT z{$%Ett}htXJ>X^g2ap}>JyJ=h9a#{%XLK$di!k$f_`5FuE}V)Sm#MBm(1tZkC+?3y z@5cZQ?yX-64(WZe+aQ>mB`j|39(_zBRC(F%mr#2!jmOoWH<%Keba`^`{~h7rU`3tS zKJ$P3-;ciJV}A4(P}Z zrvMA~I?1N_`!A-<^x2pLOo_Pp%PjBmF-h>~Prg~-2x5*ks}!1v1zv7UsMSBC#u8H8 zvp)b6#+(}f7hrVv1CrqHyw}a);c;d)_3z-puDhd(NteUUuwL3h0wAWh`!nik%ju~S z;0=#GxFPLg!;A;#|MnCJ!Lf)?6lBVG3`?RF{r-cGR4AX&#Bq-SPXWK zJY;rf&#C!Ill%P_(!YNs5dd?biJv~5+m)RIdzkM=vt?iboMjOk9V{hx*x&pY61@G^ z{TA>(euq2&8~#V}tWWQ_gRFrAJRT%7#`?2Cjayu+l|S7K%^MKFhW;*1TTGAbUgDl% zAO0T<+-v0@DZue#`fciai?iL@7jOQCF>E)3YuJBU-DAidtLyyJab(Bu*q&eZjU9dI zkUxMZNp+JA)WmbjO6V`aLMLh*<6w}CQ z)c)e`YqK7}MQ#3@g$Yb&8k2eR*&c2klxLcOnSaw*Fl2rDbs95B-Tc$1eOi5=!DQxd za0=LapiS}J)Dl1&=?2A>DOntvXLc#w@ss}E!{|%L-t4^atId5o8tT~jmBkx=_R3v5 zve#ANYGr(gKY)+fdtT--NY8QR9PO7<7VP>9Cgm*BcO1N5b2=t_irF21@vHhQ(4>jl zy|BXlDv_)sQq9o~)57pet)yd3o>Ryxd1+sl)L88Z+W{E++ip+QmD^99T z%yjN;hBx1+9(-Y&#q;pG)>CM);QU+8Z<-8CP8e!xa8})}s95BE{2BVyuB^u;U?kR72MIRNZK3a-pBQEL}1r0q!r|ub+BqMq*yhJwQT-8DE&bJD}Y> z35}ar?L={O?22_TN)xaf)EEBceCjYUMGLMvf}FUpt3vMmYPhE{!b0_q<(iuVzyA5m z^uD3>{EPGYItcEZ_y>y7l!2CY-2hZ1CE_NA|w62 zVJ34qufFZXch<*3;!fRJokgh!tF9Cw7L*V6y+%wSr*6*}dp7)e((an9v53a@^yxUX zRDJ(flNJ?uzSj*|IqdT6hXv3AnKoE1i{o~efEuMSbF0e?kfseYf%`$W6fc-Z_UxPAedB7k!` zkDl;OR@ZLfEmU;Ed2kFj=L@W8>P1EHFKfsOM)#BzaCd>l%=dSw^}XUuoP?T&S))mQd^oBg5oE#V2+B z9|S@=Chm4R^TqiPuK@?5?_5!cn>$vsa%JXpOD|Tw)x5s|l-E;e>!q1GWlnA=nJiF; zrGpVLWXyx$nrknDI$p#K&MJSuR_yfY=dJ^!)A}1GOyU?WAs_kH1A4xt!-`JTX6P-& z)4L6H*HgfI@T;jM#;=Q5rha30MEy7tl8oY~bW}Dqd3LjG`XvbGQ>L$y$q+h#;cSoO z&;QPaiPSs65Mo?d{Bb~?1HUImQSn7`@_^UkD6x`_fX3}#l9IIg2$U?LA&Y}m12H3u z$oG>%BNh5ayZwALz+EAXmflDIVS-+gey*NpK5YaZL)W`O6K<}laNuvv_^_nLXxH_! z&gaaf@n4KdhX*bsabRKbVXwGW&fdU_Ildu~@_1MeYwZMr+*;%X9rd`OdFCQ@)Ad8Q z!0B8r(N^UqaL(liaj`Rf$=)p4hoQf%X38y-w1XjppjEXC4_*xA?w|dFu#`ta`+68P zt9R94+Zv!1?N_n)#;v#0WOhHZU~)yUsJars_L%exD02T2gd3{h-z#>g8I_<+K=gRg z3RW+Ktc)xU;xE~kMeuXZZ|#?8&{Vwou>EiQ9|UL zX2d#{Rj#L9f7Os}z24(7o-~;m;WuGW7Y4D{c;(yKq9<=-iYN9-?K120h%M(*Qqcj$ z7_mrhGdoA|ViDXUcm7=T%30AoqkmS>M}3dtwI+4BP2a6$8p@5%Qd7If8Zzh13a)5J z<{Q1wI*yro57c4aJ2?{{X{(sEv*-p$owfjL0}O(ESWX-7ak|l$$*<0*6;rUY17KT ze7$gSI{)tCNjdct71fBo#uq_N#b_y8>SDKTwO_V?C)$Fm&j|J~U?Ia+DhsVYn9k9| z-d%(+K6PdE;$)bt+zIMz?#u}EVRn$ebc{|@6U@E>pedIvNad^y)`c?x5=tH?>UDdl zHa|aeXV>=wPAA!!eKXok(Q9o*c@7yh*-m9affcVNCdGwg{Av@G7xn6zdz0L$8e%$a z6XN`?I1^B$LYfemgmkSeG%$F#j;?NxP(TXQe{~Q;Dko-n4+w$+D^n^_Yu$WFh2=hF zJ(N~N&s@F6UP_rCe<~koVp#f4uX~2buYz`3Pd_{k+FR%OPi;4yQN! zpklV?BgzX?>btUNigH+*_M7!YWeEJ-gW#bkqk^kBZL>5vbxw_kOZvt&NjFzR$3IB) z0_!j)(-t?rHN|G3TMgw*PUb)PBv|O-ZG)=6zRUNIX}G^Clv|iI+;UtjqAP2S`0);& zOnu_jW17unq$z7As4jC)Qi1j(pwY~{#VVsWO8KJx^BDwTk)j7_lgXjB89gwDNUs(i zLGZ`zqr1^gh*h>$Cq;0l#3bb_QlFm$yA4g3=GxFD8OE3z!TN7nEKpf7?JG$Fd_#Ez zUM&GkY}L44VbQDnhJhIc1uh0{+ z+Fghe<${ajujGrLToDpKtZTVua6Pk<@+9N3R?cK60%cp))WgZ(aJj z!3oA)s~kFp+KlEKv)zjpEALOQ=>KC%^DABQT_5ygA+Gstb)I(274P)CJ2F;OG0Bd^ z>ALK))M%Wu%3wVDVnY+ca!KPM&)YO4e@45?po00_B*J(~Azf}T`|B#9q$;1k;3x}x ze%JxY0NBU6*ebyoYc~?XnCA7O$YgV6q52A~PCKg8I$%vhp&)Cx+NnXctC4iKa&z|P z^+V*RlgJcPbTDi3bL<}=xCxXwQKp1L^PNfYuDz*=FRUkH>RXLmGCx{U0i?zRn1ghYt=PHA^H!rj9`6nr z8@sNOBQRY}ig3Ti8AiQxrN8xwVjv5;a1n=rnpS^jk{kcT%yu+xKILC4$K;fGmg~GB z&We>uM7SS*9_b))2pW~F2QQX_?2XQby>d}=Ef9?9T1%n#T;|G;=!qq5;abk%|ZGa7`HnXpY>kn?glbZvd8S)-^l!<2Yxz) zRf^JcRo*{S4T@NlPqKl0V2R;;okvN@Gk@IJ={$*cnzFwUd-wMgr`wJ>0;%hUbwO$N z|MpmvI*1k=wfe40_}Cfw#RK$Qvi5=ow7+z3>aw8)EHwvIiO1SgP!m|8EMwEx>Qy0t z>gIMpXWoaozj?O2?A#J|0^y~Ut0!+!nuiZVee3dXTA7XHbHzQ%5M{((Ru%<6T{nD` z#sl_~X3f@uHPx7z55}Fw+`CIJ)be~MUEklYDgpZ5eZ;ZD#6iO|M}R^$ymF0eZU5b; zesJ&m;Mi4tV_7iF zMDoVG%V?oe1R*e+NU1GWP=J1MmE%ghYmWdMCD&)($); zlSGb2FavnKnR|OLNz~U8*KXJo91&wh?51F!-oJ_l=anO?PSt=_OYnF5|Elr7~ zl>SSI8%i*Dx$#OfYH^y{JDF8isi%71lQHx3_5aEJE+jyM4K}}2RuVB^t6N44BZKnB zL#B9YjU^AsM(J@z>P0T_4YpB!uL^G+g*QF;q9Fg7i#JRq?jHAIdvN7@p zMgmjDTaOUZ%ahW~%h!il4fAUGi? zPgbA{;ZiwpcOMKD^|`F4kSEhM_pZ!y@e5cF*@&6mP6y2y@DxGR_l=8%K<1;szanyt z&f3PPlV&TvJ@_EP{!;8NGr!F;C9A$^k2gyWP1?w>Kd9j~(r7L;TtZoJTBEszoJVNf zIb59vsyYv|RTI5OjXlrmT(wOfeBvwxn}OIIH|IR`3cCU_7#2g9p^uRUdT;`?_E14Eu&Mx|GQ(A$~_j`7{b00d!u=A5yi5=Xa$zf$Yov*nT zViuJwOGt>)ZzU(VXdA>%clYEM+}`Sp1Xrzum6r0NCPBQdz!I;b#6G__{}aLXUHuq4 zS)YHTYOqH}!(GK9JQr2ZoeruT4p6X3sCikMk*6(VT9xKydc@3$23-xW^P_sZ8N}*5-=K=&f3sL(ezFW=Q4jew}2JM-piy6PeH#m(GbbJi`{vxpdRU7mxU zxQ;(<#wA&d&nws!Ilr8TOIm!>O|O*T?C zc7S;LUS-yVbhDOI1z(>PUac4h(IKghRN>MCATSeqXBH=gj3 z$I-RQV`en^FNek>waj`ZmTS?_Gnk8yJk_fZr?roAcPM5CHU7iZ=F*V;C#y`^6UD8K z3HCa-B@a%vUcQ1aG8davcDAe0J7aNZDiT#<++<`eAIhj59U&wWQydyhvIJ;aAqm*N zn6^jUh)L^Oyga|@u@G`hRqP>#AFOUWG z0kSU|pST*l<(nCwC1|ymnG>t}Jy#<3d~6Z+`U5RPAFj&yR1{Vl8>SJz8CikeP-y|4 z*V;Z!TIclrt>$N~`*qv{W3x>6Di+Ejc)CN~;8=R?w=SHN?v-4!OH-hiy_X!) zCG~?ucVy0kAeAdv6;15q>(_ar{ClZbWgn3|Z&+czH>^((BfOyuZxxH2FL3Ba%9F@* z)@3Zl2v zQt$C}yTtF90)`S@L=47ww(E@thZJkAl*kUPI&`3)jbIE*9pHqq*WJAg4t$xU*u>?5 zAX;6bB2h`CRVAoJZ>;box?sL(wDiv{iVs3y#wG@OrYcWs&->RLWZ+bZB>>%|(dB>) zjgo)jahK;P3Al zwPR_6I3=t%=EsG%5Nu(d6Xec5I>g5g>8xEZC$60KM0nss7eXrXwRj!S{mTclV0@`7 z5tdSX4CkloJtBjp2JYKqn`eB>c2k9ItWe{nWHpc$OK`Va_>u!b#RsAWxs|8MD)sE_*z`{fO>F6Zt0J;AQCFs{FBm z|2-=joz6cYYqB@2nm<sWPWg}`-mOtqDF;w z_BYqmWx&~Lz7bgdJtm+;G$b~No6(VPv)e;{I2TZcIW&s-kd7*`;)hwDow=cDD3w2Lcuh>)bLv2o-`(@=`#u3z>U^{IJnvTU%rIAp^=m#W5r?=^kd zYZMKKJnsto0x3z9S+$ZmI3)+Rs?qa?%=J0hsn8+n(-k7j%D0%8wq-o|)~{vVZO;|; z3}M!_6Nw4$sS7ys3Bb>92IsZdcM{4%`@Iy6S z?e$$%DDYtD<69aQY-M_+r3EE-y|+igJiZX|xr+9-ksyVm`fH}Mdci|~zgx2Wk00S- zMLuGwWyXlemEO|Tgh|K%l_A`M;(-j4d%Ng=AMK2szcagOXlhB4?E3cruZi<>HTV1L zMm#_ox#L$pNHow&M(Dj?E(qE@f<(s;xfse+_Gq6!I1U8Sk~&h#5-H2?XGPs3VYy+%NdrkY2gNQ1&LwNvzu-&XS$Rb>|QN2#Wi z{>W46d9ZFArbAKq;$lN08>V3NQQPQ~Rv+up%>n<=HRlMu6CsYx9aWW!Nv3&5YOth3 zM;SE$r&F;t`b*let75RRPK5#8Z-3741%*AjPpBqrij?=7-NAmUcLZFM}C zHF}J>Fwh;!;&4r$Ut1!s>oTisndcQ~!GdJ;MeW9_%WXb@!Qwj##jIqdDJnx7!f& zFRW@%*8r4duNM=s~05u<3T?1 zNrOI*^8s91S-q0C>tO)#kyRvyG2z4|)(1rVT#p%970C_l^A}h`PcVK+_L)nx2xpmG+RPqqIz+Q%6IH1+zV3^<4 zoHI|M_ZnKQht9~FP(*+D%_aj25GWOm3CbWoiuFm>z?b`fG_zNk8mjX0{cFDAW{n`p z;eOO;)4~MOHhBE+!(26`{xR3v&Yu(Jm1uYL=LV|K>!I>)r8z85pkU+-wcN#;o1 zqRrH1s5lcL4tA}GWo(cB36V|Y`aZq^5oT>%gr?i^QrpUoMB>Aa)K!hb#ZCn5`J_!Z zokAJ1$X#@w)EA z_&Tb}6K*bz49@pD)h*3L8<8@5!Ky>kKn}j#ecne9#Xao`dMlV}%h8?1RN~#yQw-=A zlAl8TFpbv_R*m%@y$!pHtIycFxkOW&Et!k{!x?Vv`80qTuUx6xkMXpcjy@hx{v+~B zYstn?S0`Z{274 zs^JJmvZE=Zf(fCt(is; zB2ZE9t@6H+72dg3uvop!Zfdq5Q29`EAwKza+0e@-!)Y7&)hFKbrkb;{Td}d%amw>4 zNki^`?$XW{*6`TN8Vf{Cs()1dDYJ@J2Z73?apQO9_4|fE_+>W68PB3?N;N<%@O_eX zjuU1`Nw&gz=!x@vFyLDUC`Js$rrQ0#_P+g}=|22l-4%Dale+_Qs#J<1Ip(l+ARQc1 zPC0Bv$Z1ves3as-gb+&(!)&V@R!(zh<}^!-VYacE*=Bs!UHAR@{sG?~Kl|ym z$77Gz%x5>G6=VTxN}(53_b<^*~Lcmm=!5l(-1HrP{oimFrMwM()#?|Doxg?UP> zcC|WZ^Gyarei@9Qkg{ltAB85QZZ=KfY;hWL}3O4h}@ zyf5ua{_u_<`?K0f%RV`)$3m|NV%2>GlF&~QnmThqb4l+5;?ec+(nD63}Ts_>=cH|QrYpyuG&VB_e4N5IAEDu<4-dXPFG!M|ad^W2Dp?4ny zc+>8J41sz}LZLM2H#c<82<@IP&0e*$(hQC{NCMoDcrvDV9Wu+0W5Ze*lvN;KUXG1+ z(+R2%B<@Bp*$4XW6OxxoXMY!3u;K7cK;`Wu*YP5YAjs;=vorQ5jD(j@0ARUAPLE@p zgMD&sAt4MHpR?n2b|}oj@f9q5=*?-qKC>|=xXR@{)mEv)-pI82nA?M<4yM^N+jie) zIcI)8knl(Ias%+H)2YPl(!GVc!D#7 z&!|2hlRNj3kYuvV>~M0XNs0rm2!`2QsGQ8WrK>$%Q~!HjP{JS8$$kB>EgX047wL8? zsSmD|I;Vi_30|SJHP6}z7;HL8a`hEWS zMr23F%7d^uZsm#5djMjH2qs}ahhBUmku@3cgC3*g1_480MFDf6hWuM~>Obupq~FL` zbHvP7wJG#>Z$tV+QEezSXcQM2HyS-{29f+vLsFFNcML3CsNZI3vx8|;V9R`~PYgks zQjZjL*S9f38TA&Qar%OoV7CTxot*V`fFd)Rt~L6~xO60W_P#EmS~OrT}3-~&COV*hq_u6ER8`_$X#~!(?S{$j-08 zM_zGo&Uj+l_3;-si+NGdE@d6Q`pSY-`*>nAQsD1fI?5kzVI#eYc$a*)Asgy|Y^?6t zBJ=0T(;J#w0u0>LOG^4~ngAreHbD>FX);a9;)Qmnrgbtp9m5ClzB7T3gD1W6Qhk)Q zbH6k#IO?hE`Wk!fh%w*V(ZQwZDcRr9N826w9O8s@G3qfKs@qH%Nz6*LO$6ptYn}3z>i13Wy+K8ftuUnYnVGVjX&Xl}+s8zuUDEtI z0ARL05Nzjrt{!stbWdf>C>|jkH;tQhj2M+Tgv0hmt{!eFNo{R8KBvnfJ${_$vU=SR}U8>+-H0luIbOc1*=- z-4ynz)0?7|1(QzBsYM|cGgm|6B}B1YU;HFEyVML+bYy(Cj@f+I+JBh9x6XM#65~^u zy?)F*1*78UQ@z?hd*LmwA7`H_6$iL*QG8+YWNpAZaxcQtq)Oi{049%Vr)Oylp8=od_9xqo`tib$WZK~dwF_dA%|w>ueL zIehWsX?|;jETk&U$83?4NaursGM!cIZ$ru+>bN*Ndq4U_ zajwX{Q_Xv2+`(toKVodnhnkg?mXxpQa(1j#@r3?vsmfc7i$-Tcm=d-&JDb+BOwFRv z0PC7zSyrXF(u9PEIp+|c@ipQdx?{U(hZJ_@{H=hn<558Rp%N*@Z6euE}3(U7F=GBk7~gO`UCI zwM1pFcL3xEQhmJige3f}15n2Uu#s<-nckUw`0BrDB|)R(abEP~Pganp^^t~B=_wC( zBxGGVOiXP!*5J5N;+3}|O+z{9lbt@B5)qpX@|-^7#L3y5{YAW4(%a4EotGqR)SFrb zXMIBJV%=T?ZT7}J$1AD1Xu1^>f=fhMw?~-39a)T-(qkwOc!h+fK=d-w)JMc~&zka) z5p=X|@n4gzNT` zsM^d%ROR7krW%OJ|8}YV8+Z+5R8APJ96i~H5BU!3$@7TI-Qhrz)oZ1HSOX&8F5QVxjkrDW*_SIp;i!9CO zJCrN?IXgkmC8e&SB$1Gw?LHwq zdmrE)Q6W&!NSYq*`?HUp^2J1TfV50E)9#3>zlnF}380vJGoj>->aJC00YBRSme7C4 zGW##r`ZhhT3yJ&ecJFIqF=$820<6WKXm921Mc-?9ZXyO}B$Gg}-C20_NNaanTcx^d z$2)e!=3(QCcMu(&w&Dtxaw8&ThYr=l?O|Djdt+C)F3vS6+=szT%1d>BHS|Rw(7La~ zHr`!{iqcAgN*Eo=+dXu$-Q7Uxo#ZN~(M#un*K8vOj%(UIBsI+WNR9YMl}lk|!52{= zoTC%Z!Ff)p^WOcGcSlKLl3Fo(exXr>O$z3znw)Es;>jKT`+iHIW>UA&F zweC3(8~Vf&MXso?lT9KbPvq5mA8$V&8Ad3+ND4PKGTZKFmtQr7Dq*0`l2dh#QbR0#^L2~gLSp3s7d!C}0r9@=K zv%~l(-R70BI$}?;PY2rtxN%;g-&B~$+kALFD4?)5KBGA*^=_ zD^ISh1+(oC`@gV63HUd0Gr_Q2)3a5L@uY`9 zv@+T0u~)^ZF*K-fvqgQp>!xSw0K|*zQQ*)yI0i0=wzi6Y=&#CfKa#=j@8_L3eEo&Y zB_tk^HF`Y?-1)(D$>P(zoI3DJu>Q#OQV+D0sWT~l7&?c{BFt==TzHdA>Vwjvt=u2d z_m0G<@J-h3wOhQu2T)Mz3b{)m2sUJNkL@b*v{p{@7O#*x#}0Z?LWf6&Z3BJWkw=l8 zGNb$@xu%Jj5b`9As-;fzRk9%v8v7;92igfD=pUHyGiSLsf>>K*t%xth<0;`-w!86kb#8jC>YkqxQtai z*OOWnMOCY!9qWZCuM;+U&Slpzt9#M?WBHtmUz2(iO5{_J+YqhdC9r2mG{rO|B%aSr zi-^5d(_Sx?V|jN1Vq_Gyli4E83>cGnUjT?N&~_jj4JxmLbz$NH4JnxLs68sFy?X5q z=`W47-f6shDCY)VnNJKGZd_bxORdE zH{rl~Qh@AYJ{H>yls{H=#Nf>IPkZ*4l+8I32nb62GwMztuwHYuYt)t!P@PRoz;$eZh};qfazqfmNj7 zlu`)m2S}>$Z#dh{MRTXnIvuRX^y0;CJ=axh-6i5QlsfT90*>Ak_}C5#tlB zTzbn=IgPgPW1?tG95t zL7=CXy)ZC&bu*IY*;fy+!omCE@YCDUo(})Fe1&Pfv zV}%t{GB&99LUWgq?7d!|j^y?~H)(5Eq|JmLVU6J^l{mJ7sLq2${`k>XX*p!FW5i@o z-j~|rnOLf_>+Bol(X?`|IS58?@pfE18{I%w(gc*>tLT`O8H^XS=uR>EHb$*ffj!&~ z{UM~Nm=LNnj-7CELg6etud(OqN3XxQw9S)FXZVQRbP`E7f%=oH%`mR1ea1H)PY8q6 zH1^>rN;-SARvF(FTmRe=D*Ju8O>tWOrFvA;Rmli=ThSzA?`eTO67dX;X|By-9i@EP z*+&|h-V56nJu*|(O!^b*4LY(t*ton$;~WIdNE$m^Sh2Vqvy*bUZ55!^vLMR;W^T9Z zKC8cC`5WZihQIpWPVic$xj|_Bd2&rQN8gL;MjdN{a-Q$k=GqYF~!OSKJC6YM5)RnALlA zQ)N`~4BR_VKRHo8$oi&|6H$B8=)0@sHAT-E`kwdO3)EH7@$t^qcvzKhfVvj<&n=B9l%nVWBFj^1Q&QBht&w^n7t;d2>Z5q2iC7-Y*G0Rj3N_ zGVN99`cuYIA?jp!R7iF(!br}lSFQZp9-zy@3+rf)wA&zys@b}~2Qajb?16o65i8TD zM0cMae@LZJIyE=Eh16pXWMm%D;acA7W>$`cpW4uic7{xDKOB-3@7#7?DNnCwB~_u3 zV)YJJTWb)ZO1dQ1ch9|YNpw=2LCyqN+rGzFcft9rDSlEKM^AlDy@V%DYnFu1#f!@y z6Jz^-@?6gAC_5#AFmCF~SZDunWx2RxmXMuTk|PB}Y~`D`dBBF;zR-#YpQn}e;WNiP zGe8_Kxgs|}yD2_@*9f&}Qi&qdynuBppW5la#}#r!e_Me5RXRLzZlnhDT3 z71}E>|6qLVn@(tg!0xl+$G{D6rzobwdF8AUEAX_d&BBehiDBYopK~e`oJ2T3%qi$@ zb|YiCju*inWU3qoC&}1S% z-FsEgNk1VY$6RQuT}y)x}9`zcVv5g*S7{k*Fh*V8EP zwP$r;*V3KdjBgnbDd@WKBBAZ)bn(WX7xewB4YOIQx~1f{kdY(0E)v2{&=K_10A=uU zlhY=8jYHUF1ALw8bUij4<8pgkwlh^I-c8nA0W7G1&3X=<1WM$2BACf8PST#+ytiq= zqXGYfa1J z(f6ySyv>Dam6c;wf>_pRgq~+zwoflL{`9B*?_o-ZBnTjAq)(l!J)iSvJ)~nkrcAbk zY&7cmEm=_$5m~m9SUkp-=_)ccZ6arUGha_0VoS3nRqt6qL580$sJ=x&a*q+q|7~p; zVUwC*QII!%7VwGlCZ$Ky>S?f1yHUm{H=MaJQe>QDa(>_XADS8C#8dHbSxHj1)47AT z=nFk6J!p>v>hhU(<0zRoMNCUxOf7M;ZK=AvDJKKkbXASp_+G3s86u%b`(m;+6ltRL z66{bhn^C`;aP*$f{q0s>pt~$R94rEf}5`3hS*0-Te`)yTr%WxUR$q9&(9QUGU9D6#p z$KC}f|E%%!;k6YKgN!w_CX;=>HMnm1B2kcTy96cdW{+g=Ml8nc*wNLKjTZY);_I6F zt4wwHJfG4}fL3d+47!NY>L=#2#%*;wz2yl;h@K4;~W?hM!I?Q>uPCnMUZh5fB zxnS3Awuc%JaX_7Ja(-`lA??Q#V&^eayVW;#i2LLVDs-7O@*ufWN_Ys(kSWbDrM)F^ z-ye%_BwDGYz8R~69f)=?D~KU>icqE`?C80Z43Q)H=(s;5#9(jvF9|3vWz!!l&^@>$=x;m3^g z?Ee8Ty)irC!d8~U;;w04LxmVgXaTYF=D6+W%XYmcz7~YNlCI2(kVJ(1{!=4%BL9jZ zdw81z4fBMX@+h{>2|;uMJ?j`UpY6`u8(o)YRg=;TG`H_g;jGR#!-)@?LXcG%;Mf8a zb&08vJ#!MHrZ zuj-{_3RKwWLW6?$3itbKpUy}4vE)@r1f0``=$T?w!$sRVt(KLq2Y8^?ce?5{wWWX1 zD=+iLL$a=i#6C*XYp4j}(G-C?*{ORhJ}nfr6sh!_tqWQ6z7N{?y)os3L3xR8co(zD zWS6{6sl4|M>uGfYyipcIGqJ1-yU!wfgK5=-^M5!jsED?sM=5=1s{}QIp9GIi8=BO= zt)S449gS-8ZjlSbgqrW`O84Kgh3T!Zmvf;}T&`vIGs9p@66dhfb3{qj^SZ7(trm^R z&QASitahbVuni4d`L-p-LcZ2oVe#ULUU2uR6`y#;2XSd+PCz4*m)32rCJfz*PaU%> z#4HLK21_)twA{$fACt@^PIUV!AuzINtTgex`0QxfH4B*ZXVj=$_99r~^F-Jm^DoEP zZ7aL)ORc1;n;!-(F-6fO94Z!EZ(|ksa zpF7b>ym1q{(@D@4QY!1=tBIJ<)6sxsOIeUbMdZ$Dy7Lin4`c=|QdymWtU`s!_|>QZ zr(E~LR7=DR*5faL4dY#`#urZv`!x;Q644O8O*{gn3cha{DIDho#>DxC9CUd`q=0N{ zDz-3FxO?}W8spD*+l{O)c4t8+R*S(RGeN4)H_CSP+Vu3T;V$jutBwOp8F0ka(X=Rf zuJ+Ipudm}eV?O~na%FWM;5E!UJ`}seaoYNLZBSahnM7J4;il%{U7@+oqm}gv@#_|& zP5^@%Lr0s_nWk#tD|!kfTH6q9I3SAYieAI zOXNUJn(>U5Y6!cOnE@_MO0H-WEfuma&J@bF&_j;?xcFv0Pf^1Hcg_M}azWqWBWPTg zWmaQ={A00!YC;fM`?CeUBk_$sE8z*-J*-H4rw~3&NqxxZnTq_ikl^4_Y@*m>or?Nw zQPl17zeT}@{)YgSMg}uW2d}bB>lx8qHEmyly`dn{)vV$zF0IC zJAJpk`Bm0*8a%RUXPNOPSOq9}yy`45^=nfY1Xc6-RZNs8DQa)NKl4&-eNBEQK2=F* z`}vVX@!&6Kn`>Y5NM*Yo~DWAUMzuvCR3K2l_U(+pTea=>eDLIdWPss9a>S|Tok(S zCqgSh+0XL+c#cP$Ds+T3HX1eSluy?gALR!ZJL^BH%E$mwdTLVJTNns^W38gy9Ezu& z7oujAHy+v`F2_a;_p#9L#F^2Xb;y zf4?Q$PVLGA&|*M;YIz=ibq{trEv?{k=>^HgQ3varK1O)kSr{N+e=b&(ziQlc3x(Km zhW^(ETHNiJKKFO^O;_&GuXOZFz*qfX1gEA|u)HaH^D1$`et^1G47MeI3{?tIho}^{ zq+t!*H;wBV31gK?h*ez~DxI-8gQptPnQsE9_Wwkg{5|VFU~WeG9H=}fIXkI_kDH!w zGJS1(^WjCOvEf*oZuiOnXHq4+beDyFP~@4}fVoB3dy?rH;-i*wV%|iIo`EZyorRso z$LlpDP1X1B&$t;d5pyXHpt^qX@bWlejXknqI#M1+n0zPuVIb(S@09Pr*sK>u3mdD~ zbHo6-S|n~+Nr%?fWfRUqR8^I!zX*rEc^<1BGcSIC31>Uzqfg_@=9%DY(Q3E_G5cVTeF9+P znkW-l68~N@x|rP__jK$p(RFQARke!!zfqPAT*pFk$yZDW#EDXkzKq&!;pF`aEo=%$ zwe{AmG?P5}y?wpX>eC;*-%?fnu9AzO0ru@tS1u^sYuw(I6BeyTL9b<~%k-9BKNuP^ zDuT}t5gghtY_k-k&TH}R?UxOM8)14k+;Gm3sRi_%W#`U>M6p~9oE;)`u`}3so*?a_ zcbPALjwjU&leMa&6!N7~Mjnng7m5^{qGo}nZI4>?qkojB6(q|LH7w~X8>X*tu=a@O zKcyxm^{vv_z*6@tan`D{u1!KDq_^*!E==OZ+|&wS>!5uKPZhj%&Sak#UxicO=UW__ zorubPa>bF1H}g5-@6{vZnsfBR9SEeq0}MVQOHl>_P|n_Y!f zsv`HV=_SvFed4|p#!7l@uSkHR?WcbU?OXdHyBEi9K=m4K_`%j1eo$DX3ZJHR&~O#* z$TqrWF})1P4|!V=pZoOrz-8u+ghIi*a@OpbPh1D|y`#XR<-}5nV)(2rVAixHS=fPJ zuiPMi@7}Z(wQhA%*&%`(!VBO&eNLL{#+169hs~yb%1d4BW;#7k=uWyEe-lK_aJvP1 zI5bc_y!_yc(9!Bvl`vKUqrOt_d5^v}gP~sdwdXDG9W>4KuAx*|Ed_|jkC+#~@agIN^^^wN-5oJkp7N@iucRaPnen&T0|Fb_K1#K(?fq&+ z65z$c`12mJ>x3I0RE@2Pkq8S5s)zmWH!FImYsjvcQhR^y=zjHG>2f-G{nxUYnI474tg&3yeB7wx7yj8QWqyUaLR{0b{4L6<6OCVESG>}4TQp|< zC;O~+Z@-f<-23gV-WJe1Epua*pzZIM;A4A#Q!g41ex7esx~h<=?iTvw5`av(=oyW z+X}!E{+a`2rx)3AD+B+YagSx$@;v5+Rx04|86d6v9#JX0^x`}dFT5_4UK1j>5Op6* ziT}2`zsi;jO`G|Dl-#&~=oma=B(CSkie+0_J-~Nk_O&>;cHCqU&;~Ce#b=?yW2XMj zW(88$xQl*3frAiBUA3OcW&{7#77eG{;f&WC`+GO)>Yxg4b!YF$Axq@I7Ei=qibPm# zn0DD;-nZ7KzD8f11dGxZGuFl-CDv%x{@)t>*iN=zq+pXdJ|QdT)s$*?al;f?FfG?_ zj9a6$lH^~4t0Cea)`ES%@nqlH%_0Ga2j`b|-ksX3)dLhR84sw((eoZuNR;oOgeK;q zJuU~gQh|Sr5PwPj^qU$rM3PsQUcG@BB;5Mq<-wI?_9N!&BdX-q2@SclQsmUd`wM*< zm)81e$8GR&#^R1d!i)iXkCgyr2o=n#maXNN#lgRdlWo=Ul?&|HQZRU+tIH-Pxe&6Q zgoqqesIgy|GJ^{An;)aevB*Or@49*2>-dc@sAqH4dshh z;?e{`QO|j4H;I{5J?y#m<|_+k@YZ@e9G(d6jv4q~L&+h8pJLt!h^;q^86RRIvH33o z1c97k*71=CM5v#K%kx$Eg1_fy_FTl~IJx){NcLY>f;5rMvCk08Ux@utt8BReFhZ#R zN?`a;Z0+fq=OtW)DixxR=u9!>%cH3KO@SgRYd3x%`H24f5A<{Vo`FZaNb2XaZ&4yd z-_;nJLddK;4id);-U<&QEzOQLMv3g)JUpg@QAYR?*R4Oy*gddfnMxp zV49&%`g8a{f6YxsXMT-M^K>*T8#ML>GYCg*W9M|<QC4ygthK&=USnsHG=m>d&vq)kH28=U*nFA@Qf4d~5HvG5R z|KIAyKj5ODKRx%~>hpgjrH}r1^&cVVp8s9_+gT@d@;{#UlLvpd#!4mZ|8HgUZ_lID z`TthA|MrPWJ^62S{J+QgzXy~^ Date: Thu, 22 Feb 2024 00:58:36 +0700 Subject: [PATCH 008/647] fix: 32978 --- src/libs/SidebarUtils.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 49436576295c..9ac5227399f8 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -292,27 +292,35 @@ function getOptionData({ const isThreadMessage = ReportUtils.isThread(report) && reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && reportAction?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; + // Currently the back-end is not returning the `lastActionName` so I use this to mock, + // ideally it should be returned from the back-end in the `report`, like the `lastMessageText` and `lastMessageHtml` + if (report.lastMessageHtml && report.lastMessageHtml.includes('invited ]*><\/mention-user>/g)?.length ?? []; const verb = - lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM + lastActionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || lastActionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM ? Localize.translate(preferredLocale, 'workspace.invite.invited') : Localize.translate(preferredLocale, 'workspace.invite.removed'); - const users = Localize.translate(preferredLocale, targetAccountIDs.length > 1 ? 'workspace.invite.users' : 'workspace.invite.user'); - result.alternateText = `${lastActorDisplayName} ${verb} ${targetAccountIDs.length} ${users}`.trim(); + const users = Localize.translate(preferredLocale, targetAccountIDsLength > 1 ? 'workspace.invite.users' : 'workspace.invite.user'); + result.alternateText = `${lastActorDisplayName} ${verb} ${targetAccountIDsLength} ${users}`.trim(); const roomName = lastAction?.originalMessage?.roomName ?? ''; if (roomName) { From 9467b3a7f068b071f380ae36a3e39aeeb607b1e1 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Wed, 6 Mar 2024 13:35:59 +0530 Subject: [PATCH 009/647] feat: Receipt Audit Feature / Note type violations. Signed-off-by: Krishna Gupta --- src/components/ReceiptAudit.tsx | 33 +++++++++++++++++++ .../ReportActionItem/MoneyRequestView.tsx | 4 +++ src/styles/index.ts | 13 ++++++++ 3 files changed, 50 insertions(+) create mode 100644 src/components/ReceiptAudit.tsx diff --git a/src/components/ReceiptAudit.tsx b/src/components/ReceiptAudit.tsx new file mode 100644 index 000000000000..af8c063668e1 --- /dev/null +++ b/src/components/ReceiptAudit.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import {View} from 'react-native'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Icon from './Icon'; +import * as Expensicons from './Icon/Expensicons'; +import Text from './Text'; + +export default function ReceiptAudit({notes}: {notes: string[]}) { + const styles = useThemeStyles(); + const theme = useTheme(); + + return ( + + + + 0 ? Expensicons.Receipt : Expensicons.Checkmark} + fill={theme.white} + /> + + {notes.length > 0 ? `Receipt Audit : ${notes.length} Issue(s) Found` : 'Receipt Verified : No issues Found'} + + + + + {/* // If notes is a array of strings, map through it & show notes. */} + {notes.length > 0 && notes.map((message) => {message})} + + ); +} diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 0bd18d8ee7ea..234aa4465d40 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -6,6 +6,7 @@ import ConfirmedRoute from '@components/ConfirmedRoute'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import ReceiptAudit from '@components/ReceiptAudit'; import ReceiptEmptyState from '@components/ReceiptEmptyState'; import SpacerView from '@components/SpacerView'; import Switch from '@components/Switch'; @@ -285,6 +286,9 @@ function MoneyRequestView({ } /> )} + + + {canUseViolations && } borderWidth: 1, }, + receiptAuditTitleContainer: { + flexDirection: 'row', + gap: 4, + padding: 4, + paddingHorizontal: 8, + height: variables.inputHeightSmall, + borderRadius: variables.componentBorderRadiusSmall, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: theme.border, + }, + mapViewContainer: { ...flex.flex1, minHeight: 300, From 8c400c3fae12d28f95ded76a52806e92ec54f740 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Wed, 6 Mar 2024 14:57:49 +0530 Subject: [PATCH 010/647] added translations. Signed-off-by: Krishna Gupta --- src/components/ReceiptAudit.tsx | 8 +++++--- src/components/ReportActionItem/MoneyRequestView.tsx | 5 ++++- src/languages/en.ts | 4 ++++ src/languages/es.ts | 4 ++++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/components/ReceiptAudit.tsx b/src/components/ReceiptAudit.tsx index af8c063668e1..7f11e9b9a1e4 100644 --- a/src/components/ReceiptAudit.tsx +++ b/src/components/ReceiptAudit.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {View} from 'react-native'; +import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Icon from './Icon'; @@ -9,7 +10,9 @@ import Text from './Text'; export default function ReceiptAudit({notes}: {notes: string[]}) { const styles = useThemeStyles(); const theme = useTheme(); + const {translate} = useLocalize(); + const issuesFoundText = notes.length > 0 ? translate('iou.receiptIssuesFound', notes.length) : translate('iou.receiptNoIssuesFound'); return ( @@ -20,10 +23,9 @@ export default function ReceiptAudit({notes}: {notes: string[]}) { src={notes.length > 0 ? Expensicons.Receipt : Expensicons.Checkmark} fill={theme.white} /> - - {notes.length > 0 ? `Receipt Audit : ${notes.length} Issue(s) Found` : 'Receipt Verified : No issues Found'} - + {notes.length > 0 ? translate('iou.receiptAudit') : translate('iou.receiptVerified')} + {issuesFoundText} {/* // If notes is a array of strings, map through it & show notes. */} diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 234aa4465d40..639739780a73 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -287,7 +287,10 @@ function MoneyRequestView({ /> )} - + {canUseViolations && } diff --git a/src/languages/en.ts b/src/languages/en.ts index 0a52cca62ef5..f073e79335ea 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -601,6 +601,10 @@ export default { posted: 'Posted', deleteReceipt: 'Delete receipt', routePending: 'Route pending...', + receiptAudit: 'Receipt Audit', + receiptVerified: 'Receipt Verified', + receiptNoIssuesFound: 'No issues Found', + receiptIssuesFound: (count: number) => `${count} Issue(s) Found`, receiptScanning: 'Scan in progress…', receiptMissingDetails: 'Receipt missing details', receiptStatusTitle: 'Scanning…', diff --git a/src/languages/es.ts b/src/languages/es.ts index 013255c1e11e..b908300f4e46 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -594,6 +594,10 @@ export default { posted: 'Contabilizado', deleteReceipt: 'Eliminar recibo', routePending: 'Ruta pendiente...', + receiptAudit: 'Auditoría de recibos', + receiptVerified: 'Recibo verificado', + receiptNoIssuesFound: 'No se encontraron problemas', + receiptIssuesFound: (count: number) => `Se encontró ${count} problema(s)`, receiptScanning: 'Escaneo en curso…', receiptMissingDetails: 'Recibo con campos vacíos', receiptStatusTitle: 'Escaneando…', From 96744d5c4aa2622075e9a2febbe7d2c357eb740d Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Wed, 6 Mar 2024 15:56:41 +0530 Subject: [PATCH 011/647] extract noticeViolations from transactionViolations. Signed-off-by: Krishna Gupta --- src/components/ReceiptAudit.tsx | 6 +++--- src/components/ReportActionItem/MoneyRequestView.tsx | 7 ++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/components/ReceiptAudit.tsx b/src/components/ReceiptAudit.tsx index 7f11e9b9a1e4..f756ecd5dc7e 100644 --- a/src/components/ReceiptAudit.tsx +++ b/src/components/ReceiptAudit.tsx @@ -7,14 +7,14 @@ import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import Text from './Text'; -export default function ReceiptAudit({notes}: {notes: string[]}) { +export default function ReceiptAudit({notes = []}: {notes?: string[]}) { const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); const issuesFoundText = notes.length > 0 ? translate('iou.receiptIssuesFound', notes.length) : translate('iou.receiptNoIssuesFound'); return ( - + {/* // If notes is a array of strings, map through it & show notes. */} - {notes.length > 0 && notes.map((message) => {message})} + {notes.length > 0 && notes.map((message) => {message})} ); } diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 639739780a73..c4f067e35e89 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -147,6 +147,7 @@ function MoneyRequestView({ const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback((field: ViolationField): boolean => !!canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); + const noticeViolations = transactionViolations?.filter((violation) => violation.type === 'notice').map((v) => ViolationsUtils.getViolationTranslation(v, translate)); let amountDescription = `${translate('iou.amount')}`; @@ -286,11 +287,7 @@ function MoneyRequestView({ } /> )} - - + {noticeViolations?.length && } {canUseViolations && } From 952a4dac2dff9293d91ce53a58467042262bb531 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 7 Mar 2024 11:50:47 +0530 Subject: [PATCH 012/647] Update the dot separator sub-state for the request preview. Signed-off-by: Krishna Gupta --- .../MoneyRequestPreviewContent.tsx | 3 +++ .../ReportActionItem/MoneyRequestView.tsx | 2 +- src/libs/TransactionUtils.ts | 13 +++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 8577c9fa5f97..fb524c6b5cb2 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -87,6 +87,7 @@ function MoneyRequestPreviewContent({ const hasReceipt = TransactionUtils.hasReceipt(transaction); const isScanning = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction); const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '', transactionViolations); + const hasNoteTypeViolations = TransactionUtils.hasNoteTypeViolation(transaction?.transactionID ?? '', transactionViolations); const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction); const shouldShowRBR = hasViolations || hasFieldErrors; const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); @@ -159,6 +160,8 @@ function MoneyRequestPreviewContent({ const isTooLong = violations.filter((v) => v.type === 'violation').length > 1 || violationMessage.length > 15; message += ` • ${isTooLong ? translate('violations.reviewRequired') : violationMessage}`; } + } else if (hasNoteTypeViolations && transaction && !ReportUtils.isReportApproved(iouReport) && !ReportUtils.isSettled(iouReport?.reportID)) { + message += ` • ${translate('violations.reviewRequired')}`; } else if (ReportUtils.isPaidGroupPolicyExpenseReport(iouReport) && ReportUtils.isReportApproved(iouReport) && !ReportUtils.isSettled(iouReport?.reportID)) { message += ` • ${translate('iou.approved')}`; } else if (iouReport?.isWaitingOnBankAccount) { diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index c4f067e35e89..440f8afb73ee 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -147,7 +147,7 @@ function MoneyRequestView({ const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback((field: ViolationField): boolean => !!canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); - const noticeViolations = transactionViolations?.filter((violation) => violation.type === 'notice').map((v) => ViolationsUtils.getViolationTranslation(v, translate)); + const noticeViolations = transactionViolations?.filter((violation) => violation.type === 'note').map((v) => ViolationsUtils.getViolationTranslation(v, translate)); let amountDescription = `${translate('iou.amount')}`; diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 8a98fe0f2cdc..5dea541b2af2 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -593,6 +593,17 @@ function hasViolation(transactionID: string, transactionViolations: OnyxCollecti return Boolean(transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === 'violation')); } +/** + * Checks if any violations for the provided transaction are of type 'note' + */ +function hasNoteTypeViolation(transactionID: string, transactionViolations: OnyxCollection): boolean { + return Boolean(transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === 'note')); +} + +function getTransactionNoteViolations(transactionID: string, transactionViolations: OnyxCollection): TransactionViolation[] | null { + return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((violation: TransactionViolation) => violation.type === 'note') ?? null; +} + function getTransactionViolations(transactionID: string, transactionViolations: OnyxCollection): TransactionViolation[] | null { return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null; } @@ -638,6 +649,7 @@ export { getTagArrayFromName, getTagForDisplay, getTransactionViolations, + getTransactionNoteViolations, getLinkedTransaction, getAllReportTransactions, hasReceipt, @@ -663,6 +675,7 @@ export { waypointHasValidAddress, getRecentTransactions, hasViolation, + hasNoteTypeViolation, }; export type {TransactionChanges}; From 8438383448a735e0d5dd871e10ae0e27f85fd9f4 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 7 Mar 2024 11:51:50 +0530 Subject: [PATCH 013/647] Remove redundant code. Signed-off-by: Krishna Gupta --- src/libs/TransactionUtils.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 5dea541b2af2..bc2fcaa39290 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -600,10 +600,6 @@ function hasNoteTypeViolation(transactionID: string, transactionViolations: Onyx return Boolean(transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === 'note')); } -function getTransactionNoteViolations(transactionID: string, transactionViolations: OnyxCollection): TransactionViolation[] | null { - return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((violation: TransactionViolation) => violation.type === 'note') ?? null; -} - function getTransactionViolations(transactionID: string, transactionViolations: OnyxCollection): TransactionViolation[] | null { return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null; } @@ -649,7 +645,6 @@ export { getTagArrayFromName, getTagForDisplay, getTransactionViolations, - getTransactionNoteViolations, getLinkedTransaction, getAllReportTransactions, hasReceipt, From 7f975195bf133d3ef94d5b1123a53b5fd171a457 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 7 Mar 2024 11:56:22 +0530 Subject: [PATCH 014/647] minor fix. Signed-off-by: Krishna Gupta --- src/components/ReportActionItem/MoneyRequestView.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 440f8afb73ee..481bcb177569 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -147,7 +147,12 @@ function MoneyRequestView({ const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback((field: ViolationField): boolean => !!canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); - const noticeViolations = transactionViolations?.filter((violation) => violation.type === 'note').map((v) => ViolationsUtils.getViolationTranslation(v, translate)); + const noticeViolations = [ + {name: 'missingComment', type: 'violation'}, + {name: 'modifiedDate', type: 'violation'}, + ] + ?.filter((violation) => violation.type === 'note') + .map((v) => ViolationsUtils.getViolationTranslation(v, translate)); let amountDescription = `${translate('iou.amount')}`; @@ -287,7 +292,7 @@ function MoneyRequestView({ } /> )} - {noticeViolations?.length && } + {canUseViolations && } From cebc960dcf6e17bc04f2c0ed7158c91e2b112a23 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 7 Mar 2024 12:20:13 +0530 Subject: [PATCH 015/647] minor fix. Signed-off-by: Krishna Gupta --- src/components/ReportActionItem/MoneyRequestView.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 481bcb177569..79639a62babe 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -147,12 +147,7 @@ function MoneyRequestView({ const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback((field: ViolationField): boolean => !!canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); - const noticeViolations = [ - {name: 'missingComment', type: 'violation'}, - {name: 'modifiedDate', type: 'violation'}, - ] - ?.filter((violation) => violation.type === 'note') - .map((v) => ViolationsUtils.getViolationTranslation(v, translate)); + const noteTypeViolations = transactionViolations?.filter((violation) => violation.type === 'note').map((v) => ViolationsUtils.getViolationTranslation(v, translate)); let amountDescription = `${translate('iou.amount')}`; @@ -292,8 +287,7 @@ function MoneyRequestView({ } /> )} - - + {canUseViolations && } Date: Thu, 7 Mar 2024 15:25:49 +0530 Subject: [PATCH 016/647] hide ReceiptAudit when scan is in progress. Signed-off-by: Krishna Gupta --- src/components/ReportActionItem/MoneyRequestView.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 79639a62babe..415a2666512a 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -129,6 +129,7 @@ function MoneyRequestView({ const canEditDate = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DATE); const canEditReceipt = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.RECEIPT); const canEditDistance = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE); + const isReceiptBeingScanned = TransactionUtils.hasReceipt(transaction) && !TransactionUtils.isReceiptBeingScanned(transaction); // A flag for verifying that the current report is a sub-report of a workspace chat // if the policy of the report is either Collect or Control, then this report must be tied to workspace chat @@ -287,7 +288,7 @@ function MoneyRequestView({ } /> )} - + {!isReceiptBeingScanned && canUseViolations && } {canUseViolations && } Date: Fri, 8 Mar 2024 16:53:21 +0530 Subject: [PATCH 017/647] minor updates. Signed-off-by: Krishna Gupta --- src/components/ReceiptAudit.tsx | 2 -- src/components/ReportActionItem/MoneyRequestView.tsx | 7 ++++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/ReceiptAudit.tsx b/src/components/ReceiptAudit.tsx index f756ecd5dc7e..5b91fe97ca0d 100644 --- a/src/components/ReceiptAudit.tsx +++ b/src/components/ReceiptAudit.tsx @@ -27,8 +27,6 @@ export default function ReceiptAudit({notes = []}: {notes?: string[]}) { {issuesFoundText} - - {/* // If notes is a array of strings, map through it & show notes. */} {notes.length > 0 && notes.map((message) => {message})} ); diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 415a2666512a..ffe373aade00 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -129,7 +129,8 @@ function MoneyRequestView({ const canEditDate = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DATE); const canEditReceipt = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.RECEIPT); const canEditDistance = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE); - const isReceiptBeingScanned = TransactionUtils.hasReceipt(transaction) && !TransactionUtils.isReceiptBeingScanned(transaction); + const hasReceipt = TransactionUtils.hasReceipt(transaction); + const isReceiptBeingScanned = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction); // A flag for verifying that the current report is a sub-report of a workspace chat // if the policy of the report is either Collect or Control, then this report must be tied to workspace chat @@ -149,6 +150,7 @@ function MoneyRequestView({ const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback((field: ViolationField): boolean => !!canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); const noteTypeViolations = transactionViolations?.filter((violation) => violation.type === 'note').map((v) => ViolationsUtils.getViolationTranslation(v, translate)); + const shouldShowNotesViolations = !isReceiptBeingScanned && canUseViolations && hasReceipt; let amountDescription = `${translate('iou.amount')}`; @@ -190,7 +192,6 @@ function MoneyRequestView({ } } - const hasReceipt = TransactionUtils.hasReceipt(transaction); let receiptURIs; const hasErrors = canEdit && TransactionUtils.hasMissingSmartscanFields(transaction); if (hasReceipt) { @@ -288,7 +289,7 @@ function MoneyRequestView({ } /> )} - {!isReceiptBeingScanned && canUseViolations && } + {shouldShowNotesViolations && } {canUseViolations && } Date: Mon, 11 Mar 2024 11:50:00 +0530 Subject: [PATCH 018/647] Update MoneyRequestView.tsx --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 3569791d64d4..d79ff88a4f1e 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -147,7 +147,7 @@ function MoneyRequestView({ const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true)); const {getViolationsForField} = useViolations(transactionViolations ?? []); - const hasViolations = useCallback( + const hasViolations = useCallback( (field: ViolationField, data?: OnyxTypes.TransactionViolation['data']): boolean => !!canUseViolations && getViolationsForField(field, data).length > 0, [canUseViolations, getViolationsForField], ); From 48d1543f0ff3a75aea75eb2bb9f176671475c5dd Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 12 Mar 2024 13:12:52 +0530 Subject: [PATCH 019/647] fix: translations. Signed-off-by: Krishna Gupta --- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 2c4f5d21f66f..7979981392fe 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -604,7 +604,7 @@ export default { receiptAudit: 'Receipt Audit', receiptVerified: 'Receipt Verified', receiptNoIssuesFound: 'No issues Found', - receiptIssuesFound: (count: number) => `${count} Issue(s) Found`, + receiptIssuesFound: (count: number) => `${count} ${count === 1 ? 'Issue' : 'Issues'} Found`, receiptScanning: 'Scan in progress…', receiptMissingDetails: 'Receipt missing details', receiptStatusTitle: 'Scanning…', diff --git a/src/languages/es.ts b/src/languages/es.ts index 2f6a064a3615..730d110a60a9 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -597,7 +597,7 @@ export default { receiptAudit: 'Auditoría de recibos', receiptVerified: 'Recibo verificado', receiptNoIssuesFound: 'No se encontraron problemas', - receiptIssuesFound: (count: number) => `Se encontró ${count} problema(s)`, + receiptIssuesFound: (count: number) => `Se encontró ${count} ${count === 1 ? 'problema' : 'problemas'}`, receiptScanning: 'Escaneo en curso…', receiptMissingDetails: 'Recibo con campos vacíos', receiptStatusTitle: 'Escaneando…', From 77165e73b114711e721e98d4b1c0ba6d74cd37d3 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sun, 17 Mar 2024 18:23:23 +0530 Subject: [PATCH 020/647] minor fixes. Signed-off-by: Krishna Gupta --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- src/languages/en.ts | 4 ++-- src/libs/TransactionUtils.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index d79ff88a4f1e..096ac8b69863 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -151,7 +151,7 @@ function MoneyRequestView({ (field: ViolationField, data?: OnyxTypes.TransactionViolation['data']): boolean => !!canUseViolations && getViolationsForField(field, data).length > 0, [canUseViolations, getViolationsForField], ); - const noteTypeViolations = transactionViolations?.filter((violation) => violation.type === 'note').map((v) => ViolationsUtils.getViolationTranslation(v, translate)); + const noteTypeViolations = transactionViolations?.filter((violation) => violation.type === 'notice').map((v) => ViolationsUtils.getViolationTranslation(v, translate)); const shouldShowNotesViolations = !isReceiptBeingScanned && canUseViolations && hasReceipt; let amountDescription = `${translate('iou.amount')}`; diff --git a/src/languages/en.ts b/src/languages/en.ts index 7979981392fe..cdefb85e0168 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -603,8 +603,8 @@ export default { routePending: 'Route pending...', receiptAudit: 'Receipt Audit', receiptVerified: 'Receipt Verified', - receiptNoIssuesFound: 'No issues Found', - receiptIssuesFound: (count: number) => `${count} ${count === 1 ? 'Issue' : 'Issues'} Found`, + receiptNoIssuesFound: 'No issues found', + receiptIssuesFound: (count: number) => `${count} ${count === 1 ? 'issue' : 'issues'} found`, receiptScanning: 'Scan in progress…', receiptMissingDetails: 'Receipt missing details', receiptStatusTitle: 'Scanning…', diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index bc2fcaa39290..86285aef06b3 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -597,7 +597,7 @@ function hasViolation(transactionID: string, transactionViolations: OnyxCollecti * Checks if any violations for the provided transaction are of type 'note' */ function hasNoteTypeViolation(transactionID: string, transactionViolations: OnyxCollection): boolean { - return Boolean(transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === 'note')); + return Boolean(transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === 'notice')); } function getTransactionViolations(transactionID: string, transactionViolations: OnyxCollection): TransactionViolation[] | null { From 7635de8bf5fb4a5d8e17a122cddc4f610787960e Mon Sep 17 00:00:00 2001 From: dragnoir Date: Mon, 18 Mar 2024 13:14:44 +0100 Subject: [PATCH 021/647] Fix: Hold request style and visibility --- src/components/MoneyRequestHeader.tsx | 12 +++++++++--- src/components/MoneyRequestHeaderStatusBar.tsx | 10 ++++++++-- src/styles/index.ts | 5 ++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index a9304b9c3138..0d1425405a92 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -18,7 +18,6 @@ import type {Policy, Report, ReportAction, ReportActions, Session, Transaction} import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import ConfirmModal from './ConfirmModal'; import HeaderWithBackButton from './HeaderWithBackButton'; -import HoldBanner from './HoldBanner'; import * as Expensicons from './Icon/Expensicons'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; import {usePersonalDetails} from './OnyxProvider'; @@ -119,7 +118,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, onSelected: () => changeMoneyRequestStatus(), }); } - if (!isOnHold && (isRequestIOU || canModifyStatus)) { + if (!isOnHold && (isRequestIOU || canModifyStatus) && !isScanning) { threeDotsMenuItems.push({ icon: Expensicons.Stopwatch, text: translate('iou.holdRequest'), @@ -188,10 +187,17 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, + )} + {isOnHold && ( + )} - {isOnHold && } ; }; -function MoneyRequestHeaderStatusBar({title, description, shouldShowBorderBottom}: MoneyRequestHeaderStatusBarProps) { +function MoneyRequestHeaderStatusBar({title, description, shouldShowBorderBottom, badgeColorStyle}: MoneyRequestHeaderStatusBarProps) { const styles = useThemeStyles(); const borderBottomStyle = shouldShowBorderBottom ? styles.borderBottom : {}; + const backgroundColorStyle = badgeColorStyle ?? styles.moneyRequestHeaderStatusBarBadgeBackground; + return ( - + {title} diff --git a/src/styles/index.ts b/src/styles/index.ts index df89cd823fa4..ca7e4ae8f7cf 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4160,10 +4160,13 @@ const styles = (theme: ThemeColors) => display: 'flex', justifyContent: 'center', alignItems: 'center', - backgroundColor: theme.border, marginRight: 12, }, + moneyRequestHeaderStatusBarBadgeBackground: { + backgroundColor: theme.border, + }, + staticHeaderImage: { minHeight: 240, }, From 3e6c006ca8bc275f94b3cc8db694440fd4d1b76b Mon Sep 17 00:00:00 2001 From: dragnoir Date: Mon, 18 Mar 2024 15:50:35 +0100 Subject: [PATCH 022/647] Fix: scan status badge danger text --- src/components/MoneyRequestHeaderStatusBar.tsx | 7 ++++--- src/styles/index.ts | 8 ++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/MoneyRequestHeaderStatusBar.tsx b/src/components/MoneyRequestHeaderStatusBar.tsx index 99913c3b927b..9bbee03b0926 100644 --- a/src/components/MoneyRequestHeaderStatusBar.tsx +++ b/src/components/MoneyRequestHeaderStatusBar.tsx @@ -21,12 +21,13 @@ type MoneyRequestHeaderStatusBarProps = { function MoneyRequestHeaderStatusBar({title, description, shouldShowBorderBottom, badgeColorStyle}: MoneyRequestHeaderStatusBarProps) { const styles = useThemeStyles(); const borderBottomStyle = shouldShowBorderBottom ? styles.borderBottom : {}; - const backgroundColorStyle = badgeColorStyle ?? styles.moneyRequestHeaderStatusBarBadgeBackground; + const badgeBackgroundColorStyle = badgeColorStyle ?? styles.moneyRequestHeaderStatusBarBadgeBackground; + const badgeTextColorStyle = badgeColorStyle ? styles.textMicroBoldDangerColor : styles.textMicroBoldColor; return ( - - {title} + + {title} {description} diff --git a/src/styles/index.ts b/src/styles/index.ts index ca7e4ae8f7cf..306ab55dd215 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -383,6 +383,14 @@ const styles = (theme: ThemeColors) => lineHeight: variables.lineHeightSmall, }, + textMicroBoldColor: { + color: theme.text, + }, + + textMicroBoldDangerColor: { + color: theme.textLight, + }, + textMicroSupporting: { color: theme.textSupporting, fontFamily: FontUtils.fontFamily.platform.EXP_NEUE, From 25cc0fa936630e866bca9a470604632869890b2f Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Wed, 20 Mar 2024 15:35:07 +0530 Subject: [PATCH 023/647] receipt audit design changes. Signed-off-by: Krishna Gupta --- src/components/ReceiptAudit.tsx | 33 +++++++++++-------- .../ReportActionItem/MoneyRequestView.tsx | 5 +-- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/components/ReceiptAudit.tsx b/src/components/ReceiptAudit.tsx index 5b91fe97ca0d..0c387ff0ae79 100644 --- a/src/components/ReceiptAudit.tsx +++ b/src/components/ReceiptAudit.tsx @@ -7,27 +7,32 @@ import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import Text from './Text'; -export default function ReceiptAudit({notes = []}: {notes?: string[]}) { +function ReceiptAuditHeader({notes = []}: {notes?: string[]}) { const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); const issuesFoundText = notes.length > 0 ? translate('iou.receiptIssuesFound', notes.length) : translate('iou.receiptNoIssuesFound'); return ( - - - - 0 ? Expensicons.Receipt : Expensicons.Checkmark} - fill={theme.white} - /> - {notes.length > 0 ? translate('iou.receiptAudit') : translate('iou.receiptVerified')} - - {issuesFoundText} + + + {translate('common.receipt')} + {` • ${issuesFoundText}`} + 0 ? Expensicons.DotIndicator : Expensicons.Checkmark} + fill={notes.length ? theme.danger : theme.success} + additionalStyles={styles.ml2} + /> - {notes.length > 0 && notes.map((message) => {message})} ); } + +function ReceiptAuditMessages({notes = []}: {notes?: string[]}) { + const styles = useThemeStyles(); + return {notes.length > 0 && notes.map((message) => {message})}; +} + +export {ReceiptAuditHeader, ReceiptAuditMessages}; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 096ac8b69863..47a193946083 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -6,7 +6,7 @@ import ConfirmedRoute from '@components/ConfirmedRoute'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import ReceiptAudit from '@components/ReceiptAudit'; +import {ReceiptAuditHeader, ReceiptAuditMessages} from '@components/ReceiptAudit'; import ReceiptEmptyState from '@components/ReceiptEmptyState'; import SpacerView from '@components/SpacerView'; import Switch from '@components/Switch'; @@ -244,6 +244,7 @@ function MoneyRequestView({ + {shouldShowNotesViolations && } {/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */} {(showMapAsImage || hasReceipt) && ( )} - {shouldShowNotesViolations && } + {shouldShowNotesViolations && } {canUseViolations && } Date: Fri, 22 Mar 2024 07:02:41 +0530 Subject: [PATCH 024/647] Receipt audit design updates. Signed-off-by: Krishna Gupta --- src/components/ReceiptAudit.tsx | 29 +++++++++++-------- .../ReportActionItem/MoneyRequestView.tsx | 10 +++++-- src/languages/en.ts | 6 ++-- src/languages/es.ts | 6 ++-- src/styles/index.ts | 13 --------- 5 files changed, 28 insertions(+), 36 deletions(-) diff --git a/src/components/ReceiptAudit.tsx b/src/components/ReceiptAudit.tsx index 0c387ff0ae79..3ec5e7f8e6de 100644 --- a/src/components/ReceiptAudit.tsx +++ b/src/components/ReceiptAudit.tsx @@ -7,24 +7,29 @@ import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import Text from './Text'; -function ReceiptAuditHeader({notes = []}: {notes?: string[]}) { +function ReceiptAuditHeader({notes = [], showAuditMessage = false}: {notes?: string[]; showAuditMessage?: boolean}) { const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); - const issuesFoundText = notes.length > 0 ? translate('iou.receiptIssuesFound', notes.length) : translate('iou.receiptNoIssuesFound'); + const issuesFoundText = notes.length > 0 ? translate('iou.receiptIssuesFound', notes.length) : translate('common.verified'); return ( - + {translate('common.receipt')} - {` • ${issuesFoundText}`} - 0 ? Expensicons.DotIndicator : Expensicons.Checkmark} - fill={notes.length ? theme.danger : theme.success} - additionalStyles={styles.ml2} - /> + {showAuditMessage && ( + <> + {' • '} + {`${issuesFoundText}`} + 0 ? Expensicons.DotIndicator : Expensicons.Checkmark} + fill={notes.length ? theme.danger : theme.success} + additionalStyles={styles.ml1} + /> + + )} ); @@ -32,7 +37,7 @@ function ReceiptAuditHeader({notes = []}: {notes?: string[]}) { function ReceiptAuditMessages({notes = []}: {notes?: string[]}) { const styles = useThemeStyles(); - return {notes.length > 0 && notes.map((message) => {message})}; + return {notes.length > 0 && notes.map((message) => {message})}; } export {ReceiptAuditHeader, ReceiptAuditMessages}; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 47a193946083..7fd5fec6d2b9 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -244,8 +244,12 @@ function MoneyRequestView({ - {shouldShowNotesViolations && } - {/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */} + {hasReceipt && ( + + )} {(showMapAsImage || hasReceipt) && ( )} {shouldShowNotesViolations && } - {canUseViolations && } + `${count} ${count === 1 ? 'issue' : 'issues'} found`, + receiptIssuesFound: (count: number) => `${count === 1 ? 'Issue' : 'Issues'} found`, receiptScanning: 'Scan in progress…', receiptMissingDetails: 'Receipt missing details', missingAmount: 'Missing amount', diff --git a/src/languages/es.ts b/src/languages/es.ts index 9097bf1b1370..9dbaa2eadd79 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -281,6 +281,7 @@ export default { nonBillable: 'No facturable', tag: 'Etiqueta', receipt: 'Recibo', + verified: `Verificado`, replace: 'Sustituir', distance: 'Distancia', mile: 'milla', @@ -595,10 +596,7 @@ export default { posted: 'Contabilizado', deleteReceipt: 'Eliminar recibo', routePending: 'Ruta pendiente...', - receiptAudit: 'Auditoría de recibos', - receiptVerified: 'Recibo verificado', - receiptNoIssuesFound: 'No se encontraron problemas', - receiptIssuesFound: (count: number) => `Se encontró ${count} ${count === 1 ? 'problema' : 'problemas'}`, + receiptIssuesFound: (count: number) => `${count === 1 ? 'Problema' : 'Problemas'}`, receiptScanning: 'Escaneo en curso…', receiptMissingDetails: 'Recibo con campos vacíos', missingAmount: 'Falta importe', diff --git a/src/styles/index.ts b/src/styles/index.ts index f25b349cba60..8a91291a0c71 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4225,19 +4225,6 @@ const styles = (theme: ThemeColors) => borderWidth: 1, }, - receiptAuditTitleContainer: { - flexDirection: 'row', - gap: 4, - padding: 4, - paddingHorizontal: 8, - height: variables.inputHeightSmall, - borderRadius: variables.componentBorderRadiusSmall, - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - backgroundColor: theme.border, - }, - mapViewContainer: { ...flex.flex1, minHeight: 300, From fd457cd933ade344d19b0a17d40b6cd47d9214e3 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 26 Mar 2024 03:35:19 +0530 Subject: [PATCH 025/647] show notes violation for only admins and approvers in a paid policy. Signed-off-by: Krishna Gupta --- .../ReportActionItem/MoneyRequestView.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 7fd5fec6d2b9..f17f635f3260 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -41,6 +41,9 @@ import type {TransactionPendingFieldsKey} from '@src/types/onyx/Transaction'; import ReportActionItemImage from './ReportActionItemImage'; type MoneyRequestViewTransactionOnyxProps = { + /** Session info for the currently logged in user. */ + session: OnyxEntry; + /** The transaction associated with the transactionThread */ transaction: OnyxEntry; @@ -85,6 +88,7 @@ function MoneyRequestView({ policyTagList, policy, transactionViolations, + session, }: MoneyRequestViewProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -131,6 +135,10 @@ function MoneyRequestView({ const hasReceipt = TransactionUtils.hasReceipt(transaction); const isReceiptBeingScanned = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction); + const isActionOwner = typeof parentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && parentReportAction.actorAccountID === session?.accountID; + const isPolicyAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; + const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && (session?.accountID ?? null) === moneyRequestReport?.managerID; + // A flag for verifying that the current report is a sub-report of a workspace chat // if the policy of the report is either Collect or Control, then this report must be tied to workspace chat const isPolicyExpenseChat = ReportUtils.isGroupPolicy(report); @@ -152,7 +160,7 @@ function MoneyRequestView({ [canUseViolations, getViolationsForField], ); const noteTypeViolations = transactionViolations?.filter((violation) => violation.type === 'notice').map((v) => ViolationsUtils.getViolationTranslation(v, translate)); - const shouldShowNotesViolations = !isReceiptBeingScanned && canUseViolations && hasReceipt; + const shouldShowNotesViolations = !isReceiptBeingScanned && canUseViolations && ReportUtils.isPaidGroupPolicy(report) && (isActionOwner || isPolicyAdmin || isApprover); let amountDescription = `${translate('iou.amount')}`; @@ -487,5 +495,8 @@ export default withOnyx Date: Tue, 26 Mar 2024 12:59:31 +0100 Subject: [PATCH 026/647] make wallet page cards grouped by domain --- src/ROUTES.ts | 4 +- src/languages/en.ts | 12 ++ src/languages/es.ts | 12 ++ src/libs/Navigation/types.ts | 7 +- .../settings/Wallet/ExpensifyCardPage.tsx | 158 ++++++++++++------ .../settings/Wallet/PaymentMethodList.tsx | 62 +++++-- src/styles/index.ts | 5 + 7 files changed, 184 insertions(+), 76 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index c216d5ac288c..2f0f5851407a 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -84,8 +84,8 @@ const ROUTES = { SETTINGS_APP_DOWNLOAD_LINKS: 'settings/about/app-download-links', SETTINGS_WALLET: 'settings/wallet', SETTINGS_WALLET_DOMAINCARD: { - route: 'settings/wallet/card/:domain', - getRoute: (domain: string) => `settings/wallet/card/${domain}` as const, + route: 'settings/wallet/card/:domain/:cardId', + getRoute: (domain: string, cardId: string) => `settings/wallet/card/${domain}/${cardId}` as const, }, SETTINGS_REPORT_FRAUD: { route: 'settings/wallet/card/:domain/report-virtual-fraud', diff --git a/src/languages/en.ts b/src/languages/en.ts index c3ad6d82d6b2..d5b6d5107f3f 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1029,6 +1029,18 @@ export default { cardPage: { expensifyCard: 'Expensify Card', availableSpend: 'Remaining limit', + smartLimit: { + name: 'Smart limit', + title: (formattedLimit: string) => `You can spend up to ${formattedLimit} on this card, and the limit will reset as your submitted expenses are approved.`, + }, + fixedLimit: { + name: 'Fixed limit', + title: (formattedLimit: string) => `You can spend up to ${formattedLimit} on this card, and then it will deactivate.`, + }, + monthlyLimit: { + name: 'Monthly limit', + title: (formattedLimit: string) => `You can spend up to ${formattedLimit} on this card per month. The limit will reset on the 1st day of each calendar month.`, + }, virtualCardNumber: 'Virtual card number', physicalCardNumber: 'Physical card number', getPhysicalCard: 'Get physical card', diff --git a/src/languages/es.ts b/src/languages/es.ts index 78b80adb16d4..d28b112a5471 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1028,6 +1028,18 @@ export default { cardPage: { expensifyCard: 'Tarjeta Expensify', availableSpend: 'Límite restante', + smartLimit: { + name: 'Smart limit', + title: (formattedLimit: string) => `Puedes gastar hasta ${formattedLimit} en esta tarjeta al mes. El límite se restablecerá el primer día del mes.`, + }, + fixedLimit: { + name: 'Fixed limit', + title: (formattedLimit: string) => `Puedes gastar hasta ${formattedLimit} en esta tarjeta, luego se desactivará.`, + }, + monthlyLimit: { + name: 'Monthly limit', + title: (formattedLimit: string) => `Puedes gastar hasta ${formattedLimit} en esta tarjeta y el límite se restablecerá a medida que se aprueben tus gastos.`, + }, virtualCardNumber: 'Número de la tarjeta virtual', physicalCardNumber: 'Número de la tarjeta física', getPhysicalCard: 'Obtener tarjeta física', diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 3f85aec3a560..ae74ac795f3c 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -111,7 +111,12 @@ type SettingsNavigatorParamList = { }; [SCREENS.SETTINGS.WALLET.ROOT]: undefined; [SCREENS.SETTINGS.WALLET.CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS]: undefined; - [SCREENS.SETTINGS.WALLET.DOMAIN_CARD]: undefined; + [SCREENS.SETTINGS.WALLET.DOMAIN_CARD]: { + /** domain passed via route /settings/wallet/card/:domain/:card */ + domain: string; + /** cardId passed via route /settings/wallet/card/:domain/:card */ + cardId: string; + }; [SCREENS.SETTINGS.WALLET.REPORT_VIRTUAL_CARD_FRAUD]: undefined; [SCREENS.SETTINGS.WALLET.CARD_ACTIVATE]: undefined; [SCREENS.SETTINGS.WALLET.CARD_GET_PHYSICAL.NAME]: { diff --git a/src/pages/settings/Wallet/ExpensifyCardPage.tsx b/src/pages/settings/Wallet/ExpensifyCardPage.tsx index 4c8b02eabdc6..80f134fc575a 100644 --- a/src/pages/settings/Wallet/ExpensifyCardPage.tsx +++ b/src/pages/settings/Wallet/ExpensifyCardPage.tsx @@ -3,6 +3,7 @@ import React, {useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import Button from '@components/Button'; import CardPreview from '@components/CardPreview'; import DotIndicatorMessage from '@components/DotIndicatorMessage'; @@ -20,7 +21,7 @@ import * as CardUtils from '@libs/CardUtils'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as GetPhysicalCardUtils from '@libs/GetPhysicalCardUtils'; import Navigation from '@libs/Navigation/Navigation'; -import type {PublicScreensParamList} from '@libs/Navigation/types'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import * as Card from '@userActions/Card'; import * as Link from '@userActions/Link'; @@ -31,7 +32,6 @@ import type SCREENS from '@src/SCREENS'; import type {GetPhysicalCardForm} from '@src/types/form'; import type {LoginList, Card as OnyxCard, PrivatePersonalDetails} from '@src/types/onyx'; import type {TCardDetails} from '@src/types/onyx/Card'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; import RedDotCardSection from './RedDotCardSection'; import CardDetails from './WalletPage/CardDetails'; @@ -49,7 +49,7 @@ type ExpensifyCardPageOnyxProps = { loginList: OnyxEntry; }; -type ExpensifyCardPageProps = ExpensifyCardPageOnyxProps & StackScreenProps; +type ExpensifyCardPageProps = ExpensifyCardPageOnyxProps & StackScreenProps; function ExpensifyCardPage({ cardList, @@ -57,45 +57,85 @@ function ExpensifyCardPage({ privatePersonalDetails, loginList, route: { - params: {domain = ''}, + params: {domain = '', cardId = ''}, }, }: ExpensifyCardPageProps) { const styles = useThemeStyles(); const {isOffline} = useNetwork(); const {translate} = useLocalize(); - const domainCards = useMemo(() => cardList && CardUtils.getDomainCards(cardList)[domain], [cardList, domain]); - const virtualCard = useMemo(() => domainCards?.find((card) => card.isVirtual), [domainCards]); - const physicalCard = useMemo(() => domainCards?.find((card) => !card.isVirtual), [domainCards]); + const isCardDomain = !cardList?.[cardId].isAdminIssuedVirtualCard; - const [isLoading, setIsLoading] = useState(false); const [isNotFound, setIsNotFound] = useState(false); - const [details, setDetails] = useState(); - const [cardDetailsError, setCardDetailsError] = useState(''); - + const cardsToShow = useMemo( + () => (isCardDomain ? CardUtils.getDomainCards(cardList)[domain].filter((card) => !card.isAdminIssuedVirtualCard) : [cardList?.[cardId]]), + [isCardDomain, cardList, cardId, domain], + ); useEffect(() => { - if (!cardList) { - return; - } - setIsNotFound(isEmptyObject(virtualCard) && isEmptyObject(physicalCard)); - }, [cardList, physicalCard, virtualCard]); + setIsNotFound(!cardsToShow); + }, [cardList, cardsToShow]); - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- availableSpend can be 0 - const formattedAvailableSpendAmount = CurrencyUtils.convertToDisplayString(physicalCard?.availableSpend || virtualCard?.availableSpend || 0); + const virtualCards = useMemo(() => cardsToShow.filter((card) => card.isVirtual), [cardsToShow]); + const physicalCards = useMemo(() => cardsToShow.filter((card) => !card.isVirtual), [cardsToShow]); + const [cardsDetails, setCardsDetails] = useState>({}); + const [isCardDetailsLoading, setIsCardDetailsLoading] = useState>({}); + const [cardsDetailsErrors, setCardsDetailsErrors] = useState>({}); - const handleRevealDetails = () => { - setIsLoading(true); + const handleRevealDetails = (revealedCardId: number) => { + setIsCardDetailsLoading((prevState: Record) => { + const newLoadingStates = {...prevState}; + newLoadingStates[revealedCardId] = true; + return newLoadingStates; + }); // We can't store the response in Onyx for security reasons. // That is why this action is handled manually and the response is stored in a local state // Hence eslint disable here. // eslint-disable-next-line rulesdir/no-thenable-actions-in-views - Card.revealVirtualCardDetails(virtualCard?.cardID ?? 0) + Card.revealVirtualCardDetails(revealedCardId) .then((value) => { - setDetails(value as TCardDetails); - setCardDetailsError(''); + setCardsDetails((prevState: Record) => { + const newCardsDetails = {...prevState}; + newCardsDetails[revealedCardId] = value as TCardDetails; + return newCardsDetails; + }); + setCardsDetailsErrors((prevState) => { + const newCardsDetailsErrors = {...prevState}; + newCardsDetailsErrors[revealedCardId] = ''; + return newCardsDetailsErrors; + }); + }) + .catch((error) => { + setCardsDetailsErrors((prevState) => { + const newCardsDetailsErrors = {...prevState}; + newCardsDetailsErrors[revealedCardId] = error; + return newCardsDetailsErrors; + }); }) - .catch(setCardDetailsError) - .finally(() => setIsLoading(false)); + .finally(() => + setIsCardDetailsLoading((prevState: Record) => { + const newLoadingStates = {...prevState}; + newLoadingStates[revealedCardId] = false; + return newLoadingStates; + }), + ); + }; + + const hasDetectedDomainFraud = cardsToShow?.some((card) => card?.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN); + const hasDetectedIndividualFraud = cardsToShow?.some((card) => card?.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL); + + const formattedAvailableSpendAmount = CurrencyUtils.convertToDisplayString(cardsToShow?.[0]?.availableSpend); + const getLimitStrings = (limitType: ValueOf) => { + switch (limitType) { + case CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART: + return {limitName: translate('cardPage.smartLimit.name'), limitTitle: translate('cardPage.smartLimit.title', formattedAvailableSpendAmount)}; + case CONST.EXPENSIFY_CARD.LIMIT_TYPES.MONTHLY: + return {limitName: translate('cardPage.monthlyLimit.name'), limitTitle: translate('cardPage.monthlyLimit.title', formattedAvailableSpendAmount)}; + case CONST.EXPENSIFY_CARD.LIMIT_TYPES.FIXED: + return {limitName: translate('cardPage.fixedLimit.name'), limitTitle: translate('cardPage.fixedLimit.title', formattedAvailableSpendAmount)}; + default: + return {limitName: '', limitTitle: ''}; + } }; + const {limitName, limitTitle} = getLimitStrings(cardsToShow?.[0]?.limitType); const goToGetPhysicalCardFlow = () => { let updatedDraftValues = draftValues; @@ -109,9 +149,6 @@ function ExpensifyCardPage({ GetPhysicalCardUtils.goToNextPhysicalCardRoute(domain, GetPhysicalCardUtils.getUpdatedPrivatePersonalDetails(updatedDraftValues)); }; - const hasDetectedDomainFraud = domainCards?.some((card) => card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN); - const hasDetectedIndividualFraud = domainCards?.some((card) => card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL); - if (isNotFound) { return Navigation.goBack(ROUTES.SETTINGS_WALLET)} />; } @@ -165,13 +202,21 @@ function ExpensifyCardPage({ interactive={false} titleStyle={styles.newKansasLarge} /> - {!isEmptyObject(virtualCard) && ( + + + {virtualCards.map((card) => ( <> - {details?.pan ? ( + {!!cardsDetails[card.cardID] && cardsDetails[card.cardID]?.pan ? ( ) : ( @@ -186,14 +231,14 @@ function ExpensifyCardPage({ + + )} ) : ( - + ); } diff --git a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.tsx b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.tsx deleted file mode 100644 index 64391909b197..000000000000 --- a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import {Animated, View} from 'react-native'; -import useThemeStyles from '@hooks/useThemeStyles'; -import type FloatingMessageCounterContainerProps from './types'; - -function FloatingMessageCounterContainer({containerStyles, children}: FloatingMessageCounterContainerProps) { - const styles = useThemeStyles(); - - return ( - - {children} - - ); -} - -FloatingMessageCounterContainer.displayName = 'FloatingMessageCounterContainer'; - -export default FloatingMessageCounterContainer; diff --git a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.tsx b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.tsx deleted file mode 100644 index 8757d66160c4..000000000000 --- a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import {Animated} from 'react-native'; -import useThemeStyles from '@hooks/useThemeStyles'; -import type FloatingMessageCounterContainerProps from './types'; - -function FloatingMessageCounterContainer({accessibilityHint, containerStyles, children}: FloatingMessageCounterContainerProps) { - const styles = useThemeStyles(); - - return ( - - {children} - - ); -} - -FloatingMessageCounterContainer.displayName = 'FloatingMessageCounterContainer'; - -export default FloatingMessageCounterContainer; diff --git a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/types.ts b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/types.ts deleted file mode 100644 index cfe791eed79c..000000000000 --- a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type {StyleProp, ViewStyle} from 'react-native'; -import type ChildrenProps from '@src/types/utils/ChildrenProps'; - -type FloatingMessageCounterContainerProps = ChildrenProps & { - /** Styles to be assigned to Container */ - containerStyles?: StyleProp; - - /** Specifies the accessibility hint for the component */ - accessibilityHint?: string; -}; - -export default FloatingMessageCounterContainerProps; From 35ea3650ee17950c264c2af6275fec819511e24b Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 16 Apr 2024 17:28:44 -0700 Subject: [PATCH 167/647] Remove unused styles --- src/styles/index.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index 537038d9f2e1..9bba82ac6b95 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3341,21 +3341,6 @@ const styles = (theme: ThemeColors) => ...visibility.hidden, }, - floatingMessageCounterWrapperAndroid: { - left: 0, - width: '100%', - alignItems: 'center', - position: 'absolute', - top: 0, - zIndex: 100, - ...visibility.hidden, - }, - - floatingMessageCounterSubWrapperAndroid: { - left: '50%', - width: 'auto', - }, - floatingMessageCounter: { left: '-50%', ...visibility.visible, From fda608b3b896d4d2ea2ede7101b5cd42ad85c93f Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 16 Apr 2024 17:31:41 -0700 Subject: [PATCH 168/647] Combine styles together --- src/pages/home/report/FloatingMessageCounter.tsx | 2 +- src/styles/index.ts | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/pages/home/report/FloatingMessageCounter.tsx b/src/pages/home/report/FloatingMessageCounter.tsx index 997198192bb6..34eb01b62817 100644 --- a/src/pages/home/report/FloatingMessageCounter.tsx +++ b/src/pages/home/report/FloatingMessageCounter.tsx @@ -52,7 +52,7 @@ function FloatingMessageCounter({isActive = false, onClick = () => {}}: Floating return ( diff --git a/src/styles/index.ts b/src/styles/index.ts index 9bba82ac6b95..df94258b5c77 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3333,24 +3333,20 @@ const styles = (theme: ThemeColors) => height: variables.communicationsLinkHeight, }, - floatingMessageCounterWrapper: { + floatingMessageCounterWrapper: (translateY: AnimatableNumericValue) => ({ position: 'absolute', left: '50%', top: 0, zIndex: 100, + transform: [{translateY}], ...visibility.hidden, - }, + }), floatingMessageCounter: { left: '-50%', ...visibility.visible, }, - floatingMessageCounterTransformation: (translateY: AnimatableNumericValue) => - ({ - transform: [{translateY}], - } satisfies ViewStyle), - confirmationAnimation: { height: 180, width: 180, From cf6d23e8bd6eefe3af55e1b6c529e336d69e1ada Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 16 Apr 2024 17:48:03 -0700 Subject: [PATCH 169/647] Migrate animation to reanimated --- .../home/report/FloatingMessageCounter.tsx | 27 ++++++++++--------- src/styles/index.ts | 5 ++-- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/pages/home/report/FloatingMessageCounter.tsx b/src/pages/home/report/FloatingMessageCounter.tsx index 34eb01b62817..8e73d36a878a 100644 --- a/src/pages/home/report/FloatingMessageCounter.tsx +++ b/src/pages/home/report/FloatingMessageCounter.tsx @@ -1,5 +1,6 @@ import React, {useCallback, useEffect, useMemo} from 'react'; -import {Animated, View} from 'react-native'; +import {View} from 'react-native'; +import Animated, {useAnimatedStyle, useSharedValue, withSpring} from 'react-native-reanimated'; import Button from '@components/Button'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -7,7 +8,6 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import useNativeDriver from '@libs/useNativeDriver'; import CONST from '@src/CONST'; type FloatingMessageCounterProps = { @@ -25,20 +25,18 @@ function FloatingMessageCounter({isActive = false, onClick = () => {}}: Floating const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); - const translateY = useMemo(() => new Animated.Value(MARKER_INACTIVE_TRANSLATE_Y), []); + const translateY = useSharedValue(MARKER_INACTIVE_TRANSLATE_Y); const show = useCallback(() => { - Animated.spring(translateY, { - toValue: MARKER_ACTIVE_TRANSLATE_Y, - useNativeDriver, - }).start(); + 'worklet'; + + translateY.value = withSpring(MARKER_ACTIVE_TRANSLATE_Y); }, [translateY]); const hide = useCallback(() => { - Animated.spring(translateY, { - toValue: MARKER_INACTIVE_TRANSLATE_Y, - useNativeDriver, - }).start(); + 'worklet'; + + translateY.value = withSpring(MARKER_INACTIVE_TRANSLATE_Y); }, [translateY]); useEffect(() => { @@ -49,10 +47,15 @@ function FloatingMessageCounter({isActive = false, onClick = () => {}}: Floating } }, [isActive, show, hide]); + const wrapperStyle = useAnimatedStyle(() => ({ + ...styles.floatingMessageCounterWrapper, + transform: [{translateY: translateY.value}], + })); + return ( diff --git a/src/styles/index.ts b/src/styles/index.ts index df94258b5c77..0badb7412b1a 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3333,14 +3333,13 @@ const styles = (theme: ThemeColors) => height: variables.communicationsLinkHeight, }, - floatingMessageCounterWrapper: (translateY: AnimatableNumericValue) => ({ + floatingMessageCounterWrapper: { position: 'absolute', left: '50%', top: 0, zIndex: 100, - transform: [{translateY}], ...visibility.hidden, - }), + }, floatingMessageCounter: { left: '-50%', From 1044bad394543fde51d2e89b6f935444b5fd3779 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 17 Apr 2024 11:49:42 +0700 Subject: [PATCH 170/647] rename props Avatar --- src/components/Avatar.tsx | 6 +++--- src/components/HeaderWithBackButton/index.tsx | 2 +- src/components/MentionSuggestions.tsx | 2 +- src/components/MultipleAvatars.tsx | 8 ++++---- src/components/RoomHeaderAvatars.tsx | 4 ++-- src/components/SubscriptAvatar.tsx | 4 ++-- .../UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx | 2 +- src/pages/home/report/ReportActionItemSingle.tsx | 2 +- src/pages/workspace/WorkspaceProfilePage.tsx | 2 +- src/pages/workspace/WorkspacesListRow.tsx | 2 +- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index dfcf2cc675b9..09d22132b44e 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -51,7 +51,7 @@ type AvatarProps = { name?: string; /** ID of the Icon */ - iconID?: number | string; + accountID?: number | string; }; function Avatar({ @@ -65,7 +65,7 @@ function Avatar({ fallbackIconTestID = '', type = CONST.ICON_TYPE_AVATAR, name = '', - iconID, + accountID, }: AvatarProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -92,7 +92,7 @@ function Avatar({ let iconColors; if (isWorkspace) { - iconColors = StyleUtils.getDefaultWorkspaceAvatarColor(iconID?.toString() ?? ''); + iconColors = StyleUtils.getDefaultWorkspaceAvatarColor(accountID?.toString() ?? ''); } else if (useFallBackAvatar) { iconColors = StyleUtils.getBackgroundColorAndFill(theme.border, theme.icon); } else { diff --git a/src/components/HeaderWithBackButton/index.tsx b/src/components/HeaderWithBackButton/index.tsx index 6e649955880a..29d010d9c803 100755 --- a/src/components/HeaderWithBackButton/index.tsx +++ b/src/components/HeaderWithBackButton/index.tsx @@ -182,7 +182,7 @@ function HeaderWithBackButton({ containerStyles={[StyleUtils.getWidthAndHeightStyle(StyleUtils.getAvatarSize(CONST.AVATAR_SIZE.DEFAULT)), styles.mr3]} source={policyAvatar?.source} name={policyAvatar?.name} - iconID={policyAvatar?.id} + accountID={policyAvatar?.id} type={policyAvatar?.type} /> )} diff --git a/src/components/MentionSuggestions.tsx b/src/components/MentionSuggestions.tsx index e69484eacadd..b11ae4f5ecd8 100644 --- a/src/components/MentionSuggestions.tsx +++ b/src/components/MentionSuggestions.tsx @@ -83,7 +83,7 @@ function MentionSuggestions({prefix, mentions, highlightedMentionIndex = 0, onSe source={item.icons[0].source} size={isIcon ? CONST.AVATAR_SIZE.MENTION_ICON : CONST.AVATAR_SIZE.SMALLER} name={item.icons[0].name} - iconID={item.icons[0].id} + accountID={item.icons[0].id} type={item.icons[0].type} fill={isIcon ? theme.success : undefined} fallbackIcon={item.icons[0].fallbackIcon} diff --git a/src/components/MultipleAvatars.tsx b/src/components/MultipleAvatars.tsx index cbceace6df16..d5fcf6607179 100644 --- a/src/components/MultipleAvatars.tsx +++ b/src/components/MultipleAvatars.tsx @@ -156,7 +156,7 @@ function MultipleAvatars({ size={size} fill={icons[0].fill} name={icons[0].name} - iconID={icons[0].id} + accountID={icons[0].id} type={icons[0].type} fallbackIcon={icons[0].fallbackIcon} /> @@ -206,7 +206,7 @@ function MultipleAvatars({ source={icon.source ?? fallbackIcon} size={size} name={icon.name} - iconID={icon.id} + accountID={icon.id} type={icon.type} fallbackIcon={icon.fallbackIcon} /> @@ -265,7 +265,7 @@ function MultipleAvatars({ imageStyles={[singleAvatarStyle]} name={icons[0].name} type={icons[0].type} - iconID={icons[0].id} + accountID={icons[0].id} fallbackIcon={icons[0].fallbackIcon} /> @@ -285,7 +285,7 @@ function MultipleAvatars({ size={avatarSize} imageStyles={[singleAvatarStyle]} name={icons[1].name} - iconID={icons[1].id} + accountID={icons[1].id} type={icons[1].type} fallbackIcon={icons[1].fallbackIcon} /> diff --git a/src/components/RoomHeaderAvatars.tsx b/src/components/RoomHeaderAvatars.tsx index 341398e44b06..bdb4a0ac78ab 100644 --- a/src/components/RoomHeaderAvatars.tsx +++ b/src/components/RoomHeaderAvatars.tsx @@ -47,7 +47,7 @@ function RoomHeaderAvatars({icons, reportID}: RoomHeaderAvatarsProps) { imageStyles={styles.avatarLarge} size={CONST.AVATAR_SIZE.LARGE} name={icons[0].name} - iconID={icons[0].id} + accountID={icons[0].id} type={icons[0].type} fallbackIcon={icons[0].fallbackIcon} /> @@ -83,7 +83,7 @@ function RoomHeaderAvatars({icons, reportID}: RoomHeaderAvatarsProps) { size={CONST.AVATAR_SIZE.LARGE} containerStyles={[...iconStyle, StyleUtils.getAvatarBorderRadius(CONST.AVATAR_SIZE.LARGE_BORDERED, icon.type)]} name={icon.name} - iconID={icon.id} + accountID={icon.id} type={icon.type} fallbackIcon={icon.fallbackIcon} /> diff --git a/src/components/SubscriptAvatar.tsx b/src/components/SubscriptAvatar.tsx index d7ddb9ddf9b0..cc36657826f6 100644 --- a/src/components/SubscriptAvatar.tsx +++ b/src/components/SubscriptAvatar.tsx @@ -82,7 +82,7 @@ function SubscriptAvatar({ source={mainAvatar?.source} size={size} name={mainAvatar?.name} - iconID={mainAvatar?.id} + accountID={mainAvatar?.id} type={mainAvatar?.type} fallbackIcon={mainAvatar?.fallbackIcon} /> @@ -109,7 +109,7 @@ function SubscriptAvatar({ size={isSmall ? CONST.AVATAR_SIZE.SMALL_SUBSCRIPT : CONST.AVATAR_SIZE.SUBSCRIPT} fill={secondaryAvatar.fill} name={secondaryAvatar.name} - iconID={secondaryAvatar.id} + accountID={secondaryAvatar.id} type={secondaryAvatar.type} fallbackIcon={secondaryAvatar.fallbackIcon} /> diff --git a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx index fa301167a230..a530bcfddc46 100644 --- a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx +++ b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx @@ -59,7 +59,7 @@ function BaseUserDetailsTooltip({accountID, fallbackUserDetails, icon, delegateA type={icon?.type ?? CONST.ICON_TYPE_AVATAR} name={icon?.name ?? userLogin} fallbackIcon={icon?.fallbackIcon} - iconID={icon?.id} + accountID={icon?.id} /> {title} diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index 59c5b0e240ad..ce8d116a0b2d 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -200,7 +200,7 @@ function ReportActionItemSingle({ source={icon.source} type={icon.type} name={icon.name} - iconID={icon.id} + accountID={icon.id} fallbackIcon={fallbackIcon} /> diff --git a/src/pages/workspace/WorkspaceProfilePage.tsx b/src/pages/workspace/WorkspaceProfilePage.tsx index c328bea9bb96..d872cb0275f1 100644 --- a/src/pages/workspace/WorkspaceProfilePage.tsx +++ b/src/pages/workspace/WorkspaceProfilePage.tsx @@ -83,7 +83,7 @@ function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfi fallbackIcon={Expensicons.FallbackWorkspaceAvatar} size={CONST.AVATAR_SIZE.XLARGE} name={policyName} - iconID={policy?.id ?? ''} + accountID={policy?.id ?? ''} type={CONST.ICON_TYPE_WORKSPACE} /> ), diff --git a/src/pages/workspace/WorkspacesListRow.tsx b/src/pages/workspace/WorkspacesListRow.tsx index 21c14ff8cda4..c9473914eaae 100644 --- a/src/pages/workspace/WorkspacesListRow.tsx +++ b/src/pages/workspace/WorkspacesListRow.tsx @@ -151,7 +151,7 @@ function WorkspacesListRow({ source={workspaceIcon} fallbackIcon={fallbackWorkspaceIcon} name={title} - iconID={policyID} + accountID={policyID} type={CONST.ICON_TYPE_WORKSPACE} /> Date: Wed, 17 Apr 2024 13:00:27 +0700 Subject: [PATCH 171/647] safely get personal detail --- src/components/OptionListContextProvider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/OptionListContextProvider.tsx b/src/components/OptionListContextProvider.tsx index 94fe51711e02..5d4e704262f5 100644 --- a/src/components/OptionListContextProvider.tsx +++ b/src/components/OptionListContextProvider.tsx @@ -121,8 +121,8 @@ function OptionsListContextProvider({reports, children}: OptionsListProviderProp }> = []; Object.keys(personalDetails).forEach((accoutID) => { - const prevPersonalDetail = prevPersonalDetails.current[accoutID]; - const personalDetail = personalDetails[accoutID]; + const prevPersonalDetail = prevPersonalDetails.current?.[accoutID]; + const personalDetail = personalDetails?.[accoutID]; if (isEqualPersonalDetail(prevPersonalDetail, personalDetail)) { return; From 1fecd977f9b534f119955a62bd4c7ecf31135bd2 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 17 Apr 2024 13:07:31 +0700 Subject: [PATCH 172/647] update to use usePrevious hook --- src/components/OptionListContextProvider.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/OptionListContextProvider.tsx b/src/components/OptionListContextProvider.tsx index 5d4e704262f5..473c0f5bb083 100644 --- a/src/components/OptionListContextProvider.tsx +++ b/src/components/OptionListContextProvider.tsx @@ -51,7 +51,7 @@ function OptionsListContextProvider({reports, children}: OptionsListProviderProp }); const personalDetails = usePersonalDetails(); - const prevPersonalDetails = useRef(personalDetails); + const prevPersonalDetails = usePrevious(personalDetails); const prevReports = usePrevious(reports); /** @@ -121,7 +121,7 @@ function OptionsListContextProvider({reports, children}: OptionsListProviderProp }> = []; Object.keys(personalDetails).forEach((accoutID) => { - const prevPersonalDetail = prevPersonalDetails.current?.[accoutID]; + const prevPersonalDetail = prevPersonalDetails?.[accoutID]; const personalDetail = personalDetails?.[accoutID]; if (isEqualPersonalDetail(prevPersonalDetail, personalDetail)) { @@ -153,7 +153,6 @@ function OptionsListContextProvider({reports, children}: OptionsListProviderProp return newOptions; }); - prevPersonalDetails.current = personalDetails; // eslint-disable-next-line react-hooks/exhaustive-deps }, [personalDetails]); From 4d872eb62691bc0f30b5ec0f9605e9a2926691b1 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 17 Apr 2024 10:10:13 +0200 Subject: [PATCH 173/647] Minor improvements --- src/libs/PolicyUtils.ts | 2 +- src/libs/actions/IOU.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 35abc7258d45..48e0032da8cd 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -11,8 +11,8 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; import getPolicyIDFromState from './Navigation/getPolicyIDFromState'; import Navigation, {navigationRef} from './Navigation/Navigation'; import type {RootStackParamList, State} from './Navigation/types'; -import {getPersonalDetailByEmail} from './PersonalDetailsUtils'; import * as NetworkStore from './Network/NetworkStore'; +import {getPersonalDetailByEmail} from './PersonalDetailsUtils'; type MemberEmailsToAccountIDs = Record; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index ab39072a56b8..9916afbf3a96 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -856,7 +856,7 @@ function buildOnyxDataForInvoice( optimisticPolicyRecentlyUsedTags: OnyxTypes.RecentlyUsedTags, isNewChatReport: boolean, transactionThreadReport: OptimisticChatReport, - transactionThreadCreatedReportAction: OptimisticCreatedReportAction, + transactionThreadCreatedReportAction: OptimisticCreatedReportAction | EmptyObject, inviteReportAction?: OptimisticInviteReportAction, policy?: OnyxEntry, policyTagList?: OnyxEntry, From a9d6b4bd5ad26334c9645abe424f70ed7b94e33e Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 17 Apr 2024 16:35:14 +0700 Subject: [PATCH 174/647] fix add custom icon right button --- src/components/Button/index.tsx | 20 ++++++++++++------- .../ButtonWithDropdownMenu/index.tsx | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 68f1aac41a5b..25a9fcbc215a 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -115,6 +115,9 @@ type ButtonProps = Partial & { /** Boolean whether to display the right icon */ shouldShowRightIcon?: boolean; + + /** The custom icon to display to the right of the text */ + customRightIcon?: React.ReactNode; }; type KeyboardShortcutComponentProps = Pick; @@ -198,6 +201,7 @@ function Button( id = '', accessibilityLabel = '', + customRightIcon, ...rest }: ButtonProps, ref: ForwardedRef, @@ -253,13 +257,15 @@ function Button( {shouldShowRightIcon && ( - + {customRightIcon ?? ( + + )} )} diff --git a/src/components/ButtonWithDropdownMenu/index.tsx b/src/components/ButtonWithDropdownMenu/index.tsx index 9c2ab4b36734..16c9804f79d0 100644 --- a/src/components/ButtonWithDropdownMenu/index.tsx +++ b/src/components/ButtonWithDropdownMenu/index.tsx @@ -98,7 +98,7 @@ function ButtonWithDropdownMenu({ medium={!isButtonSizeLarge} innerStyles={[innerStyleDropButton, !isSplit && styles.dropDownButtonCartIconView]} enterKeyEventListenerPriority={enterKeyEventListenerPriority} - iconRight={getIconRightButton} + customRightIcon={getIconRightButton()} shouldShowRightIcon={!isSplit} /> From 23dd180b89f7b656b5c2138b49bac1c0a1a4c3ac Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 11:48:47 +0200 Subject: [PATCH 175/647] improve getInvoicesChatName --- src/libs/ReportUtils.ts | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 28edbd79a9ba..4f50562de698 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3011,26 +3011,20 @@ function getInvoicesChatName(report: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; const isIndividual = invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : -1; - const policyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; - let isReceiver = false; - - if (isIndividual && invoiceReceiverAccountID === currentUserAccountID) { - isReceiver = true; - } + const invoiceReceiverPolicyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; + const isCurrentUserReceiver = + (isIndividual && invoiceReceiverAccountID === currentUserAccountID) || (!isIndividual && PolicyUtils.isPolicyEmployee(invoiceReceiverPolicyID, allPolicies)); - if (!isIndividual && PolicyUtils.isPolicyEmployee(policyID, allPolicies)) { - isReceiver = true; - } - - if (isReceiver) { - return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]); + if (isCurrentUserReceiver) { + return getPolicyName(report); } if (isIndividual) { return PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[invoiceReceiverAccountID]); } - return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]); + // TODO: Check this flow in a scope of the Invoice V0.3 + return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiverPolicyID}`]); } /** From 12bd9c484f46510441726eb5626cb974153b6f5b Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 11:52:24 +0200 Subject: [PATCH 176/647] add comment --- src/libs/ReportUtils.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 4f50562de698..8a0b1d43adf6 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2977,6 +2977,11 @@ function getAdminRoomInvitedParticipants(parentReportAction: ReportAction | Reco return roomName ? `${verb} ${users} ${preposition} ${roomName}` : `${verb} ${users}`; } +/** + * Get the invoice payer name based on its type: + * - Individual - a receiver display name. + * - Policy - a receiver policy name. + */ function getInvoicePayerName(report: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; const isIndividual = invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; From 7b711429ea0a4ff3a965b531a7097aacd3e945bc Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 11:53:03 +0200 Subject: [PATCH 177/647] Revert "integrate deleted invoice message" This reverts commit 4e81a6c86a7a00ffbaa7692f17cf82d29f0cfb24. --- src/components/ReportActionItem/MoneyRequestAction.tsx | 2 -- src/languages/en.ts | 1 - src/languages/es.ts | 1 - src/pages/home/report/ReportActionItem.tsx | 2 -- 4 files changed, 6 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index 9bef637bf292..7d9ba2697c7a 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -116,8 +116,6 @@ function MoneyRequestAction({ message = 'parentReportAction.reversedTransaction'; } else if (isTrackExpenseAction) { message = 'parentReportAction.deletedExpense'; - } else if (action.childType === CONST.REPORT.TYPE.INVOICE) { - message = 'parentReportAction.deletedInvoice'; } else { message = 'parentReportAction.deletedRequest'; } diff --git a/src/languages/en.ts b/src/languages/en.ts index b6de47a3aaf0..05313113c521 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2541,7 +2541,6 @@ export default { reversedTransaction: '[Reversed transaction]', deletedTask: '[Deleted task]', hiddenMessage: '[Hidden message]', - deletedInvoice: '[Deleted invoice]', }, threads: { thread: 'Thread', diff --git a/src/languages/es.ts b/src/languages/es.ts index 6b963a874599..f99eaa4eef10 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3033,7 +3033,6 @@ export default { reversedTransaction: '[Transacción anulada]', deletedTask: '[Tarea eliminada]', hiddenMessage: '[Mensaje oculto]', - deletedInvoice: '[Factura eliminada]', }, threads: { thread: 'Hilo', diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 06e222d86928..6a6eca9fb734 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -738,8 +738,6 @@ function ReportActionItem({ message = 'parentReportAction.reversedTransaction'; } else if (ReportActionsUtils.isTrackExpenseAction(parentReportAction)) { message = 'parentReportAction.deletedExpense'; - } else if (parentReportAction?.childType === CONST.REPORT.TYPE.INVOICE) { - message = 'parentReportAction.deletedInvoice'; } else { message = 'parentReportAction.deletedRequest'; } From 78f5f642773d697474736dbe2cbcf4eea6384d8b Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 11:54:01 +0200 Subject: [PATCH 178/647] Revert "integrate deleted invoice message in getTransactionReportName" This reverts commit 19b54e34b25b9d9ddcb7c27ae4b237cdcac744ff. --- src/libs/ReportUtils.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8a0b1d43adf6..4f8a16246d61 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -191,9 +191,6 @@ type OptimisticIOUReportAction = Pick< | 'receipt' | 'whisperedToAccountIDs' | 'childReportID' - | 'childType' - | 'childVisibleActionCount' - | 'childCommenterCount' >; type ReportRouteParams = { @@ -2691,10 +2688,6 @@ function getTransactionReportName(reportAction: OnyxEntry Date: Wed, 17 Apr 2024 11:54:30 +0200 Subject: [PATCH 179/647] clear redundant participant props --- src/types/onyx/Report.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 2bad31bc372c..36f124a4b826 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -26,9 +26,6 @@ type PendingChatMember = { type Participant = { hidden?: boolean; role?: 'admin' | 'member'; - // TODO: Confirm - type?: 'policy' | 'individual'; - policyID?: string; }; type Participants = Record; From 75f8b2386c9056ec06bd781bb00d61b3afa4ffab Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 17 Apr 2024 11:58:26 +0200 Subject: [PATCH 180/647] Update optimistic invoice room creation to include current user as a member --- src/libs/actions/IOU.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 9916afbf3a96..130a43a79319 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1619,6 +1619,7 @@ function getDeleteTrackExpenseInformation( /** Gathers all the data needed to create an invoice. */ function getSendInvoiceInformation( transaction: OnyxEntry, + currentUserAccountID: number, invoiceChatReport?: OnyxEntry, receipt?: Receipt, policy?: OnyxEntry, @@ -1641,7 +1642,7 @@ function getSendInvoiceInformation( if (!chatReport) { isNewChatReport = true; - chatReport = ReportUtils.buildOptimisticChatReport([receiverAccountID], CONST.REPORT.DEFAULT_REPORT_NAME, CONST.REPORT.CHAT_TYPE.INVOICE, senderWorkspaceID); + chatReport = ReportUtils.buildOptimisticChatReport([receiverAccountID, currentUserAccountID], CONST.REPORT.DEFAULT_REPORT_NAME, CONST.REPORT.CHAT_TYPE.INVOICE, senderWorkspaceID); } // STEP 3: Create a new optimistic invoice report. @@ -3341,7 +3342,7 @@ function sendInvoice( optimisticTransactionID, optimisticTransactionThreadReportID, onyxData, - } = getSendInvoiceInformation(transaction, invoiceChatReport, receiptFile, policy, policyTagList, policyCategories); + } = getSendInvoiceInformation(transaction, currentUserAccountID, invoiceChatReport, receiptFile, policy, policyTagList, policyCategories); let parameters: SendInvoiceParams = { senderWorkspaceID, From 5625582f11ab9b8a4c136651fce4319b09ad6689 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 12:16:41 +0200 Subject: [PATCH 181/647] sync changes --- src/CONST.ts | 3 ++- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- src/libs/ReportUtils.ts | 32 +++++++++++++++++--------------- src/types/onyx/Report.ts | 23 +++++++++++++---------- 5 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 0dbf26851ead..95056970457b 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -52,7 +52,7 @@ const chatTypes = { POLICY_ROOM: 'policyRoom', POLICY_EXPENSE_CHAT: 'policyExpenseChat', SELF_DM: 'selfDM', - INVOICE: 'invoiceRoom', + INVOICE: 'invoice', } as const; // Explicit type annotation is required @@ -1423,6 +1423,7 @@ const CONST = { SPLIT: 'split', REQUEST: 'request', TRACK_EXPENSE: 'track-expense', + INVOICE: 'invoice', }, REQUEST_TYPE: { DISTANCE: 'distance', diff --git a/src/languages/en.ts b/src/languages/en.ts index 05313113c521..f9c845b42a5a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -505,6 +505,7 @@ export default { beginningOfChatHistoryAnnounceRoomPartTwo: ({workspaceName}: BeginningOfChatHistoryAnnounceRoomPartTwo) => ` to chat about anything ${workspaceName} related.`, beginningOfChatHistoryUserRoomPartOne: 'Collaboration starts here! 🎉\nUse this space to chat about anything ', beginningOfChatHistoryUserRoomPartTwo: ' related.', + beginningOfChatHistoryInvoiceRoom: 'Collaboration starts here! 🎉 Use this room to view, discuss, and pay invoices.', beginningOfChatHistory: 'This is the beginning of your chat with ', beginningOfChatHistoryPolicyExpenseChatPartOne: 'Collaboration between ', beginningOfChatHistoryPolicyExpenseChatPartTwo: ' and ', @@ -522,7 +523,6 @@ export default { // eslint-disable-next-line @typescript-eslint/naming-convention 'track-expense': 'track an expense', }, - beginningOfChatHistoryInvoiceRoom: 'Collaboration starts here! 🎉 \nUse this room to view, discuss, and pay invoices.', }, reportAction: { asCopilot: 'as copilot for', diff --git a/src/languages/es.ts b/src/languages/es.ts index f99eaa4eef10..568e14e6b241 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -501,6 +501,7 @@ export default { beginningOfChatHistoryAnnounceRoomPartTwo: ({workspaceName}: BeginningOfChatHistoryAnnounceRoomPartTwo) => ` para chatear sobre cualquier cosa relacionada con ${workspaceName}.`, beginningOfChatHistoryUserRoomPartOne: '¡Este es el lugar para colaborar! 🎉\nUsa este espacio para chatear sobre cualquier cosa relacionada con ', beginningOfChatHistoryUserRoomPartTwo: '.', + beginningOfChatHistoryInvoiceRoom: '¡Este es el lugar para colaborar! 🎉 Utilice esta sala para ver, discutir y pagar facturas.', beginningOfChatHistory: 'Aquí comienzan tus conversaciones con ', beginningOfChatHistoryPolicyExpenseChatPartOne: '¡La colaboración entre ', beginningOfChatHistoryPolicyExpenseChatPartTwo: ' y ', @@ -518,7 +519,6 @@ export default { // eslint-disable-next-line @typescript-eslint/naming-convention 'track-expense': 'rastrear un gasto', }, - beginningOfChatHistoryInvoiceRoom: '¡Este es el lugar para colaborar! 🎉\nUsa esta sala para ver, discutir y pagar facturas.', }, reportAction: { asCopilot: 'como copiloto de', diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 4f8a16246d61..76693877b462 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -141,6 +141,7 @@ type OptimisticAddCommentReportAction = Pick< | 'childStatusNum' | 'childStateNum' | 'errors' + | 'whisperedToAccountIDs' > & {isOptimisticAction: boolean}; type OptimisticReportAction = { @@ -283,6 +284,7 @@ type OptimisticChatReport = Pick< | 'description' | 'writeCapability' | 'avatarUrl' + | 'invoiceReceiver' > & { isOptimisticReport: true; }; @@ -675,6 +677,13 @@ function isChatReport(report: OnyxEntry | EmptyObject): boolean { return report?.type === CONST.REPORT.TYPE.CHAT; } +/** + * Checks if a report is an invoice report. + */ +function isInvoiceReport(report: OnyxEntry | EmptyObject): boolean { + return report?.type === CONST.REPORT.TYPE.INVOICE; +} + /** * Checks if a report is an Expense report. */ @@ -862,6 +871,13 @@ function isPolicyExpenseChat(report: OnyxEntry | Participant | EmptyObje return getChatType(report) === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT || (report?.isPolicyExpenseChat ?? false); } +/** + * Whether the provided report is an invoice room chat. + */ +function isInvoiceRoom(report: OnyxEntry): boolean { + return getChatType(report) === CONST.REPORT.CHAT_TYPE.INVOICE; +} + /** * Whether the provided report belongs to a Control policy and is an expense chat */ @@ -906,20 +922,6 @@ function isPaidGroupPolicyExpenseReport(report: OnyxEntry): boolean { return isExpenseReport(report) && isPaidGroupPolicy(report); } -/** - * Check if Report is an invoice room - */ -function isInvoiceRoom(report: OnyxEntry): boolean { - return getChatType(report) === CONST.REPORT.CHAT_TYPE.INVOICE; -} - -/** - * Check if Report is an invoice report - */ -function isInvoiceReport(report: OnyxEntry | EmptyObject): boolean { - return report?.type === CONST.REPORT.TYPE.INVOICE; -} - /** * Checks if the supplied report is an invoice report in Open state and status. */ @@ -2218,7 +2220,7 @@ function hasNonReimbursableTransactions(iouReportID: string | undefined): boolea function getMoneyRequestSpendBreakdown(report: OnyxEntry, allReportsDict: OnyxCollection = null): SpendBreakdown { const allAvailableReports = allReportsDict ?? allReports; let moneyRequestReport; - if (isMoneyRequestReport(report)) { + if (isMoneyRequestReport(report) || isInvoiceReport(report)) { moneyRequestReport = report; } if (allAvailableReports && report?.iouReportID) { diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 36f124a4b826..344b7df5b2eb 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -28,6 +28,16 @@ type Participant = { role?: 'admin' | 'member'; }; +type InvoiceReceiver = + | { + type: 'individual'; + accountID: number; + } + | { + type: 'policy'; + policyID: string; + }; + type Participants = Record; type Report = OnyxCommon.OnyxValueWithOfflineFeedback< @@ -128,6 +138,9 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< /** Report cached total */ cachedTotal?: string; + /** Invoice room receiver data */ + invoiceReceiver?: InvoiceReceiver; + lastMessageTranslationKey?: string; parentReportID?: string; parentReportActionID?: string; @@ -186,16 +199,6 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< transactionThreadReportID?: string; fieldList?: Record; - - invoiceReceiver?: - | { - type: typeof CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; - accountID: number; - } - | { - type: typeof CONST.INVOICE_RECEIVER_TYPE.POLICY; - policyID: string; - }; }, PolicyReportField['fieldID'] >; From 1b92e52f10d874af91ab346590e315aa640be244 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 12:18:30 +0200 Subject: [PATCH 182/647] update invoice receiver const --- src/CONST.ts | 9 ++++----- src/libs/ReportUtils.ts | 10 +++++----- src/libs/actions/IOU.ts | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 95056970457b..6467769263dd 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -839,6 +839,10 @@ const CONST = { OWNER_EMAIL_FAKE: '__FAKE__', OWNER_ACCOUNT_ID_FAKE: 0, DEFAULT_REPORT_NAME: 'Chat Report', + INVOICE_RECEIVER_TYPE: { + INDIVIDUAL: 'individual', + BUSINESS: 'policy', + }, }, NEXT_STEP: { FINISHED: 'Finished!', @@ -4353,11 +4357,6 @@ const CONST = { MAX_TAX_RATE_INTEGER_PLACES: 4, MAX_TAX_RATE_DECIMAL_PLACES: 4, - - INVOICE_RECEIVER_TYPE: { - INDIVIDUAL: 'individual', - POLICY: 'policy', - }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 76693877b462..b2a1fd53afeb 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1971,7 +1971,7 @@ function getIcons( const invoiceRoomReport = getReport(report.chatReportID); const icons = [getWorkspaceIcon(invoiceRoomReport, policy)]; - if (invoiceRoomReport?.invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { + if (invoiceRoomReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { icons.push(...getIconsForParticipants([invoiceRoomReport?.invoiceReceiver.accountID], personalDetails)); return icons; @@ -2979,7 +2979,7 @@ function getAdminRoomInvitedParticipants(parentReportAction: ReportAction | Reco */ function getInvoicePayerName(report: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; - const isIndividual = invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; + const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; if (isIndividual) { return PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[invoiceReceiver.accountID]); @@ -3009,7 +3009,7 @@ function getReportActionMessage(reportAction: ReportAction | EmptyObject, parent */ function getInvoicesChatName(report: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; - const isIndividual = invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; + const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : -1; const invoiceReceiverPolicyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; const isCurrentUserReceiver = @@ -3032,7 +3032,7 @@ function getInvoicesChatName(report: OnyxEntry): string { */ function getInvoicesChatSubtitle(report: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; - const isIndividual = invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; + const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : -1; const policyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; let isReceiver = false; @@ -5236,7 +5236,7 @@ function canLeaveRoom(report: OnyxEntry, isPolicyEmployee: boolean): boo } const isReceiverPolicyAdmin = - report?.invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.POLICY ? getPolicy(report?.invoiceReceiver?.policyID)?.role === CONST.POLICY.ROLE.ADMIN : false; + report?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS ? getPolicy(report?.invoiceReceiver?.policyID)?.role === CONST.POLICY.ROLE.ADMIN : false; if (isReceiverPolicyAdmin) { return false; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 947a800f5ee5..ae93ec7413ac 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5328,7 +5328,7 @@ function canIOUBePaid(iouReport: OnyxEntry | EmptyObject, chat } if (ReportUtils.isInvoiceReport(iouReport)) { - if (chatReport?.invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { + if (chatReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { return chatReport?.invoiceReceiver?.accountID === userAccountID; } From fb69172a697a9892cc07f7a6a80581f3f3ee99e6 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 12:22:27 +0200 Subject: [PATCH 183/647] improve getInvoicesChatSubtitle --- src/libs/ReportUtils.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b2a1fd53afeb..efd51ef4af99 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3034,18 +3034,11 @@ function getInvoicesChatSubtitle(report: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : -1; - const policyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; - let isReceiver = false; - - if (isIndividual && invoiceReceiverAccountID === currentUserAccountID) { - isReceiver = true; - } - - if (!isIndividual && PolicyUtils.isPolicyEmployee(policyID, allPolicies)) { - isReceiver = true; - } + const invoiceReceiverPolicyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; + const isCurrentUserReceiver = + (isIndividual && invoiceReceiverAccountID === currentUserAccountID) || (!isIndividual && PolicyUtils.isPolicyEmployee(invoiceReceiverPolicyID, allPolicies)); - if (isReceiver) { + if (isCurrentUserReceiver) { let receiver = ''; if (isIndividual) { @@ -3057,7 +3050,8 @@ function getInvoicesChatSubtitle(report: OnyxEntry): string { return Localize.translateLocal('workspace.invoices.invoicesTo', {receiver}); } - return Localize.translateLocal('workspace.invoices.invoicesFrom', {sender: getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`])}); + // TODO: Check this flow in a scope of the Invoice V0.3 + return Localize.translateLocal('workspace.invoices.invoicesFrom', {sender: getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? ''}`])}); } /** From 2610f14920e41ec6a9b067bab9c9c4dab4c7fa77 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 12:25:59 +0200 Subject: [PATCH 184/647] revert extra changes --- src/libs/ReportUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index efd51ef4af99..f41b310c2f2e 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -141,7 +141,6 @@ type OptimisticAddCommentReportAction = Pick< | 'childStatusNum' | 'childStateNum' | 'errors' - | 'whisperedToAccountIDs' > & {isOptimisticAction: boolean}; type OptimisticReportAction = { @@ -192,6 +191,8 @@ type OptimisticIOUReportAction = Pick< | 'receipt' | 'whisperedToAccountIDs' | 'childReportID' + | 'childVisibleActionCount' + | 'childCommenterCount' >; type ReportRouteParams = { From b8e9d4368a6d2e90e3114cd8ea291d469a11330f Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 17 Apr 2024 12:31:31 +0200 Subject: [PATCH 185/647] feat: add proper copies --- src/languages/en.ts | 6 ++++++ src/languages/es.ts | 6 ++++++ .../EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx | 2 +- src/pages/EnablePayments/PersonalInfo/substeps/FullName.tsx | 2 +- .../PersonalInfo/substeps/SocialSecurityNumber.tsx | 4 ++-- .../utils/getInitialSubstepForPersonalInfo.ts | 4 ++-- 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 495ec8426ea8..bc4192735cea 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1702,6 +1702,12 @@ export default { address: 'Address', letsDoubleCheck: "Let's double check that everything looks right.", byAddingThisBankAccount: 'By adding this bank account, you confirm that you have read, understand and accept', + whatsYourLegalName: 'What’s your legal name?', + whatsYourDOB: 'What’s your date of birth?', + whatsYourAddress: 'What’s your address?', + noPOBoxesPlease: 'No PO boxes or mail-drop addresses, please.', + whatsYourSSN: 'What are the last four digits of your Social Security Number?', + noPersonalChecks: 'Don’t worry, no personal credit checks here!', whatsYourPhoneNumber: 'What’s your phone number?', weNeedThisToVerify: 'We need this to verify your wallet.', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 74076c22393f..37c51d2148aa 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1727,6 +1727,12 @@ export default { address: 'Dirección', letsDoubleCheck: 'Revisemos que todo esté bien', byAddingThisBankAccount: 'Añadiendo esta cuenta bancaria, confirmas que has leído, entendido y aceptado', + whatsYourLegalName: '¿Cuál es tu nombre legal?', + whatsYourDOB: '¿Cuál es tu fecha de nacimiento?', + whatsYourAddress: '¿Cuál es tu dirección?', + noPOBoxesPlease: 'Nada de apartados de correos ni direcciones de envío, por favor.', + whatsYourSSN: '¿Cuáles son los últimos 4 dígitos de tu número de la seguridad social?', + noPersonalChecks: 'No te preocupes, no hacemos verificaciones de crédito personales.', whatsYourPhoneNumber: '¿Cuál es tu número de teléfono?', weNeedThisToVerify: 'Necesitamos esto para verificar tu billetera.', }, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx index a42b9c76d1a9..f83edb706686 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx @@ -67,7 +67,7 @@ function DateOfBirth({walletAdditionalDetails, onNext, isEditing}: DateOfBirthPr style={[styles.mh5, styles.flexGrow2, styles.justifyContentBetween]} submitButtonStyles={[styles.pb5, styles.mb0]} > - {translate('personalInfoStep.enterYourDateOfBirth')} + {translate('personalInfoStep.whatsYourDOB')} - {translate('personalInfoStep.enterYourLegalFirstAndLast')} + {translate('personalInfoStep.whatsYourLegalName')} - {translate('personalInfoStep.enterTheLast4')} - {translate('personalInfoStep.dontWorry')} + {translate('personalInfoStep.whatsYourSSN')} + {translate('personalInfoStep.noPersonalChecks')} Date: Wed, 17 Apr 2024 12:32:51 +0200 Subject: [PATCH 186/647] simplify invoice room subtitle --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index f41b310c2f2e..417cfdc09bf9 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3149,7 +3149,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu */ function getChatRoomSubtitle(report: OnyxEntry): string | undefined { if (isInvoiceRoom(report)) { - return getInvoicesChatSubtitle(report); + return Localize.translateLocal('workspace.common.invoices'); } if (isChatThread(report)) { return ''; From 04ee7b1fb55945358135fcb907271a11a34be45e Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 12:33:55 +0200 Subject: [PATCH 187/647] Revert "integrate report subtitle for invoice room" This reverts commit a524346f9402a98b9b6900a167c9ca15a1569078. --- src/languages/en.ts | 4 ---- src/languages/es.ts | 4 ---- src/languages/types.ts | 6 ------ src/libs/ReportUtils.ts | 31 +++---------------------------- 4 files changed, 3 insertions(+), 42 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index f9c845b42a5a..d2f76ee4ed44 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -30,8 +30,6 @@ import type { GoBackMessageParams, GoToRoomParams, InstantSummaryParams, - InvoicesFromParams, - InvoicesToParams, LocalTimeParams, LoggedInAsParams, LogSizeParams, @@ -2148,8 +2146,6 @@ export default { unlockVBACopy: "You're all set to accept payments by ACH or credit card!", viewUnpaidInvoices: 'View unpaid invoices', sendInvoice: 'Send invoice', - invoicesFrom: ({sender}: InvoicesFromParams) => `Invoices from ${sender}`, - invoicesTo: ({receiver}: InvoicesToParams) => `Invoices to ${receiver}`, }, travel: { unlockConciergeBookingTravel: 'Unlock Concierge travel booking', diff --git a/src/languages/es.ts b/src/languages/es.ts index 568e14e6b241..141e3ad3db91 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -29,8 +29,6 @@ import type { GoBackMessageParams, GoToRoomParams, InstantSummaryParams, - InvoicesFromParams, - InvoicesToParams, LocalTimeParams, LoggedInAsParams, LogSizeParams, @@ -2176,8 +2174,6 @@ export default { unlockVBACopy: '¡Todo listo para recibir pagos por transferencia o con tarjeta!', viewUnpaidInvoices: 'Ver facturas emitidas pendientes', sendInvoice: 'Enviar factura', - invoicesFrom: ({sender}: InvoicesFromParams) => `Facturas de ${sender}`, - invoicesTo: ({receiver}: InvoicesToParams) => `Facturas a ${receiver}`, }, travel: { unlockConciergeBookingTravel: 'Desbloquea la reserva de viajes con Concierge', diff --git a/src/languages/types.ts b/src/languages/types.ts index 8c4d287b7dfc..59e1bfe40af2 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -251,10 +251,6 @@ type ViolationsTaxOutOfPolicyParams = {taxName?: string}; type TaskCreatedActionParams = {title: string}; -type InvoicesFromParams = {sender: string}; - -type InvoicesToParams = {receiver: string}; - /* Translation Object types */ // eslint-disable-next-line @typescript-eslint/no-explicit-any type TranslationBaseValue = string | string[] | ((...args: any[]) => string); @@ -407,6 +403,4 @@ export type { ZipCodeExampleFormatParams, LogSizeParams, HeldRequestParams, - InvoicesFromParams, - InvoicesToParams, }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 417cfdc09bf9..0ba38d8f91de 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3028,33 +3028,6 @@ function getInvoicesChatName(report: OnyxEntry): string { return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiverPolicyID}`]); } -/** - * Get the subtitle for an invoice room. - */ -function getInvoicesChatSubtitle(report: OnyxEntry): string { - const invoiceReceiver = report?.invoiceReceiver; - const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; - const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : -1; - const invoiceReceiverPolicyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; - const isCurrentUserReceiver = - (isIndividual && invoiceReceiverAccountID === currentUserAccountID) || (!isIndividual && PolicyUtils.isPolicyEmployee(invoiceReceiverPolicyID, allPolicies)); - - if (isCurrentUserReceiver) { - let receiver = ''; - - if (isIndividual) { - receiver = PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[invoiceReceiverAccountID]); - } else { - receiver = getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiver?.policyID}`]); - } - - return Localize.translateLocal('workspace.invoices.invoicesTo', {receiver}); - } - - // TODO: Check this flow in a scope of the Invoice V0.3 - return Localize.translateLocal('workspace.invoices.invoicesFrom', {sender: getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? ''}`])}); -} - /** * Get the title for a report. */ @@ -3167,6 +3140,9 @@ function getChatRoomSubtitle(report: OnyxEntry): string | undefined { if (isArchivedRoom(report)) { return report?.oldPolicyName ?? ''; } + if (isInvoiceRoom(report)) { + return Localize.translateLocal('workspace.common.invoices'); + } return getPolicyName(report); } @@ -6208,7 +6184,6 @@ export { getDisplayNamesWithTooltips, getInvoicePayerName, getInvoicesChatName, - getInvoicesChatSubtitle, getReportName, getReport, getReportNotificationPreference, From c135183a053761716647812c8686e1fe6c81295d Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 17 Apr 2024 13:18:35 +0200 Subject: [PATCH 188/647] refactor: make useStepSubmit generic --- .../useReimbursementAccountStepFormSubmit.ts | 39 +++++------------- src/hooks/useStepFormSubmit.ts | 40 +++++++++++++++++++ ...seWalletAdditionalDetailsStepFormSubmit.ts | 27 +++++++++++++ .../PersonalInfo/substeps/Address.tsx | 5 +-- .../PersonalInfo/substeps/DateOfBirth.tsx | 5 +-- .../PersonalInfo/substeps/FullName.tsx | 5 +-- .../PersonalInfo/substeps/PhoneNumber.tsx | 5 +-- .../substeps/SocialSecurityNumber.tsx | 5 +-- 8 files changed, 87 insertions(+), 44 deletions(-) create mode 100644 src/hooks/useStepFormSubmit.ts create mode 100644 src/hooks/useWalletAdditionalDetailsStepFormSubmit.ts diff --git a/src/hooks/useReimbursementAccountStepFormSubmit.ts b/src/hooks/useReimbursementAccountStepFormSubmit.ts index 85a9b21b4d8d..bd17bfaa1234 100644 --- a/src/hooks/useReimbursementAccountStepFormSubmit.ts +++ b/src/hooks/useReimbursementAccountStepFormSubmit.ts @@ -1,46 +1,27 @@ -import {useCallback} from 'react'; -import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; -import * as FormActions from '@userActions/FormActions'; +import type {FormOnyxKeys} from '@components/Form/types'; +import useStepFormSubmit from '@hooks/useStepFormSubmit'; import type {OnyxFormKey} from '@src/ONYXKEYS'; import ONYXKEYS from '@src/ONYXKEYS'; import type {SubStepProps} from './useSubStep/types'; type UseReimbursementAccountStepFormSubmitParams = Pick & { formId?: OnyxFormKey; - fieldIds: Array>; + fieldIds: Array>; shouldSaveDraft: boolean; }; /** * Hook for handling submit method in ReimbursementAccount substeps. * When user is in editing mode we should save values only when user confirm that - * @param formId - ID for particular form * @param onNext - callback * @param fieldIds - field IDs for particular step * @param shouldSaveDraft - if we should save draft values */ -export default function useReimbursementAccountStepFormSubmit({ - formId = ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, - onNext, - fieldIds, - shouldSaveDraft, -}: UseReimbursementAccountStepFormSubmitParams) { - return useCallback( - (values: FormOnyxValues) => { - if (shouldSaveDraft) { - const stepValues = fieldIds.reduce( - (acc, key) => ({ - ...acc, - [key]: values[key], - }), - {}, - ); - - FormActions.setDraftValues(formId, stepValues); - } - - onNext(); - }, - [onNext, formId, fieldIds, shouldSaveDraft], - ); +export default function useReimbursementAccountStepFormSubmit({onNext, fieldIds, shouldSaveDraft}: UseReimbursementAccountStepFormSubmitParams) { + return useStepFormSubmit({ + formId: ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, + onNext, + fieldIds, + shouldSaveDraft, + }); } diff --git a/src/hooks/useStepFormSubmit.ts b/src/hooks/useStepFormSubmit.ts new file mode 100644 index 000000000000..86d754bf2fc9 --- /dev/null +++ b/src/hooks/useStepFormSubmit.ts @@ -0,0 +1,40 @@ +import {useCallback} from 'react'; +import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; +import * as FormActions from '@userActions/FormActions'; +import type {OnyxFormKey, OnyxFormValuesMapping} from '@src/ONYXKEYS'; +import type {SubStepProps} from './useSubStep/types'; + +type UseStepFormSubmitParams = Pick & { + formId: OnyxFormKey; + fieldIds: Array>; + shouldSaveDraft: boolean; +}; + +/** + * Hook for handling submit method in substeps. + * When user is in editing mode we should save values only when user confirm that + * @param formId - ID for particular form + * @param onNext - callback + * @param fieldIds - field IDs for particular step + * @param shouldSaveDraft - if we should save draft values + */ +export default function useStepFormSubmit({formId, onNext, fieldIds, shouldSaveDraft}: UseStepFormSubmitParams) { + return useCallback( + (values: FormOnyxValues) => { + if (shouldSaveDraft) { + const stepValues = fieldIds.reduce( + (acc, key) => ({ + ...acc, + [key]: values[key], + }), + {}, + ); + + FormActions.setDraftValues(formId, stepValues); + } + + onNext(); + }, + [onNext, formId, fieldIds, shouldSaveDraft], + ); +} diff --git a/src/hooks/useWalletAdditionalDetailsStepFormSubmit.ts b/src/hooks/useWalletAdditionalDetailsStepFormSubmit.ts new file mode 100644 index 000000000000..2ca079a506cc --- /dev/null +++ b/src/hooks/useWalletAdditionalDetailsStepFormSubmit.ts @@ -0,0 +1,27 @@ +import type {FormOnyxKeys} from '@components/Form/types'; +import useStepFormSubmit from '@hooks/useStepFormSubmit'; +import type {OnyxFormKey} from '@src/ONYXKEYS'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {SubStepProps} from './useSubStep/types'; + +type UseWalletAdditionalDetailsStepFormSubmitParams = Pick & { + formId?: OnyxFormKey; + fieldIds: Array>; + shouldSaveDraft: boolean; +}; + +/** + * Hook for handling submit method in WalletAdditionalDetails substeps. + * When user is in editing mode we should save values only when user confirm that + * @param onNext - callback + * @param fieldIds - field IDs for particular step + * @param shouldSaveDraft - if we should save draft values + */ +export default function useWalletAdditionalDetailsStepFormSubmit({onNext, fieldIds, shouldSaveDraft}: UseWalletAdditionalDetailsStepFormSubmitParams) { + return useStepFormSubmit({ + formId: ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, + onNext, + fieldIds, + shouldSaveDraft, + }); +} diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/Address.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/Address.tsx index 445cf11282b1..d63dd7c08f01 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/Address.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/Address.tsx @@ -6,9 +6,9 @@ import FormProvider from '@components/Form/FormProvider'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; -import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; import * as ValidationUtils from '@libs/ValidationUtils'; import AddressFormFields from '@pages/ReimbursementAccount/AddressFormFields'; import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; @@ -59,8 +59,7 @@ function Address({walletAdditionalDetails, onNext, isEditing}: AddressProps) { zipCode: walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.ZIP_CODE] ?? '', }; - const handleSubmit = useReimbursementAccountStepFormSubmit({ - formId: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, + const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: isEditing, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx index f83edb706686..42cfe4ca11de 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx @@ -8,9 +8,9 @@ import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; -import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; import * as ValidationUtils from '@libs/ValidationUtils'; import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; @@ -51,8 +51,7 @@ function DateOfBirth({walletAdditionalDetails, onNext, isEditing}: DateOfBirthPr const dobDefaultValue = walletAdditionalDetails?.[PERSONAL_INFO_DOB_KEY] ?? walletAdditionalDetails?.[PERSONAL_INFO_DOB_KEY] ?? ''; - const handleSubmit = useReimbursementAccountStepFormSubmit({ - formId: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, + const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: isEditing, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/FullName.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/FullName.tsx index 32bd1537ce3b..6dbe855b2721 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/FullName.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/FullName.tsx @@ -8,9 +8,9 @@ import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; -import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; import * as ValidationUtils from '@libs/ValidationUtils'; import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; @@ -49,8 +49,7 @@ function FullName({walletAdditionalDetails, onNext, isEditing}: FullNameProps) { lastName: walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.LAST_NAME] ?? '', }; - const handleSubmit = useReimbursementAccountStepFormSubmit({ - formId: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, + const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: isEditing, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumber.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumber.tsx index b465efe6fe79..9b5f8cf0a611 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumber.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumber.tsx @@ -8,9 +8,9 @@ import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; -import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; import * as ValidationUtils from '@libs/ValidationUtils'; import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; @@ -42,8 +42,7 @@ function PhoneNumber({walletAdditionalDetails, onNext, isEditing}: PhoneNumberPr const defaultPhoneNumber = walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.PHONE_NUMBER] ?? ''; - const handleSubmit = useReimbursementAccountStepFormSubmit({ - formId: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, + const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: isEditing, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumber.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumber.tsx index fe23f4b9cbea..de9c9ce25938 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumber.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumber.tsx @@ -8,9 +8,9 @@ import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; -import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; import * as ValidationUtils from '@libs/ValidationUtils'; import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; @@ -43,8 +43,7 @@ function SocialSecurityNumber({walletAdditionalDetails, onNext, isEditing}: Soci const defaultSsnLast4 = walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.SSN_LAST_4] ?? ''; - const handleSubmit = useReimbursementAccountStepFormSubmit({ - formId: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, + const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: isEditing, From 74260e0750b612f6786a5ce585a5630b92fd76ea Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Wed, 17 Apr 2024 13:31:19 +0200 Subject: [PATCH 189/647] make connect to integrations work --- src/CONFIG.ts | 3 ++- src/libs/ApiUtils.ts | 6 ++++-- src/libs/actions/connections/ConnectToXero.ts | 2 +- .../actions/connections/getQuickBooksOnlineSetupLink.ts | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/CONFIG.ts b/src/CONFIG.ts index 76ea18d37d5f..9ed4242d7604 100644 --- a/src/CONFIG.ts +++ b/src/CONFIG.ts @@ -48,6 +48,7 @@ export default { EXPENSIFY: { // Note: This will be EXACTLY what is set for EXPENSIFY_URL whether the proxy is enabled or not. EXPENSIFY_URL: expensifyURL, + SECURE_EXPENSIFY_URL: secureExpensifyUrl, NEW_EXPENSIFY_URL: newExpensifyURL, // The DEFAULT API is the API used by most environments, except staging, where we use STAGING (defined below) @@ -72,7 +73,7 @@ export default { IS_USING_LOCAL_WEB: useNgrok || expensifyURLRoot.includes('dev'), PUSHER: { APP_KEY: get(Config, 'PUSHER_APP_KEY', '268df511a204fbb60884'), - SUFFIX: get(Config, 'PUSHER_DEV_SUFFIX', ''), + SUFFIX: ENVIRONMENT === CONST.ENVIRONMENT.DEV ? get(Config, 'PUSHER_DEV_SUFFIX', '') : '', CLUSTER: 'mt1', }, SITE_TITLE: 'New Expensify', diff --git a/src/libs/ApiUtils.ts b/src/libs/ApiUtils.ts index 0c8fa3f53915..1da9795f333b 100644 --- a/src/libs/ApiUtils.ts +++ b/src/libs/ApiUtils.ts @@ -38,12 +38,14 @@ function getApiRoot(request?: Request): string { const shouldUseSecure = request?.shouldUseSecure ?? false; if (shouldUseStagingServer) { - if (CONFIG.IS_USING_WEB_PROXY) { + if (CONFIG.IS_USING_WEB_PROXY && !request?.shouldSkipWebProxy) { return shouldUseSecure ? proxyConfig.STAGING_SECURE : proxyConfig.STAGING; } return shouldUseSecure ? CONFIG.EXPENSIFY.STAGING_SECURE_API_ROOT : CONFIG.EXPENSIFY.STAGING_API_ROOT; } - + if (request?.shouldSkipWebProxy) { + return shouldUseSecure ? CONFIG.EXPENSIFY.SECURE_EXPENSIFY_URL : CONFIG.EXPENSIFY.EXPENSIFY_URL; + } return shouldUseSecure ? CONFIG.EXPENSIFY.DEFAULT_SECURE_API_ROOT : CONFIG.EXPENSIFY.DEFAULT_API_ROOT; } diff --git a/src/libs/actions/connections/ConnectToXero.ts b/src/libs/actions/connections/ConnectToXero.ts index 0bd3d61a6c6e..abe96603fd85 100644 --- a/src/libs/actions/connections/ConnectToXero.ts +++ b/src/libs/actions/connections/ConnectToXero.ts @@ -2,7 +2,7 @@ import {getCommandURL} from '@libs/ApiUtils'; const getXeroSetupLink = (policyID: string) => { const params = new URLSearchParams({policyID}); - const commandURL = getCommandURL({command: 'ConnectPolicyToXero'}); + const commandURL = getCommandURL({command: 'ConnectPolicyToXero', shouldSkipWebProxy: true}); return commandURL + params.toString(); }; diff --git a/src/libs/actions/connections/getQuickBooksOnlineSetupLink.ts b/src/libs/actions/connections/getQuickBooksOnlineSetupLink.ts index b4317025b057..56b8309f7134 100644 --- a/src/libs/actions/connections/getQuickBooksOnlineSetupLink.ts +++ b/src/libs/actions/connections/getQuickBooksOnlineSetupLink.ts @@ -2,7 +2,7 @@ import {getCommandURL} from '@libs/ApiUtils'; function getQuickBooksOnlineSetupLink(policyID: string) { const params = new URLSearchParams({policyID}); - const commandURL = getCommandURL({command: 'ConnectPolicyToQuickbooksOnline'}); + const commandURL = getCommandURL({command: 'ConnectPolicyToQuickbooksOnline', shouldSkipWebProxy: true}); return commandURL + params.toString(); } From ed736323a070ed66e4e9f8208e56f1c779b60271 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Wed, 17 Apr 2024 13:47:52 +0200 Subject: [PATCH 190/647] fix connections empty object bug --- src/pages/workspace/accounting/WorkspaceAccountingPage.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx b/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx index ad28cac7032c..c932b38f7642 100644 --- a/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx +++ b/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx @@ -30,6 +30,7 @@ import type {AnchorPosition} from '@styles/index'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy, PolicyConnectionSyncProgress} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import ConnectToQuickbooksOnlineButton from './qboConnectionButton'; type WorkspaceAccountingPageOnyxProps = { @@ -44,6 +45,8 @@ type WorkspaceAccountingPageProps = WithPolicyAndFullscreenLoadingProps & policy: OnyxEntry; }; +// const AccountingIntegrations = Object.values(CONST.POLICY.CONNECTIONS.NAME); + function WorkspaceAccountingPage({policy, connectionSyncProgress}: WorkspaceAccountingPageProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -80,7 +83,7 @@ function WorkspaceAccountingPage({policy, connectionSyncProgress}: WorkspaceAcco ); const connectionsMenuItems: MenuItemProps[] = useMemo(() => { - if (!policyConnectedToQbo && !policyConnectedToXero && !isSyncInProgress) { + if (isEmptyObject(policy?.connections) && !isSyncInProgress) { return [ { icon: Expensicons.QBOSquare, From 6db22e200e3c0ca660000d34303c52fa6c04acf0 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 17 Apr 2024 14:26:15 +0200 Subject: [PATCH 191/647] Add INVOICE_RECEIVER_TYPE to consts --- src/CONST.ts | 9 +++++++++ src/types/onyx/Report.ts | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index fa66b916ab7c..08ba93053685 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -839,6 +839,10 @@ const CONST = { OWNER_EMAIL_FAKE: '__FAKE__', OWNER_ACCOUNT_ID_FAKE: 0, DEFAULT_REPORT_NAME: 'Chat Report', + INVOICE_RECEIVER_TYPE: { + INDIVIDUAL: 'individual', + BUSINESS: 'policy', + }, }, NEXT_STEP: { FINISHED: 'Finished!', @@ -4353,6 +4357,11 @@ const CONST = { MAX_TAX_RATE_INTEGER_PLACES: 4, MAX_TAX_RATE_DECIMAL_PLACES: 4, + + INVOICE_RECEIVER_TYPE: { + INDIVIDUAL: 'individual', + POLICY: 'policy', + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 344b7df5b2eb..51e3ce5fb37d 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -30,11 +30,11 @@ type Participant = { type InvoiceReceiver = | { - type: 'individual'; + type: typeof CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; accountID: number; } | { - type: 'policy'; + type: typeof CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS; policyID: string; }; From 173432f67cb35058def61c27c6ab78d90b6087d2 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 17 Apr 2024 14:37:20 +0200 Subject: [PATCH 192/647] Code improvements --- src/CONST.ts | 5 -- ...raryForRefactorRequestConfirmationList.tsx | 5 +- src/libs/actions/IOU.ts | 67 +++++++++---------- 3 files changed, 32 insertions(+), 45 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 08ba93053685..6467769263dd 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4357,11 +4357,6 @@ const CONST = { MAX_TAX_RATE_INTEGER_PLACES: 4, MAX_TAX_RATE_DECIMAL_PLACES: 4, - - INVOICE_RECEIVER_TYPE: { - INDIVIDUAL: 'individual', - POLICY: 'policy', - }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index e41c6cc96085..7b444be18da9 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -256,10 +256,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ return allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${senderWorkspaceParticipant?.policyID}`]; }, [allPolicies, pickedParticipants]); - const canUpdateSenderWorkspace = useMemo( - () => PolicyUtils.getActiveAdminWorkspaces(allPolicies).length > 0 && !!transaction?.isFromGlobalCreate, - [allPolicies, transaction?.isFromGlobalCreate], - ); + const canUpdateSenderWorkspace = useMemo(() => PolicyUtils.canSendInvoice(allPolicies) && !!transaction?.isFromGlobalCreate, [allPolicies, transaction?.isFromGlobalCreate]); // A flag for showing the tags field const shouldShowTags = useMemo(() => isPolicyExpenseChat && OptionsListUtils.hasEnabledTags(policyTagLists), [isPolicyExpenseChat, policyTagLists]); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 130a43a79319..d6be557b8107 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -863,24 +863,7 @@ function buildOnyxDataForInvoice( policyCategories?: OnyxEntry, ): [OnyxUpdate[], OnyxUpdate[], OnyxUpdate[]] { const clearedPendingFields = Object.fromEntries(Object.keys(transaction.pendingFields ?? {}).map((key) => [key, null])); - const optimisticData: OnyxUpdate[] = []; - - if (chatReport) { - optimisticData.push({ - // Use SET for new reports because it doesn't exist yet, is faster and we need the data to be available when we navigate to the chat page - onyxMethod: isNewChatReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, - value: { - ...chatReport, - lastReadTime: DateUtils.getDBTime(), - lastMessageTranslationKey: '', - iouReportID: iouReport.reportID, - ...(isNewChatReport ? {pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}} : {}), - }, - }); - } - - optimisticData.push( + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, @@ -950,7 +933,22 @@ function buildOnyxDataForInvoice( key: `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${CONST.IOU.OPTIMISTIC_TRANSACTION_ID}`, value: null, }, - ); + ]; + + if (chatReport) { + optimisticData.push({ + // Use SET for new reports because it doesn't exist yet, is faster and we need the data to be available when we navigate to the chat page + onyxMethod: isNewChatReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: { + ...chatReport, + lastReadTime: DateUtils.getDBTime(), + lastMessageTranslationKey: '', + iouReportID: iouReport.reportID, + ...(isNewChatReport ? {pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}} : {}), + }, + }); + } if (optimisticPolicyRecentlyUsedCategories.length) { optimisticData.push({ @@ -968,21 +966,7 @@ function buildOnyxDataForInvoice( }); } - const successData: OnyxUpdate[] = []; - - if (isNewChatReport) { - successData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`, - value: { - pendingFields: null, - errorFields: null, - isOptimisticReport: false, - }, - }); - } - - successData.push( + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, @@ -1007,7 +991,6 @@ function buildOnyxDataForInvoice( pendingFields: clearedPendingFields, }, }, - { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`, @@ -1049,7 +1032,19 @@ function buildOnyxDataForInvoice( }, }, }, - ); + ]; + + if (isNewChatReport) { + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`, + value: { + pendingFields: null, + errorFields: null, + isOptimisticReport: false, + }, + }); + } const errorKey = DateUtils.getMicroseconds(); From b8a7641fb717f9210082575e24475fa8e09d9db7 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Wed, 17 Apr 2024 15:17:05 +0200 Subject: [PATCH 193/647] make accounting integrations more generative --- .../workspace/WorkspaceMoreFeaturesPage.tsx | 2 +- .../accounting/WorkspaceAccountingPage.tsx | 158 +++++++++--------- src/types/onyx/Request.ts | 1 + 3 files changed, 83 insertions(+), 78 deletions(-) diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx index f37921f07c36..d496d3872683 100644 --- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx +++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx @@ -48,7 +48,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro const {translate} = useLocalize(); const {canUseAccountingIntegrations} = usePermissions(); const hasAccountingConnection = !!policy?.areConnectionsEnabled && !!policy?.connections; - const isSyncTaxEnabled = !!policy?.connections?.quickbooksOnline.config.syncTax; + const isSyncTaxEnabled = !!policy?.connections?.quickbooksOnline?.config.syncTax; const spendItems: Item[] = [ { diff --git a/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx b/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx index c932b38f7642..c269db9f667b 100644 --- a/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx +++ b/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx @@ -30,6 +30,7 @@ import type {AnchorPosition} from '@styles/index'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy, PolicyConnectionSyncProgress} from '@src/types/onyx'; +import type {ConnectionName} from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import ConnectToQuickbooksOnlineButton from './qboConnectionButton'; @@ -45,7 +46,47 @@ type WorkspaceAccountingPageProps = WithPolicyAndFullscreenLoadingProps & policy: OnyxEntry; }; -// const AccountingIntegrations = Object.values(CONST.POLICY.CONNECTIONS.NAME); +const accountingIntegrations = Object.values(CONST.POLICY.CONNECTIONS.NAME); + +function connectToAccountingIntegrationButton(connectionName: ConnectionName, policyID: string, environmentURL: string) { + // eslint-disable-next-line default-case + switch (connectionName) { + case 'quickbooksOnline': + return ( + + ); + case 'xero': + return ( + + ); + } +} + +function accountingIntegrationIcon(connectionName: ConnectionName) { + // eslint-disable-next-line default-case + switch (connectionName) { + case 'quickbooksOnline': + return Expensicons.QBOSquare; + case 'xero': + return Expensicons.XeroSquare; + } +} + +function accountingIntegrationTitleKey(connectionName: ConnectionName) { + // eslint-disable-next-line default-case + switch (connectionName) { + case 'quickbooksOnline': + return 'workspace.accounting.qbo'; + case 'xero': + return 'workspace.accounting.xero'; + } +} function WorkspaceAccountingPage({policy, connectionSyncProgress}: WorkspaceAccountingPageProps) { const theme = useTheme(); @@ -60,10 +101,7 @@ function WorkspaceAccountingPage({policy, connectionSyncProgress}: WorkspaceAcco const threeDotsMenuContainerRef = useRef(null); const isSyncInProgress = !!connectionSyncProgress?.stageInProgress && connectionSyncProgress.stageInProgress !== CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.JOB_DONE; - const qboSyncInProgress = isSyncInProgress && connectionSyncProgress.connectionName === 'quickbooksOnline'; - const xeroSyncInProgress = isSyncInProgress && connectionSyncProgress.connectionName === 'xero'; - const policyConnectedToQbo = !!policy?.connections?.quickbooksOnline; - const policyConnectedToXero = !!policy?.connections?.xero; + const connectedIntegration = accountingIntegrations.find((integration) => !!policy?.connections?.[integration]); const policyID = policy?.id ?? ''; const overflowMenu: ThreeDotsMenuProps['menuItems'] = useMemo( @@ -84,47 +122,26 @@ function WorkspaceAccountingPage({policy, connectionSyncProgress}: WorkspaceAcco const connectionsMenuItems: MenuItemProps[] = useMemo(() => { if (isEmptyObject(policy?.connections) && !isSyncInProgress) { - return [ - { - icon: Expensicons.QBOSquare, - iconType: 'avatar', - interactive: false, - wrapperStyle: [styles.sectionMenuItemTopDescription], - shouldShowRightComponent: true, - title: translate('workspace.accounting.qbo'), - rightComponent: ( - - ), - }, - { - icon: Expensicons.XeroSquare, - iconType: 'avatar', - interactive: false, - wrapperStyle: [styles.sectionMenuItemTopDescription], - shouldShowRightComponent: true, - title: translate('workspace.accounting.xero'), - rightComponent: ( - - ), - }, - ]; + return accountingIntegrations.map((integration) => ({ + icon: accountingIntegrationIcon(integration), + iconType: 'avatar', + interactive: false, + wrapperStyle: [styles.sectionMenuItemTopDescription], + shouldShowRightComponent: true, + title: translate(accountingIntegrationTitleKey(integration)), + rightComponent: connectToAccountingIntegrationButton(integration, policyID, environmentURL), + })); } return [ { - icon: qboSyncInProgress || policyConnectedToQbo ? Expensicons.QBOSquare : Expensicons.XeroSquare, + icon: accountingIntegrationIcon(connectedIntegration ?? connectionSyncProgress?.connectionName), iconType: 'avatar', interactive: false, wrapperStyle: [styles.sectionMenuItemTopDescription], shouldShowRightComponent: true, - title: qboSyncInProgress || policyConnectedToQbo ? translate('workspace.accounting.qbo') : translate('workspace.accounting.xero'), - description: qboSyncInProgress + title: translate(accountingIntegrationTitleKey(connectedIntegration ?? connectionSyncProgress?.connectionName)), + description: isSyncInProgress ? translate('workspace.accounting.connections.syncStageName', connectionSyncProgress.stageInProgress) : translate('workspace.accounting.lastSync'), rightComponent: isSyncInProgress ? ( @@ -150,7 +167,7 @@ function WorkspaceAccountingPage({policy, connectionSyncProgress}: WorkspaceAcco ), }, - ...(policyConnectedToQbo || policyConnectedToXero + ...(connectedIntegration ? [ { icon: Expensicons.Pencil, @@ -180,14 +197,14 @@ function WorkspaceAccountingPage({policy, connectionSyncProgress}: WorkspaceAcco : []), ]; }, [ + connectedIntegration, + connectionSyncProgress?.connectionName, connectionSyncProgress?.stageInProgress, environmentURL, isSyncInProgress, overflowMenu, - policyConnectedToQbo, - policyConnectedToXero, + policy?.connections, policyID, - qboSyncInProgress, styles.popoverMenuIcon, styles.sectionMenuItemTopDescription, theme.spinner, @@ -195,32 +212,17 @@ function WorkspaceAccountingPage({policy, connectionSyncProgress}: WorkspaceAcco translate, ]); - const otherIntegrationsItem = useMemo(() => { - if (qboSyncInProgress || policyConnectedToQbo) { - return { - icon: Expensicons.XeroSquare, - title: translate('workspace.accounting.xero'), - rightComponent: ( - - ), - }; - } - if (xeroSyncInProgress || policyConnectedToXero) { - return { - icon: Expensicons.QBOSquare, - title: translate('workspace.accounting.qbo'), - rightComponent: ( - - ), - }; + const otherIntegrationsItems = useMemo(() => { + if (isEmptyObject(policy?.connections) && !isSyncInProgress) { + return; } - }, [environmentURL, policyConnectedToQbo, policyConnectedToXero, policyID, qboSyncInProgress, translate, xeroSyncInProgress]); + const otherIntegrations = accountingIntegrations.filter((integration) => integration !== connectionSyncProgress?.connectionName && integration !== connectedIntegration); + return otherIntegrations.map((integration) => ({ + icon: Expensicons.XeroSquare, + title: translate(accountingIntegrationTitleKey(integration)), + rightComponent: connectToAccountingIntegrationButton(integration, policyID, environmentURL), + })); + }, [connectedIntegration, connectionSyncProgress?.connectionName, environmentURL, isSyncInProgress, policy?.connections, policyID, translate]); const headerThreeDotsMenuItems: ThreeDotsMenuProps['menuItems'] = [ { @@ -271,21 +273,23 @@ function WorkspaceAccountingPage({policy, connectionSyncProgress}: WorkspaceAcco menuItems={connectionsMenuItems} shouldUseSingleExecution /> - {otherIntegrationsItem && ( + {otherIntegrationsItems && ( - + {otherIntegrationsItems.map((integration) => ( + + ))} )} diff --git a/src/types/onyx/Request.ts b/src/types/onyx/Request.ts index f6e82d75db70..233519f010fc 100644 --- a/src/types/onyx/Request.ts +++ b/src/types/onyx/Request.ts @@ -21,6 +21,7 @@ type RequestData = { finallyData?: OnyxUpdate[]; resolve?: (value: Response) => void; reject?: (value?: unknown) => void; + shouldSkipWebProxy?: boolean; }; type Request = RequestData & OnyxData; From c711542ba80bfc705bad89ade38209adc5a2b135 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 17 Apr 2024 21:58:33 +0800 Subject: [PATCH 194/647] correctly access the report data --- src/libs/TaskUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/TaskUtils.ts b/src/libs/TaskUtils.ts index 19e1025a09c8..188b8f5b31ae 100644 --- a/src/libs/TaskUtils.ts +++ b/src/libs/TaskUtils.ts @@ -38,7 +38,7 @@ function getTaskReportActionMessage(action: OnyxEntry): Pick Date: Wed, 17 Apr 2024 16:00:02 +0200 Subject: [PATCH 195/647] add permissions to report --- src/CONST.ts | 6 ++++++ src/types/onyx/Report.ts | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index 2b85bcb2c326..425391f6ebea 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -837,6 +837,12 @@ const CONST = { OWNER_EMAIL_FAKE: '__FAKE__', OWNER_ACCOUNT_ID_FAKE: 0, DEFAULT_REPORT_NAME: 'Chat Report', + PERMISSIONS: { + READ: 'read', + WRITE: 'write', + SHARE: 'share', + OWN: 'own', + } }, NEXT_STEP: { FINISHED: 'Finished!', diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index fe3eec6dc11e..36d345b1084c 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -186,6 +186,8 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< transactionThreadReportID?: string; fieldList?: Record; + + permissions?: Array>; }, PolicyReportField['fieldID'] >; From de29862ae77c1cd6b5fd2fa03fc8e0443ed133ed Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 16:01:28 +0200 Subject: [PATCH 196/647] create isReadOnly --- src/libs/ReportUtils.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 39964bfcc4d7..6b9ef9031cf0 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5989,6 +5989,13 @@ function canReportBeMentionedWithinPolicy(report: OnyxEntry, policyID: s return isChatRoom(report) && !isThread(report); } +/** + * + * Checks if report is in read-only mode. + */ +function isReadOnly(report: OnyxEntry): boolean { + return !report?.permissions?.includes(CONST.REPORT.PERMISSIONS.WRITE) ?? false; +} export { getReportParticipantsTitle, @@ -6228,6 +6235,7 @@ export { buildParticipantsFromAccountIDs, canReportBeMentionedWithinPolicy, getAllHeldTransactions, + isReadOnly, }; export type { From 7a13ae3e44fc50e05432badaf9b3db0025275ce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Wed, 17 Apr 2024 16:01:32 +0200 Subject: [PATCH 197/647] Change caret position styles --- src/components/Composer/index.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index a8737fdac47a..59f7333433bb 100755 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -289,13 +289,7 @@ function Composer( opacity: 0, }} > - + {`${valueBeforeCaret} `} Date: Wed, 17 Apr 2024 16:08:32 +0200 Subject: [PATCH 198/647] integrate OnboardingReportFooterMessage into ReportFooter --- src/libs/ReportUtils.ts | 17 +++++++++-------- .../report/OnboardingReportFooterMessage.tsx | 11 +++++++---- src/pages/home/report/ReportFooter.tsx | 6 ++++++ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 6b9ef9031cf0..e20686e1326a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5198,6 +5198,14 @@ function isMoneyRequestReportPendingDeletion(report: OnyxEntry | EmptyOb return parentReportAction?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; } +/** + * + * Checks if report is in read-only mode. + */ +function isReadOnly(report: OnyxEntry): boolean { + return !report?.permissions?.includes(CONST.REPORT.PERMISSIONS.WRITE) ?? false; +} + function canUserPerformWriteAction(report: OnyxEntry) { const reportErrors = getAddWorkspaceRoomOrChatReportErrors(report); @@ -5206,7 +5214,7 @@ function canUserPerformWriteAction(report: OnyxEntry) { return false; } - return !isArchivedRoom(report) && isEmptyObject(reportErrors) && report && isAllowedToComment(report) && !isAnonymousUser; + return !isArchivedRoom(report) && isEmptyObject(reportErrors) && report && isAllowedToComment(report) && !isAnonymousUser && !isReadOnly(report); } /** @@ -5989,13 +5997,6 @@ function canReportBeMentionedWithinPolicy(report: OnyxEntry, policyID: s return isChatRoom(report) && !isThread(report); } -/** - * - * Checks if report is in read-only mode. - */ -function isReadOnly(report: OnyxEntry): boolean { - return !report?.permissions?.includes(CONST.REPORT.PERMISSIONS.WRITE) ?? false; -} export { getReportParticipantsTitle, diff --git a/src/pages/home/report/OnboardingReportFooterMessage.tsx b/src/pages/home/report/OnboardingReportFooterMessage.tsx index a48a609b51a7..f6c91d3b9f12 100644 --- a/src/pages/home/report/OnboardingReportFooterMessage.tsx +++ b/src/pages/home/report/OnboardingReportFooterMessage.tsx @@ -1,8 +1,7 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import type {OnyxCollection} from 'react-native-onyx'; -import type {ValueOf} from 'type-fest'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; @@ -16,8 +15,9 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Policy as PolicyType, Report} from '@src/types/onyx'; -type OnboardingReportFooterMessageOnyxProps = {reports: OnyxCollection; policies: OnyxCollection}; -type OnboardingReportFooterMessageProps = OnboardingReportFooterMessageOnyxProps & {choice: ValueOf}; +// TODO: Use a proper choice type +type OnboardingReportFooterMessageOnyxProps = {choice: OnyxEntry; reports: OnyxCollection; policies: OnyxCollection}; +type OnboardingReportFooterMessageProps = OnboardingReportFooterMessageOnyxProps; function OnboardingReportFooterMessage({choice, reports, policies}: OnboardingReportFooterMessageProps) { const {translate} = useLocalize(); @@ -83,6 +83,9 @@ function OnboardingReportFooterMessage({choice, reports, policies}: OnboardingRe } OnboardingReportFooterMessage.displayName = 'OnboardingReportFooterMessage'; export default withOnyx({ + choice: { + key: ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, + }, reports: { key: ONYXKEYS.COLLECTION.REPORT, }, diff --git a/src/pages/home/report/ReportFooter.tsx b/src/pages/home/report/ReportFooter.tsx index bd143f9ef196..bccec0597fb0 100644 --- a/src/pages/home/report/ReportFooter.tsx +++ b/src/pages/home/report/ReportFooter.tsx @@ -20,6 +20,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import type {PendingAction} from '@src/types/onyx/OnyxCommon'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; +import OnboardingReportFooterMessage from './OnboardingReportFooterMessage'; import ReportActionCompose from './ReportActionCompose/ReportActionCompose'; type ReportFooterOnyxProps = { @@ -81,6 +82,7 @@ function ReportFooter({ const isSmallSizeLayout = windowWidth - (isSmallScreenWidth ? 0 : variables.sideBarWidth) < variables.anonymousReportFooterBreakpoint; const hideComposer = !ReportUtils.canUserPerformWriteAction(report); + const isReadOnlyReport = ReportUtils.isReadOnly(report); const allPersonalDetails = usePersonalDetails(); @@ -125,6 +127,10 @@ function ReportFooter({ [report.reportID, handleCreateTask], ); + if (isReadOnlyReport) { + ; + } + return ( <> {hideComposer && ( From 471a02adff994ef218ec3128614a92534fa7f6c4 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 16:09:13 +0200 Subject: [PATCH 199/647] prettify --- src/pages/home/report/OnboardingReportFooterMessage.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/home/report/OnboardingReportFooterMessage.tsx b/src/pages/home/report/OnboardingReportFooterMessage.tsx index f6c91d3b9f12..54659c14cf6e 100644 --- a/src/pages/home/report/OnboardingReportFooterMessage.tsx +++ b/src/pages/home/report/OnboardingReportFooterMessage.tsx @@ -81,7 +81,9 @@ function OnboardingReportFooterMessage({choice, reports, policies}: OnboardingRe ); } + OnboardingReportFooterMessage.displayName = 'OnboardingReportFooterMessage'; + export default withOnyx({ choice: { key: ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, From b4aed0fa5eb550664687f06008359fc21bb881c3 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 16:10:17 +0200 Subject: [PATCH 200/647] restrict task action in header --- src/components/TaskHeaderActionButton.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/TaskHeaderActionButton.tsx b/src/components/TaskHeaderActionButton.tsx index 2d964f58c253..a7e3abcd3012 100644 --- a/src/components/TaskHeaderActionButton.tsx +++ b/src/components/TaskHeaderActionButton.tsx @@ -25,6 +25,10 @@ function TaskHeaderActionButton({report, session}: TaskHeaderActionButtonProps) const {translate} = useLocalize(); const styles = useThemeStyles(); + if (ReportUtils.isReadOnly(report)) { + return null; + } + return (