Skip to content

Commit 59898de

Browse files
authored
Merge branch 'main' into 7fd29-customizable-file-upload-size-limit
2 parents a8a0327 + bf95096 commit 59898de

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+374
-77
lines changed

.github/workflows/laravel.yml

-3
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,6 @@ jobs:
8787
# Maps tcp port 3306 on service container to the host
8888
- 3306:3306
8989

90-
concurrency:
91-
group: 'run-tests'
92-
9390
strategy:
9491
fail-fast: true
9592
matrix:

README.md

+5-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<a href="https://github.com/JhumanJ/OpnForm/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-AGPLv3-purple" alt="License">
1616
<a href="https://github.com/JhumanJ/OpnForm/issues/new"><img src="https://img.shields.io/badge/Report a bug-Github-%231F80C0" alt="Report a bug"></a>
1717
<a href="https://github.com/JhumanJ/OpnForm/discussions/new?category=q-a"><img src="https://img.shields.io/badge/Ask a question-Github-%231F80C0" alt="Ask a question"></a>
18-
<a href="https://opnform.featurebase.app/"><img src="https://img.shields.io/badge/Feature request-Featurebase-%231F80C0" alt="Ask a question"></a>
18+
<a href="https://feedback.opnform.com"><img src="https://img.shields.io/badge/Feature request-Featurebase-%231F80C0" alt="Ask a question"></a>
1919
<a href="https://discord.gg/YTSjU2a9TS"><img src="https://dcbadge.vercel.app/api/server/YTSjU2a9TS?style=flat" alt="Ask a question"></a>
2020
<a href="https://console.algora.io/org/OpnForm/bounties?status=open"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2FOpnForm%2Fbounties%3Fstatus%3Dopen" alt="Open Bounties"></a>
2121
<a href="https://console.algora.io/org/OpnForm/bounties?status=completed"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2FOpnForm%2Fbounties%3Fstatus%3Dcompleted" alt="Rewarded Bounties"></a>
@@ -69,8 +69,6 @@ It takes 1 minute to try out the builder for free. You'll have high availability
6969

7070
### Docker installation 🐳
7171

72-
> ⚠️ **Warning**: the Docker setup is currently not working as we're migrating the front-end to Nuxt. [Track progress here](https://github.com/JhumanJ/OpnForm/issues/283).
73-
7472
This can be built and run locally but is also hosted publicly on docker hub at `jhumanj/opnform` and is generally best run directly from there.
7573

7674
#### Running from docker hub
@@ -79,7 +77,10 @@ This can be built and run locally but is also hosted publicly on docker hub at `
7977
docker run --name opnform -v $PWD/my-opnform-data:/persist -p 80:80 jhumanj/opnform
8078
```
8179

82-
You should now be able to access the application by visiting http://localhost in a web browser.
80+
You should now be able to access the application by visiting http://localhost in a web browser.
81+
82+
> 👀 **Server Deployment**: If you are deploying OpnForm on a server (not locally), then you will [need to use 2 .env files](https://github.com/JhumanJ/opnform?tab=readme-ov-file#using-custom-env-files) to configure the app URLs (`APP_URL` in `.env` and both `NUXT_PUBLIC_APP_URL` & `NUXT_PUBLIC_API_BASE` in `client/.env`).
83+
8384

8485
The `-v` argument creates a local directory called `my-opnform-data` which will store your database and files so that your work is not lost when you restart the container.
8586

app/Http/Controllers/Forms/FormController.php

+20
Original file line numberDiff line numberDiff line change
@@ -218,4 +218,24 @@ public function viewFile($id, $fileName)
218218

219219
return redirect()->to(Storage::temporaryUrl($path, now()->addMinutes(5)));
220220
}
221+
222+
/**
223+
* Updates a form's workspace
224+
*/
225+
public function updateWorkspace($id, $workspace_id)
226+
{
227+
$form = Form::findOrFail($id);
228+
$workspace = Workspace::findOrFail($workspace_id);
229+
230+
$this->authorize('update', $form);
231+
$this->authorize('view', $workspace);
232+
233+
$form->workspace_id = $workspace_id;
234+
$form->creator_id = auth()->user()->id;
235+
$form->save();
236+
237+
return $this->success([
238+
'message' => 'Form workspace updated successfully.',
239+
]);
240+
}
221241
}

app/Rules/StorageFile.php

+5-15
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,15 @@
1111

