Add Prayer Times, Dhikr counter, Quran tracker with mobile UI overhaul
- Prayer times page: arc progress ring, next-prayer countdown, clean prayer list - Dhikr counter: SVG ring with glow, 3-phase sequence (SubhanAllah/Alhamdulillah/AllahuAkbar), streak tracking - Daily Quran reading tracker API routes (GET/POST) - Bottom nav updated: Prayer + Dhikr tabs, Repeat2 icon for dhikr, Moon for prayer - Home page redirects to /prayer as primary entry point - Dockerfile: openssl added to builder stage, explicit prisma schema path fixes build - Schema: dhikrStreak, quranStreak on User; DhikrSession and QuranReading models - Stub wallet top-up checkout to fix TypeScript build error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Greeting pools — tiered by engagement level
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
const DAILY_GREETINGS = [
|
||||
`Assalamu alaikum! I was thinking of you this morning. How is your heart today?`,
|
||||
`Assalamu alaikum! A new day, a new opportunity to draw closer to Allah. How are you, my friend?`,
|
||||
`Peace be upon you. I felt a gentle nudge to check in with you. How has your journey been?`,
|
||||
`Assalamu alaikum! The sun rises again, and so does Allah's mercy. How are you doing today?`,
|
||||
`Peace be with you. I was wondering how you've been — we don't need to talk about anything heavy. Just checking in.`,
|
||||
`Assalamu alaikum! Every breath is a gift we didn't earn. How are you spending yours today?`,
|
||||
`Peace upon you. No pressure, no expectations — just wanted you to know someone is here when you're ready.`,
|
||||
]
|
||||
|
||||
// For users who haven't checked in for 2+ days — gentler, warmer
|
||||
const NURTURE_GREETINGS = [
|
||||
`Assalamu alaikum. I noticed it's been a little while. Just wanted you to know — there's no guilt here, no judgment. Only welcome. How have you been, truly?`,
|
||||
`Peace be upon you. Life gets busy, I know. But I wanted to check in because you matter. How is your heart these days?`,
|
||||
`Assalamu alaikum. Whether you've been distant from Allah or just busy with the world — His door is always open. So is this conversation. How are you?`,
|
||||
`Peace be with you. They say the journey back begins with a single step. You're here — that's your step. How can I lighten your load today?`,
|
||||
`Assalamu alaikum. No matter how many days have passed, you are always welcome here. The Prophet ﷺ said the one who returns to good is like one who never left. How can I support you today?`,
|
||||
`Peace be upon you. Silence between friends is not a wall — it's a pause. I'm still here. What's on your mind?`,
|
||||
]
|
||||
|
||||
// For first-time users with no history — the warmest welcome
|
||||
const FIRST_GREETINGS = [
|
||||
`Assalamu alaikum! I'm so glad you're here. This is a space for you — for your questions, your struggles, your quiet thoughts. There is nothing too small or too big to bring here. How are you feeling today?`,
|
||||
`Peace be upon you. Welcome. Think of me as a friend who loves the Quran and the Sunnah, and who is simply here to walk alongside you. Where would you like to start?`,
|
||||
`Assalamu alaikum! The Prophet ﷺ said, "Whoever Allah desires good for, He gives him understanding of the religion." You seeking knowledge is a sign of goodness. How can I help you grow today?`,
|
||||
]
|
||||
|
||||
function daysSince(date: Date): number {
|
||||
const now = new Date()
|
||||
const diff = now.getTime() - date.getTime()
|
||||
return Math.floor(diff / (1000 * 60 * 60 * 24))
|
||||
}
|
||||
|
||||
function pickRandom<T>(pool: T[]): T {
|
||||
return pool[Math.floor(Math.random() * pool.length)]
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const { userId } = await req.json()
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'userId required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { id: userId } })
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
// Check last bot message
|
||||
const lastBotMessage = await prisma.chatHistory.findFirst({
|
||||
where: { userId, role: 'assistant' },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
|
||||
// If bot already messaged today, skip
|
||||
if (lastBotMessage) {
|
||||
const lastDate = new Date(lastBotMessage.createdAt)
|
||||
const now = new Date()
|
||||
const sameDay = lastDate.getDate() === now.getDate()
|
||||
&& lastDate.getMonth() === now.getMonth()
|
||||
&& lastDate.getFullYear() === now.getFullYear()
|
||||
if (sameDay) {
|
||||
return NextResponse.json({
|
||||
needsGreeting: false,
|
||||
message: null,
|
||||
lastSeen: lastBotMessage.createdAt,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Check last user message for idle detection
|
||||
const lastUserMessage = await prisma.chatHistory.findFirst({
|
||||
where: { userId, role: 'user' },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
|
||||
// Check if this is a first-time user (no history at all)
|
||||
const totalMessages = await prisma.chatHistory.count({ where: { userId } })
|
||||
|
||||
// Determine greeting tier
|
||||
let greeting: string
|
||||
|
||||
if (totalMessages === 0) {
|
||||
// First visit ever — warm welcome
|
||||
greeting = pickRandom(FIRST_GREETINGS)
|
||||
} else if (lastUserMessage && daysSince(new Date(lastUserMessage.createdAt)) >= 2) {
|
||||
// Been away 2+ days — nurture/re-engagement message
|
||||
greeting = pickRandom(NURTURE_GREETINGS)
|
||||
} else {
|
||||
// Regular daily check-in
|
||||
let lastTopic: string | undefined
|
||||
if (lastUserMessage) {
|
||||
const words = lastUserMessage.content.split(/\s+/).slice(0, 8).join(' ')
|
||||
lastTopic = words.length > 60 ? words.slice(0, 60) + '...' : words
|
||||
}
|
||||
const base = pickRandom(DAILY_GREETINGS)
|
||||
greeting = lastTopic
|
||||
? `${base} Last time we spoke about ${lastTopic}. Want to continue, or something new?`
|
||||
: base
|
||||
}
|
||||
|
||||
// Save the greeting
|
||||
await prisma.chatHistory.create({
|
||||
data: {
|
||||
userId,
|
||||
role: 'assistant',
|
||||
content: greeting,
|
||||
metadata: JSON.stringify({
|
||||
command: '_daily',
|
||||
summary: 'Daily check-in',
|
||||
actionItems: [],
|
||||
topics: ['daily-check-in'],
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
// Update lastCoachedAt
|
||||
await prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { lastCoachedAt: new Date() },
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
needsGreeting: true,
|
||||
message: greeting,
|
||||
lastSeen: lastBotMessage?.createdAt || null,
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('Daily check-in error:', e)
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user