refactor: redesign current plan UI with features panel and expandable capacity limits view
This commit is contained in:
@@ -128,8 +128,7 @@
|
||||
box-shadow: 0 18px 34px color-mix(in srgb, var(--black-dark) 14%, transparent);
|
||||
}
|
||||
|
||||
.summaryGrid,
|
||||
.limitsGrid {
|
||||
.summaryGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
|
||||
gap: 14px;
|
||||
@@ -199,8 +198,7 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.summaryItem,
|
||||
.limitItem {
|
||||
.summaryItem {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: var(--white);
|
||||
@@ -211,15 +209,13 @@
|
||||
transition: transform 180ms ease, box-shadow 180ms ease, border-color 180ms ease;
|
||||
}
|
||||
|
||||
.summaryItem:hover,
|
||||
.limitItem:hover {
|
||||
.summaryItem:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--wine-lighter);
|
||||
box-shadow: 0 18px 34px color-mix(in srgb, var(--wine-darkest) 12%, transparent);
|
||||
}
|
||||
|
||||
.summaryItem::before,
|
||||
.limitItem::before {
|
||||
.summaryItem::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0 auto 0 0;
|
||||
@@ -227,8 +223,7 @@
|
||||
background: linear-gradient(180deg, var(--wine-red), var(--wine-dark));
|
||||
}
|
||||
|
||||
.summaryItem span,
|
||||
.limitItem span {
|
||||
.summaryItem span {
|
||||
display: block;
|
||||
margin-bottom: 9px;
|
||||
color: var(--gray-dark);
|
||||
@@ -238,16 +233,14 @@
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.summaryItem strong,
|
||||
.limitItem strong {
|
||||
.summaryItem strong {
|
||||
display: block;
|
||||
color: var(--black);
|
||||
font-size: 24px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.summaryItem small,
|
||||
.limitItem small {
|
||||
.summaryItem small {
|
||||
display: block;
|
||||
margin-top: 9px;
|
||||
color: var(--gray-darkest);
|
||||
@@ -306,10 +299,31 @@
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
.features {
|
||||
.featuresPanel {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 12px;
|
||||
grid-template-columns: minmax(180px, 0.45fr) minmax(0, 1fr);
|
||||
gap: clamp(16px, 3vw, 28px);
|
||||
align-items: start;
|
||||
padding: clamp(18px, 3vw, 24px);
|
||||
border: 1px solid var(--wine-lighterX2);
|
||||
border-radius: 22px;
|
||||
background:
|
||||
radial-gradient(circle at 0% 0%, var(--wine-lighterX2), transparent 30%),
|
||||
linear-gradient(135deg, var(--white) 0%, var(--white-dark) 100%);
|
||||
}
|
||||
|
||||
.featuresPanel p {
|
||||
margin: 0;
|
||||
color: var(--gray-dark);
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.features {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
@@ -317,57 +331,87 @@
|
||||
|
||||
.features li {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-height: 68px;
|
||||
padding: 14px 16px 14px 54px;
|
||||
border: 1px solid var(--wine-lighterX2);
|
||||
border-radius: 18px;
|
||||
background:
|
||||
radial-gradient(circle at 100% 0%, var(--wine-lighterX2), transparent 34%),
|
||||
linear-gradient(135deg, var(--white) 0%, var(--white-dark) 100%);
|
||||
gap: 8px;
|
||||
padding: 9px 12px;
|
||||
border: 1px solid var(--white-darkest);
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--white) 78%, var(--wine-lighterX2));
|
||||
color: var(--black);
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
box-shadow: 0 12px 28px color-mix(in srgb, var(--wine-darkest) 8%, transparent);
|
||||
transition: transform 180ms ease, box-shadow 180ms ease, border-color 180ms ease;
|
||||
}
|
||||
|
||||
.features li::before {
|
||||
content: "✓";
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
flex: 0 0 auto;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(135deg, var(--wine-red), var(--wine-dark));
|
||||
color: var(--white);
|
||||
background: var(--wine-lighterX2);
|
||||
color: var(--wine-red);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.limitsPanel {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--white-darkest);
|
||||
border-radius: 22px;
|
||||
background: var(--white);
|
||||
box-shadow: 0 14px 36px color-mix(in srgb, var(--wine-darkest) 8%, transparent);
|
||||
}
|
||||
|
||||
.limitRow {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
padding: 16px 18px;
|
||||
border-bottom: 1px solid var(--white-darkest);
|
||||
}
|
||||
|
||||
.limitRow:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.limitRow span {
|
||||
display: block;
|
||||
color: var(--black);
|
||||
font-size: 15px;
|
||||
font-weight: 900;
|
||||
box-shadow: 0 8px 18px color-mix(in srgb, var(--wine-darkest) 18%, transparent);
|
||||
}
|
||||
|
||||
.features li::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: auto 14px 10px 54px;
|
||||
height: 3px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, var(--wine-red), var(--wine-lighter));
|
||||
opacity: 0.45;
|
||||
.limitRow small {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
color: var(--gray-dark);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.features li:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--wine-lighter);
|
||||
box-shadow: 0 18px 34px color-mix(in srgb, var(--wine-darkest) 12%, transparent);
|
||||
.limitValues {
|
||||
min-width: 116px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 16px;
|
||||
background: var(--white-dark);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.limitValues strong {
|
||||
display: block;
|
||||
color: var(--wine-red);
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.limitValues small {
|
||||
margin-top: 6px;
|
||||
color: var(--gray-darkest);
|
||||
}
|
||||
|
||||
.message {
|
||||
@@ -380,6 +424,7 @@
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
grid-template-columns: minmax(190px, 0.8fr) minmax(0, 1.2fr);
|
||||
gap: 18px;
|
||||
align-items: stretch;
|
||||
@@ -390,6 +435,15 @@
|
||||
radial-gradient(circle at 8% 20%, var(--yellow-light), transparent 30%),
|
||||
linear-gradient(135deg, var(--wine-superdark) 0%, var(--wine-darkest) 45%, var(--wine-dark) 100%);
|
||||
box-shadow: 0 18px 42px color-mix(in srgb, var(--wine-darkest) 16%, transparent);
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.usageShowcase:focus-visible {
|
||||
outline: 3px solid var(--yellow);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.usageShowcase::after {
|
||||
@@ -434,6 +488,17 @@
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
|
||||
.usageIntro small {
|
||||
width: fit-content;
|
||||
margin-top: 4px;
|
||||
padding: 7px 11px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--white) 14%, transparent);
|
||||
color: var(--white);
|
||||
font-size: 13px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.usageStats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
@@ -521,10 +586,20 @@
|
||||
}
|
||||
|
||||
.usageShowcase,
|
||||
.usageStats {
|
||||
.usageStats,
|
||||
.featuresPanel {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.limitRow {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.limitValues {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.usageStat {
|
||||
min-height: 104px;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,21 @@ import styles from "./page.module.css";
|
||||
|
||||
const formatLimit = (value: number) => (value === -1 ? "Ilimitado" : value.toString());
|
||||
|
||||
const getLimitDetail = (limit: number, current?: number) => {
|
||||
if (limit === -1) return "Sin tope definido";
|
||||
if (typeof current !== "number") return "Tope disponible";
|
||||
|
||||
const available = Math.max(limit - current, 0);
|
||||
return `${available} disponibles`;
|
||||
};
|
||||
|
||||
const getLimitValue = (limit: number, current?: number) => (typeof current === "number" ? current.toString() : formatLimit(limit));
|
||||
|
||||
const getLimitCaption = (limit: number, current?: number) => {
|
||||
if (typeof current !== "number") return limit === -1 ? "sin tope" : "límite total";
|
||||
return `de ${formatLimit(limit)}`;
|
||||
};
|
||||
|
||||
const formatDate = (date?: Date) => (date ? dayjs(date).format("DD/MM/YYYY") : "No disponible");
|
||||
|
||||
const getLimitCards = (plan: ISubscriptionInfo["plan"], metrics: DashboardMetrics | null) => [
|
||||
@@ -45,6 +60,7 @@ export default function CurrentPlanPage() {
|
||||
const [isOwner, setIsOwner] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
const [isCapacityExpanded, setIsCapacityExpanded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!SessionInfo.userId) {
|
||||
@@ -212,9 +228,12 @@ export default function CurrentPlanPage() {
|
||||
<h2>Características</h2>
|
||||
</div>
|
||||
{plan.features.length > 0 ? (
|
||||
<ul className={styles.features}>
|
||||
{plan.features.map((feature) => <li key={feature}>{feature}</li>)}
|
||||
</ul>
|
||||
<div className={styles.featuresPanel}>
|
||||
<p>Un resumen rápido de los beneficios incluidos para operar tu organización.</p>
|
||||
<ul className={styles.features}>
|
||||
{plan.features.map((feature) => <li key={feature}>{feature}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
) : (
|
||||
<p className={styles.message}>Este plan no tiene características cargadas.</p>
|
||||
)}
|
||||
@@ -223,22 +242,21 @@ export default function CurrentPlanPage() {
|
||||
<div className={styles.section}>
|
||||
<div className={styles.sectionHeader}>
|
||||
<span>Capacidad del plan</span>
|
||||
<h2>Límites principales</h2>
|
||||
<h2>Uso y límites principales</h2>
|
||||
</div>
|
||||
<div className={styles.limitsGrid}>
|
||||
{getLimitCards(plan, metrics).map((item) => (
|
||||
<div className={styles.limitItem} key={item.label}>
|
||||
<span>{item.label}</span>
|
||||
<strong>{formatLimit(item.limit)}</strong>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{metrics && (
|
||||
<div className={styles.usageShowcase}>
|
||||
<div className={styles.usageIntro}>
|
||||
<span>Uso actual</span>
|
||||
<strong>Actividad real de tu organización</strong>
|
||||
</div>
|
||||
<button
|
||||
className={styles.usageShowcase}
|
||||
type="button"
|
||||
aria-expanded={isCapacityExpanded}
|
||||
aria-controls="capacity-details"
|
||||
onClick={() => setIsCapacityExpanded((current) => !current)}
|
||||
>
|
||||
<div className={styles.usageIntro}>
|
||||
<span>Uso actual</span>
|
||||
<strong>Actividad real de tu organización</strong>
|
||||
<small>{isCapacityExpanded ? "Ocultar límites del plan" : "Ver límites del plan"}</small>
|
||||
</div>
|
||||
{metrics && (
|
||||
<div className={styles.usageStats}>
|
||||
<div className={styles.usageStat}>
|
||||
<span>Clientes</span>
|
||||
@@ -253,6 +271,22 @@ export default function CurrentPlanPage() {
|
||||
<strong>{metrics.employeesCount}</strong>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
{isCapacityExpanded && (
|
||||
<div className={styles.limitsPanel} id="capacity-details">
|
||||
{getLimitCards(plan, metrics).map((item) => (
|
||||
<div className={styles.limitRow} key={item.label}>
|
||||
<div>
|
||||
<span>{item.label}</span>
|
||||
<small>{getLimitDetail(item.limit, item.current)}</small>
|
||||
</div>
|
||||
<div className={styles.limitValues}>
|
||||
<strong>{getLimitValue(item.limit, item.current)}</strong>
|
||||
<small>{getLimitCaption(item.limit, item.current)}</small>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user