Files
ksan.dev/app/[locale]/plans/[code]/pricing.ts
T
2026-08-01 16:07:57 +02:00

250 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 12 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 48 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 12 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 48 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<typeof getPlansDict>;