feat(landing,billing): public pricing, global discount, services bar & estimator

Backend
- Add platform-wide global discount (platform_settings: global_discount_percent),
  applied centrally in PricingCatalogService.computeTotalsFromDb so it reaches
  every real charge (previews, deploys, renewals, upgrades, invoices). Admin
  GET/PATCH /billing/settings/global-discount.
- Add unauthenticated PublicPricingController (catalog + calculate) for the
  public landing page, returning gross/net and the discount percentage.
- Bill application replicas by the user-selected footprint: app CPU/RAM/storage
  now all scale by replica count; the single-replica database stays unscaled.

Frontend
- Landing: Services bar (PaaS active, DBaaS, KaaS/LaaS "coming soon" with
  expandable runtime/database menus), transparent Pricing section (per-resource
  rate cards with cycle toggle + discount strikethrough), and a cost Estimator
  ("estimate your package").
- Optional services and the database are priced like runtimes: the estimator
  lets users pick their CPU/RAM/storage (and DB type) so the cost scales by need.
- Admin billing: global-discount editor.
- i18n: fa/en strings for services, pricing, estimator and global discount.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
keyhan
2026-06-22 23:44:30 +03:30
parent a58142cc4a
commit f7974dd382
13 changed files with 1226 additions and 10 deletions
+86
View File
@@ -66,6 +66,32 @@ const en: Dictionary = {
ctaSecondary: 'Sign in',
scrollHint: 'Dive into the clouds',
},
services: {
title: 'Platform services',
subtitle: 'Everything you need to build and run your product, in one place.',
comingSoon: 'Coming soon',
explore: 'Click to explore',
paas: {
label: 'PaaS',
name: 'Platform as a Service',
desc: 'Ship your app straight from code to Kubernetes — automatic build and release.',
},
dbaas: {
label: 'DBaaS',
name: 'Database as a Service',
desc: 'Managed, reliable databases ready in seconds.',
},
kaas: {
label: 'KaaS',
name: 'Kubernetes as a Service',
desc: 'A dedicated, managed Kubernetes cluster for your team.',
},
laas: {
label: 'LaaS',
name: 'Logging as a Service',
desc: 'Centralized log collection, retention and analysis.',
},
},
value: {
lead: 'A ',
highlight: 'self-service PaaS',
@@ -103,6 +129,53 @@ const en: Dictionary = {
{ kpi: 'Iran-native', label: 'Optimized for Iranian users' },
],
},
pricing: {
title: 'Transparent pricing',
subtitle: 'Pay only for the resources you use. These rates are read straight from the platforms active plans.',
runtimeLabel: 'Application type',
cycle: { hourly: 'Hourly', monthly: 'Monthly', yearly: 'Yearly' },
perCycle: { hourly: 'per hour', monthly: 'per month', yearly: 'per year' },
discountBadge: '{p}% off',
free: 'Free',
resources: {
base_fee: 'Base fee',
cpu_per_core: 'CPU (per core)',
memory_per_gb: 'Memory (per GB)',
storage_per_gb: 'Storage (per GB)',
database_addon: 'Managed database',
custom_domain_addon: 'Custom domain + SSL',
},
currency: 'Toman',
note: 'Your final bill is based on actual usage; these are the per-unit rates for each resource.',
loadError: 'Could not load pricing.',
},
estimator: {
title: 'Estimate your package',
subtitle: 'Pick the resources you need and see an instant cost estimate.',
runtime: 'Application type',
cpu: 'CPU',
memory: 'Memory (RAM)',
storage: 'Storage',
replicas: 'Replicas',
database: 'Database',
databaseType: 'Database type',
options: 'Add-on services',
redis: 'Redis',
rabbitmq: 'RabbitMQ',
elasticsearch: 'Elasticsearch (logging)',
customDomain: 'Custom domain + SSL',
cores: '{n} cores',
gb: '{n} GB',
none: 'None',
estimatedTitle: 'Estimated cost',
perCycle: { hourly: 'per hour', monthly: 'per month', yearly: 'per year' },
cycle: { hourly: 'Hourly', monthly: 'Monthly', yearly: 'Yearly' },
youSave: '{p}% discount applied',
calculating: 'Calculating…',
cta: 'Build this package',
currency: 'Toman',
error: 'Could not calculate cost.',
},
finalCta: {
titleLead: 'The sky is clear; its time to ',
titleHighlight: 'go live',
@@ -1103,6 +1176,19 @@ const en: Dictionary = {
saveFailedShort: 'Failed to save',
hours: 'hours',
days: 'days',
globalDiscount: {
title: 'Platform-wide discount',
subtitle: 'A single percentage applied to every price and real invoice. 0 means no discount.',
label: 'Global discount %',
hint: 'A number between 0 and 100',
save: 'Save discount',
saving: 'Saving…',
saved: 'Global discount saved',
edit: 'Edit',
cancel: 'Cancel',
none: 'No platform-wide discount is currently active.',
active: 'A {p}% discount is active on all services.',
},
discounts: {
title: 'Discount codes',
subtitle: 'Percentage discounts on different services — public or for specific users.',