Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
4e13d20
Refactor landing page as per V2 design
chiragchhatrala Jan 28, 2026
ecd74a1
Opnform vs Typeform, enterprise, Testimonials, and landing page impro…
chiragchhatrala Jan 29, 2026
58d2452
Fix missing newline at end of integrations.json file to comply with f…
chiragchhatrala Jan 29, 2026
1913d2a
Pricing, Industry, Feature Comparision pages as per V2 design
chiragchhatrala Feb 2, 2026
38fa04e
improve footer as per new V2 design
chiragchhatrala Feb 2, 2026
6b61fcf
improve landing page UI
chiragchhatrala Feb 2, 2026
25090f4
improve V2 landing page as per final UI
chiragchhatrala Feb 10, 2026
b9add1a
improve V2 pages as per final UI
chiragchhatrala Feb 10, 2026
ae8d0d1
improve V2 pages as per final UI
chiragchhatrala Feb 10, 2026
cd26bb9
Improve new design V2
chiragchhatrala Feb 12, 2026
2ba95ca
Improve new design V2
chiragchhatrala Feb 12, 2026
10319bd
remove extra pricing component
chiragchhatrala Feb 13, 2026
5a80f3a
New Pricing changes
chiragchhatrala Feb 16, 2026
724b22a
New Pricing changes
chiragchhatrala Feb 18, 2026
51484de
update testcase for new pricing
chiragchhatrala Feb 18, 2026
0586b82
update testcase
chiragchhatrala Feb 18, 2026
ff28195
New Pricing changes
chiragchhatrala Feb 19, 2026
69b06a3
New Pricing changes
chiragchhatrala Feb 20, 2026
b2328cf
Version restore as business plan
chiragchhatrala Feb 20, 2026
c1bfe39
Version restore as business plan
chiragchhatrala Feb 20, 2026
9b91969
update testcase
chiragchhatrala Feb 20, 2026
c927309
SSO now enterprise plan
chiragchhatrala Feb 20, 2026
7b80633
fix testcase
chiragchhatrala Feb 20, 2026
bc9b24e
New Pricing changes
chiragchhatrala Feb 20, 2026
be9fd14
Fix formatting in summary.vue for subscription message
chiragchhatrala Feb 20, 2026
76e9cb4
Merge branch 'main-v2' into 2f1a6-new-pricing
chiragchhatrala Mar 17, 2026
68afe2c
Refactor PdfEditorNavbar component to replace ProTag with PlanTag
chiragchhatrala Mar 17, 2026
0c97097
Refactor plan handling and feature access across the application
chiragchhatrala Mar 18, 2026
807222e
Merge branch 'main-v2' into 2f1a6-new-pricing
chiragchhatrala Mar 18, 2026
4df2087
Merge branch 'main-v2' into 2f1a6-new-pricing
chiragchhatrala Mar 18, 2026
a238866
Refactor subscription handling and improve user model
chiragchhatrala Mar 18, 2026
a9d58e4
Update version restoration test to reflect new error handling
chiragchhatrala Mar 18, 2026
63204d1
Enhance plan tier handling and feature access across the application
chiragchhatrala Mar 19, 2026
00a3032
Refactor plan features and pricing display for improved clarity
chiragchhatrala Mar 19, 2026
08bccb3
Refactor plan features handling by removing form features
chiragchhatrala Mar 19, 2026
8f9a145
Update plan tiers configuration and simplify API response structure
chiragchhatrala Mar 19, 2026
d1ecb7a
Refactor pricing display and plan features integration
chiragchhatrala Mar 19, 2026
614778e
Remove nonprofit and student discount section from pricing page; add …
chiragchhatrala Mar 19, 2026
b9c999b
Refactor pricing permissions and billing access
JhumanJ Mar 21, 2026
9788eb4
Enhance feature access logic in PlanAccessService
JhumanJ Mar 22, 2026
0264a69
Refactor UI components for improved layout and accessibility
JhumanJ Mar 22, 2026
ce24f80
Refactor pricing and feature display for improved layout and clarity
JhumanJ Mar 22, 2026
f4c1c3a
fix pricing source and feature gating
JhumanJ Mar 22, 2026
75c7793
Refactor exception handling and improve health check logic
JhumanJ Mar 22, 2026
661a5af
Align pricing page CTAs with upgrade flows
JhumanJ Mar 22, 2026
0e57c07
Update OpenFormFooter links, enhance layout in Integrations and MoreF…
JhumanJ Mar 22, 2026
6fda968
Add Playwright E2E coverage and CI gating (#1056)
JhumanJ Mar 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
New Pricing changes
  • Loading branch information
chiragchhatrala committed Feb 19, 2026
commit ff28195573b9f24a88e06502f1d0b3991748cb4d
6 changes: 6 additions & 0 deletions 6 api/app/Http/Resources/FormResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public function toArray($request)
return array_merge(parent::toArray($request), $ownerData, [
'settings' => $this->settings ?? new \stdClass(),
'is_pro' => $this->workspaceIsPro(),
'is_business' => $this->workspaceIsBusiness(),
'is_trialing' => $this->workspaceIsTrialing(),
'workspace_id' => $this->workspace_id,
'workspace' => $this->userIsFormOwner()
Expand Down Expand Up @@ -111,6 +112,11 @@ private function workspaceIsPro()
return $this->workspace->is_pro ?? $this->is_pro;
}

private function workspaceIsBusiness()
{
return $this->workspace->is_business ?? $this->is_business;
}

private function workspaceIsTrialing()
{
return $this->workspace->is_trialing;
Expand Down
1 change: 1 addition & 0 deletions 1 api/app/Http/Resources/UserResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public function toArray($request)

// Legacy - kept for backward compatibility (derive from plan_tier in frontend)
'is_pro' => $this->is_pro,
'is_business' => $this->is_business,
'is_subscribed' => $this->is_subscribed,

'admin' => $this->admin,
Expand Down
7 changes: 7 additions & 0 deletions 7 api/app/Models/Forms/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,13 @@ public function getIsProAttribute()
});
}

