diff --git a/static/app/components/featureFeedback/feedbackModal.tsx b/static/app/components/featureFeedback/feedbackModal.tsx index 93e2963f327d..0dbb6179b2c1 100644 --- a/static/app/components/featureFeedback/feedbackModal.tsx +++ b/static/app/components/featureFeedback/feedbackModal.tsx @@ -1,4 +1,4 @@ -import {Fragment, useCallback, useMemo, useState} from 'react'; +import {createContext, Fragment, useCallback, useContext, useMemo, useState} from 'react'; import {css, useTheme} from '@emotion/react'; import type {Event} from '@sentry/core'; import { @@ -83,6 +83,118 @@ export type FeedbackModalProps = ( useNewUserFeedback?: boolean; }; +type FeedbackModalContextValue = { + Body: ModalRenderProps['Body']; + Footer: ModalRenderProps['Footer']; + Header: ModalRenderProps['Header']; + closeModal: () => void; + handleSubmit: (submitEventData?: Event) => void; + isCustomChildren: boolean; + isScreenSmall: boolean; + isSelfHosted: boolean; + state: Data; +}; + +const FeedbackModalContext = createContext(null); + +function useFeedbackModalContext(): FeedbackModalContextValue { + const ctx = useContext(FeedbackModalContext); + if (!ctx) { + throw new Error('useFeedbackModalContext must be used within FeedbackModal'); + } + return ctx; +} + +function FeedbackModalHeader({children: headerChildren}: {children: React.ReactNode}) { + const {Header} = useFeedbackModalContext(); + return ( +
+

{headerChildren}

+
+ ); +} + +type FooterProps = { + onBack?: () => void; + onNext?: () => void; + primaryDisabledReason?: string; + secondaryAction?: React.ReactNode; + submitEventData?: Event; +}; + +function FeedbackModalFooter({ + onBack, + onNext, + submitEventData, + primaryDisabledReason, + secondaryAction, +}: FooterProps) { + const {Footer, closeModal, handleSubmit, isScreenSmall, isCustomChildren, state} = + useFeedbackModalContext(); + return ( +
+ {secondaryAction && ( + + {secondaryAction} + + )} + {onBack && ( + + + + )} + + + + +
+ ); +} + +type BodyProps = { + children: React.ReactNode; + showSelfHostedMessage?: boolean; +}; + +function FeedbackModalBody({ + children: bodyChildren, + showSelfHostedMessage = true, +}: BodyProps) { + const {Body, isSelfHosted} = useFeedbackModalContext(); + return ( + + {bodyChildren} + {isSelfHosted && showSelfHostedMessage && ( + + + {tct( + "You agree that any feedback you submit is subject to Sentry's [privacyPolicy:Privacy Policy] and Sentry may use such feedback without restriction or obligation.", + { + privacyPolicy: , + } + )} + + + )} + + ); +} + /** * A modal that allows users to submit feedback to Sentry (feedbacks project). * @@ -194,156 +306,100 @@ export function FeedbackModal({ ] ); - const ModalHeader = useCallback( - ({children: headerChildren}: {children: React.ReactNode}) => { - return ( -
-

{headerChildren}

-
- ); - }, - [Header] - ); - - const ModalFooter = useCallback( - ({ - onBack, - onNext, - submitEventData, - primaryDisabledReason, - secondaryAction, - }: Parameters['Footer']>[0]) => { - return ( -
- {secondaryAction && ( - - {secondaryAction} - - )} - {onBack && ( - - - - )} - - - - -
- ); - }, - [Footer, isScreenSmall, closeModal, handleSubmit, state, props.children] - ); - - const ModalBody = useCallback( - ({ - children: bodyChildren, - showSelfHostedMessage = true, - }: Parameters['Body']>[0]) => { - return ( - - {bodyChildren} - {isSelfHosted && showSelfHostedMessage && ( - - - {tct( - "You agree that any feedback you submit is subject to Sentry's [privacyPolicy:Privacy Policy] and Sentry may use such feedback without restriction or obligation.", - { - privacyPolicy: , - } - )} - - - )} - - ); - }, - [Body, isSelfHosted] - ); - function handleFieldChange(field: Field, value: T[Field]) { const newState = cloneDeep(state); newState[field] = value; setState(newState); } + const contextValue: FeedbackModalContextValue = useMemo( + () => ({ + Header, + Body, + Footer, + closeModal, + handleSubmit, + isSelfHosted, + isScreenSmall, + isCustomChildren: props.children !== undefined, + state, + }), + [ + Header, + Body, + Footer, + closeModal, + handleSubmit, + isSelfHosted, + isScreenSmall, + props.children, + state, + ] + ); + if (props.children === undefined) { const feedbackTypes = props.feedbackTypes ?? defaultFeedbackTypes; return ( - - {t('Submit Feedback')} - - ({ - value: index, - label: feedbackType, - }))} - placeholder={t('Select type of feedback')} - value={state.subject} - onChange={(value: any) => setState({...state, subject: value})} - flexibleControlStateSize - stacked - required - /> - -