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:
parent
a2c1757815
commit
f4386fbcbc
|
|
@ -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>
|
||||
|
||||
|
|
@ -3,7 +3,11 @@
|
|||
size="sm"
|
||||
orientation="horizontal"
|
||||
>
|
||||
<UTooltip text="Undo" :shortcuts="undoShortcut" :popper="{ placement: 'left' }">
|
||||
<UTooltip
|
||||
text="Undo"
|
||||
:shortcuts="[metaSymbol,'Z']"
|
||||
:popper="{ placement: 'left' }"
|
||||
>
|
||||
<UButton
|
||||
:disabled="!canUndo"
|
||||
color="white"
|
||||
|
|
@ -12,7 +16,11 @@
|
|||
@click="undo"
|
||||
/>
|
||||
</UTooltip>
|
||||
<UTooltip text="Redo" :shortcuts="redoShortcut" :popper="{ placement: 'right' }">
|
||||
<UTooltip
|
||||
text="Redo"
|
||||
:shortcuts="[metaSymbol,'Shift','Z']"
|
||||
:popper="{ placement: 'right' }"
|
||||
>
|
||||
<UButton
|
||||
:disabled="!canRedo"
|
||||
icon="i-material-symbols-redo"
|
||||
|
|
@ -44,30 +52,10 @@ defineShortcuts({
|
|||
}
|
||||
}
|
||||
})
|
||||
|
||||
const undoShortcut = computed(() => {
|
||||
return getOS() == 'macOS' ? ['⌘', 'Z'] : ['Ctrl', 'Z']
|
||||
})
|
||||
|
||||
const redoShortcut = computed(() => {
|
||||
return getOS() == 'macOS' ? ['⌘', 'Shift', 'Z'] : ['Ctrl', 'Shift', 'Z']
|
||||
})
|
||||
const { metaSymbol } = useShortcuts()
|
||||
|
||||
onMounted(() => {
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@
|
|||
</v-button>
|
||||
</div>
|
||||
</div>
|
||||
<FormEditorErrorHandler>
|
||||
|
||||
<div class="w-full flex grow overflow-y-scroll relative bg-gray-50">
|
||||
<div
|
||||
|
|
@ -116,6 +117,7 @@
|
|||
@close="showFormErrorModal = false"
|
||||
/>
|
||||
</div>
|
||||
</FormEditorErrorHandler>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
|
|
@ -141,10 +143,12 @@ import FormAccess from "./form-components/FormAccess.vue"
|
|||
import { validatePropertiesLogic } from "~/composables/forms/validatePropertiesLogic.js"
|
||||
import opnformConfig from "~/opnform.config.js"
|
||||
import { captureException } from "@sentry/core"
|
||||
import FormEditorErrorHandler from '~/components/open/forms/components/FormEditorErrorHandler.vue'
|
||||
|
||||
export default {
|
||||
name: "FormEditor",
|
||||
components: {
|
||||
FormEditorErrorHandler,
|
||||
UndoRedo,
|
||||
FormEditorSidebar,
|
||||
FormEditorPreview,
|
||||
|
|
@ -250,9 +254,6 @@ export default {
|
|||
},
|
||||
]
|
||||
},
|
||||
helpUrl() {
|
||||
return this.opnformConfig.links.help
|
||||
},
|
||||
},
|
||||
|
||||
watch: {},
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
@ -98,6 +98,28 @@ export function useCrisp () {
|
|||
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 {
|
||||
crisp,
|
||||
onCrispInit,
|
||||
|
|
@ -111,6 +133,9 @@ export function useCrisp () {
|
|||
sendTextMessage,
|
||||
pushEvent,
|
||||
setSegments,
|
||||
setUser
|
||||
setUser,
|
||||
pauseChatBot,
|
||||
enableChatbot,
|
||||
showMessage
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="w-full flex flex-col">
|
||||
<div class="w-full flex flex-col flex-grow">
|
||||
<form-editor
|
||||
v-if="(!formsLoading || form ) && !error "
|
||||
ref="editor"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
<template>
|
||||
<div class="flex flex-wrap flex-col">
|
||||
<div key="2">
|
||||
<div class="flex flex-wrap flex-col flex-grow">
|
||||
<div key="2"
|
||||
class="w-full flex flex-grow flex-col"
|
||||
>
|
||||
<create-form-base-modal
|
||||
:show="showInitialFormModal"
|
||||
@form-generated="formGenerated"
|
||||
|
|
|
|||
Loading…
Reference in New Issue