Skip to content

Commit

Permalink
Merge branch 'main' into georgia-LightModeThemeQA
Browse files Browse the repository at this point in the history
  • Loading branch information
grgia committed Dec 5, 2023
2 parents 2230d21 + a031ba0 commit 17c83d7
Show file tree
Hide file tree
Showing 21 changed files with 174 additions and 112 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Your Expensify Partner Manager
title: Expensify Partner Support
description: Understanding support for our partners
---

Expand All @@ -10,23 +10,47 @@ Our well-rounded support methodology is designed to provide comprehensive assist

## 1. ExpensifyApproved! University
**Purpose:** Equip your team with a comprehensive understanding of Expensify.

**Benefits:**
- Foundation-level knowledge about the platform.
- 3 CPE credits upon successful completion (US-only).
- Unlock exclusive partner perks, including tickets to ExpensiCon!
- Visit university.Expensify.com to access our comprehensive training program.

## 2. Partner Manager
**Role:** A designated liaison for your firm.
**Role:**
A Partner Manager is a dedicated point of contact for your firm Partner Managers support our accounting partners by providing recommendations for client’s accounts, assisting with firm-wide training, and ensuring partners receive the full benefits of our partnership program. They will actively monitor open technical issues and be proactive with recommendations to increase efficiency.


**Key Responsibilities:**
- Handle any escalations promptly.
- Organize firm-wide training sessions.
- Assist with strategic planning and the introduction of new features.
- Once you've completed the ExpensifyApproved! University, log in to your Expensify account. Click on the "Support" option to connect with your dedicated Partner Manager.

**How do I know if I have a Partner Manager?**

For your firm to be assigned a Partner Manager, you must complete the ExpensifyApproved! University training course. Every external accountant or bookkeeper who completes the training is automatically enrolled in our program and receives all the benefits, including access to the Partner Manager. So everyone at your firm must complete the training to receive the maximum benefit.

You can check to see if you’ve completed the course and enrolled in the ExpensifyApproved! Accountants program simply by logging into your Expensify account. In the bottom left-hand corner of the website, you will see the ExpensifyApproved! logo.

**How do I contact my Partner Manager?**
1. Signing in to new.expensify.com and searching for your Partner Manager
2. Replying to or clicking the chat link on any email you get from your Partner Manager

**How do I know if my Partner Manager is online?**

You will be able to see if they are online via their status in new.expensify.com, which will either say “online” or have their working hours.

**Can I get on a call with my Partner Manager?**

Of course! You can ask your Partner Manager to schedule a call whenever you think one might be helpful. Partner Managers can discuss client onboarding strategies, firm-wide training, and client setups.

We recommend continuing to work with Concierge for general support questions, as this team is always online and available to help immediately.

## 3. Client Setup Specialist
**Purpose:** Ensure smooth onboarding for every client you refer.

**Duties:**
- Comprehensive assistance with setting up Expensify.
- Help with configuring accounting integrations.
Expand All @@ -35,13 +59,15 @@ Our well-rounded support methodology is designed to provide comprehensive assist

## 4. Client Account Manager
**Role:** Dedicated support for ongoing client needs.

**Responsibilities:**
- Address day-to-day product inquiries.
- Assist clients in navigating and optimizing their use of Expensify.
- After logging into Expensify, click on the "Support" option in the left-hand navigation pane. This will connect you directly to your assigned Account Manager.

## 5. Concierge chat support
**Availability:** Real-time support for any urgent inquiries.

**Features:**
- Immediate assistance with an average response time of under two minutes.
- Available to both accountants and clients for all product-related questions.
Expand Down

This file was deleted.

Binary file modified ios/expensify_chat_adhoc.mobileprovision.gpg
Binary file not shown.
26 changes: 12 additions & 14 deletions src/components/AttachmentModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,6 @@ const propTypes = {

/** Denotes whether it is a workspace avatar or not */
isWorkspaceAvatar: PropTypes.bool,

/** Whether it is a receipt attachment or not */
isReceiptAttachment: PropTypes.bool,
};

