notification working

This commit is contained in:
2026-04-20 15:23:28 +05:30
parent 93dbf2023c
commit 237ba6b3c1
12 changed files with 523 additions and 86 deletions

View File

@@ -2112,7 +2112,7 @@
}
.confirm-modal {
background: var(--color-surface);
background: #ffffff;
border-radius: 20px;
padding: 1.75rem;
max-width: 380px;
@@ -2897,7 +2897,7 @@
}
[data-theme="dark"] .confirm-modal {
background: var(--color-surface);
background: #1e1e1e;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
}
@@ -3189,7 +3189,6 @@
[data-theme="liquid-glass"] .calendar-card,
[data-theme="liquid-glass"] .entry-card,
[data-theme="liquid-glass"] .entry-modal,
[data-theme="liquid-glass"] .confirm-modal,
[data-theme="liquid-glass"] .settings-profile,
[data-theme="liquid-glass"] .settings-card,
[data-theme="liquid-glass"] .settings-tutorial-btn,
@@ -3412,6 +3411,14 @@
-webkit-backdrop-filter: blur(6px);
}
/* -- Modal boxes always opaque -- */
[data-theme="liquid-glass"] .confirm-modal,
[data-theme="liquid-glass"] .bg-modal {
background: #ffffff;
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
/* -- Bottom nav -- */
[data-theme="liquid-glass"] .bottom-nav-btn {
color: #475569;
@@ -3820,7 +3827,7 @@ body.gj-has-bg .settings-page {
============================ */
.bg-modal {
background: var(--color-surface, #fff);
background: #ffffff;
border-radius: 20px;
padding: 1.5rem;
width: min(440px, calc(100vw - 2rem));

View File

@@ -30,6 +30,7 @@ import {
saveEncryptedSecretKey,
getEncryptedSecretKey,
} from '../lib/crypto'
import { REMINDER_TIME_KEY, REMINDER_ENABLED_KEY } from '../hooks/useReminder'
type MongoUser = {
id: string
@@ -40,6 +41,11 @@ type MongoUser = {
tutorial?: boolean
backgroundImage?: string | null
backgroundImages?: string[]
reminder?: {
enabled: boolean
time?: string
timezone?: string
}
}
type AuthContextValue = {
@@ -135,6 +141,18 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}
}
function syncReminderFromDb(mongoUser: MongoUser) {
const r = mongoUser.reminder
if (r) {
localStorage.setItem(REMINDER_ENABLED_KEY, r.enabled ? 'true' : 'false')
if (r.time) localStorage.setItem(REMINDER_TIME_KEY, r.time)
else localStorage.removeItem(REMINDER_TIME_KEY)
} else {
localStorage.setItem(REMINDER_ENABLED_KEY, 'false')
localStorage.removeItem(REMINDER_TIME_KEY)
}
}
// Register or fetch user from MongoDB
async function syncUserWithDatabase(authUser: User) {
try {
@@ -148,12 +166,11 @@ export function AuthProvider({ children }: { children: ReactNode }) {
try {
console.log('[Auth] Fetching user by email:', email)
const existingUser = await getUserByEmail(email, token) as MongoUser
// console.log('[Auth] Found existing user:', existingUser.id)
setUserId(existingUser.id)
setMongoUser(existingUser)
syncReminderFromDb(existingUser)
} catch (error) {
console.warn('[Auth] User not found, registering...', error)
// User doesn't exist, register them
const newUser = await registerUser(
{
email,
@@ -165,6 +182,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
console.log('[Auth] Registered new user:', newUser.id)
setUserId(newUser.id)
setMongoUser(newUser)
syncReminderFromDb(newUser)
}
} catch (error) {
console.error('[Auth] Error syncing user with database:', error)
@@ -226,13 +244,11 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}
async function signOut() {
// Clear secret key from memory
setSecretKey(null)
setMongoUser(null)
// Clear pending tour step (session state)
localStorage.removeItem('gj-tour-pending-step')
// Keep device key and encrypted key for next login
// Do NOT clear localStorage or IndexedDB
localStorage.removeItem(REMINDER_TIME_KEY)
localStorage.removeItem(REMINDER_ENABLED_KEY)
await firebaseSignOut(auth)
setUserId(null)
}

View File

@@ -29,11 +29,21 @@ export function isReminderEnabled(): boolean {
/** Get FCM token using the existing sw.js (which includes Firebase messaging). */
async function getFcmToken(): Promise<string | null> {
const messaging = await messagingPromise
if (!messaging) return null
if (!messaging) {
console.warn('[FCM] Firebase Messaging not supported in this browser')
return null
}
// Use the already-registered sw.js — no second SW needed
const swReg = await navigator.serviceWorker.ready
return getToken(messaging, { vapidKey: VAPID_KEY, serviceWorkerRegistration: swReg })
console.log('[FCM] Service worker ready:', swReg.active?.scriptURL)
const token = await getToken(messaging, { vapidKey: VAPID_KEY, serviceWorkerRegistration: swReg })
if (token) {
console.log('[FCM] Token obtained:', token.slice(0, 20) + '…')
} else {
console.warn('[FCM] getToken returned empty — VAPID key wrong or SW not registered?')
}
return token
}
/**
@@ -64,16 +74,21 @@ export async function enableReminder(
}
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
console.log('[FCM] Saving token and reminder settings:', { timeStr, timezone })
await saveFcmToken(userId, fcmToken, authToken)
console.log('[FCM] Token saved to backend')
await saveReminderSettings(userId, { time: timeStr, enabled: true, timezone }, authToken)
console.log('[FCM] Reminder settings saved to backend')
localStorage.setItem(REMINDER_TIME_KEY, timeStr)
localStorage.setItem(REMINDER_ENABLED_KEY, 'true')
return null
} catch (err) {
console.error('FCM reminder setup failed', err)
return 'Failed to set up push notification. Please try again.'
const msg = err instanceof Error ? err.message : String(err)
console.error('[FCM] Reminder setup failed:', msg)
return `Failed to set up reminder: ${msg}`
}
}
@@ -98,16 +113,21 @@ export async function listenForegroundMessages(): Promise<() => void> {
const messaging = await messagingPromise
if (!messaging) return () => {}
console.log('[FCM] Foreground message listener registered')
const unsubscribe = onMessage(messaging, (payload) => {
console.log('[FCM] Foreground message received:', payload)
const title = payload.notification?.title || 'Grateful Journal 🌱'
const body = payload.notification?.body || "You haven't written today yet."
if (Notification.permission === 'granted') {
new Notification(title, {
body,
icon: '/web-app-manifest-192x192.png',
tag: 'gj-daily-reminder',
})
if (Notification.permission !== 'granted') {
console.warn('[FCM] Notification permission not granted — cannot show notification')
return
}
new Notification(title, {
body,
icon: '/web-app-manifest-192x192.png',
tag: 'gj-daily-reminder',
})
})
return unsubscribe

View File

@@ -15,7 +15,9 @@ if ('serviceWorker' in navigator) {
}
// Show FCM notifications when app is open in foreground
listenForegroundMessages()
listenForegroundMessages().catch((err) => {
console.error('[FCM] Failed to set up foreground message listener:', err)
})
createRoot(document.getElementById('root')!).render(
<StrictMode>

View File

@@ -311,7 +311,6 @@ export default function SettingsPage() {
}
}
// @ts-ignore — intentionally unused, reminder is disabled (coming soon)
const handleReminderToggle = async () => {
if (!user || !userId) return
if (!reminderTime) {
@@ -477,8 +476,12 @@ export default function SettingsPage() {
<div className="settings-divider"></div>
{/* Daily Reminder — disabled for now, logic preserved */}
<div className="settings-item" style={{ opacity: 0.5 }}>
{/* Daily Reminder */}
<button
type="button"
className="settings-item settings-item-button"
onClick={handleOpenReminderModal}
>
<div className="settings-item-icon settings-item-icon-orange">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
@@ -487,18 +490,14 @@ export default function SettingsPage() {
</div>
<div className="settings-item-content">
<h4 className="settings-item-title">Daily Reminder</h4>
<p className="settings-item-subtitle">Coming soon</p>
<p className="settings-item-subtitle">
{reminderEnabled && reminderTime ? `Set for ${reminderTime}` : 'Set a daily reminder' }
</p>
</div>
<label className="settings-toggle">
<input
type="checkbox"
checked={false}
disabled
readOnly
/>
<span className="settings-toggle-slider" style={{ cursor: 'not-allowed' }}></span>
</label>
</div>
<svg className="settings-item-arrow" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="9 18 15 12 9 6" />
</svg>
</button>
</div>
</section>
@@ -999,6 +998,35 @@ export default function SettingsPage() {
{reminderSaving ? 'Saving…' : 'Save'}
</button>
</div>
{reminderEnabled && reminderTime && (
<button
type="button"
onClick={async () => {
if (!user || !userId) return
setReminderSaving(true)
const authToken = await user.getIdToken()
await disableReminder(userId, authToken)
setReminderEnabled(false)
setReminderSaving(false)
setShowReminderModal(false)
setMessage({ type: 'success', text: 'Reminder disabled' })
setTimeout(() => setMessage(null), 2000)
}}
disabled={reminderSaving}
style={{
marginTop: '0.5rem',
width: '100%',
background: 'none',
border: 'none',
color: 'var(--color-error, #ef4444)',
fontSize: '0.85rem',
cursor: 'pointer',
padding: '0.4rem',
}}
>
Disable Reminder
</button>
)}
</div>
</div>
)}