Add client linting to GitHub Actions workflow

Enhance CI/CD pipeline by introducing a new job to run ESLint on the client application. This ensures code quality and consistency for the frontend codebase.
This commit is contained in:
Julien Nahum 2025-01-27 18:24:27 +01:00
parent a91c194161
commit 4a2adcf8f7
38 changed files with 302 additions and 302 deletions

View File

@ -50,6 +50,35 @@ jobs:
working-directory: ./api working-directory: ./api
run: ./vendor/bin/pint --test run: ./vendor/bin/pint --test
client_lint:
runs-on: ubuntu-latest
name: Run client linters
defaults:
run:
working-directory: ./client
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v1
with:
node-version: "20"
- uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install npm dependencies
run: npm ci --no-audit --no-progress --silent
- name: Run ESLint
run: npm run lint
tests: tests:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -199,7 +228,7 @@ jobs:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
production-deploy: production-deploy:
needs: [code_lint, tests, build-nuxt-app] needs: [code_lint, client_lint, tests, build-nuxt-app]
if: success() && github.ref == 'refs/heads/main' && github.event_name == 'push' if: success() && github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Triggers Deployment (Vapor & Amplify) name: Triggers Deployment (Vapor & Amplify)

View File

@ -285,7 +285,7 @@ export default {
src: this.getFileSrc(file) src: this.getFileSrc(file)
}) })
this.loading = false this.loading = false
}).catch((error) => { }).catch(() => {
this.loading = false this.loading = false
}) })
} else { } else {
@ -297,7 +297,7 @@ export default {
this.loading = false this.loading = false
} }
}) })
.catch((error) => { .catch(() => {
this.clearAll() this.clearAll()
this.loading = false this.loading = false
}) })

View File

@ -56,7 +56,7 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, watch, computed } from 'vue' import { ref, onMounted, watch } from 'vue'
import { inputProps, useFormInput } from './useFormInput.js' import { inputProps, useFormInput } from './useFormInput.js'
import InputWrapper from './components/InputWrapper.vue' import InputWrapper from './components/InputWrapper.vue'
import MentionDropdown from './components/MentionDropdown.vue' import MentionDropdown from './components/MentionDropdown.vue'
@ -66,7 +66,7 @@
disableMention: { type: Boolean, default: false }, disableMention: { type: Boolean, default: false },
}) })
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { compVal, inputStyle, hasValidation, hasError, inputWrapperProps } = useFormInput(props, { emit }) const { compVal, inputStyle, hasError, inputWrapperProps } = useFormInput(props, { emit })
const editableDiv = ref(null) const editableDiv = ref(null)
const savedRange = ref(null) const savedRange = ref(null)
const subscriptionModalStore = useSubscriptionModalStore() const subscriptionModalStore = useSubscriptionModalStore()

View File

@ -169,7 +169,7 @@ export default {
} }
this.loading = false this.loading = false
}) })
.catch((error) => { .catch(() => {
this.loading = false this.loading = false
this.file = null this.file = null
}) })

View File

