feat: add manual subscription finalization functionality and support for expired plan notifications

This commit is contained in:
2026-07-19 13:20:33 -03:00
parent 78ee86ac48
commit af6c87acb6
20 changed files with 462 additions and 39 deletions
+52 -16
View File
@@ -1,5 +1,4 @@
"use client";
import MetricGraph from "@components/MetricGraph/MetricGraph";
import AnimatedContainer from "@core/app/components/AnimatedConainer/AnimatedContainer";
import Button from "@core/app/components/Button/Button";
import { useNavigation } from "@core/app/hooks/goto";
@@ -36,7 +35,6 @@ import { blurElementsOnLoad } from "@core/helpers/blur";
import HeaderConfProvider from "@core/app/components/HeaderConfProvider/HeaderConfProvider";
import classNames from "classnames";
import { ScrollToTop } from "@core/app/components/ScrollTop";
import { isNull } from "@core/app/helpers/IsNull";
import StorefrontOutlinedIcon from "@mui/icons-material/StorefrontOutlined";
import NotificationsNoneOutlinedIcon from "@mui/icons-material/NotificationsNoneOutlined";
@@ -56,16 +54,8 @@ function DashboardContent() {
const eventHandler = useEventHandlerStore();
const alert = useAlert();
const confirm = useConfirmStore();
const infoEmail = process.env.NEXT_PUBLIC_INFO_EMAIL || "info@turnosxpress.com.ar";
const [metrics, setMetrics] = useState({
organizationsCount: 0,
employeesCount: 0,
clientsCount: 0,
appointmentsCount: 0,
servicesCount: 0,
repeatsCount: 0,
});
const [showOnlyMine, setShowOnlyMine] = useState(false);
const [isAgendaCollapsed, setIsAgendaCollapsed] = useState(true);
@@ -164,6 +154,7 @@ function DashboardContent() {
const loadCompanyMetrics = async (companyId: string) => {
setCompanySubscription(null);
setCompanyMetrics(null);
setIsCompanyOwner(false);
try {
const res = await getCompanyMetrics({ sessionUser: SessionInfo.userId, companyId });
setCompanyMetrics(res.metrics);
@@ -315,6 +306,7 @@ function DashboardContent() {
};
const isUsingSessionSubscriptionFallback = !companySubscription && Boolean(subscription.plan.id);
const selectedOrganization = organizations.find((organization) => organization.id === selectedOrgId);
const displayedSubscription = companySubscription ?? (subscription.plan.id ? subscription : null);
const displayedPlan = displayedSubscription?.plan;
const displayedPlanStatusText = displayedPlan?.price === 0 ? "ACTIVO" : getSubStatusText(displayedSubscription?.mpStatus);
@@ -325,11 +317,30 @@ function DashboardContent() {
displayedSubscription.lastPaymentAt &&
dayjs().diff(dayjs(displayedSubscription.lastPaymentAt), "day") <= 14
);
const showPaymentAction = Boolean(
(displayedSubscription?.mpStatus === MP_SUBS_STATUS.PENDING || hasPendingPaymentMetadata) &&
(isCompanyOwner || isUsingSessionSubscriptionFallback)
const selectedCompanyHasPendingPayment = Boolean(
companySubscription &&
(companySubscription.mpStatus === MP_SUBS_STATUS.PENDING || companySubscription.pendingPaymentPreferenceId)
);
const showPaymentAction = selectedOrgId
? Boolean(selectedCompanyHasPendingPayment && isCompanyOwner)
: Boolean(
(displayedSubscription?.mpStatus === MP_SUBS_STATUS.PENDING || hasPendingPaymentMetadata) &&
isUsingSessionSubscriptionFallback
);
const showFailedPaymentCard = Boolean(hasRecentFailedPayment && !showPaymentAction);
const showExpiredDowngradeCard = Boolean(
displayedSubscription?.downgradeReason === "expired" &&
displayedSubscription.downgradedAt &&
isCompanyOwner
);
const selectedCompanyPlanSupportsRepeats = Boolean(
companySubscription?.plan &&
(companySubscription.plan.limitRepeats === -1 || companySubscription.plan.limitRepeats > 0)
);
const canLoadPendingRepeats = Boolean(
selectedCompanyPlanSupportsRepeats &&
(isCompanyOwner || selectedOrganization?.isAdmin)
);
const showFreePlanStarterContent = Boolean(displayedPlan && displayedPlan.price === 0 && organizations.length === 0);
const agendaItems = dashboardData?.personalAgenda ?? [];
const filteredAgendaItems = agendaItems.filter(item => !showOnlyMine || item.collaboratorId === SessionInfo.userId);
@@ -365,7 +376,12 @@ function DashboardContent() {
<div style={{ position: "absolute", top: "20px", right: "20px", display: "flex", alignItems: "center", gap: "10px" }}>
<select
value={selectedOrgId}
onChange={e => setSelectedOrgId(e.target.value)}
onChange={e => {
setCompanySubscription(null);
setCompanyMetrics(null);
setIsCompanyOwner(false);
setSelectedOrgId(e.target.value);
}}
style={{ padding: "8px 12px", borderRadius: "8px", border: "1px solid #e5e7eb", fontSize: "14px", fontWeight: "500", backgroundColor: "#f9fafb", cursor: "pointer", outline: "none" }}
>
{organizations.map(org => (
@@ -435,6 +451,7 @@ function DashboardContent() {
<p>
Si ya pagaste, puede tardar unos minutos en actualizarse porque dependemos de la notificación de MercadoPago.
No te preocupes: podés volver al checkout si quedó incompleto o pedirnos que verifiquemos el pago ahora mismo.
{" "}Si el problema continúa, escribinos a <a className={style.pendingPaymentLink} href={`mailto:${infoEmail}`}>{infoEmail}</a> y te ayudamos a revisarlo.
</p>
</div>
<div className={style.pendingPaymentActions}>
@@ -482,6 +499,25 @@ function DashboardContent() {
</div>
)}
{showExpiredDowngradeCard && (
<div className={style.expiredDowngradeCard}>
<div className={style.pendingPaymentContent}>
<span className={style.expiredDowngradeEyebrow}>Plan vencido</span>
<h2>Tu plan pago venció y pasaste al plan básico</h2>
<p>
No borramos tu organización ni tus datos. Desde ahora las nuevas acciones usan los límites del plan básico.
{displayedSubscription?.downgradedFromPlanName && ` Tu plan anterior era ${displayedSubscription.downgradedFromPlanName}.`}
</p>
</div>
<button
className={style.expiredDowngradeButton}
onClick={() => goTo(selectedOrgId ? `/landing/upgrade-plan?org=${selectedOrgId}` : "/landing/pricing")}
>
Volver a contratar un plan
</button>
</div>
)}
{showFreePlanStarterContent && (
<div className={style.starterCard}>
<div className={style.starterContent}>
@@ -649,7 +685,7 @@ function DashboardContent() {
)}
{/* Repeats Pending Card */}
{dashboardData && selectedOrgId && (
{dashboardData && selectedOrgId && canLoadPendingRepeats && (
<PendingRepeatsCard
companyId={selectedOrgId}
allowEmployeeFilter={true}