UNPKG

react-native-feedback-hub

Version:

React Native feedback SDK with Slack, Jira, Discord and Microsoft Teams integration

142 lines 10.7 kB
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