diff --git a/lang/main.json b/lang/main.json index 7c38786c80ec..e0bde2de7fd6 100644 --- a/lang/main.json +++ b/lang/main.json @@ -344,9 +344,11 @@ "contactSupport": "Contact support", "copied": "Copied", "copy": "Copy", + "currentLinks": "Current Links", "demoteParticipantDialog": "Are you sure you want to move this participant to viewer?", "demoteParticipantTitle": "Move to viewer", "dismiss": "Dismiss", + "dismissAll": "Dismiss All", "displayNameRequired": "Hi! What's your name?", "done": "Done", "e2eeDescription": "End-to-End Encryption is currently EXPERIMENTAL. Please keep in mind that turning on end-to-end encryption will effectively disable server-side provided services such as: phone participation. Also keep in mind that the meeting will only work for people joining from browsers with support for insertable streams.", @@ -374,6 +376,7 @@ "kickSystemTitle": "Ouch! You were kicked out of the meeting", "kickTitle": "Ouch! {{participantDisplayName}} kicked you out of the meeting", "learnMore": "Learn more", + "link": "Link", "linkMeeting": "Link meeting", "linkMeetingTitle": "Link meeting to Salesforce", "liveStreaming": "Live Streaming", @@ -439,6 +442,8 @@ "passwordNotSupported": "Setting a meeting $t(lockRoomPassword) is not supported.", "passwordNotSupportedTitle": "$t(lockRoomPasswordUppercase) not supported", "passwordRequired": "$t(lockRoomPasswordUppercase) required", + "pendingDealsDescription": "These opportunities may be related to this meeting", + "pendingDescription": "We found these potential matches based on participant emails", "permissionCameraRequiredError": "Camera permission is required to participate in conferences with video. Please grant it in Settings", "permissionErrorTitle": "Permission required", "permissionMicRequiredError": "Microphone permission is required to participate in conferences with audio. Please grant it in Settings", @@ -465,6 +470,7 @@ "reservationError": "Reservation system error", "reservationErrorMsg": "Error code: {{code}}, message: {{msg}}", "retry": "Retry", + "salesforceDataError": "Failed to load Salesforce data", "screenSharingAudio": "Share audio", "screenSharingFailed": "Oops! Something went wrong, we weren't able to start screen sharing!", "screenSharingFailedTitle": "Screen sharing failed!", @@ -516,6 +522,8 @@ "stopRecordingWarning": "Are you sure you would like to stop the recording?", "stopStreamingWarning": "Are you sure you would like to stop the live streaming?", "streamKey": "Live stream key", + "suggestedAccounts": "Suggested Accounts", + "suggestedDeals": "Suggested Deals", "thankYou": "Thank you for using {{appName}}!", "token": "token", "tokenAuthFailed": "Sorry, you're not allowed to join this call.", @@ -540,6 +548,7 @@ "tokenAuthUnsupported": "Token URL is not supported.", "transcribing": "Transcribing", "unauthenticatedAccessDisabled": "This call requires authentication. Please login in order to proceed.", + "unlink": "Unlink", "unlockRoom": "Remove meeting $t(lockRoomPassword)", "user": "User", "userIdentifier": "User identifier", @@ -820,6 +829,10 @@ "audioUnmuteBlockedDescription": "Mic unmute operation has been temporarily blocked because of system limits.", "audioUnmuteBlockedTitle": "Mic unmute blocked!", "chatMessages": "Chat messages", + "confirmAccountError": "Failed to confirm account", + "confirmAccountSuccess": "Account confirmed", + "confirmDealError": "Failed to confirm deal", + "confirmDealSuccess": "Deal confirmed", "connectedOneMember": "{{name}} joined the meeting", "connectedThreePlusMembers": "{{name}} and many others joined the meeting", "connectedTwoMembers": "{{first}} and {{second}} joined the meeting", @@ -833,6 +846,8 @@ "disabledIframeSecondaryNative": "Embedding {{domain}} is only meant for demo purposes, so this call will disconnect in {{timeout}} minutes.", "disabledIframeSecondaryWeb": "Embedding {{domain}} is only meant for demo purposes, so this call will disconnect in {{timeout}} minutes. Please use Jitsi as a Service for production embedding!", "disconnected": "disconnected", + "dismissError": "Failed to dismiss suggestions", + "dismissSuccess": "Suggestions dismissed", "displayNotifications": "Display notifications for", "dontRemindMe": "Do not remind me", "focus": "Conference focus", @@ -909,6 +924,8 @@ "suggestRecordingAction": "Start", "suggestRecordingDescription": "Would you like to start a recording?", "suggestRecordingTitle": "Record this meeting", + "unlinkFromSalesforceError": "Failed to unlink from Salesforce", + "unlinkFromSalesforceSuccess": "Unlinked from Salesforce", "unmute": "Unmute Audio", "unmuteScreen": "Start screen sharing", "unmuteVideo": "Unmute Video", diff --git a/react/features/salesforce/components/native/CurrentLinksSection.tsx b/react/features/salesforce/components/native/CurrentLinksSection.tsx new file mode 100644 index 000000000000..9287dd5b23da --- /dev/null +++ b/react/features/salesforce/components/native/CurrentLinksSection.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Text, View, ViewStyle } from 'react-native'; + +import { + IconRecordAccount, + IconRecordContact, + IconRecordLead, + IconRecordOpportunity +} from '../../../base/icons/svg'; +import { BUTTON_TYPES } from '../../../base/ui/constants.native'; +import { ISalesforceData, SalesforceObjectType } from '../../types'; + +import { RecordListItem } from './RecordListItem'; +import styles from './styles'; + +interface IProps { + onUnlink: (type: SalesforceObjectType, id: string) => void; + salesforceData: ISalesforceData | null; + unlinkingId: string | null; +} + +/** + * Component for displaying currently linked Salesforce objects with unlink buttons. + * + * @param {IProps} props - Component props. + * @returns {React.ReactElement} + */ +export const CurrentLinksSection = ({ salesforceData, unlinkingId, onUnlink }: IProps) => { + const { t } = useTranslation(); + + const hasAccount = Boolean(salesforceData?.account); + const hasLeads = (salesforceData?.leads?.length ?? 0) > 0; + const hasContacts = (salesforceData?.contacts?.length ?? 0) > 0; + const hasDeal = Boolean(salesforceData?.deal); + const hasAnyLinks = hasAccount || hasLeads || hasContacts || hasDeal; + + if (!hasAnyLinks) { + return null; + } + + return ( + + {t('dialog.currentLinks')} + + {salesforceData?.account && ( + onUnlink('Account', salesforceData.account!.accountId) } /> + )} + + {salesforceData?.leads?.map(lead => ( + onUnlink('Lead', lead.leadId) } /> + ))} + + {salesforceData?.contacts?.map(contact => ( + onUnlink('Contact', contact.contactId) } /> + ))} + + {salesforceData?.deal && ( + onUnlink('Opportunity', salesforceData.deal!.opportunityId) } /> + )} + + ); +}; + +export default CurrentLinksSection; diff --git a/react/features/salesforce/components/native/PendingSection.tsx b/react/features/salesforce/components/native/PendingSection.tsx new file mode 100644 index 000000000000..94bfed8f15a6 --- /dev/null +++ b/react/features/salesforce/components/native/PendingSection.tsx @@ -0,0 +1,136 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Text, View, ViewStyle } from 'react-native'; + +import LoadingIndicator from '../../../base/react/components/native/LoadingIndicator'; +import Button from '../../../base/ui/components/native/Button'; +import { BUTTON_TYPES } from '../../../base/ui/constants.native'; +import { IPendingAccount, IPendingDeal } from '../../types'; + +import styles from './styles'; + +interface IPendingAccountsSectionProps { + confirmingId: string | null; + items: IPendingAccount[]; + onConfirm: (accountId: string) => void; + onRejectAll: () => void; + rejecting: boolean; + type: 'accounts'; +} + +interface IPendingDealsSectionProps { + confirmingId: string | null; + items: IPendingDeal[]; + onConfirm: (opportunityId: string) => void; + onRejectAll: () => void; + rejecting: boolean; + type: 'deals'; +} + +type IProps = IPendingAccountsSectionProps | IPendingDealsSectionProps; + +/** + * Component for displaying pending Salesforce suggestions (accounts or deals). + * + * @param {IProps} props - Component props. + * @returns {React.ReactElement} + */ +export const PendingSection = (props: IProps) => { + const { t } = useTranslation(); + const { items, confirmingId, rejecting, onConfirm, onRejectAll, type } = props; + + if (items.length === 0) { + return null; + } + + const isAccounts = type === 'accounts'; + const title = isAccounts + ? t('dialog.suggestedAccounts') + : t('dialog.suggestedDeals'); + const description = isAccounts + ? t('dialog.pendingDescription') + : t('dialog.pendingDealsDescription'); + + return ( + + + {title} +