Nuxt Migration notifications (#265)

* Nuxt Migration notifications

* @input to @update:model-value

* change field type fixes

* @update:model-value

* Enable form-block-logic-editor

* vue-confetti migration

* PR request changes

* useAlert in setup
This commit is contained in:
formsdev 2023-12-31 17:09:01 +05:30 committed by GitHub
parent b4365b5e30
commit 6fd2985ff5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 586 additions and 1390 deletions

View File

@ -27,7 +27,8 @@
<NuxtPage/>
</NuxtLayout>
<ToolsStopImpersonation/>
<!-- <notifications />-->
<Notifications />
</div>
</template>

View File

@ -1,9 +1,9 @@
<template>
<div class="fixed top-0 bottom-24 right-0 flex px-4 items-start justify-end z-50 pointer-events-none">
<notification v-slot="{ notifications, close }">
<div class="relative pointer-events-auto" v-for="notification in notifications" :key="notification.id">
<div class="fixed top-0 bottom-24 right-0 flex px-4 items-start justify-end z-50 relative pointer-events-auto">
<NuxtNotifications>
<template #body="props">
<div
v-if="notification.type==='success'"
v-if="props.item.type==='success'"
class="flex max-w-sm w-full mx-auto bg-white shadow-md rounded-lg overflow-hidden mt-4"
>
<div class="flex justify-center items-center w-12 bg-green-500">
@ -14,13 +14,13 @@
<div class="-mx-3 py-2 px-4">
<div class="mx-3">
<span class="text-green-500 font-semibold pr-6">{{notification.title}}</span>
<p class="text-gray-600 text-sm">{{notification.text}}</p>
<span class="text-green-500 font-semibold pr-6">{{props.item.title}}</span>
<p class="text-gray-600 text-sm">{{props.item.text}}</p>
</div>
</div>
</div>
<div
v-if="notification.type==='info'"
v-if="props.item.type==='info'"
class="flex max-w-sm w-full mx-auto bg-white shadow-md rounded-lg overflow-hidden mt-4"
>
<div class="flex justify-center items-center w-12 bg-blue-500">
@ -31,13 +31,13 @@
<div class="-mx-3 py-2 px-4">
<div class="mx-3">
<span class="text-blue-500 font-semibold pr-6">{{notification.title}}</span>
<p class="text-gray-600 text-sm">T{{notification.text}}</p>
<span class="text-blue-500 font-semibold pr-6">{{props.item.title}}</span>
<p class="text-gray-600 text-sm">T{{props.item.text}}</p>
</div>
</div>
</div>
<div
v-if="notification.type==='error'"
v-if="props.item.type==='error'"
class="flex max-w-sm w-full mx-auto bg-white shadow-md rounded-lg overflow-hidden mt-4"
>
<div class="flex justify-center items-center w-12 bg-red-500">
@ -54,14 +54,14 @@
<div class="-mx-3 py-2 px-4">
<div class="mx-3">
<span class="text-red-500 font-semibold pr-6">{{notification.title}}</span>
<p class="text-gray-600 text-sm">{{notification.text}}</p>
<span class="text-red-500 font-semibold pr-6">{{props.item.title}}</span>
<p class="text-gray-600 text-sm">{{props.item.text}}</p>
</div>
</div>
</div>
<div
class="flex max-w-sm w-full mx-auto bg-white shadow-md rounded-lg overflow-hidden mt-4"
v-if="notification.type==='warning'"
v-if="props.item.type==='warning'"
>
<div class="flex justify-center items-center w-12 bg-yellow-500">
<svg
@ -77,14 +77,14 @@
<div class="-mx-3 py-2 px-4">
<div class="mx-3">
<span class="text-yellow-500 font-semibold pr-6">{{notification.title}}</span>
<p class="text-gray-600 text-sm">{{notification.text}}</p>
<span class="text-yellow-500 font-semibold pr-6">{{props.item.title}}</span>
<p class="text-gray-600 text-sm">{{props.item.text}}</p>
</div>
</div>
</div>
<div
class="flex max-w-sm w-full mx-auto bg-white shadow-md rounded-lg overflow-hidden mt-4"
v-if="notification.type==='confirm'"
v-if="props.item.type==='confirm'"
>
<div class="flex justify-center items-center w-12 bg-blue-500">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6 text-white">
@ -94,16 +94,16 @@
<div class="-mx-3 py-2 px-4">
<div class="mx-3">
<span class="text-blue-500 font-semibold pr-6">{{notification.title}}</span>
<p class="text-gray-600 text-sm">{{notification.text}}</p>
<span class="text-blue-500 font-semibold pr-6">{{props.item.title}}</span>
<p class="text-gray-600 text-sm">{{props.item.text}}</p>
<div class="w-full flex gap-2 mt-1">
<v-button color="blue" size="small" @click.prevent="notification.success();close(notification.id)">Yes</v-button>
<v-button color="gray" shade="light" size="small" @click.prevent="notification.failure();close(notification.id)">No</v-button>
<v-button color="blue" size="small" @click.prevent="props.item.data.success();props.close()">Yes</v-button>
<v-button color="gray" shade="light" size="small" @click.prevent="props.item.data.failure();props.close()">No</v-button>
</div>
</div>
</div>
</div>
<button @click="close(notification.id)" class="absolute top-0 right-0 px-2 py-2 cursor-pointer">
<button @click="props.close()" class="absolute top-0 right-0 px-2 py-2 cursor-pointer">
<svg
class="fill-current h-6 w-6 text-gray-300 hover:text-gray-500"
role="button"
@ -116,8 +116,8 @@
/>
</svg>
</button>
</div>
</notification>
</template>
</NuxtNotifications>
</div>
</template>

View File

@ -34,6 +34,11 @@ export default {
default: () => {}
}
},
setup () {
return {
useAlert: useAlert()
}
},
data () {
return {
}
@ -44,18 +49,18 @@ export default {
},
methods: {
onDeleteClick () {
this.alertConfirm('Do you really want to delete this record?', this.deleteRecord)
this.useAlert.confirm('Do you really want to delete this record?', this.deleteRecord)
},
async deleteRecord () {
axios.delete('/api/open/forms/' + this.form.id + '/records/' + this.rowid + '/delete').then(async (response) => {
if (response.data.type === 'success') {
this.$emit('deleted')
this.alertSuccess(response.data.message)
this.useAlert.success(response.data.message)
} else {
this.alertError('Something went wrong!')
this.useAlert.error('Something went wrong!')
}
}).catch((error) => {
this.alertError(error.response.data.message)
this.useAlert.error(error.response.data.message)
})
}
}

View File

@ -129,7 +129,8 @@ export default {
setup(props) {
return {
isIframe: useIsIframe(),
pendingSubmission: pendingSubmission(props.form)
pendingSubmission: pendingSubmission(props.form),
confetti: useConfetti()
}
},
@ -214,12 +215,11 @@ export default {
// If enabled display confetti
if (this.form.confetti_on_submission) {
this.playConfetti()
this.confetti.play()
}
}).catch((error) => {
if (error.response && error.response.data && error.response.data.message) {
console.error(error)
// this.alertError(error.response.data.message)
useAlert().error(error.response.data.message)
}
this.loading = false
onFailure()

View File

@ -56,9 +56,9 @@ export default {
document.execCommand('copy')
document.body.removeChild(el)
if(this.isDraft){
this.alertWarning('Copied! But other people won\'t be able to see the form since it\'s currently in draft mode')
useAlert().warning('Copied! But other people won\'t be able to see the form since it\'s currently in draft mode')
} else {
this.alertSuccess('Copied!')
useAlert().success('Copied!')
}
}

View File

@ -217,9 +217,9 @@ export default {
methods: {
displayFormModificationAlert (responseData) {
if (responseData.form && responseData.form.cleanings && Object.keys(responseData.form.cleanings).length > 0) {
// this.alertWarning(responseData.message)
useAlert().warning(responseData.message)
} else {
// this.alertSuccess(responseData.message)
useAlert().success(responseData.message)
}
},
openCrisp () {

View File

@ -19,7 +19,7 @@
/>
<div class="-mt-3 mb-3 text-gray-400 dark:text-gray-500">
<small>
Need another theme? <a href="#" @click.prevent="openChat">Send us some suggestions!</a>
Need another theme? <a href="#" @click.prevent="crisp.openAndShowChat">Send us some suggestions!</a>
</small>
</div>
@ -80,56 +80,25 @@
</editor-options-panel>
</template>
<script>
<script setup>
import { useWorkingFormStore } from '../../../../../stores/working_form'
import EditorOptionsPanel from '../../../editors/EditorOptionsPanel.vue'
import ProTag from '~/components/global/ProTag.vue'
export default {
components: { EditorOptionsPanel, ProTag },
props: {
},
setup () {
const workingFormStore = useWorkingFormStore()
return {
workingFormStore
}
},
data () {
return {
isMounted: false
}
},
const workingFormStore = useWorkingFormStore()
const form = storeToRefs(workingFormStore).content
const isMounted = ref(false)
const crisp = useCrisp()
const confetti = useConfetti()
computed: {
form: {
get () {
return this.workingFormStore.content
},
/* We add a setter */
set (value) {
this.workingFormStore.set(value)
}
}
},
onMounted(() => {
isMounted.value = true
})
watch: {},
mounted () {
this.isMounted = true
},
methods: {
onChangeConfettiOnSubmission (val) {
this.form.confetti_on_submission = val
if (this.isMounted && val) {
this.playConfetti()
}
},
openChat () {
window.$crisp.push(['do', 'chat:show'])
window.$crisp.push(['do', 'chat:open'])
}
const onChangeConfettiOnSubmission = (val) => {
form.confetti_on_submission = val
if (isMounted.value && val) {
confetti.play()
}
}
</script>

View File

@ -6,20 +6,20 @@
</div>
<SelectInput v-model="content.operator" class="w-full" :options="operators"
:name="'operator_'+property.id" placeholder="Comparison operator"
@update:modelValue="operatorChanged()"
@update:model-value="operatorChanged()"
/>
<template v-if="hasInput">
<component v-bind="inputComponentData" :is="inputComponentData.component" v-model="content.value" class="w-full"
:name="'value_'+property.id" placeholder="Filter Value"
@update:modelValue="emitInput()"
@update:model-value="emitInput()"
/>
</template>
</div>
</template>
<script>
import OpenFilters from '../../../../../../data/open_filters.json'
import OpenFilters from '../../../../../data/open_filters.json'
export default {
components: { },

View File

@ -1,5 +1,5 @@
<template>
<query-builder v-model="query" :rules="rules" :config="config" @update:modelValue="onChange">
<query-builder v-model="query" :rules="rules" :config="config" @update:model-value="onChange">
<template #groupOperator="props">
<div class="query-builder-group-slot__group-selection flex items-center px-5 border-b py-1 mb-1 flex">
<p class="mr-2 font-semibold">
@ -13,7 +13,7 @@
option-key="identifier"
name="operator-input"
margin-bottom=""
@update:modelValue="props.updateCurrentOperator($event)"
@update:model-value="props.updateCurrentOperator($event)"
/>
</div>
</template>
@ -24,7 +24,7 @@
<component
:is="ruleCtrl.ruleComponent"
:model-value="ruleCtrl.ruleData"
@update:modelValue="ruleCtrl.updateRuleData"
@update:model-value="ruleCtrl.updateRuleData"
/>
</template>
</query-builder>

View File

@ -69,12 +69,11 @@
<script>
import ConditionEditor from './ConditionEditor.vue'
import Modal from '../../../../global/Modal.vue'
import SelectInput from '../../../../forms/SelectInput.vue'
import clonedeep from 'clone-deep'
export default {
name: 'FormBlockLogicEditor',
components: { SelectInput, Modal, ConditionEditor },
components: { Modal, ConditionEditor },
props: {
field: {
type: Object,

View File

@ -93,7 +93,8 @@ export default {
user : computed(() => authStore.user),
templates : computed(() => templatesStore.content),
industries : computed(() => templatesStore.industries),
types : computed(() => templatesStore.types)
types : computed(() => templatesStore.types),
useAlert: useAlert()
}
},
@ -156,7 +157,7 @@ export default {
this.templateForm.form = this.form
await this.templateForm.post('/api/templates').then((response) => {
if (response.data.message) {
this.alertSuccess(response.data.message)
this.useAlert.success(response.data.message)
}
this.templatesStore.save(response.data.data)
this.$emit('close')
@ -166,7 +167,7 @@ export default {
this.templateForm.form = this.form
await this.templateForm.put('/api/templates/' + this.template.id).then((response) => {
if (response.data.message) {
this.alertSuccess(response.data.message)
this.useAlert.success(response.data.message)
}
this.templatesStore.save(response.data.data)
this.$emit('close')
@ -176,7 +177,7 @@ export default {
if (!this.template) return
axios.delete('/api/templates/' + this.template.id).then((response) => {
if (response.data.message) {
this.alertSuccess(response.data.message)
this.useAlert.success(response.data.message)
}
this.$router.push({ name: 'templates' })
this.templatesStore.remove(this.template)

View File

@ -109,7 +109,7 @@ export default {
return this.field && this.field.type.startsWith('nf')
},
typeCanBeChanged () {
return ['text', 'email', 'phone', 'number', 'select', 'multi_select'].includes(this.field.type)
return ['text', 'email', 'phone_number', 'number', 'select', 'multi_select'].includes(this.field.type)
}
},

View File

@ -86,14 +86,16 @@
</div>
<!-- Logic Block -->
<!-- <form-block-logic-editor class="py-2 px-4 border-b" :form="form" :field="field" />-->
<form-block-logic-editor class="py-2 px-4 border-b" :form="form" :field="field" />
</div>
</template>
<script>
import FormBlockLogicEditor from '../../components/form-logic-components/FormBlockLogicEditor.vue'
export default {
name: 'BlockOptions',
components: { },
components: {FormBlockLogicEditor},
props: {
field: {
type: Object,

View File

@ -41,11 +41,11 @@ export default {
computed: {
changeTypeOptions () {
let newTypes = []
if (['text', 'email', 'phone', 'number'].includes(this.field.type)) {
if (['text', 'email', 'phone_number', 'number'].includes(this.field.type)) {
newTypes = [
{ name: 'Text Input', value: 'text' },
{ name: 'Email Input', value: 'email' },
{ name: 'Phone Input', value: 'phone' },
{ name: 'Phone Input', value: 'phone_number' },
{ name: 'Number Input', value: 'number' }
]
}

View File

@ -82,7 +82,7 @@
/>
<v-checkbox v-model="field.is_scale" class="mt-4"
:name="field.id+'_is_scale'" @input="initScale"
:name="field.id+'_is_scale'" @update:model-value="initScale"
>
Scale
</v-checkbox>
@ -337,7 +337,7 @@
{name:'Above input',value:'above_input'},
]"
:form="field" label="Field Help Position"
@input="onFieldHelpPositionChange"
@update:model-value="onFieldHelpPositionChange"
/>
<template v-if="['text','number','url','email'].includes(field.type)">
@ -382,7 +382,7 @@
</div>
<!-- Logic Block -->
<!-- <form-block-logic-editor class="py-2 px-4 border-b" :form="form" :field="field" />-->
<form-block-logic-editor class="py-2 px-4 border-b" :form="form" :field="field" />
</div>
</template>
@ -390,10 +390,11 @@
import timezones from '~/data/timezones.json'
import countryCodes from '~/data/country_codes.json'
import CountryFlag from 'vue-country-flag-next'
import FormBlockLogicEditor from '../../components/form-logic-components/FormBlockLogicEditor.vue'
export default {
name: 'FieldOptions',
components: { CountryFlag },
components: { CountryFlag, FormBlockLogicEditor },
props: {
field: {
type: Object,
@ -533,23 +534,23 @@ export default {
},
initRating () {
if (this.field.is_rating) {
this.$set(this.field, 'is_scale', false)
this.field.is_scale = false
if (!this.field.rating_max_value) {
this.$set(this.field, 'rating_max_value', 5)
this.field.rating_max_value = 5
}
}
},
initScale () {
if (this.field.is_scale) {
this.$set(this.field, 'is_rating', false)
this.field.is_rating = false
if (!this.field.scale_min_value) {
this.$set(this.field, 'scale_min_value', 1)
this.field.scale_min_value = 1
}
if (!this.field.scale_max_value) {
this.$set(this.field, 'scale_max_value', 5)
this.field.scale_max_value = 5
}
if (!this.field.scale_step_value) {
this.$set(this.field, 'scale_step_value', 1)
this.field.scale_step_value = 1
}
}
},

View File

@ -14,8 +14,7 @@ export default {
components: { OpenTag },
props: {
value: {
type: Object | null,
required: true
type: Object
}
},

View File

@ -129,10 +129,10 @@ export default {
// AppSumo License
if (data.appsumo_license === false) {
this.alertError('Invalid AppSumo license. This probably happened because this license was already' +
useAlert().error('Invalid AppSumo license. This probably happened because this license was already' +
' attached to another OpnForm account. Please contact support.')
} else if (data.appsumo_license === true) {
this.alertSuccess('Your AppSumo license was successfully activated! You now have access to all the' +
useAlert().success('Your AppSumo license was successfully activated! You now have access to all the' +
' features of the AppSumo deal.')
}

View File

@ -97,7 +97,11 @@ export default {
props: {
show: { type: Boolean, required: true }
},
setup () {
return {
useAlert: useAlert()
}
},
data: () => ({
state: 'default',
aiForm: useForm({
@ -118,10 +122,10 @@ export default {
this.loading = true
this.aiForm.post('/api/forms/ai/generate').then(response => {
this.alertSuccess(response.data.message)
this.useAlert.success(response.data.message)
this.fetchGeneratedForm(response.data.ai_form_completion_id)
}).catch(error => {
this.alertError(error.response.data.message)
this.useAlert.error(error.response.data.message)
this.loading = false
this.state = 'default'
})
@ -131,18 +135,18 @@ export default {
setTimeout(() => {
axios.get('/api/forms/ai/' + generationId).then(response => {
if (response.data.ai_form_completion.status === 'completed') {
this.alertSuccess(response.data.message)
this.useAlert.success(response.data.message)
this.$emit('form-generated', JSON.parse(response.data.ai_form_completion.result))
this.$emit('close')
} else if (response.data.ai_form_completion.status === 'failed') {
this.alertError('Something went wrong, please try again.')
this.useAlert.error('Something went wrong, please try again.')
this.state = 'default'
this.loading = false
} else {
this.fetchGeneratedForm(generationId)
}
}).catch(error => {
this.alertError(error.response.data.message)
this.useAlert.error(error.response.data.message)
this.state = 'default'
this.loading = false
})

View File

@ -157,7 +157,8 @@ export default {
const formsStore = useFormsStore()
return {
formsStore,
user: computed(() => authStore.user)
user: computed(() => authStore.user),
useAlert: useAlert()
}
},
@ -180,7 +181,7 @@ export default {
el.select()
document.execCommand('copy')
document.body.removeChild(el)
this.alertSuccess('Copied!')
this.useAlert.success('Copied!')
},
duplicateForm () {
if (this.loadingDuplicate) return
@ -188,7 +189,7 @@ export default {
opnFetch(this.formEndpoint.replace('{id}', this.form.id) + '/duplicate',{method: 'POST'}).then((data) => {
this.formsStore.save(data.new_form)
this.$router.push({ name: 'forms-show', params: { slug: data.new_form.slug } })
this.alertSuccess('Form was successfully duplicated.')
this.useAlert.success('Form was successfully duplicated.')
this.loadingDuplicate = false
})
},
@ -198,7 +199,7 @@ export default {
opnFetch(this.formEndpoint.replace('{id}', this.form.id),{method:'DELETE'}).then(() => {
this.formsStore.remove(this.form)
this.$router.push({ name: 'home' })
this.alertSuccess('Form was deleted.')
this.useAlert.success('Form was deleted.')
this.loadingDelete = false
})
}

View File

@ -106,7 +106,7 @@ export default {
axios.put(this.formEndpoint.replace('{id}', this.form.id) + '/regenerate-link/' + option).then((response) => {
this.formsStore.addOrUpdate(response.data.form)
this.$router.push({name: 'forms-slug-show-share', params: {slug: response.data.form.slug}})
this.alertSuccess(response.data.message)
useAlert().success(response.data.message)
this.loadingNewLink = false
}).finally(() => {
this.showGenerateFormLinkModal = false

View File

@ -84,7 +84,7 @@ export default {
axios.get('/api/subscription/new/' + this.plan + '/' + (!this.yearly ? 'monthly' : 'yearly') + '/checkout/with-trial').then((response) => {
window.location = response.data.checkout_url
}).catch((error) => {
this.alertError(error.response.data.message)
useAlert().error(error.response.data.message)
}).finally(() => {
this.loading = false
this.close()

51
client/composables/useAlert.js vendored Normal file
View File

@ -0,0 +1,51 @@
const { notify } = useNotification()
export const useAlert = () => {
function success(message, autoClose = 10000) {
notify({
title: 'Success',
text: message,
type: 'success',
duration: autoClose
})
}
function error(message, autoClose = 10000) {
notify({
title: 'Error',
text: message,
type: 'error',
duration: autoClose
})
}
function warning(message, autoClose = 10000) {
notify({
title: 'Warning',
text: message,
type: 'warning',
duration: autoClose
})
}
function confirm(message, success, failure = ()=>{}, autoClose = 10000) {
notify({
title: 'Confirm',
text: message,
type: 'confirm',
duration: autoClose,
data: {
success,
failure
}
})
}
return {
success,
error,
warning,
confirm
}
}

22
client/composables/useConfetti.js vendored Normal file
View File

@ -0,0 +1,22 @@
import { ref, onUnmounted } from 'vue'
export const useConfetti = () => {
let timeoutId = ref(null)
const nuxtApp = useNuxtApp()
const $confetti = nuxtApp.vueApp.config.globalProperties.$confetti
function play(duration=3000) {
$confetti.start({ defaultSize: 6 })
timeoutId = setTimeout(() => {
$confetti.stop()
}, duration)
}
onUnmounted(() => {
if (timeoutId) clearTimeout(timeoutId)
})
return {
play
}
}

View File

@ -2,9 +2,9 @@ export default {
methods: {
displayFormModificationAlert (responseData) {
if (responseData.form && responseData.form.cleanings && Object.keys(responseData.form.cleanings).length > 0) {
this.alertWarning(responseData.message)
useAlert().warning(responseData.message)
} else {
this.alertSuccess(responseData.message)
useAlert().success(responseData.message)
}
}
}

View File

@ -29,7 +29,8 @@ export default defineNuxtConfig({
modules: [
'@pinia/nuxt',
'@vueuse/nuxt',
'@vueuse/motion/nuxt'
'@vueuse/motion/nuxt',
'nuxt3-notifications'
],
postcss: {
plugins: {

1621
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"dev": "export NODE_TLS_REJECT_UNAUTHORIZED=0; nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
@ -50,7 +50,7 @@
"vue-notion": "^3.0.0-beta.1",
"vue-signature-pad": "^3.0.2",
"vue3-editor": "^0.1.1",
"vue3-vt-notifications": "^1.0.0",
"nuxt3-notifications": "^1.1.9",
"vuedraggable": "next"
}
}

View File

@ -25,7 +25,7 @@ export default {
beforeRouteLeave (to, from, next) {
if (this.isDirty()) {
return this.alertConfirm('Changes you made may not be saved. Are you sure want to leave?', () => {
return useAlert().confirm('Changes you made may not be saved. Are you sure want to leave?', () => {
window.onbeforeunload = null
next()
}, () => {})

View File

@ -30,7 +30,7 @@ import CreateFormBaseModal from '../../../components/pages/forms/create/CreateFo
// beforeRouteLeave (to, from, next) {
// if (this.isDirty()) {
// return this.alertConfirm('Changes you made may not be saved. Are you sure want to leave?', () => {
// return useAlert().confirm('Changes you made may not be saved. Are you sure want to leave?', () => {
// window.onbeforeunload = null
// next()
// }, () => {})

View File

@ -41,14 +41,14 @@ export default {
this.loading = true
axios.delete('/api/user').then(async (response) => {
this.loading = false
this.alertSuccess(response.data.message)
useAlert().success(response.data.message)
// Log out the user.
await this.authStore.logout()
// Redirect to login.
this.$router.push({ name: 'login' })
}).catch((error) => {
this.alertError(error.response.data.message)
useAlert().error(error.response.data.message)
this.loading = false
})
}

View File

@ -76,7 +76,7 @@ export default {
this.workspacesStore.set([])
this.$router.push({ name: 'home' })
}).catch((error) => {
this.alertError(error.response.data.message)
useAlert().error(error.response.data.message)
this.loading = false
})

View File

@ -51,7 +51,7 @@ export default {
const url = response.data.portal_url
window.location = url
}).catch((error) => {
this.alertError(error.response.data.message)
useAlert().error(error.response.data.message)
}).finally(() => {
this.billingLoading = false
})

View File

@ -169,9 +169,9 @@ export default {
.filter(domain => domain && domain.length > 0)
}).then((response) => {
this.workspacesStore.addOrUpdate(response.data)
this.alertSuccess('Custom domains saved.')
useAlert().success('Custom domains saved.')
}).catch((error) => {
this.alertError('Failed to update custom domains: ' + error.response.data.message)
useAlert().error('Failed to update custom domains: ' + error.response.data.message)
}).finally(() => {
this.customDomainsLoading = false
})
@ -182,12 +182,12 @@ export default {
},
deleteWorkspace (workspace) {
if (this.workspaces.length <= 1) {
this.alertError('You cannot delete your only workspace.')
useAlert().error('You cannot delete your only workspace.')
return
}
this.alertConfirm('Do you really want to delete this workspace? All forms created in this workspace will be removed.', () => {
useAlert().confirm('Do you really want to delete this workspace? All forms created in this workspace will be removed.', () => {
this.workspacesStore.delete(workspace.id).then(() => {
this.alertSuccess('Workspace successfully removed.')
useAlert().success('Workspace successfully removed.')
})
})
},

View File

@ -24,7 +24,7 @@ export default {
mounted () {
this.$router.push({ name: 'pricing' })
this.alertError('Unfortunately we could not confirm your subscription. Please try again and contact us if the issue persists.')
useAlert().error('Unfortunately we could not confirm your subscription. Please try again and contact us if the issue persists.')
},
computed: {}

View File

@ -63,10 +63,10 @@ export default {
this.$router.push({ name: 'home' })
if (this.user.has_enterprise_subscription) {
this.alertSuccess('Awesome! Your subscription to OpnForm is now confirmed! You now have access to all Enterprise ' +
useAlert().success('Awesome! Your subscription to OpnForm is now confirmed! You now have access to all Enterprise ' +
'features. No need to invite your teammates, just ask them to create a OpnForm account and to connect the same Notion workspace. Feel free to contact us if you have any question 🙌')
} else {
this.alertSuccess('Awesome! Your subscription to OpnForm is now confirmed! You now have access to all Pro ' +
useAlert().success('Awesome! Your subscription to OpnForm is now confirmed! You now have access to all Pro ' +
'features. Feel free to contact us if you have any question 🙌')
}
}

View File

@ -261,7 +261,7 @@ const copyTemplateUrl = () => {
el.select()
document.execCommand('copy')
document.body.removeChild(el)
this.alertSuccess('Copied!')
useAlert().success('Copied!')
}
// metaTitle() {

5
client/plugins/vue-confetti.client.js vendored Normal file
View File

@ -0,0 +1,5 @@
import VueConfetti from 'vue-confetti'
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp.use(VueConfetti)
})

View File

@ -20,6 +20,11 @@ export const useWorkspacesStore = defineStore('workspaces', () => {
storedWorkspaceId.value = id
}
const set = (items) => {
contentStore.content.value = new Map
save(items)
}
const save = (items) => {
contentStore.save(items)
if ((getCurrent.value == null) && contentStore.length.value) {
@ -39,6 +44,7 @@ export const useWorkspacesStore = defineStore('workspaces', () => {
currentId,
getCurrent,
setCurrentId,
set,
save,
remove
}