Merge branch 'main' into vue-3
This commit is contained in:
@@ -43,25 +43,17 @@
|
||||
Log In
|
||||
</router-link>
|
||||
</p>
|
||||
|
||||
<!-- GitHub Register Button -->
|
||||
<login-with-github />
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Form from 'vform'
|
||||
import LoginWithGithub from '~/components/LoginWithGithub.vue'
|
||||
import SelectInput from '../../../components/forms/SelectInput.vue'
|
||||
import { initCrisp } from '../../../middleware/check-auth.js'
|
||||
|
||||
export default {
|
||||
name: 'RegisterForm',
|
||||
components: {
|
||||
SelectInput,
|
||||
LoginWithGithub
|
||||
},
|
||||
components: {},
|
||||
props: {
|
||||
isQuick: {
|
||||
type: Boolean,
|
||||
@@ -76,7 +68,8 @@ export default {
|
||||
email: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
agree_terms: false
|
||||
agree_terms: false,
|
||||
appsumo_license: null
|
||||
}),
|
||||
mustVerifyEmail: false
|
||||
}),
|
||||
@@ -99,6 +92,13 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
// Set appsumo license
|
||||
if (this.$route.query.appsumo_license !== undefined && this.$route.query.appsumo_license) {
|
||||
this.form.appsumo_license = this.$route.query.appsumo_license
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async register () {
|
||||
// Register the user.
|
||||
@@ -123,6 +123,15 @@ export default {
|
||||
initCrisp(data)
|
||||
this.$crisp.push(['set', 'session:event', [[['register', {}, 'blue']]]])
|
||||
|
||||
// AppSumo License
|
||||
if (data.appsumo_license === false) {
|
||||
this.alertError('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' +
|
||||
' features of the AppSumo deal.')
|
||||
}
|
||||
|
||||
// Redirect
|
||||
if (this.isQuick) {
|
||||
this.$emit('afterQuickLogin')
|
||||
|
||||
@@ -3,16 +3,17 @@
|
||||
<div class="flex mt-6 mb-10">
|
||||
<div class="w-full md:max-w-6xl mx-auto px-4 flex items-center md:flex-row-reverse flex-wrap">
|
||||
<div class="w-full lg:w-1/2 md:p-6">
|
||||
<app-sumo-register class="mb-10 p-6 lg:hidden" />
|
||||
<div class="border rounded-md p-6 shadow-md sticky top-4">
|
||||
<h2 class="font-semibold text-2xl">
|
||||
Create an account
|
||||
</h2>
|
||||
<small>Sign up in less than 2 minutes.</small>
|
||||
|
||||
<register-form />
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full hidden lg:block lg:w-1/2 md:p-6 mt-8 md:mt-0 ">
|
||||
<app-sumo-register class="mb-10" />
|
||||
<h1 class="font-bold">
|
||||
Create beautiful forms and share them anywhere
|
||||
</h1>
|
||||
@@ -22,7 +23,7 @@
|
||||
<div class="flex flex-wrap justify-center">
|
||||
<p class="px-3 pb-3 text-sm text-gray-500">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 inline" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor" stroke-width="2"
|
||||
stroke="currentColor" stroke-width="2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
@@ -30,7 +31,7 @@
|
||||
</p>
|
||||
<p class="px-3 pb-3 text-sm text-gray-500">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 inline" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor" stroke-width="2"
|
||||
stroke="currentColor" stroke-width="2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
@@ -38,16 +39,16 @@
|
||||
</p>
|
||||
<p class="px-3 pb-3 text-sm text-gray-500">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 inline" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor" stroke-width="2"
|
||||
stroke="currentColor" stroke-width="2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
Unlimited submissions
|
||||
</p>
|
||||
</div>
|
||||
<!-- <div class="mt-3 p-6">-->
|
||||
<!-- <testimonials />-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="mt-3 p-6">-->
|
||||
<!-- <testimonials />-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -57,29 +58,27 @@
|
||||
|
||||
<script>
|
||||
import OpenFormFooter from '../../components/pages/OpenFormFooter.vue'
|
||||
import Testimonials from '../../components/pages/welcome/Testimonials.vue'
|
||||
import RegisterForm from './components/RegisterForm.vue'
|
||||
import SeoMeta from '../../mixins/seo-meta.js'
|
||||
import AppSumoRegister from '../../components/vendor/appsumo/AppSumoRegister.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Testimonials,
|
||||
AppSumoRegister,
|
||||
OpenFormFooter,
|
||||
RegisterForm
|
||||
},
|
||||
|
||||
middleware: 'guest',
|
||||
|
||||
mixins: [SeoMeta],
|
||||
|
||||
middleware: 'guest',
|
||||
|
||||
data: () => ({
|
||||
metaTitle: 'Register',
|
||||
metaTitle: 'Register'
|
||||
}),
|
||||
|
||||
computed: {
|
||||
},
|
||||
computed: {},
|
||||
|
||||
methods: {
|
||||
}
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -49,14 +49,17 @@
|
||||
<span class="pr-1">- {{ form.submissions_count }}
|
||||
submission{{ form.submissions_count > 0 ? 's' : '' }}
|
||||
</span>
|
||||
<span class="pr-1 text-blue-500" v-if="form.visibility=='closed'">- Closed</span>
|
||||
<span class="">- Edited {{ form.last_edited_human }}</span>
|
||||
<span>- Edited {{ form.last_edited_human }}</span>
|
||||
</p>
|
||||
<div v-if="form.visibility=='draft' || (form.tags && form.tags.length > 0)" class="mt-2 flex items-center flex-wrap gap-3">
|
||||
<div v-if="['draft','closed'].includes(form.visibility) || (form.tags && form.tags.length > 0)" class="mt-2 flex items-center flex-wrap gap-3">
|
||||
<span v-if="form.visibility=='draft'"
|
||||
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700">
|
||||
Draft - not publicly accessible
|
||||
</span>
|
||||
<span v-else-if="form.visibility=='closed'"
|
||||
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700">
|
||||
Closed - won't accept new submissions
|
||||
</span>
|
||||
<span v-for="(tag,i) in form.tags" :key="tag"
|
||||
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 dark:text-white dark:bg-gray-700"
|
||||
>
|
||||
|
||||
@@ -73,12 +73,15 @@
|
||||
Edited {{ form.last_edited_human }}
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="form.visibility=='draft' || (form.tags && form.tags.length > 0)" class="mt-1 flex items-center flex-wrap gap-3">
|
||||
<span v-if="form.visibility=='draft'"
|
||||
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700"
|
||||
>
|
||||
<div v-if="['draft','closed'].includes(form.visibility) || (form.tags && form.tags.length > 0)" class="mt-1 flex items-center flex-wrap gap-3">
|
||||
<span v-if="form.visibility=='draft'"
|
||||
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700">
|
||||
Draft
|
||||
</span>
|
||||
<span v-else-if="form.visibility=='closed'"
|
||||
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700">
|
||||
Closed
|
||||
</span>
|
||||
<span v-for="(tag,i) in form.tags" :key="tag"
|
||||
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 dark:text-white dark:bg-gray-700"
|
||||
>
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<h3 class="font-semibold text-2xl text-gray-900">Billing details</h3>
|
||||
<small class="text-gray-600">Manage your billing. Download invoices, update your plan, or cancel it at any
|
||||
time.</small>
|
||||
<h3 class="font-semibold text-2xl text-gray-900">
|
||||
Billing details
|
||||
</h3>
|
||||
|
||||
<div class="mt-4">
|
||||
<v-button color="gray" shade="light" :loading="billingLoading" @click.prevent="openBillingDashboard">
|
||||
Manage Subscription
|
||||
</v-button>
|
||||
</div>
|
||||
<template v-if="user.has_customer_id">
|
||||
<small class="text-gray-600">Manage your billing. Download invoices, update your plan, or cancel it at any
|
||||
time.</small>
|
||||
|
||||
<div class="mt-4">
|
||||
<v-button color="gray" shade="light" :loading="billingLoading" @click.prevent="openBillingDashboard">
|
||||
Manage Subscription
|
||||
</v-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<app-sumo-billing class="mt-4" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -16,11 +23,13 @@
|
||||
import axios from 'axios'
|
||||
import VButton from '../../components/common/Button.vue'
|
||||
import SeoMeta from '../../mixins/seo-meta.js'
|
||||
import { mapGetters } from 'vuex'
|
||||
import AppSumoBilling from '../../components/vendor/appsumo/AppSumoBilling.vue'
|
||||
|
||||
export default {
|
||||
components: {VButton},
|
||||
scrollToTop: false,
|
||||
components: { AppSumoBilling, VButton },
|
||||
mixins: [SeoMeta],
|
||||
scrollToTop: false,
|
||||
|
||||
data: () => ({
|
||||
metaTitle: 'Billing',
|
||||
@@ -28,7 +37,7 @@ export default {
|
||||
}),
|
||||
|
||||
methods: {
|
||||
openBillingDashboard() {
|
||||
openBillingDashboard () {
|
||||
this.billingLoading = true
|
||||
axios.get('/api/subscription/billing-portal').then((response) => {
|
||||
const url = response.data.portal_url
|
||||
@@ -39,6 +48,12 @@ export default {
|
||||
this.billingLoading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
user: 'auth/user'
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
232
resources/js/pages/templates/industries-show.vue
Normal file
232
resources/js/pages/templates/industries-show.vue
Normal file
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<div class="flex flex-col min-h-full">
|
||||
<breadcrumb :path="breadcrumbs" />
|
||||
|
||||
<div v-if="templatesLoading" class="text-center my-4">
|
||||
<loader class="h-6 w-6 text-nt-blue mx-auto" />
|
||||
</div>
|
||||
<p v-else-if="industry === null || !industry" class="text-center my-4">
|
||||
We could not find this industry.
|
||||
</p>
|
||||
<template v-else>
|
||||
<section class="py-12 sm:py-16 bg-gray-50 border-b border-gray-200">
|
||||
<div class="px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto">
|
||||
<div class="text-center mx-auto">
|
||||
<div class="font-semibold sm:w-full text-blue-500 mb-3">
|
||||
{{ industry.name }}
|
||||
</div>
|
||||
<h1 class="text-3xl sm:text-4xl lg:text-5xl font-bold tracking-tight text-gray-900">
|
||||
{{ industry.meta_title }}
|
||||
</h1>
|
||||
<p class="max-w-xl mx-auto text-gray-600 mt-4 text-lg font-normal">
|
||||
{{ industry.meta_description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bg-white py-12 sm:py-16">
|
||||
<div class="px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 sm:gap-6 relative z-20">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex-1 sm:flex-none">
|
||||
<select-input v-model="selectedType" name="type"
|
||||
:options="typesOptions" class="w-full sm:w-auto md:w-56"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 w-full md:max-w-xs">
|
||||
<text-input name="search" :form="searchTemplate" placeholder="Search..." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="templatesLoading" class="text-center mt-4">
|
||||
<loader class="h-6 w-6 text-nt-blue mx-auto" />
|
||||
</div>
|
||||
<p v-else-if="enrichedTemplates.length === 0" class="text-center mt-4">
|
||||
No templates found.
|
||||
</p>
|
||||
<div v-else class="relative z-10">
|
||||
<div class="grid grid-cols-1 mt-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8 sm:gap-y-12">
|
||||
<single-template v-for="template in enrichedTemplates" :key="template.id" :slug="template.slug" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section 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">
|
||||
<p class="text-gray-600 font-normal">
|
||||
{{ industry.description }}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section 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="flex items-center justify-between">
|
||||
<h4 class="text-xl font-bold tracking-tight text-gray-900 sm:text-2xl">
|
||||
Other Industries
|
||||
</h4>
|
||||
|
||||
<v-button :to="{name:'templates'}" color="white" size="small" :arrow="true">
|
||||
View All Templates
|
||||
</v-button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-8 mt-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
<router-link v-for="row in otherIndustries" :key="row.slug"
|
||||
:to="{params:{slug:row.slug}, name:'templates.industries.show'}"
|
||||
:title="row.name"
|
||||
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
|
||||
>
|
||||
{{ row.name }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</template>
|
||||
|
||||
<open-form-footer class="mt-8 border-t"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from '~/store'
|
||||
import Form from 'vform'
|
||||
import Fuse from 'fuse.js'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import SeoMeta from '../../mixins/seo-meta.js'
|
||||
import OpenFormFooter from '../../components/pages/OpenFormFooter.vue'
|
||||
import Breadcrumb from '../../components/common/Breadcrumb.vue'
|
||||
import SingleTemplate from '../../components/pages/templates/SingleTemplate.vue'
|
||||
|
||||
const loadTemplates = function () {
|
||||
store.commit('open/templates/startLoading')
|
||||
store.dispatch('open/templates/loadIfEmpty').then(() => {
|
||||
store.commit('open/templates/stopLoading')
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
components: { Breadcrumb, OpenFormFooter, SingleTemplate },
|
||||
mixins: [SeoMeta],
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
loadTemplates()
|
||||
next()
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
selectedType: 'all',
|
||||
searchTemplate: new Form({
|
||||
search: ''
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
authenticated: 'auth/check',
|
||||
user: 'auth/user'
|
||||
}),
|
||||
...mapState({
|
||||
templates: state => state['open/templates'].content,
|
||||
templatesLoading: state => state['open/templates'].loading,
|
||||
industries: state => state['open/templates'].industries,
|
||||
types: state => state['open/templates'].types
|
||||
}),
|
||||
breadcrumbs () {
|
||||
if (!this.industry) {
|
||||
return [{ route: { name: 'templates' }, label: 'Templates' }]
|
||||
}
|
||||
return [{ route: { name: 'templates' }, label: 'Templates' }, { label: this.industry.name }]
|
||||
},
|
||||
industry () {
|
||||
return Object.values(this.industries).find((industry) => {
|
||||
return industry.slug === this.$route.params.slug
|
||||
})
|
||||
},
|
||||
typesOptions () {
|
||||
return [{ name: 'All Types', value: 'all' }].concat(Object.values(this.types).map((type) => {
|
||||
return {
|
||||
name: type.name,
|
||||
value: type.slug
|
||||
}
|
||||
}))
|
||||
},
|
||||
otherIndustries() {
|
||||
return Object.values(this.industries).filter((industry) => {
|
||||
return industry.slug !== this.$route.params.slug
|
||||
})
|
||||
},
|
||||
enrichedTemplates () {
|
||||
let enrichedTemplates = this.templates
|
||||
|
||||
// Filter by current Industry only
|
||||
enrichedTemplates = enrichedTemplates.filter((item) => {
|
||||
return (item.industries && item.industries.length > 0) ? item.industries.includes(this.$route.params.slug) : false
|
||||
})
|
||||
|
||||
// Filter by Selected Type
|
||||
if (this.selectedType && this.selectedType !== 'all') {
|
||||
enrichedTemplates = enrichedTemplates.filter((item) => {
|
||||
return (item.types && item.types.length > 0) ? item.types.includes(this.selectedType) : false
|
||||
})
|
||||
}
|
||||
|
||||
if (this.searchTemplate.search === '' || this.searchTemplate.search === null) {
|
||||
return enrichedTemplates
|
||||
}
|
||||
|
||||
// Fuze search
|
||||
const fuzeOptions = {
|
||||
keys: [
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
'short_description'
|
||||
]
|
||||
}
|
||||
const fuse = new Fuse(enrichedTemplates, fuzeOptions)
|
||||
return fuse.search(this.searchTemplate.search).map((res) => {
|
||||
return res.item
|
||||
})
|
||||
},
|
||||
metaTitle () {
|
||||
return this.industry ? this.industry.meta_title : 'Form Template Industry'
|
||||
},
|
||||
metaDescription () {
|
||||
if (!this.industry) return null
|
||||
return this.industry.meta_description.substring(0, 140)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
.nf-text {
|
||||
@apply space-y-4;
|
||||
h2 {
|
||||
@apply text-sm font-normal tracking-widest text-gray-500 uppercase;
|
||||
}
|
||||
|
||||
p {
|
||||
@apply font-normal leading-7 text-gray-900 dark:text-gray-100;
|
||||
}
|
||||
|
||||
ol {
|
||||
@apply list-decimal list-inside;
|
||||
}
|
||||
|
||||
ul {
|
||||
@apply list-disc list-inside;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
233
resources/js/pages/templates/types-show.vue
Normal file
233
resources/js/pages/templates/types-show.vue
Normal file
@@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<div class="flex flex-col min-h-full">
|
||||
<breadcrumb :path="breadcrumbs" />
|
||||
|
||||
<div v-if="templatesLoading" class="text-center my-4">
|
||||
<loader class="h-6 w-6 text-nt-blue mx-auto" />
|
||||
</div>
|
||||
<p v-else-if="type === null || !type" class="text-center my-4">
|
||||
We could not find this type.
|
||||
</p>
|
||||
<template v-else>
|
||||
<section class="py-12 sm:py-16 bg-gray-50 border-b border-gray-200">
|
||||
<div class="px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto">
|
||||
<div class="text-center mx-auto">
|
||||
<div class="font-semibold sm:w-full text-blue-500 mb-3">
|
||||
{{ type.name }}
|
||||
</div>
|
||||
<h1 class="text-3xl sm:text-4xl lg:text-5xl font-bold tracking-tight text-gray-900">
|
||||
{{ type.meta_title }}
|
||||
</h1>
|
||||
<p class="max-w-xl mx-auto text-gray-600 mt-4 text-lg font-normal">
|
||||
{{ type.meta_description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bg-white py-12 sm:py-16">
|
||||
<div class="px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 sm:gap-6 relative z-20">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex-1 sm:flex-none">
|
||||
<select-input v-model="selectedIndustry" name="industry"
|
||||
:options="industriesOptions" class="w-full sm:w-auto md:w-56"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 w-full md:max-w-xs">
|
||||
<text-input name="search" :form="searchTemplate" placeholder="Search..." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="templatesLoading" class="text-center mt-4">
|
||||
<loader class="h-6 w-6 text-nt-blue mx-auto" />
|
||||
</div>
|
||||
<p v-else-if="enrichedTemplates.length === 0" class="text-center mt-4">
|
||||
No templates found.
|
||||
</p>
|
||||
<div v-else class="relative z-10">
|
||||
<div class="grid grid-cols-1 mt-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8 sm:gap-y-12">
|
||||
<single-template v-for="template in enrichedTemplates" :key="template.id" :slug="template.slug" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section 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">
|
||||
<p class="text-gray-600 font-normal">
|
||||
{{ type.description }}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section 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="flex items-center justify-between">
|
||||
<h4 class="text-xl font-bold tracking-tight text-gray-900 sm:text-2xl">
|
||||
Other Types
|
||||
</h4>
|
||||
|
||||
<v-button :to="{name:'templates'}" color="white" size="small" :arrow="true">
|
||||
View All Templates
|
||||
</v-button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-8 mt-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
<router-link v-for="row in otherTypes" :key="row.slug"
|
||||
:to="{params:{slug:row.slug}, name:'templates.types.show'}"
|
||||
:title="row.name"
|
||||
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
|
||||
>
|
||||
{{ row.name }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</template>
|
||||
|
||||
<open-form-footer class="mt-8 border-t"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from '~/store'
|
||||
import Form from 'vform'
|
||||
import Fuse from 'fuse.js'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import SeoMeta from '../../mixins/seo-meta.js'
|
||||
import OpenFormFooter from '../../components/pages/OpenFormFooter.vue'
|
||||
import Breadcrumb from '../../components/common/Breadcrumb.vue'
|
||||
import SingleTemplate from '../../components/pages/templates/SingleTemplate.vue'
|
||||
|
||||
const loadTemplates = function () {
|
||||
store.commit('open/templates/startLoading')
|
||||
store.dispatch('open/templates/loadIfEmpty').then(() => {
|
||||
store.commit('open/templates/stopLoading')
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
components: { Breadcrumb, OpenFormFooter, SingleTemplate },
|
||||
mixins: [SeoMeta],
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
loadTemplates()
|
||||
next()
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
selectedIndustry: 'all',
|
||||
searchTemplate: new Form({
|
||||
search: ''
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
authenticated: 'auth/check',
|
||||
user: 'auth/user'
|
||||
}),
|
||||
...mapState({
|
||||
templates: state => state['open/templates'].content,
|
||||
templatesLoading: state => state['open/templates'].loading,
|
||||
industries: state => state['open/templates'].industries,
|
||||
types: state => state['open/templates'].types
|
||||
}),
|
||||
breadcrumbs () {
|
||||
if (!this.type) {
|
||||
return [{ route: { name: 'templates' }, label: 'Templates' }]
|
||||
}
|
||||
return [{ route: { name: 'templates' }, label: 'Templates' }, { label: this.type.name }]
|
||||
},
|
||||
type () {
|
||||
return Object.values(this.types).find((type) => {
|
||||
return type.slug === this.$route.params.slug
|
||||
})
|
||||
},
|
||||
industriesOptions () {
|
||||
return [{ name: 'All Industries', value: 'all' }].concat(Object.values(this.industries).map((industry) => {
|
||||
return {
|
||||
name: industry.name,
|
||||
value: industry.slug
|
||||
}
|
||||
}))
|
||||
},
|
||||
otherTypes() {
|
||||
return Object.values(this.types).filter((type) => {
|
||||
return type.slug !== this.$route.params.slug
|
||||
})
|
||||
},
|
||||
enrichedTemplates () {
|
||||
let enrichedTemplates = this.templates
|
||||
|
||||
// Filter by current Type only
|
||||
enrichedTemplates = enrichedTemplates.filter((item) => {
|
||||
return (item.types && item.types.length > 0) ? item.types.includes(this.$route.params.slug) : false
|
||||
})
|
||||
|
||||
// Filter by Selected Industry
|
||||
if (this.selectedIndustry && this.selectedIndustry !== 'all') {
|
||||
enrichedTemplates = enrichedTemplates.filter((item) => {
|
||||
return (item.industries && item.industries.length > 0) ? item.industries.includes(this.selectedIndustry) : false
|
||||
})
|
||||
}
|
||||
|
||||
if (this.searchTemplate.search === '' || this.searchTemplate.search === null) {
|
||||
return enrichedTemplates
|
||||
}
|
||||
|
||||
// Fuze search
|
||||
const fuzeOptions = {
|
||||
keys: [
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
'short_description'
|
||||
]
|
||||
}
|
||||
const fuse = new Fuse(enrichedTemplates, fuzeOptions)
|
||||
return fuse.search(this.searchTemplate.search).map((res) => {
|
||||
return res.item
|
||||
})
|
||||
},
|
||||
metaTitle () {
|
||||
return this.type ? this.type.meta_title : 'Form Template Type'
|
||||
},
|
||||
metaDescription () {
|
||||
if (!this.type) return null
|
||||
return this.type.meta_description.substring(0, 140)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
.nf-text {
|
||||
@apply space-y-4;
|
||||
h2 {
|
||||
@apply text-sm font-normal tracking-widest text-gray-500 uppercase;
|
||||
}
|
||||
|
||||
p {
|
||||
@apply font-normal leading-7 text-gray-900 dark:text-gray-100;
|
||||
}
|
||||
|
||||
ol {
|
||||
@apply list-decimal list-inside;
|
||||
}
|
||||
|
||||
ul {
|
||||
@apply list-disc list-inside;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user