Refactoring stores and templates pages to comp. api

This commit is contained in:
Julien Nahum 2023-12-19 13:46:55 +01:00
parent 3b798c12fd
commit bb519907f6
24 changed files with 432 additions and 457 deletions

View File

@ -20,7 +20,7 @@
</button> </button>
</div> </div>
<div class="sm:flex sm:flex-col sm:items-start"> <div class="sm:flex sm:flex-col sm:items-start">
<div v-if="$scopedSlots.hasOwnProperty('icon')" class="flex w-full justify-center mb-4"> <div v-if="$slots.hasOwnProperty('icon')" class="flex w-full justify-center mb-4">
<div class="w-14 h-14 rounded-full flex justify-center items-center" <div class="w-14 h-14 rounded-full flex justify-center items-center"
:class="'bg-'+iconColor+'-100 text-'+iconColor+'-600'" :class="'bg-'+iconColor+'-100 text-'+iconColor+'-600'"
> >
@ -28,7 +28,7 @@
</div> </div>
</div> </div>
<div class="mt-3 text-center sm:mt-0 w-full"> <div class="mt-3 text-center sm:mt-0 w-full">
<h2 v-if="$scopedSlots.hasOwnProperty('title')" <h2 v-if="$slots.hasOwnProperty('title')"
class="text-2xl font-semibold text-center text-gray-900" class="text-2xl font-semibold text-center text-gray-900"
> >
<slot name="title"/> <slot name="title"/>

View File

@ -72,7 +72,7 @@
My Forms My Forms
</NuxtLink> </NuxtLink>
<NuxtLink v-if="userOnboarded" :to="{ name: 'my_templates' }" <NuxtLink v-if="userOnboarded" :to="{ name: 'templates-my-templates' }"
class="block block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center" class="block block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
> >
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 mr-2" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 mr-2" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
@ -81,7 +81,7 @@
My Templates My Templates
</NuxtLink> </NuxtLink>
<NuxtLink :to="{ name: 'settings.profile' }" <NuxtLink :to="{ name: 'settings-profile' }"
class="block block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center" class="block block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
> >
<svg class="w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" <svg class="w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
@ -209,7 +209,8 @@ export default {
this.formsStore.resetState() this.formsStore.resetState()
// Redirect to login. // Redirect to login.
this.$router.push({ name: 'login' }) const router = useRouter()
router.push({ name: 'login' })
}, },
} }
} }

View File

