Initial commit

This commit is contained in:
2026-08-01 16:07:57 +02:00
commit 20b254e614
68 changed files with 8467 additions and 0 deletions
+249
View File
@@ -0,0 +1,249 @@
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>;