-
-
Notifications
You must be signed in to change notification settings - Fork 337
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
(1) feat: Add Feedback From Component #4328
base: main
Are you sure you want to change the base?
Changes from 65 commits
817eac8
5370a99
42e2fa1
514b102
0935bbd
9ea5496
da0d4ac
3e36c6d
5f3df64
71b28e8
f9d2b59
d05d531
2bb104b
4339274
6ce799b
f2cefc6
694ee33
18b1c33
034efde
67a492d
8eaa61d
0b88cc5
ae11b8d
7f2ca06
064b6c4
e2add4a
e21718a
407f179
14ac005
ddade00
1bc1e4c
7934756
95e1e0f
a169362
4b5df7a
4fa81ce
4fff82f
0258bf2
458ebc2
f0e1bef
b9235f2
6717a84
39a67bd
501a134
4b290a2
7109deb
c80c5cb
8c56753
d6e9229
5292475
efd809f
bc7ae65
79ee5ba
ba13320
1f5fb56
439367a
9831482
4097347
f8a82fd
30a7b10
3eccf25
bc96fce
995a9ca
3aacaf7
20e3a6c
78e412c
c45a5e6
6e39119
57d99e9
6fb8ab4
fd2e317
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
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: 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: 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: PURPLE, | ||
paddingVertical: 15, | ||
borderRadius: 5, | ||
alignItems: 'center', | ||
marginBottom: 10, | ||
}, | ||
submitText: { | ||
color: BACKROUND_COLOR, | ||
fontSize: 18, | ||
}, | ||
cancelButton: { | ||
paddingVertical: 15, | ||
alignItems: 'center', | ||
}, | ||
cancelText: { | ||
color: FORGROUND_COLOR, | ||
fontSize: 16, | ||
}, | ||
}; | ||
|
||
export default defaultStyles; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import { captureFeedback, lastEventId } 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 { defaultConfiguration } from './defaults'; | ||
import defaultStyles from './FeedbackForm.styles'; | ||
import type { FeedbackFormProps, FeedbackFormState, FeedbackFormStyles,FeedbackGeneralConfiguration, FeedbackTextConfiguration } from './FeedbackForm.types'; | ||
|
||
/** | ||
* @beta | ||
* Implements a feedback form screen that sends feedback to Sentry using Sentry.captureFeedback. | ||
*/ | ||
export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFormState> { | ||
public constructor(props: FeedbackFormProps) { | ||
super(props); | ||
|
||
const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...props }; | ||
this.state = { | ||
isVisible: true, | ||
name: config.useSentryUser.name, | ||
email: config.useSentryUser.email, | ||
description: '', | ||
}; | ||
} | ||
|
||
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 trimmedName = name?.trim(); | ||
const trimmedEmail = email?.trim(); | ||
const trimmedDescription = description?.trim(); | ||
|
||
if ((config.isNameRequired && !trimmedName) || (config.isEmailRequired && !trimmedEmail) || !trimmedDescription) { | ||
Alert.alert(text.errorTitle, text.formError); | ||
return; | ||
} | ||
|
||
if ((config.isEmailRequired || trimmedEmail.length > 0) && !this._isValidEmail(trimmedEmail)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we are using custom regex for the email validation, let's add an option to enable/disable the validation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added an option with c45a5e6 |
||
Alert.alert(text.errorTitle, text.emailError); | ||
return; | ||
} | ||
|
||
const eventId = lastEventId(); | ||
const userFeedback: SendFeedbackParams = { | ||
message: trimmedDescription, | ||
name: trimmedName, | ||
email: trimmedEmail, | ||
associatedEventId: eventId, | ||
}; | ||
|
||
onFormClose(); | ||
this.setState({ isVisible: false }); | ||
|
||
captureFeedback(userFeedback); | ||
Alert.alert(text.successMessageText); | ||
}; | ||
|
||
/** | ||
* Renders the feedback form screen. | ||
*/ | ||
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 styles: FeedbackFormStyles = { ...defaultStyles, ...this.props.styles }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let do the merge only once On moder devices it should not make a perf difference, but let's be safe. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea 👍 |
||
const onCancel = (): void => { | ||
onFormClose(); | ||
this.setState({ isVisible: false }); | ||
} | ||
|
||
if (!this.state.isVisible) { | ||
return null; | ||
} | ||
|
||
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> | ||
); | ||
} | ||
|
||
private _isValidEmail = (email: string): boolean => { | ||
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ | ||
return emailRegex.test(email); | ||
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved
Hide resolved
|
||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import type { TextStyle, ViewStyle } from 'react-native'; | ||
|
||
export interface FeedbackFormProps extends FeedbackGeneralConfiguration, FeedbackTextConfiguration, FeedbackCallbacks { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the configuration parameters used are copied from the JS APi https://github.com/getsentry/sentry-javascript/blob/develop/packages/core/src/types-hoist/feedback/config.ts |
||
styles?: FeedbackFormStyles; | ||
} | ||
|
||
/** | ||
* General feedback configuration | ||
*/ | ||
export interface FeedbackGeneralConfiguration { | ||
/** | ||
* Should the email field be required? | ||
*/ | ||
isEmailRequired?: boolean; | ||
|
||
/** | ||
* Should the name field be required? | ||
*/ | ||
isNameRequired?: boolean; | ||
|
||
/** | ||
* Should the email input field be visible? Note: email will still be collected if set via `Sentry.setUser()` | ||
*/ | ||
showEmail?: boolean; | ||
|
||
/** | ||
* Should the name input field be visible? Note: name will still be collected if set via `Sentry.setUser()` | ||
*/ | ||
showName?: boolean; | ||
|
||
/** | ||
* Fill in email/name input fields with Sentry user context if it exists. | ||
* The value of the email/name keys represent the properties of your user context. | ||
*/ | ||
useSentryUser?: { | ||
email: string; | ||
name: string; | ||
}; | ||
} | ||
|
||
/** | ||
* All of the different text labels that can be customized | ||
*/ | ||
export interface FeedbackTextConfiguration { | ||
/** | ||
* The label for the Feedback form cancel button that closes dialog | ||
*/ | ||
cancelButtonLabel?: string; | ||
|
||
/** | ||
* The label for the Feedback form submit button that sends feedback | ||
*/ | ||
submitButtonLabel?: string; | ||
|
||
/** | ||
* The title of the Feedback form | ||
*/ | ||
formTitle?: string; | ||
|
||
/** | ||
* Label for the email input | ||
*/ | ||
emailLabel?: string; | ||
|
||
/** | ||
* Placeholder text for Feedback email input | ||
*/ | ||
emailPlaceholder?: string; | ||
|
||
/** | ||
* Label for the message input | ||
*/ | ||
messageLabel?: string; | ||
|
||
/** | ||
* Placeholder text for Feedback message input | ||
*/ | ||
messagePlaceholder?: string; | ||
|
||
/** | ||
* Label for the name input | ||
*/ | ||
nameLabel?: string; | ||
|
||
/** | ||
* Message after feedback was sent successfully | ||
*/ | ||
successMessageText?: string; | ||
|
||
/** | ||
* Placeholder text for Feedback name input | ||
*/ | ||
namePlaceholder?: string; | ||
|
||
/** | ||
* Text which indicates that a field is required | ||
*/ | ||
isRequiredLabel?: string; | ||
|
||
/** | ||
* The title of the error dialog | ||
*/ | ||
errorTitle?: string; | ||
|
||
/** | ||
* The error message when the form is invalid | ||
*/ | ||
formError?: string; | ||
|
||
/** | ||
* The error message when the email is invalid | ||
*/ | ||
emailError?: string; | ||
} | ||
|
||
/** | ||
* The public callbacks available for the feedback integration | ||
*/ | ||
export interface FeedbackCallbacks { | ||
/** | ||
* Callback when form is closed and not submitted | ||
*/ | ||
onFormClose?: () => void; | ||
krystofwoldrich marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
export interface FeedbackFormStyles { | ||
container?: ViewStyle; | ||
title?: TextStyle; | ||
label?: TextStyle; | ||
input?: TextStyle; | ||
textArea?: TextStyle; | ||
submitButton?: ViewStyle; | ||
submitText?: TextStyle; | ||
cancelButton?: ViewStyle; | ||
cancelText?: TextStyle; | ||
} | ||
|
||
export interface FeedbackFormState { | ||
isVisible: boolean; | ||
name: string; | ||
email: string; | ||
description: string; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's match the default with JS.
Mainly I've noticed submitButton backgroundColor (JS has
rgba(88, 74, 192, 1)
) and test color#2b2233
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried to align with the light theme of JS with 30a7b10
Screenshot