Skip to content

Commit

Permalink
Merge branch 'main' into 12fa6-form-translation
Browse files Browse the repository at this point in the history
  • Loading branch information
JhumanJ authored Dec 4, 2024
2 parents cec2aa1 + c927a23 commit e8fe266
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 11 deletions.
17 changes: 14 additions & 3 deletions api/app/Http/Requests/AnswerFormRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Stevebauman\Purify\Facades\Purify;

class AnswerFormRequest extends FormRequest
{
Expand Down Expand Up @@ -67,11 +68,16 @@ public function rules()
if (isset($data[$field['id']]) && is_array($data[$field['id']])) {
$data[$field['id']] = array_map(function ($val) use ($field) {
$tmpop = collect($field[$field['type']]['options'])->first(function ($op) use ($val) {
return $op['id'] ?? $op['value'] === $val;
return isset($op['id'], $op['name']) && ($op['id'] === $val || $op['name'] === $val);
});

return isset($tmpop['name']) ? $tmpop['name'] : '';
return isset($tmpop['name']) ? $tmpop['name'] : $val;
}, $data[$field['id']]);
} elseif (isset($data[$field['id']])) {
// Handle single select values
$tmpop = collect($field[$field['type']]['options'])->first(function ($op) use ($field, $data) {
return isset($op['id'], $op['name']) && ($op['id'] === $data[$field['id']] || $op['name'] === $data[$field['id']]);
});
$data[$field['id']] = isset($tmpop['name']) ? $tmpop['name'] : $data[$field['id']];
}
}
if (FormLogicPropertyResolver::isRequired($property, $data)) {
Expand Down Expand Up @@ -167,6 +173,7 @@ private function getPropertyRules($property): array
{
switch ($property['type']) {
case 'text':
case 'rich_text':
case 'signature':
return ['string'];
case 'number':
Expand Down Expand Up @@ -280,6 +287,10 @@ protected function prepareForValidation()
if ($property['type'] === 'phone_number' && (!isset($property['use_simple_text_input']) || !$property['use_simple_text_input']) && $receivedValue && in_array($receivedValue, $countryCodeMapper)) {
$mergeData[$property['id']] = null;
}

if ($property['type'] === 'rich_text' && $receivedValue) {
$mergeData[$property['id']] = Purify::clean($receivedValue);
}
});

$this->merge($mergeData);
Expand Down
235 changes: 235 additions & 0 deletions api/tests/Feature/Forms/FormLogicTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,238 @@
],
]);
});

it('preserves multi-select values during validation with logic conditions', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);

// Create a form with a multi-select field and a text field that has logic based on the multi-select
$form = $this->createForm($user, $workspace, [
'properties' => [
[
'id' => 'multi_select_field',
'name' => 'Multi Select Field',
'type' => 'multi_select',
'hidden' => false,
'required' => true,
'multi_select' => [
'options' => [
['id' => 'option1', 'name' => 'Option 1'],
['id' => 'option2', 'name' => 'Option 2'],
['id' => 'option3', 'name' => 'Option 3']
]
]
],
[
'id' => 'text_field',
'name' => 'Text Field',
'type' => 'text',
'hidden' => false,
'required' => false,
'logic' => [
'conditions' => [
'operatorIdentifier' => 'and',
'children' => [
[
'identifier' => 'multi_select',
'value' => [
'operator' => 'contains',
'property_meta' => [
'id' => 'multi_select_field',
'type' => 'multi_select'
],
'value' => 'Option 1'
]
]
]
],
'actions' => ['require-answer']
]
]
]
]);

// Submit form data with multi-select values
$formData = [
'multi_select_field' => ['Option 1', 'Option 2']
];

ray($formData)->blue('Original form data');

$response = $this->postJson(route('forms.answer', $form->slug), $formData);

// The validation should fail because text_field is required when Option 1 is selected
$response->assertStatus(422)
->assertJson([
'message' => 'The Text Field field is required.',
'errors' => [
'text_field' => ['The Text Field field is required.']
]
]);

// Check that the multi-select values were preserved in the validation data
ray($response->json())->purple('Response data');
expect($response->json('errors.multi_select_field'))->toBeNull();
});

it('correctly handles multi-select values with complex form logic', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);

// Create form with the specific fields from your example
$form = $this->createForm($user, $workspace, [
'properties' => [
[
'id' => '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b',
'name' => 'Which event would you like to join?',
'type' => 'multi_select',
'hidden' => false,
'required' => true,
'multi_select' => [
'options' => [
['id' => 'Ashkelon Run (March 21)', 'name' => 'Ashkelon Run (March 21)'],
['id' => 'Jerusalem Marathon (April 4)', 'name' => 'Jerusalem Marathon (April 4)'],
['id' => 'Neither', 'name' => 'Neither']
]
]
],
[
'id' => '0ca51469-6bda-40f4-831c-084f066643d7',
'name' => 'Jerusalem Marathon - Run Options',
'type' => 'select',
'hidden' => true,
'required' => false,
'select' => [
'options' => [
['id' => '10km (Most popular)', 'name' => '10km (Most popular)']
]
],
'logic' => [
'conditions' => [
'operatorIdentifier' => 'and',
'children' => [
[
'identifier' => '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b',
'value' => [
'operator' => 'contains',
'property_meta' => [
'id' => '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b',
'type' => 'multi_select'
],
'value' => 'Jerusalem Marathon (April 4)'
]
]
]
],
'actions' => ['require-answer', 'show-block']
]
]
]
]);

// Submit form data matching your payload
$formData = [
'93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b' => ['Jerusalem Marathon (April 4)'],
'0ca51469-6bda-40f4-831c-084f066643d7' => '10km (Most popular)'
];

ray($formData)->blue('Original form data');