@ -1,8 +1,8 @@
<template> <template>
<div <div
v-html="processedContent" v-html="processedContent"
/> />
</template> </template>
<script setup> <script setup>
const props = defineProps({ const props = defineProps({

View File

@ -22,7 +22,7 @@
</template> </template>
<script setup> <script setup>
const props = defineProps({ defineProps({
isChecked: { isChecked: {
type: Boolean, type: Boolean,
required: true required: true

View File

@ -1,71 +1,71 @@
<template> <template>
<UPopover <UPopover
ref="popover" ref="popover"
v-model:open="open" v-model:open="open"
class="h-0" class="h-0"
@close="cancel" @close="cancel"
> >
<span class="hidden" /> <span class="hidden" />
<template #panel> <template #panel>
<div class="p-2 max-h-[300px] flex flex-col"> <div class="p-2 max-h-[300px] flex flex-col">
<div class="flex items-center border-b -mx-2 px-2"> <div class="flex items-center border-b -mx-2 px-2">
<div class="font-semibold w-1/2 mb-2 flex-grow"> <div class="font-semibold w-1/2 mb-2 flex-grow">
Insert Mention Insert Mention
</div>
<input
v-model="fallbackValue"
class="p-1 mb-2 text-sm w-1/2 border rounded-md hover:bg-gray-50"
placeholder="Fallback value"
>
</div> </div>
<div class="overflow-scroll pt-2"> <input
<div class="w-full max-w-xs mb-2"> v-model="fallbackValue"
<div class="text-sm text-gray-500 mb-1"> class="p-1 mb-2 text-sm w-1/2 border rounded-md hover:bg-gray-50"
Select a field placeholder="Fallback value"
</div> >
<div class="space-y-1"> </div>
<div <div class="overflow-scroll pt-2">
v-for="field in filteredMentions" <div class="w-full max-w-xs mb-2">
:key="field.id" <div class="text-sm text-gray-500 mb-1">
class="flex items-center p-2 rounded-md cursor-pointer hover:bg-gray-100" Select a field
:class="{ 'bg-blue-50 border border-blue-100 inset-0': selectedField?.id === field.id, 'border border-transparent': selectedField?.id !== field.id }" </div>
@click="selectField(field)" <div class="space-y-1">
@dblclick="selectField(field, true)" <div
> v-for="field in filteredMentions"
<BlockTypeIcon :key="field.id"
:type="field.type" class="flex items-center p-2 rounded-md cursor-pointer hover:bg-gray-100"
class="mr-2" :class="{ 'bg-blue-50 border border-blue-100 inset-0': selectedField?.id === field.id, 'border border-transparent': selectedField?.id !== field.id }"
/> @click="selectField(field)"
<p class="text-sm text-gray-700 truncate"> @dblclick="selectField(field, true)"
{{ field.name }} >
</p> <BlockTypeIcon
</div> :type="field.type"
class="mr-2"
/>
<p class="text-sm text-gray-700 truncate">
{{ field.name }}
</p>
</div> </div>
</div> </div>
</div> </div>
<div class="flex border-t pt-2 -mx-2 px-2 justify-end space-x-2">
<UButton
size="sm"
color="primary"
class="px-6"
:disabled="!selectedField"
@click="insertMention"
>
Insert
</UButton>
<UButton
size="sm"
color="gray"
@click="cancel"
>
Cancel
</UButton>
</div>
</div> </div>
</template>
</UPopover> <div class="flex border-t pt-2 -mx-2 px-2 justify-end space-x-2">
</template> <UButton
size="sm"
color="primary"
class="px-6"
:disabled="!selectedField"
@click="insertMention"
>
Insert
</UButton>
<UButton
size="sm"
color="gray"
@click="cancel"
>
Cancel
</UButton>
</div>
</div>
</template>
</UPopover>
</template>
<script setup> <script setup>
import { ref, toRefs } from 'vue' import { ref, toRefs } from 'vue'

View File

@ -1,9 +1,9 @@
<template> <template>
<div <div
ref="container" ref="container"
class="quilly-editor" class="quilly-editor"
/> />
</template> </template>
<script setup> <script setup>
import Quill from 'quill' import Quill from 'quill'

View File

@ -22,7 +22,7 @@
</template> </template>
<script setup> <script setup>
const props = defineProps({ defineProps({
isChecked: { isChecked: {
type: Boolean, type: Boolean,
required: true required: true

View File

@ -1,12 +1,12 @@
<template> <template>
<slot v-if="!error" /> <slot v-if="!error" />
<slot <slot
v-else v-else
name="error" name="error"
:error="error" :error="error"
:clear-error="clearError" :clear-error="clearError"
/> />
</template> </template>
<script setup> <script setup>
const error = ref() const error = ref()

View File

@ -25,7 +25,7 @@
</template> </template>
<script setup> <script setup>
const props = defineProps({ defineProps({
name: { name: {
type: String, type: String,
default: "slideInUp" default: "slideInUp"

View File

@ -59,7 +59,7 @@ const props = defineProps({
} }
}) })
const emit = defineEmits(['select-font']) defineEmits(['select-font'])
const getFontUrl = computed(() => { const getFontUrl = computed(() => {
const family = props.fontName.replace(/ /g, '+') const family = props.fontName.replace(/ /g, '+')

View File

@ -80,7 +80,7 @@ const props = defineProps({
}, },
}) })
const emit = defineEmits(['close','apply']) defineEmits(['close','apply'])
const loading = ref(false) const loading = ref(false)
const fonts = ref([]) const fonts = ref([])
const selectedFont = ref(props.font || null) const selectedFont = ref(props.font || null)

View File

@ -117,14 +117,14 @@
:admin-preview="adminPreview" :admin-preview="adminPreview"
@submit="submitForm" @submit="submitForm"
> >
<template #submit-btn="{submitForm}"> <template #submit-btn="{rootSubmitForm}">
<open-form-button <open-form-button
:loading="loading" :loading="loading"
:theme="theme" :theme="theme"
:color="form.color" :color="form.color"
class="mt-2 px-8 mx-1" class="mt-2 px-8 mx-1"
:class="submitButtonClass" :class="submitButtonClass"
@click.prevent="submitForm" @click.prevent="rootSubmitForm"
> >
{{ form.submit_button_text }} {{ form.submit_button_text }}
</open-form-button> </open-form-button>
@ -223,6 +223,8 @@ export default {
} }
}, },
emits: ['submitted', 'password-entered', 'restarted'],
setup(props) { setup(props) {
const { setLocale } = useI18n() const { setLocale } = useI18n()
const authStore = useAuthStore() const authStore = useAuthStore()

View File

@ -174,7 +174,7 @@ export default {
adminPreview: {type: Boolean, default: false} // If used in FormEditorPreview adminPreview: {type: Boolean, default: false} // If used in FormEditorPreview
}, },
setup(props) { setup() {
const workingFormStore = useWorkingFormStore() const workingFormStore = useWorkingFormStore()
return { return {
workingFormStore, workingFormStore,

View File

@ -74,7 +74,7 @@ const props = defineProps({
show: { type: Boolean, required: true }, show: { type: Boolean, required: true },
form: { type: Object, required: true } form: { type: Object, required: true }
}) })
const emit = defineEmits(['close']) defineEmits(['close'])
const confetti = useConfetti() const confetti = useConfetti()
const crisp = useCrisp() const crisp = useCrisp()
const amplitude = useAmplitude() const amplitude = useAmplitude()

View File

@ -33,47 +33,45 @@
</ErrorBoundary> </ErrorBoundary>
</template> </template>
<script setup> <script setup>
import { computed } from 'vue' const crisp = useCrisp()
const crisp = useCrisp() const workingFormStore = useWorkingFormStore()
const workingFormStore = useWorkingFormStore() const form = storeToRefs(workingFormStore).content
const authStore = useAuthStore()
const form = storeToRefs(workingFormStore).content // Clear error and go back 1 step in history
const user = computed(() => authStore.user) const clearEditorError = (error, clearError) => {
// Clear error and go back 1 step in history crisp.enableChatbot()
const clearEditorError = (error, clearError) => { workingFormStore.undo()
crisp.enableChatbot() clearError()
workingFormStore.undo() }
clearError() const onFormEditorError = (error) => {
} console.error('Form Editor Error Handled', error)
const onFormEditorError = (error) => { crisp.pauseChatBot()
console.error('Form Editor Error Handled', error) const eventData = {
crisp.pauseChatBot() message: error.message,
const eventData = { // take first 200 characters
message: error.message, stack: error.stack.substring(0, 100)
// take first 200 characters }
stack: error.stack.substring(0, 100) try {
} crisp.pushEvent('form-editor-error', eventData)
try { } catch (e) {
crisp.pushEvent('form-editor-error', eventData) console.error('Failed to send event to crisp', e, eventData)
} catch (e) { }
console.error('Failed to send event to crisp', e, eventData) }
} const onErrorContact = (error) => {
} crisp.pauseChatBot()
const onErrorContact = (error) => { let errorReport = 'Hi there, I have a technical issue with the form editor.'
crisp.pauseChatBot() if (form.value.slug) {
let errorReport = 'Hi there, I have a technical issue with the form editor.' errorReport += ` The form I am editing is: \`${form.value.slug}\`.`
if (form.value.slug) { }
errorReport += ` The form I am editing is: \`${form.value.slug}\`.` errorReport += ` And here are technical details about the error: \`\`\`${error.stack}\`\`\``
} try {
errorReport += ` And here are technical details about the error: \`\`\`${error.stack}\`\`\`` crisp.openAndShowChat(errorReport)
try { crisp.showMessage(`Hi there, we're very sorry to hear you experienced an issue with OpnForm.
crisp.openAndShowChat(errorReport) We'll be in touch about it very soon! In the meantime, I recommend that you try going back one step, and save your changes.`, 2000)
crisp.showMessage(`Hi there, we're very sorry to hear you experienced an issue with OpnForm. } catch (e) {
We'll be in touch about it very soon! In the meantime, I recommend that you try going back one step, and save your changes.`, 2000) console.error('Crisp error', e)
} catch (e) { }
console.error('Crisp error', e) }
} </script>
}
</script>

View File

@ -118,13 +118,4 @@ const onSubmit = () => {
loading.value = false loading.value = false
}) })
} }
const isUrl = (str) => {
try {
new URL(str)
} catch (_) {
return false
}
return true
}
</script> </script>

View File

@ -1,30 +1,30 @@
<template> <template>
<div <div
class="border border-nt-blue-light bg-blue-50 dark:bg-notion-dark-light rounded-md p-2 overflow-hidden" class="border border-nt-blue-light bg-blue-50 dark:bg-notion-dark-light rounded-md p-2 overflow-hidden"
> >
<div class="flex items-center w-full gap-2"> <div class="flex items-center w-full gap-2">
<p class="select-all text-nt-blue flex-grow truncate overflow-hidden"> <p class="select-all text-nt-blue flex-grow truncate overflow-hidden">
<a <a
v-if="link" v-if="link"
:href="share_url" :href="share_url"
target="_blank" target="_blank"
> >
{{ share_url }} {{ share_url }}
</a> </a>
<span v-else> <span v-else>
{{ share_url }} {{ share_url }}
</span> </span>
</p> </p>
<UButton <UButton
class="shrink-0" class="shrink-0"
size="sm" size="sm"
icon="i-heroicons-clipboard-document" icon="i-heroicons-clipboard-document"
label="Copy" label="Copy"
@click="copyToClipboard" @click="copyToClipboard"
/> />
</div>
</div> </div>
</template> </div>
</template>
<script setup> <script setup>
import { computed, defineProps } from 'vue' import { computed, defineProps } from 'vue'

View File

@ -56,7 +56,7 @@ const props = defineProps({
} }
}) })
const emit = defineEmits(['update:field']) defineEmits(['update:field'])
const options = ref([ const options = ref([
{ {

View File

@ -1,15 +1,17 @@
<template> <template>
<p class="font-semibold">Prefilled values</p> <p class="font-semibold">
<select-input Prefilled values
v-for="row in matrixData" </p>
:key="row.label" <select-input
name="prefill" v-for="row in matrixData"
class="mt-3" :key="row.label"
:options="row.options" v-model="selection[row.label]"
:label="row.label" name="prefill"
v-model="selection[row.label]" class="mt-3"
@update:model-value="onSelection" :options="row.options"
/> :label="row.label"
@update:model-value="onSelection"
/>
</template> </template>
<script> <script>
export default { export default {

View File

@ -15,9 +15,11 @@ import { default as _has } from 'lodash/has'
export default { export default {
components: {}, components: {},
props: { props: {
// eslint-disable-next-line vue/require-prop-types
property: { property: {
required: true required: true
}, },
// eslint-disable-next-line vue/require-prop-types
value: { value: {
required: true required: true
} }

View File

@ -35,8 +35,8 @@
</template> </template>
<script setup> <script setup>
const props = defineProps({ defineProps({
isWorkspaceAdmin: {}, isWorkspaceAdmin: { type: Boolean, default: false },
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false, default: false,

View File

@ -63,7 +63,7 @@ onMounted(() => {
loadingBillingEmail.value = false loadingBillingEmail.value = false
userCreated.value = true userCreated.value = true
form.billing_email = data.billing_email form.billing_email = data.billing_email
}).catch(error => { }).catch(() => {
loadingBillingEmail.value = false loadingBillingEmail.value = false
userCreated.value = false userCreated.value = false
}) })

View File

@ -26,7 +26,8 @@
</UTable> </UTable>
<div <div
v-if="forms?.length > pageCount" v-if="forms?.length > pageCount"
class="flex justify-end px-3 py-3.5 border-t border-gray-200 dark:border-gray-700"> class="flex justify-end px-3 py-3.5 border-t border-gray-200 dark:border-gray-700"
>
<UPagination <UPagination
v-model="page" v-model="page"
:page-count="pageCount" :page-count="pageCount"

View File

@ -42,6 +42,7 @@
<script setup> <script setup>
import {watch, ref} from "vue" import {watch, ref} from "vue"
// eslint-disable-next-line vue/require-prop-types
const props = defineProps(['user', 'showEditUserModal']) const props = defineProps(['user', 'showEditUserModal'])
const emit = defineEmits(['close', 'fetchUsers']) const emit = defineEmits(['close', 'fetchUsers'])
@ -73,7 +74,7 @@ const updateUserRole = () => {
useAlert().success("User role updated.") useAlert().success("User role updated.")
emit('fetchUsers') emit('fetchUsers')
emit('close') emit('close')
}).catch((error) => { }).catch(() => {
useAlert().error("There was an error updating user role") useAlert().error("There was an error updating user role")
}).finally(() => { }).finally(() => {
updatingUserRoleState.value = false updatingUserRoleState.value = false

View File

@ -3,6 +3,7 @@
size="sm" size="sm"
color="white" color="white"
icon="i-heroicons-eye-16-solid" icon="i-heroicons-eye-16-solid"
:loading="loading"
@click="impersonate" @click="impersonate"
> >
Impersonate User Impersonate User
@ -18,13 +19,13 @@ const authStore = useAuthStore()
const formsStore = useFormsStore() const formsStore = useFormsStore()
const workspacesStore = useWorkspacesStore() const workspacesStore = useWorkspacesStore()
let loading = ref(false) const loading = ref(false)
const impersonate = () => { const impersonate = () => {
loading = true loading.value = true
authStore.startImpersonating() authStore.startImpersonating()
opnFetch(`/moderator/impersonate/${props.user.id}`).then(async (data) => { opnFetch(`/moderator/impersonate/${props.user.id}`).then(async (data) => {
loading = false loading.value = false
// Save the token. // Save the token.
authStore.setToken(data.token, false) authStore.setToken(data.token, false)
@ -47,7 +48,7 @@ const impersonate = () => {
}) })
.catch((error) => { .catch((error) => {
useAlert().error(error.data.message) useAlert().error(error.data.message)
loading = false loading.value = false
}) })
} }
</script> </script>

View File

@ -1,16 +1,16 @@
<template> <template>
<AdminCard <AdminCard
title="Workspaces" title="Workspaces"
icon="heroicons:globe-alt" icon="heroicons:globe-alt"
>
<UTable
:loading-state="{ icon: 'i-heroicons-arrow-path-20-solid', label: 'Loading...' }"
:progress="{ color: 'primary', animation: 'carousel' }"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'No items.' }"
:columns="columns"
:rows="rows"
class="-mx-6"
> >
<UTable
:loading-state="{ icon: 'i-heroicons-arrow-path-20-solid', label: 'Loading...' }"
:progress="{ color: 'primary', animation: 'carousel' }"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'No items.' }"
:columns="columns"
:rows="rows"
class="-mx-6"
>
<template #plan-data="{ row }"> <template #plan-data="{ row }">
<span <span
class="text-xs select-all rounded-md px-2 py-1 border" class="text-xs select-all rounded-md px-2 py-1 border"
@ -19,18 +19,19 @@
{{ row.plan }} {{ row.plan }}
</span> </span>
</template> </template>
</UTable> </UTable>
<div <div
v-if="workspaces?.length > pageCount" v-if="workspaces?.length > pageCount"
class="flex justify-end px-3 py-3.5 border-t border-gray-200 dark:border-gray-700"> class="flex justify-end px-3 py-3.5 border-t border-gray-200 dark:border-gray-700"
<UPagination >
v-model="page" <UPagination
:page-count="pageCount" v-model="page"
:total="workspaces?.length" :page-count="pageCount"
/> :total="workspaces?.length"
</div> />
</AdminCard> </div>
</template> </AdminCard>
</template>
<script setup> <script setup>

View File

@ -84,7 +84,6 @@
<script setup> <script setup>
import { ref, defineProps, computed } from "vue" import { ref, defineProps, computed } from "vue"
import Dropdown from "~/components/global/Dropdown.vue"
import FormTemplateModal from "../../../open/forms/components/templates/FormTemplateModal.vue" import FormTemplateModal from "../../../open/forms/components/templates/FormTemplateModal.vue"
import FormWorkspaceModal from "../../../open/forms/components/FormWorkspaceModal.vue" import FormWorkspaceModal from "../../../open/forms/components/FormWorkspaceModal.vue"

View File

@ -74,7 +74,6 @@ import {watch} from "vue"
const crisp = useCrisp() const crisp = useCrisp()
const workspacesStore = useWorkspacesStore() const workspacesStore = useWorkspacesStore()
const workspace = computed(() => workspacesStore.getCurrent) const workspace = computed(() => workspacesStore.getCurrent)
const loading = computed(() => workspacesStore.loading)
const customDomainsForm = useForm({ const customDomainsForm = useForm({
custom_domain: "", custom_domain: "",

View File

@ -281,7 +281,7 @@ const removeUser = (index) => {
).then(() => { ).then(() => {
useAlert().success("User successfully removed.") useAlert().success("User successfully removed.")
getWorkspaceUsers() getWorkspaceUsers()
}).catch((error) => { }).catch(() => {
useAlert().error("There was an error removing user") useAlert().error("There was an error removing user")
}).finally(() => { }).finally(() => {
loadingUsers.value = false loadingUsers.value = false
@ -319,7 +319,7 @@ const leaveWorkSpace = (workspaceId) => {
useAlert().success("You have left the workspace.") useAlert().success("You have left the workspace.")
workspacesStore.remove(workspaceId) workspacesStore.remove(workspaceId)
getWorkspaceUsers() getWorkspaceUsers()
}).catch((error) => { }).catch(() => {
useAlert().error("There was an error leaving the workspace.") useAlert().error("There was an error leaving the workspace.")
}).finally(() => { }).finally(() => {
leaveWorkspaceLoadingState.value = false leaveWorkspaceLoadingState.value = false

View File

@ -32,8 +32,6 @@ function redirectToMainDomain(details = {}) {
export default defineNuxtRouteMiddleware((to) => { export default defineNuxtRouteMiddleware((to) => {
if (!customDomainUsed()) return if (!customDomainUsed()) return
const config = useRuntimeConfig()
const customDomainHeaderValue = useRequestHeaders()[customDomainHeaderName] const customDomainHeaderValue = useRequestHeaders()[customDomainHeaderName]
if ( if (
import.meta.server && import.meta.server &&

View File

@ -1,6 +1,5 @@
export default defineNuxtRouteMiddleware(() => { export default defineNuxtRouteMiddleware(() => {
const authStore = useAuthStore() const authStore = useAuthStore()
const runtimeConfig = useRuntimeConfig()
if (useFeatureFlag('self_hosted')) { if (useFeatureFlag('self_hosted')) {
if (authStore.check && authStore.user?.email === 'admin@opnform.com') { if (authStore.check && authStore.user?.email === 'admin@opnform.com') {

View File

@ -179,7 +179,6 @@
<script setup> <script setup>
import { computed } from "vue" import { computed } from "vue"
import VButton from "~/components/global/VButton.vue"
import ExtraMenu from "../../../components/pages/forms/show/ExtraMenu.vue" import ExtraMenu from "../../../components/pages/forms/show/ExtraMenu.vue"
import FormCleanings from "../../../components/pages/forms/show/FormCleanings.vue" import FormCleanings from "../../../components/pages/forms/show/FormCleanings.vue"

View File

@ -1,38 +1,36 @@
<template> <template>
<template> <div class="flex flex-grow mt-6 mb-10">
<div class="flex flex-grow mt-6 mb-10"> <div class="w-full md:w-2/3 md:mx-auto md:max-w-md px-4">
<div class="w-full md:w-2/3 md:mx-auto md:max-w-md px-4"> <div
<div v-if="loading"
v-if="loading" class="m-10"
class="m-10" >
<h3 class="my-6 text-center">
Please wait...
</h3>
<Loader class="h-6 w-6 mx-auto m-10" />
</div>
<div
v-else
class="m-6 flex flex-col items-center space-y-4"
>
<p class="text-center">
Unable to sign it at the moment.
</p>
<v-button
:to="{ name: 'login' }"
> >
<h3 class="my-6 text-center"> Back to login
Please wait... </v-button>
</h3>
<Loader class="h-6 w-6 mx-auto m-10" />
</div>
<div
v-else
class="m-6 flex flex-col items-center space-y-4"
>
<p class="text-center">
Unable to sign it at the moment.
</p>
<v-button
:to="{ name: 'login' }"
>
Back to login
</v-button>
</div>
</div> </div>
</div> </div>
</template> </div>
</template> </template>
<script setup> <script setup>
import { useNuxtApp } from "nuxt/app"; import { useNuxtApp } from "nuxt/app"
const { $utm } = useNuxtApp(); const { $utm } = useNuxtApp()
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()

View File

@ -1,7 +1,13 @@
<template> <template>
<modal :show="showModal" @close="logout" max-width="lg"> <modal
:show="showModal"
max-width="lg"
@close="logout"
>
<div class=""> <div class="">
<h2 class="font-medium text-3xl mb-3">Welcome to OpnForm!</h2> <h2 class="font-medium text-3xl mb-3">
Welcome to OpnForm!
</h2>
<p class="text-sm text-gray-600"> <p class="text-sm text-gray-600">
You're using the self-hosted version of OpnForm and need to set up your account. You're using the self-hosted version of OpnForm and need to set up your account.
Please enter your email and create a password to continue. Please enter your email and create a password to continue.
@ -43,7 +49,10 @@
/> />
<!-- Submit Button --> <!-- Submit Button -->
<v-button class="mx-auto" :loading="form.busy || loading"> <v-button
class="mx-auto"
:loading="form.busy || loading"
>
Update Credentials Update Credentials
</v-button> </v-button>
</form> </form>
@ -51,14 +60,14 @@
</template> </template>
<script setup> <script setup>
import { onMounted } from "vue"; import { onMounted } from "vue"
const authStore = useAuthStore(); const authStore = useAuthStore()
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore()
const formsStore = useFormsStore(); const formsStore = useFormsStore()
const user = computed(() => authStore.user); const user = computed(() => authStore.user)
const router = useRouter(); const router = useRouter()
const showModal = ref(true); const showModal = ref(true)
const form = useForm({ const form = useForm({
name: "", name: "",
email: "", email: "",
@ -66,31 +75,31 @@ const form = useForm({
password_confirmation: "", password_confirmation: "",
agree_terms: false, agree_terms: false,
appsumo_license: null, appsumo_license: null,
}); })
onMounted(() => { onMounted(() => {
form.email = user?.value?.email; form.email = user?.value?.email
}); })
const updateCredentials = () => { const updateCredentials = () => {
form form
.post("update-credentials") .post("update-credentials")
.then(async (data) => { .then(async (data) => {
authStore.setUser(data.user); authStore.setUser(data.user)
const workspaces = await fetchAllWorkspaces(); const workspaces = await fetchAllWorkspaces()
workspacesStore.set(workspaces.data.value); workspacesStore.set(workspaces.data.value)
formsStore.loadAll(workspacesStore.currentId); formsStore.loadAll(workspacesStore.currentId)
router.push({ name: "home" }); router.push({ name: "home" })
}) })
.catch((error) => { .catch((error) => {
console.error(error); console.error(error)
useAlert().error(error.response._data.message); useAlert().error(error.response._data.message)
}); })
}; }
const logout = () => { const logout = () => {
authStore.logout(); authStore.logout()
showModal.value = false; showModal.value = false
router.push({ name: "login" }); router.push({ name: "login" })
}; }
</script> </script>

View File

@ -1,11 +1,3 @@
function parseBoolean(value, defaultValue = false) {
if (typeof value === 'string') {
value = value.toLowerCase().trim()
if (value === 'true' || value === '1') return true
if (value === 'false' || value === '0') return false
}
return defaultValue
}
function parseNumber(value, defaultValue = 0) { function parseNumber(value, defaultValue = 0) {
const parsedValue = parseFloat(value) const parsedValue = parseFloat(value)

View File

@ -160,27 +160,6 @@ export const useWorkingFormStore = defineStore("working_form", {
this.content.properties.splice(index, 1) this.content.properties.splice(index, 1)
} }
}, },
removeField(field) {
this.internalRemoveField(field)
},
internalRemoveField(field) {
const index = this.objectToIndex(field)
if (index !== -1) {
useAlert().success('Ctrl + Z to undo',10000,{
title: 'Field removed',
actions: [{
label: 'Undo',
icon:"i-material-symbols-undo",
click: () => {
this.undo()
}
}]
})
this.content.properties.splice(index, 1)
}
},
moveField(oldIndex, newIndex) { moveField(oldIndex, newIndex) {
const newFields = clonedeep(this.content.properties) const newFields = clonedeep(this.content.properties)
const field = newFields.splice(oldIndex, 1)[0] const field = newFields.splice(oldIndex, 1)[0]