export type Price = number | string; export interface PlanPage { code: string; landing: Price; webApp: Price; care: [Price, Price, Price]; } export function getPlanPages(): PlanPage[] { const raw = process.env.PLANS_PAGES; if (!raw) return []; try { const parsed = JSON.parse(raw) as unknown; if (!Array.isArray(parsed)) return []; return parsed.filter( (p): p is PlanPage => !!p && typeof p.code === "string" && Array.isArray(p.care), ); } catch (err) { console.error( "PLANS_PAGES is not valid JSON — no pricing pages will render.", err, ); return []; } } export function getPlanPage(code: string): PlanPage | null { return getPlanPages().find((p) => p.code === code) ?? null; } function euro(value: Price, isSR: boolean): string { if (typeof value !== "number") return value; const n = new Intl.NumberFormat(isSR ? "sr-RS" : "en-US", { maximumFractionDigits: 0, }).format(value); return `€${n}`; } function fromPrice(value: Price, isSR: boolean): string { if (typeof value !== "number") return value; return `${isSR ? "od " : "from "}${euro(value, isSR)}`; } function carePrice(value: Price, isSR: boolean): string { if (typeof value !== "number") return value; return `${euro(value, isSR)}${isSR ? "/mj" : "/mo"}`; } export function getPlansDict(locale: string, page: PlanPage) { const isSR = locale === "sr"; const landing = fromPrice(page.landing, isSR); const webApp = fromPrice(page.webApp, isSR); const care = page.care.map((c) => carePrice(c, isSR)) as [ string, string, string, ]; if (isSR) { return { badge: "Za klijente", title: "Paketi i", titleAccent: "cijene.", subtitle: "Okvirne početne cijene za najčešće tipove projekata. Svaki projekat dobija prilagođenu ponudu nakon kratkog razgovora — ova stranica služi da dobijete realan okvir.", tiersTitle: "Cijene projekata", popularBadge: "Najtraženije", tiers: [ { name: "Landing / prezentacioni sajt", price: landing, period: "jednokratno", description: "Brz, SEO-spreman sajt za vaš biznis — dizajniran, izrađen, deployovan i hostovan.", features: [ "Custom dizajn, responzivan na svim uređajima", "SEO podešavanje, analitika, kontakt forma", "Deploy sa SSL-om na upravljanoj infrastrukturi", "Isporuka za 1–2 sedmice", ], highlighted: false, }, { name: "Web aplikacija", price: webApp, period: "jednokratno", description: "Custom web aplikacija sa backendom, bazom podataka i admin panelom — prilagođena vašem načinu rada.", features: [ "Frontend, backend i dizajn baze podataka", "Autentifikacija i upravljanje ulogama", "Admin panel i interni alati", "CI/CD, monitoring i backup uključeni", "Isporuka za 4–8 sedmica u zavisnosti od obima", ], highlighted: true, }, { name: "Custom platforma", price: "po dogovoru", period: "zajedničko planiranje", description: "Veći proizvodi: e-commerce, SaaS, mobilna + web aplikacija. Obim i cijena se definišu nakon analize.", features: [ "Sve iz paketa Web aplikacija", "Mobilna aplikacija (React Native) po potrebi", "Plaćanja, integracije, eksterni API-ji", "Dugoročna razvojna saradnja", ], highlighted: false, }, ], careTitle: "Mjesečni paketi održavanja", careSubtitle: "Opcioni mjesečni paketi nakon lansiranja, da vaš projekat ostane brz, siguran i dostupan.", care: [ { name: "Hosting", price: care[0], features: [ "Upravljani hosting sa SSL-om", "Dnevni backup", "Monitoring dostupnosti", ], }, { name: "Standard", price: care[1], features: [ "Sve iz paketa Hosting", "Izmjene sadržaja i manje dorade", "Sigurnosna ažuriranja", "Prioritetna email podrška", ], }, { name: "Priority", price: care[2], features: [ "Sve iz paketa Standard", "Mjesečni sati za razvoj funkcionalnosti", "Odgovor isti dan", "Pregledi performansi", ], }, ], disclaimer: "Cijene su početne, ne fiksne ponude — konačna cijena zavisi od obima i potvrđuje se pisanom ponudom prije početka rada.", cta: "Razgovarajmo o projektu", }; } return { badge: "For clients", title: "Plans &", titleAccent: "pricing.", subtitle: "Indicative starting prices for the most common project types. Every project gets a tailored quote after a short discovery call — this page is just to give you a realistic frame.", tiersTitle: "Project pricing", popularBadge: "Popular", tiers: [ { name: "Landing / marketing site", price: landing, period: "one-time", description: "A fast, SEO-ready site for your business — designed, built, deployed, and hosted.", features: [ "Custom design, responsive on all devices", "SEO setup, analytics, contact form", "Deployed with SSL on managed infrastructure", "Delivered in 1–2 weeks", ], highlighted: false, }, { name: "Web application", price: webApp, period: "one-time", description: "A custom web app with backend, database, and admin panel — built around your workflow.", features: [ "Frontend, backend, and database design", "Authentication and role management", "Admin dashboard and internal tools", "CI/CD, monitoring, and backups included", "Delivered in 4–8 weeks depending on scope", ], highlighted: true, }, { name: "Custom platform", price: "custom quote", period: "scoped together", description: "Larger products: e-commerce, SaaS, mobile + web. Scoped and priced after discovery.", features: [ "Everything in Web application", "Mobile app (React Native) if needed", "Payments, integrations, third-party APIs", "Long-term development partnership", ], highlighted: false, }, ], careTitle: "Ongoing care plans", careSubtitle: "Optional monthly plans after launch, so your project stays fast, secure, and online.", care: [ { name: "Hosting", price: care[0], features: [ "Managed hosting with SSL", "Daily backups", "Uptime monitoring", ], }, { name: "Standard", price: care[1], features: [ "Everything in Hosting", "Content and minor updates", "Security and dependency updates", "Priority email support", ], }, { name: "Priority", price: care[2], features: [ "Everything in Standard", "Monthly feature-development hours", "Same-day response", "Performance reviews", ], }, ], disclaimer: "Prices are starting points, not fixed quotes — final pricing depends on scope and is confirmed in a written proposal before any work begins.", cta: "Discuss your project", }; } export type PlansDict = ReturnType;