public function getIsBusinessAttribute()
{
return $this->remember('is_business', 15 * 60, function (): ?bool {
return $this->workspace?->is_business === true;
});
}

public function getShareUrlAttribute()
{
if ($this->custom_domain) {
Expand Down
17 changes: 17 additions & 0 deletions 17 api/app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ protected function casts()
'has_forms',
'is_subscribed',
'is_pro',
'is_business',
'active_license',
'plan_tier',
];
Expand Down Expand Up @@ -203,6 +204,22 @@ public function getIsProAttribute()
});
}

public function getIsBusinessAttribute()
{
return $this->remember('is_business', 5 * 60, function (): bool {
// Use loaded relationship if available to avoid queries
if ($this->relationLoaded('workspaces')) {
return $this->workspaces->some(function ($workspace) {
return $workspace->is_business;
});
}

return $this->workspaces()->get()->some(function ($workspace) {
return $workspace->is_business;
});
});
}

/**
* Get the user's current plan tier.
* This is the SINGLE source of truth for plan status.
Expand Down
51 changes: 41 additions & 10 deletions 51 api/app/Models/Workspace.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class Workspace extends Model implements CachableAttributes

public const MAX_DOMAIN_PRO = 1;

private const CACHE_TTL = 15 * 60;

protected $fillable = [
'name',
'icon',
Expand All @@ -37,6 +39,7 @@ class Workspace extends Model implements CachableAttributes
protected $appends = [
'plan_tier',
'is_pro',
'is_business',
'is_trialing',
'is_enterprise',
'users_count',
Expand All @@ -55,6 +58,7 @@ protected function casts(): array
protected $cachableAttributes = [
'plan_tier',
'is_pro',
'is_business',
'is_trialing',
'is_enterprise',
'is_risky',
Expand All @@ -65,13 +69,27 @@ protected function casts(): array
'users_count',
];

/**
* Flush workspace cache and also flush owners' cache (is_pro depends on workspace tier).
*/
public function flush(): bool
{
$result = parent::flush();

foreach ($this->owners as $owner) {
$owner->flush();
}

return $result;
}