$response = $this->postJson(route('forms.answer', $form->slug), $formData);

ray($response->json())->purple('Response data');

// Should be successful since all required fields are filled
$response->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form submission saved.'
]);

// Now let's verify the saved submission data
$submission = $form->submissions()->first();
expect($submission->data['93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b'])->toBe(['Jerusalem Marathon (April 4)']);
});

it('preserves multi-select values when building validation rules', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);

// Create form with the exact fields from your real form
$form = $this->createForm($user, $workspace, [
'properties' => [
[
'id' => '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b',
'name' => 'Which event would you like to join?',
'type' => 'multi_select',
'required' => true,
'multi_select' => [
'options' => [
['id' => 'Jerusalem Marathon (April 4)', 'name' => 'Jerusalem Marathon (April 4)'],
['id' => 'Ashkelon Run (March 21)', 'name' => 'Ashkelon Run (March 21)']
]
]
],
[
'id' => '72565901-c345-427a-b988-0ce3de9ad9f4',
'name' => 'Additional Days',
'type' => 'multi_select',
'required' => false,
'multi_select' => [
'options' => [
['id' => 'Thursday', 'name' => 'Thursday'],
['id' => 'Sunday', 'name' => 'Sunday']
]
],
'logic' => [
'conditions' => [
'operatorIdentifier' => 'and',
'children' => [
[
'identifier' => '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b',
'value' => [
'operator' => 'contains',
'property_meta' => [
'id' => '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b',
'type' => 'multi_select'
],
'value' => 'Ashkelon Run (March 21)'
]
]
]
],
'actions' => ['require-answer']
]
]
]
]);

// Submit form data with Jerusalem Marathon
$formData = [
'93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b' => ['Jerusalem Marathon (April 4)']
];

ray($formData)->blue('Original form data');

$response = $this->postJson(route('forms.answer', $form->slug), $formData);

ray($response->json())->purple('Response data');

// Should be successful since Jerusalem Marathon doesn't require Additional Days
$response->assertSuccessful();

// Now try with Ashkelon Run which requires Additional Days
$formData = [
'93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b' => ['Ashkelon Run (March 21)']
];

$response = $this->postJson(route('forms.answer', $form->slug), $formData);

// Should fail because Additional Days is required when Ashkelon Run is selected
$response->assertStatus(422)
->assertJson([
'errors' => [
'72565901-c345-427a-b988-0ce3de9ad9f4' => ['The Additional Days field is required.']
]
]);
});
1 change: 1 addition & 0 deletions client/components/open/forms/OpenFormField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ export default {
}
return {
text: 'TextInput',
rich_text: 'RichTextAreaInput',
number: 'TextInput',
rating: 'RatingInput',
scale: 'ScaleInput',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
errorReport += ` And here are technical details about the error: \`\`\`${error.stack}\`\`\``
try {
crisp.openAndShowChat(errorReport)
crisp.showMessage(`Hi there, we're very sorry to hear you experienced an issue with NoteForms.
crisp.showMessage(`Hi there, we're very sorry to hear you experienced an issue with OpnForm.
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)
} catch (e) {
console.error('Crisp error', e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,8 +448,15 @@
:multiple="field.multiple === true"
:move-to-form-assets="true"
/>
<rich-text-area-input
v-else-if="field.type === 'rich_text'"
name="prefill"
class="mt-3"
:form="field"
label="Pre-filled value"
/>
<text-input
v-else-if="!['files', 'signature'].includes(field.type)"
v-else-if="!['files', 'signature', 'rich_text'].includes(field.type)"
name="prefill"
class="mt-3"
:form="field"
Expand Down
1 change: 1 addition & 0 deletions client/components/open/tables/OpenTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ export default {
rafId: null,
fieldComponents: {
text: shallowRef(OpenText),
rich_text: shallowRef(OpenText),
number: shallowRef(OpenText),
rating: shallowRef(OpenText),
scale: shallowRef(OpenText),
Expand Down
4 changes: 1 addition & 3 deletions client/components/open/tables/components/OpenText.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
<template>
<span>
{{ value }}
</span>
<div v-html="value" />
</template>

<script>
Expand Down
2 changes: 1 addition & 1 deletion client/components/pages/pricing/CustomPlan.vue
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@
<template v-else>$200</template>
</span>
<span class="text-sm font-medium leading-6 text-gray-600">
starting from {{ isYearly ? 'per year' : 'per month' }}
starting from per month
</span>
</p>
<div class="flex justify-center">
Expand Down
9 changes: 9 additions & 0 deletions client/data/blocks_types.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@
"text_class": "text-blue-900",
"is_input": true
},
"rich_text": {
"name": "rich_text",
"title": "Rich Text Input",
"icon": "i-heroicons-bars-3-20-solid",
"default_block_name": "Description",
"bg_class": "bg-blue-100",
"text_class": "text-blue-900",
"is_input": true
},
"date": {
"name": "date",
"title": "Date Input",
Expand Down
2 changes: 1 addition & 1 deletion client/pages/home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@
<template #description>
<div class="flex flex-wrap sm:flex-nowrap gap-4 items-start">
<p class="flex-grow">
Remove NoteForms branding, customize forms further, use your custom domain, integrate with your
Remove OpnForm branding, customize forms further, use your custom domain, integrate with your
favorite tools, invite users, and more!
</p>
<UButton
Expand Down
2 changes: 1 addition & 1 deletion client/pages/subscriptions/success.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const redirectIfSubscribed = () => {
}
const checkSubscription = () => {
// Fetch the user.
return noteFormsFetch('user').then((data) => {
return opnFetch('user').then((data) => {
authStore.setUser(data)
redirectIfSubscribed()
}).catch((error) => {
Expand Down

0 comments on commit e8fe266

Please sign in to comment.