Skip to content

Commit af57333

Browse files
committed
Merge master
2 parents 4994fd3 + 7b89ebd commit af57333

16 files changed

+174
-10
lines changed

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/Http/Requests/AnswerFormRequest.php

+7-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ public function __construct(Request $request)
2727
$this->maxFileSize = $this->form->workspace->max_file_size;
2828
}
2929

30+
private function getFieldMaxFileSize($fieldProps)
31+
{
32+
return array_key_exists('max_file_size', $fieldProps) ?
33+
min($fieldProps['max_file_size'] * 1000000, $this->maxFileSize) : $this->maxFileSize;
34+
}
35+
3036
/**
3137
* Validate form before use it
3238
*
@@ -180,7 +186,7 @@ private function getPropertyRules($property): array
180186
if (! empty($property['allowed_file_types'])) {
181187
$allowedFileTypes = explode(',', $property['allowed_file_types']);
182188
}
183-
$this->requestRules[$property['id'].'.*'] = [new StorageFile($this->maxFileSize, $allowedFileTypes, $this->form)];
189+
$this->requestRules[$property['id'] . '.*'] = [new StorageFile($this->getFieldMaxFileSize($property), $allowedFileTypes, $this->form)];
184190

185191
return ['array'];
186192
case 'email':

app/Http/Requests/UserFormRequest.php

+3
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ public function rules()
118118
'properties.*.generates_uuid' => 'boolean|nullable',
119119
'properties.*.generates_auto_increment_id' => 'boolean|nullable',
120120

121+
// For file (min and max)
122+
'properties.*.max_file_size' => 'min:1|numeric',
123+
121124
// Security & Privacy
122125
'can_be_indexed' => 'boolean',
123126
'password' => 'sometimes|nullable',

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/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': hasError, '!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/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': hasError, '!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/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': hasError, '!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': hasError, '!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/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/open/forms/OpenFormField.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ export default {
303303
}
304304
} else if (field.type === 'files' || (field.type === 'url' && field.file_upload)) {
305305
inputProperties.multiple = (field.multiple !== undefined && field.multiple)
306-
inputProperties.mbLimit = this.form?.max_file_size ?? this.currentWorkspace?.max_file_size
306+
inputProperties.mbLimit = Math.min(Math.max(field.max_file_size, 1), this.form?.max_file_size ?? this.currentWorkspace?.max_file_size)
307307
inputProperties.accept = (this.form.is_pro && field.allowed_file_types) ? field.allowed_file_types : ''
308308
} else if (field.type === 'number' && field.is_rating) {
309309
inputProperties.numberOfStars = parseInt(field.rating_max_value)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<template>
2+
<modal :show="show" @close="emit('close')">
3+
<template #icon>
4+
<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">
5+
<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" />
6+
</svg>
7+
</template>
8+
<template #title>
9+
Change form workspace
10+
</template>
11+
<div class="p-4">
12+
<div class="flex space-x-4 items-center">
13+
<p>Current workspace:</p>
14+
<div class="flex items-center cursor group p-2 rounded border">
15+
<div class="rounded-full h-8 8">
16+
<img v-if="isUrl(workspace.icon)"
17+
:src="workspace.icon"
18+
:alt="workspace.name + ' icon'" class="flex-shrink-0 h-8 w-8 rounded-full shadow"
19+
/>
20+
<div v-else class="rounded-full pt-2 text-xs truncate bg-nt-blue-lighter h-8 w-8 text-center shadow"
21+
v-text="workspace.icon"
22+
/>
23+
</div>
24+
<p class="lg:block max-w-10 truncate ml-2 text-gray-800 dark:text-gray-200">
25+
{{ workspace.name }}
26+
</p>
27+
</div>
28+
</div>
29+
<form @submit.prevent="onSubmit">
30+
<div class=" my-4">
31+
<select-input name="workspace" class=""
32+
:options="workspacesSelectOptions"
33+
v-model="selectedWorkspace"
34+
:required="true"
35+
label="Select workspace"
36+
/>
37+
</div>
38+
<div class="flex justify-end mt-4 pb-5">
39+
<v-button class="mr-2" :loading="loading">
40+
Change workspace
41+
</v-button>
42+
<v-button color="white" @click.prevent="emit('close')">
43+
Close
44+
</v-button>
45+
</div>
46+
</form>
47+
</div>
48+
</modal>
49+
</template>
50+
51+
<script setup>
52+
import { ref, defineProps, defineEmits, computed } from 'vue'
53+
const emit = defineEmits(['close'])
54+
const workspacesStore = useWorkspacesStore()
55+
const formsStore = useFormsStore()
56+
57+
const selectedWorkspace = ref(null);
58+
const props = defineProps({
59+
show: { type: Boolean, required: true },
60+
form: { type: Object, required: true },
61+
})
62+
const workspaces = computed(() => workspacesStore.getAll)
63+
const workspace = computed(() => workspacesStore.getByKey(props.form?.workspace_id))
64+
const loading = ref(false)
65+
const workspacesSelectOptions = computed(()=> workspaces.value.filter((w)=>{
66+
return w.id !== workspace.value.id
67+
}).map(workspace => ({ name: workspace.name, value: workspace.id })))
68+
69+
70+
const onSubmit = () => {
71+
const endpoint = '/open/forms/' + props.form.id + '/workspace/' + selectedWorkspace.value
72+
if(! selectedWorkspace.value) {
73+
useAlert().error('Please select a workspace!')
74+
return;
75+
}
76+
opnFetch(endpoint, { method: 'POST' }).then(data => {
77+
loading.value = false;
78+
emit('close')
79+
useAlert().success('Form workspace updated successfully.')
80+
workspacesStore.setCurrentId(selectedWorkspace.value)
81+
formsStore.resetState()
82+
formsStore.loadAll(selectedWorkspace.value)
83+
const router = useRouter()
84+
const route = useRoute()
85+
if (route.name !== 'home') {
86+
router.push({ name: 'home' })
87+
}
88+
formsStore.loadAll(selectedWorkspace.value)
89+
}).catch((error) => {
90+
useAlert().error(error?.data?.message ?? 'Something went wrong, please try again!')
91+
loading.value = false;
92+
})
93+
}
94+
95+
const isUrl = (str) => {
96+
try {
97+
new URL(str)
98+
} catch (_) {
99+
return false
100+
}
101+
return true
102+
}
103+
</script>

client/components/open/forms/components/form-components/FormEditorPreview.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
>
2525
<img alt="Logo Picture" :src="coverPictureSrc(form.logo_picture)"
2626
:class="{'top-5':!form.cover_picture, '-top-10':form.cover_picture}"
27-
class="w-20 h-20 object-contain absolute left-5 transition-all"
27+
class="max-w-60 h-20 object-contain absolute left-5 transition-all"
2828
/>
2929
</div>
3030
</div>

client/components/open/forms/fields/components/FieldOptions.vue

+16
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@
6060
label="Allowed file types" placeholder="jpg,jpeg,png,gif"
6161
help="Comma separated values, leave blank to allow all file types"
6262
/>
63+
64+
<text-input name="max_file_size" class="mt-3" :form="field" native-type="number"
65+
:min="1"
66+
:max="mbLimit"
67+
label="Maximum file size (in MB)" :placeholder="`1MB - ${mbLimit}MB`"
68+
help="Set the maximum file size that can be uploaded"
69+
/>
6370
</div>
6471

6572
<!-- Number Options -->
@@ -428,6 +435,9 @@ export default {
428435
required: false
429436
}
430437
},
438+
setup() {
439+
return { currentWorkspace: computed(() => useWorkspacesStore().getCurrent), }
440+
},
431441
data () {
432442
return {
433443
typesWithoutPlaceholder: ['date', 'checkbox', 'files'],
@@ -442,6 +452,9 @@ export default {
442452
hasPlaceholder () {
443453
return !this.typesWithoutPlaceholder.includes(this.field.type)
444454
},
455+
mbLimit() {
456+
return this.form?.max_file_size ?? this.currentWorkspace?.max_file_size
457+
},
445458
prefillSelectsOptions () {
446459
if (!['select', 'multi_select'].includes(this.field.type)) return {}
447460
@@ -504,6 +517,9 @@ export default {
504517
if (['text', 'number', 'url', 'email'].includes(this.field?.type) && !this.field?.max_char_limit) {
505518
this.field.max_char_limit = 2000
506519
}
520+
if (this.field.type == 'files') {
521+
this.field.max_file_size = Math.min((this.field.max_file_size ?? this.mbLimit), this.mbLimit)
522+
}
507523
},
508524
509525
methods: {

client/components/pages/forms/show/ExtraMenu.vue

+15
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,17 @@
107107
</svg>
108108
Create Template
109109
</a>
110+
111+
<a v-track.change_workspace_click="{form_id:form.id, form_slug:form.slug}" href="#"
112+
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"
113+
@click.prevent="showFormWorkspaceModal=true"
114+
>
115+
<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">
116+
<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" />
117+
</svg>
118+
119+
Change workspace
120+
</a>
110121
<a v-track.delete_form_click="{form_id:form.id, form_slug:form.slug}"
111122
href="#"
112123
class="block block px-4 py-2 text-md text-red-600 hover:bg-red-50 flex items-center"
@@ -152,13 +163,16 @@
152163
</div>
153164
</modal>
154165
<form-template-modal v-if="!isMainPage && user" :form="form" :show="showFormTemplateModal" @close="showFormTemplateModal=false" />
166+
<form-workspace-modal v-if="user" :form="form" :show="showFormWorkspaceModal" @close="showFormWorkspaceModal=false" />
167+
155168
</div>
156169
</template>
157170

158171
<script setup>
159172
import { ref, defineProps, computed } from 'vue'
160173
import Dropdown from '~/components/global/Dropdown.vue'
161174
import FormTemplateModal from '../../../open/forms/components/templates/FormTemplateModal.vue'
175+
import FormWorkspaceModal from '../../../open/forms/components/FormWorkspaceModal.vue'
162176
163177
const { copy } = useClipboard()
164178
const router = useRouter()
@@ -177,6 +191,7 @@ let loadingDuplicate = ref(false)
177191
let loadingDelete = ref(false)
178192
let showDeleteFormModal = ref(false)
179193
let showFormTemplateModal = ref(false)
194+
let showFormWorkspaceModal = ref(false)
180195
181196
const copyLink = () => {
182197
copy(props.form.share_url)

routes/api.php

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989

9090
Route::prefix('forms')->name('forms.')->group(function () {
9191
Route::post('/', [FormController::class, 'store'])->name('store');
92+
Route::post('/{id}/workspace/{workspace_id}', [FormController::class, 'updateWorkspace'])->name('workspace.update');
9293
Route::put('/{id}', [FormController::class, 'update'])->name('update');
9394
Route::delete('/{id}', [FormController::class, 'destroy'])->name('destroy');
9495

0 commit comments

Comments
 (0)