Working on home page and modal

This commit is contained in:
Julien Nahum 2023-12-19 19:42:02 +01:00
parent 5640f43b9d
commit 933f95e944
7 changed files with 183 additions and 222 deletions

View File

@ -49,13 +49,11 @@
</Teleport> </Teleport>
</template> </template>
<script> <script setup>
import {useMotions} from '@vueuse/motion' import {useMotions} from '@vueuse/motion'
import {watch} from "vue";
export default { const props = defineProps({
name: 'Modal',
props: {
show: { show: {
default: false default: false
}, },
@ -72,49 +70,47 @@ export default {
closeable: { closeable: {
default: true default: true
} }
}, })
setup(props) { const emits = defineEmits(['close'])
useHead({
useHead({
bodyAttrs: { bodyAttrs: {
class: { class: {
'overflow-hidden': props.show 'overflow-hidden': props.show
} }
} }
}) })
const closeOnEscape = (e) => { const closeOnEscape = (e) => {
if (e.key === 'Escape' && this.show) { if (e.key === 'Escape' && this.show) {
this.close() this.close()
} }
} }
onMounted(() => { onMounted(() => {
if (process.server) return if (process.server) return
document.addEventListener('keydown', closeOnEscape) document.addEventListener('keydown', closeOnEscape)
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (process.server) return if (process.server) return
document.removeEventListener('keydown', closeOnEscape) document.removeEventListener('keydown', closeOnEscape)
}) })
return { const motions = useMotions()
motions: useMotions(),
}
},
computed: { const maxWidthClass = computed(() => {
maxWidthClass() {
return { return {
sm: 'sm:max-w-sm', sm: 'sm:max-w-sm',
md: 'sm:max-w-md', md: 'sm:max-w-md',
lg: 'sm:max-w-lg', lg: 'sm:max-w-lg',
xl: 'sm:max-w-xl', xl: 'sm:max-w-xl',
'2xl': 'sm:max-w-2xl' '2xl': 'sm:max-w-2xl'
}[this.maxWidth] }[props.maxWidth]
}, })
motionFadeIn() {
const motionFadeIn = computed(() => {
return { return {
initial: { initial: {
opacity: 0, opacity: 0,
@ -131,8 +127,9 @@ export default {
} }
} }
} }
}, })
motionSlideBottom() {
const motionSlideBottom = computed(() => {
return { return {
initial: { initial: {
y: 150, y: 150,
@ -152,26 +149,23 @@ export default {
} }
} }
} }
} })
},
watch: { watch(() => props.show, (newVal, oldVal) => {
show(newVal, oldVal) {
if (newVal !== oldVal) { if (newVal !== oldVal) {
if (!newVal) { if (newVal) {
this.motions.body.apply('initial') motions.body.apply('enter')
this.motions.backdrop.apply('initial') motions.backdrop.apply('enter')
} else {
motions.body.apply('initial')
motions.backdrop.apply('initial')
} }
} }
} })
},
methods: { const close = () => {
close() { if (props.closeable) {
if (this.closeable) { emits('close')
this.$emit('close')
}
}
} }
} }
</script> </script>

View File

