From f8d74a1933fcbb8a658bf3333fe607e06b950003 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 1 Dec 2025 15:53:56 +0000
Subject: [PATCH 1/5] feat: Add BSL 1.1 licensing and update pricing model
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Add BSL 1.1 LICENSE file (source available, commercial hosting restricted)
- Update README with new pricing tiers and self-hosting info
- Update MyPlan.tsx with correct hosted tier limits:
- Free: 100 jobs, 1K parts/mo, 5GB
- Pro: 1K jobs/mo, 10K parts/mo, 50GB (€97/mo)
- Enterprise: Unlimited, SSO, dedicated (€497/mo)
- Update Pricing.tsx with self-hosted option callout
- Add comprehensive SELF_HOSTING_GUIDE.md documentation
Self-hosted (BSL 1.1) is unlimited and free, separate from hosted tiers.
Upgrades via email request (no Stripe integration yet).
---
LICENSE | 116 ++++++++++++
README.md | 95 +++++-----
docs/SELF_HOSTING_GUIDE.md | 361 ++++++++++++++++++++++++++++++++++++
src/pages/Pricing.tsx | 40 +++-
src/pages/common/MyPlan.tsx | 27 ++-
5 files changed, 569 insertions(+), 70 deletions(-)
create mode 100644 LICENSE
create mode 100644 docs/SELF_HOSTING_GUIDE.md
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..053fba24
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,116 @@
+Business Source License 1.1
+
+License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
+"Business Source License" is a trademark of MariaDB Corporation Ab.
+
+-----------------------------------------------------------------------------
+
+Parameters
+
+Licensor: Sheet Metal Connect e.U.
+Licensed Work: Eryxon Flow
+ The Licensed Work is (c) 2025 Sheet Metal Connect e.U.
+Additional Use Grant: You may make use of the Licensed Work, provided that
+ you do not use the Licensed Work for a Production Use
+ that is Competing with any offering of the Licensor.
+
+ A Production Use is Competing if it offers to third
+ parties access to the Licensed Work in a manner that
+ competes with or substitutes for the Licensor's own
+ commercial hosting offering of the Licensed Work.
+
+ For clarity, the following uses are expressly permitted:
+
+ 1. Self-hosting the Licensed Work for your own internal
+ business operations, regardless of organization size
+
+ 2. Using the Licensed Work for development, testing,
+ evaluation, and educational purposes
+
+ 3. Offering consulting, integration, or customization
+ services built around the Licensed Work
+
+ 4. Creating derivative works for your own internal use
+
+Change Date: Four years from the date the Licensed Work is published
+
+Change License: Apache License, Version 2.0
+
+-----------------------------------------------------------------------------
+
+Terms
+
+The Licensor hereby grants you the right to copy, modify, create derivative
+works, redistribute, and make non-production use of the Licensed Work. The
+Licensor may make an Additional Use Grant, above, permitting limited
+production use.
+
+Effective on the Change Date, or the fourth anniversary of the first publicly
+available distribution of a specific version of the Licensed Work under this
+License, whichever comes first, the Licensor hereby grants you rights under
+the terms of the Change License, and the rights granted in the paragraph
+above terminate.
+
+If your use of the Licensed Work does not comply with the requirements
+currently in effect as described in this License, you must purchase a
+commercial license from the Licensor, its affiliated entities, or authorized
+resellers, or you must refrain from using the Licensed Work.
+
+All copies of the original and modified Licensed Work, and derivative works
+of the Licensed Work, are subject to this License. This License applies
+separately for each version of the Licensed Work and the Change Date may vary
+for each version of the Licensed Work released by Licensor.
+
+You must conspicuously display this License on each original or modified copy
+of the Licensed Work. If you receive the Licensed Work in original or
+modified form from a third party, the terms and conditions set forth in this
+License apply to your use of that work.
+
+Any use of the Licensed Work in violation of this License will automatically
+terminate your rights under this License for the current and all other
+versions of the Licensed Work.
+
+This License does not grant you any right in any trademark or logo of
+Licensor or its affiliates (provided that you may use a trademark or logo of
+Licensor as expressly required by this License).
+
+TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
+AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
+EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
+TITLE.
+
+MariaDB hereby grants you permission to use this License's text to license
+your works, and to refer to it using the trademark "Business Source License",
+as long as you comply with the Covenants of Licensor below.
+
+-----------------------------------------------------------------------------
+
+Covenants of Licensor
+
+In consideration of the right to use this License's text and the "Business
+Source License" name and trademark, Licensor covenants to MariaDB, and to all
+other recipients of the licensed work to be provided by Licensor:
+
+1. To specify as the Change License the GPL Version 2.0 or any later version,
+ or a license that is compatible with GPL Version 2.0 or a later version,
+ where "compatible" means that software provided under the Change License can
+ be included in a program with software provided under GPL Version 2.0 or a
+ later version. Licensor may specify additional Change Licenses without
+ limitation.
+
+2. To either: (a) specify an additional grant of rights to use that does not
+ impose any additional restriction on the right granted in this License, as
+ the Additional Use Grant; or (b) insert the text "None".
+
+3. To specify a Change Date.
+
+4. Not to modify this License in any other way.
+
+-----------------------------------------------------------------------------
+
+Notice
+
+The Business Source License (this document, or the "License") is not an Open
+Source license. However, the Licensed Work will eventually be made available
+under an Open Source License, as stated in this License.
diff --git a/README.md b/README.md
index 7ee2af8a..f5f50b34 100644
--- a/README.md
+++ b/README.md
@@ -1,41 +1,27 @@
-# Eryxon Flow - Manufacturing Execution System
+# Eryxon MES
-**Internal Project - Proprietary**
+**The simple, elegant and powerful manufacturing execution system that your people will love to use. Made for SMB metal fabrication.**
-
-
-
-
+## What Makes This Different
-
+ Want to run it yourself? The source code is available under BSL 1.1.
+ Self-host on your own infrastructure with unlimited jobs, parts, and storage.
+ Community support via docs only. See the{" "}
+
+ GitHub repository
+ for setup instructions.
@@ -202,29 +222,29 @@ Thank you!`;
-
Self-Service Only
+
Self-Service
- No onboarding calls. No consultants. No phone support. Sign up, configure your stages
- and materials, connect your API, and go. Documentation and email support only.
+ No onboarding calls. No consultants. Sign up, configure your stages
+ and materials, connect your API, and go. Documentation and email support (Pro+) only.
- Monitor your usage, track jobs and parts count, and manage your subscription from your{' '}
+ Monitor your usage from your{' '}
My Plan page.
- You'll see real-time usage statistics and receive alerts when approaching tier limits.
+ You'll see real-time statistics and alerts when approaching limits.
diff --git a/src/pages/common/MyPlan.tsx b/src/pages/common/MyPlan.tsx
index 375837cc..08e00c40 100644
--- a/src/pages/common/MyPlan.tsx
+++ b/src/pages/common/MyPlan.tsx
@@ -28,15 +28,14 @@ const pricingTiers = [
id: "free",
name: "Free",
price: "€0",
- description: "Perfect for small shops getting started",
+ description: "Try the hosted version with limits",
features: [
- "100 jobs per month",
+ "100 jobs",
"1,000 parts per month",
"5 GB file storage",
"Limited API access",
- "Multi-tenant architecture",
"Community support (docs only)",
- "Basic workflow tracking",
+ "Multi-tenant hosted",
],
},
{
@@ -44,36 +43,32 @@ const pricingTiers = [
name: "Pro",
price: "€97",
popular: true,
- description: "For growing shops with higher volume",
+ description: "We run it for you, multi-tenant",
features: [
"Unlimited users",
"1,000 jobs per month",
"10,000 parts per month",
"50 GB file storage",
"Full API access",
- "Multi-tenant architecture",
- "Priority email support",
- "Advanced analytics",
"Webhook integrations",
+ "Email support",
+ "EU-hosted (Hetzner)",
],
},
{
id: "premium",
name: "Enterprise",
price: "€497",
- description: "Custom, bespoke solution",
+ description: "Hand-holding tier, your infra or ours",
features: [
- "Everything in Pro",
"Unlimited jobs & parts",
"Unlimited storage",
- "Unlimited usage",
- "Self-hosted (on-premises)",
"Single-tenant architecture",
+ "Your infrastructure OR dedicated hosted",
+ "Email support + SLA",
"SSO/SAML authentication",
- "Dedicated support channel",
- "Custom SLA",
- "White-label options",
- "Advanced security controls",
+ "Custom integrations",
+ "Onboarding assistance",
],
},
];
From 2c476b0c2d5d777f9012ab9ec5c2847b70fc5ceb Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 1 Dec 2025 16:01:15 +0000
Subject: [PATCH 2/5] fix: Update pricing tiers to accurate model
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Free tier (hosted):
- 25 jobs/mo, 250 parts/mo, 500MB storage
- Limited API, NO webhooks, NO MCP server
- Docs only support
Pro tier (€97/mo):
- 500 jobs/mo, 5K parts/mo, 10GB storage
- Full API, webhooks, MCP server
- Email support (no SLA)
Enterprise tier (€497/mo):
- Unlimited, their infrastructure
- SSO/SAML, white-label, dedicated support
Self-hosted (free):
- Unlimited everything, they manage it
- Community/docs support only
---
README.md | 33 ++++++++++++++++++-----------
src/pages/Pricing.tsx | 10 ++++-----
src/pages/common/MyPlan.tsx | 42 ++++++++++++++++++-------------------
3 files changed, 47 insertions(+), 38 deletions(-)
diff --git a/README.md b/README.md
index f5f50b34..1763e193 100644
--- a/README.md
+++ b/README.md
@@ -102,18 +102,27 @@ Additional documentation:
- **API Security**: Bearer token auth with bcrypt-hashed keys
- **Webhooks**: HMAC-SHA256 signatures for verification
-## Pricing (Hosted)
-
-| Tier | Limits | Support | Price |
-|------|--------|---------|-------|
-| **Free** | 100 jobs/mo, 1K parts | Docs only | €0 |
-| **Pro** | 1K jobs/mo, 10K parts | Email | €97/mo |
-| **Enterprise** | Unlimited | Dedicated + SSO | €497/mo |
-
-- **Free** = Try it without installing anything
-- **Enterprise** = Hand-holding tier (your infra or ours)
-
-Want to self-host? See the [Self-Hosting Guide](docs/SELF_HOSTING_GUIDE.md) - unlimited, free, you manage it.
+## Pricing
+
+| | Free | Pro | Enterprise | Self-hosted |
+|---|---|---|---|---|
+| **Price** | €0 | €97/mo | €497/mo | €0 |
+| **Where** | My infra | My infra | Your infra | Your infra |
+| **Jobs/mo** | 25 | 500 | Unlimited | Unlimited |
+| **Parts/mo** | 250 | 5,000 | Unlimited | Unlimited |
+| **Storage** | 500MB | 10GB | Your cost | Your cost |
+| **API** | Limited | Full | Full | Full |
+| **Webhooks** | No | Yes | Yes | Yes |
+| **MCP Server** | No | Yes | Yes | Yes |
+| **SSO/SAML** | No | No | Yes | DIY |
+| **Support** | Docs | Email | Dedicated | Community |
+
+- **Free** — Try it. Limited. No support.
+- **Pro** — I run it. Real usage. Email support.
+- **Enterprise** — Your network, your Supabase, I deploy and support it.
+- **Self-hosted** — Download it, run it yourself, unlimited, you're on your own.
+
+Want to self-host? See the [Self-Hosting Guide](docs/SELF_HOSTING_GUIDE.md).
Need integration work or custom forks? [Contact us](mailto:office@sheetmetalconnect.com).
diff --git a/src/pages/Pricing.tsx b/src/pages/Pricing.tsx
index ce8e6d9c..e9d44539 100644
--- a/src/pages/Pricing.tsx
+++ b/src/pages/Pricing.tsx
@@ -113,14 +113,14 @@ Thank you!`;
-
Self-Hosted Option (Free, Unlimited)
+
Self-Hosted (Free, Unlimited)
- Want to run it yourself? The source code is available under BSL 1.1.
- Self-host on your own infrastructure with unlimited jobs, parts, and storage.
- Community support via docs only. See the{" "}
+ Download it, run it yourself, you're on your own. Source code available under BSL 1.1.
+ Unlimited jobs, parts, storage. Full API, webhooks, MCP server.
+ Community support via docs and GitHub Discussions only. See the{" "}
GitHub repository
- for setup instructions.
+ .
diff --git a/src/pages/common/MyPlan.tsx b/src/pages/common/MyPlan.tsx
index 08e00c40..7024905a 100644
--- a/src/pages/common/MyPlan.tsx
+++ b/src/pages/common/MyPlan.tsx
@@ -28,14 +28,15 @@ const pricingTiers = [
id: "free",
name: "Free",
price: "€0",
- description: "Try the hosted version with limits",
+ description: "Try it. Limited. No support.",
features: [
- "100 jobs",
- "1,000 parts per month",
- "5 GB file storage",
+ "25 jobs per month",
+ "250 parts per month",
+ "500 MB storage",
"Limited API access",
- "Community support (docs only)",
- "Multi-tenant hosted",
+ "No webhooks",
+ "No MCP server",
+ "Docs only",
],
},
{
@@ -43,32 +44,31 @@ const pricingTiers = [
name: "Pro",
price: "€97",
popular: true,
- description: "We run it for you, multi-tenant",
+ description: "We run it. Real usage. Email support.",
features: [
- "Unlimited users",
- "1,000 jobs per month",
- "10,000 parts per month",
- "50 GB file storage",
+ "500 jobs per month",
+ "5,000 parts per month",
+ "10 GB storage",
"Full API access",
- "Webhook integrations",
- "Email support",
- "EU-hosted (Hetzner)",
+ "Webhooks included",
+ "MCP server access",
+ "Email support (no SLA)",
+ "Extra storage on request",
],
},
{
id: "premium",
name: "Enterprise",
price: "€497",
- description: "Hand-holding tier, your infra or ours",
+ description: "Your network, we deploy and support it.",
features: [
"Unlimited jobs & parts",
- "Unlimited storage",
- "Single-tenant architecture",
- "Your infrastructure OR dedicated hosted",
- "Email support + SLA",
+ "Your infrastructure",
+ "Your Supabase instance",
"SSO/SAML authentication",
- "Custom integrations",
- "Onboarding assistance",
+ "White-label options",
+ "Dedicated support channel",
+ "Custom SLA available",
],
},
];
From 3fc5a37825d22047e98a7f99ab54c479e5907bf4 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 1 Dec 2025 16:04:33 +0000
Subject: [PATCH 3/5] feat: Add plan limits migration and .env.example for BSL
1.1
- Add migration to update plan limits:
- Free: 25 jobs/mo, 250 parts/mo, 1GB storage
- Pro: 500 jobs/mo, 5000 parts/mo, 10GB storage
- Enterprise: Unlimited (NULL)
- Update handle_new_tenant() with new free tier defaults
- Add get_plan_limits() function for plan info
- Update plan-limits.ts comments to reflect BSL 1.1 model
- Add .env.example for self-hosters with setup instructions
---
.env.example | 42 +++++++
supabase/functions/_shared/plan-limits.ts | 9 +-
.../20251201000000_update_plan_limits_bsl.sql | 114 ++++++++++++++++++
3 files changed, 161 insertions(+), 4 deletions(-)
create mode 100644 .env.example
create mode 100644 supabase/migrations/20251201000000_update_plan_limits_bsl.sql
diff --git a/.env.example b/.env.example
new file mode 100644
index 00000000..f996e373
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,42 @@
+# Eryxon Flow - Environment Configuration
+# Copy this file to .env and fill in your values
+
+# =============================================================================
+# SUPABASE CONFIGURATION (Required)
+# =============================================================================
+# Get these from your Supabase project dashboard: Settings -> API
+
+# Your Supabase project URL
+VITE_SUPABASE_URL="https://your-project-id.supabase.co"
+
+# Supabase anon/public key (safe to expose in frontend)
+VITE_SUPABASE_PUBLISHABLE_KEY="your-anon-key-here"
+
+# Supabase project ID (the part before .supabase.co)
+VITE_SUPABASE_PROJECT_ID="your-project-id"
+
+# =============================================================================
+# OPTIONAL CONFIGURATION
+# =============================================================================
+
+# App title (shown in browser tab)
+# VITE_APP_TITLE="Eryxon Flow"
+
+# Default language (en, nl, de)
+# VITE_DEFAULT_LANGUAGE="en"
+
+# =============================================================================
+# SELF-HOSTED NOTES
+# =============================================================================
+#
+# For self-hosted deployments:
+# 1. Create a Supabase project (cloud or self-hosted)
+# 2. Apply the database schema from supabase/migrations/
+# 3. Deploy edge functions: supabase functions deploy
+# 4. Configure storage buckets: parts-images, issues
+# 5. Set these environment variables
+#
+# See docs/SELF_HOSTING_GUIDE.md for complete instructions.
+#
+# License: BSL 1.1 - Self-hosting is free and unlimited.
+# You cannot offer commercial hosted versions that compete with eryxon.eu
diff --git a/supabase/functions/_shared/plan-limits.ts b/supabase/functions/_shared/plan-limits.ts
index 64382323..8fbc7270 100644
--- a/supabase/functions/_shared/plan-limits.ts
+++ b/supabase/functions/_shared/plan-limits.ts
@@ -4,10 +4,11 @@
* This module provides utilities for enforcing subscription plan limits
* across all API endpoints.
*
- * Plan Limits:
- * - Free: 100 jobs, 1000 parts/month, 5GB storage, limited API access
- * - Pro: 1000 jobs, 10000 parts/month, 50GB storage, full API access
- * - Enterprise: Unlimited everything (premium tier in DB)
+ * Plan Limits (BSL 1.1 Model):
+ * - Free: 25 jobs/mo, 250 parts/mo, 1GB storage, limited API (no webhooks, no MCP)
+ * - Pro: 500 jobs/mo, 5000 parts/mo, 10GB storage, full API + webhooks + MCP
+ * - Enterprise: Unlimited everything, SSO/SAML (premium tier in DB)
+ * - Self-hosted: Unlimited (configured via env, not enforced)
*/
import { SupabaseClient } from "https://esm.sh/@supabase/supabase-js@2";
diff --git a/supabase/migrations/20251201000000_update_plan_limits_bsl.sql b/supabase/migrations/20251201000000_update_plan_limits_bsl.sql
new file mode 100644
index 00000000..a70e6570
--- /dev/null
+++ b/supabase/migrations/20251201000000_update_plan_limits_bsl.sql
@@ -0,0 +1,114 @@
+-- Update plan limits for BSL 1.1 pricing model
+-- Free: 25 jobs/mo, 250 parts/mo, 500MB storage
+-- Pro: 500 jobs/mo, 5000 parts/mo, 10GB storage
+-- Premium/Enterprise: Unlimited (NULL)
+
+-- Update existing tenants by plan
+UPDATE public.tenants
+SET
+ max_jobs = 25,
+ max_parts_per_month = 250,
+ max_storage_gb = 1 -- 500MB rounded to 1GB for simplicity
+WHERE plan = 'free';
+
+UPDATE public.tenants
+SET
+ max_jobs = 500,
+ max_parts_per_month = 5000,
+ max_storage_gb = 10
+WHERE plan = 'pro';
+
+UPDATE public.tenants
+SET
+ max_jobs = NULL,
+ max_parts_per_month = NULL,
+ max_storage_gb = NULL
+WHERE plan = 'premium';
+
+-- Update the handle_new_tenant function with new free tier defaults
+CREATE OR REPLACE FUNCTION public.handle_new_tenant()
+RETURNS TRIGGER
+LANGUAGE plpgsql
+SECURITY DEFINER
+SET search_path = public
+AS $$
+BEGIN
+ -- Check if tenant exists
+ IF NOT EXISTS (SELECT 1 FROM public.tenants WHERE id = NEW.tenant_id) THEN
+ -- Create new tenant with free plan defaults (BSL 1.1 model)
+ INSERT INTO public.tenants (
+ id,
+ name,
+ plan,
+ status,
+ max_jobs,
+ max_parts_per_month,
+ max_storage_gb,
+ contact_email
+ ) VALUES (
+ NEW.tenant_id,
+ COALESCE(NEW.full_name || '''s Organization', 'New Organization'),
+ 'free',
+ 'active',
+ 25, -- Free tier: 25 jobs/mo
+ 250, -- Free tier: 250 parts/mo
+ 1, -- Free tier: ~500MB (1GB for simplicity)
+ NEW.email
+ );
+ END IF;
+
+ RETURN NEW;
+END;
+$$;
+
+-- Create or replace function to get plan limits by plan type
+-- Used for documentation and plan upgrades
+CREATE OR REPLACE FUNCTION public.get_plan_limits(p_plan subscription_plan)
+RETURNS TABLE (
+ plan_name TEXT,
+ max_jobs INTEGER,
+ max_parts_per_month INTEGER,
+ max_storage_gb INTEGER,
+ has_webhooks BOOLEAN,
+ has_mcp_server BOOLEAN,
+ has_sso BOOLEAN
+)
+LANGUAGE SQL
+STABLE
+AS $$
+ SELECT
+ p_plan::TEXT,
+ CASE p_plan
+ WHEN 'free' THEN 25
+ WHEN 'pro' THEN 500
+ WHEN 'premium' THEN NULL -- unlimited
+ END,
+ CASE p_plan
+ WHEN 'free' THEN 250
+ WHEN 'pro' THEN 5000
+ WHEN 'premium' THEN NULL -- unlimited
+ END,
+ CASE p_plan
+ WHEN 'free' THEN 1
+ WHEN 'pro' THEN 10
+ WHEN 'premium' THEN NULL -- unlimited
+ END,
+ CASE p_plan
+ WHEN 'free' THEN FALSE
+ WHEN 'pro' THEN TRUE
+ WHEN 'premium' THEN TRUE
+ END,
+ CASE p_plan
+ WHEN 'free' THEN FALSE
+ WHEN 'pro' THEN TRUE
+ WHEN 'premium' THEN TRUE
+ END,
+ CASE p_plan
+ WHEN 'free' THEN FALSE
+ WHEN 'pro' THEN FALSE
+ WHEN 'premium' THEN TRUE
+ END;
+$$;
+
+-- Add comment for documentation
+COMMENT ON FUNCTION public.get_plan_limits IS 'Returns plan limits for BSL 1.1 pricing model. Free: 25 jobs, 250 parts, 1GB. Pro: 500 jobs, 5000 parts, 10GB. Premium: unlimited.';
From d92327834ac044cf8caae42f03b8d11deb507250 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 1 Dec 2025 16:08:33 +0000
Subject: [PATCH 4/5] feat: Add 4-tier pricing model (Free, Pro, Premium,
Enterprise)
Pricing tiers (BSL 1.1):
- Free: 25 jobs/mo, 250 parts/mo, 1GB, limited API
- Pro: 500 jobs/mo, 5K parts/mo, 10GB, full API + webhooks + MCP
- Premium: 2K jobs/mo (fair use), 20K parts/mo, 100GB, SSO, priority
- Enterprise: Unlimited, their infra, custom scope, by request
- Self-hosted: Unlimited, free forever
Changes:
- Add 'enterprise' to subscription_plan enum
- Update migration with new limits
- Update plan-limits.ts with 4 tiers
- Update useSubscription.ts types
- Update MyPlan.tsx with all 4 tiers
- Update README pricing table
---
README.md | 36 ++++++++--------
src/hooks/useSubscription.ts | 10 +++--
src/pages/common/MyPlan.tsx | 34 ++++++++++-----
supabase/functions/_shared/plan-limits.ts | 24 +++++++----
.../20251201000000_update_plan_limits_bsl.sql | 43 ++++++++++++++++---
5 files changed, 101 insertions(+), 46 deletions(-)
diff --git a/README.md b/README.md
index 1763e193..e33a433c 100644
--- a/README.md
+++ b/README.md
@@ -104,23 +104,25 @@ Additional documentation:
## Pricing
-| | Free | Pro | Enterprise | Self-hosted |
-|---|---|---|---|---|
-| **Price** | €0 | €97/mo | €497/mo | €0 |
-| **Where** | My infra | My infra | Your infra | Your infra |
-| **Jobs/mo** | 25 | 500 | Unlimited | Unlimited |
-| **Parts/mo** | 250 | 5,000 | Unlimited | Unlimited |
-| **Storage** | 500MB | 10GB | Your cost | Your cost |
-| **API** | Limited | Full | Full | Full |
-| **Webhooks** | No | Yes | Yes | Yes |
-| **MCP Server** | No | Yes | Yes | Yes |
-| **SSO/SAML** | No | No | Yes | DIY |
-| **Support** | Docs | Email | Dedicated | Community |
-
-- **Free** — Try it. Limited. No support.
-- **Pro** — I run it. Real usage. Email support.
-- **Enterprise** — Your network, your Supabase, I deploy and support it.
-- **Self-hosted** — Download it, run it yourself, unlimited, you're on your own.
+| | Free | Pro | Premium | Enterprise | Self-hosted |
+|---|---|---|---|---|---|
+| **Price** | €0 | €97/mo | €497/mo | By request | €0 |
+| **Where** | My infra | My infra | My infra | Your infra | Your infra |
+| **Jobs/mo** | 25 | 500 | Fair use | Unlimited | Unlimited |
+| **Parts/mo** | 250 | 5,000 | Fair use | Unlimited | Unlimited |
+| **Storage** | 500MB | 10GB | 100GB | Your cost | Your cost |
+| **API** | Limited | Full | Full | Full | Full |
+| **Webhooks** | No | Yes | Yes | Yes | Yes |
+| **MCP Server** | No | Yes | Yes | Yes | Yes |
+| **SSO/SAML** | No | No | Yes | Yes | DIY |
+| **White-label** | No | No | Optional | Yes | DIY |
+| **Support** | Docs | Email | Priority | Dedicated | Community |
+
+- **Free** — Try it. Very limited.
+- **Pro** — Real usage, my infra, email support.
+- **Premium** — High limits, SSO, priority support. Still my infra.
+- **Enterprise** — Your network, I deploy, custom scope. Contact me.
+- **Self-hosted** — On your own. Free forever.
Want to self-host? See the [Self-Hosting Guide](docs/SELF_HOSTING_GUIDE.md).
diff --git a/src/hooks/useSubscription.ts b/src/hooks/useSubscription.ts
index 80807a3d..cbcbd23d 100644
--- a/src/hooks/useSubscription.ts
+++ b/src/hooks/useSubscription.ts
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
import { supabase } from '../integrations/supabase/client';
import { useAuth } from '../contexts/AuthContext';
-export type SubscriptionPlan = 'free' | 'pro' | 'premium';
+export type SubscriptionPlan = 'free' | 'pro' | 'premium' | 'enterprise';
export type SubscriptionStatus = 'active' | 'cancelled' | 'suspended' | 'trial';
export interface TenantSubscription {
@@ -76,19 +76,21 @@ export const useSubscription = () => {
}, [profile?.tenant_id]);
const getPlanDisplayName = (plan: SubscriptionPlan): string => {
- const planNames = {
+ const planNames: Record = {
free: 'Free Plan',
pro: 'Pro Plan',
premium: 'Premium Plan',
+ enterprise: 'Enterprise Plan',
};
return planNames[plan] || 'Unknown Plan';
};
const getPlanColor = (plan: SubscriptionPlan): string => {
- const planColors = {
+ const planColors: Record = {
free: '#64748b', // slate
pro: '#8b5cf6', // purple
- premium: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', // gradient
+ premium: '#f59e0b', // amber
+ enterprise: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', // gradient
};
return planColors[plan] || '#64748b';
};
diff --git a/src/pages/common/MyPlan.tsx b/src/pages/common/MyPlan.tsx
index 7024905a..329f552b 100644
--- a/src/pages/common/MyPlan.tsx
+++ b/src/pages/common/MyPlan.tsx
@@ -28,7 +28,7 @@ const pricingTiers = [
id: "free",
name: "Free",
price: "€0",
- description: "Try it. Limited. No support.",
+ description: "Try it. Very limited.",
features: [
"25 jobs per month",
"250 parts per month",
@@ -44,7 +44,7 @@ const pricingTiers = [
name: "Pro",
price: "€97",
popular: true,
- description: "We run it. Real usage. Email support.",
+ description: "Real usage, my infra, email support.",
features: [
"500 jobs per month",
"5,000 parts per month",
@@ -52,23 +52,37 @@ const pricingTiers = [
"Full API access",
"Webhooks included",
"MCP server access",
- "Email support (no SLA)",
- "Extra storage on request",
+ "Email support",
],
},
{
id: "premium",
- name: "Enterprise",
+ name: "Premium",
price: "€497",
- description: "Your network, we deploy and support it.",
+ description: "High limits, SSO, priority support. Still my infra.",
+ features: [
+ "Fair use (high limits)",
+ "100 GB storage",
+ "Full API access",
+ "Webhooks + MCP server",
+ "SSO/SAML authentication",
+ "White-label (optional)",
+ "Priority email support",
+ ],
+ },
+ {
+ id: "enterprise",
+ name: "Enterprise",
+ price: "Contact",
+ description: "Your network, I deploy, custom scope.",
features: [
"Unlimited jobs & parts",
"Your infrastructure",
"Your Supabase instance",
- "SSO/SAML authentication",
- "White-label options",
- "Dedicated support channel",
- "Custom SLA available",
+ "SSO/SAML included",
+ "White-label included",
+ "Dedicated support",
+ "Custom SLA",
],
},
];
diff --git a/supabase/functions/_shared/plan-limits.ts b/supabase/functions/_shared/plan-limits.ts
index 8fbc7270..a24086b6 100644
--- a/supabase/functions/_shared/plan-limits.ts
+++ b/supabase/functions/_shared/plan-limits.ts
@@ -4,17 +4,18 @@
* This module provides utilities for enforcing subscription plan limits
* across all API endpoints.
*
- * Plan Limits (BSL 1.1 Model):
+ * Plan Limits (BSL 1.1 Model - 4 hosted tiers + self-hosted):
* - Free: 25 jobs/mo, 250 parts/mo, 1GB storage, limited API (no webhooks, no MCP)
* - Pro: 500 jobs/mo, 5000 parts/mo, 10GB storage, full API + webhooks + MCP
- * - Enterprise: Unlimited everything, SSO/SAML (premium tier in DB)
+ * - Premium: 2000 jobs/mo, 20000 parts/mo, 100GB storage, SSO/SAML, priority support
+ * - Enterprise: Unlimited, their infrastructure, custom scope
* - Self-hosted: Unlimited (configured via env, not enforced)
*/
import { SupabaseClient } from "https://esm.sh/@supabase/supabase-js@2";
export interface PlanLimits {
- plan: 'free' | 'pro' | 'premium';
+ plan: 'free' | 'pro' | 'premium' | 'enterprise';
max_jobs: number | null;
max_parts_per_month: number | null;
max_storage_gb: number | null;
@@ -149,16 +150,16 @@ export async function canCreateParts(
/**
* Check if a tenant has API access
* Free plan has limited API access (rate limited more aggressively)
- * Pro and Enterprise have full API access
+ * Pro, Premium, and Enterprise have full API access
*/
-export function getApiAccessLevel(plan: 'free' | 'pro' | 'premium'): 'limited' | 'full' {
+export function getApiAccessLevel(plan: 'free' | 'pro' | 'premium' | 'enterprise'): 'limited' | 'full' {
return plan === 'free' ? 'limited' : 'full';
}
/**
* Get rate limit configuration based on plan
*/
-export function getRateLimitConfig(plan: 'free' | 'pro' | 'premium'): {
+export function getRateLimitConfig(plan: 'free' | 'pro' | 'premium' | 'enterprise'): {
maxRequests: number;
windowMs: number;
} {
@@ -175,7 +176,12 @@ export function getRateLimitConfig(plan: 'free' | 'pro' | 'premium'): {
};
case 'premium':
return {
- maxRequests: 10000, // 10000 requests per hour
+ maxRequests: 5000, // 5000 requests per hour (priority)
+ windowMs: 60 * 60 * 1000,
+ };
+ case 'enterprise':
+ return {
+ maxRequests: 10000, // 10000 requests per hour (dedicated)
windowMs: 60 * 60 * 1000,
};
default:
@@ -275,13 +281,15 @@ export async function canUploadFile(
/**
* Get a user-friendly plan name
*/
-export function getPlanDisplayName(plan: 'free' | 'pro' | 'premium'): string {
+export function getPlanDisplayName(plan: 'free' | 'pro' | 'premium' | 'enterprise'): string {
switch (plan) {
case 'free':
return 'Free';
case 'pro':
return 'Pro';
case 'premium':
+ return 'Premium';
+ case 'enterprise':
return 'Enterprise';
default:
return 'Unknown';
diff --git a/supabase/migrations/20251201000000_update_plan_limits_bsl.sql b/supabase/migrations/20251201000000_update_plan_limits_bsl.sql
index a70e6570..f52abb08 100644
--- a/supabase/migrations/20251201000000_update_plan_limits_bsl.sql
+++ b/supabase/migrations/20251201000000_update_plan_limits_bsl.sql
@@ -1,7 +1,20 @@
--- Update plan limits for BSL 1.1 pricing model
+-- Update plan limits for BSL 1.1 pricing model (4 hosted tiers + self-hosted)
-- Free: 25 jobs/mo, 250 parts/mo, 500MB storage
-- Pro: 500 jobs/mo, 5000 parts/mo, 10GB storage
--- Premium/Enterprise: Unlimited (NULL)
+-- Premium: Fair use (2000 jobs/mo, 20000 parts/mo, 100GB storage)
+-- Enterprise: Unlimited (NULL) - their infrastructure
+
+-- Add 'enterprise' to the subscription_plan enum if not exists
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM pg_enum
+ WHERE enumlabel = 'enterprise'
+ AND enumtypid = (SELECT oid FROM pg_type WHERE typname = 'subscription_plan')
+ ) THEN
+ ALTER TYPE subscription_plan ADD VALUE 'enterprise';
+ END IF;
+END $$;
-- Update existing tenants by plan
UPDATE public.tenants
@@ -18,12 +31,22 @@ SET
max_storage_gb = 10
WHERE plan = 'pro';
+-- Premium now has fair use limits (high but not unlimited)
+UPDATE public.tenants
+SET
+ max_jobs = 2000,
+ max_parts_per_month = 20000,
+ max_storage_gb = 100
+WHERE plan = 'premium';
+
+-- Enterprise has unlimited (NULL)
+-- Note: Existing 'premium' tenants that should be enterprise need manual migration
UPDATE public.tenants
SET
max_jobs = NULL,
max_parts_per_month = NULL,
max_storage_gb = NULL
-WHERE plan = 'premium';
+WHERE plan = 'enterprise';
-- Update the handle_new_tenant function with new free tier defaults
CREATE OR REPLACE FUNCTION public.handle_new_tenant()
@@ -81,34 +104,40 @@ AS $$
CASE p_plan
WHEN 'free' THEN 25
WHEN 'pro' THEN 500
- WHEN 'premium' THEN NULL -- unlimited
+ WHEN 'premium' THEN 2000 -- Fair use
+ WHEN 'enterprise' THEN NULL -- Unlimited
END,
CASE p_plan
WHEN 'free' THEN 250
WHEN 'pro' THEN 5000
- WHEN 'premium' THEN NULL -- unlimited
+ WHEN 'premium' THEN 20000 -- Fair use
+ WHEN 'enterprise' THEN NULL -- Unlimited
END,
CASE p_plan
WHEN 'free' THEN 1
WHEN 'pro' THEN 10
- WHEN 'premium' THEN NULL -- unlimited
+ WHEN 'premium' THEN 100
+ WHEN 'enterprise' THEN NULL -- Unlimited
END,
CASE p_plan
WHEN 'free' THEN FALSE
WHEN 'pro' THEN TRUE
WHEN 'premium' THEN TRUE
+ WHEN 'enterprise' THEN TRUE
END,
CASE p_plan
WHEN 'free' THEN FALSE
WHEN 'pro' THEN TRUE
WHEN 'premium' THEN TRUE
+ WHEN 'enterprise' THEN TRUE
END,
CASE p_plan
WHEN 'free' THEN FALSE
WHEN 'pro' THEN FALSE
WHEN 'premium' THEN TRUE
+ WHEN 'enterprise' THEN TRUE
END;
$$;
-- Add comment for documentation
-COMMENT ON FUNCTION public.get_plan_limits IS 'Returns plan limits for BSL 1.1 pricing model. Free: 25 jobs, 250 parts, 1GB. Pro: 500 jobs, 5000 parts, 10GB. Premium: unlimited.';
+COMMENT ON FUNCTION public.get_plan_limits IS 'Returns plan limits for BSL 1.1 pricing model. Free: 25/250/1GB. Pro: 500/5K/10GB. Premium: 2K/20K/100GB (fair use). Enterprise: unlimited.';
From 195965563c675cd9392b482bf9afd31465529657 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 1 Dec 2025 16:10:16 +0000
Subject: [PATCH 5/5] docs: Add yearly billing note to README pricing
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index e33a433c..4c7de92e 100644
--- a/README.md
+++ b/README.md
@@ -124,7 +124,7 @@ Additional documentation:
- **Enterprise** — Your network, I deploy, custom scope. Contact me.
- **Self-hosted** — On your own. Free forever.
-Want to self-host? See the [Self-Hosting Guide](docs/SELF_HOSTING_GUIDE.md).
+Paid plans billed yearly. Want to self-host? See the [Self-Hosting Guide](docs/SELF_HOSTING_GUIDE.md).
Need integration work or custom forks? [Contact us](mailto:office@sheetmetalconnect.com).