Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions static/app/components/featureFeedback/feedbackModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ export type FeedbackModalProps<T extends Data> = (
useNewUserFeedback?: boolean;
};

/**
* A modal that allows users to submit feedback to Sentry (feedbacks project).
*
* @deprecated Use `<FeedbackButton/>` instead.
*/
export function FeedbackModal<T extends Data>({
Header,
Body,
Expand Down
8 changes: 6 additions & 2 deletions static/app/components/featureFeedback/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ type FeatureFeedbackProps<T extends Data> = FeedbackModalProps<T> & {
secondaryAction?: React.ReactNode;
};

// Provides a button that, when clicked, opens a modal with a form that,
// when filled and submitted, will send feedback to Sentry (feedbacks project).
/**
* Provides a button that, when clicked, opens a modal with a form that,
* when filled and submitted, will send feedback to Sentry (feedbacks project).
*
* @deprecated Use `<FeedbackButton/>` instead.
*/
export function FeatureFeedback<T extends Data>({
buttonProps = {},
...props
Expand Down
27 changes: 0 additions & 27 deletions static/app/components/feedback/widget/feedbackWidgetButton.tsx

This file was deleted.

Copy link
Member Author

@ryan953 ryan953 Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was moved into feedbackButton/floatingFeedbackButton.tsx and got parts of useFeedbackWidget inserted into it along with a big docstring, which means git isn't tracking it as a move anymore.

This file was deleted.

54 changes: 0 additions & 54 deletions static/app/components/feedback/widget/useFeedbackWidget.tsx
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was moved into feedbackButton/feedbackButton.tsx and got parts of useFeedbackWidget inserted into it along with a big docstring, which means git isn't tracking it as a move anymore.

This file was deleted.

79 changes: 79 additions & 0 deletions static/app/components/feedbackButton/feedbackButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {useRef} from 'react';

import {Button, type ButtonProps} from 'sentry/components/core/button';
import {type UseFeedbackOptions} from 'sentry/components/feedbackButton/useFeedbackSDKIntegration';
import {IconMegaphone} from 'sentry/icons/iconMegaphone';
import {t} from 'sentry/locale';
import {useFeedbackForm} from 'sentry/utils/useFeedbackForm';

interface Props extends Omit<ButtonProps, 'icon'> {
feedbackOptions?: UseFeedbackOptions;
}

/**
* A button component that opens the Sentry feedback widget when clicked.
*
* Use this component to embed a feedback collection button anywhere in the UI where users
* might want to submit feedback, report issues, or share suggestions with your team.
*
* The feedback form will stay open even if the button itself it removed from the
* DOM. So you can add buttons inside of Dropdowns or Tooltips for example.
*
* The component will return null when the Feedback SDK integration is not enabled,
* like in self-hosted environments.
*
* It's strongly recommended to add the tags: `feedback.source` and `feedback.owner`
* and then setup an alert rule to notify you when feedback is submitted.
*
* @example
* // Mix of Button and Feedback props
* <FeedbackButton
* priority="primary"
* size="lg"
* feedbackOptions={{
* messagePlaceholder: 'Tell us what you think...',
* tags: {
* ['feedback.source']: 'issue-details'
* ['feedback.owner']: 'issues'
* }
* }}
* />
*
* @param feedbackOptions - Optional configuration to customize the feedback widget behavior,
* such as form labels, tags, or user metadata
*
* @param children - The content to display inside the button. If not provided, the default label 'Give Feedback' will be used.
*
* @param * - All standard Button props except `icon` (icon is fixed to megaphone).
* Includes size, priority, disabled, onClick handlers, etc.
*
* @returns A Button that opens the feedback widget on click, or null if feedback is not enabled
*/
export default function FeedbackButton({
feedbackOptions,
children,
...buttonProps
}: Props) {
const buttonRef = useRef<HTMLButtonElement>(null);
const openForm = useFeedbackForm();

// Do not show button if Feedback integration is not enabled
if (!openForm) {
return null;
}

return (
<Button
ref={buttonRef}
size="sm"
{...buttonProps}
icon={<IconMegaphone />}
onClick={e => {
openForm?.(feedbackOptions);
buttonProps.onClick?.(e);
}}
>
{children || t('Give Feedback')}
</Button>
);
}
43 changes: 43 additions & 0 deletions static/app/components/feedbackButton/floatingFeedbackButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {useEffect} from 'react';
import {css, Global, useTheme} from '@emotion/react';

import {useFeedbackSDKIntegration} from 'sentry/components/feedbackButton/useFeedbackSDKIntegration';

/**
* `<FloatingFeedbackButton /> renders a 'Give Feedback' button that floats in
* the bottom right corner of the page.
*
* This button can float overtop of content, but can also be helpful because it
* allows users to scroll anywhere and still be able to trigger the Feedback form
* which allows taking screenshots of what's visible on the page.
*/
export default function FloatingFeedbackButton() {
const theme = useTheme();
const {feedback, defaultOptions} = useFeedbackSDKIntegration();

useEffect(() => {
if (!feedback) {
return undefined;
}

const widget = feedback.createWidget(defaultOptions);
return () => {
widget.removeFromDom();
};
}, [feedback, defaultOptions]);

if (!feedback) {
return null;
}

// z-index needs to be below our indicators which is 10001
return (
<Global
styles={css`
#sentry-feedback {
--z-index: ${theme.zIndex.toast - 1};
}
`}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,23 @@ export type FeedbackIntegration = NonNullable<ReturnType<typeof Sentry.getFeedba

export type UseFeedbackOptions = Parameters<FeedbackIntegration['createForm']>[0];

export function useFeedback({
formTitle,
messagePlaceholder,
tags,
}: NonNullable<UseFeedbackOptions>): {
export function useFeedbackSDKIntegration(): {
defaultOptions: NonNullable<UseFeedbackOptions>;
feedback: FeedbackIntegration | undefined;
options: NonNullable<UseFeedbackOptions>;
} {
const config = useLegacyStore(ConfigStore);
const {state} = useAsyncSDKIntegrationStore();

const feedback = state.Feedback as FeedbackIntegration | undefined;

const options = useMemo(() => {
const defaultOptions = useMemo((): NonNullable<UseFeedbackOptions> => {
return {
colorScheme: config.theme === 'dark' ? ('dark' as const) : ('light' as const),
buttonLabel: t('Give Feedback'),
submitButtonLabel: t('Send Feedback'),
messagePlaceholder: messagePlaceholder ?? t('What did you expect?'),
formTitle: formTitle ?? t('Give Feedback'),
tags,
messagePlaceholder: t('What did you expect?'),
formTitle: t('Give Feedback'),
};
}, [config.theme, formTitle, messagePlaceholder, tags]);
}, [config.theme]);

return {feedback, options};
return {feedback, defaultOptions};
}
36 changes: 36 additions & 0 deletions static/app/components/feedbackButton/useFeedbackWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type {RefObject} from 'react';
import {useEffect} from 'react';

import {useFeedbackSDKIntegration} from 'sentry/components/feedbackButton/useFeedbackSDKIntegration';

interface Props {
buttonRef?: RefObject<HTMLButtonElement | null> | RefObject<HTMLAnchorElement | null>;
}

/**
* @deprecated This layer isn't needed. Call `useFeedbackSDKIntegration` or use `<FeedbackButton/>` or `<FloatingFeedbackButton/>`
*/
export default function useFeedbackWidget({buttonRef}: Props) {
Comment on lines +10 to +13
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is deprecated now. it's a layer that isn't providing a lot of value.

I've already inlined bits into FeedbackButton and FloatingFeedbackButton,
Next step is cleanup: there's one more callsite to go and remove.

const {feedback, defaultOptions} = useFeedbackSDKIntegration();

useEffect(() => {
if (!feedback) {
return undefined;
}

if (buttonRef) {
if (buttonRef.current) {
return feedback.attachTo(buttonRef.current, defaultOptions);
}
} else {
const widget = feedback.createWidget(defaultOptions);
return () => {
widget.removeFromDom();
};
}

return undefined;
}, [buttonRef, feedback, defaultOptions]);

return feedback;
}
4 changes: 2 additions & 2 deletions static/app/components/profiling/continuousProfileHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {useCallback, useMemo} from 'react';
import styled from '@emotion/styled';

import {LinkButton} from 'sentry/components/core/button/linkButton';
import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton';
import FeedbackButton from 'sentry/components/feedbackButton/feedbackButton';
import * as Layout from 'sentry/components/layouts/thirds';
import type {ProfilingBreadcrumbsProps} from 'sentry/components/profiling/profilingBreadcrumbs';
import {ProfilingBreadcrumbs} from 'sentry/components/profiling/profilingBreadcrumbs';
Expand Down Expand Up @@ -51,7 +51,7 @@ export function ContinuousProfileHeader({transaction}: ContinuousProfileHeader)
</SmallerProfilingBreadcrumbsWrapper>
</SmallerHeaderContent>
<StyledHeaderActions>
<FeedbackWidgetButton />
<FeedbackButton />
{transactionTarget && (
<LinkButton size="sm" onClick={handleGoToTransaction} to={transactionTarget}>
{t('Go to Trace')}
Expand Down
4 changes: 2 additions & 2 deletions static/app/components/profiling/profileHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {useCallback, useMemo} from 'react';
import styled from '@emotion/styled';

import {LinkButton} from 'sentry/components/core/button/linkButton';
import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton';
import FeedbackButton from 'sentry/components/feedbackButton/feedbackButton';
import * as Layout from 'sentry/components/layouts/thirds';
import type {ProfilingBreadcrumbsProps} from 'sentry/components/profiling/profilingBreadcrumbs';
import {ProfilingBreadcrumbs} from 'sentry/components/profiling/profilingBreadcrumbs';
Expand Down Expand Up @@ -90,7 +90,7 @@ function ProfileHeader({transaction, projectId, eventId}: ProfileHeaderProps) {
</SmallerProfilingBreadcrumbsWrapper>
</SmallerHeaderContent>
<StyledHeaderActions>
<FeedbackWidgetButton />
<FeedbackButton />
{transactionTarget && (
<LinkButton size="sm" onClick={handleGoToTransaction} to={transactionTarget}>
{t('Go to Trace')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {Button} from 'sentry/components/core/button';
import {Flex} from 'sentry/components/core/layout';
import {ExternalLink} from 'sentry/components/core/link';
import {Tooltip} from 'sentry/components/core/tooltip';
import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton';
import FeedbackButton from 'sentry/components/feedbackButton/feedbackButton';
import {useGlobalModal} from 'sentry/components/globalModal/useGlobalModal';
import {Hovercard} from 'sentry/components/hovercard';
import {DiffCompareContextProvider} from 'sentry/components/replays/diff/diffCompareContext';
Expand Down Expand Up @@ -88,8 +88,8 @@ export default function ReplayComparisonModal({
</AutoWideHovercard>
) : null}
{focusTrap ? (
<FeedbackWidgetButton
optionOverrides={{
<FeedbackButton
feedbackOptions={{
onFormOpen: () => {
focusTrap.pause();
},
Expand Down
Loading
Loading