Skip to content

chore(feedback): Use Widget instead of Form #4547

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 17, 2025
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
8 changes: 4 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@

### Features

- User Feedback Form Component Beta ([#4435](https://github.com/getsentry/sentry-react-native/pull/4435))
- User Feedback Widget Beta ([#4435](https://github.com/getsentry/sentry-react-native/pull/4435))

To collect user feedback from inside your application call `Sentry.showFeedbackForm()` or add the `FeedbackForm` component.
To collect user feedback from inside your application call `Sentry.showFeedbackWidget()` or add the `FeedbackWidget` component.

```jsx
import { FeedbackForm } from "@sentry/react-native";
import { FeedbackWidget } from "@sentry/react-native";
...
<FeedbackForm/>
<FeedbackWidget/>
```

### Fixes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { ViewStyle } from 'react-native';

import type { FeedbackFormStyles } from './FeedbackForm.types';
import type { FeedbackWidgetStyles } from './FeedbackWidget.types';

const PURPLE = 'rgba(88, 74, 192, 1)';
const FORGROUND_COLOR = '#2b2233';
const BACKROUND_COLOR = '#ffffff';
const BORDER_COLOR = 'rgba(41, 35, 47, 0.13)';

const defaultStyles: FeedbackFormStyles = {
const defaultStyles: FeedbackWidgetStyles = {
container: {
flex: 1,
padding: 20,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,23 @@ import {
View
} from 'react-native';

import { NATIVE } from './../wrapper';
import { NATIVE } from '../wrapper';
import { sentryLogo } from './branding';
import { defaultConfiguration } from './defaults';
import defaultStyles from './FeedbackForm.styles';
import type { FeedbackFormProps, FeedbackFormState, FeedbackFormStyles, FeedbackGeneralConfiguration, FeedbackTextConfiguration, ImagePickerConfiguration } from './FeedbackForm.types';
import defaultStyles from './FeedbackWidget.styles';
import type { FeedbackGeneralConfiguration, FeedbackTextConfiguration, FeedbackWidgetProps, FeedbackWidgetState, FeedbackWidgetStyles, ImagePickerConfiguration } from './FeedbackWidget.types';
import { isValidEmail } from './utils';

/**
* @beta
* Implements a feedback form screen that sends feedback to Sentry using Sentry.captureFeedback.
*/
export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFormState> {
public static defaultProps: Partial<FeedbackFormProps> = {
export class FeedbackWidget extends React.Component<FeedbackWidgetProps, FeedbackWidgetState> {
public static defaultProps: Partial<FeedbackWidgetProps> = {
...defaultConfiguration
}

public constructor(props: FeedbackFormProps) {
public constructor(props: FeedbackWidgetProps) {
super(props);

const currentUser = {
Expand Down Expand Up @@ -159,7 +159,7 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
const config: FeedbackGeneralConfiguration = this.props;
const imagePickerConfiguration: ImagePickerConfiguration = this.props;
const text: FeedbackTextConfiguration = this.props;
const styles: FeedbackFormStyles = { ...defaultStyles, ...this.props.styles };
const styles: FeedbackWidgetStyles = { ...defaultStyles, ...this.props.styles };
const onCancel = (): void => {
onFormClose();
this.setState({ isVisible: false });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import type { ImageStyle, TextStyle, ViewStyle } from 'react-native';
/**
* The props for the feedback form
*/
export interface FeedbackFormProps
export interface FeedbackWidgetProps
extends FeedbackGeneralConfiguration,
FeedbackTextConfiguration,
FeedbackCallbacks,
ImagePickerConfiguration {
styles?: FeedbackFormStyles;
styles?: FeedbackWidgetStyles;
}

/**
Expand Down Expand Up @@ -226,7 +226,7 @@ export interface ImagePicker {
/**
* The styles for the feedback form
*/
export interface FeedbackFormStyles {
export interface FeedbackWidgetStyles {
container?: ViewStyle;
title?: TextStyle;
label?: TextStyle;
Expand All @@ -245,7 +245,7 @@ export interface FeedbackFormStyles {
/**
* The state of the feedback form
*/
export interface FeedbackFormState {
export interface FeedbackWidgetState {
isVisible: boolean;
name: string;
email: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import { logger } from '@sentry/core';
import * as React from 'react';
import { Animated, KeyboardAvoidingView, Modal, PanResponder, Platform } from 'react-native';

import { FeedbackForm } from './FeedbackForm';
import { modalBackground, modalSheetContainer, modalWrapper } from './FeedbackForm.styles';
import type { FeedbackFormStyles } from './FeedbackForm.types';
import { FeedbackWidget } from './FeedbackWidget';
import { modalBackground, modalSheetContainer, modalWrapper } from './FeedbackWidget.styles';
import type { FeedbackWidgetStyles } from './FeedbackWidget.types';
import { getFeedbackOptions } from './integration';
import { isModalSupported } from './utils';

const PULL_DOWN_CLOSE_THREESHOLD = 200;
const PULL_DOWN_ANDROID_ACTIVATION_HEIGHT = 150;

class FeedbackFormManager {
class FeedbackWidgetManager {
private static _isVisible = false;
private static _setVisibility: (visible: boolean) => void;

Expand All @@ -38,25 +38,24 @@ class FeedbackFormManager {
}
}

interface FeedbackFormProviderProps {
interface FeedbackWidgetProviderProps {
children: React.ReactNode;
styles?: FeedbackFormStyles;
styles?: FeedbackWidgetStyles;
}

interface FeedbackFormProviderState {
interface FeedbackWidgetProviderState {
isVisible: boolean;
backgroundOpacity: Animated.Value;
panY: Animated.Value;
}

class FeedbackFormProvider extends React.Component<FeedbackFormProviderProps> {
public state: FeedbackFormProviderState = {
class FeedbackWidgetProvider extends React.Component<FeedbackWidgetProviderProps> {
public state: FeedbackWidgetProviderState = {
isVisible: false,
backgroundOpacity: new Animated.Value(0),
panY: new Animated.Value(0),
};


private _panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, _gestureState) => {
// On Android allow pulling down only from the top to avoid breaking native gestures
Expand Down Expand Up @@ -88,15 +87,15 @@ class FeedbackFormProvider extends React.Component<FeedbackFormProviderProps> {
},
});

public constructor(props: FeedbackFormProviderProps) {
public constructor(props: FeedbackWidgetProviderProps) {
super(props);
FeedbackFormManager.initialize(this._setVisibilityFunction);
FeedbackWidgetManager.initialize(this._setVisibilityFunction);
}

/**
* Animates the background opacity when the modal is shown.
*/
public componentDidUpdate(_prevProps: any, prevState: FeedbackFormProviderState): void {
public componentDidUpdate(_prevProps: any, prevState: FeedbackWidgetProviderState): void {
if (!prevState.isVisible && this.state.isVisible) {
Animated.timing(this.state.backgroundOpacity, {
toValue: 1,
Expand All @@ -113,7 +112,7 @@ class FeedbackFormProvider extends React.Component<FeedbackFormProviderProps> {
*/
public render(): React.ReactNode {
if (!isModalSupported()) {
logger.error('FeedbackForm Modal is not supported in React Native < 0.71 with Fabric renderer.');
logger.error('FeedbackWidget Modal is not supported in React Native < 0.71 with Fabric renderer.');
return <>{this.props.children}</>;
}

Expand All @@ -140,7 +139,7 @@ class FeedbackFormProvider extends React.Component<FeedbackFormProviderProps> {
style={[modalSheetContainer, { transform: [{ translateY: this.state.panY }] }]}
{...this._panResponder.panHandlers}
>
<FeedbackForm {...getFeedbackOptions()}
<FeedbackWidget {...getFeedbackOptions()}
onFormClose={this._handleClose}
onFormSubmitted={this._handleClose}
/>
Expand All @@ -161,13 +160,13 @@ class FeedbackFormProvider extends React.Component<FeedbackFormProviderProps> {
};

private _handleClose = (): void => {
FeedbackFormManager.hide();
FeedbackWidgetManager.hide();
this.setState({ isVisible: false });
};
}

const showFeedbackForm = (): void => {
FeedbackFormManager.show();
const showFeedbackWidget = (): void => {
FeedbackWidgetManager.show();
};

export { showFeedbackForm, FeedbackFormProvider };
export { showFeedbackWidget, FeedbackWidgetProvider };
4 changes: 2 additions & 2 deletions packages/core/src/js/feedback/defaults.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Alert } from 'react-native';

import type { FeedbackFormProps } from './FeedbackForm.types';
import type { FeedbackWidgetProps } from './FeedbackWidget.types';

const FORM_TITLE = 'Report a Bug';
const NAME_PLACEHOLDER = 'Your Name';
Expand All @@ -20,7 +20,7 @@ const ADD_SCREENSHOT_LABEL = 'Add a screenshot';
const REMOVE_SCREENSHOT_LABEL = 'Remove screenshot';
const GENERIC_ERROR_TEXT = 'Unable to send feedback due to an unexpected error.';

export const defaultConfiguration: Partial<FeedbackFormProps> = {
export const defaultConfiguration: Partial<FeedbackWidgetProps> = {
// FeedbackCallbacks
onFormOpen: () => {
// Does nothing by default
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/js/feedback/integration.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type { Integration } from '@sentry/core';

import type { FeedbackFormProps } from './FeedbackForm.types';
import type { FeedbackWidgetProps } from './FeedbackWidget.types';

export const FEEDBACK_FORM_INTEGRATION_NAME = 'MobileFeedback';

type FeedbackIntegration = Integration & {
options: Partial<FeedbackFormProps>;
options: Partial<FeedbackWidgetProps>;
};

let savedOptions: Partial<FeedbackFormProps> = {};
let savedOptions: Partial<FeedbackWidgetProps> = {};

export const feedbackIntegration = (initOptions: FeedbackFormProps = {}): FeedbackIntegration => {
export const feedbackIntegration = (initOptions: FeedbackWidgetProps = {}): FeedbackIntegration => {
savedOptions = initOptions;

return {
Expand All @@ -19,4 +19,4 @@ export const feedbackIntegration = (initOptions: FeedbackFormProps = {}): Feedba
};
};

export const getFeedbackOptions = (): Partial<FeedbackFormProps> => savedOptions;
export const getFeedbackOptions = (): Partial<FeedbackWidgetProps> => savedOptions;
4 changes: 2 additions & 2 deletions packages/core/src/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,5 @@ export type { TimeToDisplayProps } from './tracing';

export { Mask, Unmask } from './replay/CustomMask';

export { FeedbackForm } from './feedback/FeedbackForm';
export { showFeedbackForm } from './feedback/FeedbackFormManager';
export { FeedbackWidget } from './feedback/FeedbackWidget';
export { showFeedbackWidget } from './feedback/FeedbackWidgetManager';
6 changes: 3 additions & 3 deletions packages/core/src/js/sdk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import * as React from 'react';

import { ReactNativeClient } from './client';
import { FeedbackFormProvider } from './feedback/FeedbackFormManager';
import { FeedbackWidgetProvider } from './feedback/FeedbackWidgetManager';
import { getDevServer } from './integrations/debugsymbolicatorutils';
import { getDefaultIntegrations } from './integrations/default';
import type { ReactNativeClientOptions, ReactNativeOptions, ReactNativeWrapperOptions } from './options';
Expand Down Expand Up @@ -164,9 +164,9 @@ export function wrap<P extends Record<string, unknown>>(
return (
<TouchEventBoundary {...(options?.touchEventBoundaryProps ?? {})}>
<ReactNativeProfiler {...profilerProps}>
<FeedbackFormProvider>
<FeedbackWidgetProvider>
<RootComponent {...appProps} />
</FeedbackFormProvider>
</FeedbackWidgetProvider>
</ReactNativeProfiler>
</TouchEventBoundary>
);
Expand Down
Loading
Loading