diff --git a/resources/js/components/pages/pricing/CheckoutDetailsModal.vue b/resources/js/components/pages/pricing/CheckoutDetailsModal.vue
new file mode 100644
index 0000000..f2645be
--- /dev/null
+++ b/resources/js/components/pages/pricing/CheckoutDetailsModal.vue
@@ -0,0 +1,94 @@
+
+
+
+
+
+ Go to checkout
+
+
+
+
+
diff --git a/resources/js/components/pages/pricing/MonthlyYearlySelector.vue b/resources/js/components/pages/pricing/MonthlyYearlySelector.vue
new file mode 100644
index 0000000..11a7f2c
--- /dev/null
+++ b/resources/js/components/pages/pricing/MonthlyYearlySelector.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+ Save 20% with annual plans
+
+
+
+
+
diff --git a/resources/js/components/pages/pricing/PricingTable.vue b/resources/js/components/pages/pricing/PricingTable.vue
new file mode 100644
index 0000000..eddcf13
--- /dev/null
+++ b/resources/js/components/pages/pricing/PricingTable.vue
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+ Pro Plan
+
+
+ OpnForm Pro offers empowering features tailored to the advanced needs of teams and creators. Enjoy our free 3-day trial!
+
+
+
+
+ What's included
+
+
+
+
+
+ -
+
+ {{ title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $16
+ $19
+
+
+ per month
+
+
+
+
+
+ Start free trial
+
+
+ View Billing
+
+
+ Start free trial
+
+
+
+ Invoices and receipts available for easy company reimbursement.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/config/form-themes.js b/resources/js/config/form-themes.js
index f32b095..720e05e 100644
--- a/resources/js/config/form-themes.js
+++ b/resources/js/config/form-themes.js
@@ -13,7 +13,7 @@ export const themes = {
},
CodeInput: {
label: 'text-gray-700 dark:text-gray-300 font-semibold',
- input: 'rounded-lg border-gray-300 flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:border-transparent p-2',
+ input: 'rounded-lg border border-gray-300 dark:border-gray-600 overflow-hidden',
help: 'text-gray-400 dark:text-gray-500'
},
RichTextAreaInput: {
@@ -43,7 +43,7 @@ export const themes = {
},
CodeInput: {
label: 'text-gray-700 dark:text-gray-300 font-semibold',
- input: 'border-transparent flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-2 focus:border-transparent p-2',
+ input: 'border border-gray-300 dark:border-gray-600 overflow-hidden',
help: 'text-gray-400 dark:text-gray-500'
},
RichTextAreaInput: {
@@ -68,7 +68,7 @@ export const themes = {
},
CodeInput: {
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
- input: 'rounded border-transparent flex-1 appearance-none shadow-inner-notion border border-gray-300 dark:border-gray-600 w-full text-gray-900 bg-notion-input-background dark:bg-notion-dark-light shadow-inner dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-focus-notion p-2',
+ input: 'rounded shadow-inner-notion border border-gray-300 dark:border-gray-600 overflow-hidden',
help: 'text-notion-input-help dark:text-gray-500'
},
RichTextAreaInput: {
diff --git a/resources/js/mixins/forms/saveUpdateAlert.js b/resources/js/mixins/forms/saveUpdateAlert.js
index 9909fda..17c6c0f 100644
--- a/resources/js/mixins/forms/saveUpdateAlert.js
+++ b/resources/js/mixins/forms/saveUpdateAlert.js
@@ -1,17 +1,8 @@
export default {
methods: {
- displayFormModificationAlert(responseData) {
- if (responseData.form_cleaning && Object.keys(responseData.form_cleaning).length > 0) {
- let message = responseData.message + '
'
- Object.keys(responseData.form_cleaning).forEach((key) => {
- const fieldName = key.charAt(0).toUpperCase() + key.slice(1)
- let fieldInfo = "
" + fieldName + "
"
- responseData.form_cleaning[key].forEach((msg) => {
- fieldInfo = fieldInfo + "- " + msg +"
"
- })
- message = message + fieldInfo + ""
- })
- this.alertWarning(message)
+ displayFormModificationAlert (responseData) {
+ if (responseData.form.cleanings && Object.keys(responseData.form.cleanings).length > 0) {
+ this.alertWarning(responseData.message)
} else {
this.alertSuccess(responseData.message)
}
diff --git a/resources/js/pages/forms/show/index.vue b/resources/js/pages/forms/show/index.vue
index 5d970c8..482fa45 100644
--- a/resources/js/pages/forms/show/index.vue
+++ b/resources/js/pages/forms/show/index.vue
@@ -67,11 +67,13 @@
This form will stop accepting submissions after {{ form.max_submissions_count }} submissions.
-
+
+
+
-
{{tab.name}}
@@ -109,6 +111,7 @@ import ProTag from '../../../components/common/ProTag.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'
const loadForms = function () {
store.commit('open/forms/startLoading')
@@ -122,7 +125,8 @@ export default {
components: {
VButton,
ProTag,
- ExtraMenu
+ ExtraMenu,
+ FormCleanings
},
mixins: [SeoMeta],
diff --git a/resources/js/pages/pricing.vue b/resources/js/pages/pricing.vue
new file mode 100644
index 0000000..a83b22c
--- /dev/null
+++ b/resources/js/pages/pricing.vue
@@ -0,0 +1,281 @@
+
+
+
+
+
+
+ Simple, transparent pricing. No surprises.
+
+
+ Just like our codebase, our pricing is 100% transparent. One flat price for all features. No hidden fees.
+
+
+
+
+
+
+
+
+
+
+
+ 99% of features are available to all users for free and without
+ limits.
+
+
+
+
+
+
+
+ Unlimited forms
+
+
+
+
+
+
+ Unlimited submissions
+
+
+
+
+
+
+ Unlimited fields
+
+
+
+
+
+
+ Multiple input types
+
+
+
+
+
+
+ Form password
+
+
+
+
+
+
+
+
+ Closing date
+
+
+
+
+
+
+ And much more...
+
+
+
+
+
+
+
+
+
+ Nonprofit & Student Discount — 50%
+
+
+ Whether your nonprofit is large or small, OpnForm’s online Form Builder helps your organization help
+ others. It takes just a few minutes to create and publish your forms online. As an exclusive benefit,
+ we offer nonprofits & students a 50-percent discount!
+
+
+
+
+
+
+
+
+
+
+
+ Got any question?
+
+
+ We've compiled a list of the most common questions we get asked.
+
+
+
+
+
+
-
+ {{ q.question }}
+
+ -
+ {{ q.answer }}
+
+
+
+
+
+
+ Didn't find the answer? Contact us
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/pages/settings/index.vue b/resources/js/pages/settings/index.vue
index b1b924f..b371a51 100644
--- a/resources/js/pages/settings/index.vue
+++ b/resources/js/pages/settings/index.vue
@@ -16,7 +16,7 @@
-
{{tab.name}}
diff --git a/resources/js/router/routes.js b/resources/js/router/routes.js
index ac52ef3..d31da75 100644
--- a/resources/js/router/routes.js
+++ b/resources/js/router/routes.js
@@ -61,6 +61,7 @@ export default [
// Guest Routes
{ path: '/', name: 'welcome', component: page('welcome.vue') },
+ { path: '/pricing', name: 'pricing', component: page('pricing.vue') },
{ path: '/ai-form-builder', name: 'aiformbuilder', component: page('ai-form-builder.vue') },
{ path: '/integrations', name: 'integrations', component: page('integrations.vue') },
{ path: '/forms/:slug', name: 'forms.show_public', component: page('forms/show-public.vue') },
diff --git a/resources/views/spa.blade.php b/resources/views/spa.blade.php
index 0be87c3..6d909c5 100644
--- a/resources/views/spa.blade.php
+++ b/resources/views/spa.blade.php
@@ -14,7 +14,8 @@
'amplitude_code' => config('services.amplitude_code'),
'crisp_website_id' => config('services.crisp_website_id'),
'ai_features_enabled' => !is_null(config('services.openai.api_key')),
- 's3_enabled' => config('filesystems.default') === 's3'
+ 's3_enabled' => config('filesystems.default') === 's3',
+ 'paid_plans_enabled' => !is_null(config('cashier.key'))
];
@endphp
diff --git a/routes/api.php b/routes/api.php
index e588568..005fe67 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -41,6 +41,7 @@ Route::group(['middleware' => 'auth:api'], function () {
Route::patch('settings/password', [PasswordController::class, 'update']);
Route::prefix('subscription')->name('subscription.')->group(function () {
+ Route::put('/update-customer-details', [SubscriptionController::class, 'updateStripeDetails'])->name('update-stripe-details');
Route::get('/new/{subscription}/{plan}/checkout/{trial?}', [SubscriptionController::class, 'checkout'])
->name('checkout')
->where('subscription', '('.implode('|', SubscriptionController::SUBSCRIPTION_NAMES).')')
@@ -70,9 +71,7 @@ Route::group(['middleware' => 'auth:api'], function () {
[FormController::class, 'index'])->name('forms.index');
Route::delete('/', [WorkspaceController::class, 'delete'])->name('delete');
- Route::middleware('pro-form')->group(function () {
- Route::get('form-stats/{formId}', [FormStatsController::class, 'getFormStats'])->name('form.stats');
- });
+ Route::get('form-stats/{formId}', [FormStatsController::class, 'getFormStats'])->name('form.stats');
});
});
@@ -84,7 +83,7 @@ Route::group(['middleware' => 'auth:api'], function () {
Route::get('/{id}/submissions', [FormSubmissionController::class, 'submissions'])->name('submissions');
Route::get('/{id}/submissions/export', [FormSubmissionController::class, 'export'])->name('submissions.export');
Route::get('/{id}/submissions/file/{filename}', [FormSubmissionController::class, 'submissionFile'])->name('submissions.file');
-
+
Route::delete('/{id}/records/{recordid}/delete', [RecordController::class, 'delete'])->name('records.delete');
// Form Admin tool
diff --git a/tests/Feature/Forms/ConfirmationEmailTest.php b/tests/Feature/Forms/ConfirmationEmailTest.php
index 3e7df13..2f7683f 100644
--- a/tests/Feature/Forms/ConfirmationEmailTest.php
+++ b/tests/Feature/Forms/ConfirmationEmailTest.php
@@ -50,7 +50,7 @@ it('creates confirmation emails without the submitted data', function () {
});
it('sends a confirmation email if needed', function () {
- $user = $this->actingAsUser();
+ $user = $this->actingAsProUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace, [
'send_submission_confirmation' => true,
diff --git a/tests/Feature/Forms/FormStatTest.php b/tests/Feature/Forms/FormStatTest.php
index de1a7f6..19222cc 100644
--- a/tests/Feature/Forms/FormStatTest.php
+++ b/tests/Feature/Forms/FormStatTest.php
@@ -38,6 +38,8 @@ it('check formstat chart data', function () {
}
// Now check chart data
+ $response = $this->getJson(route('open.workspaces.form.stats', [$workspace->id, $form->id]));
+
$this->getJson(route('open.workspaces.form.stats', [$workspace->id, $form->id]))
->assertSuccessful()
->assertJson(function (\Illuminate\Testing\Fluent\AssertableJson $json) use ($views, $submissions) {
@@ -62,4 +64,4 @@ it('check formstat chart data', function () {
->etc();
});
-});
\ No newline at end of file
+});
diff --git a/tests/Feature/Forms/FormUpdateTest.php b/tests/Feature/Forms/FormUpdateTest.php
index 7de4c21..1a0f446 100644
--- a/tests/Feature/Forms/FormUpdateTest.php
+++ b/tests/Feature/Forms/FormUpdateTest.php
@@ -3,7 +3,7 @@
use Tests\Helpers\FormSubmissionDataFactory;
it('can update form with existing record', function () {
- $user = $this->actingAsUser();
+ $user = $this->actingAsProUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace, [
'editable_submissions' => true,
diff --git a/vite.config.js b/vite.config.js
index 09d936c..daf4732 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -11,7 +11,8 @@ export default defineConfig({
laravel({
input: [
'resources/js/app.js'
- ]
+ ],
+ valetTls: 'opnform.test'
}),
vue({
template: {