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:
@@ -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 platform’s 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; it’s 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.',
|
||||
|
||||
Reference in New Issue
Block a user