chore: remove legacy routes and email APIs
This commit is contained in:
1 parent
e53e8088bb
commit
74686cece1
6 files changed
-1035
No files matched your search
@@ -1,33 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { useAuth } from '@/context/AuthContext';
|
|
||||||
import Admin from '@/components/Admin';
|
|
||||||
import Navbar from '@/components/Navbar';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
export default function AdminPage() {
|
|
||||||
const { user, isAdmin } = useAuth();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!user) {
|
|
||||||
router.push('/');
|
|
||||||
} else if (!isAdmin) {
|
|
||||||
router.push('/');
|
|
||||||
}
|
|
||||||
}, [user, isAdmin, router]);
|
|
||||||
|
|
||||||
if (!user || !isAdmin) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-black to-gray-900 text-white">
|
|
||||||
<Navbar />
|
|
||||||
<div className="max-w-6xl mx-auto p-6">
|
|
||||||
<Admin />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// Debug API endpoint to check SMTP config server-side
|
|
||||||
// This will work even without client authentication
|
|
||||||
|
|
||||||
import { db } from '@/lib/firebase';
|
|
||||||
import { doc, getDoc } from 'firebase/firestore';
|
|
||||||
|
|
||||||
export async function GET() {
|
|
||||||
try {
|
|
||||||
const configDoc = await getDoc(doc(db, 'config', 'smtp'));
|
|
||||||
|
|
||||||
if (configDoc.exists()) {
|
|
||||||
const config = configDoc.data();
|
|
||||||
return Response.json({
|
|
||||||
found: true,
|
|
||||||
host: config?.host,
|
|
||||||
port: config?.port,
|
|
||||||
secure: config?.secure,
|
|
||||||
user: config?.user ? 'SET' : 'MISSING',
|
|
||||||
pass: config?.pass ? 'SET' : 'MISSING',
|
|
||||||
from: config?.from,
|
|
||||||
hasAllFields: !!(config?.host && config?.port && config?.user && config?.pass && config?.from)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return Response.json({ found: false, error: 'SMTP config not found' });
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
return Response.json({ error: error.message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,325 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
|
||||||
import { doc, getDoc } from 'firebase/firestore';
|
|
||||||
import { db } from '@/lib/firebase';
|
|
||||||
import nodemailer from 'nodemailer';
|
|
||||||
|
|
||||||
function generatePrayerEmailHTML(message: string, subject: string) {
|
|
||||||
// Parse the message to extract prayer details
|
|
||||||
const lines = message.split('\n');
|
|
||||||
let currentPrayer = null;
|
|
||||||
const prayers = [];
|
|
||||||
let summarySection = false;
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.includes('New prayer added') || line.includes('New praise added')) {
|
|
||||||
currentPrayer = { type: '', date: '', text: '', journal: '', prayFor: '' };
|
|
||||||
} else if (line.startsWith('Date: ')) {
|
|
||||||
if (currentPrayer) currentPrayer.date = line.replace('Date: ', '');
|
|
||||||
} else if (line.startsWith('Praying for: ')) {
|
|
||||||
if (currentPrayer) currentPrayer.prayFor = line.replace('Praying for: ', '');
|
|
||||||
} else if (line.startsWith('Journal: ')) {
|
|
||||||
if (currentPrayer) currentPrayer.journal = line.replace('Journal: ', '');
|
|
||||||
} else if (line.includes('--- Active Prayers Summary ---')) {
|
|
||||||
summarySection = true;
|
|
||||||
prayers.push({ type: 'summary-header', text: 'Active Prayers Summary' });
|
|
||||||
} else if (summarySection && line.includes(' - ')) {
|
|
||||||
// Parse summary lines like "2025-01-15 - PRAYER: prayer text"
|
|
||||||
const match = line.match(/(\d{4}-\d{2}-\d{2}) - (PRAYER|PRAISE): (.+)/);
|
|
||||||
if (match) {
|
|
||||||
prayers.push({
|
|
||||||
type: 'summary-item',
|
|
||||||
date: match[1],
|
|
||||||
prayerType: match[2].toLowerCase(),
|
|
||||||
text: match[3]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (currentPrayer && !currentPrayer.text && line.trim()) {
|
|
||||||
currentPrayer.text = line;
|
|
||||||
prayers.push(currentPrayer);
|
|
||||||
currentPrayer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const html = `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>${subject}</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: #333;
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
color: white;
|
|
||||||
padding: 30px 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.header h1 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.header .subtitle {
|
|
||||||
margin: 8px 0 0 0;
|
|
||||||
opacity: 0.9;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
padding: 30px 20px;
|
|
||||||
}
|
|
||||||
.prayer-card {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-left: 4px solid #667eea;
|
|
||||||
padding: 20px;
|
|
||||||
margin: 20px 0;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
.prayer-type {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #667eea;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 14px;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
|
||||||
.prayer-date {
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.prayer-text {
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 1.6;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
.prayer-journal {
|
|
||||||
background: #fff;
|
|
||||||
border-left: 3px solid #764ba2;
|
|
||||||
padding: 15px;
|
|
||||||
margin: 15px 0;
|
|
||||||
font-style: italic;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
.pray-for {
|
|
||||||
background: #e8f4fd;
|
|
||||||
padding: 10px 15px;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin: 10px 0;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #2c5282;
|
|
||||||
}
|
|
||||||
.summary-section {
|
|
||||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
||||||
color: white;
|
|
||||||
padding: 20px;
|
|
||||||
margin: 20px 0;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
.summary-section h3 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
.summary-item {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
padding: 15px;
|
|
||||||
margin: 10px 0;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
.summary-item .date {
|
|
||||||
color: #ffed4e;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
.summary-item .prayer-type {
|
|
||||||
color: #ffed4e;
|
|
||||||
font-size: 12px;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
.footer {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.verse {
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin: 20px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.verse blockquote {
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 16px;
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
}
|
|
||||||
.verse cite {
|
|
||||||
font-size: 14px;
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="header">
|
|
||||||
<h1>🙏 Prayer App</h1>
|
|
||||||
<div class="subtitle">${subject}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
${prayers.map(prayer => {
|
|
||||||
if (prayer.type === 'summary-header') {
|
|
||||||
return `
|
|
||||||
<div class="summary-section">
|
|
||||||
<h3>📚 ${prayer.text}</h3>
|
|
||||||
<p>Here are all your active prayers and praises:</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
} else if (prayer.type === 'summary-item') {
|
|
||||||
return `
|
|
||||||
<div class="summary-item">
|
|
||||||
<div class="date">${prayer.date ? new Date(prayer.date).toLocaleDateString() : 'Unknown Date'}</div>
|
|
||||||
<div class="prayer-type">${prayer.prayerType}</div>
|
|
||||||
<div class="prayer-text">${prayer.text}</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
return `
|
|
||||||
<div class="prayer-card">
|
|
||||||
<div class="prayer-type">${prayer.type}</div>
|
|
||||||
<div class="prayer-date">${prayer.date ? new Date(prayer.date).toLocaleDateString() : 'Unknown Date'}</div>
|
|
||||||
<div class="prayer-text">${prayer.text}</div>
|
|
||||||
${prayer.prayFor ? `<div class="pray-for">🙋 Praying for: ${prayer.prayFor}</div>` : ''}
|
|
||||||
${prayer.journal && prayer.journal !== 'None' ? `<div class="prayer-journal">📝 Journal: ${prayer.journal}</div>` : ''}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}).join('')}
|
|
||||||
|
|
||||||
<div class="verse">
|
|
||||||
<blockquote>"Pray without ceasing"</blockquote>
|
|
||||||
<cite>— 1 Thessalonians 5:17</cite>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<p>Sent with ❤️ from your Prayer App</p>
|
|
||||||
<p>This email helps you stay connected to your spiritual journey.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>`;
|
|
||||||
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
|
||||||
try {
|
|
||||||
const { email, subject, message } = await request.json();
|
|
||||||
|
|
||||||
console.log('📧 Attempting to send email to:', email);
|
|
||||||
|
|
||||||
// Get SMTP config from Firestore
|
|
||||||
console.log('📧 Fetching SMTP config from Firestore...');
|
|
||||||
const configDoc = await getDoc(doc(db, 'config', 'smtp'));
|
|
||||||
|
|
||||||
if (!configDoc.exists()) {
|
|
||||||
console.error('❌ SMTP configuration not found in Firestore');
|
|
||||||
return NextResponse.json({
|
|
||||||
error: 'SMTP configuration not found',
|
|
||||||
details: 'Please configure SMTP settings in the Admin panel at /admin'
|
|
||||||
}, { status: 500 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const smtpConfig = configDoc.data();
|
|
||||||
console.log('✅ SMTP config found, host:', smtpConfig?.host);
|
|
||||||
|
|
||||||
// Validate SMTP config
|
|
||||||
if (!smtpConfig?.host || !smtpConfig?.user || !smtpConfig?.pass || !smtpConfig?.from) {
|
|
||||||
console.error('❌ SMTP config incomplete:', {
|
|
||||||
hasHost: !!smtpConfig?.host,
|
|
||||||
hasUser: !!smtpConfig?.user,
|
|
||||||
hasPass: !!smtpConfig?.pass,
|
|
||||||
hasFrom: !!smtpConfig?.from
|
|
||||||
});
|
|
||||||
return NextResponse.json({
|
|
||||||
error: 'SMTP configuration incomplete',
|
|
||||||
details: 'Missing required SMTP fields. Please check Admin panel.'
|
|
||||||
}, { status: 500 });
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('📧 Creating email transporter...');
|
|
||||||
|
|
||||||
// Create transporter with dynamic config
|
|
||||||
const transporter = nodemailer.createTransport({
|
|
||||||
host: smtpConfig.host,
|
|
||||||
port: smtpConfig.port,
|
|
||||||
secure: smtpConfig.secure,
|
|
||||||
auth: {
|
|
||||||
user: smtpConfig.user,
|
|
||||||
pass: smtpConfig.pass,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('📧 Sending email...');
|
|
||||||
|
|
||||||
// Send email
|
|
||||||
const result = await transporter.sendMail({
|
|
||||||
from: smtpConfig.from,
|
|
||||||
to: email,
|
|
||||||
subject,
|
|
||||||
text: message,
|
|
||||||
html: generatePrayerEmailHTML(message, subject),
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('✅ Email sent successfully:', result.messageId);
|
|
||||||
return NextResponse.json({ success: true, messageId: result.messageId });
|
|
||||||
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('❌ Email send error:', error);
|
|
||||||
|
|
||||||
// Provide more specific error messages
|
|
||||||
if (error.code === 'ECONNREFUSED') {
|
|
||||||
return NextResponse.json({
|
|
||||||
error: 'SMTP connection failed',
|
|
||||||
details: 'Cannot connect to SMTP server. Check host and port.'
|
|
||||||
}, { status: 500 });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error.code === 'EAUTH') {
|
|
||||||
return NextResponse.json({
|
|
||||||
error: 'SMTP authentication failed',
|
|
||||||
details: 'Invalid username or password. For Gmail, use App Password.'
|
|
||||||
}, { status: 500 });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error.code === 'ESOCKET') {
|
|
||||||
return NextResponse.json({
|
|
||||||
error: 'SMTP socket error',
|
|
||||||
details: 'Network or firewall issue. Check internet connection.'
|
|
||||||
}, { status: 500 });
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
error: 'Failed to send email',
|
|
||||||
details: error.message || 'Unknown error occurred'
|
|
||||||
}, { status: 500 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,457 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
|
||||||
import nodemailer from 'nodemailer';
|
|
||||||
|
|
||||||
function generatePrayerEmailHTML(message: string, subject: string) {
|
|
||||||
// Parse the message to extract prayer details and stats
|
|
||||||
const lines = message.split('\n');
|
|
||||||
const stats = { total: 0, active: 0, answered: 0, archived: 0 };
|
|
||||||
const activePrayers = [];
|
|
||||||
const answeredPrayers = [];
|
|
||||||
let inActiveSection = false;
|
|
||||||
let inAnsweredSection = false;
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.includes('Total Prayers:')) {
|
|
||||||
stats.total = parseInt(line.split(':')[1].trim()) || 0;
|
|
||||||
} else if (line.includes('Active Prayers:')) {
|
|
||||||
stats.active = parseInt(line.split(':')[1].trim()) || 0;
|
|
||||||
} else if (line.includes('Answered Prayers:')) {
|
|
||||||
stats.answered = parseInt(line.split(':')[1].trim()) || 0;
|
|
||||||
} else if (line.includes('Archived Prayers:')) {
|
|
||||||
stats.archived = parseInt(line.split(':')[1].trim()) || 0;
|
|
||||||
} else if (line.includes('--- Your Active Prayers ---')) {
|
|
||||||
inActiveSection = true;
|
|
||||||
inAnsweredSection = false;
|
|
||||||
} else if (line.includes('--- Recently Answered Prayers ---')) {
|
|
||||||
inActiveSection = false;
|
|
||||||
inAnsweredSection = true;
|
|
||||||
} else if (inActiveSection && line.includes(' - ')) {
|
|
||||||
const match = line.match(/(\d{4}-\d{2}-\d{2}) - (PRAYER|PRAISE): (.+)/);
|
|
||||||
if (match) {
|
|
||||||
activePrayers.push({
|
|
||||||
date: match[1],
|
|
||||||
type: match[2].toLowerCase(),
|
|
||||||
text: match[3]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (inAnsweredSection && line.includes(' - ')) {
|
|
||||||
const match = line.match(/(\d{4}-\d{2}-\d{2}) - (PRAYER|PRAISE): (.+)/);
|
|
||||||
if (match) {
|
|
||||||
answeredPrayers.push({
|
|
||||||
date: match[1],
|
|
||||||
type: match[2].toLowerCase(),
|
|
||||||
text: match[3]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const html = `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>${subject}</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: #333;
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
color: white;
|
|
||||||
padding: 30px 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.header h1 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.header .subtitle {
|
|
||||||
margin: 8px 0 0 0;
|
|
||||||
opacity: 0.9;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
padding: 30px 20px;
|
|
||||||
}
|
|
||||||
.prayer-card {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-left: 4px solid #667eea;
|
|
||||||
padding: 20px;
|
|
||||||
margin: 20px 0;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
.prayer-type {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #667eea;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 14px;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
|
||||||
.prayer-date {
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.prayer-text {
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 1.6;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
.prayer-journal {
|
|
||||||
background: #fff;
|
|
||||||
border-left: 3px solid #764ba2;
|
|
||||||
padding: 15px;
|
|
||||||
margin: 15px 0;
|
|
||||||
font-style: italic;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
.pray-for {
|
|
||||||
background: #e8f4fd;
|
|
||||||
padding: 10px 15px;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin: 10px 0;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #2c5282;
|
|
||||||
}
|
|
||||||
.summary-section {
|
|
||||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
||||||
color: white;
|
|
||||||
padding: 20px;
|
|
||||||
margin: 20px 0;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
.summary-section h3 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
.summary-item {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
padding: 15px;
|
|
||||||
margin: 10px 0;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
.summary-item .date {
|
|
||||||
color: #ffed4e;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
.summary-item .prayer-type {
|
|
||||||
color: #ffed4e;
|
|
||||||
font-size: 12px;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
.footer {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.verse {
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin: 20px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.verse blockquote {
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 16px;
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
}
|
|
||||||
.stats-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: 15px;
|
|
||||||
margin: 25px 0;
|
|
||||||
}
|
|
||||||
.stat-card {
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
color: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 12px;
|
|
||||||
text-align: center;
|
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.stat-number {
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
.stat-label {
|
|
||||||
font-size: 14px;
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
.section {
|
|
||||||
margin: 30px 0;
|
|
||||||
}
|
|
||||||
.section h3 {
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
.celebration {
|
|
||||||
text-align: center;
|
|
||||||
padding: 40px 20px;
|
|
||||||
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
|
|
||||||
border-radius: 12px;
|
|
||||||
margin: 30px 0;
|
|
||||||
}
|
|
||||||
.celebration h3 {
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="header">
|
|
||||||
<h1>🙏 Prayer App</h1>
|
|
||||||
<div class="subtitle">${subject}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
<!-- Prayer Statistics -->
|
|
||||||
<div class="stats-grid">
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-number">${stats.total}</div>
|
|
||||||
<div class="stat-label">Total Prayers</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-number">${stats.active}</div>
|
|
||||||
<div class="stat-label">Active</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-number">${stats.answered}</div>
|
|
||||||
<div class="stat-label">Answered</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-number">${stats.archived}</div>
|
|
||||||
<div class="stat-label">Archived</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${activePrayers.length > 0 ? `
|
|
||||||
<div class="section">
|
|
||||||
<h3>📝 Your Active Prayers</h3>
|
|
||||||
${activePrayers.map(prayer => `
|
|
||||||
<div class="prayer-card">
|
|
||||||
<div class="prayer-type">${prayer.type}</div>
|
|
||||||
<div class="prayer-date">${new Date(prayer.date).toLocaleDateString()}</div>
|
|
||||||
<div class="prayer-text">${prayer.text}</div>
|
|
||||||
</div>
|
|
||||||
`).join('')}
|
|
||||||
</div>
|
|
||||||
` : `
|
|
||||||
<div class="celebration">
|
|
||||||
<h3>🎉 All Caught Up!</h3>
|
|
||||||
<p>You have no active prayers. Great job staying on top of your prayer life!</p>
|
|
||||||
</div>
|
|
||||||
`}
|
|
||||||
|
|
||||||
${answeredPrayers.length > 0 ? `
|
|
||||||
<div class="section">
|
|
||||||
<h3>🙏 Recently Answered Prayers</h3>
|
|
||||||
${answeredPrayers.map(prayer => `
|
|
||||||
<div class="prayer-card answered">
|
|
||||||
<div class="prayer-type">${prayer.type}</div>
|
|
||||||
<div class="prayer-date">${new Date(prayer.date).toLocaleDateString()}</div>
|
|
||||||
<div class="prayer-text">${prayer.text}</div>
|
|
||||||
</div>
|
|
||||||
`).join('')}
|
|
||||||
</div>
|
|
||||||
` : ''}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="verse">
|
|
||||||
<blockquote>"Pray without ceasing"</blockquote>
|
|
||||||
<cite>— 1 Thessalonians 5:17</cite>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<p>Sent with ❤️ from your Prayer App</p>
|
|
||||||
<p>This email helps you stay connected to your spiritual journey.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>`;
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function GET() {
|
|
||||||
try {
|
|
||||||
// Dynamically import Firebase admin to avoid build-time initialization issues
|
|
||||||
const { admin } = await import('@/lib/firebase-admin');
|
|
||||||
|
|
||||||
console.log('🔄 Checking for scheduled emails...');
|
|
||||||
|
|
||||||
const configDoc = await admin.firestore().collection('config').doc('smtp').get();
|
|
||||||
if (!configDoc.exists) {
|
|
||||||
console.log('❌ SMTP configuration not found');
|
|
||||||
return NextResponse.json({ message: 'SMTP configuration not found' }, { status: 500 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const smtpConfig = configDoc.data();
|
|
||||||
if (!smtpConfig) {
|
|
||||||
console.log('❌ SMTP configuration data is empty');
|
|
||||||
return NextResponse.json({ message: 'SMTP configuration data is empty' }, { status: 500 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create transporter
|
|
||||||
const transporter = nodemailer.createTransport({
|
|
||||||
host: smtpConfig.host,
|
|
||||||
port: smtpConfig.port,
|
|
||||||
secure: smtpConfig.secure,
|
|
||||||
auth: {
|
|
||||||
user: smtpConfig.user,
|
|
||||||
pass: smtpConfig.pass,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get all users (send daily emails to everyone) using Firebase Admin SDK
|
|
||||||
const usersSnapshot = await admin.firestore().collection('users').get();
|
|
||||||
|
|
||||||
let emailsSent = 0;
|
|
||||||
|
|
||||||
for (const userDoc of usersSnapshot.docs) {
|
|
||||||
const userData = userDoc.data();
|
|
||||||
const userId = userDoc.id;
|
|
||||||
|
|
||||||
// Skip users without email
|
|
||||||
if (!userData.email) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check last email sent time - send daily
|
|
||||||
const lastEmailSent = userData.lastEmailSent ? new Date(userData.lastEmailSent.toDate()) : new Date(0);
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
// Send if more than 24 hours have passed
|
|
||||||
const shouldSendEmail = (now.getTime() - lastEmailSent.getTime()) > (24 * 60 * 60 * 1000);
|
|
||||||
|
|
||||||
if (!shouldSendEmail) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get user's prayers for stats using Firebase Admin SDK
|
|
||||||
const prayersSnapshot = await admin.firestore().collection('prayers').where('userId', '==', userId).get();
|
|
||||||
const userPrayers = prayersSnapshot.docs.map(doc => doc.data());
|
|
||||||
|
|
||||||
// Calculate stats
|
|
||||||
const activePrayers = userPrayers.filter(p => !p.archived);
|
|
||||||
const archivedPrayers = userPrayers.filter(p => p.archived);
|
|
||||||
const answeredPrayers = userPrayers.filter(p => p.answered);
|
|
||||||
const totalPrayers = userPrayers.length;
|
|
||||||
|
|
||||||
// Build email content with stats
|
|
||||||
let message = `--- Daily Prayer Summary ---\n\n`;
|
|
||||||
message += `📊 Your Prayer Statistics:\n`;
|
|
||||||
message += `• Total Prayers: ${totalPrayers}\n`;
|
|
||||||
message += `• Active Prayers: ${activePrayers.length}\n`;
|
|
||||||
message += `• Answered Prayers: ${answeredPrayers.length}\n`;
|
|
||||||
message += `• Archived Prayers: ${archivedPrayers.length}\n\n`;
|
|
||||||
|
|
||||||
if (activePrayers.length > 0) {
|
|
||||||
message += `--- Your Active Prayers ---\n`;
|
|
||||||
activePrayers.forEach(prayer => {
|
|
||||||
message += `\n${prayer.date} - ${prayer.type.toUpperCase()}: ${prayer.text}`;
|
|
||||||
if (prayer.prayFor) {
|
|
||||||
message += ` (Praying for: ${prayer.prayFor})`;
|
|
||||||
}
|
|
||||||
if (prayer.journal) {
|
|
||||||
message += `\n Journal: ${prayer.journal}`;
|
|
||||||
}
|
|
||||||
message += `\n`;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
message += `🎉 Great job! You have no active prayers - all caught up!\n\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (answeredPrayers.length > 0) {
|
|
||||||
message += `\n--- Recently Answered Prayers ---\n`;
|
|
||||||
// Show answered prayers from the last 7 days
|
|
||||||
const recentAnswered = answeredPrayers.filter(p => {
|
|
||||||
const answeredDate = p.answeredDate ? new Date(p.answeredDate) : new Date(p.date);
|
|
||||||
const weekAgo = new Date();
|
|
||||||
weekAgo.setDate(weekAgo.getDate() - 7);
|
|
||||||
return answeredDate >= weekAgo;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (recentAnswered.length > 0) {
|
|
||||||
recentAnswered.forEach(prayer => {
|
|
||||||
message += `\n${prayer.date} - ${prayer.type.toUpperCase()}: ${prayer.text}`;
|
|
||||||
if (prayer.answeredDate) {
|
|
||||||
message += ` (Answered: ${new Date(prayer.answeredDate).toLocaleDateString()})`;
|
|
||||||
}
|
|
||||||
message += `\n`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const subject = `Daily Prayer Summary - ${activePrayers.length} active prayers`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Send email
|
|
||||||
await transporter.sendMail({
|
|
||||||
from: smtpConfig.from,
|
|
||||||
to: userData.email,
|
|
||||||
subject,
|
|
||||||
text: message,
|
|
||||||
html: generatePrayerEmailHTML(message, subject),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update last email sent time using Firebase Admin SDK
|
|
||||||
await admin.firestore().collection('users').doc(userId).update({
|
|
||||||
lastEmailSent: new Date(),
|
|
||||||
});
|
|
||||||
|
|
||||||
emailsSent++;
|
|
||||||
console.log(`✅ Sent daily summary email to ${userData.email}`);
|
|
||||||
|
|
||||||
} catch (emailError) {
|
|
||||||
console.error(`❌ Failed to send email to ${userData.email}:`, emailError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`📧 Daily email summary complete. Sent ${emailsSent} emails.`);
|
|
||||||
return NextResponse.json({
|
|
||||||
message: `Daily email summary complete. Sent ${emailsSent} emails.`,
|
|
||||||
emailsSent
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Scheduled email error:', error);
|
|
||||||
console.error('❌ Error details:', {
|
|
||||||
message: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
name: error instanceof Error ? error.name : 'Unknown'
|
|
||||||
});
|
|
||||||
return NextResponse.json({
|
|
||||||
error: 'Failed to process scheduled emails',
|
|
||||||
details: error instanceof Error ? error.message : 'Unknown error'
|
|
||||||
}, { status: 500 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST() {
|
|
||||||
// POST handler calls the same logic as GET for testing purposes
|
|
||||||
return GET();
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { useAuth } from '@/context/AuthContext';
|
|
||||||
import Profile from '@/components/Profile';
|
|
||||||
import Navbar from '@/components/Navbar';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
export default function ProfilePage() {
|
|
||||||
const { user } = useAuth();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!user) {
|
|
||||||
router.push('/');
|
|
||||||
}
|
|
||||||
}, [user, router]);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-black to-gray-900 text-white">
|
|
||||||
<Navbar />
|
|
||||||
<div className="max-w-4xl mx-auto p-6">
|
|
||||||
<Profile />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { collection, query, where, orderBy, getDocs } from 'firebase/firestore';
|
|
||||||
import { db } from '@/lib/firebase';
|
|
||||||
import Navbar from '@/components/Navbar';
|
|
||||||
|
|
||||||
interface Testimony {
|
|
||||||
id: string;
|
|
||||||
text: string;
|
|
||||||
userId: string;
|
|
||||||
answeredDate: string;
|
|
||||||
tags?: string[];
|
|
||||||
type: 'prayer' | 'praise';
|
|
||||||
prayFor?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function TestimoniesPage() {
|
|
||||||
const [testimonies, setTestimonies] = useState<Testimony[]>([]);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [filter, setFilter] = useState<'all' | 'prayer' | 'praise'>('all');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchTestimonies = async () => {
|
|
||||||
try {
|
|
||||||
const q = query(
|
|
||||||
collection(db, 'prayers'),
|
|
||||||
where('answered', '==', true),
|
|
||||||
orderBy('answeredDate', 'desc')
|
|
||||||
);
|
|
||||||
const querySnapshot = await getDocs(q);
|
|
||||||
const testimoniesData = querySnapshot.docs.map(doc => ({
|
|
||||||
id: doc.id,
|
|
||||||
...doc.data()
|
|
||||||
})) as Testimony[];
|
|
||||||
setTestimonies(testimoniesData);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching testimonies:', error);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchTestimonies();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const filteredTestimonies = testimonies.filter(testimony =>
|
|
||||||
filter === 'all' || testimony.type === filter
|
|
||||||
);
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-black to-gray-900 text-white">
|
|
||||||
<Navbar />
|
|
||||||
<div className="max-w-4xl mx-auto p-6">
|
|
||||||
<div className="animate-pulse space-y-4">
|
|
||||||
{[...Array(5)].map((_, i) => (
|
|
||||||
<div key={i} className="bg-gray-800/50 rounded-lg p-6">
|
|
||||||
<div className="h-4 bg-gray-700 rounded w-3/4 mb-2"></div>
|
|
||||||
<div className="h-3 bg-gray-700 rounded w-1/2"></div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-black to-gray-900 text-white">
|
|
||||||
<Navbar />
|
|
||||||
<div className="max-w-4xl mx-auto p-6">
|
|
||||||
<div className="text-center mb-8">
|
|
||||||
<h1 className="text-4xl font-bold mb-4">🙏 Testimony Wall</h1>
|
|
||||||
<p className="text-gray-300">Celebrating answered prayers and praises</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Filter Buttons */}
|
|
||||||
<div className="flex justify-center space-x-4 mb-8">
|
|
||||||
<button
|
|
||||||
onClick={() => setFilter('all')}
|
|
||||||
className={`px-4 py-2 rounded-lg transition-colors ${
|
|
||||||
filter === 'all'
|
|
||||||
? 'bg-blue-600 text-white'
|
|
||||||
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
All Testimonies
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setFilter('prayer')}
|
|
||||||
className={`px-4 py-2 rounded-lg transition-colors ${
|
|
||||||
filter === 'prayer'
|
|
||||||
? 'bg-blue-600 text-white'
|
|
||||||
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
🙏 Prayers
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setFilter('praise')}
|
|
||||||
className={`px-4 py-2 rounded-lg transition-colors ${
|
|
||||||
filter === 'praise'
|
|
||||||
? 'bg-blue-600 text-white'
|
|
||||||
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
🙌 Praises
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
|
||||||
{filteredTestimonies.map((testimony) => (
|
|
||||||
<div key={testimony.id} className="bg-gray-800/50 backdrop-blur-sm rounded-2xl p-6 shadow-2xl border border-gray-700/50">
|
|
||||||
<div className="flex items-start justify-between mb-4">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<span className="text-2xl">{testimony.type === 'prayer' ? '🙏' : '🙌'}</span>
|
|
||||||
<span className="text-green-400 font-semibold">
|
|
||||||
Answered {testimony.type === 'prayer' ? 'Prayer' : 'Praise'}
|
|
||||||
</span>
|
|
||||||
{testimony.prayFor && (
|
|
||||||
<span className="text-sm text-gray-400">for {testimony.prayFor}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<span className="text-sm text-gray-400">
|
|
||||||
{new Date(testimony.answeredDate).toLocaleDateString()}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="text-gray-200 mb-4 leading-relaxed">{testimony.text}</p>
|
|
||||||
|
|
||||||
{testimony.tags && testimony.tags.length > 0 && (
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{testimony.tags.map((tag, index) => (
|
|
||||||
<span key={index} className="px-2 py-1 bg-blue-600/20 text-blue-300 rounded-full text-xs">
|
|
||||||
#{tag}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{filteredTestimonies.length === 0 && (
|
|
||||||
<div className="text-center py-12">
|
|
||||||
<div className="text-6xl mb-4">🙏</div>
|
|
||||||
<h2 className="text-2xl font-semibold mb-2">No Testimonies Yet</h2>
|
|
||||||
<p className="text-gray-400">
|
|
||||||
{filter === 'all'
|
|
||||||
? 'Answered prayers will appear here to inspire others.'
|
|
||||||
: `No answered ${filter === 'prayer' ? 'prayers' : 'praises'} yet.`
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user