@ -161,7 +161,7 @@ export default {
showFormErrorModal: false, showFormErrorModal: false,
validationErrorResponse: null, validationErrorResponse: null,
updateFormLoading: false, updateFormLoading: false,
createdFormId: null createdFormSlug: null
} }
}, },
@ -176,7 +176,7 @@ export default {
} }
}, },
createdForm () { createdForm () {
return this.formsStore.getById(this.createdFormId) return this.formsStore.getBySlug(this.createdFormSlug)
}, },
workspace () { workspace () {
return this.workspacesStore.getCurrent() return this.workspacesStore.getCurrent()
@ -278,7 +278,7 @@ export default {
this.form.post('/api/open/forms').then((response) => { this.form.post('/api/open/forms').then((response) => {
this.formsStore.addOrUpdate(response.data.form) this.formsStore.addOrUpdate(response.data.form)
this.$emit('on-save') this.$emit('on-save')
this.createdFormId = response.data.form.id this.createdFormSlug = response.data.form.slug
this.$logEvent('form_created', { form_id: response.data.form.id, form_slug: response.data.form.slug }) this.$logEvent('form_created', { form_id: response.data.form.id, form_slug: response.data.form.slug })
this.$crisp.push(['set', 'session:event', [[['form_created', { this.$crisp.push(['set', 'session:event', [[['form_created', {

View File

@ -47,9 +47,9 @@
<h3 class="w-full mt-4 text-center text-gray-900 font-semibold"> <h3 class="w-full mt-4 text-center text-gray-900 font-semibold">
No forms found No forms found
</h3> </h3>
<div v-if="isFilteringForms && enrichedForms.length === 0 && searchForm.search" <div v-if="isFilteringForms && enrichedForms.length === 0 && search"
class="mt-2 w-full text-center"> class="mt-2 w-full text-center">
Your search "{{ searchForm.search }}" did not match any forms. Please try again. Your search "{{ search }}" did not match any forms. Please try again.
</div> </div>
<v-button v-if="forms.length === 0" v-track.create_form_click class="mt-4" :to="{name:'forms-create'}"> <v-button v-if="forms.length === 0" v-track.create_form_click class="mt-4" :to="{name:'forms-create'}">
<svg class="w-4 h-4 text-white inline mr-1 -mt-1" viewBox="0 0 14 14" fill="none" <svg class="w-4 h-4 text-white inline mr-1 -mt-1" viewBox="0 0 14 14" fill="none"
@ -112,23 +112,18 @@
</template> </template>
<script setup> <script setup>
import {useAuthStore} from '../stores/auth'; import {useAuthStore} from '../stores/auth'
import {useFormsStore} from '../stores/forms'; import {useFormsStore} from '../stores/forms'
import {useWorkspacesStore} from '../stores/workspaces'; import {useWorkspacesStore} from '../stores/workspaces'
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import TextInput from '../components/forms/TextInput.vue' import TextInput from '../components/forms/TextInput.vue'
import OpenFormFooter from '../components/pages/OpenFormFooter.vue' import OpenFormFooter from '../components/pages/OpenFormFooter.vue'
import ExtraMenu from '../components/pages/forms/show/ExtraMenu.vue' import ExtraMenu from '../components/pages/forms/show/ExtraMenu.vue'
import {refDebounced} from "@vueuse/core"; import {refDebounced} from "@vueuse/core"
const loadForms = function () { definePageMeta({
const formsStore = useFormsStore() middleware: "auth"
const workspacesStore = useWorkspacesStore() })
formsStore.startLoading()
workspacesStore.loadIfEmpty().then(() => {
formsStore.loadIfEmpty(workspacesStore.currentId)
})
}
// metaTitle: {type: String, default: 'Your Forms'}, // metaTitle: {type: String, default: 'Your Forms'},
// metaDescription: { // metaDescription: {
@ -140,16 +135,16 @@ const authStore = useAuthStore()
const formsStore = useFormsStore() const formsStore = useFormsStore()
const workspacesStore = useWorkspacesStore() const workspacesStore = useWorkspacesStore()
definePageMeta({ onMounted(() => {
middleware: "auth" formsStore.load(workspacesStore.currentId)
}) })
// State // State
const {content: forms, loading: formsLoading} = storeToRefs(formsStore) const {getAll: forms, loading: formsLoading} = storeToRefs(formsStore)
const showEditFormModal = ref(false) const showEditFormModal = ref(false)
const selectedForm = ref(null) const selectedForm = ref(null)
const search = ref('') const search = ref('')
const debounceSearch = refDebounced(search, 500) const debouncedSearch = refDebounced(search, 500)
const selectedTags = ref(new Set()) const selectedTags = ref(new Set())
// Methods // Methods
@ -173,13 +168,24 @@ const isFilteringForms = computed(() => {
return (search.value !== '' && search.value !== null) || selectedTags.value.size > 0 return (search.value !== '' && search.value !== null) || selectedTags.value.size > 0
}) })
const allTags = computed(() => { const allTags = computed(() => {
return formsStore.getAllTags let tags = []
forms.value.forEach((form) => {
console.log(form.tags)
// TODO: check this works
if (form.tags && form.tags.length) {
tags = tags.concat(form.tags.split(','))
}
})
return [...new Set(tags)]
}) })
const enrichedForms = computed(() => { const enrichedForms = computed(() => {
let enrichedForms = forms.value.map((form) => { let enrichedForms = forms.value.map((form) => {
form.workspace = workspacesStore.getByKey(form.workspace_id) form.workspace = workspacesStore.getByKey(form.workspace_id)
return form return form
}).filter((form) => { }).filter((form) => {
if (selectedTags.value.size === 0) {
return true
}
return form.tags && form.tags.length ? [...selectedTags].every(r => form.tags.includes(r)) : false return form.tags && form.tags.length ? [...selectedTags].every(r => form.tags.includes(r)) : false
}) })
@ -196,7 +202,7 @@ const enrichedForms = computed(() => {
] ]
} }
const fuse = new Fuse(enrichedForms, fuzeOptions) const fuse = new Fuse(enrichedForms, fuzeOptions)
return fuse.search(search.value).map((res) => { return fuse.search(debouncedSearch.value).map((res) => {
return res.item return res.item
}) })
}) })

100
client/stores/forms.js vendored
View File

@ -1,82 +1,36 @@
import {defineStore} from 'pinia' import {defineStore} from 'pinia'
import {useContentStore} from "~/composables/stores/useContentStore.js";
export const formsEndpoint = '/open/workspaces/{workspaceId}/forms' export const formsEndpoint = '/open/workspaces/{workspaceId}/forms'
export let currentPage = 1
export const useFormsStore = defineStore('forms', { export const useFormsStore = defineStore('forms', () => {
state: () => ({
content: [], const contentStore = useContentStore('slug')
loading: false contentStore.startLoading()
}), const currentPage = ref(1)
getters: {
getById: (state) => (id) => { const load = (workspaceId) => {
if (state.content.length === 0) return null contentStore.startLoading()
return state.content.find(item => item.id === id) return opnFetch(formsEndpoint.replace('{workspaceId}', workspaceId),{query: {page: currentPage.value}})
}, .then((response) => {
getBySlug: (state) => (slug) => { if (currentPage.value === 1) {
if (state.content.length === 0) return null contentStore.resetState()
return state.content.find(item => item.slug === slug) contentStore.save(response.data)
}, } else {
getAllTags: (state) => { contentStore.save(response.data)
if (state.content.length === 0) return [] }
let allTags = [] if (currentPage.value < response.meta.last_page) {
state.content.forEach(form => { currentPage.value++
if (form.tags && form.tags.length > 0) { load(workspaceId)
allTags = allTags.concat(form.tags) } else {
contentStore.stopLoading()
currentPage.value = 1
} }
}) })
return allTags.filter((item, i, ar) => ar.indexOf(item) === i)
}
},
actions: {
set(items) {
this.content = items
},
append(items) {
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
},
resetState() {
this.set([])
this.stopLoading()
currentPage = 1
},
load(workspaceId) {
this.startLoading()
return useOpnApi(formsEndpoint.replace('{workspaceId}', workspaceId) + '?page=' + currentPage)
.then(({data, error}) => {
if (currentPage === 1) {
this.set(data.value.data)
} else {
this.append(data.value.data)
}
if (currentPage < data.value.meta.last_page) {
currentPage += 1
this.load(workspaceId)
} else {
this.stopLoading()
currentPage = 1
}
})
},
loadIfEmpty(workspaceId) {
if (this.content.length === 0) {
return this.load(workspaceId)
}
this.stopLoading()
return Promise.resolve()
} }
return {
...contentStore,
load
} }
}) })

View File

@ -52,7 +52,6 @@ export const fetchAllTemplates = (options = {}) => {
} }
export const loadAllTemplates = async (store, options={}) => { export const loadAllTemplates = async (store, options={}) => {
console.log('in------',store, store.allLoaded)
if (!store.allLoaded) { if (!store.allLoaded) {
store.startLoading() store.startLoading()
store.initTypesAndIndustries() store.initTypesAndIndustries()

View File

@ -22,7 +22,6 @@ export const useWorkspacesStore = defineStore('workspaces', () => {
const save = (items) => { const save = (items) => {
contentStore.save(items) contentStore.save(items)
console.log('cookie issi', currentId.value, contentStore.length.value)
if (currentId.value == null && contentStore.length.value) { if (currentId.value == null && contentStore.length.value) {
setCurrentId(items[0].id) setCurrentId(items[0].id)
} }

View File

@ -24,7 +24,8 @@
</svg> </svg>
</v-button> </v-button>
</template> </template>
<a v-if="isMainPage && user" v-track.view_form_click="{form_id:form.id, form_slug:form.slug}" :href="form.share_url" <a v-if="isMainPage && user" v-track.view_form_click="{form_id:form.id, form_slug:form.slug}"
:href="form.share_url"
target="_blank" target="_blank"
class="block px-4 py-2 text-md text-gray-700 dark:text-white 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 px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
> >
@ -45,7 +46,9 @@
:to="{name:'forms.edit', params: {slug: form.slug}}" :to="{name:'forms.edit', params: {slug: form.slug}}"
class="block block px-4 py-2 text-md text-gray-700 dark:text-white 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 dark:text-white 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" width="18" height="17" viewBox="0 0 18 17" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg class="w-4 h-4 mr-2" width="18" height="17" viewBox="0 0 18 17" fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path <path
d="M8.99998 15.6662H16.5M1.5 15.6662H2.89545C3.3031 15.6662 3.50693 15.6662 3.69874 15.6202C3.8688 15.5793 4.03138 15.512 4.1805 15.4206C4.34869 15.3175 4.49282 15.1734 4.78107 14.8852L15.25 4.4162C15.9404 3.72585 15.9404 2.60656 15.25 1.9162C14.5597 1.22585 13.4404 1.22585 12.75 1.9162L2.28105 12.3852C1.9928 12.6734 1.84867 12.8175 1.7456 12.9857C1.65422 13.1348 1.58688 13.2974 1.54605 13.4675C1.5 13.6593 1.5 13.8631 1.5 14.2708V15.6662Z" d="M8.99998 15.6662H16.5M1.5 15.6662H2.89545C3.3031 15.6662 3.50693 15.6662 3.69874 15.6202C3.8688 15.5793 4.03138 15.512 4.1805 15.4206C4.34869 15.3175 4.49282 15.1734 4.78107 14.8852L15.25 4.4162C15.9404 3.72585 15.9404 2.60656 15.25 1.9162C14.5597 1.22585 13.4404 1.22585 12.75 1.9162L2.28105 12.3852C1.9928 12.6734 1.84867 12.8175 1.7456 12.9857C1.65422 13.1348 1.58688 13.2974 1.54605 13.4675C1.5 13.6593 1.5 13.8631 1.5 14.2708V15.6662Z"
stroke="currentColor" stroke-width="1.67" stroke-linecap="round" stroke-linejoin="round" stroke="currentColor" stroke-width="1.67" stroke-linecap="round" stroke-linejoin="round"
@ -58,7 +61,10 @@
@click.prevent="copyLink" @click.prevent="copyLink"
> >
<svg class="w-4 h-4 mr-2" viewBox="0 0 16 10" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg class="w-4 h-4 mr-2" viewBox="0 0 16 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.00016 8.33317H4.66683C2.82588 8.33317 1.3335 6.84079 1.3335 4.99984C1.3335 3.15889 2.82588 1.6665 4.66683 1.6665H6.00016M10.0002 8.33317H11.3335C13.1744 8.33317 14.6668 6.84079 14.6668 4.99984C14.6668 3.15889 13.1744 1.6665 11.3335 1.6665H10.0002M4.66683 4.99984L11.3335 4.99984" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <path
d="M6.00016 8.33317H4.66683C2.82588 8.33317 1.3335 6.84079 1.3335 4.99984C1.3335 3.15889 2.82588 1.6665 4.66683 1.6665H6.00016M10.0002 8.33317H11.3335C13.1744 8.33317 14.6668 6.84079 14.6668 4.99984C14.6668 3.15889 13.1744 1.6665 11.3335 1.6665H10.0002M4.66683 4.99984L11.3335 4.99984"
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"
/>
</svg> </svg>
Copy link to share Copy link to share
</a> </a>
@ -134,7 +140,9 @@
</div> </div>
</modal> </modal>
<form-template-modal v-if="!isMainPage && user" :form="form" :show="showFormTemplateModal" @close="showFormTemplateModal=false" /> <form-template-modal v-if="!isMainPage && user" :form="form" :show="showFormTemplateModal"
@close="showFormTemplateModal=false"
/>
</div> </div>
</template> </template>
@ -159,6 +167,7 @@ export default {
const formsStore = useFormsStore() const formsStore = useFormsStore()
return { return {
formsStore, formsStore,
router: useRouter(),
user: computed(() => authStore.user) user: computed(() => authStore.user)
} }
}, },
@ -187,9 +196,9 @@ export default {
duplicateForm () { duplicateForm () {
if (this.loadingDuplicate) return if (this.loadingDuplicate) return
this.loadingDuplicate = true this.loadingDuplicate = true
axios.post(this.formEndpoint.replace('{id}', this.form.id) + '/duplicate').then((response) => { opnFetch(this.formEndpoint.replace('{id}', this.form.id) + '/duplicate', { method: 'POST' }).then((response) => {
this.formsStore.addOrUpdate(response.data.new_form) this.formsStore.save(response.data.new_form)
this.$router.push({ name: 'forms.show', params: { slug: response.data.new_form.slug } }) this.router.push({ name: 'forms.show', params: { slug: response.data.new_form.slug } })
this.alertSuccess('Form was successfully duplicated.') this.alertSuccess('Form was successfully duplicated.')
this.loadingDuplicate = false this.loadingDuplicate = false
}) })
@ -198,8 +207,8 @@ export default {
if (this.loadingDelete) return if (this.loadingDelete) return
this.loadingDelete = true this.loadingDelete = true
axios.delete(this.formEndpoint.replace('{id}', this.form.id)).then(() => { axios.delete(this.formEndpoint.replace('{id}', this.form.id)).then(() => {
this.formsStore.remove(this.form) this.formsStore.remove(this.form.id)
this.$router.push({ name: 'home' }) this.router.push({ name: 'home' })
this.alertSuccess('Form was deleted.') this.alertSuccess('Form was deleted.')
this.loadingDelete = false this.loadingDelete = false
}) })