Skip to content

Commit

Permalink
Merge branch 'antonis/3859-newCaptureFeedbackAPI-Form' into antonis/4…
Browse files Browse the repository at this point in the history
…358-Feedback-Form-Autoinject

# Conflicts:
#	packages/core/src/js/feedback/FeedbackForm.tsx
  • Loading branch information
antonis committed Dec 13, 2024
2 parents 8b9f4d5 + fd2e317 commit 0261e04
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 81 deletions.
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@
});
```

- Adds feedback form (beta) ([#4320](https://github.com/getsentry/sentry-react-native/pull/4328))
- User Feedback From Component Beta ([#4320](https://github.com/getsentry/sentry-react-native/pull/4328))

To collect user feedback from inside your application add the `FeedbackFrom` component.

You can add the form component in your UI like:
```jsx
import { FeedbackForm } from "@sentry/react-native";
...
Expand All @@ -57,7 +58,6 @@

- Export `Span` type from `@sentry/types` ([#4345](https://github.com/getsentry/sentry-react-native/pull/4345))


### Fixes

- Return `lastEventId` export from `@sentry/core` ([#4315](https://github.com/getsentry/sentry-react-native/pull/4315))
Expand Down
20 changes: 14 additions & 6 deletions packages/core/src/js/feedback/FeedbackForm.styles.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,60 @@
import type { FeedbackFormStyles } from './FeedbackForm.types';

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

const defaultStyles: FeedbackFormStyles = {
container: {
flex: 1,
padding: 20,
backgroundColor: '#fff',
backgroundColor: BACKROUND_COLOR,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
textAlign: 'center',
color: FORGROUND_COLOR,
},
label: {
marginBottom: 4,
fontSize: 16,
color: FORGROUND_COLOR,
},
input: {
height: 50,
borderColor: '#ccc',
borderColor: BORDER_COLOR,
borderWidth: 1,
borderRadius: 5,
paddingHorizontal: 10,
marginBottom: 15,
fontSize: 16,
color: FORGROUND_COLOR,
},
textArea: {
height: 100,
textAlignVertical: 'top',
color: FORGROUND_COLOR,
},
submitButton: {
backgroundColor: '#6a1b9a',
backgroundColor: PURPLE,
paddingVertical: 15,
borderRadius: 5,
alignItems: 'center',
marginBottom: 10,
},
submitText: {
color: '#fff',
color: BACKROUND_COLOR,
fontSize: 18,
fontWeight: 'bold',
},
cancelButton: {
paddingVertical: 15,
alignItems: 'center',
},
cancelText: {
color: '#6a1b9a',
color: FORGROUND_COLOR,
fontSize: 16,
},
};
Expand Down
161 changes: 94 additions & 67 deletions packages/core/src/js/feedback/FeedbackForm.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { captureFeedback, lastEventId, logger } from '@sentry/core';
import { captureFeedback, getCurrentScope, lastEventId, logger } from '@sentry/core';
import type { SendFeedbackParams } from '@sentry/types';
import * as React from 'react';
import type { KeyboardTypeOptions } from 'react-native';
import { Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
import {
Alert,
Keyboard,
KeyboardAvoidingView,
SafeAreaView,
ScrollView,
Text,
TextInput,
TouchableOpacity,
TouchableWithoutFeedback,
View
} from 'react-native';

import { defaultConfiguration } from './defaults';
import defaultStyles from './FeedbackForm.styles';
Expand Down Expand Up @@ -38,14 +49,23 @@ export const showFeedbackForm = (navigation: Navigation): void => {
* Implements a feedback form screen that sends feedback to Sentry using Sentry.captureFeedback.
*/
export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFormState> {
private _config: FeedbackFormProps;

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

const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...props };
const currentUser = {
useSentryUser: {
email: getCurrentScope().getUser().email || '',
name: getCurrentScope().getUser().name || '',
}
}

this._config = { ...defaultConfiguration, ...currentUser, ...props };
this.state = {
isVisible: true,
name: config.useSentryUser.name,
email: config.useSentryUser.email,
name: this._config.useSentryUser.name,
email: this._config.useSentryUser.email,
description: '',
};
}
Expand All @@ -59,20 +79,19 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor

public handleFeedbackSubmit: () => void = () => {
const { name, email, description } = this.state;
const { onFormClose } = { ...defaultConfiguration, ...this.props };
const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...this.props };
const text: FeedbackTextConfiguration = { ...defaultConfiguration, ...this.props };
const { onFormClose } = this._config;
const text: FeedbackTextConfiguration = this._config;

const trimmedName = name?.trim();
const trimmedEmail = email?.trim();
const trimmedDescription = description?.trim();

if ((config.isNameRequired && !trimmedName) || (config.isEmailRequired && !trimmedEmail) || !trimmedDescription) {
if ((this._config.isNameRequired && !trimmedName) || (this._config.isEmailRequired && !trimmedEmail) || !trimmedDescription) {
Alert.alert(text.errorTitle, text.formError);
return;
}

if ((config.isEmailRequired || trimmedEmail.length > 0) && !this._isValidEmail(trimmedEmail)) {
if (this._config.shouldValidateEmail && (this._config.isEmailRequired || trimmedEmail.length > 0) && !this._isValidEmail(trimmedEmail)) {
Alert.alert(text.errorTitle, text.emailError);
return;
}
Expand All @@ -97,9 +116,9 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
*/
public render(): React.ReactNode {
const { name, email, description } = this.state;
const { onFormClose } = { ...defaultConfiguration, ...this.props };
const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...this.props };
const text: FeedbackTextConfiguration = { ...defaultConfiguration, ...this.props };
const { onFormClose } = this._config;
const config: FeedbackGeneralConfiguration = this._config;
const text: FeedbackTextConfiguration = this._config;
const styles: FeedbackFormStyles = { ...defaultStyles, ...this.props.styles };
const onCancel = (): void => {
onFormClose();
Expand All @@ -111,60 +130,68 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
}

return (
<View style={styles.container}>
<Text style={styles.title}>{text.formTitle}</Text>

{config.showName && (
<>
<Text style={styles.label}>
{text.nameLabel}
{config.isNameRequired && ` ${text.isRequiredLabel}`}
</Text>
<TextInput
style={styles.input}
placeholder={text.namePlaceholder}
value={name}
onChangeText={(value) => this.setState({ name: value })}
/>
</>
)}

{config.showEmail && (
<>
<Text style={styles.label}>
{text.emailLabel}
{config.isEmailRequired && ` ${text.isRequiredLabel}`}
</Text>
<TextInput
style={styles.input}
placeholder={text.emailPlaceholder}
keyboardType={'email-address' as KeyboardTypeOptions}
value={email}
onChangeText={(value) => this.setState({ email: value })}
/>
</>
)}

<Text style={styles.label}>
{text.messageLabel}
{` ${text.isRequiredLabel}`}
</Text>
<TextInput
style={[styles.input, styles.textArea]}
placeholder={text.messagePlaceholder}
value={description}
onChangeText={(value) => this.setState({ description: value })}
multiline
/>

<TouchableOpacity style={styles.submitButton} onPress={this.handleFeedbackSubmit}>
<Text style={styles.submitText}>{text.submitButtonLabel}</Text>
</TouchableOpacity>

<TouchableOpacity style={styles.cancelButton} onPress={onCancel}>
<Text style={styles.cancelText}>{text.cancelButtonLabel}</Text>
</TouchableOpacity>
</View>
<SafeAreaView style={[styles.container, { padding: 0 }]}>
<KeyboardAvoidingView behavior={'padding'} style={[styles.container, { padding: 0 }]}>
<ScrollView>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.container}>
<Text style={styles.title}>{text.formTitle}</Text>

{config.showName && (
<>
<Text style={styles.label}>
{text.nameLabel}
{config.isNameRequired && ` ${text.isRequiredLabel}`}
</Text>
<TextInput
style={styles.input}
placeholder={text.namePlaceholder}
value={name}
onChangeText={(value) => this.setState({ name: value })}
/>
</>
)}

{config.showEmail && (
<>
<Text style={styles.label}>
{text.emailLabel}
{config.isEmailRequired && ` ${text.isRequiredLabel}`}
</Text>
<TextInput
style={styles.input}
placeholder={text.emailPlaceholder}
keyboardType={'email-address' as KeyboardTypeOptions}
value={email}
onChangeText={(value) => this.setState({ email: value })}
/>
</>
)}

<Text style={styles.label}>
{text.messageLabel}
{` ${text.isRequiredLabel}`}
</Text>
<TextInput
style={[styles.input, styles.textArea]}
placeholder={text.messagePlaceholder}
value={description}
onChangeText={(value) => this.setState({ description: value })}
multiline
/>

<TouchableOpacity style={styles.submitButton} onPress={this.handleFeedbackSubmit}>
<Text style={styles.submitText}>{text.submitButtonLabel}</Text>
</TouchableOpacity>

<TouchableOpacity style={styles.cancelButton} onPress={onCancel}>
<Text style={styles.cancelText}>{text.cancelButtonLabel}</Text>
</TouchableOpacity>
</View>
</TouchableWithoutFeedback>
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
);
}

Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/js/feedback/FeedbackForm.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ export interface FeedbackGeneralConfiguration {
*/
isEmailRequired?: boolean;

/**
* Should the email field be validated?
*/
shouldValidateEmail?: boolean;

/**
* Should the name field be required?
*/
Expand Down
6 changes: 1 addition & 5 deletions packages/core/src/js/feedback/defaults.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { getCurrentScope } from '@sentry/core';
import { Alert } from 'react-native';

import type { FeedbackFormProps } from './FeedbackForm.types';
Expand Down Expand Up @@ -31,13 +30,10 @@ export const defaultConfiguration: Partial<FeedbackFormProps> = {

// FeedbackGeneralConfiguration
isEmailRequired: false,
shouldValidateEmail: true,
isNameRequired: false,
showEmail: true,
showName: true,
useSentryUser: {
email: getCurrentScope().getUser().email || '',
name: getCurrentScope().getUser().name || '',
},

// FeedbackTextConfiguration
cancelButtonLabel: CANCEL_BUTTON_LABEL,
Expand Down

0 comments on commit 0261e04

Please sign in to comment.