55919 form editor error boundary (#494)

* fix password reset bug

* form editor error boundary

* fix crisp

* fix layout on create and edit pages

---------

Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
Favour Olayinka 2024-07-17 15:07:19 +01:00 committed by GitHub
parent a2c1757815
commit f4386fbcbc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 145 additions and 30 deletions

View File

@ -0,0 +1,30 @@
<template>
<slot v-if="!error" />
<slot
v-else
name="error"
:error="error"
:clear-error="clearError"
/>
</template>
<script setup lang="ts">
const error = ref()
const emit = defineEmits(['on-error'])
function clearError() {
error.value = undefined
}
onErrorCaptured(err => {
error.value = err
emit('on-error', err)
return false
})
const route = useRoute()
watch(
() => route.fullPath,
() => {
error.value = undefined
}
)
</script>

View File

@ -3,7 +3,11 @@
size="sm" size="sm"
orientation="horizontal" orientation="horizontal"
> >
<UTooltip text="Undo" :shortcuts="undoShortcut" :popper="{ placement: 'left' }"> <UTooltip
text="Undo"
:shortcuts="[metaSymbol,'Z']"
:popper="{ placement: 'left' }"
>
<UButton <UButton
:disabled="!canUndo" :disabled="!canUndo"
color="white" color="white"
@ -12,7 +16,11 @@
@click="undo" @click="undo"
/> />
</UTooltip> </UTooltip>
<UTooltip text="Redo" :shortcuts="redoShortcut" :popper="{ placement: 'right' }"> <UTooltip
text="Redo"
:shortcuts="[metaSymbol,'Shift','Z']"
:popper="{ placement: 'right' }"
>
<UButton <UButton
:disabled="!canRedo" :disabled="!canRedo"
icon="i-material-symbols-redo" icon="i-material-symbols-redo"
@ -44,30 +52,10 @@ defineShortcuts({
} }
} }
}) })
const { metaSymbol } = useShortcuts()
const undoShortcut = computed(() => {
return getOS() == 'macOS' ? ['⌘', 'Z'] : ['Ctrl', 'Z']
})
const redoShortcut = computed(() => {
return getOS() == 'macOS' ? ['⌘', 'Shift', 'Z'] : ['Ctrl', 'Shift', 'Z']
})
onMounted(() => { onMounted(() => {
setTimeout(() => { clearHistory() }, 500) setTimeout(() => { clearHistory() }, 500)
}) })
const getOS = ()=> {
if (navigator.userAgentData) {
// Modern method
return navigator.userAgentData.platform;
} else {
// Fallback for older browsers
const userAgent = navigator.userAgent.toLowerCase();
if (userAgent.indexOf("mac") > -1) return "macOS";
if (userAgent.indexOf("win") > -1) return "Windows";
if (userAgent.indexOf("linux") > -1) return "Linux";
return "Unknown";
}
}
</script> </script>

View File

@ -81,6 +81,7 @@
</v-button> </v-button>
</div> </div>
</div> </div>
<FormEditorErrorHandler>
<div class="w-full flex grow overflow-y-scroll relative bg-gray-50"> <div class="w-full flex grow overflow-y-scroll relative bg-gray-50">
<div <div
@ -116,6 +117,7 @@
@close="showFormErrorModal = false" @close="showFormErrorModal = false"
/> />
</div> </div>
</FormEditorErrorHandler>
</div> </div>
<div <div
v-else v-else
@ -141,10 +143,12 @@ import FormAccess from "./form-components/FormAccess.vue"
import { validatePropertiesLogic } from "~/composables/forms/validatePropertiesLogic.js" import { validatePropertiesLogic } from "~/composables/forms/validatePropertiesLogic.js"
import opnformConfig from "~/opnform.config.js" import opnformConfig from "~/opnform.config.js"
import { captureException } from "@sentry/core" import { captureException } from "@sentry/core"
import FormEditorErrorHandler from '~/components/open/forms/components/FormEditorErrorHandler.vue'
export default { export default {
name: "FormEditor", name: "FormEditor",
components: { components: {
FormEditorErrorHandler,
UndoRedo, UndoRedo,
FormEditorSidebar, FormEditorSidebar,
FormEditorPreview, FormEditorPreview,
@ -250,9 +254,6 @@ export default {
}, },
] ]
}, },
helpUrl() {
return this.opnformConfig.links.help
},
}, },
watch: {}, watch: {},

