0351d front end linting (#377)

* feat: disable custom script for  trial users

* after lint fix

* frontend linting

---------

Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
Favour Olayinka
2024-04-15 18:39:03 +01:00
committed by GitHub
parent 8d35fc8b1a
commit bcd45ce8a6
228 changed files with 17036 additions and 8744 deletions

View File

@@ -7,34 +7,52 @@
</p>
</div>
<div class="flex justify-center mt-5 md:mt-0">
<router-link :to="{ name: user ? 'home' : 'index' }" class="flex-shrink-0 font-semibold flex items-center">
<img src="/img/logo.svg" alt="notion tools logo" class="w-10 h-10">
<span class="ml-2 text-xl text-black dark:text-white">
OpnForm
</span>
<router-link
:to="{ name: user ? 'home' : 'index' }"
class="flex-shrink-0 font-semibold flex items-center"
>
<img
src="/img/logo.svg"
alt="notion tools logo"
class="w-10 h-10"
>
<span class="ml-2 text-xl text-black dark:text-white"> OpnForm </span>
</router-link>
</div>
<div class="flex justify-center mt-5 md:mt-0">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-x-4 gap-y-2">
<router-link :to="{name:'privacy-policy'}"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue">
<router-link
:to="{ name: 'privacy-policy' }"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
>
Privacy Policy
</router-link>
<router-link :to="{name:'terms-conditions'}"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue">
<router-link
:to="{ name: 'terms-conditions' }"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
>
Terms & Conditions
</router-link>
<a :href="opnformConfig.links.feature_requests" target="_blank"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue">
<a
:href="opnformConfig.links.feature_requests"
target="_blank"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
>
Feature Requests
</a>
<a :href="opnformConfig.links.roadmap" target="_blank"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue">
<a
:href="opnformConfig.links.roadmap"
target="_blank"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
>
Roadmap
</a>
<a :href="opnformConfig.links.discord" target="_blank"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue">
<a
:href="opnformConfig.links.discord"
target="_blank"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
>
Discord
</a>
</div>
@@ -44,21 +62,20 @@
</template>
<script>
import { computed } from 'vue'
import opnformConfig from "~/opnform.config.js";
import { computed } from "vue"
import opnformConfig from "~/opnform.config.js"
export default {
setup () {
setup() {
const authStore = useAuthStore()
return {
user : computed(() => authStore.user),
opnformConfig
user: computed(() => authStore.user),
opnformConfig,
}
},
data: () => ({
currYear: new Date().getFullYear(),
}),
}
</script>

View File

@@ -1,76 +1,162 @@
<template>
<!-- Forgot password modal -->
<modal :show="show" @close="close" max-width="lg">
<template #icon>
<template v-if="isMailSent">
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect class="text-blue-50" width="56" height="56" rx="28" fill="currentColor"/>
<path d="M16.3333 22.1666L25.859 28.8346C26.6304 29.3746 27.016 29.6446 27.4356 29.7492C27.8061 29.8415 28.1937 29.8415 28.5643 29.7492C28.9838 29.6446 29.3695 29.3746 30.1408 28.8346L39.6666 22.1666M21.9333 37.3333H34.0666C36.0268 37.3333 37.0069 37.3333 37.7556 36.9518C38.4141 36.6163 38.9496 36.0808 39.2851 35.4223C39.6666 34.6736 39.6666 33.6935 39.6666 31.7333V24.2666C39.6666 22.3064 39.6666 21.3264 39.2851 20.5777C38.9496 19.9191 38.4141 19.3837 37.7556 19.0481C37.0069 18.6666 36.0268 18.6666 34.0666 18.6666H21.9333C19.9731 18.6666 18.993 18.6666 18.2443 19.0481C17.5857 19.3837 17.0503 19.9191 16.7147 20.5777C16.3333 21.3264 16.3333 22.3064 16.3333 24.2666V31.7333C16.3333 33.6935 16.3333 34.6736 16.7147 35.4223C17.0503 36.0808 17.5857 36.6163 18.2443 36.9518C18.993 37.3333 19.9731 37.3333 21.9333 37.3333Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</template>
<template v-else>
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect class="text-blue-50" width="56" height="56" rx="28" fill="currentColor"/>
<path d="M33.8333 24.4999C33.8333 23.9028 33.6055 23.3057 33.1499 22.8501C32.6943 22.3945 32.0972 22.1667 31.5 22.1667M31.5 31.5C35.366 31.5 38.5 28.366 38.5 24.5C38.5 20.634 35.366 17.5 31.5 17.5C27.634 17.5 24.5 20.634 24.5 24.5C24.5 24.8193 24.5214 25.1336 24.5628 25.4415C24.6309 25.948 24.6649 26.2013 24.642 26.3615C24.6181 26.5284 24.5877 26.6184 24.5055 26.7655C24.4265 26.9068 24.2873 27.046 24.009 27.3243L18.0467 33.2866C17.845 33.4884 17.7441 33.5893 17.6719 33.707C17.608 33.8114 17.5608 33.9252 17.5322 34.0442C17.5 34.1785 17.5 34.3212 17.5 34.6065V36.6333C17.5 37.2867 17.5 37.6134 17.6272 37.863C17.739 38.0825 17.9175 38.261 18.137 38.3728C18.3866 38.5 18.7133 38.5 19.3667 38.5H22.1667V36.1667H24.5V33.8333H26.8333L28.6757 31.991C28.954 31.7127 29.0932 31.5735 29.2345 31.4945C29.3816 31.4123 29.4716 31.3819 29.6385 31.358C29.7987 31.3351 30.052 31.3691 30.5585 31.4372C30.8664 31.4786 31.1807 31.5 31.5 31.5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</template>
</template>
<template #title>
<template v-if="isMailSent">Check your email</template>
<template v-else>Forgot password?</template>
</template>
<!-- Forgot password modal -->
<modal
:show="show"
max-width="lg"
@close="close"
>
<template #icon>
<template v-if="isMailSent">
<div class="text-center">We sent a password reset link to <br/><span>{{form.email}}</span></div>
<div class="w-full p-4 text-center">
<span class="mt-4">Didn't receive the email? <a href="#" class="ml-1" @click.prevent="send">Click to resend</a></span>
</div>
<svg
width="56"
height="56"
viewBox="0 0 56 56"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
class="text-blue-50"
width="56"
height="56"
rx="28"
fill="currentColor"
/>
<path
d="M16.3333 22.1666L25.859 28.8346C26.6304 29.3746 27.016 29.6446 27.4356 29.7492C27.8061 29.8415 28.1937 29.8415 28.5643 29.7492C28.9838 29.6446 29.3695 29.3746 30.1408 28.8346L39.6666 22.1666M21.9333 37.3333H34.0666C36.0268 37.3333 37.0069 37.3333 37.7556 36.9518C38.4141 36.6163 38.9496 36.0808 39.2851 35.4223C39.6666 34.6736 39.6666 33.6935 39.6666 31.7333V24.2666C39.6666 22.3064 39.6666 21.3264 39.2851 20.5777C38.9496 19.9191 38.4141 19.3837 37.7556 19.0481C37.0069 18.6666 36.0268 18.6666 34.0666 18.6666H21.9333C19.9731 18.6666 18.993 18.6666 18.2443 19.0481C17.5857 19.3837 17.0503 19.9191 16.7147 20.5777C16.3333 21.3264 16.3333 22.3064 16.3333 24.2666V31.7333C16.3333 33.6935 16.3333 34.6736 16.7147 35.4223C17.0503 36.0808 17.5857 36.6163 18.2443 36.9518C18.993 37.3333 19.9731 37.3333 21.9333 37.3333Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
<template v-else>
<div class="text-center">No worries, we'll send you reset instructions.</div>
<form @submit.prevent="send" @keydown="form.onKeydown($event)" class="p-4">
<text-input name="email" :form="form" label="Email" placeholder="Your email address" :required="true" />
<div class="w-full mt-6">
<v-button :loading="form.busy" class="w-full my-3">Reset password</v-button>
</div>
</form>
<svg
width="56"
height="56"
viewBox="0 0 56 56"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
class="text-blue-50"
width="56"
height="56"
rx="28"
fill="currentColor"
/>
<path
d="M33.8333 24.4999C33.8333 23.9028 33.6055 23.3057 33.1499 22.8501C32.6943 22.3945 32.0972 22.1667 31.5 22.1667M31.5 31.5C35.366 31.5 38.5 28.366 38.5 24.5C38.5 20.634 35.366 17.5 31.5 17.5C27.634 17.5 24.5 20.634 24.5 24.5C24.5 24.8193 24.5214 25.1336 24.5628 25.4415C24.6309 25.948 24.6649 26.2013 24.642 26.3615C24.6181 26.5284 24.5877 26.6184 24.5055 26.7655C24.4265 26.9068 24.2873 27.046 24.009 27.3243L18.0467 33.2866C17.845 33.4884 17.7441 33.5893 17.6719 33.707C17.608 33.8114 17.5608 33.9252 17.5322 34.0442C17.5 34.1785 17.5 34.3212 17.5 34.6065V36.6333C17.5 37.2867 17.5 37.6134 17.6272 37.863C17.739 38.0825 17.9175 38.261 18.137 38.3728C18.3866 38.5 18.7133 38.5 19.3667 38.5H22.1667V36.1667H24.5V33.8333H26.8333L28.6757 31.991C28.954 31.7127 29.0932 31.5735 29.2345 31.4945C29.3816 31.4123 29.4716 31.3819 29.6385 31.358C29.7987 31.3351 30.052 31.3691 30.5585 31.4372C30.8664 31.4786 31.1807 31.5 31.5 31.5Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
<div class="w-full text-center">
<a href="#" @click.prevent="close" class="text-xs hover:underline text-gray-500 sm:text-sm hover:text-gray-700">
<svg class="inline mr-1" width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.3334 6.99996H1.66669M1.66669 6.99996L7.50002 12.8333M1.66669 6.99996L7.50002 1.16663" stroke="#475467" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Back to log in
</a>
</template>
<template #title>
<template v-if="isMailSent">
Check your email
</template>
<template v-else>
Forgot password?
</template>
</template>
<template v-if="isMailSent">
<div class="text-center">
We sent a password reset link to <br><span>{{ form.email }}</span>
</div>
</modal>
</template>
<div class="w-full p-4 text-center">
<span class="mt-4">Didn't receive the email?
<a
href="#"
class="ml-1"
@click.prevent="send"
>Click to resend</a></span>
</div>
</template>
<template v-else>
<div class="text-center">
No worries, we'll send you reset instructions.
</div>
<form
class="p-4"
@submit.prevent="send"
@keydown="form.onKeydown($event)"
>
<text-input
name="email"
:form="form"
label="Email"
placeholder="Your email address"
:required="true"
/>
<script>
export default {
name: 'ForgotPasswordModal',
components: { },
props: {
show: {
type: Boolean,
required: true
}
<div class="w-full mt-6">
<v-button
:loading="form.busy"
class="w-full my-3"
>
Reset password
</v-button>
</div>
</form>
</template>
<div class="w-full text-center">
<a
href="#"
class="text-xs hover:underline text-gray-500 sm:text-sm hover:text-gray-700"
@click.prevent="close"
>
<svg
class="inline mr-1"
width="15"
height="14"
viewBox="0 0 15 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13.3334 6.99996H1.66669M1.66669 6.99996L7.50002 12.8333M1.66669 6.99996L7.50002 1.16663"
stroke="#475467"
stroke-width="1.66667"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
Back to log in
</a>
</div>
</modal>
</template>
<script>
export default {
name: "ForgotPasswordModal",
components: {},
props: {
show: {
type: Boolean,
required: true,
},
data: () => ({
isMailSent: false,
form: useForm({
email: ''
})
},
emits: ['close'],
data: () => ({
isMailSent: false,
form: useForm({
email: "",
}),
methods: {
async send () {
const { data } = await this.form.post('/password/email')
this.isMailSent = true
},
close () {
this.$emit('close')
this.isMailSent = false
}
}
}
</script>
}),
methods: {
async send() {
await this.form.post("/password/email")
this.isMailSent = true
},
close() {
this.$emit("close")
this.isMailSent = false
},
},
}
</script>

View File

@@ -1,39 +1,77 @@
<template>
<div>
<forgot-password-modal :show="showForgotModal" @close="showForgotModal=false"/>
<forgot-password-modal
:show="showForgotModal"
@close="showForgotModal = false"
/>
<form class="mt-4" @submit.prevent="login" @keydown="form.onKeydown($event)">
<form
class="mt-4"
@submit.prevent="login"
@keydown="form.onKeydown($event)"
>
<!-- Email -->
<text-input name="email" :form="form" label="Email" :required="true" placeholder="Your email address"/>
<text-input
name="email"
:form="form"
label="Email"
:required="true"
placeholder="Your email address"
/>
<!-- Password -->
<text-input native-type="password" placeholder="Your password"
name="password" :form="form" label="Password" :required="true"
<text-input
native-type="password"
placeholder="Your password"
name="password"
:form="form"
label="Password"
:required="true"
/>
<!-- Remember Me -->
<div class="relative flex items-center my-5">
<v-checkbox v-model="remember" class="w-full md:w-1/2" name="remember" size="small">
<v-checkbox
v-model="remember"
class="w-full md:w-1/2"
name="remember"
size="small"
>
Remember me
</v-checkbox>
<div class="w-full md:w-1/2 text-right">
<a href="#" class="text-xs hover:underline text-gray-500 sm:text-sm hover:text-gray-700"
@click.prevent="showForgotModal=true">
<a
href="#"
class="text-xs hover:underline text-gray-500 sm:text-sm hover:text-gray-700"
@click.prevent="showForgotModal = true"
>
Forgot your password?
</a>
</div>
</div>
<!-- Submit Button -->
<v-button dusk="btn_login" :loading="form.busy || loading">
<v-button
dusk="btn_login"
:loading="form.busy || loading"
>
Log in to continue
</v-button>
<p class="text-gray-500 mt-4">
Don't have an account?
<a v-if="isQuick" href="#" class="font-semibold ml-1" @click.prevent="$emit('openRegister')">Sign Up</a>
<NuxtLink v-else :to="{name:'register'}" class="font-semibold ml-1">
<a
v-if="isQuick"
href="#"
class="font-semibold ml-1"
@click.prevent="$emit('openRegister')"
>Sign Up</a>
<NuxtLink
v-else
:to="{ name: 'register' }"
class="font-semibold ml-1"
>
Sign Up
</NuxtLink>
</p>
@@ -42,81 +80,89 @@
</template>
<script>
import ForgotPasswordModal from '../ForgotPasswordModal.vue'
import {opnFetch} from "~/composables/useOpnApi.js"
import {fetchAllWorkspaces} from "~/stores/workspaces.js"
import ForgotPasswordModal from "../ForgotPasswordModal.vue"
import { opnFetch } from "~/composables/useOpnApi.js"
import { fetchAllWorkspaces } from "~/stores/workspaces.js"
export default {
name: 'LoginForm',
name: "LoginForm",
components: {
ForgotPasswordModal
ForgotPasswordModal,
},
props: {
isQuick: {
type: Boolean,
required: false,
default: false
}
default: false,
},
},
emits: ['afterQuickLogin', 'openRegister'],
setup() {
return {
authStore: useAuthStore(),
formsStore: useFormsStore(),
workspaceStore: useWorkspacesStore()
workspaceStore: useWorkspacesStore(),
}
},
data: () => ({
form: useForm({
email: '',
password: ''
email: "",
password: "",
}),
loading: false,
remember: false,
showForgotModal: false
showForgotModal: false,
}),
methods: {
login() {
// Submit the form.
this.loading = true
this.form.post('login').then(async (data) => {
// Save the token.
this.authStore.setToken(data.token)
this.form
.post("login")
.then(async (data) => {
// Save the token.
this.authStore.setToken(data.token)
const [userDataResponse, workspacesResponse] = await Promise.all([opnFetch('user'), fetchAllWorkspaces()]);
this.authStore.setUser(userDataResponse)
this.workspaceStore.set(workspacesResponse.data.value)
const [userDataResponse, workspacesResponse] = await Promise.all([
opnFetch("user"),
fetchAllWorkspaces(),
])
this.authStore.setUser(userDataResponse)
this.workspaceStore.set(workspacesResponse.data.value)
// Load forms
this.formsStore.loadAll(this.workspaceStore.currentId)
// Load forms
this.formsStore.loadAll(this.workspaceStore.currentId)
// Redirect home.
this.redirect()
}).catch((error) => {
console.error(error)
}).finally(() => {
this.loading = false
})
// Redirect home.
this.redirect()
})
.catch((error) => {
console.error(error)
})
.finally(() => {
this.loading = false
})
},
redirect() {
if (this.isQuick) {
this.$emit('afterQuickLogin')
this.$emit("afterQuickLogin")
return
}
const intendedUrlCookie = useCookie('intended_url')
const intendedUrlCookie = useCookie("intended_url")
const router = useRouter()
if (intendedUrlCookie.value) {
router.push({path: intendedUrlCookie.value})
useCookie('intended_url').value = null
router.push({ path: intendedUrlCookie.value })
useCookie("intended_url").value = null
} else {
router.push({name: 'home'})
router.push({ name: "home" })
}
}
}
},
},
}
</script>

View File

@@ -1,76 +1,112 @@
<template>
<div>
<!-- Login modal -->
<modal :show="showLoginModal" @close="showLoginModal=false" max-width="lg">
<modal
:show="showLoginModal"
max-width="lg"
@close="showLoginModal = false"
>
<template #icon>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-8 h-8">
<path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" />
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-8 h-8"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
</template>
<template #title>
Login to OpnForm
</template>
<div class="px-4">
<login-form :isQuick="true" @openRegister="openRegister" @afterQuickLogin="afterQuickLogin" />
<login-form
:is-quick="true"
@open-register="openRegister"
@after-quick-login="afterQuickLogin"
/>
</div>
</modal>
<!-- Register modal -->
<modal :show="showRegisterModal" @close="$emit('close')" max-width="lg">
<modal
:show="showRegisterModal"
max-width="lg"
@close="$emit('close')"
>
<template #icon>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-8 h-8">
<path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" />
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-8 h-8"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
</template>
<template #title>
Create an account
</template>
<div class="px-4">
<register-form :isQuick="true" @openLogin="openLogin" @afterQuickLogin="afterQuickLogin" />
<register-form
:is-quick="true"
@open-login="openLogin"
@after-quick-login="afterQuickLogin"
/>
</div>
</modal>
</div>
</template>
<script>
import LoginForm from './LoginForm.vue'
import RegisterForm from './RegisterForm.vue'
import LoginForm from "./LoginForm.vue"
import RegisterForm from "./RegisterForm.vue"
export default {
name: 'QuickRegister',
name: "QuickRegister",
components: {
LoginForm,
RegisterForm
RegisterForm,
},
props: {
showRegisterModal: {
type: Boolean,
required: true
}
required: true,
},
},
emits: ['afterLogin', 'close', 'reopen'],
data: () => ({
showLoginModal: false,
}),
mounted() {
},
mounted() {},
methods: {
openLogin(){
openLogin() {
this.showLoginModal = true
this.$emit('close')
this.$emit("close")
},
openRegister(){
openRegister() {
this.showLoginModal = false
this.$emit('reopen')
this.$emit("reopen")
},
afterQuickLogin(){
afterQuickLogin() {
this.showLoginModal = false
this.$emit('afterLogin')
}
}
this.$emit("afterLogin")
},
},
}
</script>

View File

@@ -1,34 +1,75 @@
<template>
<div>
<form class="mt-4" @submit.prevent="register" @keydown="form.onKeydown($event)">
<form
class="mt-4"
@submit.prevent="register"
@keydown="form.onKeydown($event)"
>
<!-- Name -->
<text-input name="name" :form="form" label="Name" placeholder="Your name" :required="true"/>
<text-input
name="name"
:form="form"
label="Name"
placeholder="Your name"
:required="true"
/>
<!-- Email -->
<text-input name="email" :form="form" label="Email" :required="true" placeholder="Your email address"/>
<text-input
name="email"
:form="form"
label="Email"
:required="true"
placeholder="Your email address"
/>
<select-input name="hear_about_us" :options="hearAboutUsOptions" :form="form" placeholder="Select option"
label="How did you hear about us?" :required="true"
<select-input
name="hear_about_us"
:options="hearAboutUsOptions"
:form="form"
placeholder="Select option"
label="How did you hear about us?"
:required="true"
/>
<!-- Password -->
<text-input native-type="password" placeholder="Enter password"
name="password" :form="form" label="Password" :required="true"
<text-input
native-type="password"
placeholder="Enter password"
name="password"
:form="form"
label="Password"
:required="true"
/>
<!-- Password Confirmation-->
<text-input native-type="password" :form="form" :required="true" placeholder="Enter confirm password"
name="password_confirmation" label="Confirm Password"
<text-input
native-type="password"
:form="form"
:required="true"
placeholder="Enter confirm password"
name="password_confirmation"
label="Confirm Password"
/>
<checkbox-input :form="form" name="agree_terms" :required="true">
<checkbox-input
:form="form"
name="agree_terms"
:required="true"
>
<template #label>
I agree with the
<NuxtLink :to="{name:'terms-conditions'}" target="_blank">
<NuxtLink
:to="{ name: 'terms-conditions' }"
target="_blank"
>
Terms and conditions
</NuxtLink>
and
<NuxtLink :to="{name:'privacy-policy'}" target="_blank">
<NuxtLink
:to="{ name: 'privacy-policy' }"
target="_blank"
>
Privacy policy
</NuxtLink>
of the website and I accept them.
@@ -42,8 +83,17 @@
<p class="text-gray-500 mt-4">
Already have an account?
<a v-if="isQuick" href="#" class="font-semibold ml-1" @click.prevent="$emit('openLogin')">Log In</a>
<NuxtLink v-else :to="{name:'login'}" class="font-semibold ml-1">
<a
v-if="isQuick"
href="#"
class="font-semibold ml-1"
@click.prevent="$emit('openLogin')"
>Log In</a>
<NuxtLink
v-else
:to="{ name: 'login' }"
class="font-semibold ml-1"
>
Log In
</NuxtLink>
</p>
@@ -52,61 +102,69 @@
</template>
<script>
import {opnFetch} from "~/composables/useOpnApi.js";
import {fetchAllWorkspaces} from "~/stores/workspaces.js";
import { opnFetch } from "~/composables/useOpnApi.js"
import { fetchAllWorkspaces } from "~/stores/workspaces.js"
export default {
name: 'RegisterForm',
name: "RegisterForm",
components: {},
props: {
isQuick: {
type: Boolean,
required: false,
default: false
}
default: false,
},
},
emits: ['afterQuickLogin', 'openLogin'],
setup() {
return {
authStore: useAuthStore(),
formsStore: useFormsStore(),
workspaceStore: useWorkspacesStore(),
logEvent: useAmplitude().logEvent
logEvent: useAmplitude().logEvent,
}
},
data: () => ({
form: useForm({
name: '',
email: '',
password: '',
password_confirmation: '',
name: "",
email: "",
password: "",
password_confirmation: "",
agree_terms: false,
appsumo_license: null
appsumo_license: null,
}),
}),
computed: {
hearAboutUsOptions() {
const options = [
{name: 'Facebook', value: 'facebook'},
{name: 'Twitter', value: 'twitter'},
{name: 'Reddit', value: 'reddit'},
{name: 'Github', value: 'github'},
{name: 'Search Engine (Google, DuckDuckGo...)', value: 'search_engine'},
{name: 'Friend or Colleague', value: 'friend_colleague'},
{name: 'Blog/Article', value: 'blog_article'}
].map((value) => ({value, sort: Math.random()}))
{ name: "Facebook", value: "facebook" },
{ name: "Twitter", value: "twitter" },
{ name: "Reddit", value: "reddit" },
{ name: "Github", value: "github" },
{
name: "Search Engine (Google, DuckDuckGo...)",
value: "search_engine",
},
{ name: "Friend or Colleague", value: "friend_colleague" },
{ name: "Blog/Article", value: "blog_article" },
]
.map((value) => ({ value, sort: Math.random() }))
.sort((a, b) => a.sort - b.sort)
.map(({value}) => value)
options.push({name: 'Other', value: 'other'})
.map(({ value }) => value)
options.push({ name: "Other", value: "other" })
return options
}
},
},
mounted() {
// Set appsumo license
if (this.$route.query.appsumo_license !== undefined && this.$route.query.appsumo_license) {
if (
this.$route.query.appsumo_license !== undefined &&
this.$route.query.appsumo_license
) {
this.form.appsumo_license = this.$route.query.appsumo_license
}
},
@@ -114,15 +172,15 @@ export default {
methods: {
async register() {
// Register the user.
const data = await this.form.post('/register')
const data = await this.form.post("/register")
// Log in the user.
const tokenData = await this.form.post('/login')
const tokenData = await this.form.post("/login")
// Save the token.
this.authStore.setToken(tokenData.token)
const userData = await opnFetch('user')
const userData = await opnFetch("user")
this.authStore.setUser(userData)
const workspaces = await fetchAllWorkspaces()
@@ -131,24 +189,28 @@ export default {
// Load forms
this.formsStore.loadAll(this.workspaceStore.currentId)
this.logEvent('register', {source: this.form.hear_about_us})
this.logEvent("register", { source: this.form.hear_about_us })
// AppSumo License
if (data.appsumo_license === false) {
useAlert().error('Invalid AppSumo license. This probably happened because this license was already' +
' attached to another OpnForm account. Please contact support.')
useAlert().error(
"Invalid AppSumo license. This probably happened because this license was already" +
" attached to another OpnForm account. Please contact support.",
)
} else if (data.appsumo_license === true) {
useAlert().success('Your AppSumo license was successfully activated! You now have access to all the' +
' features of the AppSumo deal.')
useAlert().success(
"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')
this.$emit("afterQuickLogin")
} else {
this.$router.push({name: 'forms-create'})
this.$router.push({ name: "forms-create" })
}
}
}
},
},
}
</script>

View File

@@ -1,18 +1,28 @@
<template>
<div id="new-features"
class="w-full bg-gray-50 dark:bg-gray-800 border rounded-lg mt-4"
<div
id="new-features"
class="w-full bg-gray-50 dark:bg-gray-800 border rounded-lg mt-4"
>
<div class="border-b">
<div v-track.new_in_notionforms_click
class="relative flex items-center cursor-pointer hover:bg-gray-100 p-4" role="button"
@click.prevent="showNewFeatures=!showNewFeatures"
<div
v-track.new_in_notionforms_click
class="relative flex items-center cursor-pointer hover:bg-gray-100 p-4"
role="button"
@click.prevent="showNewFeatures = !showNewFeatures"
>
<div class="text-gray-700 dark:text-gray-300 pr-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2"
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path stroke-linecap="round" stroke-linejoin="round"
d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"
/>
</svg>
</div>
@@ -26,26 +36,57 @@
</div>
</div>
<v-transition>
<ul v-if="showNewFeatures" class="list-disc list-inside border-t pt-2 p-4">
<li v-for="changelog in changelogEntries" :key="changelog.id" v-track.new_feature_click class="text-sm">
<a :href="changelog.url" target="_blank" class="text-gray-700 dark:text-gray-300">{{ changelog.title }}</a>
<ul
v-if="showNewFeatures"
class="list-disc list-inside border-t pt-2 p-4"
>
<li
v-for="changelog in changelogEntries"
:key="changelog.id"
v-track.new_feature_click
class="text-sm"
>
<a
:href="changelog.url"
target="_blank"
class="text-gray-700 dark:text-gray-300"
>{{ changelog.title }}</a>
</li>
<li v-track.new_feature_read_more_click class="text-sm">
<a class="text-gray-700 dark:text-gray-300" :href="changelogLink" target="_blank">Read more</a>
<li
v-track.new_feature_read_more_click
class="text-sm"
>
<a
class="text-gray-700 dark:text-gray-300"
:href="changelogLink"
target="_blank"
>Read more</a>
</li>
</ul>
</v-transition>
</div>
<div class="relative flex items-center cursor-pointer hover:bg-gray-100 p-4">
<a v-track.feature_request_click="{user_has_forms:user.has_forms}" :href="requestFeatureLink"
class="absolute inset-0" target="_blank"
<div
class="relative flex items-center cursor-pointer hover:bg-gray-100 p-4"
>
<a
v-track.feature_request_click="{ user_has_forms: user.has_forms }"
:href="requestFeatureLink"
class="absolute inset-0"
target="_blank"
/>
<div class="text-gray-700 dark:text-gray-300 pr-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"
stroke-width="2"
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path stroke-linecap="round" stroke-linejoin="round"
d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"
/>
</svg>
</div>
@@ -62,45 +103,45 @@
</template>
<script>
import { computed } from 'vue'
import { useAuthStore } from '../../../stores/auth';
import VTransition from '~/components/global/transitions/VTransition.vue'
import { computed } from "vue"
import { useAuthStore } from "../../../stores/auth"
import VTransition from "~/components/global/transitions/VTransition.vue"
export default {
components: { VTransition },
props: {},
setup () {
setup() {
const authStore = useAuthStore()
return {
user : computed(() => authStore.user)
user: computed(() => authStore.user),
}
},
data: () => ({
changelogEntries: [],
showNewFeatures: false
showNewFeatures: false,
}),
mounted () {
computed: {
requestFeatureLink() {
return this.$config.links.feature_requests
},
changelogLink() {
return this.$config.links.changelog_url
},
},
mounted() {
this.loadChangelogEntries()
},
computed: {
requestFeatureLink () {
return this.$config.links.feature_requests
},
changelogLink () {
return this.$config.links.changelog_url
}
},
methods: {
loadChangelogEntries () {
opnFetch('/content/changelog/entries').then(data => {
loadChangelogEntries() {
opnFetch("/content/changelog/entries").then((data) => {
this.changelogEntries = data.splice(0, 3)
})
}
}
},
},
}
</script>

View File

@@ -1,18 +1,36 @@
<template>
<Modal :show="show" :closeable="!aiForm.busy" @close="$emit('close')">
<Modal
:show="show"
:closeable="!aiForm.busy"
@close="$emit('close')"
>
<template #icon>
<template v-if="state == 'default'">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-10 h-10 text-blue">
<path fill-rule="evenodd"
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-10 h-10 text-blue"
>
<path
fill-rule="evenodd"
d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
clip-rule="evenodd" />
clip-rule="evenodd"
/>
</svg>
</template>
<template v-else-if="state == 'ai'">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-8 h-8 text-blue-500">
<path fill-rule="evenodd"
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-8 h-8 text-blue-500"
>
<path
fill-rule="evenodd"
d="M14.615 1.595a.75.75 0 01.359.852L12.982 9.75h7.268a.75.75 0 01.548 1.262l-10.5 11.25a.75.75 0 01-1.272-.71l1.992-7.302H3.75a.75.75 0 01-.548-1.262l10.5-11.25a.75.75 0 01.913-.143z"
clip-rule="evenodd" />
clip-rule="evenodd"
/>
</svg>
</template>
</template>
@@ -24,27 +42,54 @@
AI-powered form generator
</template>
</template>
<div v-if="state == 'default'" class="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-8">
<div v-track.select_form_base="{ base: 'contact-form' }" role="button"
class="rounded-md border p-4 flex flex-col items-center cursor-pointer hover:bg-gray-50" @click="$emit('close')">
<div
v-if="state == 'default'"
class="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-8"
>
<div
v-track.select_form_base="{ base: 'contact-form' }"
role="button"
class="rounded-md border p-4 flex flex-col items-center cursor-pointer hover:bg-gray-50"
@click="$emit('close')"
>
<div class="p-4">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-8 h-8 text-blue-500">
<path d="M1.5 8.67v8.58a3 3 0 003 3h15a3 3 0 003-3V8.67l-8.928 5.493a3 3 0 01-3.144 0L1.5 8.67z" />
<path d="M22.5 6.908V6.75a3 3 0 00-3-3h-15a3 3 0 00-3 3v.158l9.714 5.978a1.5 1.5 0 001.572 0L22.5 6.908z" />
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-8 h-8 text-blue-500"
>
<path
d="M1.5 8.67v8.58a3 3 0 003 3h15a3 3 0 003-3V8.67l-8.928 5.493a3 3 0 01-3.144 0L1.5 8.67z"
/>
<path
d="M22.5 6.908V6.75a3 3 0 00-3-3h-15a3 3 0 00-3 3v.158l9.714 5.978a1.5 1.5 0 001.572 0L22.5 6.908z"
/>
</svg>
</div>
<p class="font-medium">
Start from a simple contact form
</p>
</div>
<div v-if="aiFeaturesEnabled" v-track.select_form_base="{ base: 'ai' }"
class="rounded-md border p-4 flex flex-col items-center cursor-pointer hover:bg-gray-50" role="button"
@click="state = 'ai'">
<div
v-if="aiFeaturesEnabled"
v-track.select_form_base="{ base: 'ai' }"
class="rounded-md border p-4 flex flex-col items-center cursor-pointer hover:bg-gray-50"
role="button"
@click="state = 'ai'"
>
<div class="p-4 relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-8 h-8 text-blue-500">
<path fill-rule="evenodd"
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-8 h-8 text-blue-500"
>
<path
fill-rule="evenodd"
d="M14.615 1.595a.75.75 0 01.359.852L12.982 9.75h7.268a.75.75 0 01.548 1.262l-10.5 11.25a.75.75 0 01-1.272-.71l1.992-7.302H3.75a.75.75 0 01-.548-1.262l10.5-11.25a.75.75 0 01.913-.143z"
clip-rule="evenodd" />
clip-rule="evenodd"
/>
</svg>
</div>
<p class="font-medium text-blue-700">
@@ -52,32 +97,65 @@
</p>
<span class="text-xs text-gray-500">(1 min)</span>
</div>
<div class="rounded-md border p-4 flex flex-col items-center cursor-pointer hover:bg-gray-50 relative">
<div
class="rounded-md border p-4 flex flex-col items-center cursor-pointer hover:bg-gray-50 relative"
>
<div class="p-4 relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-8 h-8 text-blue-500">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-8 h-8 text-blue-500"
>
<path
d="M11.25 5.337c0-.355-.186-.676-.401-.959a1.647 1.647 0 01-.349-1.003c0-1.036 1.007-1.875 2.25-1.875S15 2.34 15 3.375c0 .369-.128.713-.349 1.003-.215.283-.401.604-.401.959 0 .332.278.598.61.578 1.91-.114 3.79-.342 5.632-.676a.75.75 0 01.878.645 49.17 49.17 0 01.376 5.452.657.657 0 01-.66.664c-.354 0-.675-.186-.958-.401a1.647 1.647 0 00-1.003-.349c-1.035 0-1.875 1.007-1.875 2.25s.84 2.25 1.875 2.25c.369 0 .713-.128 1.003-.349.283-.215.604-.401.959-.401.31 0 .557.262.534.571a48.774 48.774 0 01-.595 4.845.75.75 0 01-.61.61c-1.82.317-3.673.533-5.555.642a.58.58 0 01-.611-.581c0-.355.186-.676.401-.959.221-.29.349-.634.349-1.003 0-1.035-1.007-1.875-2.25-1.875s-2.25.84-2.25 1.875c0 .369.128.713.349 1.003.215.283.401.604.401.959a.641.641 0 01-.658.643 49.118 49.118 0 01-4.708-.36.75.75 0 01-.645-.878c.293-1.614.504-3.257.629-4.924A.53.53 0 005.337 15c-.355 0-.676.186-.959.401-.29.221-.634.349-1.003.349-1.036 0-1.875-1.007-1.875-2.25s.84-2.25 1.875-2.25c.369 0 .713.128 1.003.349.283.215.604.401.959.401a.656.656 0 00.659-.663 47.703 47.703 0 00-.31-4.82.75.75 0 01.83-.832c1.343.155 2.703.254 4.077.294a.64.64 0 00.657-.642z" />
d="M11.25 5.337c0-.355-.186-.676-.401-.959a1.647 1.647 0 01-.349-1.003c0-1.036 1.007-1.875 2.25-1.875S15 2.34 15 3.375c0 .369-.128.713-.349 1.003-.215.283-.401.604-.401.959 0 .332.278.598.61.578 1.91-.114 3.79-.342 5.632-.676a.75.75 0 01.878.645 49.17 49.17 0 01.376 5.452.657.657 0 01-.66.664c-.354 0-.675-.186-.958-.401a1.647 1.647 0 00-1.003-.349c-1.035 0-1.875 1.007-1.875 2.25s.84 2.25 1.875 2.25c.369 0 .713-.128 1.003-.349.283-.215.604-.401.959-.401.31 0 .557.262.534.571a48.774 48.774 0 01-.595 4.845.75.75 0 01-.61.61c-1.82.317-3.673.533-5.555.642a.58.58 0 01-.611-.581c0-.355.186-.676.401-.959.221-.29.349-.634.349-1.003 0-1.035-1.007-1.875-2.25-1.875s-2.25.84-2.25 1.875c0 .369.128.713.349 1.003.215.283.401.604.401.959a.641.641 0 01-.658.643 49.118 49.118 0 01-4.708-.36.75.75 0 01-.645-.878c.293-1.614.504-3.257.629-4.924A.53.53 0 005.337 15c-.355 0-.676.186-.959.401-.29.221-.634.349-1.003.349-1.036 0-1.875-1.007-1.875-2.25s.84-2.25 1.875-2.25c.369 0 .713.128 1.003.349.283.215.604.401.959.401a.656.656 0 00.659-.663 47.703 47.703 0 00-.31-4.82.75.75 0 01.83-.832c1.343.155 2.703.254 4.077.294a.64.64 0 00.657-.642z"
/>
</svg>
</div>
<p class="font-medium">
Start from a template
</p>
<NuxtLink v-track.select_form_base="{ base: 'template' }" :to="{ name: 'templates' }" class="absolute inset-0" />
<NuxtLink
v-track.select_form_base="{ base: 'template' }"
:to="{ name: 'templates' }"
class="absolute inset-0"
/>
</div>
</div>
<div v-else-if="state == 'ai'">
<a class="absolute top-4 left-4" href="#" role="button" @click.prevent="state = 'default'">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-4 h-4 inline -mt-1">
<path fill-rule="evenodd"
<a
class="absolute top-4 left-4"
href="#"
role="button"
@click.prevent="state = 'default'"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-4 h-4 inline -mt-1"
>
<path
fill-rule="evenodd"
d="M7.72 12.53a.75.75 0 010-1.06l7.5-7.5a.75.75 0 111.06 1.06L9.31 12l6.97 6.97a.75.75 0 11-1.06 1.06l-7.5-7.5z"
clip-rule="evenodd" />
clip-rule="evenodd"
/>
</svg>
Back
</a>
<text-area-input label="Form Description" :disabled="loading ? true : null" :form="aiForm" name="form_prompt"
<text-area-input
label="Form Description"
:disabled="loading ? true : null"
:form="aiForm"
name="form_prompt"
help="Give us a description of the form you want to build (the more details the better)"
placeholder="A simple contact form, with a name, email and message field" />
<v-button class="w-full" :loading="loading" @click.prevent="generateForm">
placeholder="A simple contact form, with a name, email and message field"
/>
<v-button
class="w-full"
:loading="loading"
@click.prevent="generateForm"
>
Generate a form
</v-button>
<p class="text-gray-500 text-xs text-center mt-1">
@@ -89,28 +167,28 @@
<script>
export default {
emits: ['close', 'form-generated'],
props: {
show: { type: Boolean, required: true }
show: { type: Boolean, required: true },
},
emits: ["close", "form-generated"],
setup() {
return {
useAlert: useAlert(),
runtimeConfig: useRuntimeConfig()
runtimeConfig: useRuntimeConfig(),
}
},
data: () => ({
state: 'default',
state: "default",
aiForm: useForm({
form_prompt: ''
form_prompt: "",
}),
loading: false
loading: false,
}),
computed: {
aiFeaturesEnabled() {
return this.runtimeConfig.public.aiFeaturesEnabled
}
},
},
methods: {
@@ -118,39 +196,47 @@ export default {
if (this.loading) return
this.loading = true
this.aiForm.post('/forms/ai/generate').then(response => {
this.useAlert.success(response.message)
this.fetchGeneratedForm(response.ai_form_completion_id)
}).catch(error => {
console.error(error)
this.loading = false
this.state = 'ai'
})
this.aiForm
.post("/forms/ai/generate")
.then((response) => {
this.useAlert.success(response.message)
this.fetchGeneratedForm(response.ai_form_completion_id)
})
.catch((error) => {
console.error(error)
this.loading = false
this.state = "ai"
})
},
fetchGeneratedForm(generationId) {
// check every 4 seconds if form is generated
setTimeout(() => {
opnFetch('/forms/ai/' + generationId).then(data => {
if (data.ai_form_completion.status === 'completed') {
this.useAlert.success(data.message)
this.$emit('form-generated', JSON.parse(data.ai_form_completion.result))
this.$emit('close')
} else if (data.ai_form_completion.status === 'failed') {
this.useAlert.error('Something went wrong, please try again.')
this.state = 'default'
opnFetch("/forms/ai/" + generationId)
.then((data) => {
if (data.ai_form_completion.status === "completed") {
this.useAlert.success(data.message)
this.$emit(
"form-generated",
JSON.parse(data.ai_form_completion.result),
)
this.$emit("close")
} else if (data.ai_form_completion.status === "failed") {
this.useAlert.error("Something went wrong, please try again.")
this.state = "default"
this.loading = false
} else {
this.fetchGeneratedForm(generationId)
}
})
.catch((error) => {
if (error?.data?.message) {
this.useAlert.error(error.data.message)
}
this.state = "default"
this.loading = false
} else {
this.fetchGeneratedForm(generationId)
}
}).catch(error => {
if (error?.data?.message) {
this.useAlert.error(error.data.message)
}
this.state = 'default'
this.loading = false
})
})
}, 4000)
}
}
},
},
}
</script>

View File

@@ -1,14 +1,27 @@
<template>
<div>
<h3 class="font-semibold text-xl">Embed</h3>
<h3 class="font-semibold text-xl">
Embed
</h3>
<p>Embed your form on your website by copying the HTML code below.</p>
<copy-content :content="embedCode" buttonText="Copy Code">
<copy-content
:content="embedCode"
button-text="Copy Code"
>
<template #icon>
<svg class="h-4 w-4 -mt-1 text-blue-600 inline mr-1" viewBox="0 0 18 18" fill="none"
xmlns="http://www.w3.org/2000/svg">
<svg
class="h-4 w-4 -mt-1 text-blue-600 inline mr-1"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.0833 11.5L13.5833 9L11.0833 6.5M6.91667 6.5L4.41667 9L6.91667 11.5M5.5 16.5H12.5C13.9001 16.5 14.6002 16.5 15.135 16.2275C15.6054 15.9878 15.9878 15.6054 16.2275 15.135C16.5 14.6002 16.5 13.9001 16.5 12.5V5.5C16.5 4.09987 16.5 3.3998 16.2275 2.86502C15.9878 2.39462 15.6054 2.01217 15.135 1.77248C14.6002 1.5 13.9001 1.5 12.5 1.5H5.5C4.09987 1.5 3.3998 1.5 2.86502 1.77248C2.39462 2.01217 2.01217 2.39462 1.77248 2.86502C1.5 3.3998 1.5 4.09987 1.5 5.5V12.5C1.5 13.9001 1.5 14.6002 1.77248 15.135C2.01217 15.6054 2.39462 15.9878 2.86502 16.2275C3.3998 16.5 4.09987 16.5 5.5 16.5Z"
stroke="currentColor" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
stroke="currentColor"
stroke-width="1.66667"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
Copy Code
@@ -17,38 +30,48 @@
</template>
<script>
import CopyContent from '../../../open/forms/components/CopyContent.vue'
import {appUrl} from "~/lib/utils.js";
/* eslint-disable */
import CopyContent from "../../../open/forms/components/CopyContent.vue"
import { appUrl } from "~/lib/utils.js"
export default {
name: 'EmbedCode',
components: {CopyContent},
name: "EmbedCode",
components: { CopyContent },
props: {
form: {type: Object, required: true},
extraQueryParam: {type: String, default: ''}
form: { type: Object, required: true },
extraQueryParam: { type: String, default: "" },
},
data: () => ({
autoresizeIframe: false
autoresizeIframe: false,
}),
computed: {
embedCode() {
// eslint-disable no-useless-escape
return `
<script type="text/javascript" src="${appUrl('/widgets/iframeResize.min.js')}"><\/script>
<script type="text/javascript" src="${appUrl("/widgets/iframeResize.min.js")}"><\/script>
${this.iframeCode}
<script type="text/javascript">iFrameResize({log: false, checkOrigin: false}, "#${this.iframeId}");<\/script>
`
},
iframeCode() {
const share_url = (this.extraQueryParam) ? this.form.share_url + "?" + this.extraQueryParam : this.form.share_url + this.extraQueryParam
return '<iframe style="border:none;width:100%;" id="' + this.iframeId + '" src="' + share_url + '"></iframe>'
const share_url = this.extraQueryParam
? this.form.share_url + "?" + this.extraQueryParam
: this.form.share_url + this.extraQueryParam
return (
'<iframe style="border:none;width:100%;" id="' +
this.iframeId +
'" src="' +
share_url +
'"></iframe>'
)
},
iframeId() {
return 'form-' + this.form.slug
}
return "form-" + this.form.slug
},
},
methods: {}
methods: {},
}
</script>

