mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 03:56:15 -08:00
fix(dashboard): address review feedback on expiry badges
- Recompute expiry badges on a minute tick so a dashboard left open crosses day-countdown and expiration boundaries (Greptile P1 / Codex P2) - Gate the expiry refresh on the recent-sources rail setting so hidden rails cost no portal checks or playlist reads (Codex P2) - Move chip colors to theme-aware tokens in m3-theme.scss; both themes now hold >= 4.5:1 small-text contrast (light warn 5.3:1, light expired 5.4:1, dark warn 7.4:1, dark expired 6.0:1) (Codex P2) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e7d32d7c61
commit
0ba3386942
4 files changed
+57
-6
No files matched your search
@@ -112,6 +112,14 @@ html {
|
||||
--app-provider-stalker: #10b981;
|
||||
--app-provider-m3u: #d97706;
|
||||
|
||||
// Subscription-expiry chips (dashboard source cards). Text/surface pairs
|
||||
// are picked per theme to keep >= 4.5:1 small-text contrast against the
|
||||
// composited chip background.
|
||||
--app-expiry-warn-text: #8a5300;
|
||||
--app-expiry-warn-surface: color-mix(in srgb, #d97706 16%, transparent);
|
||||
--app-expiry-expired-text: #b3261e;
|
||||
--app-expiry-expired-surface: color-mix(in srgb, #f44336 14%, transparent);
|
||||
|
||||
// Hero
|
||||
--app-accent-gradient: linear-gradient(
|
||||
135deg,
|
||||
@@ -188,6 +196,17 @@ html {
|
||||
--app-provider-stalker: #34d399;
|
||||
--app-provider-m3u: #fbbf24;
|
||||
|
||||
// Subscription-expiry chips — brighter text for the dark surfaces;
|
||||
// expired matches the --app-live-color accent family.
|
||||
--app-expiry-warn-text: #fbbf24;
|
||||
--app-expiry-warn-surface: color-mix(in srgb, #fbbf24 16%, transparent);
|
||||
--app-expiry-expired-text: #ff8f8f;
|
||||
--app-expiry-expired-surface: color-mix(
|
||||
in srgb,
|
||||
#ff8f8f 16%,
|
||||
transparent
|
||||
);
|
||||
|
||||
// Hero section
|
||||
--app-accent-gradient: linear-gradient(
|
||||
135deg,
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
/** Warn this many days before a portal subscription lapses. */
|
||||
export const SOURCE_EXPIRY_WARNING_DAYS = 7;
|
||||
|
||||
/**
|
||||
* How often badge consumers should re-evaluate {@link resolveSourceExpiryBadge}
|
||||
* against the wall clock. The badge state only moves at day granularity, but a
|
||||
* dashboard left open must still cross day-countdown and expiration
|
||||
* boundaries without a remount; a minute tick is imperceptibly cheap.
|
||||
*/
|
||||
export const SOURCE_EXPIRY_TICK_MS = 60_000;
|
||||
|
||||
const SECONDS_PER_DAY = 86_400;
|
||||
|
||||
/**
|
||||
|
||||
@@ -376,8 +376,9 @@
|
||||
}
|
||||
|
||||
// Subscription-expiry chip on portal source cards. Mirrors the episode
|
||||
// chip's geometry; colors follow the switcher's status palette (amber =
|
||||
// expiring soon, red = already expired).
|
||||
// chip's geometry; the theme-aware token pairs (m3-theme.scss) keep the
|
||||
// small bold text at >= 4.5:1 contrast in both themes (amber = expiring
|
||||
// soon, error tone = already expired).
|
||||
.rail__card-expiry {
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.66rem;
|
||||
@@ -385,16 +386,22 @@
|
||||
line-height: 1;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
background: color-mix(in srgb, #ff9800 16%, transparent);
|
||||
color: #e68a00;
|
||||
background: var(
|
||||
--app-expiry-warn-surface,
|
||||
color-mix(in srgb, #d97706 16%, transparent)
|
||||
);
|
||||
color: var(--app-expiry-warn-text, #8a5300);
|
||||
letter-spacing: 0.02em;
|
||||
white-space: nowrap;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.rail__card-expiry--expired {
|
||||
background: color-mix(in srgb, #f44336 14%, transparent);
|
||||
color: #f44336;
|
||||
background: var(
|
||||
--app-expiry-expired-surface,
|
||||
color-mix(in srgb, #f44336 14%, transparent)
|
||||
);
|
||||
color: var(--app-expiry-expired-text, #b3261e);
|
||||
}
|
||||
|
||||
.rail__card-subtitle {
|
||||
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
DashboardTrendingService,
|
||||
GlobalRecentItem,
|
||||
resolveSourceExpiryBadge,
|
||||
SOURCE_EXPIRY_TICK_MS,
|
||||
} from '@iptvnator/workspace/dashboard/data-access';
|
||||
import type { DashboardHeroTmdbExtras } from './dashboard-hero-tmdb.service';
|
||||
import { DashboardHeroTmdbService } from './dashboard-hero-tmdb.service';
|
||||
@@ -342,6 +343,14 @@ export class WorkspaceDashboardRailsComponent {
|
||||
.map((item) => this.toTrendingCard(item));
|
||||
});
|
||||
|
||||
// Minute heartbeat for the expiry badges: resolveSourceExpiryBadge reads
|
||||
// the wall clock, so without a reactive tick a dashboard left open would
|
||||
// never cross a day-countdown or expiration boundary.
|
||||
private readonly sourceExpiryTick = toSignal(
|
||||
interval(SOURCE_EXPIRY_TICK_MS).pipe(startWith(0)),
|
||||
{ initialValue: 0 }
|
||||
);
|
||||
|
||||
readonly sourceCards = computed<DashboardRailCard[]>(() =>
|
||||
this.data.recentPlaylists().map((playlist) => ({
|
||||
id: playlist._id,
|
||||
@@ -417,7 +426,12 @@ export class WorkspaceDashboardRailsComponent {
|
||||
// Subscription-expiry badges for the source cards. Xtream rides the
|
||||
// shared PortalStatusService cache; Stalker reads the import-time
|
||||
// snapshot (memoized per playlist), so this stays cheap on re-entry.
|
||||
// Gated on the sources rail being enabled — with the rail hidden no
|
||||
// badge can render, so the status checks would be pure waste.
|
||||
effect(() => {
|
||||
if (!this.dashboardRails().recentSources) {
|
||||
return;
|
||||
}
|
||||
const playlists = this.data.recentPlaylists();
|
||||
untracked(() => void this.sourceExpiry.refresh(playlists));
|
||||
});
|
||||
@@ -612,6 +626,9 @@ export class WorkspaceDashboardRailsComponent {
|
||||
private buildSourceExpiryBadge(
|
||||
playlistId: string
|
||||
): DashboardRailCard['expiryBadge'] {
|
||||
// Reactive read: ties the wall-clock evaluation below to the minute
|
||||
// tick (this method only runs inside the sourceCards computed).
|
||||
this.sourceExpiryTick();
|
||||
const badge = resolveSourceExpiryBadge(
|
||||
this.sourceExpiry.facts().get(playlistId),
|
||||
Date.now()
|
||||
|
||||
Reference in new issue
Block a user