Feature: Dynamic time-of-day specific positive reinforcement notifications

This commit is contained in:
Avery Felts 2026-01-31 17:44:59 -07:00
parent 3a31c8a956
commit 95f0d94411
2 changed files with 76 additions and 9 deletions

View File

@ -11,6 +11,69 @@ if (process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY && process.env.VAPID_PRIVATE_KEY) {
); );
} }
const MESSAGES = {
morning: [
"Rise and shine! Today is a perfect day to stay on track.",
"New morning, new strength. Let's keep that streak alive!",
"Success starts with your first choice today. You've got this!",
"Your morning check-in starts now. Every smoke-free minute counts!",
"The morning is yours. Claim it by logging your progress.",
"Small steps lead to big changes. Start your day by logging in!"
],
afternoon: [
"Keep the momentum going! You're doing great this afternoon.",
"Halfway through the day and looking strong. Log your progress!",
"The afternoon slump is no match for your willpower. Stay focused!",
"Just checking in—how's your journey going? Log it now!",
"You're stronger than any craving. Keep pushing forward!",
"Consistency is key to mastery. Youre doing amazing today!"
],
evening: [
"Wind down with a win. Another day closer to your goal!",
"Evening reflection: You faced the day and won. Log your success!",
"Don't let the evening cravings win. You're almost there!",
"Sweet dreams are made of smoke-free days. Keep it up!",
"The night is calm, and so are you. Finish the day strong!",
"Final check-in for the day! Log your progress and sleep proud.",
"Progress isn't always linear, but you're showing up. Great job!",
"You survived another day. Celebrate with a quick log!"
]
};
function getNotificationData(timeStr: string) {
// timeStr is HH:MM
const [h, m] = timeStr.split(':').map(Number);
const totalMinutes = h * 60 + m;
let category: 'morning' | 'afternoon' | 'evening';
// Morning: 5:30 (330m) - 11:00 (660m)
// Afternoon: 11:00 (660m) - 16:30 (990m)
// Evening: 16:30 (990m) - 5:30 (330m)
if (totalMinutes >= 330 && totalMinutes < 660) {
category = 'morning';
} else if (totalMinutes >= 660 && totalMinutes < 990) {
category = 'afternoon';
} else {
category = 'evening';
}
const options = MESSAGES[category];
const message = options[Math.floor(Math.random() * options.length)];
const titles = [
"QuitTraq Motivation",
"QuitTraq Milestone",
"QuitTraq Strength",
"QuitTraq Journey",
"QuitTraq Check-in"
];
const title = titles[Math.floor(Math.random() * titles.length)];
return { title, message };
}
export async function GET(request: NextRequest) { export async function GET(request: NextRequest) {
try { try {
// Protect with a secret if configured // Protect with a secret if configured
@ -37,6 +100,7 @@ export async function GET(request: NextRequest) {
let shouldSend = false; let shouldSend = false;
let notificationBody = ''; let notificationBody = '';
let notificationTitle = '';
let tag = ''; let tag = '';
if (user.frequency === 'hourly') { if (user.frequency === 'hourly') {
@ -52,7 +116,9 @@ export async function GET(request: NextRequest) {
if (userTimeString >= startStr && userTimeString <= endStr && currentM === startM) { if (userTimeString >= startStr && userTimeString <= endStr && currentM === startM) {
if (user.lastNotifiedDate !== currentHourKey) { if (user.lastNotifiedDate !== currentHourKey) {
shouldSend = true; shouldSend = true;
notificationBody = "How are you doing? Log your status to stay on track!"; const { title: t, message } = getNotificationData(userTimeString);
notificationBody = message;
notificationTitle = t;
tag = 'hourly-reminder'; tag = 'hourly-reminder';
// Update to match current Hour Key so we don't send again this hour // Update to match current Hour Key so we don't send again this hour
@ -69,7 +135,9 @@ export async function GET(request: NextRequest) {
if (userTimeString === user.reminderTime) { if (userTimeString === user.reminderTime) {
if (user.lastNotifiedDate !== userDateString) { if (user.lastNotifiedDate !== userDateString) {
shouldSend = true; shouldSend = true;
notificationBody = "Time to log your daily usage! Every day counts."; const { title: t, message } = getNotificationData(userTimeString);
notificationBody = message;
notificationTitle = t;
tag = 'daily-reminder'; tag = 'daily-reminder';
await updateLastNotifiedD1(user.userId, userDateString); await updateLastNotifiedD1(user.userId, userDateString);
@ -78,9 +146,8 @@ export async function GET(request: NextRequest) {
} }
if (shouldSend) { if (shouldSend) {
const title = user.frequency === 'hourly' ? 'QuitTraq Hourly Check-in' : 'QuitTraq Reminder';
const payload = JSON.stringify({ const payload = JSON.stringify({
title: title, title: notificationTitle,
body: notificationBody, body: notificationBody,
tag: tag, tag: tag,
url: '/' url: '/'

View File

@ -247,14 +247,14 @@ export function UnifiedQuitPlanCard({
if (!showNicotine && !showWeed) return null; if (!showNicotine && !showWeed) return null;
return ( return (
<Card className="backdrop-blur-xl shadow-xl border-white/10 overflow-hidden"> <Card className="backdrop-blur-2xl shadow-2xl border-white/10 overflow-hidden bg-white/5">
<CardHeader className="pb-3 border-b border-white/5 bg-white/5"> <CardHeader className="pb-1 pt-6 px-4 sm:px-6">
<CardTitle className="flex items-center gap-2 text-sm sm:text-base font-black uppercase tracking-widest opacity-80"> <CardTitle className="flex items-center gap-2 text-sm sm:text-base font-black uppercase tracking-widest opacity-60">
<TrendingDown className="h-5 w-5 text-primary" /> <TrendingDown className="h-4 w-4 text-primary" />
Quit Journey Plan Quit Journey Plan
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="pt-4 p-3 sm:p-6"> <CardContent className="pt-2 p-2 sm:p-4">
{showNicotine && ( {showNicotine && (
<SubstancePlanSection <SubstancePlanSection
substance="nicotine" substance="nicotine"