Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 {useEffect, useRef} from 'react';

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

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 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 {feedback, options} = useFeedbackSDKIntegration({
optionOverrides: feedbackOptions,
});

useEffect(() => {
if (feedback && buttonRef.current) {
return feedback.attachTo(buttonRef.current, options);
}

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

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

return (
<Button ref={buttonRef} size="sm" {...buttonProps} icon={<IconMegaphone />}>
{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, options} = useFeedbackSDKIntegration();

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

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

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,11 +10,11 @@ export type FeedbackIntegration = NonNullable<ReturnType<typeof Sentry.getFeedba

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

export function useFeedback({
formTitle,
messagePlaceholder,
tags,
}: NonNullable<UseFeedbackOptions>): {
interface Props {
optionOverrides?: UseFeedbackOptions;
}

export function useFeedbackSDKIntegration({optionOverrides = {}}: Props = {}): {
feedback: FeedbackIntegration | undefined;
options: NonNullable<UseFeedbackOptions>;
} {
Expand All @@ -23,16 +23,15 @@ export function useFeedback({

const feedback = state.Feedback as FeedbackIntegration | undefined;

const options = useMemo(() => {
const options = 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'),
...optionOverrides,
};
}, [config.theme, formTitle, messagePlaceholder, tags]);
}, [config.theme, optionOverrides]);

return {feedback, options};
}
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, options} = useFeedbackSDKIntegration();

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

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

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

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