church-api/church-website-axum/src/handlers/bulletins.rs
Benjamin Slingo 0c06e159bb Initial commit: Church API Rust implementation
Complete church management system with bulletin management, media processing, live streaming integration, and web interface. Includes authentication, email notifications, database migrations, and comprehensive test suite.
2025-08-19 20:56:41 -04:00

378 lines
19 KiB
Rust

use axum::{extract::Path, response::Html};
use crate::services::ApiService;
use crate::layout::layout;
use chrono::NaiveDate;
pub async fn bulletins_handler() -> Html<String> {
let api_service = ApiService::new();
match api_service.get_bulletins().await {
Ok(bulletins) => {
let content = format!(r#"
<!-- Bulletins Hero -->
<section class="section-2025" style="background: var(--gradient-primary); color: white; padding: 6rem 0 4rem 0;">
<div class="container-2025">
<div class="section-header scroll-reveal">
<h1 class="section-title serif" style="color: white;">Church Bulletins</h1>
<p class="section-subtitle" style="color: rgba(255,255,255,0.9);">Download our weekly bulletins to stay informed about church activities and worship services</p>
</div>
</div>
</section>
<!-- Bulletins Grid -->
<section class="section-2025">
<div class="container-2025">
<div class="cards-grid">
{}
</div>
{}
</div>
</section>
"#,
// Bulletins grid HTML
bulletins.iter().enumerate().map(|(index, bulletin)| {
let formatted_date = if let Ok(parsed_date) = NaiveDate::parse_from_str(&bulletin.date, "%Y-%m-%d") {
parsed_date.format("%A, %B %d, %Y").to_string()
} else {
bulletin.date.clone()
};
format!(r#"
<a href="/bulletins/{}" style="text-decoration: none; color: inherit; display: block;">
<div class="card-2025 scroll-reveal stagger-{}" style="cursor: pointer;">
<div class="card-icon-2025">
<i class="fas fa-file-alt"></i>
</div>
<h3 class="card-title">{}</h3>
<p style="color: var(--medium-gray); margin-bottom: 1.5rem;">
<i class="fas fa-calendar" style="color: var(--soft-gold); margin-right: 0.5rem;"></i>
{}
</p>
{}
<div class="btn-2025 btn-outline" style="display: inline-flex; align-items: center; gap: 0.5rem; margin-top: auto; background: white; color: var(--deep-navy); border: 2px solid var(--deep-navy);">
View Details <i class="fas fa-arrow-right"></i>
</div>
</div>
</a>
"#,
bulletin.id,
(index % 3) + 1,
bulletin.title,
formatted_date,
// Scripture reading preview
if let Some(ref scripture) = bulletin.scripture_reading {
if !scripture.is_empty() {
let preview = if scripture.len() > 150 {
format!("{}...", &scripture[..150])
} else {
scripture.clone()
};
format!(r#"
<div style="background: var(--soft-gray); padding: 1.5rem; border-radius: 12px; margin: 1.5rem 0; border-left: 4px solid var(--soft-gold); font-style: italic; color: var(--deep-navy);">
<strong style="color: var(--deep-navy);">Scripture Reading:</strong><br>
{}
</div>
"#, preview)
} else {
String::new()
}
} else {
String::new()
}
)
}).collect::<Vec<_>>().join(""),
// No bulletins message
if bulletins.is_empty() {
r#"
<div class="card-2025 scroll-reveal" style="text-align: center; padding: 3rem;">
<div class="card-icon-2025" style="margin: 0 auto 2rem;">
<i class="fas fa-file-alt"></i>
</div>
<h3 class="card-title">No Bulletins Available</h3>
<p class="card-text">No bulletins available at this time. Please check back later.</p>
</div>
"#
} else {
""
}
);
Html(layout(&content, "Bulletins"))
},
Err(_) => {
let content = r#"
<section class="section-2025">
<div class="container-2025">
<div class="card-2025 scroll-reveal" style="text-align: center; padding: 3rem;">
<h1>Bulletins</h1>
<p>Unable to load bulletins. Please try again later.</p>
</div>
</div>
</section>
"#;
Html(layout(content, "Bulletins"))
}
}
}
pub async fn bulletin_detail_handler(Path(id): Path<String>) -> Html<String> {
let api_service = ApiService::new();
match api_service.get_bulletin(&id).await {
Ok(Some(bulletin)) => {
let formatted_date = if let Ok(parsed_date) = NaiveDate::parse_from_str(&bulletin.date, "%Y-%m-%d") {
parsed_date.format("%A, %B %d, %Y").to_string()
} else {
bulletin.date.clone()
};
let content = format!(r#"
<!-- Bulletin Detail Hero -->
<section class="section-2025" style="background: var(--gradient-primary); color: white; padding: 6rem 0 3rem 0;">
<div class="container-2025">
<a href="/bulletins" style="color: rgba(255,255,255,0.8); text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem; margin-bottom: 2rem; font-weight: 500;">
<i class="fas fa-arrow-left"></i> Back to Bulletins
</a>
<h1 class="section-title serif" style="color: white; font-size: clamp(2.5rem, 5vw, 4rem); margin-bottom: 1rem;">{}</h1>
<p style="color: rgba(255,255,255,0.9); font-size: 1.2rem;">
<i class="fas fa-calendar-alt" style="color: var(--soft-gold); margin-right: 0.5rem;"></i>
{}
</p>
</div>
</section>
{}
{}
{}
{}
"#,
bulletin.title,
formatted_date,
// Scripture Reading Section
if let Some(ref scripture) = bulletin.scripture_reading {
if !scripture.is_empty() {
format!(r#"
<section class="section-2025">
<div class="container-2025">
<div class="card-2025 scroll-reveal" style="text-align: center; max-width: 800px; margin: 0 auto;">
<div class="card-icon-2025" style="margin: 0 auto 2rem;">
<i class="fas fa-book-open"></i>
</div>
<h3 class="card-title">Scripture Reading</h3>
<div style="background: var(--soft-gray); padding: 2rem; border-radius: 16px; margin: 2rem 0; border-left: 4px solid var(--soft-gold); font-style: italic; color: var(--deep-navy); line-height: 1.6;">
{}
</div>
</div>
</div>
</section>
"#, scripture)
} else {
String::new()
}
} else {
String::new()
},
// Service Programs Section
if bulletin.sabbath_school.is_some() || bulletin.divine_worship.is_some() {
let has_both = bulletin.sabbath_school.is_some() && bulletin.divine_worship.is_some();
format!(r#"
<section class="section-2025" style="background: var(--soft-gray);">
<div class="container-2025">
<div class="section-header scroll-reveal">
<h2 class="section-title">Service Programs</h2>
<p class="section-subtitle">Order of service for worship and fellowship</p>
</div>
{}
</div>
</section>
"#,
if has_both {
// Both programs - adaptive grid
format!(r#"
<div style="display: grid; grid-template-columns: 1fr 1.5fr; gap: 3rem; margin-top: 3rem; align-items: start;">
{}
{}
</div>
"#,
// Sabbath School - smaller card
if let Some(ref ss) = bulletin.sabbath_school {
format!(r#"
<div class="scroll-reveal" style="background: white; border-radius: 24px; padding: 3rem; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08); border: 1px solid rgba(255, 255, 255, 0.2); height: fit-content;">
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 2.5rem;">
<div style="width: 64px; height: 64px; background: linear-gradient(135deg, var(--soft-gold), #ffd700); border-radius: 16px; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 16px rgba(255, 215, 120, 0.3);">
<i class="fas fa-book" style="color: var(--deep-navy); font-size: 1.5rem;"></i>
</div>
<h3 style="color: var(--deep-navy); font-family: 'Playfair Display', serif; font-size: 1.8rem; margin: 0; font-weight: 600;">Sabbath School</h3>
</div>
<div style="color: var(--deep-navy); line-height: 2; font-size: 1.05rem;">
<div style="white-space: pre-wrap; font-family: inherit; margin: 0; line-height: inherit; font-size: inherit; color: inherit;">{}</div>
</div>
</div>
"#, ss)
} else {
String::new()
},
// Divine Worship - larger card
if let Some(ref dw) = bulletin.divine_worship {
format!(r#"
<div class="scroll-reveal" style="background: white; border-radius: 24px; padding: 3.5rem; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08); border: 1px solid rgba(255, 255, 255, 0.2);">
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 3rem;">
<div style="width: 64px; height: 64px; background: linear-gradient(135deg, var(--deep-navy), #2c4584); border-radius: 16px; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 16px rgba(25, 35, 66, 0.3);">
<i class="fas fa-praying-hands" style="color: white; font-size: 1.5rem;"></i>
</div>
<h3 style="color: var(--deep-navy); font-family: 'Playfair Display', serif; font-size: 1.8rem; margin: 0; font-weight: 600;">Divine Worship</h3>
</div>
<div style="color: var(--deep-navy); line-height: 2.2; font-size: 1.05rem; letter-spacing: 0.3px; overflow: visible; word-wrap: break-word;">
<div style="white-space: pre-wrap; font-family: inherit; margin: 0; line-height: inherit; font-size: inherit; color: inherit; letter-spacing: inherit; overflow: visible; word-wrap: break-word;">{}</div>
</div>
</div>
"#, dw)
} else {
String::new()
}
)
} else {
// Single program or responsive fallback
format!(r#"
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(500px, 1fr)); gap: 3rem; margin-top: 3rem;">
{}
{}
</div>
"#,
if let Some(ref ss) = bulletin.sabbath_school {
format!(r#"
<div class="scroll-reveal" style="background: white; border-radius: 24px; padding: 3rem; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08); border: 1px solid rgba(255, 255, 255, 0.2);">
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 2.5rem;">
<div style="width: 64px; height: 64px; background: linear-gradient(135deg, var(--soft-gold), #ffd700); border-radius: 16px; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 16px rgba(255, 215, 120, 0.3);">
<i class="fas fa-book" style="color: var(--deep-navy); font-size: 1.5rem;"></i>
</div>
<h3 style="color: var(--deep-navy); font-family: 'Playfair Display', serif; font-size: 2rem; margin: 0; font-weight: 600;">Sabbath School Program</h3>
</div>
<div style="color: var(--deep-navy); line-height: 2; font-size: 1.05rem;">
<div style="white-space: pre-wrap; font-family: inherit; margin: 0; line-height: inherit; font-size: inherit; color: inherit;">{}</div>
</div>
</div>
"#, ss)
} else {
String::new()
},
if let Some(ref dw) = bulletin.divine_worship {
format!(r#"
<div class="scroll-reveal" style="background: white; border-radius: 24px; padding: 3.5rem; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08); border: 1px solid rgba(255, 255, 255, 0.2);">
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 3rem;">
<div style="width: 64px; height: 64px; background: linear-gradient(135deg, var(--deep-navy), #2c4584); border-radius: 16px; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 16px rgba(25, 35, 66, 0.3);">
<i class="fas fa-praying-hands" style="color: white; font-size: 1.5rem;"></i>
</div>
<h3 style="color: var(--deep-navy); font-family: 'Playfair Display', serif; font-size: 2rem; margin: 0; font-weight: 600;">Divine Worship Program</h3>
</div>
<div style="color: var(--deep-navy); line-height: 2.2; font-size: 1.05rem; letter-spacing: 0.3px; overflow: visible; word-wrap: break-word;">
<div style="white-space: pre-wrap; font-family: inherit; margin: 0; line-height: inherit; font-size: inherit; color: inherit; letter-spacing: inherit; overflow: visible; word-wrap: break-word;">{}</div>
</div>
</div>
"#, dw)
} else {
String::new()
}
)
})
} else {
String::new()
},
// Sunset Information Section
if let Some(ref sunset) = bulletin.sunset {
if !sunset.is_empty() {
format!(r#"
<section class="section-2025">
<div class="container-2025">
<div class="card-2025 scroll-reveal" style="text-align: center; max-width: 600px; margin: 0 auto;">
<div class="card-icon-2025" style="margin: 0 auto 2rem;">
<i class="fas fa-sun"></i>
</div>
<h3 class="card-title">Sabbath Information</h3>
<p style="color: var(--medium-gray); font-style: italic; font-size: 1.1rem;">{}</p>
</div>
</div>
</section>
"#, sunset)
} else {
String::new()
}
} else {
String::new()
},
// PDF Download Section
if let Some(ref pdf_path) = bulletin.pdf_path {
if !pdf_path.is_empty() {
format!(r#"
<section class="section-2025" style="background: var(--soft-gray);">
<div class="container-2025">
<div class="card-2025 scroll-reveal" style="text-align: center; max-width: 600px; margin: 0 auto;">
<div class="card-icon-2025" style="margin: 0 auto 2rem;">
<i class="fas fa-file-pdf"></i>
</div>
<h3 class="card-title">Download Full Bulletin</h3>
<p class="card-text">Get the complete bulletin with all details and information.</p>
<a href="{}" target="_blank" class="btn-2025 btn-primary" style="margin-top: 1rem;">
<i class="fas fa-download"></i>
Download PDF
</a>
</div>
</div>
</section>
"#, pdf_path)
} else {
String::new()
}
} else {
String::new()
}
);
Html(layout(&content, &bulletin.title))
},
Ok(None) => {
let content = r#"
<section class="section-2025">
<div class="container-2025">
<div class="card-2025 scroll-reveal" style="text-align: center; padding: 3rem;">
<h1>Bulletin Not Found</h1>
<p>The requested bulletin could not be found.</p>
<a href="/bulletins" class="btn-2025 btn-primary">← Back to Bulletins</a>
</div>
</div>
</section>
"#;
Html(layout(content, "Bulletin Not Found"))
},
Err(_) => {
let content = r#"
<section class="section-2025">
<div class="container-2025">
<div class="card-2025 scroll-reveal" style="text-align: center; padding: 3rem;">
<h1>Error</h1>
<p>Unable to load bulletin. Please try again later.</p>
<a href="/bulletins" class="btn-2025 btn-primary">← Back to Bulletins</a>
</div>
</div>
</section>
"#;
Html(layout(content, "Error"))
}
}
}