Refactor inpus

This commit is contained in:
Julien Nahum 2023-12-01 21:24:38 +01:00
parent 47653ebe64
commit 0eea59c5ba
33 changed files with 285 additions and 383 deletions

2
package-lock.json generated
View File

@ -39,7 +39,7 @@
"vue-signature-pad": "^3.0.2",
"vue3-editor": "^0.1.1",
"vue3-vt-notifications": "^1.0.0",
"vuedraggable": "^4.1.0"
"vuedraggable": "next"
},
"devDependencies": {
"@babel/core": "^7.20.12",

View File

@ -39,7 +39,7 @@
"vue-signature-pad": "^3.0.2",
"vue3-editor": "^0.1.1",
"vue3-vt-notifications": "^1.0.0",
"vuedraggable": "^4.1.0"
"vuedraggable": "next"
},
"devDependencies": {
"@babel/core": "^7.20.12",

View File

@ -2,7 +2,7 @@
<a v-if="href" :class="btnClasses" :href="href" :target="target">
<slot />
</a>
<button v-else-if="!to" :type="nativeType" :disabled="loading" :class="btnClasses"
<button v-else-if="!to" :type="nativeType" :disabled="loading?true:null" :class="btnClasses"
@click="onClick($event)"
>
<template v-if="!loading">

View File

@ -1,10 +1,10 @@
<template>
<input-wrapper v-bind="$props">
<input-wrapper v-bind="inputWrapperProps">
<template #label>
<span />
</template>
<v-checkbox :id="id?id:name" v-model="compVal" :disabled="disabled" :name="name">
<v-checkbox :id="id?id:name" v-model="compVal" :disabled="disabled?true:null" :name="name">
<slot name="label">
{{ label }} <span v-if="required" class="text-red-500 required-dot">*</span>
</slot>
@ -34,18 +34,8 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},

View File

@ -1,6 +1,6 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
@ -13,7 +13,7 @@
<div
:class="[theme.CodeInput.input,{ '!ring-red-500 !ring-2': hasError, '!cursor-not-allowed !bg-gray-200':disabled }]"
>
<codemirror :id="id?id:name" v-model="compVal" :disabled="disabled"
<codemirror :id="id?id:name" v-model="compVal" :disabled="disabled?true:null"
:options="cmOptions"
:style="inputStyle" :name="name"
:placeholder="placeholder"
@ -44,18 +44,8 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},

View File

@ -1,11 +1,11 @@
<template>
<input-wrapper v-bind="$props">
<input-wrapper v-bind="inputWrapperProps">
<template #label>
<span />
</template>
<div class="flex items-center">
<input :id="id?id:name" v-model="compVal" :disabled="disabled"
<input :id="id?id:name" v-model="compVal" :disabled="disabled?true:null"
type="color" class="mr-2"
:name="name"
>
@ -37,13 +37,8 @@ export default {
},
setup (props, context) {
const { compVal, inputStyle, hasValidation, hasError } = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
}
}

View File

