diff --git a/src/App.tsx b/src/App.tsx
index 50517d5..2478f12 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -23,13 +23,14 @@ import CartScreen from './screens/CartScreen';
import CheckoutScreen from './screens/CheckoutScreen';
import Toast from 'react-native-toast-message';
-import {RootState, store} from './reduxApp';
+import {RootState, store, showFeedbackActionButton} from './reduxApp';
import {DSN} from './config';
import {SE} from '@env'; // SE is undefined if no .env file is set
import {RootStackParamList} from './navigation';
import {GestureHandlerRootView} from 'react-native-gesture-handler';
import {LogBox, Platform, StyleSheet} from 'react-native';
import {SafeAreaProvider} from 'react-native-safe-area-context';
+import {SentryUserFeedbackActionButton} from './components/UserFeedbackModal';
console.log('> SE', SE);
LogBox.ignoreAllLogs();
@@ -56,6 +57,12 @@ Sentry.init({
// Make issue for the SE
event.fingerprint = ['{{ default }}', SE];
}
+
+ if (!event.type) {
+ // Only show the feedback button for errors
+ store.dispatch(showFeedbackActionButton());
+ }
+
return event;
},
integrations: [
@@ -115,6 +122,7 @@ const App = () => {
}}>
{/* */}
+
@@ -170,7 +178,7 @@ const BottomTabNavigator = () => {
options={{
tabBarIcon: ({focused}) => (
diff --git a/src/components/UserFeedbackModal.tsx b/src/components/UserFeedbackModal.tsx
new file mode 100644
index 0000000..497a7f5
--- /dev/null
+++ b/src/components/UserFeedbackModal.tsx
@@ -0,0 +1,250 @@
+import React from 'react';
+import {
+ View,
+ StyleSheet,
+ Text,
+ TextInput,
+ PressableProps,
+ Pressable,
+ Keyboard,
+ ViewStyle,
+ TextStyle,
+} from 'react-native';
+import * as Sentry from '@sentry/react-native';
+import {UserFeedback} from '@sentry/react-native';
+import Icon from 'react-native-vector-icons/FontAwesome6';
+import {android} from '../../utils/platform';
+import {useSafeAreaInsets} from 'react-native-safe-area-context';
+import {hideFeedbackActionButton, RootState} from '../reduxApp';
+import {useDispatch, useSelector} from 'react-redux';
+
+export const DEFAULT_COMMENTS = "It's broken again! Please fix it.";
+
+export const SentryUserFeedbackActionButton = () => {
+ const feedbackState = useSelector((state: RootState) => state.feedback);
+ const [isFormVisible, setFromVisibility] = React.useState(false);
+ const style = getCloseButtonStyles({safeBottom: useSafeAreaInsets().bottom});
+ const pressableStyle: PressableProps['style'] = ({pressed}) =>
+ pressed
+ ? [
+ {
+ ...style.container,
+ backgroundColor: '#584774',
+ },
+ style.shadowProp,
+ ]
+ : [style.container, style.shadowProp];
+
+ const onGiveFeedbackButtonPress = () => {
+ setFromVisibility(true);
+ };
+
+ return (
+ <>
+ {feedbackState.isActionButtonVisible && (
+
+
+ Report a Bug
+
+ )}
+ {isFormVisible && (
+ setFromVisibility(false)} />
+ )}
+ >
+ );
+};
+
+const getCloseButtonStyles = ({safeBottom}: {safeBottom: number}) =>
+ StyleSheet.create({
+ container: {
+ height: 50,
+ width: 160,
+ borderRadius: 30,
+ backgroundColor: '#29232f',
+ position: 'absolute',
+ bottom: safeBottom + (android(10) || 0) + 150,
+ left: 10,
+ paddingLeft: 15,
+ paddingRight: 15,
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ borderColor: 'rgba(235, 230, 239, 0.15)',
+ borderWidth: 1.5,
+ },
+ text: {
+ color: '#fff',
+ fontWeight: 'bold',
+ },
+ shadowProp: {
+ shadowColor: '#171717',
+ shadowOffset: {width: -2, height: 4},
+ shadowOpacity: 0.3,
+ shadowRadius: 3,
+ },
+ });
+
+export function UserFeedbackModal(props: {onDismiss: () => void}) {
+ const dispatch = useDispatch();
+ const {onDismiss} = props;
+ const [comments, onChangeComments] = React.useState(DEFAULT_COMMENTS);
+ const clearComments = () => onChangeComments(DEFAULT_COMMENTS);
+ const onContainerPress = () => {
+ Keyboard.dismiss();
+ };
+ const onCloseButtonPress = () => {
+ onDismiss();
+ dispatch(hideFeedbackActionButton());
+ };
+ const onSendButtonPress = () => {
+ onDismiss();
+
+ const sentryId =
+ Sentry.lastEventId() ??
+ Sentry.captureMessage('User Feedback Fallback Message');
+
+ const userFeedback: UserFeedback = {
+ event_id: sentryId,
+ name: 'Anonymous User',
+ email: 'example@example.com',
+ comments,
+ };
+
+ Sentry.captureUserFeedback(userFeedback);
+ clearComments();
+ dispatch(hideFeedbackActionButton());
+ };
+
+ return (
+
+
+ Whoops, what happened?
+
+
+
+
+
+
+
+
+ );
+}
+
+const ModalButton = ({
+ onPress,
+ text,
+ wrapperStyle,
+ textStyle,
+}: {
+ onPress: () => void;
+ text: string;
+ wrapperStyle?: ViewStyle;
+ textStyle?: TextStyle;
+}) => {
+ return (
+
+
+ {text}
+
+
+ );
+};
+
+const modalButtonStyles = StyleSheet.create({
+ wrapper: {
+ borderColor: 'rgba(235, 230, 239, 0.15)',
+ borderWidth: 1.5,
+ backgroundColor: 'rgba(88, 74, 192, 1)',
+ padding: 10,
+ borderRadius: 6,
+ alignContent: 'center',
+ alignItems: 'center',
+ },
+ text: {
+ color: '#fff',
+ fontWeight: 'bold',
+ fontSize: 16,
+ },
+ secondaryWrapper: {
+ backgroundColor: 'rgba(0, 0, 0, 0)',
+ },
+ secondaryText: {
+ fontWeight: 'regular',
+ },
+});
+
+const styles = StyleSheet.create({
+ centeredView: {
+ flex: 1,
+ height: '100%',
+ width: '100%',
+ position: 'absolute',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ modalView: {
+ margin: 5,
+ backgroundColor: '#29232f',
+ borderRadius: 16,
+ padding: 25,
+ alignItems: 'center',
+ shadowColor: '#171717',
+ shadowOffset: {width: -2, height: 4},
+ shadowOpacity: 0.3,
+ shadowRadius: 3,
+ elevation: 5,
+ borderColor: '#584774',
+ borderWidth: 2,
+ },
+ input: {
+ marginBottom: 20,
+ borderWidth: 1.5,
+ borderColor: 'rgba(235, 230, 239, 0.15)',
+ padding: 15,
+ borderRadius: 6,
+ height: 100,
+ width: 250,
+ textAlignVertical: 'top',
+ color: '#fff',
+ },
+ actionsWrapper: {
+ width: 250,
+ },
+ modalText: {
+ marginBottom: 15,
+ textAlign: 'center',
+ fontSize: 18,
+ color: '#fff',
+ fontWeight: 'bold',
+ },
+ modalImage: {
+ marginBottom: 20,
+ width: 80,
+ height: 80,
+ },
+ buttonSpacer: {
+ marginBottom: 8,
+ },
+});
diff --git a/src/reduxApp.ts b/src/reduxApp.ts
index d6b2973..3fbfdfc 100644
--- a/src/reduxApp.ts
+++ b/src/reduxApp.ts
@@ -14,6 +14,9 @@ const initialState = {
state: '',
zipCode: '',
},
+ feedback: {
+ isActionButtonVisible: false,
+ },
};
const reducer = (state = initialState, action) => {
@@ -73,11 +76,29 @@ const reducer = (state = initialState, action) => {
...state,
counter: 0,
};
+ case 'SHOW_FEEDBACK_ACTION_BUTTON':
+ return {
+ ...state,
+ feedback: {...state.feedback, isActionButtonVisible: true},
+ };
+ case 'HIDE_FEEDBACK_ACTION_BUTTON':
+ return {
+ ...state,
+ feedback: {...state.feedback, isActionButtonVisible: false},
+ };
default:
return state;
}
};
+export const showFeedbackActionButton = () => ({
+ type: 'SHOW_FEEDBACK_ACTION_BUTTON',
+});
+
+export const hideFeedbackActionButton = () => ({
+ type: 'HIDE_FEEDBACK_ACTION_BUTTON',
+});
+
/*
Example of how to use the Sentry redux enhancer packaged with @sentry/react:
*/