public function getMaxFileSizeAttribute()
{
if (!pricing_enabled()) {
return self::MAX_FILE_SIZE_PRO;
}

return $this->remember('max_file_size', 15 * 60, function (): int {
return $this->remember('max_file_size', self::CACHE_TTL, function (): int {
// 1. Check workspace-level override
$overrideLimit = $this->plan_overrides['limits']['file_upload_size'] ?? null;
if ($overrideLimit !== null) {
Expand All @@ -98,7 +116,7 @@ public function getCustomDomainCountLimitAttribute()
return null;
}

return $this->remember('custom_domain_count', 15 * 60, function (): ?int {
return $this->remember('custom_domain_count', self::CACHE_TTL, function (): ?int {
// 1. Check workspace-level override
$overrideLimit = $this->plan_overrides['limits']['custom_domain_count'] ?? null;
if ($overrideLimit !== null) {
Expand Down Expand Up @@ -140,20 +158,33 @@ public function getIsProAttribute()
return true; // If no paid plan so TRUE for ALL
}

return $this->remember('is_pro', 15 * 60, function (): bool {
return $this->remember('is_pro', self::CACHE_TTL, function (): bool {
$tier = app(\App\Service\Plan\PlanService::class)->computeWorkspaceTier($this);

return in_array($tier, ['pro', 'business', 'enterprise']);
});
}

public function getIsBusinessAttribute()
{
if (!pricing_enabled()) {
return true; // If no paid plan so TRUE for ALL
}

return $this->remember('is_business', self::CACHE_TTL, function (): bool {
$tier = app(\App\Service\Plan\PlanService::class)->computeWorkspaceTier($this);

return in_array($tier, ['business', 'enterprise']);
});
}

public function getIsTrialingAttribute()
{
if (!pricing_enabled()) {
return false; // If no paid plan so FALSE for ALL
}

return $this->remember('is_trialing', 15 * 60, function (): bool {
return $this->remember('is_trialing', self::CACHE_TTL, function (): bool {
// Make sure at least one owner is trialing
$owners = $this->relationLoaded('users')
? $this->users->where('pivot.role', 'admin')
Expand All @@ -179,7 +210,7 @@ public function getIsEnterpriseAttribute()
return true; // If no paid plan so TRUE for ALL
}

return $this->remember('is_enterprise', 15 * 60, function (): bool {
return $this->remember('is_enterprise', self::CACHE_TTL, function (): bool {
$tier = app(\App\Service\Plan\PlanService::class)->computeWorkspaceTier($this);

return $tier === 'enterprise';
Expand All @@ -188,7 +219,7 @@ public function getIsEnterpriseAttribute()

public function getIsRiskyAttribute()
{
return $this->remember('is_risky', 15 * 60, function (): bool {
return $this->remember('is_risky', self::CACHE_TTL, function (): bool {
foreach ($this->owners as $owner) {
if (!$owner->is_risky) {
return false;
Expand All @@ -205,7 +236,7 @@ public function getIsYearlyPlanAttribute()
return false;
}

return $this->remember('is_yearly_plan', 15 * 60, function (): bool {
return $this->remember('is_yearly_plan', self::CACHE_TTL, function (): bool {
$owners = $this->relationLoaded('users')
? $this->users->where('pivot.role', 'admin')
: $this->owners()->get();
Expand All @@ -225,7 +256,7 @@ public function getIsYearlyPlanAttribute()

public function getSubmissionsCountAttribute()
{
return $this->remember('submissions_count', 15 * 60, function (): int {
return $this->remember('submissions_count', self::CACHE_TTL, function (): int {
$total = 0;
// Use loaded relationship if available to avoid queries
$forms = $this->relationLoaded('forms')
Expand All @@ -242,7 +273,7 @@ public function getSubmissionsCountAttribute()

public function getUsersCountAttribute()
{
return $this->remember('users_count', 15 * 60, function (): int {
return $this->remember('users_count', self::CACHE_TTL, function (): int {
// Use loaded relationship if available to avoid queries
if ($this->relationLoaded('users')) {
return $this->users->count();
Expand Down Expand Up @@ -349,7 +380,7 @@ public function isReadonlyUser(?User $user)
*/
public function hasFeature(string $feature): bool
{
return $this->remember('has_feature_' . $feature, 15 * 60, function () use ($feature): bool {
return $this->remember('has_feature_' . $feature, self::CACHE_TTL, function () use ($feature): bool {
return app(\App\Service\Plan\PlanService::class)->workspaceHasFeature($this, $feature);
});
}
Expand Down
2 changes: 1 addition & 1 deletion 2 client/components/dashboard/WorkspaceDropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ const isDropdownOpen = ref(false)
// Computed text for workspace plan
const workspacePlanText = computed(() => {
if (!workspace.value) return ''
return workspace.value.is_pro ? 'Pro Plan' : 'Free Plan'
return workspace.value.is_business ? 'Business Plan' : workspace.value.is_pro ? 'Pro Plan' : 'Free Plan'
})

// Computed text for member count
Expand Down
13 changes: 13 additions & 0 deletions 13 client/components/layouts/AppSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,23 @@ const navigationSections = computed(() => [
onClick: () => {
useAmplitude().logEvent('app_sidebar_upgrade_click')
openSubscriptionModal({
plan: 'pro',
modal_title: 'Upgrade to Pro plan',
})
},
color: 'primary' // Override default color
})] : []),
...(workspace.value && workspace.value.is_pro && !workspace.value.is_business && !isSelfHosted.value ? [createNavItem({
label: 'Upgrade to Business',
icon: 'i-heroicons-sparkles-solid',
onClick: () => {
useAmplitude().logEvent('app_sidebar_upgrade_click')
openSubscriptionModal({
plan: 'business',
modal_title: 'Upgrade to Business plan',
})
},
color: 'primary' // Override default color
})] : [])
]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<h3 class="text-lg font-medium text-neutral-900">
SEO & Social Sharing - Meta <PlanTag
class="ml-2"
required-tier="business"
upgrade-modal-title="Upgrade to Enhance Your Form's SEO"
upgrade-modal-description="Explore advanced SEO features in the editor on our Free plan. Upgrade to fully implement custom meta tags, Open Graph data, and improved search visibility. Boost your form's online presence and attract more respondents with our premium SEO toolkit."
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
</span>
<PlanTag
class="ml-1"
required-tier="enterprise"
upgrade-modal-title="Upgrade to collect IP addresses"
upgrade-modal-description="Automatically capture submitter IP addresses to gain valuable insights into your form traffic. Analyze geographic patterns, detect suspicious activity, and enhance your form security with detailed submission analytics."
/>
Expand Down
2 changes: 1 addition & 1 deletion 2 client/components/open/tables/OpenTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ const sortedData = computed(() => {
const filteredTableData = computed(() => props.data || [])

const hasStatus = computed(() => {
return props.form?.is_pro && (props.form.enable_partial_submissions ?? false)
return props.form?.is_business && (props.form.enable_partial_submissions ?? false)
})

// Since UTable only renders when form exists, no need for safe wrappers
Expand Down
9 changes: 3 additions & 6 deletions 9 client/components/pages/pricing/SubscriptionModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,7 @@
<section class="flex flex-col w-full max-w-[800px] max-md:max-w-full">
<div class="bg-white max-md:max-w-full">
<div class="flex gap-2 max-md:flex-col max-md:gap-0">
<article
v-if="!isSubscribed"
class="flex flex-col w-6/12 max-md:ml-0 max-md:w-full m-auto"
>
<article class="flex flex-col w-6/12 max-md:ml-0 max-md:w-full m-auto">
<div
class="flex flex-col grow justify-between p-6 w-full rounded-2xl max-md:px-5 max-md:mt-2"
:class="planCardClass"
Expand All @@ -76,7 +73,7 @@
</div>
</div>
<TrackClick
v-if="!user?.is_subscribed"
v-if="['free', 'pro'].includes(user?.plan_tier)"
name="upgrade_modal_start_trial"
:properties="{ plan: currentPlan, period: isYearly ? 'yearly' : 'monthly' }"
class="w-full"
Expand Down Expand Up @@ -161,7 +158,7 @@
</div>
<div class="flex-grow w-full max-w-sm">
<div
v-if="!isSubscribed"
v-if="['free', 'pro'].includes(user?.plan_tier)"
class="rounded-md p-4 border flex flex-col my-4 gap-1"
:class="confirmationBoxClass"
>
Expand Down
2 changes: 1 addition & 1 deletion 2 client/composables/components/tables/useTableState.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ export function useTableState(form, withActions = false) {
const cols = orderedColumns.value && Array.isArray(orderedColumns.value) ? [...orderedColumns.value] : []

// Add status column if needed
if (form.value?.is_pro && (form.value.enable_partial_submissions ?? false)) {
if (form.value?.is_business && (form.value.enable_partial_submissions ?? false)) {
cols.push({
id: 'status',
accessorKey: 'status',
Expand Down
2 changes: 1 addition & 1 deletion 2 client/pages/forms/[slug]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ onBeforeRouteLeave(() => {
})

const pageMeta = computed(() => {
if (form.value && form.value.is_pro && form.value.seo_meta) {
if (form.value && form.value.is_business && form.value.seo_meta) {
return form.value.seo_meta
}
return {}
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.