const defaultProps = {
Expand All @@ -110,7 +107,6 @@ const defaultProps = {
onModalHide: () => {},
onCarouselAttachmentChange: () => {},
isWorkspaceAvatar: false,
isReceiptAttachment: false,
};

function AttachmentModal(props) {
Expand All @@ -122,6 +118,7 @@ function AttachmentModal(props) {
const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false);
const [isDeleteReceiptConfirmModalVisible, setIsDeleteReceiptConfirmModalVisible] = useState(false);
const [isAuthTokenRequired, setIsAuthTokenRequired] = useState(props.isAuthTokenRequired);
const [isAttachmentReceipt, setIsAttachmentReceipt] = useState(null);
const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState('');
const [attachmentInvalidReason, setAttachmentInvalidReason] = useState(null);
const [source, setSource] = useState(props.source);
Expand Down Expand Up @@ -157,6 +154,7 @@ function AttachmentModal(props) {
(attachment) => {
setSource(attachment.source);
setFile(attachment.file);
setIsAttachmentReceipt(attachment.isReceipt);
setIsAuthTokenRequired(attachment.isAuthTokenRequired);
onCarouselAttachmentChange(attachment);
},
Expand Down Expand Up @@ -359,7 +357,7 @@ function AttachmentModal(props) {
const sourceForAttachmentView = props.source || source;

const threeDotsMenuItems = useMemo(() => {
if (!props.isReceiptAttachment || !props.parentReport || !props.parentReportActions) {
if (!isAttachmentReceipt || !props.parentReport || !props.parentReportActions) {
return [];
}
const menuItems = [];
Expand Down Expand Up @@ -394,17 +392,17 @@ function AttachmentModal(props) {
}
return menuItems;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.isReceiptAttachment, props.parentReport, props.parentReportActions, props.policy, props.transaction, file]);
}, [isAttachmentReceipt, props.parentReport, props.parentReportActions, props.policy, props.transaction, file]);

// There are a few things that shouldn't be set until we absolutely know if the file is a receipt or an attachment.
// props.isReceiptAttachment will be null until its certain what the file is, in which case it will then be true|false.
// isAttachmentReceipt will be null until its certain what the file is, in which case it will then be true|false.
let headerTitle = props.headerTitle;
let shouldShowDownloadButton = false;
let shouldShowThreeDotsButton = false;
if (!_.isNull(props.isReceiptAttachment)) {
headerTitle = translate(props.isReceiptAttachment ? 'common.receipt' : 'common.attachment');
shouldShowDownloadButton = props.allowDownload && isDownloadButtonReadyToBeShown && !props.isReceiptAttachment && !isOffline;
shouldShowThreeDotsButton = props.isReceiptAttachment && isModalOpen;
if (!_.isNull(isAttachmentReceipt)) {
headerTitle = translate(isAttachmentReceipt ? 'common.receipt' : 'common.attachment');
shouldShowDownloadButton = props.allowDownload && isDownloadButtonReadyToBeShown && !isAttachmentReceipt && !isOffline;
shouldShowThreeDotsButton = isAttachmentReceipt && isModalOpen;
}

return (
Expand Down Expand Up @@ -445,7 +443,7 @@ function AttachmentModal(props) {
shouldOverlay
/>
<View style={styles.imageModalImageCenterContainer}>
{!_.isEmpty(props.report) && !props.isReceiptAttachment ? (
{!_.isEmpty(props.report) ? (
<AttachmentCarousel
report={props.report}
onNavigate={onNavigate}
Expand Down Expand Up @@ -488,7 +486,7 @@ function AttachmentModal(props) {
)}
</SafeAreaConsumer>
)}
{props.isReceiptAttachment && (
{isAttachmentReceipt && (
<ConfirmModal
title={translate('receipt.deleteReceipt')}
isVisible={isDeleteReceiptConfirmModalVisible}
Expand All @@ -501,7 +499,7 @@ function AttachmentModal(props) {
/>
)}
</Modal>
{!props.isReceiptAttachment && (
{!isAttachmentReceipt && (
<ConfirmModal
title={attachmentInvalidReasonTitle ? translate(attachmentInvalidReasonTitle) : ''}
onConfirm={closeConfirmModal}
Expand Down
4 changes: 2 additions & 2 deletions src/components/Attachments/AttachmentCarousel/CarouselItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ const propTypes = {
/** Additional information about the attachment file */
file: PropTypes.shape({
/** File name of the attachment */
name: PropTypes.string,
}),
name: PropTypes.string.isRequired,
}).isRequired,

/** Whether the attachment has been flagged */
hasBeenFlagged: PropTypes.bool,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import {Parser as HtmlParser} from 'htmlparser2';
import lodashGet from 'lodash/get';
import _ from 'underscore';
import * as FileUtils from '@libs/fileDownload/FileUtils';
import * as ReceiptUtils from '@libs/ReceiptUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot';
import CONST from '@src/CONST';

/**
* Constructs the initial component state from report actions
* @param {Object} parentReportAction
* @param {Object} reportActions
* @param {Object} transaction
* @returns {Array}
*/
function extractAttachmentsFromReport(parentReportAction, reportActions) {
function extractAttachmentsFromReport(parentReportAction, reportActions, transaction) {
const actions = [parentReportAction, ...ReportActionsUtils.getSortedReportActions(_.values(reportActions))];
const attachments = [];

Expand All @@ -21,24 +26,49 @@ function extractAttachmentsFromReport(parentReportAction, reportActions) {
}

const expensifySource = attribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE];
const source = tryResolveUrlFromApiRoot(expensifySource || attribs.src);
const fileName = attribs[CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE] || FileUtils.getFileName(`${source}`);

// By iterating actions in chronological order and prepending each attachment
// we ensure correct order of attachments even across actions with multiple attachments.
attachments.unshift({
reportActionID: attribs['data-id'],
source: tryResolveUrlFromApiRoot(expensifySource || attribs.src),
source,
isAuthTokenRequired: Boolean(expensifySource),
file: {name: attribs[CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE]},
file: {name: fileName},
isReceipt: false,
hasBeenFlagged: attribs['data-flagged'] === 'true',
});
},
});

_.forEach(actions, (action, key) => {
if (!ReportActionsUtils.shouldReportActionBeVisible(action, key) || ReportActionsUtils.isMoneyRequestAction(action)) {
if (!ReportActionsUtils.shouldReportActionBeVisible(action, key)) {
return;
}

// We're handling receipts differently here because receipt images are not
// part of the report action message, the images are constructed client-side
if (ReportActionsUtils.isMoneyRequestAction(action)) {
const transactionID = lodashGet(action, ['originalMessage', 'IOUTransactionID']);
if (!transactionID) {
return;
}

if (TransactionUtils.hasReceipt(transaction)) {
const {image} = ReceiptUtils.getThumbnailAndImageURIs(transaction);
const isLocalFile = typeof image === 'string' && _.some(CONST.ATTACHMENT_LOCAL_URL_PREFIX, (prefix) => image.startsWith(prefix));
attachments.unshift({
source: tryResolveUrlFromApiRoot(image),
isAuthTokenRequired: !isLocalFile,
file: {name: transaction.filename},
isReceipt: true,
transactionID,
});
return;
}
}

const decision = _.get(action, ['message', 0, 'moderationDecision', 'decision'], '');
const hasBeenFlagged = decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_HIDE || decision === CONST.MODERATION.MODERATOR_DECISION_HIDDEN;
const html = _.get(action, ['message', 0, 'html'], '').replace('/>', `data-flagged="${hasBeenFlagged}" data-id="${action.reportActionID}"/>`);
Expand Down
27 changes: 24 additions & 3 deletions src/components/Attachments/AttachmentCarousel/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import lodashGet from 'lodash/get';
import React, {useCallback, useEffect, useRef, useState} from 'react';
import {FlatList, Keyboard, PixelRatio, View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
Expand Down Expand Up @@ -27,7 +28,7 @@ const viewabilityConfig = {
itemVisiblePercentThreshold: 95,
};

function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility, translate}) {
function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility, translate, transaction}) {
const styles = useThemeStyles();
const scrollRef = useRef(null);

Expand All @@ -38,12 +39,21 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
const [attachments, setAttachments] = useState([]);
const [activeSource, setActiveSource] = useState(source);
const [shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows] = useCarouselArrows();
const [isReceipt, setIsReceipt] = useState(false);

const compareImage = useCallback((attachment) => attachment.source === source, [source]);
const compareImage = useCallback(
(attachment) => {
if (attachment.isReceipt && isReceipt) {
return attachment.transactionID === transaction.transactionID;
}
return attachment.source === source;
},
[source, isReceipt, transaction],
);

useEffect(() => {
const parentReportAction = parentReportActions[report.parentReportActionID];
const attachmentsFromReport = extractAttachmentsFromReport(parentReportAction, reportActions);
const attachmentsFromReport = extractAttachmentsFromReport(parentReportAction, reportActions, transaction);

const initialPage = _.findIndex(attachmentsFromReport, compareImage);

Expand Down Expand Up @@ -78,10 +88,12 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
// to get the index of the current page
const entry = _.first(viewableItems);
if (!entry) {
setIsReceipt(false);
setActiveSource(null);
return;
}

setIsReceipt(entry.item.isReceipt);
setPage(entry.index);
setActiveSource(entry.item.source);

Expand Down Expand Up @@ -229,6 +241,15 @@ export default compose(
canEvict: false,
},
}),
// eslint-disable-next-line rulesdir/no-multiple-onyx-in-file
withOnyx({
transaction: {
key: ({report, parentReportActions}) => {
const parentReportAction = lodashGet(parentReportActions, [report.parentReportActionID]);
return `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(parentReportAction, 'originalMessage.IOUTransactionID', 0)}`;
},
},
}),
withLocalize,
withWindowDimensions,
)(AttachmentCarousel);
Loading

0 comments on commit 17c83d7

Please sign in to comment.