From 961897ce2f19c73c278a5a5c8241ba70903a4217 Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 21 Mar 2026 09:56:35 +0100 Subject: [PATCH] feat: add trial system UI with countdown banner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Component: - TrialBanner.jsx: Displays remaining trial days with urgency levels Features: - Calculates days left from profile.trial_ends_at - Three urgency levels: * Normal (>7 days): Accent blue, "Abo wählen" * Warning (≤7 days): Orange, "Abo wählen" * Urgent (≤3 days): Red + ⚠️, "Jetzt upgraden" - Auto-hides when no trial or trial ended - Responsive flex layout - Call-to-action button links to /settings?tab=subscription Integration: - Added to Dashboard after header greeting - Uses activeProfile from ProfileContext - Clean, non-intrusive design UX: - Clear messaging: "Trial endet in X Tagen" - Special case: "morgen" for 1 day left - Color-coded severity (blue → orange → red) - Prominent CTA button Co-Authored-By: Claude Opus 4.6 --- frontend/src/components/TrialBanner.jsx | 86 +++++++++++++++++++++++++ frontend/src/pages/Dashboard.jsx | 4 ++ 2 files changed, 90 insertions(+) create mode 100644 frontend/src/components/TrialBanner.jsx diff --git a/frontend/src/components/TrialBanner.jsx b/frontend/src/components/TrialBanner.jsx new file mode 100644 index 0000000..6208700 --- /dev/null +++ b/frontend/src/components/TrialBanner.jsx @@ -0,0 +1,86 @@ +import { useEffect, useState } from 'react' +import { Link } from 'react-router-dom' + +export default function TrialBanner({ profile }) { + const [daysLeft, setDaysLeft] = useState(null) + + useEffect(() => { + if (!profile?.trial_ends_at) { + setDaysLeft(null) + return + } + + const trialEnd = new Date(profile.trial_ends_at) + const now = new Date() + const diff = trialEnd - now + const days = Math.ceil(diff / (1000 * 60 * 60 * 24)) + + setDaysLeft(days) + }, [profile]) + + // No trial or trial ended + if (daysLeft === null || daysLeft <= 0) return null + + // Determine severity + const isUrgent = daysLeft <= 3 + const isWarning = daysLeft <= 7 + + const bgColor = isUrgent ? '#FCEBEB' : isWarning ? '#FFF4E6' : 'var(--accent-light)' + const borderColor = isUrgent ? '#D85A30' : isWarning ? '#F59E0B' : 'var(--accent)' + const textColor = isUrgent ? '#D85A30' : isWarning ? '#D97706' : 'var(--accent-dark)' + + return ( +
+
+
+ {isUrgent && '⚠️ '} + Deine Trial endet {daysLeft === 1 ? 'morgen' : `in ${daysLeft} Tagen`} +
+
+ {isUrgent + ? 'Upgrade jetzt um weiterhin alle Features nutzen zu können' + : 'Wähle ein Abo um unbegrenzt Zugriff zu erhalten' + } +
+
+ + + {isUrgent ? 'Jetzt upgraden' : 'Abo wählen'} + +
+ ) +} diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index 5ec20b6..a5fc3cb 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -8,6 +8,7 @@ import { import { api } from '../utils/api' import { useProfile } from '../context/ProfileContext' import { getBfCategory } from '../utils/calc' +import TrialBanner from '../components/TrialBanner' import { getInterpretation, getStatusColor, getStatusBg } from '../utils/interpret' import Markdown from '../utils/Markdown' import dayjs from 'dayjs' @@ -315,6 +316,9 @@ export default function Dashboard() { + {/* Trial Banner */} + + {!hasAnyData && (

Willkommen bei Mitai Jinkendo!