Skip to content

Commit 50c70c0

Browse files
authored
Merge 9831482 into fd7919f
2 parents fd7919f + 9831482 commit 50c70c0

File tree

9 files changed

+587
-0
lines changed

9 files changed

+587
-0
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,19 @@
3535
});
3636
```
3737

38+
- Adds feedback form (beta) ([#4320](https://github.com/getsentry/sentry-react-native/pull/4328))
39+
40+
You can add the form component in your UI like:
41+
```jsx
42+
import { FeedbackForm } from "@sentry/react-native";
43+
...
44+
<FeedbackForm/>
45+
```
46+
Check [the documentation](https://docs.sentry.io/platforms/react-native/user-feedback/) for more configuration options.
47+
3848
- Export `Span` type from `@sentry/types` ([#4345](https://github.com/getsentry/sentry-react-native/pull/4345))
3949

50+
4051
### Fixes
4152

4253
- Return `lastEventId` export from `@sentry/core` ([#4315](https://github.com/getsentry/sentry-react-native/pull/4315))
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type { FeedbackFormStyles } from './FeedbackForm.types';
2+
3+
const defaultStyles: FeedbackFormStyles = {
4+
container: {
5+
flex: 1,
6+
padding: 20,
7+
backgroundColor: '#fff',
8+
},
9+
title: {
10+
fontSize: 24,
11+
fontWeight: 'bold',
12+
marginBottom: 20,
13+
textAlign: 'center',
14+
},
15+
label: {
16+
marginBottom: 4,
17+
fontSize: 16,
18+
},
19+
input: {
20+
height: 50,
21+
borderColor: '#ccc',
22+
borderWidth: 1,
23+
borderRadius: 5,
24+
paddingHorizontal: 10,
25+
marginBottom: 15,
26+
fontSize: 16,
27+
},
28+
textArea: {
29+
height: 100,
30+
textAlignVertical: 'top',
31+
},
32+
submitButton: {
33+
backgroundColor: '#6a1b9a',
34+
paddingVertical: 15,
35+
borderRadius: 5,
36+
alignItems: 'center',
37+
marginBottom: 10,
38+
},
39+
submitText: {
40+
color: '#fff',
41+
fontSize: 18,
42+
fontWeight: 'bold',
43+
},
44+
cancelButton: {
45+
paddingVertical: 15,
46+
alignItems: 'center',
47+
},
48+
cancelText: {
49+
color: '#6a1b9a',
50+
fontSize: 16,
51+
},
52+
};
53+
54+
export default defaultStyles;
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { captureFeedback, lastEventId } from '@sentry/core';
2+
import type { SendFeedbackParams } from '@sentry/types';
3+
import * as React from 'react';
4+
import type { KeyboardTypeOptions } from 'react-native';
5+
import { Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
6+
7+
import { defaultConfiguration } from './defaults';
8+
import defaultStyles from './FeedbackForm.styles';
9+
import type { FeedbackFormProps, FeedbackFormState, FeedbackFormStyles,FeedbackGeneralConfiguration, FeedbackTextConfiguration } from './FeedbackForm.types';
10+
11+
/**
12+
* @beta
13+
* Implements a feedback form screen that sends feedback to Sentry using Sentry.captureFeedback.
14+
*/
15+
export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFormState> {
16+
public constructor(props: FeedbackFormProps) {
17+
super(props);
18+
19+
const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...props };
20+
this.state = {
21+
isVisible: true,
22+
name: config.useSentryUser.name,
23+
email: config.useSentryUser.email,
24+
description: '',
25+
};
26+
}
27+
28+
public handleFeedbackSubmit: () => void = () => {
29+
const { name, email, description } = this.state;
30+
const { onFormClose } = { ...defaultConfiguration, ...this.props };
31+
const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...this.props };
32+
const text: FeedbackTextConfiguration = { ...defaultConfiguration, ...this.props };
33+
34+
const trimmedName = name?.trim();
35+
const trimmedEmail = email?.trim();
36+
const trimmedDescription = description?.trim();
37+
38+
if ((config.isNameRequired && !trimmedName) || (config.isEmailRequired && !trimmedEmail) || !trimmedDescription) {
39+
Alert.alert(text.errorTitle, text.formError);
40+
return;
41+
}
42+
43+
if ((config.isEmailRequired || trimmedEmail.length > 0) && !this._isValidEmail(trimmedEmail)) {
44+
Alert.alert(text.errorTitle, text.emailError);
45+
return;
46+
}
47+
48+
const eventId = lastEventId();
49+
const userFeedback: SendFeedbackParams = {
50+
message: trimmedDescription,
51+
name: trimmedName,
52+
email: trimmedEmail,
53+
associatedEventId: eventId,
54+
};
55+
56+
onFormClose();
57+
this.setState({ isVisible: false });
58+
59+
captureFeedback(userFeedback);
60+
Alert.alert(text.successMessageText);
61+
};
62+
63+
/**
64+
* Renders the feedback form screen.
65+
*/
66+
public render(): React.ReactNode {
67+
const { name, email, description } = this.state;
68+
const { onFormClose } = { ...defaultConfiguration, ...this.props };
69+
const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...this.props };
70+
const text: FeedbackTextConfiguration = { ...defaultConfiguration, ...this.props };
71+
const styles: FeedbackFormStyles = { ...defaultStyles, ...this.props.styles };
72+
const onCancel = (): void => {
73+
onFormClose();
74+
this.setState({ isVisible: false });
75+
}
76+
77+
if (!this.state.isVisible) {
78+
return null;
79+
}
80+
81+
return (
82+
<View style={styles.container}>
83+
<Text style={styles.title}>{text.formTitle}</Text>
84+
85+
{config.showName && (
86+
<>
87+
<Text style={styles.label}>
88+
{text.nameLabel}
89+
{config.isNameRequired && ` ${text.isRequiredLabel}`}
90+
</Text>
91+
<TextInput
92+
style={styles.input}
93+
placeholder={text.namePlaceholder}
94+
value={name}
95+
onChangeText={(value) => this.setState({ name: value })}
96+
/>
97+
</>
98+
)}
99+
100+
{config.showEmail && (
101+
<>
102+
<Text style={styles.label}>
103+
{text.emailLabel}
104+
{config.isEmailRequired && ` ${text.isRequiredLabel}`}
105+
</Text>
106+
<TextInput
107+
style={styles.input}
108+
placeholder={text.emailPlaceholder}
109+
keyboardType={'email-address' as KeyboardTypeOptions}
110+
value={email}
111+
onChangeText={(value) => this.setState({ email: value })}
112+
/>
113+
</>
114+
)}
115+
116+
<Text style={styles.label}>
117+
{text.messageLabel}
118+
{` ${text.isRequiredLabel}`}
119+
</Text>
120+
<TextInput
121+
style={[styles.input, styles.textArea]}
122+
placeholder={text.messagePlaceholder}
123+
value={description}
124+
onChangeText={(value) => this.setState({ description: value })}
125+
multiline
126+
/>
127+
128+
<TouchableOpacity style={styles.submitButton} onPress={this.handleFeedbackSubmit}>
129+
<Text style={styles.submitText}>{text.submitButtonLabel}</Text>
130+
</TouchableOpacity>
131+
132+
<TouchableOpacity style={styles.cancelButton} onPress={onCancel}>
133+
<Text style={styles.cancelText}>{text.cancelButtonLabel}</Text>
134+
</TouchableOpacity>
135+
</View>
136+
);
137+
}
138+
139+
private _isValidEmail = (email: string): boolean => {
140+
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
141+
return emailRegex.test(email);
142+
};
143+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import type { TextStyle, ViewStyle } from 'react-native';
2+
3+
export interface FeedbackFormProps extends FeedbackGeneralConfiguration, FeedbackTextConfiguration, FeedbackCallbacks {
4+
styles?: FeedbackFormStyles;
5+
}
6+
7+
/**
8+
* General feedback configuration
9+
*/
10+
export interface FeedbackGeneralConfiguration {
11+
/**
12+
* Should the email field be required?
13+
*/
14+
isEmailRequired?: boolean;
15+
16+
/**
17+
* Should the name field be required?
18+
*/
19+
isNameRequired?: boolean;
20+
21+
/**
22+
* Should the email input field be visible? Note: email will still be collected if set via `Sentry.setUser()`
23+
*/
24+
showEmail?: boolean;
25+
26+
/**
27+
* Should the name input field be visible? Note: name will still be collected if set via `Sentry.setUser()`
28+
*/
29+
showName?: boolean;
30+
31+
/**
32+
* Fill in email/name input fields with Sentry user context if it exists.
33+
* The value of the email/name keys represent the properties of your user context.
34+
*/
35+
useSentryUser?: {
36+
email: string;
37+
name: string;
38+
};
39+
}
40+
41+
/**
42+
* All of the different text labels that can be customized
43+
*/
44+
export interface FeedbackTextConfiguration {
45+
/**
46+
* The label for the Feedback form cancel button that closes dialog
47+
*/
48+
cancelButtonLabel?: string;
49+
50+
/**
51+
* The label for the Feedback form submit button that sends feedback
52+
*/
53+
submitButtonLabel?: string;
54+
55+
/**
56+
* The title of the Feedback form
57+
*/
58+
formTitle?: string;
59+
60+
/**
61+
* Label for the email input
62+
*/
63+
emailLabel?: string;
64+
65+
/**
66+
* Placeholder text for Feedback email input
67+
*/
68+
emailPlaceholder?: string;
69+
70+
/**
71+
* Label for the message input
72+
*/
73+
messageLabel?: string;
74+
75+
/**
76+
* Placeholder text for Feedback message input
77+
*/
78+
messagePlaceholder?: string;
79+
80+
/**
81+
* Label for the name input
82+
*/
83+
nameLabel?: string;
84+
85+
/**
86+
* Message after feedback was sent successfully
87+
*/
88+
successMessageText?: string;
89+
90+
/**
91+
* Placeholder text for Feedback name input
92+
*/
93+
namePlaceholder?: string;
94+
95+
/**
96+
* Text which indicates that a field is required
97+
*/
98+
isRequiredLabel?: string;
99+
100+
/**
101+
* The title of the error dialog
102+
*/
103+
errorTitle?: string;
104+
105+
/**
106+
* The error message when the form is invalid
107+
*/
108+
formError?: string;
109+
110+
/**
111+
* The error message when the email is invalid
112+
*/
113+
emailError?: string;
114+
}
115+
116+
/**
117+
* The public callbacks available for the feedback integration
118+
*/
119+
export interface FeedbackCallbacks {
120+
/**
121+
* Callback when form is closed and not submitted
122+
*/
123+
onFormClose?: () => void;
124+
}
125+
126+
export interface FeedbackFormStyles {
127+
container?: ViewStyle;
128+
title?: TextStyle;
129+
label?: TextStyle;
130+
input?: TextStyle;
131+
textArea?: TextStyle;
132+
submitButton?: ViewStyle;
133+
submitText?: TextStyle;
134+
cancelButton?: ViewStyle;
135+
cancelText?: TextStyle;
136+
}
137+
138+
export interface FeedbackFormState {
139+
isVisible: boolean;
140+
name: string;
141+
email: string;
142+
description: string;
143+
}

0 commit comments

Comments
 (0)