250 lines
7.7 KiB
TypeScript
250 lines
7.7 KiB
TypeScript
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<typeof getPlansDict>;
|