diff --git a/client/app.vue b/client/app.vue index a5cd1846..2bb02f04 100644 --- a/client/app.vue +++ b/client/app.vue @@ -47,7 +47,7 @@ - + @@ -125,6 +125,7 @@ export default { }, mounted() { + useCrisp().onCrispInit() useCrisp().showChat() }, diff --git a/client/components/global/NotificationsWrapper.vue b/client/components/global/NotificationsWrapper.vue new file mode 100644 index 00000000..646007bf --- /dev/null +++ b/client/components/global/NotificationsWrapper.vue @@ -0,0 +1,35 @@ + + + diff --git a/client/composables/useAlert.js b/client/composables/useAlert.js index e0357d3b..015778c8 100644 --- a/client/composables/useAlert.js +++ b/client/composables/useAlert.js @@ -1,50 +1,68 @@ -const { notify } = useNotification() +export function useAlert () { -export const useAlert = () => { - function success(message, autoClose = 10000) { - notify({ - title: "Success", - text: message, - type: "success", - duration: autoClose, + function success (message, autoClose = 10000, options = {}) { + return useToast().add({ + icon: 'i-heroicons-check-circle', + title: options.title ?? 'Success', + description: message, + color: 'green', + timeout: autoClose, + ...options }) } - function error(message, autoClose = 10000) { - notify({ - title: "Error", - text: message, - type: "error", - duration: autoClose, + function error (message, autoClose = 10000, options = {}) { + return useToast().add({ + icon: 'i-heroicons-exclamation-circle', + title: options.title ?? 'Error', + description: message, + color: 'red', + timeout: autoClose, + ...options }) } - function warning(message, autoClose = 10000) { - notify({ - title: "Warning", - text: message, - type: "warning", - duration: autoClose, + function warning (message, autoClose = 10000, options = {}) { + return useToast().add({ + icon: 'i-heroicons-exclamation-triangle', + title: options.title ?? 'Warning', + description: message, + color: 'yellow', + timeout: autoClose, + ...options }) } - function confirm(message, success, failure = () => {}, autoClose = 10000) { - notify({ - title: "Confirm", - text: message, - type: "confirm", - duration: autoClose, - data: { - success, - failure, - }, + function confirm ( + message, + onSuccess, + onFailure = null, + autoClose = 10000, + options = {} + ) { + return useToast().add({ + icon: 'i-heroicons-question-mark-circle', + title: options.title ?? 'Are you sure?', + description: message, + color: 'blue', + timeout: autoClose, + actions: [ + { label: options.successLabel ?? 'Yes', click: onSuccess }, + ...(onFailure ? [{ label: options.failureLabel ?? 'No', click: onFailure }] : []) + ], + ...options }) } + function remove (id) { + useToast().remove(id) + } + return { success, error, warning, confirm, + remove } } diff --git a/client/composables/useCrisp.js b/client/composables/useCrisp.js index 308c8f2d..58613246 100644 --- a/client/composables/useCrisp.js +++ b/client/composables/useCrisp.js @@ -1,80 +1,106 @@ -export const useCrisp = () => { - let crisp = import.meta.client ? window.Crisp : null +export function useCrisp () { + const crisp = import.meta.client ? window.Crisp : null - function openChat() { - if (!crisp) return + function onCrispInit () { + if (!crisp) + return + crisp.chat.onChatOpened(() => { + useAppStore().crisp.chatOpened = true + }) + crisp.chat.onChatClosed(() => { + useAppStore().crisp.chatOpened = false + }) + } + + function openChat () { + if (!crisp) + return showChat() crisp.chat.open() } - function showChat() { - if (!crisp) return + function showChat () { + if (!crisp) + return crisp.chat.show() + useAppStore().crisp.hidden = false } - function hideChat() { - if (!crisp) return + function hideChat () { + if (!crisp) + return crisp.chat.hide() + useAppStore().crisp.hidden = true } - function closeChat() { - if (!crisp) return + function closeChat () { + if (!crisp) + return crisp.chat.close() } - function openAndShowChat(message = null) { - if (!crisp) return + function openAndShowChat (message = null) { + if (!crisp) + return openChat() - if (message) sendTextMessage(message) + if (message) + sendTextMessage(message) } - function openHelpdesk() { - if (!crisp) return + function openHelpdesk () { + if (!crisp) + return openChat() crisp.chat.setHelpdeskView() } - function openHelpdeskArticle(articleSlug, locale = "en") { - if (!crisp) return + function openHelpdeskArticle (articleSlug, locale = 'en') { + if (!crisp) + return crisp.chat.openHelpdeskArticle(locale, articleSlug) } - function sendTextMessage(message) { - if (!crisp) return - crisp.message.send("text", message) + function sendTextMessage (message) { + if (!crisp) + return + crisp.message.send('text', message) } - function setUser(user) { - if (!crisp) return + function setUser (user) { + if (!crisp) + return crisp.user.setEmail(user.email) crisp.user.setNickname(user.name) crisp.session.setData({ - user_id: user.id, - "pro-subscription": user?.is_subscribed ?? false, - "stripe-id": user?.stripe_id ?? "", - subscription: user?.has_enterprise_subscription ? "enterprise" : "pro", + 'user_id': user.id, + 'pro-subscription': user?.is_subscribed ?? false, + 'stripe-id': user?.stripe_id ?? '', + 'subscription': user?.has_enterprise_subscription ? 'enterprise' : 'pro' }) if (user?.is_subscribed ?? false) { setSegments([ - "subscribed", - user?.has_enterprise_subscription ? "enterprise" : "pro", + 'subscribed', + user?.has_enterprise_subscription ? 'enterprise' : 'pro' ]) } } - function pushEvent(event, data = {}) { - if (!crisp) return + function pushEvent (event, data = {}) { + if (!crisp) + return crisp.session.pushEvent(event, data) } - function setSegments(segments, overwrite = false) { - if (!crisp) return + function setSegments (segments, overwrite = false) { + if (!crisp) + return crisp.session.setSegments(segments, overwrite) } return { crisp, + onCrispInit, openChat, showChat, hideChat, @@ -84,6 +110,7 @@ export const useCrisp = () => { openHelpdeskArticle, sendTextMessage, pushEvent, - setUser, + setSegments, + setUser } } diff --git a/client/nuxt.config.ts b/client/nuxt.config.ts index 88919a77..39e7faca 100644 --- a/client/nuxt.config.ts +++ b/client/nuxt.config.ts @@ -11,7 +11,6 @@ export default defineNuxtConfig({ '@pinia/nuxt', '@vueuse/nuxt', '@vueuse/motion/nuxt', - 'nuxt3-notifications', 'nuxt-simple-sitemap', '@nuxt/ui', ...process.env.NUXT_PUBLIC_GOOGLE_ANALYTICS_CODE ? ['nuxt-gtag'] : [], diff --git a/client/package.json b/client/package.json index 44bc0d3d..47bb8ece 100644 --- a/client/package.json +++ b/client/package.json @@ -49,7 +49,6 @@ "fuse.js": "^6.4.6", "js-sha256": "^0.10.0", "libphonenumber-js": "^1.10.44", - "nuxt3-notifications": "^1.1.9", "object-to-formdata": "^4.5.1", "pinia": "^2.1.7", "prismjs": "^1.24.1", diff --git a/client/pages/forms/[slug]/edit.vue b/client/pages/forms/[slug]/edit.vue index 5e468fcc..9b9a443a 100644 --- a/client/pages/forms/[slug]/edit.vue +++ b/client/pages/forms/[slug]/edit.vue @@ -74,14 +74,12 @@ watch(form, (form) => { onBeforeRouteLeave((to, from, next) => { if (isDirty()) { - return useAlert().confirm( - "Changes you made may not be saved. Are you sure want to leave?", - () => { - window.onbeforeunload = null - next() - }, - () => {}, - ) + if (window.confirm('Changes you made may not be saved. Are you sure want to leave?')) { + window.onbeforeunload = null + next() + } else { + next(false) + } } next() }) diff --git a/client/pages/forms/create/index.vue b/client/pages/forms/create/index.vue index a69bebda..a54446e8 100644 --- a/client/pages/forms/create/index.vue +++ b/client/pages/forms/create/index.vue @@ -42,16 +42,14 @@ useOpnSeoMeta({ }) onBeforeRouteLeave((to, from, next) => { - if (isDirty()) { - return useAlert().confirm( - "Changes you made may not be saved. Are you sure want to leave?", - () => { + if (this.isDirty()) { + if (window.confirm('Changes you made may not be saved. Are you sure want to leave?')) { window.onbeforeunload = null next() - }, - () => {}, - ) - } + } else { + next(false) + } + } next() }) diff --git a/client/stores/app.js b/client/stores/app.js index 853285f7..37016c44 100644 --- a/client/stores/app.js +++ b/client/stores/app.js @@ -5,6 +5,10 @@ export const useAppStore = defineStore("app", { state: () => ({ layout: "default", navbarHidden: false, + crisp: { + chatOpened: false, + hidden: false + }, // App Loader loader: { diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..e4c309cd --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "opnform", + "lockfileVersion": 3, + "requires": true, + "packages": {} +}