feat: add app icon branding to all remaining page headers
- CalendarView header now displays icon - ReflectionsList header now displays icon - AnsweredPrayers header now displays icon - ThingsToPrayFor header now displays icon Completes visual identity unification across all main screens
This commit is contained in:
1 parent
74686cece1
commit
aa3a35bc4a
17 files changed
+234
-330
No files matched your search
@@ -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
|
||||||
@@ -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
|
## 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)
|
## Versioning Policy
|
||||||
- Add prayers and praises with date organization
|
- Uses semantic versioning: major.minor
|
||||||
- Journal entries for each prayer
|
- Minor: UI/UX tweaks, non-breaking features
|
||||||
- "Pray For" field to specify a person for each prayer
|
- Major: structural changes or breaking updates
|
||||||
- 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
|
|
||||||
|
|
||||||
## Setup
|
## 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/
|
## Notes
|
||||||
2. Enable Authentication with Email/Password provider
|
- Email sending routes have been removed in the redesign.
|
||||||
3. Enable Firestore Database
|
- Email verification remains available via Firebase.
|
||||||
4. Get your Firebase config and replace the placeholder in `src/lib/firebase.ts`
|
|
||||||
|
|
||||||
Users can now register accounts directly in the app!
|
## Firestore Rules
|
||||||
|
Deploy [firestore.rules](firestore.rules) to secure user data.
|
||||||
|
|
||||||
## Admin Setup
|
## Development
|
||||||
|
- `npm run dev` — start dev server
|
||||||
Admin access is restricted to specific email addresses. Admins can manage the admin email list directly in the app through the Admin panel.
|
- `npm run build` — production build
|
||||||
|
- `npm run lint` — lint code
|
||||||
**To add/remove admin emails:**
|
- `npm test` — run tests
|
||||||
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.
|
|
||||||
|
|
||||||
## Troubleshooting
|
## 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 run lint` for code linting
|
||||||
- Run `npm test` for running tests
|
- Run `npm test` for running tests
|
||||||
|
|
||||||
## User Guides
|
## Usage
|
||||||
|
- Compose: write your prayer, pick a date, and optionally select or type a "Pray For" name.
|
||||||
### Adding Prayers
|
- Praying: mark prayers as prayed today.
|
||||||
|
- Calendar: click a day to view/edit its entry.
|
||||||
1. Click the "Add" tab
|
- Reflections: search and filter past prayers.
|
||||||
2. Fill in the date, type (prayer/praise), text, and optional journal
|
- Answered: review answered prayers with details.
|
||||||
3. Set reminder frequency
|
- About: set reminder time, test notifications, and read "What's New".
|
||||||
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
|
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
## Email Configuration
|
## Build & Deploy
|
||||||
|
```bash
|
||||||
### Option 1: EmailJS (Free, Recommended)
|
npm run build
|
||||||
|
```
|
||||||
EmailJS provides free email sending without server-side SMTP configuration:
|
Deploy to Vercel or your preferred platform.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
@@ -244,103 +119,10 @@ npm run build
|
|||||||
|
|
||||||
Deploy to Vercel or any platform supporting Next.js PWAs.
|
Deploy to Vercel or any platform supporting Next.js PWAs.
|
||||||
|
|
||||||
## Docker Deployment
|
## Docker (optional)
|
||||||
|
Basic containerization is available. Remove or adapt scripts as needed.
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
## Learn More
|
## Learn More
|
||||||
|
- [Next.js Documentation](https://nextjs.org/docs)
|
||||||
To learn more about Next.js, take a look at the following resources:
|
- [Tailwind CSS](https://tailwindcss.com/docs)
|
||||||
|
|
||||||
- [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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 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`
|
|
||||||
+4
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "prayer",
|
"name": "prayer",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -9,7 +9,9 @@
|
|||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
"test": "jest",
|
"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": {
|
"dependencies": {
|
||||||
"@dataconnect/generated": "file:src/dataconnect-generated",
|
"@dataconnect/generated": "file:src/dataconnect-generated",
|
||||||
|
|||||||
+1
-1
@@ -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.cleanupOutdatedCaLine truncated
|
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-assLine truncated
|
||||||
@@ -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);
|
||||||
+5
-3
@@ -123,14 +123,14 @@ export default function Home() {
|
|||||||
setPrayers(prev =>
|
setPrayers(prev =>
|
||||||
prev.map(p => (p.id === editingPrayer.id ? { ...p, ...prayerData } : p))
|
prev.map(p => (p.id === editingPrayer.id ? { ...p, ...prayerData } : p))
|
||||||
);
|
);
|
||||||
toast.success('Prayer updated! 🙏');
|
toast.success('Prayer updated!');
|
||||||
setEditingPrayer(null);
|
setEditingPrayer(null);
|
||||||
} else {
|
} else {
|
||||||
// Add new prayer
|
// Add new prayer
|
||||||
const docRef = await addDoc(collection(db, 'prayers'), prayerData);
|
const docRef = await addDoc(collection(db, 'prayers'), prayerData);
|
||||||
const newPrayer: Prayer = { id: docRef.id, ...prayerData };
|
const newPrayer: Prayer = { id: docRef.id, ...prayerData };
|
||||||
setPrayers(prev => [...prev, newPrayer]);
|
setPrayers(prev => [...prev, newPrayer]);
|
||||||
toast.success('Prayer saved! 🙏');
|
toast.success('Prayer saved!');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset form
|
// Reset form
|
||||||
@@ -176,7 +176,7 @@ export default function Home() {
|
|||||||
setPrayers(prev =>
|
setPrayers(prev =>
|
||||||
prev.map(p => (p.id === prayerId ? { ...p, lastPrayedFor: today } : p))
|
prev.map(p => (p.id === prayerId ? { ...p, lastPrayedFor: today } : p))
|
||||||
);
|
);
|
||||||
toast.success('Prayed! 🙏');
|
toast.success('Prayed!');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error marking prayed:', error);
|
console.error('Error marking prayed:', error);
|
||||||
toast.error('Failed to update');
|
toast.error('Failed to update');
|
||||||
@@ -200,6 +200,7 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<img src="/icon-192x192.png" alt="Haver icon" className="w-12 h-12 mx-auto mb-3 rounded" />
|
||||||
<h1 className="text-2xl font-bold text-white mb-2 animate-pulse">Haver</h1>
|
<h1 className="text-2xl font-bold text-white mb-2 animate-pulse">Haver</h1>
|
||||||
<p className="text-gray-400 text-lg mb-4">Preparing your sacred space...</p>
|
<p className="text-gray-400 text-lg mb-4">Preparing your sacred space...</p>
|
||||||
|
|
||||||
@@ -247,6 +248,7 @@ export default function Home() {
|
|||||||
onTextChange={setText}
|
onTextChange={setText}
|
||||||
prayFor={prayFor}
|
prayFor={prayFor}
|
||||||
onPrayForChange={setPrayFor}
|
onPrayForChange={setPrayFor}
|
||||||
|
prayForOptions={Array.from(new Set(prayers.map(p => p.prayFor).filter(Boolean))) as string[]}
|
||||||
answered={answered}
|
answered={answered}
|
||||||
onAnsweredChange={setAnswered}
|
onAnsweredChange={setAnswered}
|
||||||
answerDetails={answerDetails}
|
answerDetails={answerDetails}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useAuth } from '@/context/AuthContext';
|
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 {
|
interface AboutPageProps {
|
||||||
userEmail?: string;
|
userEmail?: string;
|
||||||
@@ -102,7 +103,7 @@ export default function AboutPage({ userEmail }: AboutPageProps) {
|
|||||||
|
|
||||||
const testNotificationNow = async () => {
|
const testNotificationNow = async () => {
|
||||||
if (supportsWebPush && Notification.permission === 'granted') {
|
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!',
|
body: 'This is a test notification. Your reminders are working!',
|
||||||
icon: '/icon-192x192.png',
|
icon: '/icon-192x192.png',
|
||||||
badge: '/icon-192x192.png',
|
badge: '/icon-192x192.png',
|
||||||
@@ -139,7 +140,7 @@ export default function AboutPage({ userEmail }: AboutPageProps) {
|
|||||||
if (timeUntilNotification > 0) {
|
if (timeUntilNotification > 0) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if ('Notification' in window && Notification.permission === 'granted' && notificationsEnabled) {
|
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.',
|
body: 'Take a moment to connect with God and pray.',
|
||||||
icon: '/icon-192x192.png',
|
icon: '/icon-192x192.png',
|
||||||
badge: '/icon-192x192.png',
|
badge: '/icon-192x192.png',
|
||||||
@@ -168,6 +169,7 @@ export default function AboutPage({ userEmail }: AboutPageProps) {
|
|||||||
<div className="max-w-md mx-auto">
|
<div className="max-w-md mx-auto">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="text-center mb-8">
|
<div className="text-center mb-8">
|
||||||
|
<img src="/icon-192x192.png" alt="Haver icon" className="w-12 h-12 mx-auto mb-3 rounded" />
|
||||||
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent mb-2">
|
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent mb-2">
|
||||||
<span className="inline-flex items-center gap-2 justify-center">
|
<span className="inline-flex items-center gap-2 justify-center">
|
||||||
<Info className="w-7 h-7 text-blue-400" />
|
<Info className="w-7 h-7 text-blue-400" />
|
||||||
@@ -264,7 +266,7 @@ export default function AboutPage({ userEmail }: AboutPageProps) {
|
|||||||
{/* Platform Support Info */}
|
{/* Platform Support Info */}
|
||||||
{platform === 'ios' && (
|
{platform === 'ios' && (
|
||||||
<div className="bg-blue-900 border border-blue-700 text-blue-200 px-3 py-2 rounded-lg text-xs">
|
<div className="bg-blue-900 border border-blue-700 text-blue-200 px-3 py-2 rounded-lg text-xs">
|
||||||
<p className="font-medium mb-1">📱 iOS: In-App Reminders</p>
|
<p className="font-medium mb-1 inline-flex items-center gap-1.5"><Smartphone className="w-4 h-4" /> iOS: In-App Reminders</p>
|
||||||
<p>iOS Safari doesn't support push notifications. Enable reminders to get a message when you open the app.</p>
|
<p>iOS Safari doesn't support push notifications. Enable reminders to get a message when you open the app.</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -319,7 +321,7 @@ export default function AboutPage({ userEmail }: AboutPageProps) {
|
|||||||
onClick={testNotificationNow}
|
onClick={testNotificationNow}
|
||||||
className="w-full px-3 py-2 bg-slate-700 hover:bg-slate-600 text-slate-200 text-sm font-medium rounded-lg transition-colors border border-slate-600"
|
className="w-full px-3 py-2 bg-slate-700 hover:bg-slate-600 text-slate-200 text-sm font-medium rounded-lg transition-colors border border-slate-600"
|
||||||
>
|
>
|
||||||
📬 Test Notification
|
<span className="inline-flex items-center gap-1.5"><BellRing className="w-4 h-4" /> Test Notification</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -331,11 +333,21 @@ export default function AboutPage({ userEmail }: AboutPageProps) {
|
|||||||
<div className="border-b border-slate-700 pb-6">
|
<div className="border-b border-slate-700 pb-6">
|
||||||
<h2 className="text-lg font-semibold text-white mb-4">App Info</h2>
|
<h2 className="text-lg font-semibold text-white mb-4">App Info</h2>
|
||||||
<div className="space-y-2 text-sm text-slate-400">
|
<div className="space-y-2 text-sm text-slate-400">
|
||||||
<p>Haver v1.0</p>
|
<p>Haver v{APP_VERSION}</p>
|
||||||
<p>A simple, private space for your daily prayers and reflections.</p>
|
<p>A simple, private space for your daily prayers and reflections.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* What's New Section */}
|
||||||
|
<div className="border-b border-slate-700 pb-6">
|
||||||
|
<h2 className="text-lg font-semibold text-white mb-4">What's New</h2>
|
||||||
|
<ul className="list-disc list-inside text-sm text-slate-300 space-y-1">
|
||||||
|
{WHATS_NEW_BRIEF.map((item) => (
|
||||||
|
<li key={item}>{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Logout and Delete Section */}
|
{/* Logout and Delete Section */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ interface AnsweredPrayersProps {
|
|||||||
onEdit?: (prayer: Prayer) => void;
|
onEdit?: (prayer: Prayer) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
import { CheckCircle2 } from 'lucide-react';
|
import { CheckCircle2, Sparkles } from 'lucide-react';
|
||||||
|
|
||||||
export default function AnsweredPrayers({ prayers, onEdit }: AnsweredPrayersProps) {
|
export default function AnsweredPrayers({ prayers, onEdit }: AnsweredPrayersProps) {
|
||||||
const answeredPrayers = prayers.filter(p => p.answered);
|
const answeredPrayers = prayers.filter(p => p.answered);
|
||||||
@@ -32,6 +32,7 @@ export default function AnsweredPrayers({ prayers, onEdit }: AnsweredPrayersProp
|
|||||||
|
|
||||||
<div className="max-w-md mx-auto">
|
<div className="max-w-md mx-auto">
|
||||||
<div className="text-center mb-8">
|
<div className="text-center mb-8">
|
||||||
|
<img src="/icon-192x192.png" alt="Haver icon" className="w-12 h-12 mx-auto mb-3 rounded" />
|
||||||
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent mb-2">
|
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent mb-2">
|
||||||
<span className="inline-flex items-center gap-2 justify-center">
|
<span className="inline-flex items-center gap-2 justify-center">
|
||||||
<CheckCircle2 className="w-7 h-7 text-blue-400" />
|
<CheckCircle2 className="w-7 h-7 text-blue-400" />
|
||||||
@@ -76,7 +77,7 @@ export default function AnsweredPrayers({ prayers, onEdit }: AnsweredPrayersProp
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<div className="text-6xl mb-4 animate-bounce">🙏</div>
|
<Sparkles className="w-12 h-12 mb-4 text-blue-400 animate-pulse mx-auto" />
|
||||||
<p className="text-white font-semibold mb-2">No answered prayers yet</p>
|
<p className="text-white font-semibold mb-2">No answered prayers yet</p>
|
||||||
<p className="text-sm text-slate-400">
|
<p className="text-sm text-slate-400">
|
||||||
Mark prayers as answered in the Compose tab to see them here
|
Mark prayers as answered in the Compose tab to see them here
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export default function BottomNav({ activeTab, onTabChange }: BottomNavProps) {
|
|||||||
<nav className="hidden md:fixed md:top-0 md:left-0 md:right-0 md:bg-slate-800/80 md:backdrop-blur-md md:border-b md:border-slate-700 md:z-50 md:flex md:items-center md:justify-between md:px-6 md:py-3 md:shadow-sm">
|
<nav className="hidden md:fixed md:top-0 md:left-0 md:right-0 md:bg-slate-800/80 md:backdrop-blur-md md:border-b md:border-slate-700 md:z-50 md:flex md:items-center md:justify-between md:px-6 md:py-3 md:shadow-sm">
|
||||||
<div className="flex items-center gap-8">
|
<div className="flex items-center gap-8">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<HandHeart className="w-5 h-5 text-blue-400" />
|
<img src="/icon-192x192.png" alt="Haver icon" className="w-6 h-6 rounded" />
|
||||||
<h1 className="text-2xl font-bold text-white">Haver</h1>
|
<h1 className="text-2xl font-bold text-white">Haver</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
|
|||||||
@@ -66,8 +66,7 @@ export default function CalendarView({ prayers, onDayClick }: CalendarViewProps)
|
|||||||
|
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="text-center mb-8">
|
<div className="text-center mb-8"> <img src="/icon-192x192.png" alt="Haver icon" className="w-12 h-12 mx-auto mb-3 rounded" /> <h1 className="text-4xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent mb-2">
|
||||||
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent mb-2">
|
|
||||||
<span className="inline-flex items-center gap-2 justify-center">
|
<span className="inline-flex items-center gap-2 justify-center">
|
||||||
<CalendarIcon className="w-7 h-7 text-blue-400" />
|
<CalendarIcon className="w-7 h-7 text-blue-400" />
|
||||||
Prayer Journal
|
Prayer Journal
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { PenSquare } from 'lucide-react';
|
import { PenSquare, Calendar as CalendarIcon, HandHeart, CheckCircle2, Sparkles, Save, Loader2 } from 'lucide-react';
|
||||||
|
|
||||||
interface DailyEntryProps {
|
interface DailyEntryProps {
|
||||||
date: string;
|
date: string;
|
||||||
@@ -11,6 +11,7 @@ interface DailyEntryProps {
|
|||||||
onTextChange: (text: string) => void;
|
onTextChange: (text: string) => void;
|
||||||
prayFor: string;
|
prayFor: string;
|
||||||
onPrayForChange: (prayFor: string) => void;
|
onPrayForChange: (prayFor: string) => void;
|
||||||
|
prayForOptions?: string[];
|
||||||
answered?: boolean;
|
answered?: boolean;
|
||||||
onAnsweredChange?: (answered: boolean) => void;
|
onAnsweredChange?: (answered: boolean) => void;
|
||||||
answerDetails?: string;
|
answerDetails?: string;
|
||||||
@@ -27,6 +28,7 @@ export default function DailyEntry({
|
|||||||
onTextChange,
|
onTextChange,
|
||||||
prayFor,
|
prayFor,
|
||||||
onPrayForChange,
|
onPrayForChange,
|
||||||
|
prayForOptions = [],
|
||||||
answered = false,
|
answered = false,
|
||||||
onAnsweredChange,
|
onAnsweredChange,
|
||||||
answerDetails = '',
|
answerDetails = '',
|
||||||
@@ -50,6 +52,7 @@ export default function DailyEntry({
|
|||||||
<div className="max-w-2xl mx-auto">
|
<div className="max-w-2xl mx-auto">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-12">
|
||||||
|
<img src="/icon-192x192.png" alt="Haver icon" className="w-12 h-12 mx-auto mb-3 rounded" />
|
||||||
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent mb-2">
|
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent mb-2">
|
||||||
<span className="inline-flex items-center gap-2 justify-center">
|
<span className="inline-flex items-center gap-2 justify-center">
|
||||||
<PenSquare className="w-7 h-7 text-blue-400" />
|
<PenSquare className="w-7 h-7 text-blue-400" />
|
||||||
@@ -70,7 +73,7 @@ export default function DailyEntry({
|
|||||||
<form onSubmit={onSubmit} className="space-y-6">
|
<form onSubmit={onSubmit} className="space-y-6">
|
||||||
{/* Date Input */}
|
{/* Date Input */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-semibold text-slate-300 mb-3">📅 Date</label>
|
<label className="block text-sm font-semibold text-slate-300 mb-3 inline-flex items-center gap-1.5"><CalendarIcon className="w-4 h-4" /> Date</label>
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
value={date}
|
value={date}
|
||||||
@@ -81,20 +84,28 @@ export default function DailyEntry({
|
|||||||
|
|
||||||
{/* Pray For */}
|
{/* Pray For */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-semibold text-slate-300 mb-3">🙏 Praying For (Optional)</label>
|
<label className="block text-sm font-semibold text-slate-300 mb-3 inline-flex items-center gap-1.5"><HandHeart className="w-4 h-4" /> Praying For (Optional)</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={prayFor}
|
value={prayFor}
|
||||||
onChange={(e) => onPrayForChange(e.target.value)}
|
onChange={(e) => onPrayForChange(e.target.value)}
|
||||||
placeholder="Person, situation, or concern..."
|
placeholder="Person, situation, or concern..."
|
||||||
|
list="prayfor-options"
|
||||||
className="w-full px-4 py-3 rounded-xl bg-slate-800/60 backdrop-blur-xl border border-slate-700/50 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all"
|
className="w-full px-4 py-3 rounded-xl bg-slate-800/60 backdrop-blur-xl border border-slate-700/50 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all"
|
||||||
/>
|
/>
|
||||||
|
{prayForOptions.length > 0 && (
|
||||||
|
<datalist id="prayfor-options">
|
||||||
|
{prayForOptions.map((opt) => (
|
||||||
|
<option key={opt} value={opt} />
|
||||||
|
))}
|
||||||
|
</datalist>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Prayer Text */}
|
{/* Prayer Text */}
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<label className="block text-sm font-semibold text-slate-300">💬 Your Prayer</label>
|
<label className="block text-sm font-semibold text-slate-300 inline-flex items-center gap-1.5"><PenSquare className="w-4 h-4" /> Your Prayer</label>
|
||||||
<span className="text-xs text-slate-500">{wordCount} words</span>
|
<span className="text-xs text-slate-500">{wordCount} words</span>
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
@@ -116,15 +127,15 @@ export default function DailyEntry({
|
|||||||
onChange={(e) => onAnsweredChange?.(e.target.checked)}
|
onChange={(e) => onAnsweredChange?.(e.target.checked)}
|
||||||
className="w-5 h-5 rounded border-slate-600 bg-slate-700 text-green-500 focus:ring-green-500 cursor-pointer"
|
className="w-5 h-5 rounded border-slate-600 bg-slate-700 text-green-500 focus:ring-green-500 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
<span className="font-semibold text-slate-300">✅ Mark as answered</span>
|
<span className="font-semibold text-slate-300 inline-flex items-center gap-1.5"><CheckCircle2 className="w-4 h-4" /> Mark as answered</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Answer Details */}
|
{/* Answer Details */}
|
||||||
{answered && (
|
{answered && (
|
||||||
<div className="bg-green-900/20 backdrop-blur-xl rounded-xl p-4 border border-green-500/30 animate-in fade-in-50 duration-300">
|
<div className="bg-green-900/20 backdrop-blur-xl rounded-xl p-4 border border-green-500/30 animate-in fade-in-50 duration-300">
|
||||||
<label htmlFor="answerDetails" className="block text-sm font-semibold text-green-400 mb-3">
|
<label htmlFor="answerDetails" className="block text-sm font-semibold text-green-400 mb-3 inline-flex items-center gap-1.5">
|
||||||
✨ How was it answered?
|
<Sparkles className="w-4 h-4" /> How was it answered?
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
id="answerDetails"
|
id="answerDetails"
|
||||||
@@ -143,7 +154,19 @@ export default function DailyEntry({
|
|||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="w-full bg-gradient-to-r from-blue-600 via-blue-500 to-purple-600 hover:from-blue-700 hover:via-blue-600 hover:to-purple-700 disabled:from-slate-600 disabled:to-slate-600 text-white font-bold py-4 px-6 rounded-xl transition-all duration-200 disabled:cursor-not-allowed shadow-lg hover:shadow-2xl transform hover:scale-105 border border-blue-400/30 disabled:border-slate-400/30"
|
className="w-full bg-gradient-to-r from-blue-600 via-blue-500 to-purple-600 hover:from-blue-700 hover:via-blue-600 hover:to-purple-700 disabled:from-slate-600 disabled:to-slate-600 text-white font-bold py-4 px-6 rounded-xl transition-all duration-200 disabled:cursor-not-allowed shadow-lg hover:shadow-2xl transform hover:scale-105 border border-blue-400/30 disabled:border-slate-400/30"
|
||||||
>
|
>
|
||||||
{loading ? '⏳ Saving...' : isEditing ? '📝 Update Prayer' : '💾 Save Prayer'}
|
{loading ? (
|
||||||
|
<span className="inline-flex items-center gap-2">
|
||||||
|
<Loader2 className="w-5 h-5 animate-spin" /> Saving...
|
||||||
|
</span>
|
||||||
|
) : isEditing ? (
|
||||||
|
<span className="inline-flex items-center gap-2">
|
||||||
|
<PenSquare className="w-5 h-5" /> Update Prayer
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="inline-flex items-center gap-2">
|
||||||
|
<Save className="w-5 h-5" /> Save Prayer
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+22
-20
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import Clock from './Clock';
|
import Clock from './Clock';
|
||||||
|
import { HandHeart, CheckCircle2, PenSquare, BookOpen, Sparkles, Flame, BarChart3, MessageSquare } from 'lucide-react';
|
||||||
|
|
||||||
interface Prayer {
|
interface Prayer {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -130,6 +131,7 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect
|
|||||||
|
|
||||||
{/* Header with Personalized Greeting */}
|
{/* Header with Personalized Greeting */}
|
||||||
<div className="max-w-md mx-auto mb-12 text-center">
|
<div className="max-w-md mx-auto mb-12 text-center">
|
||||||
|
<img src="/icon-192x192.png" alt="Haver icon" className="w-12 h-12 mx-auto mb-3 rounded" />
|
||||||
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent mb-3">
|
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent mb-3">
|
||||||
{getGreeting()}, {getFirstName()}!
|
{getGreeting()}, {getFirstName()}!
|
||||||
</h1>
|
</h1>
|
||||||
@@ -139,8 +141,8 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect
|
|||||||
You have <span className="font-bold text-blue-400">{activePrayerCount}</span> {activePrayerCount === 1 ? 'thing' : 'things'} that need prayer today.
|
You have <span className="font-bold text-blue-400">{activePrayerCount}</span> {activePrayerCount === 1 ? 'thing' : 'things'} that need prayer today.
|
||||||
</p>
|
</p>
|
||||||
{prayedTodayCount > 0 && (
|
{prayedTodayCount > 0 && (
|
||||||
<p className="mt-1">
|
<p className="mt-1 inline-flex items-center gap-2">
|
||||||
You've prayed for <span className="font-bold text-green-400">{prayedTodayCount}</span> of them! 🙏
|
<HandHeart className="w-5 h-5 text-green-400" /> You've prayed for <span className="font-bold text-green-400">{prayedTodayCount}</span> of them!
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -163,7 +165,7 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect
|
|||||||
<div className="text-blue-200 text-sm font-semibold mb-2">Today's Prayers</div>
|
<div className="text-blue-200 text-sm font-semibold mb-2">Today's Prayers</div>
|
||||||
<div className="text-5xl font-bold text-white">{todayCount}</div>
|
<div className="text-5xl font-bold text-white">{todayCount}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-4xl opacity-30">📿</div>
|
<HandHeart className="w-10 h-10 text-white opacity-20" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -185,14 +187,14 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect
|
|||||||
|
|
||||||
{/* Answered Prayers */}
|
{/* Answered Prayers */}
|
||||||
<div className="bg-slate-800/60 backdrop-blur-xl rounded-xl p-4 border border-slate-700/50 hover:border-slate-600 transition-all">
|
<div className="bg-slate-800/60 backdrop-blur-xl rounded-xl p-4 border border-slate-700/50 hover:border-slate-600 transition-all">
|
||||||
<div className="text-slate-400 text-xs font-semibold mb-2">✅ Answered</div>
|
<div className="text-slate-400 text-xs font-semibold mb-2 inline-flex items-center gap-1.5"><CheckCircle2 className="w-4 h-4" /> Answered</div>
|
||||||
<div className="text-3xl font-bold text-green-400">{answeredPrayers}</div>
|
<div className="text-3xl font-bold text-green-400">{answeredPrayers}</div>
|
||||||
<div className="text-xs text-slate-500 mt-2">{answeredPercentage}% answered</div>
|
<div className="text-xs text-slate-500 mt-2">{answeredPercentage}% answered</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Streak */}
|
{/* Streak */}
|
||||||
<div className="bg-slate-800/60 backdrop-blur-xl rounded-xl p-4 border border-slate-700/50 hover:border-slate-600 transition-all">
|
<div className="bg-slate-800/60 backdrop-blur-xl rounded-xl p-4 border border-slate-700/50 hover:border-slate-600 transition-all">
|
||||||
<div className="text-slate-400 text-xs font-semibold mb-2">🔥 Streak</div>
|
<div className="text-slate-400 text-xs font-semibold mb-2 inline-flex items-center gap-1.5"><Flame className="w-4 h-4" /> Streak</div>
|
||||||
<div className="text-3xl font-bold text-orange-400">{streak}</div>
|
<div className="text-3xl font-bold text-orange-400">{streak}</div>
|
||||||
<div className="text-xs text-slate-500 mt-2">consecutive days</div>
|
<div className="text-xs text-slate-500 mt-2">consecutive days</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -202,14 +204,14 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect
|
|||||||
<div className="grid grid-cols-2 gap-4 mb-8">
|
<div className="grid grid-cols-2 gap-4 mb-8">
|
||||||
{/* Avg Prayer Length */}
|
{/* Avg Prayer Length */}
|
||||||
<div className="bg-purple-900/20 backdrop-blur-xl rounded-xl p-4 border border-purple-500/30">
|
<div className="bg-purple-900/20 backdrop-blur-xl rounded-xl p-4 border border-purple-500/30">
|
||||||
<div className="text-purple-300 text-xs font-semibold mb-2">📊 Avg Length</div>
|
<div className="text-purple-300 text-xs font-semibold mb-2 inline-flex items-center gap-1.5"><BarChart3 className="w-4 h-4" /> Avg Length</div>
|
||||||
<div className="text-2xl font-bold text-purple-200">{avgPrayerLength}</div>
|
<div className="text-2xl font-bold text-purple-200">{avgPrayerLength}</div>
|
||||||
<div className="text-xs text-purple-400 mt-2">characters</div>
|
<div className="text-xs text-purple-400 mt-2">characters</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Most Prayed For */}
|
{/* Most Prayed For */}
|
||||||
<div className="bg-pink-900/20 backdrop-blur-xl rounded-xl p-4 border border-pink-500/30">
|
<div className="bg-pink-900/20 backdrop-blur-xl rounded-xl p-4 border border-pink-500/30">
|
||||||
<div className="text-pink-300 text-xs font-semibold mb-2">💝 Most Prayed For</div>
|
<div className="text-pink-300 text-xs font-semibold mb-2 inline-flex items-center gap-1.5"><HandHeart className="w-4 h-4" /> Most Prayed For</div>
|
||||||
{mostPrayedFor ? (
|
{mostPrayedFor ? (
|
||||||
<>
|
<>
|
||||||
<div className="text-sm font-bold text-pink-200 line-clamp-1">{mostPrayedFor[0]}</div>
|
<div className="text-sm font-bold text-pink-200 line-clamp-1">{mostPrayedFor[0]}</div>
|
||||||
@@ -231,7 +233,7 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect
|
|||||||
onClick={onStartPraying}
|
onClick={onStartPraying}
|
||||||
className="w-full bg-gradient-to-r from-blue-600 via-blue-500 to-purple-600 hover:from-blue-700 hover:via-blue-600 hover:to-purple-700 text-white font-bold py-4 px-6 rounded-xl transition-all shadow-lg hover:shadow-2xl transform hover:scale-105 border border-blue-400/30 flex items-center justify-center gap-2"
|
className="w-full bg-gradient-to-r from-blue-600 via-blue-500 to-purple-600 hover:from-blue-700 hover:via-blue-600 hover:to-purple-700 text-white font-bold py-4 px-6 rounded-xl transition-all shadow-lg hover:shadow-2xl transform hover:scale-105 border border-blue-400/30 flex items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
<span className="text-xl">✍️</span>
|
<PenSquare className="w-5 h-5" />
|
||||||
<span>Start New Prayer</span>
|
<span>Start New Prayer</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -243,7 +245,7 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect
|
|||||||
return (
|
return (
|
||||||
<div className="max-w-md mx-auto mb-12">
|
<div className="max-w-md mx-auto mb-12">
|
||||||
<h2 className="text-lg font-bold text-amber-400 mb-4 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-amber-400 mb-4 flex items-center gap-2">
|
||||||
<span>🙏</span> Things to Pray For
|
<HandHeart className="w-5 h-5" /> Things to Pray For
|
||||||
</h2>
|
</h2>
|
||||||
<div className="bg-amber-900/20 backdrop-blur-xl rounded-xl p-5 border border-amber-500/30 hover:border-amber-400/50 transition-all">
|
<div className="bg-amber-900/20 backdrop-blur-xl rounded-xl p-5 border border-amber-500/30 hover:border-amber-400/50 transition-all">
|
||||||
<div className="text-3xl font-bold text-amber-300 mb-2">{pendingPrayers.length}</div>
|
<div className="text-3xl font-bold text-amber-300 mb-2">{pendingPrayers.length}</div>
|
||||||
@@ -269,7 +271,7 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect
|
|||||||
{recentPrayers.length > 0 && (
|
{recentPrayers.length > 0 && (
|
||||||
<div className="max-w-md mx-auto mb-12">
|
<div className="max-w-md mx-auto mb-12">
|
||||||
<h2 className="text-lg font-bold text-white mb-4 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-white mb-4 flex items-center gap-2">
|
||||||
<span>📖</span> Recent Prayers
|
<BookOpen className="w-5 h-5" /> Recent Prayers
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{recentPrayers.map((prayer, idx) => (
|
{recentPrayers.map((prayer, idx) => (
|
||||||
@@ -279,11 +281,11 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect
|
|||||||
className="w-full text-left group bg-slate-800/60 backdrop-blur-xl rounded-xl p-4 shadow-md border border-slate-700/50 hover:border-blue-500/50 transition-all hover:shadow-xl hover:bg-slate-800/80 transform hover:translate-x-1"
|
className="w-full text-left group bg-slate-800/60 backdrop-blur-xl rounded-xl p-4 shadow-md border border-slate-700/50 hover:border-blue-500/50 transition-all hover:shadow-xl hover:bg-slate-800/80 transform hover:translate-x-1"
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-2xl">{prayer.answered ? '✅' : '📝'}</div>
|
<div className="text-2xl">{prayer.answered ? <CheckCircle2 className="w-6 h-6 text-green-400" /> : <PenSquare className="w-6 h-6 text-blue-400" />}</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
{prayer.prayFor && (
|
{prayer.prayFor && (
|
||||||
<div className="text-xs font-bold text-blue-400 mb-1 uppercase tracking-wider">
|
<div className="text-xs font-bold text-blue-400 mb-1 uppercase tracking-wider inline-flex items-center gap-1.5">
|
||||||
🙏 {prayer.prayFor}
|
<HandHeart className="w-3.5 h-3.5" /> {prayer.prayFor}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<p className="text-sm text-slate-300 line-clamp-2 group-hover:text-slate-200">{prayer.text}</p>
|
<p className="text-sm text-slate-300 line-clamp-2 group-hover:text-slate-200">{prayer.text}</p>
|
||||||
@@ -303,7 +305,7 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect
|
|||||||
{recentAnswered.length > 0 && (
|
{recentAnswered.length > 0 && (
|
||||||
<div className="max-w-md mx-auto mb-12">
|
<div className="max-w-md mx-auto mb-12">
|
||||||
<h2 className="text-lg font-bold text-green-400 mb-4 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-green-400 mb-4 flex items-center gap-2">
|
||||||
<span>✨</span> Recently Answered
|
<Sparkles className="w-5 h-5" /> Recently Answered
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{recentAnswered.map((prayer) => (
|
{recentAnswered.map((prayer) => (
|
||||||
@@ -312,17 +314,17 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect
|
|||||||
className="bg-green-900/20 backdrop-blur-xl rounded-xl p-4 border border-green-500/30 hover:border-green-400/50 transition-all"
|
className="bg-green-900/20 backdrop-blur-xl rounded-xl p-4 border border-green-500/30 hover:border-green-400/50 transition-all"
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-2xl">✅</div>
|
<CheckCircle2 className="w-6 h-6 text-green-400" />
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
{prayer.prayFor && (
|
{prayer.prayFor && (
|
||||||
<div className="text-xs font-bold text-green-300 mb-1 uppercase tracking-wider">
|
<div className="text-xs font-bold text-green-300 mb-1 uppercase tracking-wider inline-flex items-center gap-1.5">
|
||||||
🙏 {prayer.prayFor}
|
<HandHeart className="w-3.5 h-3.5" /> {prayer.prayFor}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<p className="text-sm text-slate-300 line-clamp-2">{prayer.text}</p>
|
<p className="text-sm text-slate-300 line-clamp-2">{prayer.text}</p>
|
||||||
{prayer.answerDetails && (
|
{prayer.answerDetails && (
|
||||||
<p className="text-xs text-green-400 mt-2 italic">
|
<p className="text-xs text-green-400 mt-2 italic inline-flex items-center gap-1.5">
|
||||||
💬 "{prayer.answerDetails.substring(0, 60)}..."
|
<MessageSquare className="w-4 h-4" /> "{prayer.answerDetails.substring(0, 60)}..."
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<div className="text-xs text-slate-500 mt-2">
|
<div className="text-xs text-slate-500 mt-2">
|
||||||
@@ -339,7 +341,7 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect
|
|||||||
{/* Empty State */}
|
{/* Empty State */}
|
||||||
{prayers.length === 0 && (
|
{prayers.length === 0 && (
|
||||||
<div className="max-w-md mx-auto text-center py-12">
|
<div className="max-w-md mx-auto text-center py-12">
|
||||||
<div className="text-6xl mb-4 animate-bounce">🙏</div>
|
<Sparkles className="w-12 h-12 mb-4 text-blue-400 animate-pulse mx-auto" />
|
||||||
<p className="text-white font-semibold mb-2">Start Your Prayer Journey</p>
|
<p className="text-white font-semibold mb-2">Start Your Prayer Journey</p>
|
||||||
<p className="text-slate-400 mb-6">
|
<p className="text-slate-400 mb-6">
|
||||||
Create your first prayer entry and begin tracking your spiritual journey.
|
Create your first prayer entry and begin tracking your spiritual journey.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useMemo } from 'react';
|
import React, { useState, useMemo } from 'react';
|
||||||
import { BookOpen } from 'lucide-react';
|
import { BookOpen, Calendar as CalendarIcon, CheckCircle2, Hourglass, XCircle, HandHeart } from 'lucide-react';
|
||||||
|
|
||||||
interface Prayer {
|
interface Prayer {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -80,6 +80,7 @@ export default function PrayerList({ prayers, onPrayerClick, onEdit }: PrayerLis
|
|||||||
<div className="max-w-2xl mx-auto">
|
<div className="max-w-2xl mx-auto">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="text-center mb-8">
|
<div className="text-center mb-8">
|
||||||
|
<img src="/icon-192x192.png" alt="Haver icon" className="w-12 h-12 mx-auto mb-3 rounded" />
|
||||||
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent mb-2">
|
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent mb-2">
|
||||||
<span className="inline-flex items-center gap-2 justify-center">
|
<span className="inline-flex items-center gap-2 justify-center">
|
||||||
<BookOpen className="w-7 h-7 text-blue-400" />
|
<BookOpen className="w-7 h-7 text-blue-400" />
|
||||||
@@ -111,7 +112,7 @@ export default function PrayerList({ prayers, onPrayerClick, onEdit }: PrayerLis
|
|||||||
: 'bg-slate-700 text-slate-300 hover:bg-slate-600'
|
: 'bg-slate-700 text-slate-300 hover:bg-slate-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
📅 Newest
|
<span className="inline-flex items-center gap-1.5"><CalendarIcon className="w-4 h-4" /> Newest</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setSortOrder('oldest')}
|
onClick={() => setSortOrder('oldest')}
|
||||||
@@ -121,7 +122,7 @@ export default function PrayerList({ prayers, onPrayerClick, onEdit }: PrayerLis
|
|||||||
: 'bg-slate-700 text-slate-300 hover:bg-slate-600'
|
: 'bg-slate-700 text-slate-300 hover:bg-slate-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
📅 Oldest
|
<span className="inline-flex items-center gap-1.5"><CalendarIcon className="w-4 h-4" /> Oldest</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -145,7 +146,7 @@ export default function PrayerList({ prayers, onPrayerClick, onEdit }: PrayerLis
|
|||||||
: 'bg-slate-700 text-slate-300 hover:bg-slate-600'
|
: 'bg-slate-700 text-slate-300 hover:bg-slate-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
✅ Answered
|
<span className="inline-flex items-center gap-1.5"><CheckCircle2 className="w-4 h-4" /> Answered</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setFilterAnswered('unanswered')}
|
onClick={() => setFilterAnswered('unanswered')}
|
||||||
@@ -155,7 +156,7 @@ export default function PrayerList({ prayers, onPrayerClick, onEdit }: PrayerLis
|
|||||||
: 'bg-slate-700 text-slate-300 hover:bg-slate-600'
|
: 'bg-slate-700 text-slate-300 hover:bg-slate-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
⏳ Waiting
|
<span className="inline-flex items-center gap-1.5"><Hourglass className="w-4 h-4" /> Waiting</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -165,7 +166,7 @@ export default function PrayerList({ prayers, onPrayerClick, onEdit }: PrayerLis
|
|||||||
{filtered.length === 0 ? (
|
{filtered.length === 0 ? (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<p className="text-slate-400 text-lg">
|
<p className="text-slate-400 text-lg">
|
||||||
{searchTerm || filterAnswered !== 'all' ? '❌ No prayers match your filters.' : '📝 No prayers yet. Start praying!'}
|
{searchTerm || filterAnswered !== 'all' ? 'No prayers match your filters.' : 'No prayers yet. Start praying!'}
|
||||||
</p>
|
</p>
|
||||||
{searchTerm && (
|
{searchTerm && (
|
||||||
<button
|
<button
|
||||||
@@ -206,12 +207,12 @@ export default function PrayerList({ prayers, onPrayerClick, onEdit }: PrayerLis
|
|||||||
>
|
>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
{prayer.answered && (
|
{prayer.answered && (
|
||||||
<span className="text-2xl">✅</span>
|
<CheckCircle2 className="w-6 h-6 text-green-400" />
|
||||||
)}
|
)}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
{prayer.prayFor && (
|
{prayer.prayFor && (
|
||||||
<p className="text-sm font-medium text-blue-400 mb-2">
|
<p className="text-sm font-medium text-blue-400 mb-2 inline-flex items-center gap-1.5">
|
||||||
🙏 {prayer.prayFor}
|
<HandHeart className="w-4 h-4" /> {prayer.prayFor}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<p className="text-slate-200 line-clamp-3">
|
<p className="text-slate-200 line-clamp-3">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { BellRing, X } from 'lucide-react';
|
||||||
|
|
||||||
interface ReminderBannerProps {
|
interface ReminderBannerProps {
|
||||||
userEmail?: string;
|
userEmail?: string;
|
||||||
@@ -58,7 +59,7 @@ export default function ReminderBanner({ userEmail }: ReminderBannerProps) {
|
|||||||
<div className="fixed top-0 left-0 right-0 z-40 p-4">
|
<div className="fixed top-0 left-0 right-0 z-40 p-4">
|
||||||
<div className="max-w-md mx-auto bg-gradient-to-r from-blue-600 to-blue-500 rounded-lg shadow-lg p-4 animate-in slide-in-from-top">
|
<div className="max-w-md mx-auto bg-gradient-to-r from-blue-600 to-blue-500 rounded-lg shadow-lg p-4 animate-in slide-in-from-top">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-2xl mt-1">🙏</div>
|
<BellRing className="w-6 h-6 text-white mt-1" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="font-semibold text-white">Time to Pray</h3>
|
<h3 className="font-semibold text-white">Time to Pray</h3>
|
||||||
<p className="text-blue-100 text-sm mt-1">
|
<p className="text-blue-100 text-sm mt-1">
|
||||||
@@ -69,7 +70,7 @@ export default function ReminderBanner({ userEmail }: ReminderBannerProps) {
|
|||||||
onClick={() => setShowBanner(false)}
|
onClick={() => setShowBanner(false)}
|
||||||
className="text-blue-200 hover:text-white transition-colors mt-1"
|
className="text-blue-200 hover:text-white transition-colors mt-1"
|
||||||
>
|
>
|
||||||
✕
|
<X className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 flex gap-2">
|
<div className="mt-3 flex gap-2">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { HandHeart, Sparkles } from 'lucide-react';
|
import { HandHeart, Sparkles, CheckCircle2, Target } from 'lucide-react';
|
||||||
|
|
||||||
interface Prayer {
|
interface Prayer {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -48,6 +48,7 @@ export default function ThingsToPrayFor({ prayers, onEdit, onMarkPrayed }: Thing
|
|||||||
<div className="max-w-2xl mx-auto">
|
<div className="max-w-2xl mx-auto">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-12">
|
||||||
|
<img src="/icon-192x192.png" alt="Haver icon" className="w-12 h-12 mx-auto mb-3 rounded" />
|
||||||
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent mb-2">
|
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent mb-2">
|
||||||
<span className="inline-flex items-center gap-2 justify-center">
|
<span className="inline-flex items-center gap-2 justify-center">
|
||||||
<HandHeart className="w-7 h-7 text-blue-400" />
|
<HandHeart className="w-7 h-7 text-blue-400" />
|
||||||
@@ -73,8 +74,8 @@ export default function ThingsToPrayFor({ prayers, onEdit, onMarkPrayed }: Thing
|
|||||||
<div key={topic}>
|
<div key={topic}>
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-4">
|
||||||
<div className="h-1 flex-1 bg-gradient-to-r from-blue-500/50 to-transparent rounded"></div>
|
<div className="h-1 flex-1 bg-gradient-to-r from-blue-500/50 to-transparent rounded"></div>
|
||||||
<h2 className="text-xl font-bold text-blue-400 px-4">
|
<h2 className="text-xl font-bold text-blue-400 px-4 inline-flex items-center gap-2">
|
||||||
🎯 {topic}
|
<Target className="w-5 h-5" /> {topic}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="h-1 flex-1 bg-gradient-to-l from-blue-500/50 to-transparent rounded"></div>
|
<div className="h-1 flex-1 bg-gradient-to-l from-blue-500/50 to-transparent rounded"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -114,7 +115,15 @@ export default function ThingsToPrayFor({ prayers, onEdit, onMarkPrayed }: Thing
|
|||||||
: 'bg-blue-900/40 text-blue-300 border border-blue-500/30 hover:bg-blue-900/60 hover:border-blue-400/50'
|
: 'bg-blue-900/40 text-blue-300 border border-blue-500/30 hover:bg-blue-900/60 hover:border-blue-400/50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{prayedToday ? '✓ Prayed Today' : '🙏 Mark Prayed'}
|
{prayedToday ? (
|
||||||
|
<span className="inline-flex items-center gap-1.5">
|
||||||
|
<CheckCircle2 className="w-4 h-4" /> Prayed Today
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="inline-flex items-center gap-1.5">
|
||||||
|
<HandHeart className="w-4 h-4" /> Mark Prayed
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => onEdit(prayer)}
|
onClick={() => onEdit(prayer)}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export const APP_VERSION = '0.2.0';
|
||||||
|
|
||||||
|
export const WHATS_NEW_BRIEF: string[] = [
|
||||||
|
'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'
|
||||||
|
];
|
||||||
Reference in new issue
Block a user