@ -129,6 +129,12 @@ export default {
submitButtonClass: { type: String, default: '' } submitButtonClass: { type: String, default: '' }
}, },
setup() {
return {
isIframe: useIsIframe()
}
},
data () { data () {
return { return {
loading: false, loading: false,
@ -143,11 +149,8 @@ export default {
}, },
computed: { computed: {
isIframe () {
return window.location !== window.parent.location || window.frameElement
},
isEmbedPopup () { isEmbedPopup () {
return window.location.href.includes('popup=true') return process.client && window.location.href.includes('popup=true')
}, },
theme () { theme () {
return this.themes[this.themes.hasOwnProperty(this.form.theme) ? this.form.theme : 'default'] return this.themes[this.themes.hasOwnProperty(this.form.theme) ? this.form.theme : 'default']
@ -156,7 +159,7 @@ export default {
return this.$route.name === 'forms.show_public' return this.$route.name === 'forms.show_public'
}, },
isHideTitle () { isHideTitle () {
return this.form.hide_title || window.location.href.includes('hide_title=true') return this.form.hide_title || (process.client && window.location.href.includes('hide_title=true'))
} }
}, },

View File

@ -106,7 +106,8 @@ export default {
const workingFormStore = useWorkingFormStore() const workingFormStore = useWorkingFormStore()
return { return {
recordsStore, recordsStore,
workingFormStore workingFormStore,
darkModeEnabled: useDark()
} }
}, },
@ -118,7 +119,6 @@ export default {
* Used to force refresh components by changing their keys * Used to force refresh components by changing their keys
*/ */
formVersionId: 1, formVersionId: 1,
darkModeEnabled: document.body.classList.contains('dark'),
isAutoSubmit: false, isAutoSubmit: false,
/** /**
* If currently dragging a field * If currently dragging a field
@ -259,7 +259,7 @@ export default {
return return
} }
if (this.form.use_captcha) { if (this.form.use_captcha && process.client) {
this.dataForm['h-captcha-response'] = document.getElementsByName('h-captcha-response')[0].value this.dataForm['h-captcha-response'] = document.getElementsByName('h-captcha-response')[0].value
this.$refs.hcaptcha.reset() this.$refs.hcaptcha.reset()
} }
@ -291,6 +291,7 @@ export default {
} }
// Scroll to error // Scroll to error
if (process.server) return
const elements = document.getElementsByClassName('has-error') const elements = document.getElementsByClassName('has-error')
if (elements.length > 0) { if (elements.length > 0) {
window.scroll({ window.scroll({

View File

@ -161,7 +161,7 @@ export default {
if (response.data.message) { if (response.data.message) {
this.alertSuccess(response.data.message) this.alertSuccess(response.data.message)
} }
this.templatesStore.addOrUpdate(response.data.data) this.templatesStore.save(response.data.data)
this.$emit('close') this.$emit('close')
}) })
}, },
@ -171,7 +171,7 @@ export default {
if (response.data.message) { if (response.data.message) {
this.alertSuccess(response.data.message) this.alertSuccess(response.data.message)
} }
this.templatesStore.addOrUpdate(response.data.data) this.templatesStore.save(response.data.data)
this.$emit('close') this.$emit('close')
}) })
}, },

View File

@ -42,6 +42,7 @@
<script> <script>
import ForgotPasswordModal from '../ForgotPasswordModal.vue' import ForgotPasswordModal from '../ForgotPasswordModal.vue'
import {opnFetch} from "~/composables/useOpnApi.js";
export default { export default {
name: 'LoginForm', name: 'LoginForm',
@ -78,10 +79,10 @@ export default {
const data = await this.form.post('login') const data = await this.form.post('login')
// Save the token. // Save the token.
this.authStore.setToken(data.token, this.remember) this.authStore.setToken(data.token)
// Fetch the user. const userData = await opnFetch('user')
await this.authStore.fetchUser() this.authStore.setUser(userData)
// Redirect home. // Redirect home.
this.redirect() this.redirect()
@ -93,13 +94,14 @@ export default {
return return
} }
const intendedUrl = Cookies.get('intended_url') const intendedUrlCookie = useCookie('intended_url')
const router = useRouter()
if (intendedUrl) { if (intendedUrlCookie.value) {
Cookies.remove('intended_url') router.push({ path: intendedUrlCookie.value })
this.$router.push({ path: intendedUrl }) useCookie('intended_url').value = null
} else { } else {
this.$router.push({ name: 'home' }) router.push({ name: 'home' })
} }
} }
} }

View File

@ -29,7 +29,7 @@
<p class="line-clamp-2 mt-2 text-sm font-normal text-gray-600"> <p class="line-clamp-2 mt-2 text-sm font-normal text-gray-600">
{{ cleanQuotes(template.short_description) }} {{ cleanQuotes(template.short_description) }}
</p> </p>
<template-tags :slug="template.slug" <template-tags :template="template"
class="flex mt-4 items-center flex-wrap gap-3" class="flex mt-4 items-center flex-wrap gap-3"
/> />
<router-link :to="{params:{slug:template.slug},name:'templates-slug'}" title=""> <router-link :to="{params:{slug:template.slug},name:'templates-slug'}" title="">
@ -46,8 +46,7 @@ export default {
props: { props: {
template: { template: {
type: Object, type: Object
required: true
} }
}, },

View File

@ -1,42 +1,42 @@
<template> <template>
<div v-if="template"> <div v-if="template">
<template v-if="displayAll"> <!-- <template v-if="displayAll">-->
<span v-if="template.is_new" <!-- <span v-if="template.is_new"-->
class="inline-flex items-center gap-1 px-2 py-1 text-xs font-medium text-white bg-blue-500 rounded-full" <!-- class="inline-flex items-center gap-1 px-2 py-1 text-xs font-medium text-white bg-blue-500 rounded-full"-->
> <!-- >-->
<svg aria-hidden="true" class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" <!-- <svg aria-hidden="true" class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"-->
fill="currentColor" <!-- fill="currentColor"-->
> <!-- >-->
<path fill-rule="evenodd" <!-- <path fill-rule="evenodd"-->
d="M5 2a1 1 0 011 1v1h1a1 1 0 010 2H6v1a1 1 0 01-2 0V6H3a1 1 0 010-2h1V3a1 1 0 011-1zm0 10a1 1 0 011 1v1h1a1 1 0 110 2H6v1a1 1 0 11-2 0v-1H3a1 1 0 110-2h1v-1a1 1 0 011-1zM12 2a1 1 0 01.967.744L14.146 7.2 17.5 9.134a1 1 0 010 1.732l-3.354 1.935-1.18 4.455a1 1 0 01-1.933 0L9.854 12.8 6.5 10.866a1 1 0 010-1.732l3.354-1.935 1.18-4.455A1 1 0 0112 2z" <!-- d="M5 2a1 1 0 011 1v1h1a1 1 0 010 2H6v1a1 1 0 01-2 0V6H3a1 1 0 010-2h1V3a1 1 0 011-1zm0 10a1 1 0 011 1v1h1a1 1 0 110 2H6v1a1 1 0 11-2 0v-1H3a1 1 0 110-2h1v-1a1 1 0 011-1zM12 2a1 1 0 01.967.744L14.146 7.2 17.5 9.134a1 1 0 010 1.732l-3.354 1.935-1.18 4.455a1 1 0 01-1.933 0L9.854 12.8 6.5 10.866a1 1 0 010-1.732l3.354-1.935 1.18-4.455A1 1 0 0112 2z"-->
clip-rule="evenodd" <!-- clip-rule="evenodd"-->
/> <!-- />-->
</svg> <!-- </svg>-->
New <!-- New-->
</span> <!-- </span>-->
<span v-for="item in types" <!-- <span v-for="item in types"-->
class="inline-flex items-center rounded-full bg-gray-50 dark:bg-gray-800 dark:text-gray-400 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10" <!-- class="inline-flex items-center rounded-full bg-gray-50 dark:bg-gray-800 dark:text-gray-400 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10"-->
> <!-- >-->
{{ item }} <!-- {{ item }}-->
</span> <!-- </span>-->
<span v-for="item in industries" <!-- <span v-for="item in industries"-->
class="inline-flex items-center rounded-full bg-blue-50 dark:bg-blue-900 dark:text-gray-400 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10" <!-- class="inline-flex items-center rounded-full bg-blue-50 dark:bg-blue-900 dark:text-gray-400 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10"-->
> <!-- >-->
{{ item }} <!-- {{ item }}-->
</span> <!-- </span>-->
</template> <!-- </template>-->
<template v-else> <!-- <template v-else>-->
<span v-if="types.length > 0" <!-- <span v-if="types.length > 0"-->
class="inline-flex items-center rounded-full bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10" <!-- class="inline-flex items-center rounded-full bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10"-->
> <!-- >-->
{{ types[0] }} <template v-if="types.length > 1">+{{ types.length - 1 }}</template> <!-- {{ types[0] }} <template v-if="types.length > 1">+{{ types.length - 1 }}</template>-->
</span> <!-- </span>-->
<span v-if="industries.length > 0" <!-- <span v-if="industries.length > 0"-->
class="inline-flex items-center rounded-full bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10" <!-- class="inline-flex items-center rounded-full bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10"-->
> <!-- >-->
{{ industries[0] }} <template v-if="industries.length > 1">+{{ industries.length - 1 }}</template> <!-- {{ industries[0] }} <template v-if="industries.length > 1">+{{ industries.length - 1 }}</template>-->
</span> <!-- </span>-->
</template> <!-- </template>-->
</div> </div>
</template> </template>
@ -46,8 +46,8 @@ import { useTemplatesStore } from '../../../stores/templates'
export default { export default {
props: { props: {
slug: { template: {
type: String, type: Object,
required: true required: true
}, },
displayAll: { displayAll: {
@ -66,19 +66,17 @@ export default {
data: () => ({}), data: () => ({}),
computed: { computed: {
template () { // template () {
return this.templatesStore.getBySlug(this.slug) // return this.templatesStore.getByKey(this.slug)
}, // },
types () { // types () {
if (!this.template) return null // if (!this.template) return null
console.log('template in types',this.template) // return this.templatesStore.getTemplateTypes(this.template.types)
return this.templatesStore.getTemplateTypes(this.template.types) // },
}, // industries () {
industries () { // if (!this.template) return null
if (!this.template) return null // return this.templatesStore.getTemplateIndustries(this.template.industries)
console.log('template in types',this.template) // }
return this.templatesStore.getTemplateIndustries(this.template.industries)
}
}, },
methods: {} methods: {}

View File

@ -20,7 +20,7 @@
</div> </div>
</div> </div>
<div v-if="templatesLoading" class="text-center mt-4"> <div v-if="loading" class="text-center mt-4">
<Loader class="h-6 w-6 text-nt-blue mx-auto"/> <Loader class="h-6 w-6 text-nt-blue mx-auto"/>
</div> </div>
<p v-else-if="enrichedTemplates.length === 0" class="text-center mt-4"> <p v-else-if="enrichedTemplates.length === 0" class="text-center mt-4">
@ -83,15 +83,6 @@ import Form from 'vform'
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import SingleTemplate from './SingleTemplate.vue' import SingleTemplate from './SingleTemplate.vue'
// const loadTemplates = function (onlyMy) {
// const templatesStore = useTemplatesStore()
// if(onlyMy){
// templatesStore.loadAll({'onlymy':true})
// } else {
// templatesStore.loadIfEmpty()
// }
// }
export default { export default {
name: 'TemplatesList', name: 'TemplatesList',
components: {SingleTemplate}, components: {SingleTemplate},

View File

@ -1,6 +1,7 @@
import {serialize} from 'object-to-formdata'; import {serialize} from 'object-to-formdata';
import Errors from './Errors'; import Errors from './Errors';
import cloneDeep from 'clone-deep'; import cloneDeep from 'clone-deep';
import {opnFetch} from "~/composables/useOpnApi.js";
function hasFiles(data) { function hasFiles(data) {
return data instanceof File || return data instanceof File ||
data instanceof Blob || data instanceof Blob ||
@ -120,18 +121,14 @@ class Form {
config.transformRequest = [data => serialize(data)]; config.transformRequest = [data => serialize(data)];
} }
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
useOpnApi(config.url, config) opnFetch(config.url, config)
.then(({data, error}) => { .then((data) => {
if (error.value) {
this.handleErrors(error);
reject(error);
return;
}
this.finishProcessing(); this.finishProcessing();
resolve(data.value); resolve(data);
}).catch((error) => {
this.handleErrors(error);
resolve(error)
}) })
}); });
} }
@ -139,8 +136,8 @@ class Form {
handleErrors(error) { handleErrors(error) {
this.busy = false; this.busy = false;
if (error.value) { if (error) {
this.errors.set(this.extractErrors(error.value.data)); this.errors.set(this.extractErrors(error.data));
} }
} }

View File

@ -0,0 +1,64 @@
// Composable with all the logic to encapsulate a default content store
export const useContentStore = (mapKey = 'id') => {
const content = ref(new Map())
const loading = ref(false)
// Computed
const getAll = computed(() => {
return [...content.value.values()]
})
const getByKey = (key) => {
if (Array.isArray(key)) {
return key.map((k) => content.value.get(k)).filter((item) => item !== undefined)
}
return content.value.get(key)
}
// Actions
function set(items) {
content.value = new Map
items.forEach((item) => {
content.value.set(item[mapKey], item)
})
}
function save(items) {
if (!Array.isArray(items)) items = [items]
items.forEach((item) => {
content.value.set(item[mapKey], item)
})
}
function remove(item) {
content.value.remove(item[mapKey])
}
function startLoading() {
loading.value = true
}
function stopLoading() {
loading.value = false
}
function resetState() {
set([])
stopLoading()
}
return {
content,
loading,
getAll,
getByKey,
set,
save,
remove,
startLoading,
stopLoading,
resetState
}
}

View File

@ -44,6 +44,10 @@ export function getOpnRequestsOptions(request, opts) {
} }
} }
export const opnFetch = (request, opts = {}) => {
return $fetch(request, getOpnRequestsOptions(request, opts))
}
export const useOpnApi = (request, opts = {}) => { export const useOpnApi = (request, opts = {}) => {
return useFetch(request, getOpnRequestsOptions(request, opts)) return useFetch(request, getOpnRequestsOptions(request, opts))
} }

View File

@ -1,3 +1,5 @@
import {useWorkspacesStore} from "~/stores/workspaces.js";
export default defineNuxtRouteMiddleware(async(to, from) => { export default defineNuxtRouteMiddleware(async(to, from) => {
const authStore = useAuthStore() const authStore = useAuthStore()
authStore.initStore(useCookie('token').value, useCookie('admin_token').value) authStore.initStore(useCookie('token').value, useCookie('admin_token').value)

223
client/package-lock.json generated
View File

@ -24,6 +24,7 @@
"js-sha256": "^0.9.0", "js-sha256": "^0.9.0",
"libphonenumber-js": "^1.10.44", "libphonenumber-js": "^1.10.44",
"object-to-formdata": "^4.5.1", "object-to-formdata": "^4.5.1",
"pinia": "^2.1.7",
"prismjs": "^1.24.1", "prismjs": "^1.24.1",
"qrcode": "^1.5.1", "qrcode": "^1.5.1",
"query-builder-vue-3": "^1.0.1", "query-builder-vue-3": "^1.0.1",
@ -2549,56 +2550,6 @@
"url": "https://github.com/sponsors/posva" "url": "https://github.com/sponsors/posva"
} }
}, },
"node_modules/@pinia/nuxt/node_modules/pinia": {
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.7.tgz",
"integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
"dependencies": {
"@vue/devtools-api": "^6.5.0",
"vue-demi": ">=0.14.5"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"@vue/composition-api": "^1.4.0",
"typescript": ">=4.4.4",
"vue": "^2.6.14 || ^3.3.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/@pinia/nuxt/node_modules/vue-demi": {
"version": "0.14.6",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/@pkgjs/parseargs": { "node_modules/@pkgjs/parseargs": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@ -2828,9 +2779,9 @@
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.9.0", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.1.tgz",
"integrity": "sha512-+1ge/xmaJpm1KVBuIH38Z94zj9fBD+hp+/5WLaHgyY8XLq1ibxk/zj6dTXaqM2cAbYKq8jYlhHd6k05If1W5xA==", "integrity": "sha512-6vMdBZqtq1dVQ4CWdhFwhKZL6E4L1dV6jUjuBvsavvNJSppzi6dLBbuV+3+IyUREaj9ZFvQefnQm28v4OCXlig==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -2840,9 +2791,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.9.0", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.1.tgz",
"integrity": "sha512-im6hUEyQ7ZfoZdNvtwgEJvBWZYauC9KVKq1w58LG2Zfz6zMd8gRrbN+xCVoqA2hv/v6fm9lp5LFGJ3za8EQH3A==", "integrity": "sha512-Jto9Fl3YQ9OLsTDWtLFPtaIMSL2kwGyGoVCmPC8Gxvym9TCZm4Sie+cVeblPO66YZsYH8MhBKDMGZ2NDxuk/XQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -2852,9 +2803,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.9.0", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.1.tgz",
"integrity": "sha512-u7aTMskN6Dmg1lCT0QJ+tINRt+ntUrvVkhbPfFz4bCwRZvjItx2nJtwJnJRlKMMaQCHRjrNqHRDYvE4mBm3DlQ==", "integrity": "sha512-LtYcLNM+bhsaKAIGwVkh5IOWhaZhjTfNOkGzGqdHvhiCUVuJDalvDxEdSnhFzAn+g23wgsycmZk1vbnaibZwwA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -2864,9 +2815,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.9.0", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.1.tgz",
"integrity": "sha512-8FvEl3w2ExmpcOmX5RJD0yqXcVSOqAJJUJ29Lca29Ik+3zPS1yFimr2fr5JSZ4Z5gt8/d7WqycpgkX9nocijSw==", "integrity": "sha512-KyP/byeXu9V+etKO6Lw3E4tW4QdcnzDG/ake031mg42lob5tN+5qfr+lkcT/SGZaH2PdW4Z1NX9GHEkZ8xV7og==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -2876,9 +2827,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.9.0", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.1.tgz",
"integrity": "sha512-lHoKYaRwd4gge+IpqJHCY+8Vc3hhdJfU6ukFnnrJasEBUvVlydP8PuwndbWfGkdgSvZhHfSEw6urrlBj0TSSfg==", "integrity": "sha512-Yqz/Doumf3QTKplwGNrCHe/B2p9xqDghBZSlAY0/hU6ikuDVQuOUIpDP/YcmoT+447tsZTmirmjgG3znvSCR0Q==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -2888,9 +2839,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.9.0", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.1.tgz",
"integrity": "sha512-JbEPfhndYeWHfOSeh4DOFvNXrj7ls9S/2omijVsao+LBPTPayT1uKcK3dHW3MwDJ7KO11t9m2cVTqXnTKpeaiw==", "integrity": "sha512-u3XkZVvxcvlAOlQJ3UsD1rFvLWqu4Ef/Ggl40WAVCuogf4S1nJPHh5RTgqYFpCOvuGJ7H5yGHabjFKEZGExk5Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -2900,9 +2851,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.9.0", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.1.tgz",
"integrity": "sha512-ahqcSXLlcV2XUBM3/f/C6cRoh7NxYA/W7Yzuv4bDU1YscTFw7ay4LmD7l6OS8EMhTNvcrWGkEettL1Bhjf+B+w==", "integrity": "sha512-0XSYN/rfWShW+i+qjZ0phc6vZ7UWI8XWNz4E/l+6edFt+FxoEghrJHjX1EY/kcUGCnZzYYRCl31SNdfOi450Aw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -2912,9 +2863,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.9.0", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.1.tgz",
"integrity": "sha512-uwvOYNtLw8gVtrExKhdFsYHA/kotURUmZYlinH2VcQxNCQJeJXnkmWgw2hI9Xgzhgu7J9QvWiq9TtTVwWMDa+w==", "integrity": "sha512-LmYIO65oZVfFt9t6cpYkbC4d5lKHLYv5B4CSHRpnANq0VZUQXGcCPXHzbCXCz4RQnx7jvlYB1ISVNCE/omz5cw==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@ -2924,9 +2875,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.9.0", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.1.tgz",
"integrity": "sha512-m6pkSwcZZD2LCFHZX/zW2aLIISyzWLU3hrLLzQKMI12+OLEzgruTovAxY5sCZJkipklaZqPy/2bEEBNjp+Y7xg==", "integrity": "sha512-kr8rEPQ6ns/Lmr/hiw8sEVj9aa07gh1/tQF2Y5HrNCCEPiCBGnBUt9tVusrcBBiJfIt1yNaXN6r1CCmpbFEDpg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -2936,9 +2887,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.9.0", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.1.tgz",
"integrity": "sha512-VFAC1RDRSbU3iOF98X42KaVicAfKf0m0OvIu8dbnqhTe26Kh6Ym9JrDulz7Hbk7/9zGc41JkV02g+p3BivOdAg==", "integrity": "sha512-t4QSR7gN+OEZLG0MiCgPqMWZGwmeHhsM4AkegJ0Kiy6TnJ9vZ8dEIwHw1LcZKhbHxTY32hp9eVCMdR3/I8MGRw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -2948,9 +2899,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.9.0", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.1.tgz",
"integrity": "sha512-9jPgMvTKXARz4inw6jezMLA2ihDBvgIU9Ml01hjdVpOcMKyxFBJrn83KVQINnbeqDv0+HdO1c09hgZ8N0s820Q==", "integrity": "sha512-7XI4ZCBN34cb+BH557FJPmh0kmNz2c25SCQeT9OiFWEgf8+dL6ZwJ8f9RnUIit+j01u07Yvrsuu1rZGxJCc51g==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -2960,9 +2911,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.9.0", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.1.tgz",
"integrity": "sha512-WE4pT2kTXQN2bAv40Uog0AsV7/s9nT9HBWXAou8+++MBCnY51QS02KYtm6dQxxosKi1VIz/wZIrTQO5UP2EW+Q==", "integrity": "sha512-yE5c2j1lSWOH5jp+Q0qNL3Mdhr8WuqCNVjc6BxbVfS5cAS6zRmdiw7ktb8GNpDCEUJphILY6KACoFoRtKoqNQg==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -2972,9 +2923,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.9.0", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.1.tgz",
"integrity": "sha512-aPP5Q5AqNGuT0tnuEkK/g4mnt3ZhheiXrDIiSVIHN9mcN21OyXDVbEMqmXPE7e2OplNLDkcvV+ZoGJa2ZImFgw==", "integrity": "sha512-PyJsSsafjmIhVgaI1Zdj7m8BB8mMckFah/xbpplObyHfiXzKcI5UOUXRyOdHW7nz4DpMCuzLnF7v5IWHenCwYA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3227,9 +3178,9 @@
} }
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.10.4", "version": "20.10.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz",
"integrity": "sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==", "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==",
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
} }
@ -6732,9 +6683,9 @@
} }
}, },
"node_modules/libphonenumber-js": { "node_modules/libphonenumber-js": {
"version": "1.10.51", "version": "1.10.52",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.51.tgz", "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.52.tgz",
"integrity": "sha512-vY2I+rQwrDQzoPds0JeTEpeWzbUJgqoV0O4v31PauHBb/e+1KCXKylHcDnBMgJZ9fH9mErsEbROJY3Z3JtqEmg==" "integrity": "sha512-6vCuCHgem+OW1/VCAKgkasfegItCea8zIT7s9/CG/QxdCMIM7GfzbEBG5d7lGO3rzipjt5woOQL3DiHa8Fy78Q=="
}, },
"node_modules/lie": { "node_modules/lie": {
"version": "3.1.1", "version": "3.1.1",
@ -8195,6 +8146,56 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/pinia": {
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.7.tgz",
"integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
"dependencies": {
"@vue/devtools-api": "^6.5.0",
"vue-demi": ">=0.14.5"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"@vue/composition-api": "^1.4.0",
"typescript": ">=4.4.4",
"vue": "^2.6.14 || ^3.3.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/pinia/node_modules/vue-demi": {
"version": "0.14.6",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/pirates": { "node_modules/pirates": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
@ -9212,9 +9213,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.9.0", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.0.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.1.tgz",
"integrity": "sha512-bUHW/9N21z64gw8s6tP4c88P382Bq/L5uZDowHlHx6s/QWpjJXivIAbEw6LZthgSvlEizZBfLC4OAvWe7aoF7A==", "integrity": "sha512-pgPO9DWzLoW/vIhlSoDByCzcpX92bKEorbgXuZrqxByte3JFk2xSW2JEeAcyLc9Ru9pqcNNW+Ob7ntsk2oT/Xw==",
"bin": { "bin": {
"rollup": "dist/bin/rollup" "rollup": "dist/bin/rollup"
}, },
@ -9223,19 +9224,19 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.9.0", "@rollup/rollup-android-arm-eabi": "4.9.1",
"@rollup/rollup-android-arm64": "4.9.0", "@rollup/rollup-android-arm64": "4.9.1",
"@rollup/rollup-darwin-arm64": "4.9.0", "@rollup/rollup-darwin-arm64": "4.9.1",
"@rollup/rollup-darwin-x64": "4.9.0", "@rollup/rollup-darwin-x64": "4.9.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.9.0", "@rollup/rollup-linux-arm-gnueabihf": "4.9.1",
"@rollup/rollup-linux-arm64-gnu": "4.9.0", "@rollup/rollup-linux-arm64-gnu": "4.9.1",
"@rollup/rollup-linux-arm64-musl": "4.9.0", "@rollup/rollup-linux-arm64-musl": "4.9.1",
"@rollup/rollup-linux-riscv64-gnu": "4.9.0", "@rollup/rollup-linux-riscv64-gnu": "4.9.1",
"@rollup/rollup-linux-x64-gnu": "4.9.0", "@rollup/rollup-linux-x64-gnu": "4.9.1",
"@rollup/rollup-linux-x64-musl": "4.9.0", "@rollup/rollup-linux-x64-musl": "4.9.1",
"@rollup/rollup-win32-arm64-msvc": "4.9.0", "@rollup/rollup-win32-arm64-msvc": "4.9.1",
"@rollup/rollup-win32-ia32-msvc": "4.9.0", "@rollup/rollup-win32-ia32-msvc": "4.9.1",
"@rollup/rollup-win32-x64-msvc": "4.9.0", "@rollup/rollup-win32-x64-msvc": "4.9.1",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },

View File

@ -36,6 +36,7 @@
"js-sha256": "^0.9.0", "js-sha256": "^0.9.0",
"libphonenumber-js": "^1.10.44", "libphonenumber-js": "^1.10.44",
"object-to-formdata": "^4.5.1", "object-to-formdata": "^4.5.1",
"pinia": "^2.1.7",
"prismjs": "^1.24.1", "prismjs": "^1.24.1",
"qrcode": "^1.5.1", "qrcode": "^1.5.1",
"query-builder-vue-3": "^1.0.1", "query-builder-vue-3": "^1.0.1",

View File

@ -121,7 +121,7 @@ export default {
this.initForm() this.initForm()
this.formInitialHash = this.hashString(JSON.stringify(this.form.data())) this.formInitialHash = this.hashString(JSON.stringify(this.form.data()))
if (this.$route.query.template !== undefined && this.$route.query.template) { if (this.$route.query.template !== undefined && this.$route.query.template) {
const template = this.templatesStore.getBySlug(this.$route.query.template) const template = this.templatesStore.getByKey(this.$route.query.template)
if (template && template.structure) { if (template && template.structure) {
this.form = new Form({ ...this.form.data(), ...template.structure }) this.form = new Form({ ...this.form.data(), ...template.structure })
} }

View File

@ -116,7 +116,7 @@ export default {
this.initForm() this.initForm()
if (this.$route.query.template !== undefined && this.$route.query.template) { if (this.$route.query.template !== undefined && this.$route.query.template) {
const template = this.templatesStore.getBySlug(this.$route.query.template) const template = this.templatesStore.getByKey(this.$route.query.template)
if (template && template.structure) { if (template && template.structure) {
this.form = new Form({ ...this.form.data(), ...template.structure }) this.form = new Form({ ...this.form.data(), ...template.structure })
} }

View File

@ -126,10 +126,8 @@ const loadForms = function () {
const formsStore = useFormsStore() const formsStore = useFormsStore()
const workspacesStore = useWorkspacesStore() const workspacesStore = useWorkspacesStore()
formsStore.startLoading() formsStore.startLoading()
return workspacesStore.loadIfEmpty().then(() => { workspacesStore.loadIfEmpty().then(() => {
if (process.client) { formsStore.loadIfEmpty(workspacesStore.currentId)
formsStore.loadIfEmpty(workspacesStore.currentId)
}
}) })
} }

View File

@ -1,14 +1,14 @@
<template> <template>
<div class="flex flex-col min-h-full"> <div class="flex flex-col min-h-full">
<breadcrumb :path="breadcrumbs"> <breadcrumb :path="breadcrumbs" v-if="template">
<template #left> <template #left>
<div v-if="canEditTemplate" class="ml-5"> <div v-if="canEditTemplate" class="ml-5">
<v-button color="gray" size="small" @click.prevent="showFormTemplateModal=true"> <v-button color="gray" size="small" @click.prevent="showFormTemplateModal=true">
Edit Template Edit Template
</v-button> </v-button>
<form-template-modal v-if="form" :form="form" :template="template" :show="showFormTemplateModal" <!-- <form-template-modal v-if="form" :form="form" :template="template" :show="showFormTemplateModal"-->
@close="showFormTemplateModal=false" <!-- @close="showFormTemplateModal=false"-->
/> <!-- />-->
</div> </div>
</template> </template>
<template #right> <template #right>
@ -25,10 +25,7 @@
</template> </template>
</breadcrumb> </breadcrumb>
<div v-if="templatesLoading" class="text-center my-4"> <p v-if="template === null || !template" class="text-center my-4">
<Loader class="h-6 w-6 text-nt-blue mx-auto"/>
</div>
<p v-else-if="template === null || !template" class="text-center my-4">
We could not find this template. We could not find this template.
</p> </p>
<template v-else> <template v-else>
@ -48,9 +45,9 @@
<p class="mt-2 text-lg font-normal text-gray-600"> <p class="mt-2 text-lg font-normal text-gray-600">
{{ cleanQuotes(template.short_description) }} {{ cleanQuotes(template.short_description) }}
</p> </p>
<template-tags :slug="template.slug" :display-all="true" <!-- <template-tags :slug="template.slug" :display-all="true"-->
class="flex flex-wrap items-center justify-center gap-3 mt-4 md:justify-start" <!-- class="flex flex-wrap items-center justify-center gap-3 mt-4 md:justify-start"-->
/> <!-- />-->
</div> </div>
</div> </div>
</div> </div>
@ -119,7 +116,8 @@
</div> </div>
</section> </section>
<section v-if="template.related_templates.length > 0" class="py-12 bg-white border-t border-gray-200 sm:py-16"> <section v-if="relatedTemplates && relatedTemplates.length > 0"
class="py-12 bg-white border-t border-gray-200 sm:py-16">
<div class="px-4 mx-auto sm:px-6 lg:px-8 max-w-7xl"> <div class="px-4 mx-auto sm:px-6 lg:px-8 max-w-7xl">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h4 class="text-xl font-bold tracking-tight text-gray-900 sm:text-2xl"> <h4 class="text-xl font-bold tracking-tight text-gray-900 sm:text-2xl">
@ -131,7 +129,7 @@
</div> </div>
<div class="grid grid-cols-1 gap-8 mt-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 sm:gap-y-12"> <div class="grid grid-cols-1 gap-8 mt-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 sm:gap-y-12">
<single-template v-for="related in template.related_templates" :key="related.id" :template="related"/> <single-template v-for="related in relatedTemplates" :key="related.id" :template="related"/>
</div> </div>
</div> </div>
</section> </section>
@ -196,108 +194,93 @@
</div> </div>
</template> </template>
<script> <script setup>
import Form from 'vform'
import {computed} from 'vue' import {computed} from 'vue'
import OpenCompleteForm from '../../components/open/forms/OpenCompleteForm.vue' import OpenCompleteForm from '../../components/open/forms/OpenCompleteForm.vue'
import Breadcrumb from '~/components/global/Breadcrumb.vue' import Breadcrumb from '~/components/global/Breadcrumb.vue'
import SeoMeta from '../../mixins/seo-meta.js'
import TemplateTags from '../../components/pages/templates/TemplateTags.vue'
import SingleTemplate from '../../components/pages/templates/SingleTemplate.vue' import SingleTemplate from '../../components/pages/templates/SingleTemplate.vue'
import FormTemplateModal from '../../components/open/forms/components/templates/FormTemplateModal.vue' import {fetchTemplate} from "~/stores/templates.js";
export default { const authStore = useAuthStore()
const templatesStore = useTemplatesStore()
components: {Breadcrumb, OpenCompleteForm, TemplateTags, SingleTemplate, FormTemplateModal}, const route = useRoute()
mixins: [SeoMeta], const slug = computed(() => route.params.slug)
setup() { const template = computed(() => templatesStore.getByKey(slug.value))
const authStore = useAuthStore() const form = computed(() => template.value.structure)
const templatesStore = useTemplatesStore()
const route = useRoute() // Fetch the template
const slug = computed(() => route.params.slug) if (!template.value) {
if (slug) { const {data} = await fetchTemplate(slug.value)
templatesStore.loadTemplate(slug) templatesStore.save(data.value)
}
return {
templatesStore,
authenticated: computed(() => authStore.check),
user: computed(() => authStore.user),
templatesLoading: computed(() => templatesStore.loading)
}
},
data() {
return {
showFormTemplateModal: false
}
},
mounted() {
},
methods: {
cleanQuotes(str) {
// Remove starting and ending quotes if any
return (str) ? str.replace(/^"/, '').replace(/"$/, '') : ''
},
copyTemplateUrl() {
const str = this.template.share_url
const el = document.createElement('textarea')
el.value = str
document.body.appendChild(el)
el.select()
document.execCommand('copy')
document.body.removeChild(el)
this.alertSuccess('Copied!')
}
},
computed: {
breadcrumbs() {
if (!this.template) {
return [{route: {name: 'templates'}, label: 'Templates'}]
}
return [{route: {name: 'templates'}, label: 'Templates'}, {label: this.template.name}]
},
template() {
return this.templatesStore.getBySlug(this.$route.params.slug)
},
form() {
return this.template ? new Form(this.template.structure) : null
},
canEditTemplate() {
return this.user && this.template && (this.user.admin || this.user.template_editor || this.template.creator_id === this.user.id)
},
metaTitle() {
return this.template ? this.template.name : 'Form Template'
},
metaDescription() {
if (!this.template) return null
// take the first 140 characters of the description
return this.template.short_description?.substring(0, 140) + '... | Customize any template and create your own form in minutes.'
},
metaImage() {
if (!this.template) return null
return this.template.image_url
},
metaTags() {
if (!this.template) {
return [];
}
return this.template.publicly_listed ? [] : [{name: 'robots', content: 'noindex'}]
},
createFormWithTemplateUrl() {
if (this.authenticated) {
return '/forms/create?template=' + this.template?.slug
}
return '/forms/create/guest?template=' + this.template?.slug
}
}
} }
// Fetch related templates
const {data: relatedTemplatesData} = await useAsyncData('related-templates', () => {
return Promise.all(template.value.related_templates.map((slug) => {
if (templatesStore.getByKey(slug)) {
return Promise.resolve(templatesStore.getByKey(slug))
}
return fetchTemplate(slug).then((res) => res.data.value)
}))
})
templatesStore.save(relatedTemplatesData.value)
// State
const showFormTemplateModal = ref(false)
// Computed
const breadcrumbs = computed(() => {
if (!template.value) {
return [{route: {name: 'templates'}, label: 'Templates'}]
}
return [{route: {name: 'templates'}, label: 'Templates'}, {label: template.name}]
})
const relatedTemplates = computed(() => templatesStore.getByKey(template?.value?.related_templates))
const canEditTemplate = computed(() => authStore.authenticated && template.value && (authStore.user.admin || authStore.user.template_editor || template.creator_id === authStore.user.id))
const createFormWithTemplateUrl = computed(() => {
if (authStore.authenticated) {
return '/forms/create?template=' + template?.value?.slug
}
return '/forms/create/guest?template=' + template?.value?.slug
})
// methods
const cleanQuotes = (str) => {
// Remove starting and ending quotes if any
return (str) ? str.replace(/^"/, '').replace(/"$/, '') : ''
}
const copyTemplateUrl = () => {
const str = template.value.share_url
const el = document.createElement('textarea')
el.value = str
document.body.appendChild(el)
el.select()
document.execCommand('copy')
document.body.removeChild(el)
this.alertSuccess('Copied!')
}
// metaTitle() {
// return this.template ? this.template.name : 'Form Template'
// },
// metaDescription() {
// if (!this.template) return null
// // take the first 140 characters of the description
// return this.template.short_description?.substring(0, 140) + '... | Customize any template and create your own form in minutes.'
// },
// metaImage() {
// if (!this.template) return null
// return this.template.image_url
// },
// metaTags() {
// if (!this.template) {
// return [];
// }
// return this.template.publicly_listed ? [] : [{name: 'robots', content: 'noindex'}]
// },
</script> </script>
<style lang='scss'> <style lang='scss'>

View File

@ -13,32 +13,28 @@
</div> </div>
</section> </section>
<templates-list :templates="templates" :loading="loading" /> <templates-list :templates="templates"/>
<open-form-footer class="mt-8 border-t"/> <open-form-footer class="mt-8 border-t"/>
</div> </div>
</template> </template>
<script> <script setup>
import SeoMeta from '../../mixins/seo-meta.js' import {fetchAllTemplates} from "~/stores/templates.js";
import {useTemplatesStore} from "~/stores/templates.js";
export default { // props: {
// metaTitle: { type: String, default: 'Templates' },
// metaDescription: { type: String, default: 'Our collection of beautiful templates to create your own forms!' }
// },
mixins: [SeoMeta], const templatesStore = useTemplatesStore()
props: { if (!templatesStore.allLoaded) {
metaTitle: { type: String, default: 'Templates' }, templatesStore.startLoading()
metaDescription: { type: String, default: 'Our collection of beautiful templates to create your own forms!' } const {data} = await fetchAllTemplates()
}, templatesStore.set(data.value)
templatesStore.allLoaded = true
setup() {
const templatesStore = useTemplatesStore()
templatesStore.loadAll()
return {
templates: templatesStore.content,
loading: templatesStore.loading
}
},
} }
const templates = computed(() => templatesStore.getAll)
</script> </script>

View File

@ -22,7 +22,7 @@ export const useAuthStore = defineStore('auth', {
stopImpersonating() { stopImpersonating() {
this.token = this.admin_token this.token = this.admin_token
this.admin_token = null this.admin_token = null
this.fetchUser() // TODO: re-fetch user
}, },
setToken(token) { setToken(token) {
@ -79,7 +79,7 @@ export const useAuthStore = defineStore('auth', {
async logout() { async logout() {
try { try {
await axios.post('/api/logout') await useOpnApi('logout', {method: 'POST'})
} catch (e) { } catch (e) {
} }

View File

@ -1,120 +1,53 @@
import {defineStore} from 'pinia' import {defineStore} from 'pinia'
import {useContentStore} from "~/composables/stores/useContentStore.js";
export const templatesEndpoint = '/templates' const templatesEndpoint = 'templates'
export const useTemplatesStore = defineStore('templates', { export const useTemplatesStore = defineStore('templates', () => {
state: () => ({
content: [], // TODO: convert to a map
industries: {},
types: {},
allLoaded: false,
loading: false
}),
getters: {
getBySlug: (state) => (slug) => {
if (state.content.length === 0) return null
return state.content.find(item => item.slug === slug)
},
getTemplateTypes: (state) => (slugs) => {
if (state.types.length === 0) return null
return Object.values(state.types).filter((val) => slugs.includes(val.slug)).map((item) => {
return item.name
})
},
getTemplateIndustries: (state) => (slugs) => {
if (state.industries.length === 0) return null
return Object.values(state.industries).filter((val) => slugs.includes(val.slug)).map((item) => {
return item.name
})
}
},
actions: {
set(items) {
this.content = items
this.allLoaded = true
},
append(items) {
const ids = items.map((item) => {
return item.id
})
this.content = this.content.filter((val) => !ids.includes(val.id))
this.content = this.content.concat(items)
},
addOrUpdate(item) {
this.content = this.content.filter((val) => val.id !== item.id)
this.content.push(item)
},
remove(item) {
this.content = this.content.filter((val) => val.id !== item.id)
},
startLoading() {
this.loading = true
},
stopLoading() {
this.loading = false
},
setAllLoaded(val) {
this.allLoaded = val
},
resetState() {
this.set([])
this.stopLoading()
},
async loadTypesAndIndustries() {
if (Object.keys(this.industries).length === 0 || Object.keys(this.types).length === 0) {
// const files = import.meta.glob('~/data/forms/templates/*.json')
// console.log(await files['/data/forms/templates/industries.json']())
// this.industries = await files['/data/forms/templates/industries.json']()
// this.types = await files['/data/forms/templates/types.json']()
}
},
loadTemplate(slug) {
console.log('loading template',slug)
this.startLoading()
this.loadTypesAndIndustries()
if (this.getBySlug(slug)) { const contentStore = useContentStore('slug')
this.stopLoading()
return Promise.resolve()
}
return useOpnApi(templatesEndpoint + '/' + slug).then(({data, error}) => { const allLoaded = ref(false)
this.addOrUpdate(data.value) const industries = ref({})
this.stopLoading() const types = ref({})
}).catch((error) => {
this.stopLoading()
})
},
loadAll(options = null) {
this.startLoading()
this.loadTypesAndIndustries()
// Prepare with options const getTemplateTypes = computed((state) => (slugs) => {
let queryStr = '' if (state.types.length === 0) return null
if (options !== null) { return Object.values(state.types).filter((val) => slugs.includes(val.slug)).map((item) => {
for (const [key, value] of Object.entries(options)) { return item.name
queryStr += '&' + encodeURIComponent(key) + '=' + encodeURIComponent(value) })
} // todo: use map
queryStr = queryStr.slice(1) })
} const getTemplateIndustries = computed((state) => (slugs) => {
return useOpnApi((queryStr) ? templatesEndpoint + '?' + queryStr : templatesEndpoint).then(({data, error}) => { if (state.industries.length === 0) return null
if (options !== null) { return Object.values(state.industries).filter((val) => slugs.includes(val.slug)).map((item) => {
this.set(data.value) return item.name
this.setAllLoaded(false) })
} else { })
this.append(data.value)
this.setAllLoaded(true) const loadTypesAndIndustries = function() {
} if (Object.keys(this.industries).length === 0 || Object.keys(this.types).length === 0) {
this.stopLoading() // const files = import.meta.glob('~/data/forms/templates/*.json')
}).catch((error) => { // console.log(await files['/data/forms/templates/industries.json']())
this.stopLoading() // this.industries = await files['/data/forms/templates/industries.json']()
}) // this.types = await files['/data/forms/templates/types.json']()
},
loadIfEmpty() {
if (!this.allLoaded) {
return this.loadAll()
}
this.stopLoading()
return Promise.resolve()
} }
} }
return {
...contentStore,
industries,
types,
getTemplateTypes,
getTemplateIndustries,
loadTypesAndIndustries,
}
}) })
export const fetchTemplate = (slug) => {
return useOpnApi(templatesEndpoint + '/' + slug)
}
export const fetchAllTemplates = () => {
return useOpnApi(templatesEndpoint)
}

View File

@ -7,7 +7,7 @@ const storedWorkspaceId = useStorage('currentWorkspace', 0)
export const useWorkspacesStore = defineStore('workspaces', { export const useWorkspacesStore = defineStore('workspaces', {
state: () => ({ state: () => ({
content: [], content: new Map,
currentId: null, currentId: null,
loading: false loading: false
}), }),
@ -70,6 +70,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
load() { load() {
this.set([]) this.set([])
this.startLoading() this.startLoading()
console.log('loaindgworkspaces')
return useOpnApi(workspaceEndpoint).then(({data, error}) => { return useOpnApi(workspaceEndpoint).then(({data, error}) => {
this.set(data.value) this.set(data.value)
this.stopLoading() this.stopLoading()