Add random congrats msg

This commit is contained in:
2025-10-15 18:37:05 +02:00
parent 76111ecd2d
commit 445f27a939
4 changed files with 170 additions and 21 deletions

102
public/encouragements.json Normal file
View File

@@ -0,0 +1,102 @@
[
"Great job! Keep going!",
"You're on fire! 🔥",
"Consistency is key!",
"Amazing streak!",
"You crushed it today!",
"Small steps, big results!",
"Habit hero!",
"Progress, not perfection!",
"Every dot counts!",
"Keep up the momentum!",
"Youre building something awesome!",
"One step closer to your goal!",
"Youre unstoppable!",
"Keep the streak alive!",
"Youre making it happen!",
"Your effort is inspiring!",
"Youre a streak superstar!",
"Every day matters!",
"Youre a habit legend!",
"Youre doing fantastic!",
"Keep shining!",
"Youre a role model!",
"Youre a champion!",
"Youre making progress!",
"Youre a winner!",
"Youre a streak master!",
"Youre a habit machine!",
"Youre a streak builder!",
"Youre a streak star!",
"Youre a streak hero!",
"Youre a streak ninja!",
"Youre a streak wizard!",
"Youre a streak warrior!",
"Youre a streak explorer!",
"Youre a streak adventurer!",
"Youre a streak conqueror!",
"Youre a streak champion!",
"Youre a streak genius!",
"Youre a streak guru!",
"Youre a streak expert!",
"Youre a streak pro!",
"Youre a streak veteran!",
"Youre a streak rookie!",
"Youre a streak all-star!",
"Youre a streak MVP!",
"Youre a streak superstar!",
"Youre a streak rockstar!",
"Youre a streak dynamo!",
"Youre a streak powerhouse!",
"Youre a streak inspiration!",
"Youre a streak motivator!",
"Youre a streak leader!",
"Youre a streak innovator!",
"Youre a streak creator!",
"Youre a streak builder!",
"Youre a streak achiever!",
"Youre a streak doer!",
"Youre a streak finisher!",
"Youre a streak starter!",
"Youre a streak closer!",
"Youre a streak winner!",
"Youre a streak believer!",
"Youre a streak dreamer!",
"Youre a streak thinker!",
"Youre a streak planner!",
"Youre a streak organizer!",
"Youre a streak strategist!",
"Youre a streak tactician!",
"Youre a streak visionary!",
"Youre a streak optimist!",
"Youre a streak realist!",
"Youre a streak enthusiast!",
"Youre a streak supporter!",
"Youre a streak encourager!",
"Youre a streak helper!",
"Youre a streak friend!",
"Youre a streak teammate!",
"Youre a streak partner!",
"Youre a streak ally!",
"Youre a streak companion!",
"Youre a streak buddy!",
"Youre a streak pal!",
"Youre a streak mate!",
"Youre a streak peer!",
"Youre a streak colleague!",
"Youre a streak associate!",
"Youre a streak collaborator!",
"Youre a streak contributor!",
"Youre a streak participant!",
"Youre a streak member!",
"Youre a streak player!",
"Youre a streak contender!",
"Youre a streak competitor!",
"Youre a streak challenger!",
"Youre a streak rival!",
"Youre a streak victor!",
"Youre a streak survivor!",
"Youre a streak thriver!",
"Youre a streak overcomer!",
"Youre a streak achiever!"
]

View File