View File

@ -0,0 +1,69 @@
<template>
<ErrorBoundary @on-error="onFormEditorError">
<template #error="{ error, clearError }">
<div class="flex-grow w-full flex items-center justify-center flex-col gap-4">
<h1 class="text-blue-800 text-2xl font-medium">Oops! Something went wrong.</h1>
<p class="text-gray-500 max-w-lg text-center">It looks like your last action caused an issue on our side. We
apologize for
the
inconvenience.</p>
<div class="flex gap-2 mt-4">
<UButton icon="i-material-symbols-undo" @click="clearEditorError(error, clearError)">Go back one step
</UButton>
<UButton variant="outline" icon="i-heroicons-chat-bubble-left-right-16-solid"
@click="onErrorContact(error)">
Report this error
</UButton>
</div>
</div>
</template>
<slot />
</ErrorBoundary>
</template>
<script setup>
import { computed } from 'vue'
const crisp = useCrisp()
const workingFormStore = useWorkingFormStore()
const authStore = useAuthStore()
const form = storeToRefs(workingFormStore).content
const user = computed(() => authStore.user)
// Clear error and go back 1 step in history
const clearEditorError = (error, clearError) => {
crisp.enableChatbot()
workingFormStore.undo()
clearError()
}
const onFormEditorError = (error) => {
crisp.pauseChatBot()
const eventData = {
message: error.message,
// take first 200 characters
stack: error.stack.substring(0, 100)
}
try {
crisp.pushEvent('form-editor-error', eventData)
} catch (e) {
console.error('Failed to send event to crisp', e, eventData)
}
}
const onErrorContact = (error) => {
console.log('Contacting via crisp for an error', error)
crisp.pauseChatBot()
let errorReport = 'Hi there, I have a technical issue with the form editor.'
if (form.value.slug) {
errorReport += ` The form I am editing is: \`${form.value.slug}\`.`
}
errorReport += ` And here are technical details about the error: \`\`\`${error.stack}\`\`\``
try {
crisp.openAndShowChat(errorReport)
crisp.showMessage(`Hi there, we're very sorry to hear you experienced an issue with NoteForms.
We'll be in touch about it very soon! In the meantime, I recommend that you try going back one step, and save your changes.`, 2000)
} catch (e) {
console.error('Crisp error', e)
}
}
</script>

View File

@ -98,6 +98,28 @@ export function useCrisp () {
crisp.session.setSegments(segments, overwrite) crisp.session.setSegments(segments, overwrite)
} }
// Send message as operator
function showMessage (message, delay = 500) {
if (!crisp)
return
setTimeout(() => {
crisp.message.show('text', message)
}, delay)
}
function pauseChatBot () {
if (!crisp)
return
crisp.session.setData({ 'enum': 'pause_chatbot' })
}
function enableChatbot () {
if (!crisp)
return
crisp.session.setData({ 'enum': 'start_chatbot' })
}
return { return {
crisp, crisp,
onCrispInit, onCrispInit,
@ -111,6 +133,9 @@ export function useCrisp () {
sendTextMessage, sendTextMessage,
pushEvent, pushEvent,
setSegments, setSegments,
setUser setUser,
pauseChatBot,
enableChatbot,
showMessage
} }
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="w-full flex flex-col"> <div class="w-full flex flex-col flex-grow">
<form-editor <form-editor
v-if="(!formsLoading || form ) && !error " v-if="(!formsLoading || form ) && !error "
ref="editor" ref="editor"

View File

@ -1,6 +1,8 @@
<template> <template>
<div class="flex flex-wrap flex-col"> <div class="flex flex-wrap flex-col flex-grow">
<div key="2"> <div key="2"
class="w-full flex flex-grow flex-col"
>
<create-form-base-modal <create-form-base-modal
:show="showInitialFormModal" :show="showInitialFormModal"
@form-generated="formGenerated" @form-generated="formGenerated"