View File

@@ -1,24 +1,47 @@
<template>
<div>
<v-button
v-track.share_embed_form_popup_click="{form_id:form.id, form_slug:form.slug}"
v-track.share_embed_form_popup_click="{
form_id: form.id,
form_slug: form.slug,
}"
class="w-full"
color="light-gray"
@click="showEmbedFormAsPopupModal=true"
@click="showEmbedFormAsPopupModal = true"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-2 text-blue-600 inline" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.25 12.76c0 1.6 1.123 2.994 2.707 3.227 1.068.157 2.148.279 3.238.364.466.037.893.281 1.153.671L12 21l2.652-3.978c.26-.39.687-.634 1.153-.67 1.09-.086 2.17-.208 3.238-.365 1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z"
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 mr-2 text-blue-600 inline"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.25 12.76c0 1.6 1.123 2.994 2.707 3.227 1.068.157 2.148.279 3.238.364.466.037.893.281 1.153.671L12 21l2.652-3.978c.26-.39.687-.634 1.153-.67 1.09-.086 2.17-.208 3.238-.365 1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z"
/>
</svg>
Embed as popup
</v-button>
<modal :show="showEmbedFormAsPopupModal" @close="onClose">
<modal
:show="showEmbedFormAsPopupModal"
@close="onClose"
>
<template #icon>
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 text-blue" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.25 12.76c0 1.6 1.123 2.994 2.707 3.227 1.068.157 2.148.279 3.238.364.466.037.893.281 1.153.671L12 21l2.652-3.978c.26-.39.687-.634 1.153-.67 1.09-.086 2.17-.208 3.238-.365 1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z"
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-10 h-10 text-blue"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.25 12.76c0 1.6 1.123 2.994 2.707 3.227 1.068.157 2.148.279 3.238.364.466.037.893.281 1.153.671L12 21l2.652-3.978c.26-.39.687-.634 1.153-.67 1.09-.086 2.17-.208 3.238-.365 1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z"
/>
</svg>
</template>
@@ -31,14 +54,18 @@
Demo
</h3>
<p class="pb-6">
A live preview of your form popup was just added to this page. <span class="font-semibold text-blue-800">Click on the button on the bottom
A live preview of your form popup was just added to this page.
<span class="font-semibold text-blue-800">Click on the button on the bottom
{{ advancedOptions.position }} corner to try it</span>.
</p>
<h3 class="border-t text-xl font-semibold mb-2 pt-6">
How does it work?
</h3>
<p>Paste the following code snippet in the <b>&lt;head&gt;</b> section of your website.</p>
<p>
Paste the following code snippet in the <b>&lt;head&gt;</b> section of
your website.
</p>
<div
class="border border-nt-blue-light bg-blue-50 dark:bg-notion-dark-light rounded-md p-4 mb-5 w-full mx-auto mt-4 select-all"
@@ -47,19 +74,32 @@
<p class="select-all text-nt-blue flex-grow break-all">
{{ embedPopupCode }}
</p>
<div class="hover:bg-nt-blue-lighter rounded transition-colors cursor-pointer" @click="copyToClipboard">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-nt-blue" fill="none" viewBox="0 0 24 24"
stroke="currentColor"
<div
class="hover:bg-nt-blue-lighter rounded transition-colors cursor-pointer"
@click="copyToClipboard"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 text-nt-blue"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"
/>
</svg>
</div>
</div>
</div>
<collapse class="py-5 w-full border rounded-md px-4" :model-value="true">
<collapse
class="py-5 w-full border rounded-md px-4"
:model-value="true"
>
<template #title>
<div class="flex">
<h3 class="font-semibold block text-lg">
@@ -68,31 +108,52 @@
</div>
</template>
<div class="border-t mt-4 -mx-4" />
<toggle-switch-input v-model="advancedOptions.hide_title" name="hide_title" class="mt-4"
label="Hide Form Title"
:disabled="(form.hide_title===true)?true:null"
:help="hideTitleHelp"
<toggle-switch-input
v-model="advancedOptions.hide_title"
name="hide_title"
class="mt-4"
label="Hide Form Title"
:disabled="form.hide_title === true ? true : null"
:help="hideTitleHelp"
/>
<color-input v-model="advancedOptions.bgcolor" name="bgcolor" class="mt-4"
label="Circle Background Color"
<color-input
v-model="advancedOptions.bgcolor"
name="bgcolor"
class="mt-4"
label="Circle Background Color"
/>
<text-input v-model="advancedOptions.emoji" name="emoji" class="mt-4"
label="Emoji" :max-char-limit="2"
<text-input
v-model="advancedOptions.emoji"
name="emoji"
class="mt-4"
label="Emoji"
:max-char-limit="2"
/>
<flat-select-input v-model="advancedOptions.position" name="position" class="mt-4"
label="Position"
:options="[
{name:'Bottom Right',value:'right'},
{name:'Bottom Left',value:'left'},
]"
<flat-select-input
v-model="advancedOptions.position"
name="position"
class="mt-4"
label="Position"
:options="[
{ name: 'Bottom Right', value: 'right' },
{ name: 'Bottom Left', value: 'left' },
]"
/>
<text-input v-model="advancedOptions.width" name="width" class="mt-4"
label="Form pop max width (px)" native-type="number"
<text-input
v-model="advancedOptions.width"
name="width"
class="mt-4"
label="Form pop max width (px)"
native-type="number"
/>
</collapse>
<div class="flex justify-end mt-4">
<v-button color="gray" shade="light" @click="onClose">
<v-button
color="gray"
shade="light"
@click="onClose"
>
Close
</v-button>
</div>
@@ -102,48 +163,58 @@
</template>
<script setup>
import { ref, defineProps, computed } from 'vue'
import {appUrl} from "~/lib/utils.js";
import { ref, defineProps, computed } from "vue"
import { appUrl } from "~/lib/utils.js"
const { copy } = useClipboard()
const crisp = useCrisp()
const props = defineProps({
form: { type: Object, required: true }
form: { type: Object, required: true },
})
const embedScriptUrl = '/widgets/embed-min.js'
let showEmbedFormAsPopupModal = ref(false)
let advancedOptions = ref({
const embedScriptUrl = "/widgets/embed-min.js"
const showEmbedFormAsPopupModal = ref(false)
const advancedOptions = ref({
hide_title: false,
emoji: '💬',
position: 'right',
bgcolor: '#3B82F6',
width: '500'
emoji: "💬",
position: "right",
bgcolor: "#3B82F6",
width: "500",
})
let hideTitleHelp = computed(() => {
return props.form.hide_title ? 'This option is disabled because the form title is already hidden' : null
const hideTitleHelp = computed(() => {
return props.form.hide_title
? "This option is disabled because the form title is already hidden"
: null
})
let shareUrl = computed(() => {
return (advancedOptions.value.hide_title) ? props.form.share_url + '?hide_title=true' : props.form.share_url
const shareUrl = computed(() => {
return advancedOptions.value.hide_title
? props.form.share_url + "?hide_title=true"
: props.form.share_url
})
let embedPopupCode = computed(() => {
const embedPopupCode = computed(() => {
const nfData = {
formurl: shareUrl.value,
emoji: advancedOptions.value.emoji,
position: advancedOptions.value.position,
bgcolor: advancedOptions.value.bgcolor,
width: advancedOptions.value.width
width: advancedOptions.value.width,
}
previewPopup(nfData)
return '<script async data-nf=\'' + JSON.stringify(nfData) + '\' src=\'' + appUrl(embedScriptUrl) + '\'></scrip' + 't>'
return (
"<script async data-nf='" +
JSON.stringify(nfData) +
"' src='" +
appUrl(embedScriptUrl) +
"'></scrip" +
"t>"
)
})
onMounted(() => {
advancedOptions.value.bgcolor = props.form.color
})
const onClose = () => {
removePreview()
crisp.showChat()
@@ -152,15 +223,15 @@ const onClose = () => {
const copyToClipboard = () => {
if (import.meta.server) return
copy(embedPopupCode.value)
useAlert().success('Copied!')
useAlert().success("Copied!")
}
const removePreview = () => {
if (import.meta.server) return
const oldP = document.head.querySelector('#nf-popup-preview')
const oldP = document.head.querySelector("#nf-popup-preview")
if (oldP) {
oldP.remove()
}
const oldM = document.body.querySelector('.nf-main')
const oldM = document.body.querySelector(".nf-main")
if (oldM) {
oldM.remove()
}
@@ -178,11 +249,11 @@ const previewPopup = (nfData) => {
crisp.hideChat()
// Add new preview
const el = document.createElement('script')
el.id = 'nf-popup-preview'
const el = document.createElement("script")
el.id = "nf-popup-preview"
el.async = true
el.src = embedScriptUrl
el.setAttribute('data-nf', JSON.stringify(nfData))
el.setAttribute("data-nf", JSON.stringify(nfData))
document.head.appendChild(el)
}
</script>

View File

@@ -1,120 +1,278 @@
<template>
<div>
<div v-if="loadingDuplicate || loadingDelete" class="pr-4 pt-2">
<div
v-if="loadingDuplicate || loadingDelete"
class="pr-4 pt-2"
>
<Loader class="h-6 w-6 mx-auto" />
</div>
<dropdown v-else class="inline">
<dropdown
v-else
class="inline"
>
<template #trigger="{ toggle }">
<v-button color="white" @click="toggle">
<svg class="w-4 h-4 inline -mt-1" viewBox="0 0 16 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<v-button
color="white"
@click="toggle"
>
<svg
class="w-4 h-4 inline -mt-1"
viewBox="0 0 16 4"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.00016 2.83366C8.4604 2.83366 8.8335 2.46056 8.8335 2.00033C8.8335 1.54009 8.4604 1.16699 8.00016 1.16699C7.53993 1.16699 7.16683 1.54009 7.16683 2.00033C7.16683 2.46056 7.53993 2.83366 8.00016 2.83366Z"
stroke="#344054" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round" />
stroke="#344054"
stroke-width="1.66667"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M13.8335 2.83366C14.2937 2.83366 14.6668 2.46056 14.6668 2.00033C14.6668 1.54009 14.2937 1.16699 13.8335 1.16699C13.3733 1.16699 13.0002 1.54009 13.0002 2.00033C13.0002 2.46056 13.3733 2.83366 13.8335 2.83366Z"
stroke="#344054" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round" />
stroke="#344054"
stroke-width="1.66667"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M2.16683 2.83366C2.62707 2.83366 3.00016 2.46056 3.00016 2.00033C3.00016 1.54009 2.62707 1.16699 2.16683 1.16699C1.70659 1.16699 1.3335 1.54009 1.3335 2.00033C1.3335 2.46056 1.70659 2.83366 2.16683 2.83366Z"
stroke="#344054" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round" />
stroke="#344054"
stroke-width="1.66667"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</v-button>
</template>
<span v-if="isMainPage && form.visibility === 'draft'"
<span
v-if="isMainPage && form.visibility === 'draft'"
class="block px-4 py-2 text-md text-gray-700 cursor-pointer 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"
@click="showDraftFormWarningNotification">
<svg class="w-4 h-4 mr-2" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 12C1 12 5 4 12 4C19 4 23 12 23 12C23 12 19 20 12 20C5 20 1 12 1 12Z" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
@click="showDraftFormWarningNotification"
>
<svg
class="w-4 h-4 mr-2"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1 12C1 12 5 4 12 4C19 4 23 12 23 12C23 12 19 20 12 20C5 20 1 12 1 12Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
View form
</span>
<a v-else-if="isMainPage" v-track.view_form_click="{ form_id: form.id, form_slug: form.slug }"
:href="form.share_url" 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">
<svg class="w-4 h-4 mr-2" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 12C1 12 5 4 12 4C19 4 23 12 23 12C23 12 19 20 12 20C5 20 1 12 1 12Z" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<a
v-else-if="isMainPage"
v-track.view_form_click="{ form_id: form.id, form_slug: form.slug }"
:href="form.share_url"
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"
>
<svg
class="w-4 h-4 mr-2"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1 12C1 12 5 4 12 4C19 4 23 12 23 12C23 12 19 20 12 20C5 20 1 12 1 12Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
View form
</a>
<nuxt-link v-if="isMainPage" v-track.edit_form_click="{ form_id: form.id, form_slug: form.slug }"
<nuxt-link
v-if="isMainPage"
v-track.edit_form_click="{ form_id: form.id, form_slug: form.slug }"
:to="{ name: 'forms-slug-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">
<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">
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"
>
<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"
stroke="currentColor" stroke-width="1.67" stroke-linecap="round" stroke-linejoin="round" />
stroke="currentColor"
stroke-width="1.67"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
Edit
</nuxt-link>
<a v-if="isMainPage" href="#"
<a
v-if="isMainPage"
href="#"
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"
@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">
@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"
>
<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" />
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
Copy link to share
</a>
<a v-track.duplicate_form_click="{ form_id: form.id, form_slug: form.slug }" href="#"
<a
v-track.duplicate_form_click="{
form_id: form.id,
form_slug: form.slug,
}"
href="#"
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"
@click.prevent="duplicateForm">
<svg class="w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 7v8a2 2 0 002 2h6M8 7V5a2 2 0 012-2h4.586a1 1 0 01.707.293l4.414 4.414a1 1 0 01.293.707V15a2 2 0 01-2 2h-2M8 7H6a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2v-2" />
@click.prevent="duplicateForm"
>
<svg
class="w-4 h-4 mr-2"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 7v8a2 2 0 002 2h6M8 7V5a2 2 0 012-2h4.586a1 1 0 01.707.293l4.414 4.414a1 1 0 01.293.707V15a2 2 0 01-2 2h-2M8 7H6a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2v-2"
/>
</svg>
Duplicate form
</a>
<a v-if="!isMainPage" v-track.create_template_click="{ form_id: form.id, form_slug: form.slug }" href="#"
<a
v-if="!isMainPage"
v-track.create_template_click="{
form_id: form.id,
form_slug: form.slug,
}"
href="#"
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"
@click.prevent="showFormTemplateModal = true">
<svg class="w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M17 14v6m-3-3h6M6 10h2a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v2a2 2 0 002 2zm10 0h2a2 2 0 002-2V6a2 2 0 00-2-2h-2a2 2 0 00-2 2v2a2 2 0 002 2zM6 20h2a2 2 0 002-2v-2a2 2 0 00-2-2H6a2 2 0 00-2 2v2a2 2 0 002 2z" />
@click.prevent="showFormTemplateModal = true"
>
<svg
class="w-4 h-4 mr-2"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17 14v6m-3-3h6M6 10h2a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v2a2 2 0 002 2zm10 0h2a2 2 0 002-2V6a2 2 0 00-2-2h-2a2 2 0 00-2 2v2a2 2 0 002 2zM6 20h2a2 2 0 002-2v-2a2 2 0 00-2-2H6a2 2 0 00-2 2v2a2 2 0 002 2z"
/>
</svg>
Create Template
</a>
<a v-track.change_workspace_click="{ form_id: form.id, form_slug: form.slug }" href="#"
<a
v-track.change_workspace_click="{
form_id: form.id,
form_slug: form.slug,
}"
href="#"
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"
@click.prevent="showFormWorkspaceModal = true">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="w-4 h-4 mr-2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.25 21h19.5m-18-18v18m10.5-18v18m6-13.5V21M6.75 6.75h.75m-.75 3h.75m-.75 3h.75m3-6h.75m-.75 3h.75m-.75 3h.75M6.75 21v-3.375c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21M3 3h12m-.75 4.5H21m-3.75 3.75h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008v-.008Z" />
@click.prevent="showFormWorkspaceModal = true"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4 mr-2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.25 21h19.5m-18-18v18m10.5-18v18m6-13.5V21M6.75 6.75h.75m-.75 3h.75m-.75 3h.75m3-6h.75m-.75 3h.75m-.75 3h.75M6.75 21v-3.375c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21M3 3h12m-.75 4.5H21m-3.75 3.75h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008v-.008Z"
/>
</svg>
Change workspace
</a>
<a v-track.delete_form_click="{ form_id: form.id, form_slug: form.slug }" href="#"
<a
v-track.delete_form_click="{ form_id: form.id, form_slug: form.slug }"
href="#"
class="block block px-4 py-2 text-md text-red-600 hover:bg-red-50 flex items-center"
@click.prevent="showDeleteFormModal = true">
<svg class="w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
@click.prevent="showDeleteFormModal = true"
>
<svg
class="w-4 h-4 mr-2"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
Delete form
</a>
</dropdown>
<!-- Delete Form Modal -->
<modal :show="showDeleteFormModal" icon-color="red" max-width="sm" @close="showDeleteFormModal = false">
<modal
:show="showDeleteFormModal"
icon-color="red"
max-width="sm"
@close="showDeleteFormModal = false"
>
<template #icon>
<svg class="w-10 h-10" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
<svg
class="w-10 h-10"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
</template>
<template #title>
@@ -122,83 +280,111 @@
</template>
<div class="p-3">
<p>
If you want to permanently delete this form and all of its data, you can do so below.
If you want to permanently delete this form and all of its data, you
can do so below.
</p>
<div class="flex mt-4">
<v-button class="sm:w-1/2 mr-4" color="white" @click.prevent="showDeleteFormModal = false">
<v-button
class="sm:w-1/2 mr-4"
color="white"
@click.prevent="showDeleteFormModal = false"
>
Cancel
</v-button>
<v-button class="sm:w-1/2" color="red" :loading="loadingDelete" @click.prevent="deleteForm">
<v-button
class="sm:w-1/2"
color="red"
:loading="loadingDelete"
@click.prevent="deleteForm"
>
Yes, delete it
</v-button>
</div>
</div>
</modal>
<form-template-modal v-if="!isMainPage && user" :form="form" :show="showFormTemplateModal"
@close="showFormTemplateModal = false" />
<form-workspace-modal v-if="user" :form="form" :show="showFormWorkspaceModal"
@close="showFormWorkspaceModal = false" />
<form-template-modal
v-if="!isMainPage && user"
:form="form"
:show="showFormTemplateModal"
@close="showFormTemplateModal = false"
/>
<form-workspace-modal
v-if="user"
:form="form"
:show="showFormWorkspaceModal"
@close="showFormWorkspaceModal = false"
/>
</div>
</template>
<script setup>
import { ref, defineProps, computed } from 'vue'
import Dropdown from '~/components/global/Dropdown.vue'
import FormTemplateModal from '../../../open/forms/components/templates/FormTemplateModal.vue'
import FormWorkspaceModal from '../../../open/forms/components/FormWorkspaceModal.vue'
import { ref, defineProps, computed } from "vue"
import Dropdown from "~/components/global/Dropdown.vue"
import FormTemplateModal from "../../../open/forms/components/templates/FormTemplateModal.vue"
import FormWorkspaceModal from "../../../open/forms/components/FormWorkspaceModal.vue"
const { copy } = useClipboard()
const router = useRouter()
const props = defineProps({
form: { type: Object, required: true },
isMainPage: { type: Boolean, required: false, default: false }
isMainPage: { type: Boolean, required: false, default: false },
})
const authStore = useAuthStore()
const formsStore = useFormsStore()
const formEndpoint = '/open/forms/{id}'
let user = computed(() => authStore.user)
const formEndpoint = "/open/forms/{id}"
const user = computed(() => authStore.user)
let loadingDuplicate = ref(false)
let loadingDelete = ref(false)
let showDeleteFormModal = ref(false)
let showFormTemplateModal = ref(false)
let showFormWorkspaceModal = ref(false)
const loadingDuplicate = ref(false)
const loadingDelete = ref(false)
const showDeleteFormModal = ref(false)
const showFormTemplateModal = ref(false)
const showFormWorkspaceModal = ref(false)
const copyLink = () => {
copy(props.form.share_url)
useAlert().success('Copied!')
useAlert().success("Copied!")
}
const duplicateForm = () => {
if (loadingDuplicate.value) return
loadingDuplicate.value = true
opnFetch(formEndpoint.replace('{id}', props.form.id) + '/duplicate', { method: 'POST' }).then((data) => {
formsStore.save(data.new_form)
router.push({ name: 'forms-slug-show', params: { slug: data.new_form.slug } })
useAlert().success(data.message)
loadingDuplicate.value = false
}).catch((error) => {
useAlert().error(error.data.message)
loadingDuplicate.value = false
opnFetch(formEndpoint.replace("{id}", props.form.id) + "/duplicate", {
method: "POST",
})
.then((data) => {
formsStore.save(data.new_form)
router.push({
name: "forms-slug-show",
params: { slug: data.new_form.slug },
})
useAlert().success(data.message)
loadingDuplicate.value = false
})
.catch((error) => {
useAlert().error(error.data.message)
loadingDuplicate.value = false
})
}
const deleteForm = () => {
if (loadingDelete.value) return
loadingDelete.value = true
opnFetch(formEndpoint.replace('{id}', props.form.id), { method: 'DELETE' }).then((data) => {
formsStore.remove(props.form)
router.push({ name: 'home' })
useAlert().success(data.message)
loadingDelete.value = false
}).catch((error) => {
useAlert().error(error.data.message)
loadingDelete.value = false
})
opnFetch(formEndpoint.replace("{id}", props.form.id), { method: "DELETE" })
.then((data) => {
formsStore.remove(props.form)
router.push({ name: "home" })
useAlert().success(data.message)
loadingDelete.value = false
})
.catch((error) => {
useAlert().error(error.data.message)
loadingDelete.value = false
})
}
const showDraftFormWarningNotification = () => {
useAlert().warning('This form is currently in Draft mode and is not publicly accessible, You can change the form status on the edit form page.')
useAlert().warning(
"This form is currently in Draft mode and is not publicly accessible, You can change the form status on the edit form page.",
)
}
</script>

View File

@@ -1,46 +1,93 @@
<template>
<v-transition>
<div v-if="hasCleanings && !hideWarning" class="border border-gray-300 dark:border-gray-600 rounded-md bg-white p-2"
:class="{'hover:bg-yellow-50 dark:hover:bg-yellow-900':!collapseOpened}"
<div
v-if="hasCleanings && !hideWarning"
class="border border-gray-300 dark:border-gray-600 rounded-md bg-white p-2"
:class="{
'hover:bg-yellow-50 dark:hover:bg-yellow-900': !collapseOpened,
}"
>
<collapse v-model="collapseOpened">
<template #title>
<p class="text-yellow-500 dark:text-yellow-400 font-semibold text-sm p-1 pr-4">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-6 h-6 inline"
<p
class="text-yellow-500 dark:text-yellow-400 font-semibold text-sm p-1 pr-4"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6 inline"
>
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
Some features that are included in our {{ form.is_pro ? 'Enterprise' : 'Pro' }} plan are disabled when
publicly sharing this form<span v-if="specifyFormOwner"> (only owners of this form can see this)</span>.
Some features that are included in our
{{ form.is_pro ? "Enterprise" : "Pro" }} plan are disabled when
publicly sharing this form<span v-if="specifyFormOwner">
(only owners of this form can see this)</span>.
</p>
</template>
<div class="border-t mt-1 p-4 pb-2 -mx-2">
<p class="text-gray-500 text-sm" v-html="cleaningContent" />
<p
class="text-gray-500 text-sm"
v-html="cleaningContent"
/>
<p class="text-gray-500 text-sm mb-4 font-semibold">
<NuxtLink :to="{name:'pricing'}">
{{ form.is_pro ? 'Upgrade your OpnForms plan today' : 'Start your free OpnForms trial' }}
<NuxtLink :to="{ name: 'pricing' }">
{{
form.is_pro
? "Upgrade your OpnForms plan today"
: "Start your free OpnForms trial"
}}
</NuxtLink>
to unlock all of our features and build powerful forms.
</p>
<div class="flex flex-wrap items-end w-full">
<div class="flex-grow flex pr-2">
<v-button v-track.upgrade_from_form_cleanings_click size="small" class="inline-block" :to="{name:'pricing'}">
{{ form.is_pro ? 'Upgrade plan' : 'Start free trial' }}
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4 inline -mt-[2px]"
<v-button
v-track.upgrade_from_form_cleanings_click
size="small"
class="inline-block"
:to="{ name: 'pricing' }"
>
{{ form.is_pro ? "Upgrade plan" : "Start free trial" }}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4 inline -mt-[2px]"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3" />
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"
/>
</svg>
</v-button>
<v-button color="white" size="small" class="ml-2" @click.prevent="openCrisp">
<v-button
color="white"
size="small"
class="ml-2"
@click.prevent="openCrisp"
>
Contact us
</v-button>
</div>
<v-button v-if="hideable" color="white" size="small" class="mt-2" @click.prevent="hideWarning=true">
<v-button
v-if="hideable"
color="white"
size="small"
class="mt-2"
@click.prevent="hideWarning = true"
>
Hide warning
</v-button>
</div>
@@ -50,57 +97,58 @@
</v-transition>
</template>
<script>
import Collapse from '~/components/global/Collapse.vue'
import VButton from '~/components/global/VButton.vue'
import VTransition from '~/components/global/transitions/VTransition.vue'
import Collapse from "~/components/global/Collapse.vue"
import VButton from "~/components/global/VButton.vue"
import VTransition from "~/components/global/transitions/VTransition.vue"
export default {
name: 'FormCleanings',
name: "FormCleanings",
components: { VTransition, VButton, Collapse },
props: {
form: { type: Object, required: true },
specifyFormOwner: { type: Boolean, default: false },
hideable: { type: Boolean, default: false }
hideable: { type: Boolean, default: false },
},
data () {
data() {
return {
collapseOpened: false,
hideWarning: false
hideWarning: false,
}
},
computed: {
hasCleanings () {
hasCleanings() {
return this.form.cleanings && Object.keys(this.form.cleanings).length > 0
},
cleanings () {
cleanings() {
return this.form.cleanings
},
cleaningContent () {
let message = ''
cleaningContent() {
let message = ""
Object.keys(this.cleanings).forEach((key) => {
let fieldName = key.charAt(0).toUpperCase() + key.slice(1)
if (fieldName !== 'Form') {
if (fieldName !== "Form") {
fieldName = '"' + fieldName + '" field'
}
let fieldInfo = '<span class="font-semibold">' + fieldName + '</span><br/><ul class=\'list-disc list-inside\'>'
let fieldInfo =
'<span class="font-semibold">' +
fieldName +
"</span><br/><ul class='list-disc list-inside'>"
this.cleanings[key].forEach((msg) => {
fieldInfo = fieldInfo + '<li>' + msg + '</li>'
fieldInfo = fieldInfo + "<li>" + msg + "</li>"
})
if (fieldInfo) {
message = message + fieldInfo + '<ul/><br/>'
message = message + fieldInfo + "<ul/><br/>"
}
})
return message
}
},
},
watch: {},
mounted () {
},
mounted() {},
methods: {
openCrisp () {
openCrisp() {
useCrisp().openAndShowChat()
}
}
},
},
}
</script>

View File

@@ -1,50 +1,58 @@
<template>
<div>
<h3 class="font-semibold text-xl">QR Code</h3>
<h3 class="font-semibold text-xl">
QR Code
</h3>
<p>Scan the QR code to open the form (Right click to copy the image)</p>
<div class="flex items-center">
<img v-if="QrUrl" :src="QrUrl" class="m-auto" />
<img
v-if="QrUrl"
:src="QrUrl"
class="m-auto"
>
</div>
</div>
</template>
<script>
import QRCode from 'qrcode'
import QRCode from "qrcode"
export default {
name: 'FormQrCode',
name: "FormQrCode",
props: {
form: { type: Object, required: true },
extraQueryParam: { type: String, default: '' }
extraQueryParam: { type: String, default: "" },
},
data () {
data() {
return {
QrUrl: null
QrUrl: null,
}
},
computed: {
shareUrl () {
return (this.extraQueryParam) ? this.form.share_url + "?" + this.extraQueryParam : this.form.share_url + this.extraQueryParam
}
shareUrl() {
return this.extraQueryParam
? this.form.share_url + "?" + this.extraQueryParam
: this.form.share_url + this.extraQueryParam
},
},
watch: {
shareUrl () {
shareUrl() {
this.generateQR()
}
},
},
mounted () {
mounted() {
this.generateQR()
},
methods: {
generateQR () {
QRCode.toDataURL(this.shareUrl).then(url => {
generateQR() {
QRCode.toDataURL(this.shareUrl).then((url) => {
this.QrUrl = url
})
}
}
},
},
}
</script>

View File

@@ -1,27 +1,49 @@
<template>
<div>
<v-button
v-track.regenerate_form_link_click="{
form_id: form.id,
form_slug: form.slug,
}"
class="w-full"
color="light-gray"
v-track.regenerate_form_link_click="{form_id:form.id, form_slug:form.slug}"
@click="showGenerateFormLinkModal=true"
@click="showGenerateFormLinkModal = true"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-2 text-blue-600 inline" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 mr-2 text-blue-600 inline"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
/>
</svg>
Regenerate link
</v-button>
<!-- Regenerate form link modal -->
<modal :show="showGenerateFormLinkModal" @close="showGenerateFormLinkModal=false">
<modal
:show="showGenerateFormLinkModal"
@close="showGenerateFormLinkModal = false"
>
<template #icon>
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 text-blue-600" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-10 h-10 text-blue-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
/>
</svg>
</template>
@@ -31,19 +53,26 @@
<div class="p-4">
<p>
You can choose between two different URL formats for your form.
<span class="font-semibold">Be careful, changing your form URL is not a reversible operation</span>.
Make sure to udpate your form URL everywhere where it's used.
<span class="font-semibold">Be careful, changing your form URL is not a reversible
operation</span>. Make sure to udpate your form URL everywhere where it's used.
</p>
<div class="border-t py-4 mt-4">
<h3 class="text-xl text-gray-700 font-semibold">
Human Readable URL
</h3>
<p>If your users are going to see this url, you might want to make nice and readable. Example:</p>
<p>
If your users are going to see this url, you might want to make nice
and readable. Example:
</p>
<p class="text-gray-600 border p-4 bg-gray-50 rounded-md mt-4">
https://opnform.com/forms/contact
</p>
<div class="text-center mt-4">
<v-button :loading="loadingNewLink" color="outline-blue" @click="regenerateLink('slug')">
<v-button
:loading="loadingNewLink"
color="outline-blue"
@click="regenerateLink('slug')"
>
Generate a Human Readable URL
</v-button>
</div>
@@ -53,41 +82,43 @@
Random ID URL
</h3>
<p>
If your user are not going to see your form url (if it's embedded), and if you prefer to have a random
non-guessable URL. Example:
If your user are not going to see your form url (if it's embedded),
and if you prefer to have a random non-guessable URL. Example:
</p>
<p class="text-gray-600 p-4 border bg-gray-50 rounded-md mt-4">
https://opnform.com/forms/b4417f9c-34ae-4421-8006-832ee47786e7
</p>
<div class="text-center mt-4">
<v-button :loading="loadingNewLink" color="outline-blue" @click="regenerateLink('uuid')">
<v-button
:loading="loadingNewLink"
color="outline-blue"
@click="regenerateLink('uuid')"
>
Generate a Random ID URL
</v-button>
</div>
</div>
</div>
</modal>
</div>
</template>
<script>
import { computed } from 'vue'
import { useFormsStore } from '../../../../stores/forms'
import { useFormsStore } from "../../../../stores/forms"
export default {
name: 'RegenerateFormLink',
name: "RegenerateFormLink",
components: {},
props: {
form: { type: Object, required: true }
form: { type: Object, required: true },
},
setup () {
const formsStore = useFormsStore()
return {
formsStore
}
},
setup() {
const formsStore = useFormsStore()
return {
formsStore,
}
},
data: () => ({
loadingNewLink: false,
@@ -95,22 +126,32 @@ export default {
}),
computed: {
formEndpoint: () => '/open/forms/{id}',
formEndpoint: () => "/open/forms/{id}",
},
methods: {
regenerateLink(option) {
if (this.loadingNewLink) return
this.loadingNewLink = true
opnFetch(this.formEndpoint.replace('{id}', this.form.id) + '/regenerate-link/' + option, {method:'PUT'}).then((data) => {
this.formsStore.save(data.form)
this.$router.push({name: 'forms-slug-show-share', params: {slug: data.form.slug}})
useAlert().success(data.message)
this.loadingNewLink = false
}).finally(() => {
this.showGenerateFormLinkModal = false
})
opnFetch(
this.formEndpoint.replace("{id}", this.form.id) +
"/regenerate-link/" +
option,
{ method: "PUT" },
)
.then((data) => {
this.formsStore.save(data.form)
this.$router.push({
name: "forms-slug-show-share",
params: { slug: data.form.slug },
})
useAlert().success(data.message)
this.loadingNewLink = false
})
.finally(() => {
this.showGenerateFormLinkModal = false
})
},
}
},
}
</script>

View File

@@ -1,44 +1,58 @@
<template>
<div>
<h3 class="font-semibold text-xl">Share Link</h3>
<p>Your form is now published and ready to be shared with the world! Copy this link to share your form
on social media, messaging apps or via email.</p>
<copy-content :content="share_url" :is-draft="form.visibility=='draft'">
<template #icon>
<svg class="h-4 w-4 -mt-1 text-blue-600 inline mr-1" viewBox="0 0 20 10" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M7.49984 9.16634H5.83317C3.53198 9.16634 1.6665 7.30086 1.6665 4.99967C1.6665 2.69849 3.53198 0.833008 5.83317 0.833008H7.49984M12.4998 9.16634H14.1665C16.4677 9.16634 18.3332 7.30086 18.3332 4.99967C18.3332 2.69849 16.4677 0.833008 14.1665 0.833008H12.4998M5.83317 4.99967L14.1665 4.99968"
stroke="currentColor" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</template>
Copy Link
</copy-content>
<h3 class="font-semibold text-xl">
Share Link
</h3>
<p>
Your form is now published and ready to be shared with the world! Copy
this link to share your form on social media, messaging apps or via email.
</p>
<copy-content
:content="share_url"
:is-draft="form.visibility == 'draft'"
>
<template #icon>
<svg
class="h-4 w-4 -mt-1 text-blue-600 inline mr-1"
viewBox="0 0 20 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.49984 9.16634H5.83317C3.53198 9.16634 1.6665 7.30086 1.6665 4.99967C1.6665 2.69849 3.53198 0.833008 5.83317 0.833008H7.49984M12.4998 9.16634H14.1665C16.4677 9.16634 18.3332 7.30086 18.3332 4.99967C18.3332 2.69849 16.4677 0.833008 14.1665 0.833008H12.4998M5.83317 4.99967L14.1665 4.99968"
stroke="currentColor"
stroke-width="1.66667"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
Copy Link
</copy-content>
</div>
</template>
<script>
import CopyContent from '../../../open/forms/components/CopyContent.vue'
import CopyContent from "../../../open/forms/components/CopyContent.vue"
export default {
name: 'ShareLink',
components: { CopyContent },
props: {
form: { type: Object, required: true },
extraQueryParam: { type: String, default: '' }
name: "ShareLink",
components: { CopyContent },
props: {
form: { type: Object, required: true },
extraQueryParam: { type: String, default: "" },
},
data: () => ({}),
computed: {
share_url() {
return this.extraQueryParam
? this.form.share_url + "?" + this.extraQueryParam
: this.form.share_url + this.extraQueryParam
},
},
data: () => ({
}),
computed: {
share_url () {
return (this.extraQueryParam) ? this.form.share_url + '?' + this.extraQueryParam : this.form.share_url + this.extraQueryParam
}
},
methods: {}
methods: {},
}
</script>

View File

@@ -1,25 +1,48 @@
<template>
<div class="flex">
<v-button
v-track.url_form_prefill_click="{
form_id: form.id,
form_slug: form.slug,
}"
class="w-full"
color="light-gray"
v-track.url_form_prefill_click="{form_id:form.id, form_slug:form.slug}"
@click="showUrlFormPrefillModal=true"
@click="showUrlFormPrefillModal = true"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-2 text-blue-600 inline" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M17 16v2a2 2 0 01-2 2H5a2 2 0 01-2-2v-7a2 2 0 012-2h2m3-4H9a2 2 0 00-2 2v7a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-1m-1 4l-3 3m0 0l-3-3m3 3V3"
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 mr-2 text-blue-600 inline"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 16v2a2 2 0 01-2 2H5a2 2 0 01-2-2v-7a2 2 0 012-2h2m3-4H9a2 2 0 00-2 2v7a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-1m-1 4l-3 3m0 0l-3-3m3 3V3"
/>
</svg>
Url pre-fill
</v-button>
<modal :show="showUrlFormPrefillModal" @close="showUrlFormPrefillModal=false">
<modal
:show="showUrlFormPrefillModal"
@close="showUrlFormPrefillModal = false"
>
<template #icon>
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 text-blue" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M17 16v2a2 2 0 01-2 2H5a2 2 0 01-2-2v-7a2 2 0 012-2h2m3-4H9a2 2 0 00-2 2v7a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-1m-1 4l-3 3m0 0l-3-3m3 3V3"
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-10 h-10 text-blue"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 16v2a2 2 0 01-2 2H5a2 2 0 01-2-2v-7a2 2 0 012-2h2m3-4H9a2 2 0 00-2 2v7a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-1m-1 4l-3 3m0 0l-3-3m3 3V3"
/>
</svg>
</template>
@@ -27,10 +50,15 @@
<span>Url Form Prefill</span>
</template>
<div class="p-4" ref="content">
<div
ref="content"
class="p-4"
>
<p>
Create dynamic links when sharing your form (whether it's embedded or not), that allows you to prefill
your form fields. You can use this to personalize the form when sending it to multiple contacts for instance.
Create dynamic links when sharing your form (whether it's embedded or
not), that allows you to prefill your form fields. You can use this to
personalize the form when sending it to multiple contacts for
instance.
</p>
<h3 class="mt-6 border-t text-xl font-semibold mb-4 pt-6">
@@ -38,13 +66,25 @@
</h3>
<p>
Complete your form below and fill only the fields you want to prefill. You can even leave the required fields empty.
Complete your form below and fill only the fields you want to prefill.
You can even leave the required fields empty.
</p>
<div class="rounded-lg p-5 bg-gray-100 dark:bg-gray-900 mt-4">
<open-form v-if="form" :theme="theme" :loading="false" :show-hidden="true" :form="form" :fields="form.properties" @submit="generateUrl">
<template #submit-btn="{submitForm}">
<v-button class="mt-2 px-8 mx-1" @click.prevent="submitForm">
<open-form
v-if="form"
:theme="theme"
:loading="false"
:show-hidden="true"
:form="form"
:fields="form.properties"
@submit="generateUrl"
>
<template #submit-btn="{ submitForm }">
<v-button
class="mt-2 px-8 mx-1"
@click.prevent="submitForm"
>
Generate Pre-filled URL
</v-button>
</template>
@@ -55,26 +95,28 @@
<h3 class="mt-6 text-xl font-semibold mb-4 pt-6">
Your Prefill url
</h3>
<form-url-prefill :form="form" :form-data="prefillFormData" :extra-query-param="extraQueryParam" />
<form-url-prefill
:form="form"
:form-data="prefillFormData"
:extra-query-param="extraQueryParam"
/>
</template>
</div>
</modal>
</div>
</template>
<script>
import FormUrlPrefill from '../../../open/forms/components/FormUrlPrefill.vue'
import ProTag from '~/components/global/ProTag.vue'
import OpenForm from '../../../open/forms/OpenForm.vue'
import { themes } from '~/lib/forms/form-themes.js'
import FormUrlPrefill from "../../../open/forms/components/FormUrlPrefill.vue"
import OpenForm from "../../../open/forms/OpenForm.vue"
import { themes } from "~/lib/forms/form-themes.js"
export default {
name: 'UrlFormPrefill',
components: { FormUrlPrefill, ProTag, OpenForm },
name: "UrlFormPrefill",
components: { FormUrlPrefill, OpenForm },
props: {
form: { type: Object, required: true },
extraQueryParam: { type: String, default: '' }
extraQueryParam: { type: String, default: "" },
},
data: () => ({
@@ -86,14 +128,15 @@ export default {
computed: {},
methods: {
generateUrl (formData, onFailure) {
generateUrl(formData) {
this.prefillFormData = formData
this.$nextTick().then(() => {
if (this.$refs.content) {
this.$refs.content.parentElement.parentElement.parentElement.scrollTop = this.$refs.content.offsetHeight
this.$refs.content.parentElement.parentElement.parentElement.scrollTop =
this.$refs.content.offsetHeight
}
})
}
}
},
},
}
</script>

View File

@@ -1,9 +1,31 @@
<template>
<modal :show="show" max-width="lg" @close="close">
<text-input ref="companyName" label="Company Name" name="name" :required="true" :form="form" help="Name that will appear on invoices" />
<text-input label="Email" name="email" native-type="email" :required="true" :form="form" help="Where invoices will be sent" />
<v-button :loading="form.busy || loading" :disabled="(form.busy || loading)?true:null" class="mt-6 block mx-auto"
:arrow="true" @click="saveDetails"
<modal
:show="show"
max-width="lg"
@close="close"
>
<text-input
ref="companyName"
label="Company Name"
name="name"
:required="true"
:form="form"
help="Name that will appear on invoices"
/>
<text-input
label="Email"
name="email"
native-type="email"
:required="true"
:form="form"
help="Where invoices will be sent"
/>
<v-button
:loading="form.busy || loading"
:disabled="form.busy || loading ? true : null"
class="mt-6 block mx-auto"
:arrow="true"
@click="saveDetails"
>
Go to checkout
</v-button>
@@ -11,88 +33,98 @@
</template>
<script>
import { computed } from 'vue'
import TextInput from '../../forms/TextInput.vue'
import VButton from '~/components/global/VButton.vue'
import { computed } from "vue"
import TextInput from "../../forms/TextInput.vue"
import VButton from "~/components/global/VButton.vue"
export default {
components: { VButton, TextInput },
props: {
show: {
type: Boolean,
default: false
default: false,
},
plan: {
type: String,
default: 'pro'
default: "pro",
},
yearly: {
type: Boolean,
default: true
}
default: true,
},
},
emits: ['close'],
setup () {
setup() {
const authStore = useAuthStore()
return {
user: computed(() => authStore.user)
user: computed(() => authStore.user),
}
},
data: () => ({
form: useForm({
name: '',
email: ''
name: "",
email: "",
}),
loading: false
loading: false,
}),
computed: {},
watch: {
user () {
user() {
this.updateUser()
},
show () {
show() {
// Wait for modal to open and focus on first field
setTimeout(() => {
if (this.$refs.companyName) {
this.$refs.companyName.$el.querySelector('input').focus()
this.$refs.companyName.$el.querySelector("input").focus()
}
}, 300)
this.loading = false
}
},
},
mounted () {
mounted() {
this.updateUser()
},
methods: {
updateUser () {
updateUser() {
if (this.user) {
this.form.name = this.user.name
this.form.email = this.user.email
}
},
saveDetails () {
saveDetails() {
if (this.form.busy) return
this.form.put('subscription/update-customer-details').then(() => {
this.form.put("subscription/update-customer-details").then(() => {
this.loading = true
opnFetch('/subscription/new/' + this.plan + '/' + (!this.yearly ? 'monthly' : 'yearly') + '/checkout/with-trial').then((data) => {
window.location = data.checkout_url
}).catch((error) => {
useAlert().error(error.data.message)
}).finally(() => {
this.loading = false
this.close()
})
opnFetch(
"/subscription/new/" +
this.plan +
"/" +
(!this.yearly ? "monthly" : "yearly") +
"/checkout/with-trial",
)
.then((data) => {
window.location = data.checkout_url
})
.catch((error) => {
useAlert().error(error.data.message)
})
.finally(() => {
this.loading = false
this.close()
})
})
},
close () {
this.$emit('close')
}
}
close() {
this.$emit("close")
},
},
}
</script>

View File

@@ -1,17 +1,27 @@
<template>
<div class="border relative max-w-5xl mx-auto mt-4 lg:mt-10">
<div class="w-full">
<div class="rounded-lg bg-gray-50 dark:bg-gray-800 px-6 py-8 sm:p-10 lg:flex lg:items-center">
<div
class="rounded-lg bg-gray-50 dark:bg-gray-800 px-6 py-8 sm:p-10 lg:flex lg:items-center"
>
<div class="flex-1">
<h3 class="inline-flex px-4 py-1 rounded-full text-md font-bold tracking-wide uppercase bg-white text-gray-800">
<h3
class="inline-flex px-4 py-1 rounded-full text-md font-bold tracking-wide uppercase bg-white text-gray-800"
>
Custom plan
</h3>
<div class="mt-4 text-md text-gray-600 dark:text-gray-400">
Get a custom file upload limit, enterprise-level support, custom contract, dedicated application instance in a specific region, payment via invoice/PO etc.
Get a custom file upload limit, enterprise-level support, custom
contract, dedicated application instance in a specific region,
payment via invoice/PO etc.
</div>
</div>
<div class="mt-6 rounded-md lg:mt-0 lg:ml-10 lg:flex-shrink-0">
<v-button color="white" class="w-full mt-4" @click.prevent="customPlanClick">
<v-button
color="white"
class="w-full mt-4"
@click.prevent="customPlanClick"
>
Contact us
</v-button>
</div>
@@ -22,7 +32,7 @@
<script>
export default {
name: 'CustomPlan',
name: "CustomPlan",
components: {},
props: {},
@@ -31,9 +41,11 @@ export default {
computed: {},
methods: {
customPlanClick () {
useCrisp().sendTextMessage('Hi, I would like to discuss about a custom plan')
}
}
customPlanClick() {
useCrisp().sendTextMessage(
"Hi, I would like to discuss about a custom plan",
)
},
},
}
</script>

View File

@@ -3,16 +3,19 @@
<button class="font-semibold block flex-grow cursor-pointer">
<div
class="p-2 px-3 rounded-lg transition-colors"
:class="{'bg-blue-500 text-white': !modelValue}"
:class="{ 'bg-blue-500 text-white': !modelValue }"
@click="set(false)"
>
Monthly
</div>
</button>
<button class="font-semibold block flex-grow cursor-pointer" @click="set(true)">
<button
class="font-semibold block flex-grow cursor-pointer"
@click="set(true)"
>
<div
class="p-2 px-4 rounded-lg transition-colors"
:class="{'bg-blue-500 text-white': modelValue}"
:class="{ 'bg-blue-500 text-white': modelValue }"
>
Yearly
</div>
@@ -24,14 +27,14 @@
</template>
<script setup>
import { defineEmits, defineProps } from 'vue'
import { defineEmits, defineProps } from "vue"
defineProps({
modelValue: { type: Boolean, required: true }
modelValue: { type: Boolean, required: true },
})
const emit = defineEmits()
const emit = defineEmits(['update:modelValue'])
const set = (value) => {
emit('update:modelValue', value)
emit("update:modelValue", value)
}
</script>

View File

@@ -1,54 +1,99 @@
<template>
<div class="w-full">
<section class="relative">
<div v-if="!homePage" class="absolute inset-0 grid" aria-hidden="true">
<div
v-if="!homePage"
class="absolute inset-0 grid"
aria-hidden="true"
>
<div class="bg-gray-100" />
<div class="bg-white" />
</div>
<div class="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="max-w-5xl mx-auto bg-white shadow-xl rounded-3xl ring-1 ring-gray-200 lg:flex isolate">
<div
class="max-w-5xl mx-auto bg-white shadow-xl rounded-3xl ring-1 ring-gray-200 lg:flex isolate"
>
<div class="p-8 sm:p-8 lg:flex-auto">
<h3 v-if="homePage" class="text-3xl font-semibold tracking-tight text-gray-950">
<h3
v-if="homePage"
class="text-3xl font-semibold tracking-tight text-gray-950"
>
Check out our
<span class="ml-2 text-nt-blue">
<svg class="inline w-10 h-10" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
class="inline w-10 h-10"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g opacity="0.12">
<path d="M15.9998 27.3333L10.6665 12H21.3332L15.9998 27.3333Z" fill="currentColor" />
<path d="M13.3332 4H9.33317L2.6665 12L10.6665 12L13.3332 4Z" fill="currentColor" />
<path d="M18.6665 4H22.6665L29.3332 12L21.3332 12L18.6665 4Z" fill="currentColor" />
<path
d="M15.9998 27.3333L10.6665 12H21.3332L15.9998 27.3333Z"
fill="currentColor"
/>
<path
d="M13.3332 4H9.33317L2.6665 12L10.6665 12L13.3332 4Z"
fill="currentColor"
/>
<path
d="M18.6665 4H22.6665L29.3332 12L21.3332 12L18.6665 4Z"
fill="currentColor"
/>
</g>
<path
d="M3.33345 12H28.6668M13.3334 4L10.6668 12L16.0001 27.3333L21.3334 12L18.6668 4M16.8195 27.0167L28.7644 12.6829C28.9668 12.4399 29.0681 12.3185 29.1067 12.1829C29.1408 12.0633 29.1408 11.9367 29.1067 11.8171C29.0681 11.6815 28.9668 11.5601 28.7644 11.3171L22.9866 4.3838C22.8691 4.24273 22.8103 4.17219 22.7382 4.12148C22.6744 4.07654 22.6031 4.04318 22.5277 4.02289C22.4426 4 22.3508 4 22.1672 4H9.83305C9.64941 4 9.55758 4 9.4725 4.02289C9.39711 4.04318 9.32586 4.07654 9.26202 4.12148C9.18996 4.17219 9.13118 4.24273 9.01361 4.3838L3.23583 11.3171C3.0334 11.5601 2.93218 11.6815 2.8935 11.8171C2.85939 11.9366 2.85939 12.0633 2.8935 12.1829C2.93218 12.3185 3.0334 12.4399 3.23583 12.6829L15.1807 27.0167C15.4621 27.3544 15.6028 27.5232 15.7713 27.5848C15.919 27.6388 16.0812 27.6388 16.229 27.5848C16.3974 27.5232 16.5381 27.3544 16.8195 27.0167Z"
stroke="currentColor" stroke-width="2.66667" stroke-linecap="round" stroke-linejoin="round"
stroke="currentColor"
stroke-width="2.66667"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
Pro Features
</span>
</h3>
<h3 v-else class="text-3xl font-semibold tracking-tight text-gray-950">
<h3
v-else
class="text-3xl font-semibold tracking-tight text-gray-950"
>
Pro Plan
</h3>
<p class="mt-2 text-base font-medium leading-7 text-gray-600">
OpnForm Pro offers empowering features tailored to the advanced needs of teams and creators. Enjoy our
free 3-day trial!
OpnForm Pro offers empowering features tailored to the advanced
needs of teams and creators. Enjoy our free 3-day trial!
</p>
<div class="flex items-center mt-6 gap-x-4">
<h4 class="flex-none text-sm font-semibold leading-6 tracking-widest text-gray-400 uppercase">
<h4
class="flex-none text-sm font-semibold leading-6 tracking-widest text-gray-400 uppercase"
>
What's included
</h4>
<div class="flex-auto h-px bg-gray-200" />
</div>
<ul role="list"
class="grid grid-cols-1 gap-4 mt-4 text-sm font-medium leading-6 text-gray-900 sm:grid-cols-2 sm:gap-x-6 sm:gap-y-2"
<ul
role="list"
class="grid grid-cols-1 gap-4 mt-4 text-sm font-medium leading-6 text-gray-900 sm:grid-cols-2 sm:gap-x-6 sm:gap-y-2"
>
<li v-for="(title, i) in pricingInfo" :key="i" class="flex gap-x-3">
<svg aria-hidden="true" class="w-5 h-5 shrink-0 stroke-blue-600" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg"
<li
v-for="(title, i) in pricingInfo"
:key="i"
class="flex gap-x-3"
>
<svg
aria-hidden="true"
class="w-5 h-5 shrink-0 stroke-blue-600"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M20 6L9 17L4 12" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path
d="M20 6L9 17L4 12"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
{{ title }}
</li>
@@ -56,17 +101,22 @@
</ul>
</div>
<div class="p-2 -mt-2 flex-col lg:mt-0 lg:w-full lg:max-w-md lg:flex-shrink-0">
<div
class="p-2 -mt-2 flex-col lg:mt-0 lg:w-full lg:max-w-md lg:flex-shrink-0"
>
<div
class="grow h-full py-10 text-center rounded-2xl bg-gray-50 ring-1 ring-inset ring-gray-900/5 lg:flex lg:flex-col lg:justify-center lg:py-12"
>
<div class="max-w-xs px-8 mx-auto space-y-6">
<div class="flex items-center justify-center mb-10">
<monthly-yearly-selector v-model="isYearly" />
</div><!-- lg+ -->
</div>
<!-- lg+ -->
<p class="flex flex-col items-center">
<span class="text-6xl font-semibold tracking-tight text-gray-950">
<span
class="text-6xl font-semibold tracking-tight text-gray-950"
>
<template v-if="isYearly">$16</template>
<template v-else>$19</template>
</span>
@@ -76,20 +126,37 @@
</p>
<div class="flex justify-center">
<v-button v-if="!authenticated" class="mr-1" :to="{ name: 'register' }" :arrow="true">
<v-button
v-if="!authenticated"
class="mr-1"
:to="{ name: 'register' }"
:arrow="true"
>
Start free trial
</v-button>
<v-button v-else-if="authenticated && user.is_subscribed" class="mr-1" :arrow="true"
@click.prevent="openBilling"
<v-button
v-else-if="authenticated && user.is_subscribed"
class="mr-1"
:arrow="true"
@click.prevent="openBilling"
>
View Billing
</v-button>
<v-button v-else class="mr-1" :arrow="true" @click.prevent="openCustomerCheckout('default')">
<v-button
v-else
class="mr-1"
:arrow="true"
@click.prevent="openCustomerCheckout('default')"
>
Start free trial
</v-button>
</div>
<p v-if="!homePage" class="text-xs font-medium leading-5 text-gray-600">
Invoices and receipts available for easy company reimbursement.
<p
v-if="!homePage"
class="text-xs font-medium leading-5 text-gray-600"
>
Invoices and receipts available for easy company
reimbursement.
</p>
</div>
</div>
@@ -100,18 +167,21 @@
<custom-plan v-if="!homePage" />
<checkout-details-modal :show="showDetailsModal" :yearly="isYearly" :plan="selectedPlan"
@close="showDetailsModal=false"
<checkout-details-modal
:show="showDetailsModal"
:yearly="isYearly"
:plan="selectedPlan"
@close="showDetailsModal = false"
/>
</div>
</template>
<script>
import { computed } from 'vue'
import { useAuthStore } from '../../../stores/auth'
import MonthlyYearlySelector from './MonthlyYearlySelector.vue'
import CheckoutDetailsModal from './CheckoutDetailsModal.vue'
import CustomPlan from './CustomPlan.vue'
import { computed } from "vue"
import { useAuthStore } from "../../../stores/auth"
import MonthlyYearlySelector from "./MonthlyYearlySelector.vue"
import CheckoutDetailsModal from "./CheckoutDetailsModal.vue"
import CustomPlan from "./CustomPlan.vue"
MonthlyYearlySelector.compatConfig = { MODE: 3 }
@@ -119,54 +189,54 @@ export default {
components: {
MonthlyYearlySelector,
CheckoutDetailsModal,
CustomPlan
CustomPlan,
},
props: {
homePage: {
type: Boolean,
default: false
}
default: false,
},
},
setup () {
setup() {
const authStore = useAuthStore()
return {
authenticated : computed(() => authStore.check),
user : computed(() => authStore.user)
authenticated: computed(() => authStore.check),
user: computed(() => authStore.user),
}
},
data: () => ({
isYearly: true,
selectedPlan: 'pro',
selectedPlan: "pro",
showDetailsModal: false,
pricingInfo: [
'Form confirmation emails',
'Slack notifications',
'Discord notifications',
'Editable submissions',
'1 Custom domain',
'Custom code',
'Larger file uploads (50mb)',
'Remove OpnForm branding',
'Priority support'
]
"Form confirmation emails",
"Slack notifications",
"Discord notifications",
"Editable submissions",
"1 Custom domain",
"Custom code",
"Larger file uploads (50mb)",
"Remove OpnForm branding",
"Priority support",
],
}),
computed: {},
methods: {
openCustomerCheckout (plan) {
openCustomerCheckout(plan) {
this.selectedPlan = plan
this.showDetailsModal = true
},
openBilling () {
openBilling() {
this.billingLoading = true
opnFetch('/subscription/billing-portal').then((data) => {
opnFetch("/subscription/billing-portal").then((data) => {
this.billingLoading = false
const url = data.portal_url
window.location = url
})
}
},
},
computed: {}
}
</script>

View File

@@ -1,15 +1,26 @@
<template>
<div v-if="template" class="relative group">
<div v-if="template.is_new" class="absolute top-0 right-0 p-3 z-10">
<div
v-if="template"
class="relative group"
>
<div
v-if="template.is_new"
class="absolute top-0 right-0 p-3 z-10"
>
<span
class="inline-flex items-center gap-1 rounded-full bg-blue-500 px-2 py-1 text-xs font-medium text-white"
>
<svg aria-hidden="true" class="h-3 w-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor"
<svg
aria-hidden="true"
class="h-3 w-3"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<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"
clip-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"
clip-rule="evenodd"
/>
</svg>
New
@@ -17,9 +28,13 @@
</div>
<div class="aspect-[4/3] rounded-lg shadow-sm overflow-hidden">
<img class="group-hover:scale-110 transition-all duration-200 h-full object-cover w-full" v-if="template.image_url"
:src="template.image_url" alt="" width="450px"
/>
<img
v-if="template.image_url"
class="group-hover:scale-110 transition-all duration-200 h-full object-cover w-full"
:src="template.image_url"
alt=""
width="450px"
>
</div>
<p
class="text-lg font-semibold leading-tight tracking-tight text-gray-900 mt-4 group-hover:text-blue-500 transition-all duration-150"
@@ -29,32 +44,39 @@
<p class="line-clamp-2 mt-2 text-sm font-normal text-gray-600">
{{ cleanQuotes(template.short_description) }}
</p>
<template-tags :template="template"
class="flex mt-4 items-center flex-wrap gap-3"
<template-tags
:template="template"
class="flex mt-4 items-center flex-wrap gap-3"
/>
<NuxtLink :to="{params:{slug:template.slug},name:'templates-slug'}" title="">
<span class="absolute inset-0" aria-hidden="true" />
<NuxtLink
:to="{ params: { slug: template.slug }, name: 'templates-slug' }"
title=""
>
<span
class="absolute inset-0"
aria-hidden="true"
/>
</NuxtLink>
</div>
</template>
<script>
import TemplateTags from './TemplateTags.vue'
import TemplateTags from "./TemplateTags.vue"
export default {
components: { TemplateTags },
props: {
template: {
type: Object
}
type: Object,
},
},
methods: {
cleanQuotes (str) {
cleanQuotes(str) {
// Remove starting and ending quotes if any
return (str) ? str.replace(/^"/, '').replace(/"$/, '') : ''
}
}
return str ? str.replace(/^"/, "").replace(/"$/, "") : ""
},
},
}
</script>

View File

@@ -1,59 +1,76 @@
<template>
<div>
<template v-if="displayAll">
<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"
<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"
>
<svg aria-hidden="true" class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor"
<svg
aria-hidden="true"
class="w-3 h-3"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<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"
clip-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"
clip-rule="evenodd"
/>
</svg>
New
</span>
<span v-for="item in types" :key="item.slug"
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"
<span
v-for="item in types"
:key="item.slug"
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.name }}
</span>
<span v-for="item in industries" :key="item.slug"
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"
<span
v-for="item in industries"
:key="item.slug"
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.name }}
</span>
</template>
<template v-else>
<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"
<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"
>
{{ types[0].name }} <template v-if="types.length > 1">+{{ types.length - 1 }}</template>
{{ types[0].name }}
<template v-if="types.length > 1">+{{ types.length - 1 }}</template>
</span>
<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"
<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"
>
{{ industries[0].name }} <template v-if="industries.length > 1">+{{ industries.length - 1 }}</template>
{{ industries[0].name }}
<template v-if="industries.length > 1">+{{ industries.length - 1 }}</template>
</span>
</template>
</div>
</template>
<script setup>
const props = defineProps({
template: {
type: Object,
required: true
},
displayAll: {
type: Boolean,
default: false
}
})
template: {
type: Object,
required: true,
},
displayAll: {
type: Boolean,
default: false,
},
})
const templatesStore = useTemplatesStore()
const types = computed(() => templatesStore.getTemplateTypes(props.template.types))
const industries = computed(() => templatesStore.getTemplateIndustries(props.template.industries))
const types = computed(() =>
templatesStore.getTemplateTypes(props.template.types),
)
const industries = computed(() =>
templatesStore.getTemplateIndustries(props.template.industries),
)
</script>

View File

@@ -2,56 +2,101 @@
<div>
<section class="bg-white py-12">
<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 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" v-if="filterTypes"
:options="typesOptions" class="w-full sm:w-auto md:w-56"
<select-input
v-if="filterTypes"
v-model="selectedType"
name="type"
:options="typesOptions"
class="w-full sm:w-auto md:w-56"
/>
</div>
<div class="flex-1 sm:flex-none">
<select-input v-model="selectedIndustry" name="industry" v-if="filterIndustries"
:options="industriesOptions" class="w-full sm:w-auto md:w-56"
<select-input
v-if="filterIndustries"
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 autocomplete="off" name="search" v-model="search" placeholder="Search..."/>
<text-input
v-model="search"
autocomplete="off"
name="search"
placeholder="Search..."
/>
</div>
</div>
<div v-if="loading" class="text-center mt-4">
<Loader class="h-6 w-6 text-nt-blue mx-auto"/>
<div
v-if="loading"
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">
<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" :template="template"/>
<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"
:template="template"
/>
</div>
</div>
</div>
</section>
<slot name="before-lists"/>
<slot name="before-lists" />
<section class="py-12 bg-white border-t border-gray-200 sm:py-16" v-if="showTypes">
<section
v-if="showTypes"
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">
<h4
class="text-xl font-bold tracking-tight text-gray-900 sm:text-2xl"
>
All Types
</h4>
<v-button :to="{name:'templates'}" color="white" size="small" :arrow="true" v-if="$route.name !== 'templates'">
<v-button
v-if="$route.name !== 'templates'"
:to="{ name: 'templates' }"
color="white"
size="small"
:arrow="true"
>
View All Templates
</v-button>
</div>
<div class="grid grid-cols-1 gap-x-8 gap-y-4 mt-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<NuxtLink v-for="row in types" :key="row.slug"
:to="{params: {slug:row.slug}, name:'templates-types-slug'}"
:title="row.name"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
<div
class="grid grid-cols-1 gap-x-8 gap-y-4 mt-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
>
<NuxtLink
v-for="row in types"
:key="row.slug"
:to="{ params: { slug: row.slug }, name: 'templates-types-slug' }"
:title="row.name"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
>
{{ row.name }}
</NuxtLink>
@@ -59,22 +104,40 @@
</div>
</section>
<section class="py-12 bg-white border-t border-gray-200 sm:py-16" v-if="showIndustries">
<section
v-if="showIndustries"
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">
<h4
class="text-xl font-bold tracking-tight text-gray-900 sm:text-2xl"
>
All Industries
</h4>
<v-button :to="{name:'templates'}" color="white" size="small" :arrow="true" v-if="$route.name !== 'templates'">
<v-button
v-if="$route.name !== 'templates'"
:to="{ name: 'templates' }"
color="white"
size="small"
:arrow="true"
>
View All Templates
</v-button>
</div>
<div class="grid grid-cols-1 gap-x-8 gap-y-4 mt-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<NuxtLink v-for="row in industries" :key="row.slug"
:to="{params:{slug:row.slug}, name:'templates-industries-slug'}"
:title="row.name"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
<div
class="grid grid-cols-1 gap-x-8 gap-y-4 mt-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
>
<NuxtLink
v-for="row in industries"
:key="row.slug"
:to="{
params: { slug: row.slug },
name: 'templates-industries-slug',
}"
:title="row.name"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
>
{{ row.name }}
</NuxtLink>
@@ -85,112 +148,127 @@
</template>
<script>
import {computed} from 'vue'
import Fuse from 'fuse.js'
import SingleTemplate from './SingleTemplate.vue'
import {refDebounced} from "@vueuse/core";
import { computed } from "vue"
import Fuse from "fuse.js"
import SingleTemplate from "./SingleTemplate.vue"
import { refDebounced } from "@vueuse/core"
export default {
name: 'TemplatesList',
components: {SingleTemplate},
name: "TemplatesList",
components: { SingleTemplate },
props: {
templates: {
type: Array,
required: true
required: true,
},
loading: {
type: Boolean,
default: false
default: false,
},
showTypes: {
type: Boolean,
default: true
default: true,
},
filterTypes: {
type: Boolean,
default: true
default: true,
},
showIndustries: {
type: Boolean,
default: true
default: true,
},
filterIndustries: {
type: Boolean,
default: true
}
default: true,
},
},
setup() {
const authStore = useAuthStore()
const templatesStore = useTemplatesStore()
const search = ref('')
const search = ref("")
const debouncedSearch = refDebounced(search, 500)
return {
search,
debouncedSearch,
user: computed(() => authStore.user),
industries: computed(() => [...templatesStore.industries.values()]),
types: computed(() => [...templatesStore.types.values()])
types: computed(() => [...templatesStore.types.values()]),
}
},
data: () => ({
selectedType: 'all',
selectedIndustry: 'all',
selectedType: "all",
selectedIndustry: "all",
}),
computed: {
industriesOptions() {
return [{name: 'All Industries', value: 'all'}].concat(Object.values(this.industries).map((industry) => {
return {
name: industry.name,
value: industry.slug
}
}))
return [{ name: "All Industries", value: "all" }].concat(
Object.values(this.industries).map((industry) => {
return {
name: industry.name,
value: industry.slug,
}
}),
)
},
typesOptions() {
return [{name: 'All Types', value: 'all'}].concat(Object.values(this.types).map((type) => {
return {
name: type.name,
value: type.slug
}
}))
return [{ name: "All Types", value: "all" }].concat(
Object.values(this.types).map((type) => {
return {
name: type.name,
value: type.slug,
}
}),
)
},
enrichedTemplates() {
let enrichedTemplates = this.templates
// Filter by Selected Type
if (this.filterTypes && this.selectedType && this.selectedType !== 'all') {
if (
this.filterTypes &&
this.selectedType &&
this.selectedType !== "all"
) {
enrichedTemplates = enrichedTemplates.filter((item) => {
return (item.types && item.types.length > 0) ? item.types.includes(this.selectedType) : false
return item.types && item.types.length > 0
? item.types.includes(this.selectedType)
: false
})
}
// Filter by Selected Industry
if (this.filterIndustries && this.selectedIndustry && this.selectedIndustry !== 'all') {
if (
this.filterIndustries &&
this.selectedIndustry &&
this.selectedIndustry !== "all"
) {
enrichedTemplates = enrichedTemplates.filter((item) => {
return (item.industries && item.industries.length > 0) ? item.industries.includes(this.selectedIndustry) : false
return item.industries && item.industries.length > 0
? item.industries.includes(this.selectedIndustry)
: false
})
}
if (!this.debouncedSearch || this.debouncedSearch === '' || this.debouncedSearch === null) {
if (
!this.debouncedSearch ||
this.debouncedSearch === "" ||
this.debouncedSearch === null
) {
return enrichedTemplates
}
// Fuze search
const fuzeOptions = {
keys: [
'name',
'slug',
'description',
'short_description'
]
keys: ["name", "slug", "description", "short_description"],
}
const fuse = new Fuse(enrichedTemplates, fuzeOptions)
return fuse.search(this.debouncedSearch).map((res) => {
return res.item
})
}
},
},
}
</script>

View File

@@ -1,112 +1,166 @@
<template>
<div class="py-8">
<section>
<div class="mx-auto max-w-7xl isolate sm:px-6 lg:px-8">
<div class="py-8">
<section>
<div class="mx-auto max-w-7xl isolate sm:px-6 lg:px-8">
<div
class="relative px-4 py-8 overflow-hidden bg-blue-100 ring-blue-100 ring-1 sm:shadow-lg isolate sm:rounded-2xl sm:px-16 md:pt-20 lg:flex lg:gap-x-20 lg:px-16 lg:py-0 sm:shadow-gray-600/10"
>
<div class="absolute inset-0">
<img
class="object-cover object-top w-full h-full"
src="/img/pages/ai_form_builder/background-pattern-ai.svg"
alt=""
>
</div>
<div
class="relative px-4 py-8 overflow-hidden bg-blue-100 ring-blue-100 ring-1 sm:shadow-lg isolate sm:rounded-2xl sm:px-16 md:pt-20 lg:flex lg:gap-x-20 lg:px-16 lg:py-0 sm:shadow-gray-600/10">
<div class="absolute inset-0">
<img class="object-cover object-top w-full h-full" src="/img/pages/ai_form_builder/background-pattern-ai.svg" alt=""/>
</div>
class="relative max-w-md mx-auto text-center xl:max-w-lg lg:mx-0 lg:flex-auto lg:py-16 lg:text-left"
>
<span
class="bg-white text-xs font-semibold inline-flex items-center shadow-sm ring-blue-200 ring-1 text-blue-600 px-2.5 py-1.5 rounded-full"
>
<svg
aria-hidden="true"
class="w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<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"
clip-rule="evenodd"
/>
</svg>
Introducing OpnForm AI
</span>
<div class="relative max-w-md mx-auto text-center xl:max-w-lg lg:mx-0 lg:flex-auto lg:py-16 lg:text-left">
<h2
class="mt-6 text-2xl font-semibold tracking-tight text-gray-900 sm:text-3xl lg:text-4xl"
>
Say goodbye to tedious form building with OpnForm's new
<span
class="bg-white text-xs font-semibold inline-flex items-center shadow-sm ring-blue-200 ring-1 text-blue-600 px-2.5 py-1.5 rounded-full">
<svg aria-hidden="true" class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor">
<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"
clip-rule="evenodd" />
</svg>
Introducing OpnForm AI
</span>
class="text-transparent bg-clip-text bg-gradient-to-r lg:block from-blue-600 to-blue-300"
>AI-powered feature!</span>
</h2>
<p
class="mt-4 text-base font-medium leading-7 text-gray-500 sm:text-lg sm:leading-8"
>
Easily generate a fully working form in seconds with just a simple
description.
</p>
<h2 class="mt-6 text-2xl font-semibold tracking-tight text-gray-900 sm:text-3xl lg:text-4xl">
Say goodbye to tedious form building with OpnForm's new <span
class="text-transparent bg-clip-text bg-gradient-to-r lg:block from-blue-600 to-blue-300">AI-powered
feature!</span>
</h2>
<p class="mt-4 text-base font-medium leading-7 text-gray-500 sm:text-lg sm:leading-8">
Easily generate a fully working form in seconds with just a simple description.
</p>
<div class="flex justify-center lg:justify-start mt-4">
<v-button class="block" v-track.welcome_ai_builder_click :to="{ name: 'forms-create-guest' }" :arrow="true" color="blue">
<div class="flex justify-center lg:justify-start mt-4">
<v-button
v-track.welcome_ai_builder_click
class="block"
:to="{ name: 'forms-create-guest' }"
:arrow="true"
color="blue"
>
Try our AI form builder
</v-button>
</div>
<ul
class="flex flex-wrap items-center justify-center mt-4 gap-4 text-sm font-medium text-gray-900 lg:justify-start">
<li class="flex items-center gap-2">
<svg aria-hidden="true" class="w-5 h-5 text-gray-400 shrink-0" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clip-rule="evenodd" />
</svg>
Create form in minutes
</li>
<li class="flex items-center gap-2">
<svg aria-hidden="true" class="w-5 h-5 text-gray-400 shrink-0" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clip-rule="evenodd" />
</svg>
Customizations
</li>
<li class="flex items-center gap-2">
<svg aria-hidden="true" class="w-5 h-5 text-gray-400 shrink-0" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clip-rule="evenodd" />
</svg>
No-coding required
</li>
</ul>
<div class="mt-6 sm:mt-8 flex text-center justify-center lg:justify-start">
<!-- <v-button v-if="!authenticated" class="mr-2 block" :to="{ name: 'forms-create-guest' }" :arrow="true">-->
<!-- Get started for free-->
<!-- </v-button>-->
<!-- <v-button v-else class="mr-2 block" :to="{ name: 'forms-create' }" :arrow="true">-->
<!-- Get started for free-->
<!-- </v-button>-->
<!-- <v-button color="light-gray" class="mr-1 block" :to="{ name: 'aiformbuilder' }">-->
<!-- Learn more-->
<!-- </v-button>-->
</div>
</div>
<ul
class="flex flex-wrap items-center justify-center mt-4 gap-4 text-sm font-medium text-gray-900 lg:justify-start"
>
<li class="flex items-center gap-2">
<svg
aria-hidden="true"
class="w-5 h-5 text-gray-400 shrink-0"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clip-rule="evenodd"
/>
</svg>
Create form in minutes
</li>
<li class="flex items-center gap-2">
<svg
aria-hidden="true"
class="w-5 h-5 text-gray-400 shrink-0"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clip-rule="evenodd"
/>
</svg>
Customizations
</li>
<li class="flex items-center gap-2">
<svg
aria-hidden="true"
class="w-5 h-5 text-gray-400 shrink-0"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clip-rule="evenodd"
/>
</svg>
No-coding required
</li>
</ul>
<div
class="relative hidden lg:block w-full px-6 mx-auto mt-8 lg:px-0 sm:mt-12 lg:bottom-0 lg:absolute lg:-right-16 xl:right-0 lg:max-w-lg">
<img class="rounded-t-2xl ring-1 ring-blue-100 lg:rounded-tr-none w-full"
src="/img/pages/ai_form_builder/ai-feature-illustration.svg" alt="App screenshot"/>
class="mt-6 sm:mt-8 flex text-center justify-center lg:justify-start"
>
<!-- <v-button v-if="!authenticated" class="mr-2 block" :to="{ name: 'forms-create-guest' }" :arrow="true">-->
<!-- Get started for free-->
<!-- </v-button>-->
<!-- <v-button v-else class="mr-2 block" :to="{ name: 'forms-create' }" :arrow="true">-->
<!-- Get started for free-->
<!-- </v-button>-->
<!-- <v-button color="light-gray" class="mr-1 block" :to="{ name: 'aiformbuilder' }">-->
<!-- Learn more-->
<!-- </v-button>-->
</div>
</div>
<div
class="relative hidden lg:block w-full px-6 mx-auto mt-8 lg:px-0 sm:mt-12 lg:bottom-0 lg:absolute lg:-right-16 xl:right-0 lg:max-w-lg"
>
<img
class="rounded-t-2xl ring-1 ring-blue-100 lg:rounded-tr-none w-full"
src="/img/pages/ai_form_builder/ai-feature-illustration.svg"
alt="App screenshot"
>
</div>
</div>
</section>
</div>
</div>
</section>
</div>
</template>
<script>
import { computed } from 'vue'
import { useAuthStore } from '../../../stores/auth'
import { computed } from "vue"
import { useAuthStore } from "../../../stores/auth"
export default {
setup () {
props: {},
setup() {
const authStore = useAuthStore()
return {
authenticated : computed(() => authStore.check)
authenticated: computed(() => authStore.check),
}
},
props: {},
data: () => ({}),
computed: {},
methods: {}
methods: {},
}
</script>

View File

@@ -1,120 +1,241 @@
<template>
<div id="features" class="px-4 mx-auto sm:max-w-xl md:max-w-full lg:max-w-screen-xl md:px-24 lg:px-8">
<div class="mb-16 max-w-xl md:mx-auto sm:text-center lg:max-w-2xl ">
<div
id="features"
class="px-4 mx-auto sm:max-w-xl md:max-w-full lg:max-w-screen-xl md:px-24 lg:px-8"
>
<div class="mb-16 max-w-xl md:mx-auto sm:text-center lg:max-w-2xl">
<h2
class="mb-6 font-sans text-4xl font-semibold leading-none tracking-tight text-gray-900 dark:text-gray-100 sm:text-4xl md:mx-auto">
The easiest way to create forms. <br/>
class="mb-6 font-sans text-4xl font-semibold leading-none tracking-tight text-gray-900 dark:text-gray-100 sm:text-4xl md:mx-auto"
>
The easiest way to create forms. <br>
Generous unlimited <span class="text-nt-blue">free plan.</span>
</h2>
<p class="text-base text-gray-700 dark:text-gray-300 md:text-lg">
Need a contact form? Doing a survey? Create a form in 2 minutes and start receiving submissions.
Need a contact form? Doing a survey? Create a form in 2 minutes and
start receiving submissions.
</p>
</div>
<div class="flex flex-wrap items-center mt-16" :class="{'md:flex-row-reverse':index%2==1}" v-for="(step,index) in [
<div
v-for="(step, index) in [
{
title: 'Create',
description: 'Create a form in 2 minutes. More than 10 input types, images, logic and much more.',
features: [
'Build a simple form in minutes.',
'No coding needed.'
],
img: '/img/pages/welcome/step-1.jpg'
description:
'Create a form in 2 minutes. More than 10 input types, images, logic and much more.',
features: ['Build a simple form in minutes.', 'No coding needed.'],
img: '/img/pages/welcome/step-1.jpg',
},
{
title: 'Share',
description: 'Your form has a unique link that you can share everywhere. Send the link, or even embed the form on your website.',
description:
'Your form has a unique link that you can share everywhere. Send the link, or even embed the form on your website.',
features: [
'Share the link to your form',
'Embed the form on your website'
'Embed the form on your website',
],
img: '/img/pages/welcome/step-2.jpg'
img: '/img/pages/welcome/step-2.jpg',
},
{
title: 'Get Results',
description: 'Receive your form submissions. Receive notifications, send confirmations. Export submissions and check your form analytics.',
description:
'Receive your form submissions. Receive notifications, send confirmations. Export submissions and check your form analytics.',
features: [
'Unlimited form submissions for free',
'Easily export submissions as CSV',
'Views & Submissions Analytics'
'Views & Submissions Analytics',
],
img: '/img/pages/welcome/step-3.jpg'
}
]" :key="step.title">
<div class="w-full md:w-1/2 lg:w-5/12" :class="{'md:pl-4':index%2==1, 'md:pr-4':index%2==0}">
<svg v-if="step.title=='Create'" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"
stroke="currentColor" class="w-10 h-10 text-nt-blue">
img: '/img/pages/welcome/step-3.jpg',
},
]"
:key="step.title"
class="flex flex-wrap items-center mt-16"
:class="{ 'md:flex-row-reverse': index % 2 == 1 }"
>
<div
class="w-full md:w-1/2 lg:w-5/12"
:class="{ 'md:pl-4': index % 2 == 1, 'md:pr-4': index % 2 == 0 }"
>
<svg
v-if="step.title == 'Create'"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
stroke="currentColor"
class="w-10 h-10 text-nt-blue"
>
<path
d="M22 7.99997H13.6C10.2397 7.99997 8.55953 7.99997 7.27606 8.65393C6.14708 9.22917 5.2292 10.1471 4.65396 11.276C4 12.5595 4 14.2397 4 17.6V34.4C4 37.7603 4 39.4404 4.65396 40.7239C5.2292 41.8529 6.14708 42.7708 7.27606 43.346C8.55953 44 10.2397 44 13.6 44H30.4C33.7603 44 35.4405 44 36.7239 43.346C37.8529 42.7708 38.7708 41.8529 39.346 40.7239C40 39.4404 40 37.7603 40 34.4V26M15.9999 32H19.349C20.3274 32 20.8166 32 21.2769 31.8894C21.6851 31.7915 22.0753 31.6298 22.4331 31.4105C22.8368 31.1632 23.1827 30.8173 23.8745 30.1255L43 11C44.6569 9.34311 44.6569 6.65682 43 4.99997C41.3431 3.34311 38.6569 3.34311 37 4.99996L17.8745 24.1255C17.1827 24.8173 16.8368 25.1632 16.5894 25.5668C16.3701 25.9247 16.2085 26.3149 16.1105 26.723C15.9999 27.1834 15.9999 27.6726 15.9999 28.6509V32Z"
stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
stroke="currentColor"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<svg v-else-if="step.title=='Share'" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"
stroke="currentColor" class="w-10 h-10 text-nt-blue">
<svg
v-else-if="step.title == 'Share'"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
stroke="currentColor"
class="w-10 h-10 text-nt-blue"
>
<path
d="M17.18 27.02L30.84 34.98M30.82 13.02L17.18 20.98M42 10C42 13.3137 39.3137 16 36 16C32.6863 16 30 13.3137 30 10C30 6.68629 32.6863 4 36 4C39.3137 4 42 6.68629 42 10ZM18 24C18 27.3137 15.3137 30 12 30C8.68629 30 6 27.3137 6 24C6 20.6863 8.68629 18 12 18C15.3137 18 18 20.6863 18 24ZM42 38C42 41.3137 39.3137 44 36 44C32.6863 44 30 41.3137 30 38C30 34.6863 32.6863 32 36 32C39.3137 32 42 34.6863 42 38Z"
stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<svg v-else-if="step.title=='Share'" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-10 h-10 text-nt-blue">
<path stroke-linecap="round" stroke-linejoin="round"
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10"/>
</svg>
<svg v-else-if="step.title=='Get Results'" class="w-10 h-10 text-nt-blue" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
<svg
v-else-if="step.title == 'Get Results'"
class="w-10 h-10 text-nt-blue"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M42 42H9.2C8.07989 42 7.51984 42 7.09202 41.782C6.71569 41.5903 6.40973 41.2843 6.21799 40.908C6 40.4802 6 39.9201 6 38.8V6M40 16L32.1623 24.3653C31.8652 24.6823 31.7167 24.8409 31.5375 24.9228C31.3794 24.9951 31.2051 25.0249 31.0319 25.0093C30.8357 24.9916 30.6429 24.8915 30.2574 24.6913L23.7426 21.3087C23.3571 21.1085 23.1643 21.0084 22.9681 20.9907C22.7949 20.9751 22.6206 21.0049 22.4625 21.0772C22.2833 21.1591 22.1348 21.3177 21.8377 21.6347L14 30"
stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
stroke="currentColor"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<h4 class="my-5 text-2xl font-medium">{{ index + 1 }}. {{ step.title }}</h4>
<h4 class="my-5 text-2xl font-medium">
{{ index + 1 }}. {{ step.title }}
</h4>
<p class="dark:text-white">
{{ step.description }}
</p>
<div class="mb-8">
<div class="flex mt-4" v-for="feature in step.features" :key="feature">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4 mt-1 mr-2 text-nt-blue">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"/>
<div
v-for="feature in step.features"
:key="feature"
class="flex mt-4"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4 mt-1 mr-2 text-nt-blue"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M4.5 12.75l6 6 9-13.5"
/>
</svg>
{{ feature }}
</div>
</div>
</div>
<div class="w-full md:w-1/2 lg:w-7/12 flex items-center justify-center relative w-full"
:class="{'md:pr-8':index%2==1, 'md:pl-8':index%2==0}">
<img loading="lazy" class="block rounded-2xl w-full"
sizes="320px sm:530px"
:src="step.img" alt="product-feature-image"/>
<div
class="w-full md:w-1/2 lg:w-7/12 flex items-center justify-center relative w-full"
:class="{ 'md:pr-8': index % 2 == 1, 'md:pl-8': index % 2 == 0 }"
>
<img
loading="lazy"
class="block rounded-2xl w-full"
sizes="320px sm:530px"
:src="step.img"
alt="product-feature-image"
>
</div>
</div>
<div class="grid md:grid-cols-3 mt-20">
<div class="mb-8 md:mr-10">
<svg class="w-10 h-10 text-nt-blue" viewBox="0 0 44 42" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M26.0006 40H18.0006M2.58828 9.63978C2.55958 6.73709 4.12455 4.02649 6.6527 2.59999M41.405 9.6398C41.4337 6.7371 39.8687 4.0265 37.3406 2.60001M34.0006 14C34.0006 10.8174 32.7363 7.76516 30.4859 5.51472C28.2354 3.26428 25.1832 2 22.0006 2C18.818 2 15.7658 3.26428 13.5153 5.51472C11.2649 7.76516 10.0006 10.8174 10.0006 14C10.0006 20.1804 8.44154 24.4119 6.69993 27.2108C5.23085 29.5717 4.49631 30.7522 4.52325 31.0815C4.55307 31.4461 4.63032 31.5852 4.92415 31.8032C5.18951 32 6.38578 32 8.7783 32H35.2229C37.6154 32 38.8117 32 39.077 31.8032C39.3709 31.5852 39.4481 31.4461 39.4779 31.0815C39.5049 30.7522 38.7703 29.5718 37.3013 27.2108C35.5597 24.4119 34.0006 20.1804 34.0006 14Z" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<svg
class="w-10 h-10 text-nt-blue"
viewBox="0 0 44 42"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M26.0006 40H18.0006M2.58828 9.63978C2.55958 6.73709 4.12455 4.02649 6.6527 2.59999M41.405 9.6398C41.4337 6.7371 39.8687 4.0265 37.3406 2.60001M34.0006 14C34.0006 10.8174 32.7363 7.76516 30.4859 5.51472C28.2354 3.26428 25.1832 2 22.0006 2C18.818 2 15.7658 3.26428 13.5153 5.51472C11.2649 7.76516 10.0006 10.8174 10.0006 14C10.0006 20.1804 8.44154 24.4119 6.69993 27.2108C5.23085 29.5717 4.49631 30.7522 4.52325 31.0815C4.55307 31.4461 4.63032 31.5852 4.92415 31.8032C5.18951 32 6.38578 32 8.7783 32H35.2229C37.6154 32 38.8117 32 39.077 31.8032C39.3709 31.5852 39.4481 31.4461 39.4779 31.0815C39.5049 30.7522 38.7703 29.5718 37.3013 27.2108C35.5597 24.4119 34.0006 20.1804 34.0006 14Z"
stroke="currentColor"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<h3 class="my-3 font-semibold">Notifications</h3>
<p>Receive notifications directly in Slack or in your mailbox whenever your from has a new submission (if you
want to).</p>
<h3 class="my-3 font-semibold">
Notifications
</h3>
<p>
Receive notifications directly in Slack or in your mailbox whenever
your from has a new submission (if you want to).
</p>
</div>
<div class="mb-8 md:mr-10">
<svg class="w-10 h-10 text-nt-blue" viewBox="0 0 45 44" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.6666 2H27.0666C30.4269 2 32.1071 2 33.3906 2.65396C34.5195 3.2292 35.4374 4.14708 36.0127 5.27606C36.6666 6.55953 36.6666 8.23969 36.6666 11.6V32.4C36.6666 35.7603 36.6666 37.4405 36.0127 38.7239C35.4374 39.8529 34.5195 40.7708 33.3906 41.346C32.1071 42 30.4269 42 27.0666 42H14.2666C10.9063 42 9.22615 42 7.94268 41.346C6.81371 40.7708 5.89583 39.8529 5.32059 38.7239C4.66663 37.4405 4.66663 35.7603 4.66663 32.4V31M28.6666 24H19.6666M28.6666 16H21.6666M28.6666 32H12.6666M8.66663 18V7C8.66663 5.34315 10.0098 4 11.6666 4C13.3235 4 14.6666 5.34315 14.6666 7V18C14.6666 21.3137 11.9803 24 8.66663 24C5.35292 24 2.66663 21.3137 2.66663 18V10" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<svg
class="w-10 h-10 text-nt-blue"
viewBox="0 0 45 44"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M21.6666 2H27.0666C30.4269 2 32.1071 2 33.3906 2.65396C34.5195 3.2292 35.4374 4.14708 36.0127 5.27606C36.6666 6.55953 36.6666 8.23969 36.6666 11.6V32.4C36.6666 35.7603 36.6666 37.4405 36.0127 38.7239C35.4374 39.8529 34.5195 40.7708 33.3906 41.346C32.1071 42 30.4269 42 27.0666 42H14.2666C10.9063 42 9.22615 42 7.94268 41.346C6.81371 40.7708 5.89583 39.8529 5.32059 38.7239C4.66663 37.4405 4.66663 35.7603 4.66663 32.4V31M28.6666 24H19.6666M28.6666 16H21.6666M28.6666 32H12.6666M8.66663 18V7C8.66663 5.34315 10.0098 4 11.6666 4C13.3235 4 14.6666 5.34315 14.6666 7V18C14.6666 21.3137 11.9803 24 8.66663 24C5.35292 24 2.66663 21.3137 2.66663 18V10"
stroke="currentColor"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<h3 class="my-3 font-semibold">File Uploads</h3>
<p>Easily add file upload inputs to your forms. Uploaded files are securely stored for you. Up to 5mb!</p>
<h3 class="my-3 font-semibold">
File Uploads
</h3>
<p>
Easily add file upload inputs to your forms. Uploaded files are
securely stored for you. Up to 5mb!
</p>
</div>
<div class="mb-8 md:mr-10">
<svg class="w-10 h-10 text-nt-blue" viewBox="0 0 45 44" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.33331 22C2.33331 33.0457 11.2876 42 22.3333 42C25.647 42 28.3333 39.3137 28.3333 36V35C28.3333 34.0712 28.3333 33.6067 28.3846 33.2168C28.7391 30.5244 30.8578 28.4058 33.5502 28.0513C33.9401 28 34.4045 28 35.3333 28H36.3333C39.647 28 42.3333 25.3137 42.3333 22C42.3333 10.9543 33.379 2 22.3333 2C11.2876 2 2.33331 10.9543 2.33331 22Z" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.3333 24C13.4379 24 14.3333 23.1046 14.3333 22C14.3333 20.8954 13.4379 20 12.3333 20C11.2287 20 10.3333 20.8954 10.3333 22C10.3333 23.1046 11.2287 24 12.3333 24Z" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M30.3333 16C31.4379 16 32.3333 15.1046 32.3333 14C32.3333 12.8954 31.4379 12 30.3333 12C29.2287 12 28.3333 12.8954 28.3333 14C28.3333 15.1046 29.2287 16 30.3333 16Z" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18.3333 14C19.4379 14 20.3333 13.1046 20.3333 12C20.3333 10.8954 19.4379 10 18.3333 10C17.2287 10 16.3333 10.8954 16.3333 12C16.3333 13.1046 17.2287 14 18.3333 14Z" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<svg
class="w-10 h-10 text-nt-blue"
viewBox="0 0 45 44"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.33331 22C2.33331 33.0457 11.2876 42 22.3333 42C25.647 42 28.3333 39.3137 28.3333 36V35C28.3333 34.0712 28.3333 33.6067 28.3846 33.2168C28.7391 30.5244 30.8578 28.4058 33.5502 28.0513C33.9401 28 34.4045 28 35.3333 28H36.3333C39.647 28 42.3333 25.3137 42.3333 22C42.3333 10.9543 33.379 2 22.3333 2C11.2876 2 2.33331 10.9543 2.33331 22Z"
stroke="currentColor"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M12.3333 24C13.4379 24 14.3333 23.1046 14.3333 22C14.3333 20.8954 13.4379 20 12.3333 20C11.2287 20 10.3333 20.8954 10.3333 22C10.3333 23.1046 11.2287 24 12.3333 24Z"
stroke="currentColor"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M30.3333 16C31.4379 16 32.3333 15.1046 32.3333 14C32.3333 12.8954 31.4379 12 30.3333 12C29.2287 12 28.3333 12.8954 28.3333 14C28.3333 15.1046 29.2287 16 30.3333 16Z"
stroke="currentColor"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M18.3333 14C19.4379 14 20.3333 13.1046 20.3333 12C20.3333 10.8954 19.4379 10 18.3333 10C17.2287 10 16.3333 10.8954 16.3333 12C16.3333 13.1046 17.2287 14 18.3333 14Z"
stroke="currentColor"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<h3 class="my-3 font-semibold">Customize Everything</h3>
<p>Change form themes, change texts, colors, add images, add custom thank you pages and much more.</p>
<h3 class="my-3 font-semibold">
Customize Everything
</h3>
<p>
Change form themes, change texts, colors, add images, add custom thank
you pages and much more.
</p>
</div>
</div>
</div>
@@ -122,10 +243,9 @@
<script>
export default {
props: {},
data: () => ({}),
methods: {}
methods: {},
}
</script>

View File

@@ -1,84 +1,235 @@
<template>
<div class="bg-gray-50 dark:bg-notion-dark py-8">
<div class="md:max-w-5xl md:mx-auto w-full">
<div class="my-5 text-center">
<h3 class="font-semibold text-3xl">And many more features</h3>
<p class="w-full mt-2 mb-8">
OpnForm makes form building easy and comes with powerful features.
</p>
<div class="grid grid-cols-2 md:grid-cols-3 mt-10 mb-5 ml-5 md:ml-0 px-4">
<div class="flex font-semibold my-3">
<svg class="w-5 h-5 mr-2 text-nt-blue" viewBox="0 0 23 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 3.5H17.8C18.9201 3.5 19.4802 3.5 19.908 3.71799C20.2843 3.90973 20.5903 4.21569 20.782 4.59202C21 5.01984 21 5.57989 21 6.7V8C21 8.93188 21 9.39783 20.8478 9.76537C20.6448 10.2554 20.2554 10.6448 19.7654 10.8478C19.3978 11 18.9319 11 18 11M12.5 18.5H5.2C4.0799 18.5 3.51984 18.5 3.09202 18.282C2.71569 18.0903 2.40973 17.7843 2.21799 17.408C2 16.9802 2 16.4201 2 15.3V14C2 13.0681 2 12.6022 2.15224 12.2346C2.35523 11.7446 2.74458 11.3552 3.23463 11.1522C3.60218 11 4.06812 11 5 11M9.8 13.5H13.2C13.48 13.5 13.62 13.5 13.727 13.4455C13.8211 13.3976 13.8976 13.3211 13.9455 13.227C14 13.12 14 12.98 14 12.7V9.3C14 9.01997 14 8.87996 13.9455 8.773C13.8976 8.67892 13.8211 8.60243 13.727 8.5545C13.62 8.5 13.48 8.5 13.2 8.5H9.8C9.51997 8.5 9.37996 8.5 9.273 8.5545C9.17892 8.60243 9.10243 8.67892 9.0545 8.773C9 8.87996 9 9.01997 9 9.3V12.7C9 12.98 9 13.12 9.0545 13.227C9.10243 13.3211 9.17892 13.3976 9.273 13.4455C9.37996 13.5 9.51997 13.5 9.8 13.5ZM17.3 21H20.7C20.98 21 21.12 21 21.227 20.9455C21.3211 20.8976 21.3976 20.8211 21.4455 20.727C21.5 20.62 21.5 20.48 21.5 20.2V16.8C21.5 16.52 21.5 16.38 21.4455 16.273C21.3976 16.1789 21.3211 16.1024 21.227 16.0545C21.12 16 20.98 16 20.7 16H17.3C17.02 16 16.88 16 16.773 16.0545C16.6789 16.1024 16.6024 16.1789 16.5545 16.273C16.5 16.38 16.5 16.52 16.5 16.8V20.2C16.5 20.48 16.5 20.62 16.5545 20.727C16.6024 20.8211 16.6789 20.8976 16.773 20.9455C16.88 21 17.02 21 17.3 21ZM2.3 6H5.7C5.98003 6 6.12004 6 6.227 5.9455C6.32108 5.89757 6.39757 5.82108 6.4455 5.727C6.5 5.62004 6.5 5.48003 6.5 5.2V1.8C6.5 1.51997 6.5 1.37996 6.4455 1.273C6.39757 1.17892 6.32108 1.10243 6.227 1.0545C6.12004 1 5.98003 1 5.7 1H2.3C2.01997 1 1.87996 1 1.773 1.0545C1.67892 1.10243 1.60243 1.17892 1.5545 1.273C1.5 1.37996 1.5 1.51997 1.5 1.8V5.2C1.5 5.48003 1.5 5.62004 1.5545 5.727C1.60243 5.82108 1.67892 5.89757 1.773 5.9455C1.87996 6 2.01997 6 2.3 6Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Form logic
</div>
<div class="flex font-semibold my-3">
<svg class="w-5 h-5 mr-2 text-nt-blue" viewBox="0 0 23 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5 11H21.5M1.5 11C1.5 16.5228 5.97715 21 11.5 21M1.5 11C1.5 5.47715 5.97715 1 11.5 1M21.5 11C21.5 16.5228 17.0228 21 11.5 21M21.5 11C21.5 5.47715 17.0228 1 11.5 1M11.5 1C14.0013 3.73835 15.4228 7.29203 15.5 11C15.4228 14.708 14.0013 18.2616 11.5 21M11.5 1C8.99872 3.73835 7.57725 7.29203 7.5 11C7.57725 14.708 8.99872 18.2616 11.5 21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
URL pre-fill
</div>
<div class="flex font-semibold my-3">
<svg class="w-5 h-5 mr-2 text-nt-blue" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99999 1L4.99999 19M16 1L13 19M19 6H2M18 14H1" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Unique submission ID
</div>
<div class="flex font-semibold my-3">
<svg class="w-5 h-5 mr-2 text-nt-blue" viewBox="0 0 23 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.2429 3.09232C10.6494 3.03223 11.0686 3 11.5004 3C16.6054 3 19.9553 7.50484 21.0807 9.28682C21.2169 9.5025 21.285 9.61034 21.3231 9.77667C21.3518 9.90159 21.3517 10.0987 21.3231 10.2236C21.2849 10.3899 21.2164 10.4985 21.0792 10.7156C20.7793 11.1901 20.3222 11.8571 19.7165 12.5805M6.22432 4.71504C4.06225 6.1817 2.59445 8.21938 1.92111 9.28528C1.78428 9.50187 1.71587 9.61016 1.67774 9.77648C1.6491 9.9014 1.64909 10.0984 1.67771 10.2234C1.71583 10.3897 1.78393 10.4975 1.92013 10.7132C3.04554 12.4952 6.39541 17 11.5004 17C13.5588 17 15.3319 16.2676 16.7888 15.2766M2.50042 1L20.5004 19M9.3791 7.87868C8.8362 8.42157 8.50042 9.17157 8.50042 10C8.50042 11.6569 9.84356 13 11.5004 13C12.3288 13 13.0788 12.6642 13.6217 12.1213" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Hidden fields
</div>
<div class="flex font-semibold my-3">
<svg class="w-5 h-5 mr-2 text-nt-blue" viewBox="0 0 19 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.5 8V6C14.5 3.23858 12.2614 1 9.5 1C6.73858 1 4.5 3.23858 4.5 6V8M9.5 12.5V14.5M6.3 19H12.7C14.3802 19 15.2202 19 15.862 18.673C16.4265 18.3854 16.8854 17.9265 17.173 17.362C17.5 16.7202 17.5 15.8802 17.5 14.2V12.8C17.5 11.1198 17.5 10.2798 17.173 9.63803C16.8854 9.07354 16.4265 8.6146 15.862 8.32698C15.2202 8 14.3802 8 12.7 8H6.3C4.61984 8 3.77976 8 3.13803 8.32698C2.57354 8.6146 2.1146 9.07354 1.82698 9.63803C1.5 10.2798 1.5 11.1198 1.5 12.8V14.2C1.5 15.8802 1.5 16.7202 1.82698 17.362C2.1146 17.9265 2.57354 18.3854 3.13803 18.673C3.77976 19 4.61984 19 6.3 19Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Form password
</div>
<div class="flex font-semibold my-3">
<svg class="w-5 h-5 mr-2 text-nt-blue" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.5 13C14.8431 13 13.5 14.3431 13.5 16C13.5 17.6569 14.8431 19 16.5 19C18.1569 19 19.5 17.6569 19.5 16C19.5 14.3431 18.1569 13 16.5 13ZM16.5 13V6C16.5 5.46957 16.2893 4.96086 15.9142 4.58579C15.5391 4.21071 15.0304 4 14.5 4H11.5M4.5 7C6.15685 7 7.5 5.65685 7.5 4C7.5 2.34315 6.15685 1 4.5 1C2.84315 1 1.5 2.34315 1.5 4C1.5 5.65685 2.84315 7 4.5 7ZM4.5 7V19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Webhooks
</div>
<div class="flex font-semibold my-3">
<svg class="w-5 h-5 mr-2 text-nt-blue" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 13L16 10L13 7M8 7L5 10L8 13M6.3 19H14.7C16.3802 19 17.2202 19 17.862 18.673C18.4265 18.3854 18.8854 17.9265 19.173 17.362C19.5 16.7202 19.5 15.8802 19.5 14.2V5.8C19.5 4.11984 19.5 3.27976 19.173 2.63803C18.8854 2.07354 18.4265 1.6146 17.862 1.32698C17.2202 1 16.3802 1 14.7 1H6.3C4.61984 1 3.77976 1 3.13803 1.32698C2.57354 1.6146 2.1146 2.07354 1.82698 2.63803C1.5 3.27976 1.5 4.11984 1.5 5.8V14.2C1.5 15.8802 1.5 16.7202 1.82698 17.362C2.1146 17.9265 2.57354 18.3854 3.13803 18.673C3.77976 19 4.61984 19 6.3 19Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Custom code
</div>
<div class="flex font-semibold my-3">
<svg class="w-5 h-5 mr-2 text-nt-blue" viewBox="0 0 21 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.5 7H1.5M14.5 1V4M6.5 1V4M10.5 17V11M7.5 14H13.5M6.3 21H14.7C16.3802 21 17.2202 21 17.862 20.673C18.4265 20.3854 18.8854 19.9265 19.173 19.362C19.5 18.7202 19.5 17.8802 19.5 16.2V7.8C19.5 6.11984 19.5 5.27976 19.173 4.63803C18.8854 4.07354 18.4265 3.6146 17.862 3.32698C17.2202 3 16.3802 3 14.7 3H6.3C4.61984 3 3.77976 3 3.13803 3.32698C2.57354 3.6146 2.1146 4.07354 1.82698 4.63803C1.5 5.27976 1.5 6.11984 1.5 7.8V16.2C1.5 17.8802 1.5 18.7202 1.82698 19.362C2.1146 19.9265 2.57354 20.3854 3.13803 20.673C3.77976 21 4.61984 21 6.3 21Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Closing date
</div>
<div class="flex font-semibold my-3">
<svg class="w-5 h-5 mr-2 text-nt-blue" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.5 3C10.0523 3 10.5 2.55228 10.5 2C10.5 1.44772 10.0523 1 9.5 1C8.94772 1 8.5 1.44772 8.5 2C8.5 2.55228 8.94772 3 9.5 3Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.5 10C10.0523 10 10.5 9.55228 10.5 9C10.5 8.44772 10.0523 8 9.5 8C8.94772 8 8.5 8.44772 8.5 9C8.5 9.55228 8.94772 10 9.5 10Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.5 17C10.0523 17 10.5 16.5523 10.5 16C10.5 15.4477 10.0523 15 9.5 15C8.94772 15 8.5 15.4477 8.5 16C8.5 16.5523 8.94772 17 9.5 17Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.5 3C17.0523 3 17.5 2.55228 17.5 2C17.5 1.44772 17.0523 1 16.5 1C15.9477 1 15.5 1.44772 15.5 2C15.5 2.55228 15.9477 3 16.5 3Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.5 10C17.0523 10 17.5 9.55228 17.5 9C17.5 8.44772 17.0523 8 16.5 8C15.9477 8 15.5 8.44772 15.5 9C15.5 9.55228 15.9477 10 16.5 10Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.5 17C17.0523 17 17.5 16.5523 17.5 16C17.5 15.4477 17.0523 15 16.5 15C15.9477 15 15.5 15.4477 15.5 16C15.5 16.5523 15.9477 17 16.5 17Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.5 3C3.05228 3 3.5 2.55228 3.5 2C3.5 1.44772 3.05228 1 2.5 1C1.94772 1 1.5 1.44772 1.5 2C1.5 2.55228 1.94772 3 2.5 3Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.5 10C3.05228 10 3.5 9.55228 3.5 9C3.5 8.44772 3.05228 8 2.5 8C1.94772 8 1.5 8.44772 1.5 9C1.5 9.55228 1.94772 10 2.5 10Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.5 17C3.05228 17 3.5 16.5523 3.5 16C3.5 15.4477 3.05228 15 2.5 15C1.94772 15 1.5 15.4477 1.5 16C1.5 16.5523 1.94772 17 2.5 17Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
And much more...
</div>
<div class="bg-gray-50 dark:bg-notion-dark py-8">
<div class="md:max-w-5xl md:mx-auto w-full">
<div class="my-5 text-center">
<h3 class="font-semibold text-3xl">
And many more features
</h3>
<p class="w-full mt-2 mb-8">
OpnForm makes form building easy and comes with powerful features.
</p>
<div
class="grid grid-cols-2 md:grid-cols-3 mt-10 mb-5 ml-5 md:ml-0 px-4"
>
<div class="flex font-semibold my-3">
<svg
class="w-5 h-5 mr-2 text-nt-blue"
viewBox="0 0 23 22"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10.5 3.5H17.8C18.9201 3.5 19.4802 3.5 19.908 3.71799C20.2843 3.90973 20.5903 4.21569 20.782 4.59202C21 5.01984 21 5.57989 21 6.7V8C21 8.93188 21 9.39783 20.8478 9.76537C20.6448 10.2554 20.2554 10.6448 19.7654 10.8478C19.3978 11 18.9319 11 18 11M12.5 18.5H5.2C4.0799 18.5 3.51984 18.5 3.09202 18.282C2.71569 18.0903 2.40973 17.7843 2.21799 17.408C2 16.9802 2 16.4201 2 15.3V14C2 13.0681 2 12.6022 2.15224 12.2346C2.35523 11.7446 2.74458 11.3552 3.23463 11.1522C3.60218 11 4.06812 11 5 11M9.8 13.5H13.2C13.48 13.5 13.62 13.5 13.727 13.4455C13.8211 13.3976 13.8976 13.3211 13.9455 13.227C14 13.12 14 12.98 14 12.7V9.3C14 9.01997 14 8.87996 13.9455 8.773C13.8976 8.67892 13.8211 8.60243 13.727 8.5545C13.62 8.5 13.48 8.5 13.2 8.5H9.8C9.51997 8.5 9.37996 8.5 9.273 8.5545C9.17892 8.60243 9.10243 8.67892 9.0545 8.773C9 8.87996 9 9.01997 9 9.3V12.7C9 12.98 9 13.12 9.0545 13.227C9.10243 13.3211 9.17892 13.3976 9.273 13.4455C9.37996 13.5 9.51997 13.5 9.8 13.5ZM17.3 21H20.7C20.98 21 21.12 21 21.227 20.9455C21.3211 20.8976 21.3976 20.8211 21.4455 20.727C21.5 20.62 21.5 20.48 21.5 20.2V16.8C21.5 16.52 21.5 16.38 21.4455 16.273C21.3976 16.1789 21.3211 16.1024 21.227 16.0545C21.12 16 20.98 16 20.7 16H17.3C17.02 16 16.88 16 16.773 16.0545C16.6789 16.1024 16.6024 16.1789 16.5545 16.273C16.5 16.38 16.5 16.52 16.5 16.8V20.2C16.5 20.48 16.5 20.62 16.5545 20.727C16.6024 20.8211 16.6789 20.8976 16.773 20.9455C16.88 21 17.02 21 17.3 21ZM2.3 6H5.7C5.98003 6 6.12004 6 6.227 5.9455C6.32108 5.89757 6.39757 5.82108 6.4455 5.727C6.5 5.62004 6.5 5.48003 6.5 5.2V1.8C6.5 1.51997 6.5 1.37996 6.4455 1.273C6.39757 1.17892 6.32108 1.10243 6.227 1.0545C6.12004 1 5.98003 1 5.7 1H2.3C2.01997 1 1.87996 1 1.773 1.0545C1.67892 1.10243 1.60243 1.17892 1.5545 1.273C1.5 1.37996 1.5 1.51997 1.5 1.8V5.2C1.5 5.48003 1.5 5.62004 1.5545 5.727C1.60243 5.82108 1.67892 5.89757 1.773 5.9455C1.87996 6 2.01997 6 2.3 6Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
Form logic
</div>
<div class="flex font-semibold my-3">
<svg
class="w-5 h-5 mr-2 text-nt-blue"
viewBox="0 0 23 22"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1.5 11H21.5M1.5 11C1.5 16.5228 5.97715 21 11.5 21M1.5 11C1.5 5.47715 5.97715 1 11.5 1M21.5 11C21.5 16.5228 17.0228 21 11.5 21M21.5 11C21.5 5.47715 17.0228 1 11.5 1M11.5 1C14.0013 3.73835 15.4228 7.29203 15.5 11C15.4228 14.708 14.0013 18.2616 11.5 21M11.5 1C8.99872 3.73835 7.57725 7.29203 7.5 11C7.57725 14.708 8.99872 18.2616 11.5 21"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
URL pre-fill
</div>
<div class="flex font-semibold my-3">
<svg
class="w-5 h-5 mr-2 text-nt-blue"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.99999 1L4.99999 19M16 1L13 19M19 6H2M18 14H1"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
Unique submission ID
</div>
<div class="flex font-semibold my-3">
<svg
class="w-5 h-5 mr-2 text-nt-blue"
viewBox="0 0 23 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10.2429 3.09232C10.6494 3.03223 11.0686 3 11.5004 3C16.6054 3 19.9553 7.50484 21.0807 9.28682C21.2169 9.5025 21.285 9.61034 21.3231 9.77667C21.3518 9.90159 21.3517 10.0987 21.3231 10.2236C21.2849 10.3899 21.2164 10.4985 21.0792 10.7156C20.7793 11.1901 20.3222 11.8571 19.7165 12.5805M6.22432 4.71504C4.06225 6.1817 2.59445 8.21938 1.92111 9.28528C1.78428 9.50187 1.71587 9.61016 1.67774 9.77648C1.6491 9.9014 1.64909 10.0984 1.67771 10.2234C1.71583 10.3897 1.78393 10.4975 1.92013 10.7132C3.04554 12.4952 6.39541 17 11.5004 17C13.5588 17 15.3319 16.2676 16.7888 15.2766M2.50042 1L20.5004 19M9.3791 7.87868C8.8362 8.42157 8.50042 9.17157 8.50042 10C8.50042 11.6569 9.84356 13 11.5004 13C12.3288 13 13.0788 12.6642 13.6217 12.1213"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
Hidden fields
</div>
<div class="flex font-semibold my-3">
<svg
class="w-5 h-5 mr-2 text-nt-blue"
viewBox="0 0 19 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.5 8V6C14.5 3.23858 12.2614 1 9.5 1C6.73858 1 4.5 3.23858 4.5 6V8M9.5 12.5V14.5M6.3 19H12.7C14.3802 19 15.2202 19 15.862 18.673C16.4265 18.3854 16.8854 17.9265 17.173 17.362C17.5 16.7202 17.5 15.8802 17.5 14.2V12.8C17.5 11.1198 17.5 10.2798 17.173 9.63803C16.8854 9.07354 16.4265 8.6146 15.862 8.32698C15.2202 8 14.3802 8 12.7 8H6.3C4.61984 8 3.77976 8 3.13803 8.32698C2.57354 8.6146 2.1146 9.07354 1.82698 9.63803C1.5 10.2798 1.5 11.1198 1.5 12.8V14.2C1.5 15.8802 1.5 16.7202 1.82698 17.362C2.1146 17.9265 2.57354 18.3854 3.13803 18.673C3.77976 19 4.61984 19 6.3 19Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
Form password
</div>
<div class="flex font-semibold my-3">
<svg
class="w-5 h-5 mr-2 text-nt-blue"
viewBox="0 0 21 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16.5 13C14.8431 13 13.5 14.3431 13.5 16C13.5 17.6569 14.8431 19 16.5 19C18.1569 19 19.5 17.6569 19.5 16C19.5 14.3431 18.1569 13 16.5 13ZM16.5 13V6C16.5 5.46957 16.2893 4.96086 15.9142 4.58579C15.5391 4.21071 15.0304 4 14.5 4H11.5M4.5 7C6.15685 7 7.5 5.65685 7.5 4C7.5 2.34315 6.15685 1 4.5 1C2.84315 1 1.5 2.34315 1.5 4C1.5 5.65685 2.84315 7 4.5 7ZM4.5 7V19"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
Webhooks
</div>
<div class="flex font-semibold my-3">
<svg
class="w-5 h-5 mr-2 text-nt-blue"
viewBox="0 0 21 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13 13L16 10L13 7M8 7L5 10L8 13M6.3 19H14.7C16.3802 19 17.2202 19 17.862 18.673C18.4265 18.3854 18.8854 17.9265 19.173 17.362C19.5 16.7202 19.5 15.8802 19.5 14.2V5.8C19.5 4.11984 19.5 3.27976 19.173 2.63803C18.8854 2.07354 18.4265 1.6146 17.862 1.32698C17.2202 1 16.3802 1 14.7 1H6.3C4.61984 1 3.77976 1 3.13803 1.32698C2.57354 1.6146 2.1146 2.07354 1.82698 2.63803C1.5 3.27976 1.5 4.11984 1.5 5.8V14.2C1.5 15.8802 1.5 16.7202 1.82698 17.362C2.1146 17.9265 2.57354 18.3854 3.13803 18.673C3.77976 19 4.61984 19 6.3 19Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
Custom code
</div>
<div class="flex font-semibold my-3">
<svg
class="w-5 h-5 mr-2 text-nt-blue"
viewBox="0 0 21 22"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19.5 7H1.5M14.5 1V4M6.5 1V4M10.5 17V11M7.5 14H13.5M6.3 21H14.7C16.3802 21 17.2202 21 17.862 20.673C18.4265 20.3854 18.8854 19.9265 19.173 19.362C19.5 18.7202 19.5 17.8802 19.5 16.2V7.8C19.5 6.11984 19.5 5.27976 19.173 4.63803C18.8854 4.07354 18.4265 3.6146 17.862 3.32698C17.2202 3 16.3802 3 14.7 3H6.3C4.61984 3 3.77976 3 3.13803 3.32698C2.57354 3.6146 2.1146 4.07354 1.82698 4.63803C1.5 5.27976 1.5 6.11984 1.5 7.8V16.2C1.5 17.8802 1.5 18.7202 1.82698 19.362C2.1146 19.9265 2.57354 20.3854 3.13803 20.673C3.77976 21 4.61984 21 6.3 21Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
Closing date
</div>
<div class="flex font-semibold my-3">
<svg
class="w-5 h-5 mr-2 text-nt-blue"
viewBox="0 0 19 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.5 3C10.0523 3 10.5 2.55228 10.5 2C10.5 1.44772 10.0523 1 9.5 1C8.94772 1 8.5 1.44772 8.5 2C8.5 2.55228 8.94772 3 9.5 3Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M9.5 10C10.0523 10 10.5 9.55228 10.5 9C10.5 8.44772 10.0523 8 9.5 8C8.94772 8 8.5 8.44772 8.5 9C8.5 9.55228 8.94772 10 9.5 10Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M9.5 17C10.0523 17 10.5 16.5523 10.5 16C10.5 15.4477 10.0523 15 9.5 15C8.94772 15 8.5 15.4477 8.5 16C8.5 16.5523 8.94772 17 9.5 17Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M16.5 3C17.0523 3 17.5 2.55228 17.5 2C17.5 1.44772 17.0523 1 16.5 1C15.9477 1 15.5 1.44772 15.5 2C15.5 2.55228 15.9477 3 16.5 3Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M16.5 10C17.0523 10 17.5 9.55228 17.5 9C17.5 8.44772 17.0523 8 16.5 8C15.9477 8 15.5 8.44772 15.5 9C15.5 9.55228 15.9477 10 16.5 10Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M16.5 17C17.0523 17 17.5 16.5523 17.5 16C17.5 15.4477 17.0523 15 16.5 15C15.9477 15 15.5 15.4477 15.5 16C15.5 16.5523 15.9477 17 16.5 17Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M2.5 3C3.05228 3 3.5 2.55228 3.5 2C3.5 1.44772 3.05228 1 2.5 1C1.94772 1 1.5 1.44772 1.5 2C1.5 2.55228 1.94772 3 2.5 3Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M2.5 10C3.05228 10 3.5 9.55228 3.5 9C3.5 8.44772 3.05228 8 2.5 8C1.94772 8 1.5 8.44772 1.5 9C1.5 9.55228 1.94772 10 2.5 10Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M2.5 17C3.05228 17 3.5 16.5523 3.5 16C3.5 15.4477 3.05228 15 2.5 15C1.94772 15 1.5 15.4477 1.5 16C1.5 16.5523 1.94772 17 2.5 17Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
And much more...
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {},
data: () => ({}),
methods: {}
props: {},
data: () => ({}),
methods: {},
}
</script>

View File

@@ -1,33 +1,50 @@
<template>
<div class="mx-auto mb-12 max-w-7xl px-6 lg:px-8">
<div class="mx-auto max-w-2xl text-center">
<h2 class="text-lg font-semibold leading-8 tracking-tight text-blue-500 ">
<h2 class="text-lg font-semibold leading-8 tracking-tight text-blue-500">
Single or multi-page forms
</h2>
<p class="mt-2 text-3xl font-semibold tracking-tight text-gray-900 sm:text-4xl">
<p
class="mt-2 text-3xl font-semibold tracking-tight text-gray-900 sm:text-4xl"
>
Discover our beautiful templates
</p>
<p class="mt-3 px-8 text-center text-lg text-gray-400 ">
<p class="mt-3 px-8 text-center text-lg text-gray-400">
If you need inspiration, checkout our templates.
</p>
</div>
<div class="my-3 flex justify-center">
<NuxtLink :to="{name:'templates'}">
<NuxtLink :to="{ name: 'templates' }">
See all templates
<svg class="h-4 w-4 inline" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z"
clip-rule="evenodd"
<svg
class="h-4 w-4 inline"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z"
clip-rule="evenodd"
/>
</svg>
</NuxtLink>
</div>
<div v-if="sliderTemplates.length > 0"
class="w-full inline-flex flex-nowrap overflow-hidden [mask-image:_linear-gradient(to_right,transparent_0,_black_128px,_black_calc(100%-128px),transparent_100%)]"
<div
v-if="sliderTemplates.length > 0"
class="w-full inline-flex flex-nowrap overflow-hidden [mask-image:_linear-gradient(to_right,transparent_0,_black_128px,_black_calc(100%-128px),transparent_100%)]"
>
<ul ref="templates-slider" class="flex justify-center md:justify-start animate-infinite-scroll">
<li v-for="(template, i) in sliderTemplates" :key="template.id" class="mx-4 w-72 h-auto">
<ul
ref="templates-slider"
class="flex justify-center md:justify-start animate-infinite-scroll"
>
<li
v-for="(template) in sliderTemplates"
:key="template.id"
class="mx-4 w-72 h-auto"
>
<single-template :template="template" />
</li>
</ul>
@@ -36,18 +53,18 @@
</template>
<script>
import { computed } from 'vue'
import SingleTemplate from '../templates/SingleTemplate.vue'
import { computed } from "vue"
import SingleTemplate from "../templates/SingleTemplate.vue"
export default {
components: { SingleTemplate },
setup () {
setup() {
const templatesStore = useTemplatesStore()
templatesStore.initTypesAndIndustries()
onMounted(() => {
if (templatesStore.getAll.length < 10) {
opnFetch('templates',{query: {limit: 10}}).then((data) => {
opnFetch("templates", { query: { limit: 10 } }).then((data) => {
templatesStore.set(data)
})
}
@@ -56,29 +73,29 @@ export default {
return {
templatesStore,
allLoaded: computed(() => templatesStore.allLoaded),
sliderTemplates: computed(() => templatesStore.getAll.slice(0, 10))
sliderTemplates: computed(() => templatesStore.getAll.slice(0, 10)),
}
},
watch: {
sliderTemplates: {
deep: true,
handler () {
handler() {
this.$nextTick(() => {
this.setInfinite()
})
}
}
},
},
},
methods: {
setInfinite () {
const ul = this.$refs['templates-slider']
setInfinite() {
const ul = this.$refs["templates-slider"]
if (ul) {
ul.insertAdjacentHTML('afterend', ul.outerHTML)
ul.nextSibling.setAttribute('aria-hidden', 'true')
ul.insertAdjacentHTML("afterend", ul.outerHTML)
ul.nextSibling.setAttribute("aria-hidden", "true")
}
}
}
},
},
}
</script>

View File

@@ -12,8 +12,6 @@
</template>
<script>
import { useDarkMode } from '~/lib/forms/public-page.js'
export default {
props: {
featuresOnly: {