@@ -17,6 +17,7 @@ import { motion } from 'framer-motion';
import { getColorIntensity, isToday, formatDate } from '../lib/utils-habit'; import { getColorIntensity, isToday, formatDate } from '../lib/utils-habit';
import { getFrozenDays } from '../lib/utils-habit'; import { getFrozenDays } from '../lib/utils-habit';
import { toggleCompletion } from '../lib/storage'; import { toggleCompletion } from '../lib/storage';
import { toast } from './ui/use-toast';
const MiniGrid = ({ habit, onUpdate }) => { const MiniGrid = ({ habit, onUpdate }) => {
const today = new Date(); const today = new Date();
@@ -39,10 +40,33 @@ const MiniGrid = ({ habit, onUpdate }) => {
days.push(date); days.push(date);
} }
const handleCellClick = (e, date) => { const handleCellClick = async (e, date) => {
e.stopPropagation(); e.stopPropagation();
toggleCompletion(habit.id, formatDate(date)); const dateStr = formatDate(date);
const isTodayCell = isToday(date);
const wasCompleted = habit.completions.includes(dateStr);
toggleCompletion(habit.id, dateStr);
onUpdate(); onUpdate();
// Only show encouragement toast if validating (adding) today's dot
if (isTodayCell && !wasCompleted) {
try {
const res = await fetch('/encouragements.json');
const messages = await res.json();
const msg = messages[Math.floor(Math.random() * messages.length)];
toast({
title: '🎉 Keep Going!',
description: msg,
duration: 2500,
});
} catch (err) {
// fallback message
toast({
title: '🎉 Keep Going!',
description: 'Great job! Keep up the streak!',
duration: 2500,
});
}
}
}; };
return ( return (

View File

@@ -7,19 +7,21 @@ import React from 'react';
const ToastProvider = ToastPrimitives.Provider; const ToastProvider = ToastPrimitives.Provider;
const ToastViewport = React.forwardRef(({ className, ...props }, ref) => ( const ToastViewport = React.forwardRef(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport <ToastPrimitives.Viewport
ref={ref} ref={ref}
className={cn( className={cn(
'fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]', 'fixed z-[100] flex max-h-screen w-full flex-col-reverse p-4',
className, 'sm:bottom-4 sm:right-4 sm:top-auto sm:left-auto sm:flex-col md:max-w-[420px]',
)} 'bottom-4 left-1/2 transform -translate-x-1/2 sm:transform-none',
{...props} className,
/> )}
{...props}
/>
)); ));
ToastViewport.displayName = ToastPrimitives.Viewport.displayName; ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
const toastVariants = cva( const toastVariants = cva(
'data-[swipe=move]:transition-none group relative pointer-events-auto flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full data-[state=closed]:slide-out-to-right-full', 'data-[swipe=move]:transition-none group relative pointer-events-auto flex w-full items-center justify-between space-x-4 overflow-hidden rounded-2xl border-0 p-6 pr-8 shadow-2xl transition-all bg-white/80 backdrop-blur-lg ring-2 ring-green-300/40 drop-shadow-xl scale-95 animate-toast-in data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=open]:slide-in-from-bottom-full data-[state=closed]:slide-out-to-right-full',
{ {
variants: { variants: {
variant: { variant: {
@@ -73,20 +75,24 @@ const ToastClose = React.forwardRef(({ className, ...props }, ref) => (
ToastClose.displayName = ToastPrimitives.Close.displayName; ToastClose.displayName = ToastPrimitives.Close.displayName;
const ToastTitle = React.forwardRef(({ className, ...props }, ref) => ( const ToastTitle = React.forwardRef(({ className, ...props }, ref) => (
<ToastPrimitives.Title <ToastPrimitives.Title
ref={ref} ref={ref}
className={cn('text-sm font-semibold', className)} className={cn('text-lg font-bold flex items-center gap-2', className)}
{...props} {...props}
/> >
<span className="animate-float inline-block">🎊</span> {props.children}
</ToastPrimitives.Title>
)); ));
ToastTitle.displayName = ToastPrimitives.Title.displayName; ToastTitle.displayName = ToastPrimitives.Title.displayName;
const ToastDescription = React.forwardRef(({ className, ...props }, ref) => ( const ToastDescription = React.forwardRef(({ className, ...props }, ref) => (
<ToastPrimitives.Description <ToastPrimitives.Description
ref={ref} ref={ref}
className={cn('text-sm opacity-90', className)} className={cn('text-base opacity-95 font-medium flex items-center gap-2', className)}
{...props} {...props}
/> >
<span className="animate-float inline-block"></span> {props.children}
</ToastPrimitives.Description>
)); ));
ToastDescription.displayName = ToastPrimitives.Description.displayName; ToastDescription.displayName = ToastPrimitives.Description.displayName;

View File

@@ -94,4 +94,21 @@
.dark .grid-scroll::-webkit-scrollbar-thumb { .dark .grid-scroll::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.2); background-color: rgba(255, 255, 255, 0.2);
}
/* Toast custom animations */
@keyframes toast-in {
0% { transform: scale(0.7) translateY(40px); opacity: 0; }
100% { transform: scale(1) translateY(0); opacity: 1; }
}
.animate-toast-in {
animation: toast-in 0.5s cubic-bezier(.68,-0.55,.27,1.55);
}
@keyframes float {
0% { transform: translateY(0); }
50% { transform: translateY(-8px); }
100% { transform: translateY(0); }
}
.animate-float {
animation: float 2s infinite ease-in-out;
} }