react-native-feedback-hub
Version:
React Native feedback SDK with Slack, Jira, Discord and Microsoft Teams integration
142 lines • 10.7 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import React, { useEffect, useRef, useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, Modal, Pressable, ActivityIndicator, ScrollView, } from 'react-native';
import { Bug, Lightbulb, Camera, Video, Square, Circle, X, CircleCheck, CircleX, } from 'lucide-react-native';
import { useFeedback } from '../context/FeedbackHubProvider';
import captureScreen from '../utils/captureScreen';
import { sendToSlack } from '../Integrations/slack';
import { sendToJira } from '../Integrations/jira';
import { useScreenRecorder } from '../hooks/useScreenRecorder';
import { ModalStyles as styles } from '../Styles/ModalStyle';
import AttachmentPreview from './AttachmentPreview';
import { useStoragePermission } from '../hooks/useStoragePermision';
import { sendToTeams } from '../Integrations/teams';
import { colors } from '../tokens/colors';
import { sendToDiscord } from '../Integrations/discord';
const FeedbackModal = ({ onClose }) => {
const { slackConfig, jiraConfig, microsoftTeamsConfig, discordConfig, toggleRecording, isRecording, title, type, message, additionalInfo, screenshot, setMessage, setScreenshot, setTitle, setType } = useFeedback();
const { start, stop, videoUri, setVideoUri, cleanup } = useScreenRecorder();
const { granted, requestPermission } = useStoragePermission();
const disableSubmit = !title || !message;
const [visible, setVisible] = useState(true);
const [isPending, setIsPending] = useState(false);
const [status, setStatus] = useState(undefined);
const scrollViewRef = useRef(null);
useEffect(() => {
requestPermission();
}, [requestPermission]);
useEffect(() => {
var _a;
if (status || screenshot || videoUri) {
(_a = scrollViewRef.current) === null || _a === void 0 ? void 0 : _a.scrollToEnd();
}
}, [screenshot, status, videoUri]);
const handleRecording = async () => {
if (!isRecording) {
const res = await start();
if (res === 'started') {
toggleRecording();
onClose();
}
}
else {
await stop();
toggleRecording();
}
};
const handleCapture = async () => {
setVisible(false);
await new Promise(res => setTimeout(res, 200));
const shot = await captureScreen();
setScreenshot(shot);
setVisible(true);
};
const handleCancelAndClear = async () => {
setTitle('');
setMessage('');
setScreenshot('');
setType('bug');
if (videoUri) {
await cleanup();
}
else {
setVideoUri('');
}
};
const handleSubmit = async () => {
setStatus(undefined);
setIsPending(true);
try {
const payload = {
title,
message: `${message}\n${additionalInfo}`,
type,
screenshot,
video: videoUri,
};
if (slackConfig)
await sendToSlack(payload, slackConfig);
if (jiraConfig)
await sendToJira(payload, jiraConfig);
if (microsoftTeamsConfig)
await sendToTeams(payload, microsoftTeamsConfig);
if (discordConfig) {
await sendToDiscord(payload, discordConfig);
}
await handleCancelAndClear();
setIsPending(false);
setStatus('success');
setTimeout(() => {
onClose();
}, 2000);
}
catch (error) {
setStatus('failed');
setIsPending(false);
console.error('Feedback submission failed:', error);
}
};
if (!granted || !visible) {
return null;
}
return (_jsx(Modal, Object.assign({ animationType: "fade", transparent: true, statusBarTranslucent: true, navigationBarTranslucent: true }, { children: _jsx(Pressable, Object.assign({ style: styles.overlay, onPress: onClose }, { children: _jsxs(View, Object.assign({ style: styles.modal }, { children: [_jsxs(View, Object.assign({ style: styles.header }, { children: [_jsx(Text, Object.assign({ style: styles.title }, { children: "Feedback Hub" })), _jsx(Pressable, Object.assign({ onPress: onClose }, { children: _jsx(X, { color: colors.text.muted }) }))] })), _jsx(ScrollView, Object.assign({ ref: scrollViewRef }, { children: _jsxs(Pressable, Object.assign({ style: styles.form }, { children: [_jsx(Text, Object.assign({ style: styles.label }, { children: "Type" })), _jsxs(View, Object.assign({ style: styles.typeButtons }, { children: [_jsxs(TouchableOpacity, Object.assign({ style: [
styles.typeButton,
type === 'bug' && styles.activeBug,
], onPress: () => setType('bug') }, { children: [_jsx(Bug, { size: 16, color: type === 'bug' ? colors.status.error.text : colors.text.inverse }), _jsxs(Text, Object.assign({ style: [
styles.typeText,
type === 'bug' && styles.activeBugText,
] }, { children: [' ', "Bug"] }))] })), _jsxs(TouchableOpacity, Object.assign({ style: [
styles.typeButton,
type === 'suggestion' && styles.activeSuggestion,
], onPress: () => setType('suggestion') }, { children: [_jsx(Lightbulb, { size: 16, color: type === 'suggestion' ? colors.status.success.text : colors.text.inverse }), _jsxs(Text, Object.assign({ style: [
styles.typeText,
type === 'suggestion' && styles.activeSuggestionText,
] }, { children: [' ', "Suggestion"] }))] }))] })), _jsx(Text, Object.assign({ style: styles.label }, { children: "Title" })), _jsx(TextInput, { value: title, onChangeText: setTitle, style: styles.input, placeholder: "Brief description of the issue or suggestion", placeholderTextColor: colors.text.muted, enterKeyHint: "done", submitBehavior: "blurAndSubmit" }), _jsx(Text, Object.assign({ style: styles.label }, { children: "Details" })), _jsx(TextInput, { value: message, onChangeText: setMessage, style: [styles.input, styles.textarea], placeholder: "Please provide more details about your feedback...", placeholderTextColor: colors.text.muted, multiline: true, editable: true, scrollEnabled: true, submitBehavior: 'newline', blurOnSubmit: false }), _jsx(Text, Object.assign({ style: styles.label }, { children: "Attachments" })), _jsxs(View, Object.assign({ style: styles.attachments }, { children: [_jsxs(TouchableOpacity, Object.assign({ style: styles.attachmentButton, onPress: handleCapture }, { children: [_jsx(Camera, { size: 16, color: colors.text.muted }), _jsx(Text, Object.assign({ style: styles.attachmentText }, { children: " Screenshot" }))] })), _jsxs(TouchableOpacity, Object.assign({ style: [
styles.attachmentButton,
isRecording && styles.recording,
], onPress: handleRecording }, { children: [isRecording ? (_jsx(Square, { size: 16, color: colors.status.error.text })) : (_jsx(Video, { size: 16, color: colors.text.muted })), _jsx(Text, Object.assign({ style: [
styles.attachmentText,
isRecording && styles.recordingText,
] }, { children: isRecording ? ' Stop Recording' : ' Record Screen' }))] }))] })), isRecording && (_jsxs(View, Object.assign({ style: styles.recordingIndicator }, { children: [_jsx(Circle, { size: 12, color: colors.status.warning.text, fill: colors.status.warning.text }), _jsxs(Text, Object.assign({ style: styles.recordingLabel }, { children: [' ', "Recording in progress..."] }))] }))), _jsx(AttachmentPreview, { screenshotUri: screenshot, recordingUri: videoUri, onRemoveScreenshot: () => setScreenshot(null), onRemoveRecording: () => {
if (videoUri) {
cleanup();
}
else {
setVideoUri(null);
}
} }), _jsxs(View, Object.assign({ style: styles.actions }, { children: [_jsx(TouchableOpacity, Object.assign({ onPress: () => {
handleCancelAndClear();
onClose();
}, style: [styles.button, styles.secondaryButton] }, { children: _jsx(Text, Object.assign({ style: styles.secondaryText }, { children: "Cancel" })) })), _jsx(TouchableOpacity, Object.assign({ onPress: handleSubmit, style: [
styles.button,
disableSubmit
? styles.primaryButtonDisabled
: styles.primaryButton,
], disabled: disableSubmit || isPending }, { children: isPending ? (_jsx(ActivityIndicator, { color: colors.text.white })) : (_jsx(Text, Object.assign({ style: styles.primaryText }, { children: `${type === 'bug' ? 'Report Bug' : 'Send Suggestion'}` }))) }))] })), status && (_jsxs(View, Object.assign({ style: styles.statusNudge }, { children: [status === 'success' ? (_jsx(CircleCheck, { size: 15, color: colors.status.success.text })) : (_jsx(CircleX, { size: 15, color: colors.status.error.text })), _jsxs(Text, Object.assign({ style: status === 'success'
? styles.successLabel
: styles.errorLabel }, { children: [' ', status === 'success'
? 'Feedback submitted successfully!'
: 'Failed to submit feedback, try again!'] }))] })))] })) }))] })) })) })));
};
export default React.memo(FeedbackModal);
//# sourceMappingURL=FeedbackModal.js.map