1212
class StorageFile implements Rule
1313
{
14-
public int $maxSize;
15-
1614
public string $error = 'Invalid file.';
1715

18-
/** @var string[] */
19-
public array $fileTypes;
20-
21-
/**
22-
* @param string[] $fileTypes
23-
*/
24-
public function __construct(int $maxSize, array $fileTypes = [], public ?Form $form = null)
16+
public function __construct(public int $maxSize, public array $fileTypes = [], public ?Form $form = null)
2517
{
26-
$this->maxSize = $maxSize;
27-
28-
$this->fileTypes = $fileTypes;
2918
}
3019

3120
/**
3221
* File can have 2 formats:
33-
* - file_name-{uuid}.{ext}
22+
* - file-name_{uuid}.{ext}
3423
* - {uuid}
3524
*
3625
* @param string $attribute
@@ -69,8 +58,9 @@ public function passes($attribute, $value): bool
6958

7059
if (count($this->fileTypes) > 0) {
7160
$this->error = 'Incorrect file type. Allowed only: '.implode(',', $this->fileTypes);
72-
73-
return in_array($fileNameParser->extension, $this->fileTypes);
61+
return collect($this->fileTypes)->map(function ($type) {
62+
return strtolower($type);
63+
})->contains(strtolower($fileNameParser->extension));
7464
}
7565

7666
return true;

client/app.vue

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,21 @@
2929
<ToolsStopImpersonation/>
3030

3131
<Notifications />
32+
<feature-base/>
3233
</div>
3334
</template>
3435

3536
<script>
3637
import {computed} from 'vue'
3738
import {useAppStore} from '~/stores/app'
39+
import FeatureBase from "~/components/vendor/FeatureBase.vue";
3840
3941
export default {
4042
el: '#app',
4143
4244
name: 'OpnForm',
4345
44-
components: {},
46+
components: {FeatureBase},
4547
4648
setup() {
4749
const config = useRuntimeConfig()

client/components/forms/CodeInput.client.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
</template>
1212

1313
<div
14-
:class="[theme.CodeInput.input,{ '!ring-red-500 !ring-2': hasError, '!cursor-not-allowed !bg-gray-200':disabled }]"
14+
:class="[theme.CodeInput.input,{ '!ring-red-500 !ring-2 !border-transparent': hasError, '!cursor-not-allowed !bg-gray-200':disabled }]"
1515
>
1616
<codemirror :id="id?id:name" v-model="compVal" :disabled="disabled?true:null"
1717
:extensions="extensions"

client/components/forms/FileInput.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ export default {
175175
}
176176
},
177177
openFileUpload () {
178-
if (this.disabled) return
178+
if (this.disabled || !this.$refs['actual-input']) return
179179
this.$refs['actual-input'].click()
180180
},
181181
manualFileUpload (e) {

client/components/forms/FlatSelectInput.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<Loader v-if="loading" key="loader" class="h-6 w-6 text-nt-blue mx-auto" />
1010
<div v-for="(option, index) in options" v-else :key="option[optionKey]" role="button"
11-
:class="[theme.default.input,'cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-900 flex',{ 'mb-2': index !== options.length,'!ring-red-500 !ring-2': hasValidation && form.errors.has(name), '!cursor-not-allowed !bg-gray-200':disabled }]"
11+
:class="[theme.default.input,'cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-900 flex',{ 'mb-2': index !== options.length,'!ring-red-500 !ring-2 !border-transparent': hasError, '!cursor-not-allowed !bg-gray-200':disabled }]"
1212
@click="onSelect(option[optionKey])"
1313
>
1414
<p class="flex-grow">

client/components/forms/ImageInput.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<span class="inline-block w-full rounded-md shadow-sm">
1010
<button type="button" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label"
11-
class="cursor-pointer relative w-full" :class="[theme.default.input,{'ring-red-500 ring-2': hasValidation && form.errors.has(name)}]"
11+
class="cursor-pointer relative w-full" :class="[theme.default.input,{'ring-red-500 ring-2': hasError}]"
1212
:style="inputStyle" @click.prevent="showUploadModal=true"
1313
>
1414
<div v-if="currentUrl==null" class="h-6 text-gray-600 dark:text-gray-400">

client/components/forms/PhoneInput.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<v-select v-model="selectedCountryCode" class="w-[130px]" dropdown-class="w-[300px]" input-class="rounded-r-none"
1111
:data="countries"
1212
:disabled="(disabled || countries.length===1)?true:null" :searchable="true" :search-keys="['name']" :option-key="'code'" :color="color"
13-
:has-error="hasValidation && form.errors.has(name)"
13+
:has-error="hasError"
1414
:placeholder="'Select a country'" :uppercase-labels="true" :theme="theme" @update:model-value="onChangeCountryCode"
1515
>
1616
<template #option="props">
@@ -28,7 +28,7 @@
2828
</template>
2929
</v-select>
3030
<input v-model="inputVal" type="text" class="inline-flex-grow !border-l-0 !rounded-l-none" :disabled="disabled?true:null"
31-
:class="[theme.default.input, { '!ring-red-500 !ring-2': hasValidation && form.errors.has(name), '!cursor-not-allowed !bg-gray-200': disabled }]"
31+
:class="[theme.default.input, { '!ring-red-500 !ring-2 !border-transparent': hasError, '!cursor-not-allowed !bg-gray-200': disabled }]"
3232
:placeholder="placeholder" :style="inputStyle" @input="onInput"
3333
>
3434
</div>

client/components/forms/RichTextAreaInput.client.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</template>
88

99
<vue-editor :id="id?id:name" ref="editor" v-model="compVal" :disabled="disabled?true:null"
10-
:placeholder="placeholder" :class="[{ '!ring-red-500 !ring-2': hasValidation && form.errors.has(name), '!cursor-not-allowed !bg-gray-200':disabled }, theme.RichTextAreaInput.input]"
10+
:placeholder="placeholder" :class="[{ '!ring-red-500 !ring-2 !border-transparent': hasError, '!cursor-not-allowed !bg-gray-200':disabled }, theme.RichTextAreaInput.input]"
1111
:editor-toolbar="editorToolbar" class="rich-editor resize-y"
1212
:style="inputStyle"
1313
/>

client/components/forms/SelectInput.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
:placeholder="placeholder"
1919
:uppercase-labels="uppercaseLabels"
2020
:theme="theme"
21-
:has-error="hasValidation && form.errors.has(name)"
21+
:has-error="hasError"
2222
:allow-creation="allowCreation"
2323
:disabled="disabled?true:null"
2424
:help="help"

client/components/forms/SignatureInput.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</template>
88

99
<VueSignaturePad ref="signaturePad"
10-
:class="[theme.default.input,{ '!ring-red-500 !ring-2': hasValidation && form.errors.has(name), '!cursor-not-allowed !bg-gray-200':disabled }]"
10+
:class="[theme.default.input,{ '!ring-red-500 !ring-2 !border-transparent': hasError, '!cursor-not-allowed !bg-gray-200':disabled }]"
1111
height="150px"
1212
:name="name"
1313
:options="{ onEnd }"

client/components/forms/TextAreaInput.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</template>
88

99
<textarea :id="id?id:name" v-model="compVal" :disabled="disabled?true:null"
10-
:class="[theme.default.input,{ '!ring-red-500 !ring-2': hasValidation && form.errors.has(name), '!cursor-not-allowed !bg-gray-200':disabled }]"
10+
:class="[theme.default.input,{ '!ring-red-500 !ring-2 !border-transparent': hasError, '!cursor-not-allowed !bg-gray-200':disabled }]"
1111
class="resize-y"
1212
:name="name" :style="inputStyle"
1313
:placeholder="placeholder"

client/components/forms/TextInput.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
:type="nativeType" :autocomplete="autocomplete"
1111
:pattern="pattern"
1212
:style="inputStyle"
13-
:class="[theme.default.input, { '!ring-red-500 !ring-2': hasError, '!cursor-not-allowed !bg-gray-200': disabled }]"
13+
:class="[theme.default.input, { '!ring-red-500 !ring-2 !border-transparent': hasError, '!cursor-not-allowed !bg-gray-200': disabled }]"
1414
:name="name" :accept="accept"
1515
:placeholder="placeholder" :min="min" :max="max" :maxlength="maxCharLimit"
1616
@change="onChange" @keydown.enter.prevent="onEnterPress"

client/components/forms/components/InputHelp.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<div class="flex mb-1 input-help">
33
<small :class="theme.default.help" class="grow flex">
4-
<slot name="help"><span class="field-help" v-html="help" /></slot>
4+
<slot name="help"><span v-if="help" class="field-help" v-html="help" /></slot>
55
</small>
66
<slot name="after-help">
77
<small class="flex-grow" />

client/components/forms/components/VSelect.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<button type="button" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label"
55
class="cursor-pointer"
66
:style="inputStyle"
7-
:class="[theme.SelectInput.input,{'py-2': !multiple || loading,'py-1': multiple, '!ring-red-500 !ring-2': hasError, '!cursor-not-allowed !bg-gray-200': disabled}, inputClass]"
7+
:class="[theme.SelectInput.input,{'py-2': !multiple || loading,'py-1': multiple, '!ring-red-500 !ring-2 !border-transparent': hasError, '!cursor-not-allowed !bg-gray-200': disabled}, inputClass]"
88
@click="toggleDropdown"
99
>
1010
<div :class="{'h-6': !multiple, 'min-h-8': multiple && !loading}">

client/components/forms/components/VSwitch.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ const props = defineProps({
1313
modelValue: { type: Boolean, default: false },
1414
disabled: { type: Boolean, default: false }
1515
})
16-
const emits = defineEmits(['update:modelValue'])
16+
const emit = defineEmits(['update:modelValue'])
1717
1818
const onClick = () => {
1919
if (props.disabled) return
20-
emits('update:modelValue', !props.modelValue)
20+
emit('update:modelValue', !props.modelValue)
2121
}
2222
</script>

client/components/forms/useFormInput.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function useFormInput (props, context, formPrefixKey = null) {
3333
})
3434

3535
const hasError = computed(() => {
36-
return hasValidation && props.form?.errors?.has(props.name)
36+
return hasValidation.value && props.form?.errors?.has(props.name)
3737
})
3838

3939
const compVal = computed({

client/components/global/Modal.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ const props = defineProps({
7171
}
7272
})
7373
74-
const emits = defineEmits(['close'])
74+
const emit = defineEmits(['close'])
7575
7676
useHead({
7777
bodyAttrs: {
@@ -152,7 +152,7 @@ const onLeave = (el, done) => {
152152
153153
const close = () => {
154154
if (props.closeable) {
155-
emits('close')
155+
emit('close')
156156
}
157157
}
158158

client/components/global/Navbar.vue

+24-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,19 @@
1616
>
1717
Templates
1818
</NuxtLink>
19-
<NuxtLink v-if="$route.name !== 'ai-form-builder'" :to="{name:'ai-form-builder'}"
19+
<template v-if="featureBaseEnabled">
20+
<button v-if="user" @click.prevent="openChangelog"
21+
class="text-sm text-gray-600 dark:text-white hidden sm:inline hover:text-gray-800 cursor-pointer mt-1 mr-8"
22+
>
23+
What's new? <span id="fb-update-badge"></span>
24+
</button>
25+
<a :href="opnformConfig.links.changelog_url" target="_blank" v-else
26+
class="text-sm text-gray-600 dark:text-white hidden lg:inline hover:text-gray-800 cursor-pointer mt-1 mr-8"
27+
>
28+
What's new?
29+
</a>
30+
</template>
31+
<NuxtLink v-if="$route.name !== 'ai-form-builder' && user === null" :to="{name:'ai-form-builder'}"
2032
class="text-sm text-gray-600 dark:text-white hidden lg:inline hover:text-gray-800 cursor-pointer mt-1 mr-8"
2133
>
2234
AI Form Builder
@@ -103,8 +115,10 @@
103115
<NuxtLink :to="{ name: 'settings-admin' }" v-if="user.moderator"
104116
class="block block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
105117
>
106-
<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">
107-
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z" />
118+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
119+
stroke="currentColor" class="w-4 h-4 mr-2">
120+
<path stroke-linecap="round" stroke-linejoin="round"
121+
d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z"/>
108122
</svg>
109123
Admin
110124
</NuxtLink>
@@ -190,6 +204,9 @@ export default {
190204
paidPlansEnabled() {
191205
return this.config.public.paidPlansEnabled
192206
},
207+
featureBaseEnabled() {
208+
return this.config.public.featureBaseOrganization !== null
209+
},
193210
showAuth() {
194211
return this.$route.name && this.$route.name !== 'forms-slug'
195212
},
@@ -217,6 +234,10 @@ export default {
217234
},
218235
219236
methods: {
237+
openChangelog() {
238+
if (process.server) return
239+
window.Featurebase('manually_open_changelog_popup')
240+
},
220241
async logout() {
221242
// Log out the user.
222243
this.authStore.logout()

client/components/global/transitions/Collapsible.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const props = defineProps({
1717
modelValue: {type: Boolean},
1818
maxHeight: {type: Number, default: 200},
1919
})
20-
const emits = defineEmits(['click-away'])
20+
const emit = defineEmits(['click-away'])
2121
2222
const motion = ref(null)
2323
const collapsible = ref(null)
@@ -47,6 +47,6 @@ const onLeave = (el, done) => {
4747
}
4848
4949
const onClickAway = (event) => {
50-
emits('click-away', event)
50+
emit('click-away', event)
5151
}
5252
</script>

client/components/open/forms/OpenForm.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<div :key="currentFieldGroupIndex" class="form-group flex flex-wrap w-full">
1010
<draggable v-model="currentFields"
1111
item-key="id"
12-
class="flex flex-wrap transition-all"
12+
class="flex flex-wrap transition-all w-full"
1313
:class="{'-m-6 p-2 bg-gray-50 rounded-md':dragging}"
1414
ghost-class="ghost-item"
1515
handle=".draggable" :animation="200"

client/components/open/forms/components/FormEditor.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ import FormCustomSeo from './form-components/FormCustomSeo.vue'
9797
import FormAccess from './form-components/FormAccess.vue'
9898
import {validatePropertiesLogic} from "~/composables/forms/validatePropertiesLogic.js"
9999
import opnformConfig from "~/opnform.config.js";
100-
import {captureException} from "@sentry/vue";
100+
import {captureException} from "@sentry/core";
101101
102102
export default {
103103
name: 'FormEditor',

0 commit comments

Comments
 (0)