diff --git a/webapp/channels/src/components/common/hooks/useControlModal.ts b/webapp/channels/src/components/common/hooks/useControlModal.ts index 7a8a4af506..fdfdaec8a5 100644 --- a/webapp/channels/src/components/common/hooks/useControlModal.ts +++ b/webapp/channels/src/components/common/hooks/useControlModal.ts @@ -48,5 +48,5 @@ export function useControlModal(modalData: ModalData): ControlModal { close: () => { dispatch(closeModal(modalData.modalId)); }, - }), [modalData]); + }), [modalData, dispatch]); } diff --git a/webapp/channels/src/components/common/hooks/useCopyText.ts b/webapp/channels/src/components/common/hooks/useCopyText.ts index ba526255ae..f47602936f 100644 --- a/webapp/channels/src/components/common/hooks/useCopyText.ts +++ b/webapp/channels/src/components/common/hooks/useCopyText.ts @@ -18,20 +18,22 @@ type CopyResponse = { const DEFAULT_COPY_TIMEOUT = 4000; -export default function useCopyText(options: CopyOptions): CopyResponse { +export default function useCopyText({ + text, + successCopyTimeout: successCopyTimeoutReceived, + trackCallback, +}: CopyOptions): CopyResponse { const [copiedRecently, setCopiedRecently] = useState(false); const [copyError, setCopyError] = useState(false); const timerRef = useRef(null); let successCopyTimeout = DEFAULT_COPY_TIMEOUT; - if (options.successCopyTimeout || options.successCopyTimeout === 0) { - successCopyTimeout = options.successCopyTimeout; + if (successCopyTimeoutReceived || successCopyTimeoutReceived === 0) { + successCopyTimeout = successCopyTimeoutReceived; } const onClick = useCallback(() => { - if (options.trackCallback) { - options.trackCallback(); - } + trackCallback?.(); if (timerRef.current) { clearTimeout(timerRef.current); @@ -39,7 +41,7 @@ export default function useCopyText(options: CopyOptions): CopyResponse { } const clipboard = navigator.clipboard; if (clipboard) { - clipboard.writeText(options.text). + clipboard.writeText(text). then(() => { setCopiedRecently(true); setCopyError(false); @@ -50,7 +52,7 @@ export default function useCopyText(options: CopyOptions): CopyResponse { }); } else { const textField = document.createElement('textarea'); - textField.innerText = options.text; + textField.innerText = text; textField.style.position = 'fixed'; textField.style.opacity = '0'; @@ -72,7 +74,7 @@ export default function useCopyText(options: CopyOptions): CopyResponse { setCopiedRecently(false); setCopyError(false); }, successCopyTimeout); - }, [options.text, successCopyTimeout]); + }, [successCopyTimeout, text, trackCallback]); return { copiedRecently, diff --git a/webapp/channels/src/components/common/hooks/useExpandOverageUsersCheck.ts b/webapp/channels/src/components/common/hooks/useExpandOverageUsersCheck.ts index c6763c7600..b3611c3447 100644 --- a/webapp/channels/src/components/common/hooks/useExpandOverageUsersCheck.ts +++ b/webapp/channels/src/components/common/hooks/useExpandOverageUsersCheck.ts @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {useCallback} from 'react'; import {defineMessage} from 'react-intl'; import {useSelector} from 'react-redux'; @@ -12,23 +13,23 @@ type UseExpandOverageUsersCheckArgs = { banner: 'global banner' | 'invite modal'; } +const cta = defineMessage({ + id: 'licensingPage.overageUsersBanner.cta', + defaultMessage: 'Contact Sales', +}); + export const useExpandOverageUsersCheck = ({ isWarningState, banner, }: UseExpandOverageUsersCheckArgs) => { const expandableLink = useSelector(getExpandSeatsLink); - const cta = defineMessage({ - id: 'licensingPage.overageUsersBanner.cta', - defaultMessage: 'Contact Sales', - }); - - const trackEventFn = (cta: 'Contact Sales' | 'Self Serve') => { + const trackEventFn = useCallback((cta: 'Contact Sales' | 'Self Serve') => { trackEvent('insights', isWarningState ? 'click_true_up_warning' : 'click_true_up_error', { cta, banner, }); - }; + }, [banner, isWarningState]); return { cta, diff --git a/webapp/channels/src/components/common/hooks/useGetNotifyAdmin.ts b/webapp/channels/src/components/common/hooks/useGetNotifyAdmin.ts index 12b10fdb98..7b401f7bcd 100644 --- a/webapp/channels/src/components/common/hooks/useGetNotifyAdmin.ts +++ b/webapp/channels/src/components/common/hooks/useGetNotifyAdmin.ts @@ -1,7 +1,8 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {useState} from 'react'; +import {useCallback, useState} from 'react'; +import {defineMessages} from 'react-intl'; import type {NotifyAdminRequest} from '@mattermost/types/cloud'; @@ -19,14 +20,6 @@ export const NotifyStatus = { export type NotifyStatusValues = ValueOf; -export const DefaultBtnText = { - NotifyAdmin: 'Notify your admin', - Notifying: 'Notifying...', - Notified: 'Admin notified!', - AlreadyNotified: 'Already notified!', - Failed: 'Try again later!', -} as const; - type ValueOf = T[keyof T]; type UseNotifyAdminArgs = { @@ -45,25 +38,40 @@ type NotifyAdminArgs = { }; } +const messages = defineMessages({ + [NotifyStatus.Started]: { + id: 'notify_admin_to_upgrade_cta.notify-admin.notifying', + defaultMessage: 'Notifying...', + }, + [NotifyStatus.Success]: { + id: 'notify_admin_to_upgrade_cta.notify-admin.notified', + defaultMessage: 'Admin notified!', + }, + [NotifyStatus.AlreadyComplete]: { + id: 'notify_admin_to_upgrade_cta.notify-admin.already_notified', + defaultMessage: 'Already notified!', + }, + [NotifyStatus.Failed]: { + id: 'notify_admin_to_upgrade_cta.notify-admin.failed', + defaultMessage: 'Try again later!', + }, + [NotifyStatus.NotStarted]: { + id: 'notify_admin_to_upgrade_cta.notify-admin.notify', + defaultMessage: 'Notify your admin', + }, +}); + export const useGetNotifyAdmin = (args: UseNotifyAdminArgs) => { const [notifyStatus, setStatus] = useState>(NotifyStatus.NotStarted); - const btnText = (status: ValueOf): {id: string; defaultMessage: string} => { - switch (status) { - case NotifyStatus.Started: - return {id: 'notify_admin_to_upgrade_cta.notify-admin.notifying', defaultMessage: DefaultBtnText.Notifying}; - case NotifyStatus.Success: - return {id: 'notify_admin_to_upgrade_cta.notify-admin.notified', defaultMessage: DefaultBtnText.Notified}; - case NotifyStatus.AlreadyComplete: - return {id: 'notify_admin_to_upgrade_cta.notify-admin.already_notified', defaultMessage: DefaultBtnText.AlreadyNotified}; - case NotifyStatus.Failed: - return {id: 'notify_admin_to_upgrade_cta.notify-admin.failed', defaultMessage: DefaultBtnText.Failed}; - default: - return args.ctaText || {id: 'notify_admin_to_upgrade_cta.notify-admin.notify', defaultMessage: DefaultBtnText.NotifyAdmin}; + const btnText = useCallback((status: ValueOf): {id: string; defaultMessage: string} => { + if (args.ctaText && status === NotifyStatus.NotStarted) { + return args.ctaText; } - }; + return messages[status]; + }, [args.ctaText]); - const notifyAdmin = async ({requestData, trackingArgs}: NotifyAdminArgs) => { + const notifyAdmin = useCallback(async ({requestData, trackingArgs}: NotifyAdminArgs) => { try { setStatus(NotifyStatus.Started); await Client4.notifyAdmin(requestData); @@ -76,7 +84,7 @@ export const useGetNotifyAdmin = (args: UseNotifyAdminArgs) => { setStatus(NotifyStatus.Failed); } } - }; + }, []); return { notifyStatus, diff --git a/webapp/channels/src/components/common/hooks/useGetTotalUsersNoBots.ts b/webapp/channels/src/components/common/hooks/useGetTotalUsersNoBots.ts index 0801fff8af..7925bc00c9 100644 --- a/webapp/channels/src/components/common/hooks/useGetTotalUsersNoBots.ts +++ b/webapp/channels/src/components/common/hooks/useGetTotalUsersNoBots.ts @@ -10,12 +10,12 @@ const useGetTotalUsersNoBots = (includeInactive = false): number => { const dispatch = useDispatch(); const [userCount, setUserCount] = useState(0); - const getTotalUsers = async () => { - const {data} = await dispatch(getFilteredUsersStats({include_bots: false, include_deleted: includeInactive}, false)); - setUserCount(data?.total_users_count ?? 0); - }; - useEffect(() => { + const getTotalUsers = async () => { + const {data} = await dispatch(getFilteredUsersStats({include_bots: false, include_deleted: includeInactive}, false)); + setUserCount(data?.total_users_count ?? 0); + }; + getTotalUsers(); }, []); diff --git a/webapp/channels/src/components/common/hooks/useOpenDowngradeModal.ts b/webapp/channels/src/components/common/hooks/useOpenDowngradeModal.ts index 00c598e484..b0853e73e8 100644 --- a/webapp/channels/src/components/common/hooks/useOpenDowngradeModal.ts +++ b/webapp/channels/src/components/common/hooks/useOpenDowngradeModal.ts @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {useCallback} from 'react'; import {useDispatch} from 'react-redux'; import {trackEvent} from 'actions/telemetry_actions'; @@ -17,7 +18,7 @@ type TelemetryProps = Pick export default function useOpenDowngradeModal() { const dispatch = useDispatch(); - return (telemetryProps: TelemetryProps) => { + return useCallback((telemetryProps: TelemetryProps) => { trackEvent(TELEMETRY_CATEGORIES.CLOUD_ADMIN, 'click_open_downgrade_modal', { callerInfo: telemetryProps.trackingLocation, }); @@ -25,5 +26,5 @@ export default function useOpenDowngradeModal() { modalId: ModalIdentifiers.DOWNGRADE_MODAL, dialogType: DowngradeModal, })); - }; + }, [dispatch]); } diff --git a/webapp/channels/src/components/common/hooks/useOpenInvitePeopleModal.ts b/webapp/channels/src/components/common/hooks/useOpenInvitePeopleModal.ts index e6e05ae492..cb8642d8f6 100644 --- a/webapp/channels/src/components/common/hooks/useOpenInvitePeopleModal.ts +++ b/webapp/channels/src/components/common/hooks/useOpenInvitePeopleModal.ts @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {useCallback} from 'react'; import {useDispatch} from 'react-redux'; import {trackEvent} from 'actions/telemetry_actions'; @@ -12,11 +13,11 @@ import {ModalIdentifiers} from 'utils/constants'; export default function useOpenInvitePeopleModal() { const dispatch = useDispatch(); - return () => { + return useCallback(() => { trackEvent('invite_people', 'click_open_invite_people_modal'); dispatch(openModal({ modalId: ModalIdentifiers.INVITATION, dialogType: InvitationModal, })); - }; + }, [dispatch]); } diff --git a/webapp/channels/src/components/common/hooks/useOpenPricingModal.ts b/webapp/channels/src/components/common/hooks/useOpenPricingModal.ts index 6beeacbd1e..dfb7a049ff 100644 --- a/webapp/channels/src/components/common/hooks/useOpenPricingModal.ts +++ b/webapp/channels/src/components/common/hooks/useOpenPricingModal.ts @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {useCallback} from 'react'; import {useDispatch, useSelector} from 'react-redux'; import {isCurrentLicenseCloud} from 'mattermost-redux/selectors/entities/cloud'; @@ -19,8 +20,9 @@ export type TelemetryProps = { export default function useOpenPricingModal() { const dispatch = useDispatch(); const isCloud = useSelector(isCurrentLicenseCloud); - let category; - return (telemetryProps?: TelemetryProps) => { + const openPricingModal = useCallback((telemetryProps?: TelemetryProps) => { + let category; + if (isCloud) { category = TELEMETRY_CATEGORIES.CLOUD_PRICING; } else { @@ -36,5 +38,7 @@ export default function useOpenPricingModal() { callerCTA: telemetryProps?.trackingLocation, }, })); - }; + }, [dispatch, isCloud]); + + return openPricingModal; } diff --git a/webapp/channels/src/components/common/hooks/useOpenSalesLink.ts b/webapp/channels/src/components/common/hooks/useOpenSalesLink.ts index 2a88fd39f0..4f03a9cd59 100644 --- a/webapp/channels/src/components/common/hooks/useOpenSalesLink.ts +++ b/webapp/channels/src/components/common/hooks/useOpenSalesLink.ts @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {useCallback} from 'react'; import {useSelector} from 'react-redux'; import {getCloudCustomer, isCurrentLicenseCloud} from 'mattermost-redux/selectors/entities/cloud'; @@ -9,6 +10,8 @@ import {getCurrentUser} from 'mattermost-redux/selectors/entities/users'; import {LicenseLinks} from 'utils/constants'; import {buildMMURL, goToMattermostContactSalesForm} from 'utils/contact_support_sales'; +const utmSource = 'mattermost'; + export default function useOpenSalesLink(): [() => void, string] { const isCloud = useSelector(isCurrentLicenseCloud); const customer = useSelector(getCloudCustomer); @@ -17,7 +20,6 @@ export default function useOpenSalesLink(): [() => void, string] { let firstName = ''; let lastName = ''; let companyName = ''; - const utmSource = 'mattermost'; let utmMedium = 'in-product'; if (isCloud && customer) { @@ -31,8 +33,9 @@ export default function useOpenSalesLink(): [() => void, string] { } const contactSalesLink = buildMMURL(LicenseLinks.CONTACT_SALES, firstName, lastName, companyName, customerEmail, utmSource, utmMedium); - const goToSalesLinkFunc = () => { + const goToSalesLinkFunc = useCallback(() => { goToMattermostContactSalesForm(firstName, lastName, companyName, customerEmail, utmSource, utmMedium); - }; + }, [firstName, lastName, companyName, customerEmail, utmMedium]); + return [goToSalesLinkFunc, contactSalesLink]; } diff --git a/webapp/channels/src/components/common/hooks/useOpenStartTrialFormModal.ts b/webapp/channels/src/components/common/hooks/useOpenStartTrialFormModal.ts index 79ac7b5391..ecb5de6a40 100644 --- a/webapp/channels/src/components/common/hooks/useOpenStartTrialFormModal.ts +++ b/webapp/channels/src/components/common/hooks/useOpenStartTrialFormModal.ts @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {useCallback} from 'react'; import {useDispatch} from 'react-redux'; import {openModal} from 'actions/views/modals'; @@ -13,7 +14,7 @@ import type {TelemetryProps} from './useOpenPricingModal'; export default function useOpenStartTrialFormModal() { const dispatch = useDispatch(); - return (telemetryProps?: TelemetryProps, onClose?: () => void) => { + return useCallback((telemetryProps?: TelemetryProps, onClose?: () => void) => { dispatch(openModal({ modalId: ModalIdentifiers.START_TRIAL_FORM_MODAL, dialogType: StartTrialFormModal, @@ -22,5 +23,5 @@ export default function useOpenStartTrialFormModal() { onClose, }, })); - }; + }, [dispatch]); } diff --git a/webapp/channels/src/components/common/hooks/useOpenZendeskForm.ts b/webapp/channels/src/components/common/hooks/useOpenZendeskForm.ts index 71d4abf389..a93bdbf1bb 100644 --- a/webapp/channels/src/components/common/hooks/useOpenZendeskForm.ts +++ b/webapp/channels/src/components/common/hooks/useOpenZendeskForm.ts @@ -1,12 +1,12 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {useCallback} from 'react'; import {useSelector} from 'react-redux'; import {getCloudCustomer} from 'mattermost-redux/selectors/entities/cloud'; -import {getCurrentUser} from 'mattermost-redux/selectors/entities/users'; -import {getCloudSupportLink, getSelfHostedSupportLink, goToCloudSupportForm, goToSelfHostedSupportForm} from 'utils/contact_support_sales'; +import {getCloudSupportLink, goToCloudSupportForm} from 'utils/contact_support_sales'; export function useOpenCloudZendeskSupportForm(subject: string, description: string): [() => void, string] { const customer = useSelector(getCloudCustomer); @@ -14,14 +14,9 @@ export function useOpenCloudZendeskSupportForm(subject: string, description: str const url = getCloudSupportLink(customerEmail, subject, description, window.location.host); - return [() => goToCloudSupportForm(customerEmail, subject, description, window.location.host), url]; -} - -export function useOpenSelfHostedZendeskSupportForm(subject: string): [() => void, string] { - const currentUser = useSelector(getCurrentUser); - const customerEmail = currentUser.email || ''; - - const url = getSelfHostedSupportLink(customerEmail, subject); - - return [() => goToSelfHostedSupportForm(customerEmail, subject), url]; + const openContactSupport = useCallback( + () => goToCloudSupportForm(customerEmail, subject, description, window.location.host), + [customerEmail, subject, description], + ); + return [openContactSupport, url]; } diff --git a/webapp/channels/src/components/common/hooks/usePreference.ts b/webapp/channels/src/components/common/hooks/usePreference.ts index 968303739e..992775a533 100644 --- a/webapp/channels/src/components/common/hooks/usePreference.ts +++ b/webapp/channels/src/components/common/hooks/usePreference.ts @@ -27,7 +27,7 @@ export default function usePreference(category: string, name: string): [string | value, }; return dispatch(savePreferences(userId, [preference])); - }, [category, name, userId]); + }, [category, dispatch, name, userId]); return useMemo(() => ([preferenceValue, setPreference]), [preferenceValue, setPreference]); } diff --git a/webapp/channels/src/components/common/hooks/useSavePreferences.ts b/webapp/channels/src/components/common/hooks/useSavePreferences.ts index 4be5d47afe..9a140c04ab 100644 --- a/webapp/channels/src/components/common/hooks/useSavePreferences.ts +++ b/webapp/channels/src/components/common/hooks/useSavePreferences.ts @@ -18,7 +18,7 @@ export default function useSavePreferences(): (preferences: MinimalPreferenceTyp const preferencesList = ((preferences as MinimalPreferenceType[]).length ? preferences : [preferences]) as MinimalPreferenceType[]; const preferencesListWithUserId: PreferenceType[] = preferencesList.map((x) => ({...x, user_id: userId})); dispatch(savePreferences(userId, preferencesListWithUserId)); - }, [userId]); + }, [dispatch, userId]); } type MinimalBoolPreferenceType = Omit & {value?: boolean} diff --git a/webapp/channels/src/components/common/hooks/useTelemetryIdentifySync.tsx b/webapp/channels/src/components/common/hooks/useTelemetryIdentifySync.tsx index 6a204f91b5..68b5716c2c 100644 --- a/webapp/channels/src/components/common/hooks/useTelemetryIdentifySync.tsx +++ b/webapp/channels/src/components/common/hooks/useTelemetryIdentifySync.tsx @@ -24,8 +24,6 @@ function useTelemetryIdentitySync() { Client4.setUserRoles(userRoles); } }, [userId, userRoles]); - - return null; } export default useTelemetryIdentitySync; diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json index e7f106d084..6d7fff1559 100644 --- a/webapp/channels/src/i18n/en.json +++ b/webapp/channels/src/i18n/en.json @@ -4444,6 +4444,11 @@ "no_results.user_groups.title": "No groups yet", "notification.crt": "Reply in {title}", "notification.dm": "Direct Message", + "notify_admin_to_upgrade_cta.notify-admin.already_notified": "Already notified!", + "notify_admin_to_upgrade_cta.notify-admin.failed": "Try again later!", + "notify_admin_to_upgrade_cta.notify-admin.notified": "Admin notified!", + "notify_admin_to_upgrade_cta.notify-admin.notify": "Notify your admin", + "notify_admin_to_upgrade_cta.notify-admin.notifying": "Notifying...", "notify_all.confirm": "Confirm", "notify_all.title.confirm": "Confirm Sending Notifications to Entire Channel", "notify_all.title.confirm_groups": "Confirm sending notifications to groups", diff --git a/webapp/channels/src/utils/contact_support_sales.ts b/webapp/channels/src/utils/contact_support_sales.ts index 1899d599a6..a21121cf23 100644 --- a/webapp/channels/src/utils/contact_support_sales.ts +++ b/webapp/channels/src/utils/contact_support_sales.ts @@ -40,24 +40,6 @@ export const buildZendeskSupportForm = (form: ZendeskSupportForm, formFieldIDs: return formUrl; }; -export const goToSelfHostedSupportForm = (email: string, subject: string) => { - const form = ZendeskSupportForm.SELF_HOSTED_SUPPORT_FORM; - const url = buildZendeskSupportForm(form, [ - {id: ZendeskFormFieldIDs.EMAIL, val: email}, - {id: ZendeskFormFieldIDs.SUBJECT, val: subject}, - ]); - window.open(url, '_blank'); -}; - -export const getSelfHostedSupportLink = (email: string, subject: string) => { - const form = ZendeskSupportForm.SELF_HOSTED_SUPPORT_FORM; - const url = buildZendeskSupportForm(form, [ - {id: ZendeskFormFieldIDs.EMAIL, val: email}, - {id: ZendeskFormFieldIDs.SUBJECT, val: subject}, - ]); - return url; -}; - export const goToCloudSupportForm = (email: string, subject: string, description: string, workspaceURL: string) => { const form = ZendeskSupportForm.CLOUD_SUPPORT_FORM; let url = buildZendeskSupportForm(form, [