121 lines
3.8 KiB
TypeScript
121 lines
3.8 KiB
TypeScript
|
|
"use client";
|
|||
|
|
|
|||
|
|
import { useMemo, useState } from "react";
|
|||
|
|
import { useTranslations } from "next-intl";
|
|||
|
|
|
|||
|
|
import {
|
|||
|
|
RESOLUTION_ORDER,
|
|||
|
|
renderSecondsCost,
|
|||
|
|
type SecondsPlan,
|
|||
|
|
} from "@/lib/plans-catalog";
|
|||
|
|
|
|||
|
|
interface Props {
|
|||
|
|
plans: SecondsPlan[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Interactive "how many seconds do I need" helper. The user picks a video length
|
|||
|
|
* and resolution; we show the per-render cost (length × resolution multiplier)
|
|||
|
|
* and how many such videos each paid plan's monthly seconds would cover.
|
|||
|
|
*/
|
|||
|
|
export function SecondsCalculator({ plans }: Props) {
|
|||
|
|
const t = useTranslations("pricing");
|
|||
|
|
const [length, setLength] = useState(15);
|
|||
|
|
const [resolution, setResolution] = useState("720p");
|
|||
|
|
|
|||
|
|
const cost = useMemo(
|
|||
|
|
() => renderSecondsCost(length, resolution),
|
|||
|
|
[length, resolution]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const paidPlans = plans.filter((p) => p.priceTomans > 0);
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="rounded-2xl border border-neutral-200 bg-white p-6 shadow-sm sm:p-8">
|
|||
|
|
<h3 className="font-heading text-xl font-bold text-neutral-900">
|
|||
|
|
{t("calcTitle")}
|
|||
|
|
</h3>
|
|||
|
|
<p className="mt-1 text-sm text-neutral-500">{t("calcDesc")}</p>
|
|||
|
|
|
|||
|
|
<div className="mt-6 grid gap-6 sm:grid-cols-2">
|
|||
|
|
{/* Length */}
|
|||
|
|
<div>
|
|||
|
|
<label className="mb-2 flex items-center justify-between text-sm font-medium text-neutral-700">
|
|||
|
|
<span>{t("calcLength")}</span>
|
|||
|
|
<span className="font-bold text-neutral-900">
|
|||
|
|
{length} {t("calcSecondsUnit")}
|
|||
|
|
</span>
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="range"
|
|||
|
|
min={1}
|
|||
|
|
max={120}
|
|||
|
|
value={length}
|
|||
|
|
onChange={(e) => setLength(Number(e.target.value))}
|
|||
|
|
className="w-full accent-indigo-600"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Resolution */}
|
|||
|
|
<div>
|
|||
|
|
<label className="mb-2 block text-sm font-medium text-neutral-700">
|
|||
|
|
{t("calcResolution")}
|
|||
|
|
</label>
|
|||
|
|
<div className="flex flex-wrap gap-1.5">
|
|||
|
|
{RESOLUTION_ORDER.map((r) => (
|
|||
|
|
<button
|
|||
|
|
key={r}
|
|||
|
|
type="button"
|
|||
|
|
onClick={() => setResolution(r)}
|
|||
|
|
className={`rounded-lg border px-2.5 py-1.5 text-xs font-medium transition ${
|
|||
|
|
resolution === r
|
|||
|
|
? "border-indigo-600 bg-indigo-600 text-white"
|
|||
|
|
: "border-neutral-200 bg-white text-neutral-600 hover:border-neutral-300"
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
{r}
|
|||
|
|
</button>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Cost */}
|
|||
|
|
<div className="mt-6 flex flex-wrap items-end justify-between gap-4 rounded-xl bg-neutral-50 p-5">
|
|||
|
|
<div>
|
|||
|
|
<p className="text-sm text-neutral-500">{t("calcCost")}</p>
|
|||
|
|
<p className="mt-1 text-3xl font-extrabold text-neutral-900">
|
|||
|
|
{cost}{" "}
|
|||
|
|
<span className="text-base font-medium text-neutral-500">
|
|||
|
|
{t("calcSecondsUnit")}
|
|||
|
|
</span>
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
{paidPlans.length > 0 && (
|
|||
|
|
<div className="text-end">
|
|||
|
|
<p className="mb-1 text-sm text-neutral-500">
|
|||
|
|
{t("calcRendersWith")}
|
|||
|
|
</p>
|
|||
|
|
<div className="flex flex-wrap justify-end gap-2">
|
|||
|
|
{paidPlans.map((p) => (
|
|||
|
|
<span
|
|||
|
|
key={p.id}
|
|||
|
|
className="rounded-lg border border-neutral-200 bg-white px-2.5 py-1 text-xs text-neutral-700"
|
|||
|
|
>
|
|||
|
|
<span className="font-semibold text-neutral-900">
|
|||
|
|
{p.name}
|
|||
|
|
</span>
|
|||
|
|
{": "}
|
|||
|
|
{t("calcVideosFmt", {
|
|||
|
|
count: Math.floor(p.secondsCharge / Math.max(cost, 1)),
|
|||
|
|
})}
|
|||
|
|
</span>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|