diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml
new file mode 100644
index 0000000..b62be4b
--- /dev/null
+++ b/.github/workflows/version-bump.yml
@@ -0,0 +1,48 @@
+name: Version Bump
+
+on:
+ push:
+ branches:
+ - '**'
+
+jobs:
+ bump:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ - name: Install deps
+ run: npm ci
+ - name: Determine bump type
+ id: bump
+ run: |
+ MSG=$(git log -1 --pretty=%B)
+ echo "Commit message: $MSG"
+ if echo "$MSG" | grep -qi "bump:major"; then
+ echo "type=major" >> $GITHUB_OUTPUT
+ elif echo "$MSG" | grep -qi "bump:minor"; then
+ echo "type=minor" >> $GITHUB_OUTPUT
+ else
+ echo "type=patch" >> $GITHUB_OUTPUT
+ fi
+ - name: Bump version
+ run: |
+ npm version ${{ steps.bump.outputs.type }} -m "chore: version bump to %s"
+ node scripts/sync-version.js
+ - name: Commit version files
+ run: |
+ git config user.name "github-actions"
+ git config user.email "github-actions@github.com"
+ git add package.json package-lock.json src/lib/version.ts
+ git commit -m "chore: sync version metadata" || echo "No changes to commit"
+ - name: Push changes
+ run: |
+ git push --follow-tags
diff --git a/README.md b/README.md
index 3133a09..3eba617 100644
--- a/README.md
+++ b/README.md
@@ -1,118 +1,48 @@
-# Prayer App
+# Haver — Prayer App
-A Progressive Web App (PWA) for journaling prayers and praises, organized by date, with journaling functionality and email reminders.
+A minimalist PWA for journaling prayers and tracking answered prayers. Built with Next.js, Firebase, and Tailwind.
+
+Current Version: 0.2.0
+
+## What's New (0.2.0)
+- Redesign: unified dark theme and gradients
+- Icons: lucide-react across headings and nav
+- Cleanup: removed legacy email routes and pages
+- UX: Pray For suggestions in compose tab
## Features
+- Firebase auth (login)
+- Compose daily prayer with date and optional "Pray For"
+- Suggested "Pray For" names via dropdown (type to add new)
+- Mark prayers as answered with details
+- Calendar view of entries
+- Reflections list with search and filters
+- Answered prayers gallery
+- Reminder banner + notification time settings
+- PWA installable; dark theme by default
-- User authentication with Firebase (login and signup)
-- Add prayers and praises with date organization
-- Journal entries for each prayer
-- "Pray For" field to specify a person for each prayer
-- Email field auto-populated with user's email for reminders
-- Archive prayers instead of deleting them
-- View archived prayers with dedicated tab
-- Search prayers by content, type, or journal (dedicated Search tab)
-- Pagination for large numbers of entries
-- Share prayers on social media, email, and text
-- Prominent share buttons with visual feedback
-- Admin panel for SMTP configuration and user management
-- Email notifications (requires configuration)
-- Reminder frequency options (daily, weekly, monthly, or never)
-- Option to include active prayers summary in reminder emails
-- Beautiful HTML-formatted emails with prayer cards and visual styling
-- PWA for offline access and installation
-- Dark mode theme
-- Bottom navigation tabs for easy switching (Add Entry, Active, Archived, Search)
-- Engaging loading screen with animations and inspirational content (minimum 5-second display)
-- Enhanced bottom navigation with gradients, shadows, and improved touch targets
-
-## API Endpoints
-
-- `GET/POST /api/send-email` - Send a single email reminder
-- `GET/POST /api/send-scheduled-emails` - Send scheduled email reminders to all users
+## Versioning Policy
+- Uses semantic versioning: major.minor
+- Minor: UI/UX tweaks, non-breaking features
+- Major: structural changes or breaking updates
## Setup
+1. Create a Firebase project (Authentication + Firestore)
+2. Add your Firebase config to [src/lib/firebase.ts](src/lib/firebase.ts)
+3. Start the dev server
-1. Create a Firebase project at https://console.firebase.google.com/
-2. Enable Authentication with Email/Password provider
-3. Enable Firestore Database
-4. Get your Firebase config and replace the placeholder in `src/lib/firebase.ts`
+## Notes
+- Email sending routes have been removed in the redesign.
+- Email verification remains available via Firebase.
-Users can now register accounts directly in the app!
+## Firestore Rules
+Deploy [firestore.rules](firestore.rules) to secure user data.
-## Admin Setup
-
-Admin access is restricted to specific email addresses. Admins can manage the admin email list directly in the app through the Admin panel.
-
-**To add/remove admin emails:**
-1. Log in as an existing admin
-2. Click the "Admin" button
-3. Scroll to "Admin Email Management" section
-4. Add new admin emails or remove existing ones
-5. Changes are saved automatically to Firestore
-
-Admin users can access the admin panel by clicking the "Admin" button in the header. From there, they can configure:
-
-- SMTP settings for email sending
-- Admin email management
-- Other server configurations
-
-The admin panel saves settings to Firestore for persistent configuration.
-
-## Firestore Security Rules
-
-To enable SMTP configuration saving and other admin features, you need to deploy Firestore security rules:
-
-1. Install Firebase CLI if you haven't already:
- ```bash
- npm install -g firebase-tools
- ```
-
-2. Login to Firebase:
- ```bash
- firebase login
- ```
-
-3. Initialize or connect to your Firebase project:
- ```bash
- firebase use --add
- ```
- Select your prayer-app project.
-
-4. Deploy the security rules using the provided script:
- ```bash
- # On Linux/Mac
- ./deploy-firestore-rules.sh
-
- # On Windows
- deploy-firestore-rules.bat
- ```
-
- Or deploy manually:
- ```bash
- firebase deploy --only firestore:rules
- ```
-
-The `firestore.rules` file in this repository allows authenticated users to read/write configuration data needed for the admin panel.
-
-## SMTP Configuration
-
-After deploying Firestore rules, you can configure SMTP settings through the admin panel:
-
-1. Log in as an admin user
-2. Click the "Admin" button
-3. Fill in your SMTP provider details (Gmail, Outlook, etc.)
-4. Save the configuration
-
-**Gmail Setup:**
-- Host: `smtp.gmail.com`
-- Port: `587` (or `465` for SSL)
-- Username: Your Gmail address
-- Password: App password (not your regular password)
-- From: Your Gmail address
-- Use SSL/TLS: Checked for port 465, unchecked for port 587
-
-**Note:** For Gmail, you'll need to generate an "App Password" in your Google Account settings.
+## Development
+- `npm run dev` — start dev server
+- `npm run build` — production build
+- `npm run lint` — lint code
+- `npm test` — run tests
## Troubleshooting
@@ -133,76 +63,21 @@ After deploying Firestore rules, you can configure SMTP settings through the adm
- Run `npm run lint` for code linting
- Run `npm test` for running tests
-## User Guides
-
-### Adding Prayers
-
-1. Click the "Add" tab
-2. Fill in the date, type (prayer/praise), text, and optional journal
-3. Set reminder frequency
-4. Click "Add Prayer"
-
-### Managing Prayers
-
-- **Active**: View current prayers
-- **Archived**: View archived prayers
-- **Search**: Search by content
-- **Community**: View shared prayers
-
-### Profile Customization
-
-- Click "Profile" to update avatar, theme, and categories
-
-### Admin Features
-
-- Configure SMTP for emails
-- Manage admin users
-- View analytics
+## Usage
+- Compose: write your prayer, pick a date, and optionally select or type a "Pray For" name.
+- Praying: mark prayers as prayed today.
+- Calendar: click a day to view/edit its entry.
+- Reflections: search and filter past prayers.
+- Answered: review answered prayers with details.
+- About: set reminder time, test notifications, and read "What's New".
## Getting Started
-## Email Configuration
-
-### Option 1: EmailJS (Free, Recommended)
-
-EmailJS provides free email sending without server-side SMTP configuration:
-
-1. **Sign up at [emailjs.com](https://www.emailjs.com/)**
-2. **Create an Email Service** (Gmail, Outlook, etc.)
-3. **Create an Email Template** with these variables:
- ```
- Subject: New {{prayer_type}} added - {{reminder_frequency}} reminders enabled
-
- Hi {{to_name}},
-
- A new prayer has been added to your Prayer App:
-
- Date: {{prayer_date}}
- Type: {{prayer_type}}
- Text: {{prayer_text}}
- Journal: {{prayer_journal}}
- Praying for: {{praying_for}}
-
- Reminder Frequency: {{reminder_frequency}}
-
- Best regards,
- Prayer App
- ```
-4. **Get your credentials**:
- - Service ID
- - Template ID
- - Public Key
-
-5. **Add to `.env.local`**:
- ```
- NEXT_PUBLIC_EMAILJS_SERVICE_ID=your_service_id
- NEXT_PUBLIC_EMAILJS_TEMPLATE_ID=your_template_id
- NEXT_PUBLIC_EMAILJS_PUBLIC_KEY=your_public_key
- ```
-
-### Option 2: SMTP (Advanced)
-
-For custom SMTP servers, configure settings in the admin panel after deploying Firestore rules.
+## Build & Deploy
+```bash
+npm run build
+```
+Deploy to Vercel or your preferred platform.
## Getting Started
@@ -244,103 +119,10 @@ npm run build
Deploy to Vercel or any platform supporting Next.js PWAs.
-## Docker Deployment
-
-The app includes Docker support for containerized deployment with automated email scheduling.
-
-### Build and Push Docker Image
-
-1. **Using the automated script (Windows):**
- ```powershell
- .\build-and-push-docker.ps1
- ```
- This script will:
- - Build the Docker image
- - Prompt for your GitHub Personal Access Token
- - Login to GitHub Container Registry
- - Push the image
-
-2. **Manual build and push:**
- ```bash
- # Build the image
- docker build -t ghcr.io/nmemmert/prayer-app:latest .
-
- # Login to GitHub Container Registry
- echo $GITHUB_TOKEN | docker login ghcr.io -u nmemmert --password-stdin
-
- # Push the image
- docker push ghcr.io/nmemmert/prayer-app:latest
- ```
-
-### Run with Docker Compose
-
-```bash
-docker-compose up -d
-```
-
-The container includes:
-- Automated email scheduling (runs every 5 minutes)
-- Health checks
-- Production-optimized Next.js build
-- Cron jobs for scheduled tasks
-
-### Environment Variables
-
-Set these environment variables for the container:
-
-```bash
-# Firebase Admin SDK (required for server-side operations)
-FIREBASE_PROJECT_ID=your_project_id
-FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n..."
-FIREBASE_CLIENT_EMAIL=your_service_account_email
-
-# EmailJS (for client-side emails)
-NEXT_PUBLIC_EMAILJS_SERVICE_ID=your_service_id
-NEXT_PUBLIC_EMAILJS_TEMPLATE_ID=your_template_id
-NEXT_PUBLIC_EMAILJS_PUBLIC_KEY=your_public_key
-```
+## Docker (optional)
+Basic containerization is available. Remove or adapt scripts as needed.
## Learn More
-
-To learn more about Next.js, take a look at the following resources:
-
-- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
-- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
-
-You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
-
-## Deploy on Vercel
-
-The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
-
-Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
-
-## Docker Support
-
-This application is fully containerized and ready for deployment.
-
-### Building Locally
-
-```bash
-# Build the Docker image
-docker build -t prayer-app .
-
-# Run the container
-docker run -p 3000:3000 prayer-app
+- [Next.js Documentation](https://nextjs.org/docs)
+- [Tailwind CSS](https://tailwindcss.com/docs)
```
-
-### Using Docker Compose
-
-```bash
-# Start the application
-docker-compose up -d
-
-# Stop the application
-docker-compose down
-```
-
-### GitHub Container Registry
-
-The application includes GitHub Actions CI/CD that automatically builds and pushes Docker images to GitHub Container Registry on every push to the main branch.
-
-Images are available at: `ghcr.io/[your-username]/prayer-app:latest`
diff --git a/icon.jpeg b/icon.jpeg
new file mode 100644
index 0000000..5931411
Binary files /dev/null and b/icon.jpeg differ
diff --git a/package.json b/package.json
index 737851d..5ed2b86 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "prayer",
- "version": "0.1.0",
+ "version": "0.2.0",
"private": true,
"type": "module",
"scripts": {
@@ -9,7 +9,9 @@
"start": "next start",
"lint": "eslint",
"test": "jest",
- "test:watch": "jest --watch"
+ "test:watch": "jest --watch",
+ "version:minor": "npm version minor && node scripts/sync-version.js",
+ "version:major": "npm version major && node scripts/sync-version.js"
},
"dependencies": {
"@dataconnect/generated": "file:src/dataconnect-generated",
diff --git a/public/sw.js b/public/sw.js
index 096f09a..4a50bcd 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -1 +1 @@
-if(!self.define){let e,s={};const a=(a,n)=>(a=new URL(a+".js",n).href,s[a]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=a,e.onload=s,document.head.appendChild(e)}else e=a,importScripts(a),s()}).then(()=>{let e=s[a];if(!e)throw new Error(`Module ${a} didn’t register its module`);return e}));self.define=(n,i)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let c={};const r=e=>a(e,t),o={module:{uri:t},exports:c,require:r};s[t]=Promise.all(n.map(e=>o[e]||r(e))).then(e=>(i(...e),c))}}define(["./workbox-4754cb34"],function(e){"use strict";importScripts(),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/static/Q7AkDBdJDGBRANlWHHBOE/_buildManifest.js",revision:"44608b7d53e1eefcd54452c125d6300e"},{url:"/_next/static/Q7AkDBdJDGBRANlWHHBOE/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/chunks/172-71074a5b97c03327.js",revision:"71074a5b97c03327"},{url:"/_next/static/chunks/462-1ebca3834d195b35.js",revision:"1ebca3834d195b35"},{url:"/_next/static/chunks/497-22b66697988d7f92.js",revision:"22b66697988d7f92"},{url:"/_next/static/chunks/4bd1b696-b7529f9b306ab05d.js",revision:"b7529f9b306ab05d"},{url:"/_next/static/chunks/512-8d2b9de6fda25175.js",revision:"8d2b9de6fda25175"},{url:"/_next/static/chunks/7508b87c-97d7ae290120d64a.js",revision:"97d7ae290120d64a"},{url:"/_next/static/chunks/995.a093d11da39e25d3.js",revision:"a093d11da39e25d3"},{url:"/_next/static/chunks/app/_global-error/page-2af6c391bfa86449.js",revision:"2af6c391bfa86449"},{url:"/_next/static/chunks/app/_not-found/page-f75e7ecfbcc8dd77.js",revision:"f75e7ecfbcc8dd77"},{url:"/_next/static/chunks/app/admin/page-be774d9a59b52f3a.js",revision:"be774d9a59b52f3a"},{url:"/_next/static/chunks/app/api/debug-smtp/route-2af6c391bfa86449.js",revision:"2af6c391bfa86449"},{url:"/_next/static/chunks/app/api/send-email/route-2af6c391bfa86449.js",revision:"2af6c391bfa86449"},{url:"/_next/static/chunks/app/api/send-scheduled-emails/route-2af6c391bfa86449.js",revision:"2af6c391bfa86449"},{url:"/_next/static/chunks/app/layout-7f3802a6a2a60f7d.js",revision:"7f3802a6a2a60f7d"},{url:"/_next/static/chunks/app/page-4907ce715213b04e.js",revision:"4907ce715213b04e"},{url:"/_next/static/chunks/app/profile/page-a3a0e0cd20fcb9d6.js",revision:"a3a0e0cd20fcb9d6"},{url:"/_next/static/chunks/app/testimonies/page-ed6616cfcac7de9d.js",revision:"ed6616cfcac7de9d"},{url:"/_next/static/chunks/dd8162e8-580391f39948d3f7.js",revision:"580391f39948d3f7"},{url:"/_next/static/chunks/framework-292291387d6b2e39.js",revision:"292291387d6b2e39"},{url:"/_next/static/chunks/main-5e55aa9a8969fd08.js",revision:"5e55aa9a8969fd08"},{url:"/_next/static/chunks/main-app-1640b70ccc992192.js",revision:"1640b70ccc992192"},{url:"/_next/static/chunks/next/dist/client/components/builtin/app-error-2af6c391bfa86449.js",revision:"2af6c391bfa86449"},{url:"/_next/static/chunks/next/dist/client/components/builtin/forbidden-2af6c391bfa86449.js",revision:"2af6c391bfa86449"},{url:"/_next/static/chunks/next/dist/client/components/builtin/global-error-647436fbb68222b8.js",revision:"647436fbb68222b8"},{url:"/_next/static/chunks/next/dist/client/components/builtin/not-found-2af6c391bfa86449.js",revision:"2af6c391bfa86449"},{url:"/_next/static/chunks/next/dist/client/components/builtin/unauthorized-2af6c391bfa86449.js",revision:"2af6c391bfa86449"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-6b6a51bcb4cc4744.js",revision:"6b6a51bcb4cc4744"},{url:"/_next/static/css/bff524108e499dd4.css",revision:"bff524108e499dd4"},{url:"/_next/static/media/4cf2300e9c8272f7-s.p.woff2",revision:"18bae71b1e1b2bb25321090a3b563103"},{url:"/_next/static/media/747892c23ea88013-s.woff2",revision:"a0761690ccf4441ace5cec893b82d4ab"},{url:"/_next/static/media/8d697b304b401681-s.woff2",revision:"cc728f6c0adb04da0dfcb0fc436a8ae5"},{url:"/_next/static/media/93f479601ee12b01-s.p.woff2",revision:"da83d5f06d825c5ae65b7cca706cb312"},{url:"/_next/static/media/9610d9e46709d722-s.woff2",revision:"7b7c0ef93df188a852344fc272fc096b"},{url:"/_next/static/media/ba015fad6dcf6784-s.woff2",revision:"8ea4f719af3312a055caf09f34c89a77"},{url:"/file.svg",revision:"d09f95206c3fa0bb9bd9fefabfd0ea71"},{url:"/firebase-messaging-sw.js",revision:"396ffcf00a63da03dfc03d78d4815494"},{url:"/globe.svg",revision:"2aaafa6a49b6563925fe440891e32717"},{url:"/icon-192x192.png",revision:"bbbd7efc0e31088ae0a3aeede7608534"},{url:"/icon-512x512.png",revision:"400538d76556a3a8ee4fd3dfda7bc092"},{url:"/locales/en/common.json",revision:"41332abbb11f0b33b3eb308a5448f8d6"},{url:"/manifest.json",revision:"6dbf565091414e98afd54d79577bb36d"},{url:"/next.svg",revision:"8e061864f388b47f33a1c3780831193e"},{url:"/vercel.svg",revision:"c0af2f507b369b085b35ef4bbe3bcf1e"},{url:"/window.svg",revision:"a2760511c65806022ad20adf74370ff3"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:s,event:a,state:n})=>s&&"opaqueredirect"===s.type?new Response(s.body,{status:200,statusText:"OK",headers:s.headers}):s}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp4)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:json|xml|csv)$/i,new e.NetworkFirst({cacheName:"static-data-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>{if(!(self.origin===e.origin))return!1;const s=e.pathname;return!s.startsWith("/api/auth/")&&!!s.startsWith("/api/")},new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>{if(!(self.origin===e.origin))return!1;return!e.pathname.startsWith("/api/")},new e.NetworkFirst({cacheName:"others",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>!(self.origin===e.origin),new e.NetworkFirst({cacheName:"cross-origin",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:3600})]}),"GET")});
+if(!self.define){let e,s={};const a=(a,n)=>(a=new URL(a+".js",n).href,s[a]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=a,e.onload=s,document.head.appendChild(e)}else e=a,importScripts(a),s()}).then(()=>{let e=s[a];if(!e)throw new Error(`Module ${a} didn’t register its module`);return e}));self.define=(n,i)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let c={};const o=e=>a(e,t),r={module:{uri:t},exports:c,require:o};s[t]=Promise.all(n.map(e=>r[e]||o(e))).then(e=>(i(...e),c))}}define(["./workbox-4754cb34"],function(e){"use strict";importScripts(),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/static/RTXxDDjYPkCiNJ6w2qWiq/_buildManifest.js",revision:"f4d58a725936c94635b35b3f85e2a011"},{url:"/_next/static/RTXxDDjYPkCiNJ6w2qWiq/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/chunks/4bd1b696-b7529f9b306ab05d.js",revision:"b7529f9b306ab05d"},{url:"/_next/static/chunks/512-8d2b9de6fda25175.js",revision:"8d2b9de6fda25175"},{url:"/_next/static/chunks/7508b87c-dfcb7bb31c18c36f.js",revision:"dfcb7bb31c18c36f"},{url:"/_next/static/chunks/884-27d75bba95dbe213.js",revision:"27d75bba95dbe213"},{url:"/_next/static/chunks/995.a093d11da39e25d3.js",revision:"a093d11da39e25d3"},{url:"/_next/static/chunks/app/_global-error/page-48c825fda4ae9c10.js",revision:"48c825fda4ae9c10"},{url:"/_next/static/chunks/app/_not-found/page-f75e7ecfbcc8dd77.js",revision:"f75e7ecfbcc8dd77"},{url:"/_next/static/chunks/app/layout-5ea58a1aa81454ad.js",revision:"5ea58a1aa81454ad"},{url:"/_next/static/chunks/app/page-2a6751ff1202edf3.js",revision:"2a6751ff1202edf3"},{url:"/_next/static/chunks/dd8162e8-580391f39948d3f7.js",revision:"580391f39948d3f7"},{url:"/_next/static/chunks/framework-292291387d6b2e39.js",revision:"292291387d6b2e39"},{url:"/_next/static/chunks/main-5e55aa9a8969fd08.js",revision:"5e55aa9a8969fd08"},{url:"/_next/static/chunks/main-app-1640b70ccc992192.js",revision:"1640b70ccc992192"},{url:"/_next/static/chunks/next/dist/client/components/builtin/app-error-48c825fda4ae9c10.js",revision:"48c825fda4ae9c10"},{url:"/_next/static/chunks/next/dist/client/components/builtin/forbidden-48c825fda4ae9c10.js",revision:"48c825fda4ae9c10"},{url:"/_next/static/chunks/next/dist/client/components/builtin/global-error-647436fbb68222b8.js",revision:"647436fbb68222b8"},{url:"/_next/static/chunks/next/dist/client/components/builtin/not-found-48c825fda4ae9c10.js",revision:"48c825fda4ae9c10"},{url:"/_next/static/chunks/next/dist/client/components/builtin/unauthorized-48c825fda4ae9c10.js",revision:"48c825fda4ae9c10"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-6240b410a738ea5b.js",revision:"6240b410a738ea5b"},{url:"/_next/static/css/c0d6f2e4d70567da.css",revision:"c0d6f2e4d70567da"},{url:"/_next/static/media/4cf2300e9c8272f7-s.p.woff2",revision:"18bae71b1e1b2bb25321090a3b563103"},{url:"/_next/static/media/747892c23ea88013-s.woff2",revision:"a0761690ccf4441ace5cec893b82d4ab"},{url:"/_next/static/media/8d697b304b401681-s.woff2",revision:"cc728f6c0adb04da0dfcb0fc436a8ae5"},{url:"/_next/static/media/93f479601ee12b01-s.p.woff2",revision:"da83d5f06d825c5ae65b7cca706cb312"},{url:"/_next/static/media/9610d9e46709d722-s.woff2",revision:"7b7c0ef93df188a852344fc272fc096b"},{url:"/_next/static/media/ba015fad6dcf6784-s.woff2",revision:"8ea4f719af3312a055caf09f34c89a77"},{url:"/file.svg",revision:"d09f95206c3fa0bb9bd9fefabfd0ea71"},{url:"/firebase-messaging-sw.js",revision:"396ffcf00a63da03dfc03d78d4815494"},{url:"/globe.svg",revision:"2aaafa6a49b6563925fe440891e32717"},{url:"/icon-192x192.png",revision:"bbbd7efc0e31088ae0a3aeede7608534"},{url:"/icon-512x512.png",revision:"400538d76556a3a8ee4fd3dfda7bc092"},{url:"/locales/en/common.json",revision:"41332abbb11f0b33b3eb308a5448f8d6"},{url:"/manifest.json",revision:"6dbf565091414e98afd54d79577bb36d"},{url:"/next.svg",revision:"8e061864f388b47f33a1c3780831193e"},{url:"/vercel.svg",revision:"c0af2f507b369b085b35ef4bbe3bcf1e"},{url:"/window.svg",revision:"a2760511c65806022ad20adf74370ff3"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:s,event:a,state:n})=>s&&"opaqueredirect"===s.type?new Response(s.body,{status:200,statusText:"OK",headers:s.headers}):s}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp4)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:json|xml|csv)$/i,new e.NetworkFirst({cacheName:"static-data-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>{if(!(self.origin===e.origin))return!1;const s=e.pathname;return!s.startsWith("/api/auth/")&&!!s.startsWith("/api/")},new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>{if(!(self.origin===e.origin))return!1;return!e.pathname.startsWith("/api/")},new e.NetworkFirst({cacheName:"others",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>!(self.origin===e.origin),new e.NetworkFirst({cacheName:"cross-origin",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:3600})]}),"GET")});
diff --git a/scripts/sync-version.js b/scripts/sync-version.js
new file mode 100644
index 0000000..390f1e0
--- /dev/null
+++ b/scripts/sync-version.js
@@ -0,0 +1,14 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const path = require('path');
+
+const pkgPath = path.join(__dirname, '..', 'package.json');
+const versionPath = path.join(__dirname, '..', 'src', 'lib', 'version.ts');
+
+const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
+const version = pkg.version;
+
+const content = `export const APP_VERSION = '${version}';\n\nexport const WHATS_NEW_BRIEF: string[] = [\n 'Redesign: unified dark theme and gradients',\n 'Icons: lucide-react across headings and nav',\n 'Cleanup: removed legacy email routes and pages',\n 'UX: Pray For suggestions in compose tab'\n];\n`;
+
+fs.writeFileSync(versionPath, content, 'utf8');
+console.log('Synced version to src/lib/version.ts:', version);
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 56f97c6..5710baf 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -123,14 +123,14 @@ export default function Home() {
setPrayers(prev =>
prev.map(p => (p.id === editingPrayer.id ? { ...p, ...prayerData } : p))
);
- toast.success('Prayer updated! 🙏');
+ toast.success('Prayer updated!');
setEditingPrayer(null);
} else {
// Add new prayer
const docRef = await addDoc(collection(db, 'prayers'), prayerData);
const newPrayer: Prayer = { id: docRef.id, ...prayerData };
setPrayers(prev => [...prev, newPrayer]);
- toast.success('Prayer saved! 🙏');
+ toast.success('Prayer saved!');
}
// Reset form
@@ -176,7 +176,7 @@ export default function Home() {
setPrayers(prev =>
prev.map(p => (p.id === prayerId ? { ...p, lastPrayedFor: today } : p))
);
- toast.success('Prayed! 🙏');
+ toast.success('Prayed!');
} catch (error) {
console.error('Error marking prayed:', error);
toast.error('Failed to update');
@@ -200,6 +200,7 @@ export default function Home() {
+
Preparing your sacred space...
@@ -247,6 +248,7 @@ export default function Home() { onTextChange={setText} prayFor={prayFor} onPrayForChange={setPrayFor} + prayForOptions={Array.from(new Set(prayers.map(p => p.prayFor).filter(Boolean))) as string[]} answered={answered} onAnsweredChange={setAnswered} answerDetails={answerDetails} diff --git a/src/components/AboutPage.tsx b/src/components/AboutPage.tsx index 520cbf8..a98c576 100644 --- a/src/components/AboutPage.tsx +++ b/src/components/AboutPage.tsx @@ -2,7 +2,8 @@ import React, { useState } from 'react'; import { useAuth } from '@/context/AuthContext'; -import { Info } from 'lucide-react'; +import { Info, Smartphone, BellRing } from 'lucide-react'; +import { APP_VERSION, WHATS_NEW_BRIEF } from '@/lib/version'; interface AboutPageProps { userEmail?: string; @@ -102,7 +103,7 @@ export default function AboutPage({ userEmail }: AboutPageProps) { const testNotificationNow = async () => { if (supportsWebPush && Notification.permission === 'granted') { - new Notification('🙏 Test Prayer Reminder', { + new Notification('Test Prayer Reminder', { body: 'This is a test notification. Your reminders are working!', icon: '/icon-192x192.png', badge: '/icon-192x192.png', @@ -139,7 +140,7 @@ export default function AboutPage({ userEmail }: AboutPageProps) { if (timeUntilNotification > 0) { setTimeout(() => { if ('Notification' in window && Notification.permission === 'granted' && notificationsEnabled) { - new Notification('🙏 Time to Pray', { + new Notification('Time to Pray', { body: 'Take a moment to connect with God and pray.', icon: '/icon-192x192.png', badge: '/icon-192x192.png', @@ -168,6 +169,7 @@ export default function AboutPage({ userEmail }: AboutPageProps) {📱 iOS: In-App Reminders
+iOS Safari doesn't support push notifications. Enable reminders to get a message when you open the app.
Haver v1.0
+Haver v{APP_VERSION}
A simple, private space for your daily prayers and reflections.