@ -1,32 +1,36 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
</template>
<div class="flex" v-if="!dateRange">
<input :type="useTime ? 'datetime-local' : 'date'" :id="id?id:name" v-model="fromDate" :class="inputClasses"
:disabled="disabled"
<div v-if="!dateRange" class="flex">
<input :id="id?id:name" v-model="fromDate" :type="useTime ? 'datetime-local' : 'date'" :class="inputClasses"
:disabled="disabled?true:null"
:style="inputStyle" :name="name" data-date-format="YYYY-MM-DD"
:min="setMinDate" :max="setMaxDate"
/>
>
</div>
<div :class="inputClasses" v-else>
<div v-else :class="inputClasses">
<div class="flex -mx-2">
<p class="text-gray-900 px-4">From</p>
<input :type="useTime ? 'datetime-local' : 'date'" :id="id?id:name" v-model="fromDate" :disabled="disabled"
<p class="text-gray-900 px-4">
From
</p>
<input :id="id?id:name" v-model="fromDate" :type="useTime ? 'datetime-local' : 'date'" :disabled="disabled?true:null"
:style="inputStyle" :name="name" data-date-format="YYYY-MM-DD"
class="flex-grow border-transparent focus:outline-none "
:min="setMinDate" :max="setMaxDate"
/>
<p class="text-gray-900 px-4">To</p>
<input v-if="dateRange" :type="useTime ? 'datetime-local' : 'date'" :id="id?id:name" v-model="toDate"
:disabled="disabled"
>
<p class="text-gray-900 px-4">
To
</p>
<input v-if="dateRange" :id="id?id:name" v-model="toDate" :type="useTime ? 'datetime-local' : 'date'"
:disabled="disabled?true:null"
:style="inputStyle" :name="name" class="flex-grow border-transparent focus:outline-none"
:min="setMinDate" :max="setMaxDate"
/>
>
</div>
</div>
@ -58,18 +62,8 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},
@ -114,7 +108,7 @@ export default {
handler (val) {
if (this.dateRange) {
if (!Array.isArray(this.compVal)) {
this.compVal = [];
this.compVal = []
}
this.compVal[0] = this.dateToUTC(val)
} else {
@ -127,7 +121,7 @@ export default {
handler (val) {
if (this.dateRange) {
if (!Array.isArray(this.compVal)) {
this.compVal = [null];
this.compVal = [null]
}
this.compVal[1] = this.dateToUTC(val)
} else {
@ -187,7 +181,7 @@ export default {
String(dateObj.getDate()).padStart(2, '0')
if (this.useTime) {
dateStr += 'T' + String(dateObj.getHours()).padStart(2, '0') + ':' +
String(dateObj.getMinutes()).padStart(2, '0');
String(dateObj.getMinutes()).padStart(2, '0')
}
return dateStr
}

View File

@ -1,6 +1,6 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
@ -94,18 +94,8 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},

View File

@ -1,6 +1,6 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
@ -51,18 +51,8 @@ export default {
multiple: { type: Boolean, default: false }
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},
data () {

View File

@ -1,6 +1,6 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
@ -121,18 +121,8 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},
@ -156,7 +146,7 @@ export default {
handler (val) {
document.removeEventListener('paste', this.onUploadPasteEvent)
if (this.showUploadModal) {
document.addEventListener("paste", this.onUploadPasteEvent)
document.addEventListener('paste', this.onUploadPasteEvent)
}
}
}

View File

@ -1,6 +1,6 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
@ -9,7 +9,7 @@
<div :id="id ? id : name" :name="name" :style="inputStyle" class="flex items-center">
<v-select v-model="selectedCountryCode" class="w-[130px]" dropdown-class="w-[300px]" input-class="rounded-r-none"
:data="countries"
:disabled="disabled || countries.length===1" :searchable="true" :search-keys="['name']" :option-key="'code'" :color="color"
:disabled="(disabled || countries.length===1)?true:null" :searchable="true" :search-keys="['name']" :option-key="'code'" :color="color"
:has-error="hasValidation && form.errors.has(name)"
:placeholder="'Select a country'" :uppercase-labels="true" :theme="theme" @update:model-value="onChangeCountryCode"
>
@ -27,7 +27,7 @@
</div>
</template>
</v-select>
<input v-model="inputVal" type="text" class="inline-flex-grow !border-l-0 !rounded-l-none" :disabled="disabled"
<input v-model="inputVal" type="text" class="inline-flex-grow !border-l-0 !rounded-l-none" :disabled="disabled?true:null"
:class="[theme.default.input, { '!ring-red-500 !ring-2': hasValidation && form.errors.has(name), '!cursor-not-allowed !bg-gray-200': disabled }]"
:placeholder="placeholder" :style="inputStyle" @update:model-value="onInput"
>
@ -40,7 +40,6 @@
<template #error>
<slot name="error" />
</template>
</input-wrapper>
</template>
@ -61,18 +60,8 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},

View File

@ -1,12 +1,11 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
</template>
<div class="stars-outer">
<div v-for="i in numberOfStars" :key="i"
class="cursor-pointer inline-block text-gray-200 dark:text-gray-800"
@ -46,22 +45,11 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},
data () {
return {
hoverRating: -1

View File

@ -1,12 +1,12 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
</template>
<vue-editor :id="id?id:name" ref="editor" v-model="compVal" :disabled="disabled"
<vue-editor :id="id?id:name" ref="editor" v-model="compVal" :disabled="disabled?true:null"
:placeholder="placeholder" :class="[{ '!ring-red-500 !ring-2': hasValidation && form.errors.has(name), '!cursor-not-allowed !bg-gray-200':disabled }, theme.RichTextAreaInput.input]"
:editor-toolbar="editorToolbar" class="rich-editor resize-y"
:style="inputStyle"
@ -48,18 +48,8 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
}

View File

@ -1,6 +1,6 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
@ -41,18 +41,8 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},
@ -62,7 +52,7 @@ export default {
computed: {
scaleList () {
let list = []
const list = []
for (let i = this.minScale; i <= this.maxScale; i += this.stepScale) {
list.push(i)
}

View File

@ -1,6 +1,6 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
@ -21,7 +21,7 @@
:theme="theme"
:has-error="hasValidation && form.errors.has(name)"
:allow-creation="allowCreation"
:disabled="disabled"
:disabled="disabled?true:null"
:help="help"
:help-position="helpPosition"
@ -97,20 +97,11 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},
data () {
return {
additionalOptions: []

View File

@ -1,6 +1,6 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
@ -39,18 +39,8 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},

View File

@ -1,12 +1,12 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
</template>
<textarea :id="id?id:name" v-model="compVal" :disabled="disabled"
<textarea :id="id?id:name" v-model="compVal" :disabled="disabled?true:null"
:class="[theme.default.input,{ '!ring-red-500 !ring-2': hasValidation && form.errors.has(name), '!cursor-not-allowed !bg-gray-200':disabled }]"
class="resize-y"
:name="name" :style="inputStyle"
@ -38,23 +38,15 @@ export default {
props: {
...inputProps,
maxCharLimit: { type: Number, required: false, default: null },
showCharLimit: { type: Boolean, required: false, default: false },
showCharLimit: { type: Boolean, required: false, default: false }
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
setup (props, context) {
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},
computed: {
charCount () {
return (this.compVal) ? this.compVal.length : 0

View File

@ -1,12 +1,12 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
</template>
<input :id="id?id:name" v-model="compVal" :disabled="disabled"
<input :id="id?id:name" v-model="compVal" :disabled="disabled?true:null"
:type="nativeType"
:pattern="pattern"
:style="inputStyle"
@ -48,13 +48,6 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context, props.nativeType === 'file' ? 'file-' : null)
const onChange = (event) => {
if (props.nativeType !== 'file') return
@ -69,10 +62,9 @@ export default {
}
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context, props.nativeType === 'file' ? 'file-' : null),
onEnterPress,
onChange
}
},
computed: {

View File

@ -1,11 +1,11 @@
<template>
<input-wrapper v-bind="$props">
<input-wrapper v-bind="inputWrapperProps">
<template #label>
<span />
</template>
<div class="flex">
<v-switch :id="id?id:name" v-model="compVal" class="inline-block mr-2" :disabled="disabled" />
<v-switch :id="id?id:name" v-model="compVal" class="inline-block mr-2" :disabled="disabled?true:null" />
<slot name="label">
<span>{{ label }} <span v-if="required" class="text-red-500 required-dot">*</span></span>
</slot>
@ -34,13 +34,8 @@ export default {
},
setup (props, context) {
const { compVal, inputStyle, hasValidation, hasError } = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},

View File

@ -38,12 +38,12 @@ export default {
props: {
id: { type: String, required: false },
name: { type: String, required: false },
theme: { type: Object, required: true },
label: { type: String, required: false },
form: { type: Object, required: false },
theme: { type: Object, required: true },
wrapperClass: { type: String, required: false },
inputStyle: { type: Object, required: false },
help: { type: String, required: false },
label: { type: String, required: false },
helpPosition: { type: String, default: 'below_input' },
uppercaseLabels: { type: Boolean, default: true },
hideFieldName: { type: Boolean, default: true },

View File

@ -7,7 +7,7 @@
type="checkbox"
:class="sizeClasses"
class="rounded border-gray-500 cursor-pointer"
:disabled="disabled"
:disabled="disabled?true:null"
@click="handleClick"
>
<label :for="id || name" class="text-gray-700 dark:text-gray-300 ml-2" :class="{'!cursor-not-allowed':disabled}">

View File

@ -6,6 +6,7 @@ export const inputProps = {
name: { type: String, required: true },
label: { type: String, required: false },
form: { type: Object, required: false },
theme: { type: Object, default: () => themes.default },
modelValue: { required: false },
required: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
@ -14,7 +15,6 @@ export const inputProps = {
hideFieldName: { type: Boolean, default: false },
help: { type: String, default: null },
helpPosition: { type: String, default: 'below_input' },
theme: { type: Object, default: () => themes.default },
color: { type: String, default: '#3B82F6' },
wrapperClass: { type: String, default: 'relative mb-3' }
}
@ -58,6 +58,16 @@ export function useFormInput (props, context, formPrefixKey = null) {
}
})
const inputWrapperProps = computed(() => {
const wrapperProps = {}
Object.keys(inputProps).forEach((key) => {
if (!['modelValue', 'disabled', 'placeholder', 'color'].includes(key)) {
wrapperProps[key] = props[key]
}
})
return wrapperProps
})
// Watch for changes in props.modelValue and update the local content
watch(
() => props.modelValue,
@ -72,6 +82,7 @@ export function useFormInput (props, context, formPrefixKey = null) {
compVal,
inputStyle,
hasValidation,
hasError
hasError,
inputWrapperProps
}
}

View File

@ -1,5 +1,5 @@
<template>
<button :type="nativeType" :disabled="loading" :class="`py-${sizes['p-y']} px-${sizes['p-x']} text-${sizes['font']} ${theme.Button.body}`" :style="buttonStyle"
<button :type="nativeType" :disabled="loading?true:null" :class="`py-${sizes['p-y']} px-${sizes['p-x']} text-${sizes['font']} ${theme.Button.body}`" :style="buttonStyle"
class="btn" @click="$emit('click',$event)"
>
<template v-if="!loading">

View File

@ -12,7 +12,8 @@
@click.prevent="openAddFieldSidebar"
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="3"
stroke="currentColor" class="w-5 h-5">
stroke="currentColor" class="w-5 h-5"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
</div>
@ -50,7 +51,7 @@
</div>
<component :is="getFieldComponents" v-if="getFieldComponents"
v-bind="inputProperties(field)" :required="isFieldRequired"
:disabled="isFieldDisabled"
:disabled="isFieldDisabled?true:null"
/>
<template v-else>
<div v-if="field.type === 'nf-text' && field.content" :id="field.id" :key="field.id"
@ -79,7 +80,7 @@
<script>
import { computed } from 'vue'
import { useWorkingFormStore } from '../../../stores/working_form';
import { useWorkingFormStore } from '../../../stores/working_form'
import FormLogicPropertyResolver from '../../../forms/FormLogicPropertyResolver.js'
import FormPendingSubmissionKey from '../../../mixins/forms/form-pending-submission-key.js'
@ -309,7 +310,7 @@ export default {
} else if (field.type === 'files' || (field.type === 'url' && field.file_upload)) {
inputProperties.multiple = (field.multiple !== undefined && field.multiple)
inputProperties.mbLimit = 5
inputProperties.accept = (this.form.is_pro && field.allowed_file_types) ? field.allowed_file_types : ""
inputProperties.accept = (this.form.is_pro && field.allowed_file_types) ? field.allowed_file_types : ''
} else if (field.type === 'number' && field.is_rating) {
inputProperties.numberOfStars = parseInt(field.rating_max_value)
} else if (field.type === 'number' && field.is_scale) {
@ -323,7 +324,7 @@ export default {
}
return inputProperties
},
}
}
}
</script>

View File

@ -11,7 +11,7 @@
</template>
<toggle-switch-input :value="value.hide_title" name="hide_title" class="mt-4"
label="Hide Form Title"
:disabled="form.hide_title===true"
:disabled="(form.hide_title===true)?true:null"
:help="hideTitleHelp"
@update:model-value="onChangeHideTitle"
/>

View File

@ -27,7 +27,7 @@
Submission confirmation
<pro-tag />
</h2>
<toggle-switch-input :disabled="emailSubmissionConfirmationField===null" name="send_submission_confirmation"
<toggle-switch-input :disabled="(emailSubmissionConfirmationField===null)?true:null" name="send_submission_confirmation"
:form="form" class="mt-4"
label="Send submission confirmation" :help="emailSubmissionConfirmationHelp"
/>

View File

@ -10,8 +10,8 @@
/>
<template v-if="hasInput">
<component :is="inputComponentData.component" v-model="content.value" class="w-full"
:name="'value_'+property.id" v-bind="inputComponentData" placeholder="Filter Value"
<component v-bind="inputComponentData" :is="inputComponentData.component" v-model="content.value" class="w-full"
:name="'value_'+property.id" placeholder="Filter Value"
@input="$emit('input',castContent(content))"
/>
</template>
@ -62,7 +62,7 @@ export default {
}
if (['select', 'multi_select'].includes(this.property.type)) {
componentData.multiple = false;
componentData.multiple = false
componentData.options = this.property[this.property.type].options.map(option => {
return {
name: option.name,
@ -97,7 +97,7 @@ export default {
this.content.property_meta = {
id: this.property.id,
type: this.property.type,
type: this.property.type
}
this.isMounted = true
},

View File

@ -7,7 +7,7 @@
option-key="identifier"
name="group-control-slot-rule"
/>
<v-button class="ml-1 mt-1" color="blue" size="small" :disabled="selectedRule === ''" @click="addRule">
<v-button class="ml-1 mt-1" color="blue" size="small" :disabled="(selectedRule === '')?true:null" @click="addRule">
Add Condition
</v-button>
<v-button class="ml-1 mt-1" color="outline-blue" size="small" @click="groupCtrl.newGroup">

View File

@ -1,18 +1,20 @@
<template>
<modal :show="show" @close="$emit('close')" :closeable="!aiForm.busy">
<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"
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"
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,38 +26,48 @@
AI-powered form generator
</template>
</template>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-8" v-if="state=='default'">
<div class="rounded-md border p-4 flex flex-col items-center cursor-pointer hover:bg-gray-50"
@click="$emit('close')" v-track.select_form_base="{base:'contact-form'}">
<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'}"
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>
</div>
<p class="font-medium">Start from a simple contact form</p>
<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" @click="state='ai'">
class="rounded-md border p-4 flex flex-col items-center cursor-pointer hover:bg-gray-50" @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"
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">Use our AI to create the form</p>
<p class="font-medium text-blue-700">
Use our AI to create the form
</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="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
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>
<router-link :to="{name:'templates'}" v-track.select_form_base="{base:'template'}" class="absolute inset-0"/>
<p class="font-medium">
Start from a template
</p>
<router-link v-track.select_form_base="{base:'template'}" :to="{name:'templates'}" class="absolute inset-0" />
</div>
</div>
<div v-else-if="state=='ai'">
@ -65,26 +77,29 @@
</svg>
Back
</a>
<text-area-input label="Form Description" :disabled="loading" :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" @click.prevent="generateForm" :loading="loading">
<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">
Generate a form
</v-button>
<p class="text-gray-500 text-xs text-center mt-1">~60 sec</p>
<p class="text-gray-500 text-xs text-center mt-1">
~60 sec
</p>
</div>
</modal>
</template>
<script>
import Loader from "../../../common/Loader.vue";
import Form from "vform";
import axios from "axios";
import Loader from '../../../common/Loader.vue'
import Form from 'vform'
import axios from 'axios'
export default {
name: 'CreateFormBaseModal',
components: { Loader },
props: {
show: {type: Boolean, required: true},
show: { type: Boolean, required: true }
},
data: () => ({
@ -92,7 +107,7 @@ export default {
aiForm: new Form({
form_prompt: ''
}),
loading: false,
loading: false
}),
computed: {

View File

@ -70,7 +70,7 @@
<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"
:disabled="(form.hide_title===true)?true:null"
:help="hideTitleHelp"
/>
<color-input v-model="advancedOptions.bgcolor" name="bgcolor" class="mt-4"

View File

@ -2,8 +2,8 @@
<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" class="mt-6 block mx-auto"
@click="saveDetails" :arrow="true"
<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>
@ -50,6 +50,8 @@ export default {
loading: false
}),
computed: {},
watch: {
user () {
this.updateUser()
@ -94,8 +96,6 @@ export default {
close () {
this.$emit('close')
}
},
computed: {}
}
}
</script>

View File

@ -4,11 +4,13 @@
<div class="flex bg-gray-50">
<div class="w-full md:w-4/5 lg:w-3/5 md:mx-auto md:max-w-4xl px-4">
<div class="pt-4 pb-0">
<a href="#" @click.prevent="goBack" class="flex text-blue mb-2 font-semibold text-sm">
<a href="#" class="flex text-blue mb-2 font-semibold text-sm" @click.prevent="goBack">
<svg class="w-3 h-3 text-blue mt-1 mr-1" viewBox="0 0 6 10" fill="none"
xmlns="http://www.w3.org/2000/svg">
xmlns="http://www.w3.org/2000/svg"
>
<path d="M5 9L1 5L5 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"
stroke-linejoin="round"/>
stroke-linejoin="round"
/>
</svg>
Go back
</a>
@ -20,24 +22,30 @@
<div class="flex">
<extra-menu :form="form" />
<v-button target="_blank" :to="{name:'forms.show_public', params: {slug: form.slug}}"
color="white" class="mr-2 text-blue-600 hidden sm:block"
v-track.view_form_click="{form_id:form.id, form_slug:form.slug}">
<v-button v-track.view_form_click="{form_id:form.id, form_slug:form.slug}" target="_blank"
:to="{name:'forms.show_public', params: {slug: form.slug}}" color="white"
class="mr-2 text-blue-600 hidden sm:block"
>
<svg class="w-6 h-6 inline -mt-1" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
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"/>
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>
</v-button>
<v-button class="text-white" @click="openEdit">
<svg class="inline mr-1 -mt-1" width="18" height="17" viewBox="0 0 18 17" fill="none"
xmlns="http://www.w3.org/2000/svg">
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 form
</v-button>
@ -53,11 +61,13 @@
</p>
<div v-if="['draft','closed'].includes(form.visibility) || (form.tags && form.tags.length > 0)" class="mt-2 flex items-center flex-wrap gap-3">
<span v-if="form.visibility=='draft'"
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700">
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700"
>
Draft - not publicly accessible
</span>
<span v-else-if="form.visibility=='closed'"
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700">
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700"
>
Closed - won't accept new submissions
</span>
<span v-for="(tag,i) in form.tags" :key="tag"
@ -84,15 +94,16 @@
<div class="border-b border-gray-200 dark:border-gray-700">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center">
<li class="mr-6" v-for="(tab, i) in tabsList" :key="i+1">
<li v-for="(tab, i) in tabsList" :key="i+1" class="mr-6">
<router-link :to="{ name: tab.route }"
class="hover:no-underline inline-block py-4 rounded-t-lg border-b-2 text-gray-500 hover:text-gray-600"
active-class="text-blue-600 hover:text-blue-900 dark:text-blue-500 dark:hover:text-blue-500 border-blue-600 dark:border-blue-500"
>{{tab.name}}</router-link>
>
{{ tab.name }}
</router-link>
</li>
</ul>
</div>
</div>
</div>
</div>
@ -100,7 +111,11 @@
<div class="w-full md:w-4/5 lg:w-3/5 md:mx-auto md:max-w-4xl px-4">
<div class="py-4">
<transition name="fade" mode="out-in">
<router-view :form="form" />
<router-view v-slot="{ Component }">
<transition name="page" mode="out-in">
<component :is="Component" :form="form" />
</transition>
</router-view>
</transition>
</div>
</div>
@ -123,7 +138,7 @@ import { useFormsStore } from '../../../stores/forms'
import { useWorkingFormStore } from '../../../stores/working_form'
import { useWorkspacesStore } from '../../../stores/workspaces'
import ProTag from '../../../components/common/ProTag.vue'
import VButton from "../../../components/common/Button.vue";
import VButton from '../../../components/common/Button.vue'
import ExtraMenu from '../../../components/pages/forms/show/ExtraMenu.vue'
import SeoMeta from '../../../mixins/seo-meta.js'
import FormCleanings from '../../../components/pages/forms/show/FormCleanings.vue'
@ -216,14 +231,14 @@ export default {
},
displayClosesDate () {
if (this.form.closes_at) {
let dateObj = new Date(this.form.closes_at)
return dateObj.getFullYear() + "-" +
String(dateObj.getMonth() + 1).padStart(2, '0') + "-" +
String(dateObj.getDate()).padStart(2, '0') + " " +
String(dateObj.getHours()).padStart(2, '0') + ":" +
const dateObj = new Date(this.form.closes_at)
return dateObj.getFullYear() + '-' +
String(dateObj.getMonth() + 1).padStart(2, '0') + '-' +
String(dateObj.getDate()).padStart(2, '0') + ' ' +
String(dateObj.getHours()).padStart(2, '0') + ':' +
String(dateObj.getMinutes()).padStart(2, '0')
}
return "";
return ''
}
},

View File

@ -14,11 +14,13 @@
<div class="mt-4 border-b border-gray-200 dark:border-gray-700">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center">
<li class="mr-6" v-for="(tab, i) in tabsList" :key="i+1">
<li v-for="(tab, i) in tabsList" :key="i+1" class="mr-6">
<router-link :to="{ name: tab.route }"
class="hover:no-underline inline-block py-4 rounded-t-lg border-b-2 text-gray-500 hover:text-gray-600"
active-class="text-blue-600 hover:text-blue-900 dark:text-blue-500 dark:hover:text-blue-500 border-blue-600 dark:border-blue-500"
>{{tab.name}}</router-link>
>
{{ tab.name }}
</router-link>
</li>
</ul>
</div>
@ -28,9 +30,11 @@
<div class="flex bg-white">
<div class="w-full md:w-4/5 lg:w-3/5 md:mx-auto md:max-w-4xl px-4">
<div class="mt-8 pb-0">
<transition name="fade" mode="out-in">
<router-view />
<router-view v-slot="{ Component }">
<transition name="page" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</div>
</div>
</div>