Refactor streak modal into a single card with top border-b iframe animation and bottom greetings, daily check-in Snapchat-like streak update/reset logic

This commit is contained in:
2026-07-10 14:29:34 +05:30
parent 96cf5f6dc1
commit 89c704c02c
5 changed files with 109 additions and 45 deletions

View File

@@ -480,6 +480,34 @@ def get_user_profile(user_id: UUID, session: Session = Depends(get_session)):
user = session.get(models.User, user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
# Snapchat-style daily active streak check
now = datetime.utcnow()
today = now.date()
last_active_date = user.last_active.date()
delta = (today - last_active_date).days
changed = False
if user.streak_count == 0:
user.streak_count = 1
user.longest_streak = max(user.longest_streak, 1)
user.last_active = now
changed = True
elif delta == 1:
user.streak_count += 1
user.longest_streak = max(user.longest_streak, user.streak_count)
user.last_active = now
changed = True
elif delta > 1:
user.streak_count = 1
user.last_active = now
changed = True
if changed:
session.add(user)
session.commit()
session.refresh(user)
return user
class UserUpdate(BaseModel):

View File

@@ -9,7 +9,7 @@
--navy-900:#081226; --navy-800:#0c1c39; --navy-700:#13294d;
--gold-200:#fdeeb8; --gold-300:#f6dd93; --gold-400:#e8c463; --gold-500:#d4af37;
--gold-600:#b1892a; --gold-700:#7d5f18;
--ink:#e9eefb; --badge:230px; --grow:1.18; --halo-w:170%;
--ink:#e9eefb; --badge:190px; --grow:1.18; --halo-w:170%;
--font:"Space Grotesk", ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
}
*{box-sizing:border-box}
@@ -164,9 +164,13 @@
body::after {
display: none !important;
}
#stage, .hint {
background: transparent !important;
box-shadow: none !important;
.stage {
height: 280px !important;
width: 100% !important;
max-width: 100% !important;
padding: 0 !important;
gap: 0 !important;
justify-content: center !important;
}
.count, .panel, .hint {
display: none !important;

View File

@@ -19,9 +19,13 @@ def port_html():
body::after {
display: none !important;
}
#stage, .hint {
background: transparent !important;
box-shadow: none !important;
.stage {
height: 280px !important;
width: 100% !important;
max-width: 100% !important;
padding: 0 !important;
gap: 0 !important;
justify-content: center !important;
}
.count, .panel, .hint {
display: none !important;
@@ -55,6 +59,9 @@ def port_html():
html = html.replace("Math.max(target,5)/5", "Math.max(target,3)/3")
html = html.replace("(4 - target) / 3", "(3 - target) / 3")
# 5. Adjust badge size from 230px to 190px
html = html.replace("--badge:230px;", "--badge:190px;")
with open(dest_path, 'w', encoding='utf-8') as f:
f.write(html)
print("Ported successfully to public/streak-animation.html")

View File

@@ -44,26 +44,40 @@ export function StreakOnboardingModal() {
setIsOpen(false);
};
// Calculate trial days left
let trialDaysLeft = 0;
if (user.trial_end) {
const end = new Date(user.trial_end);
const now = new Date();
const diffTime = end.getTime() - now.getTime();
trialDaysLeft = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
}
return (
<div className="fixed inset-0 z-50 flex flex-col items-center justify-end pb-12 bg-black/90 backdrop-blur-lg animate-in fade-in duration-300">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/85 backdrop-blur-md animate-in fade-in duration-300">
{/* Celebration Animation Canvas & Logo Iframe */}
<iframe
src={`/streak-animation.html?streak=${streak}`}
className="absolute inset-0 w-full h-full border-none pointer-events-none z-10"
title="Streak Celebration"
allowtransparency="true"
/>
{/* Adaptive Modal Content Card (positioned at the bottom to let the animated logo shine in the center) */}
<div className="bg-background/85 backdrop-blur-md border border-border/60 shadow-[0_0_50px_-15px_rgba(0,240,255,0.1)] rounded-3xl w-full max-w-lg p-6 text-center space-y-4 relative overflow-hidden animate-in zoom-in-95 slide-in-from-bottom-4 duration-300 z-20 pointer-events-auto mx-4">
{/* Centered Modal Card Container with Border (containing both the animation and text) */}
<div className="bg-background/95 border border-border shadow-[0_0_60px_-10px_rgba(0,240,255,0.2)] rounded-3xl w-full max-w-lg relative overflow-hidden animate-in zoom-in-95 slide-in-from-bottom-4 duration-300 z-20 pointer-events-auto mx-4 flex flex-col justify-start">
{/* Subtle background glow */}
<div className="absolute -right-20 -top-20 size-48 bg-[#00F0FF]/5 rounded-full blur-3xl pointer-events-none" />
<div className="absolute -left-20 -bottom-20 size-48 bg-primary/5 rounded-full blur-3xl pointer-events-none" />
{/* Text Greetings */}
<div className="space-y-2">
{/* Top Celebration Animation Panel (Iframe with height 280px, border-b) */}
<div className="relative w-full h-[280px] border-b border-border/40 z-10 pointer-events-none overflow-hidden bg-[#0c1c39]/30">
<iframe
src={`/streak-animation.html?streak=${streak}`}
className="w-full h-full border-none"
title="Streak Celebration"
allowtransparency="true"
/>
</div>
{/* Bottom Greetings & Action Panel */}
<div className="p-6 text-center space-y-4">
{/* Badge Indicators */}
<div className="flex flex-col items-center gap-2">
{streak > 0 ? (
<div className="inline-flex items-center gap-1.5 px-3 py-0.5 bg-orange-500/10 border border-orange-500/20 text-orange-500 text-xs font-extrabold rounded-full tracking-wider uppercase">
<Flame size={12} className="fill-current animate-bounce" /> {streak} Day Streak Active
@@ -74,6 +88,15 @@ export function StreakOnboardingModal() {
</div>
)}
{trialDaysLeft > 0 && (
<div className="inline-flex items-center gap-1.5 px-3 py-0.5 bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-xs font-semibold rounded-full tracking-wider">
🎁 Free Trial: {trialDaysLeft} {trialDaysLeft === 1 ? "day" : "days"} remaining
</div>
)}
</div>
{/* Greetings Text */}
<div className="space-y-2">
<h3 className="text-xl md:text-2xl font-black tracking-tight bg-gradient-to-r from-white via-white to-gray-400 bg-clip-text text-transparent">
{wishTitle}
</h3>
@@ -93,5 +116,6 @@ export function StreakOnboardingModal() {
</div>
</div>
</div>
</div>
);
}

View File

@@ -18,6 +18,7 @@ export interface UserProfile {
longest_streak: number;
active_session_token?: string;
created_at?: string;
trial_end?: string;
}
export const DEFAULT_USER_ID = "b0e17378-864d-439e-8806-708dc48a891b"; // AI Coach