425 lines
19 KiB
TypeScript
425 lines
19 KiB
TypeScript
import { useEffect, useMemo, useState } from 'react'
|
||
import { Link } from 'react-router-dom'
|
||
import { fetchAdministratorDashboardMetrics } from '../api/session'
|
||
import type { AdministratorDashboardMetricsResponse, AuthUser } from '../api/types'
|
||
import { useAuth } from '../auth/AuthProvider'
|
||
import './HomePage.css'
|
||
|
||
/** Public home must not call administrator APIs unless the user is allowed (avoids 401 for teachers/guests). */
|
||
function userMayLoadAdministratorHomeMetrics(user: AuthUser | null): boolean {
|
||
if (!user?.roles) return false
|
||
const r = user.roles
|
||
return Boolean(r.administrator || r.admin || r.super_admin)
|
||
}
|
||
|
||
/** Carousel + sections ported from CodeIgniter `Views/index.php` (Al Rahma public home). */
|
||
const CAROUSEL_SLIDES = [
|
||
{
|
||
image: '/images/carousel-1.png',
|
||
alt: 'Not only a book to be read but a guide illuminating our lives.',
|
||
title: 'Not only a book to be read but a guide illuminating our lives.',
|
||
body:
|
||
'At Al Rahma Sunday School, we teach the Quran as a living guide, nurturing love and respect for the words of Allah (SWT). Our program supports each child’s journey through recitation, memorization, and age-appropriate translation and understanding.',
|
||
},
|
||
{
|
||
image: '/images/carousel-0.png',
|
||
alt: 'Faith and Knowledge Hand in Hand',
|
||
title: 'Faith and Knowledge Hand in Hand',
|
||
body:
|
||
'Our Sunday School program (Grades 1-9, Youth) is evenly divided between Islamic Studies in English—exploring topics about Allah, Islamic values, Muslim life and lessons from the Prophets—and Quran/Arabic studies focused on recitation, memorization and language. This balanced approach nurtures both understanding and practice of Islam.',
|
||
},
|
||
{
|
||
image: '/images/carousel-3.png',
|
||
alt: 'Nurturing Hearts and Minds',
|
||
title: 'Nurturing Hearts and Minds',
|
||
body:
|
||
'At our school, we inspire a lifelong love for Islamic values, for our worship rituals and our prophets through engaging lessons, interactive activities, and a supportive environment. Every student grows in knowledge, faith and character, learning to live the teachings of Islam in everyday life.',
|
||
},
|
||
{
|
||
image: '/images/carousel-4.png',
|
||
alt: 'Exciting Fun, Inside and Out!',
|
||
title: 'Exciting Fun, Inside and Out!',
|
||
body:
|
||
'From festive Eid celebrations to adventurous outdoor outings, our school events bring learning, laughter, and unforgettable memories together.',
|
||
},
|
||
{
|
||
image: '/images/carousel-5.png',
|
||
alt: 'Strengthening Faith, Character, and Community Bonds',
|
||
title: 'Strengthening Faith, Character, and Community Bonds',
|
||
body:
|
||
'A warm, faith-centered community helps children thrive, build confidence, compassion, and friendships.',
|
||
},
|
||
] as const
|
||
|
||
const STAT_DEF = [
|
||
{ key: 'students', label: 'Students', circle: 'circle-primary', icon: 'bi-mortarboard' },
|
||
{ key: 'teachers', label: 'Teachers', circle: 'circle-info', icon: 'bi-person-video3' },
|
||
{ key: 'teacherAssistants', label: 'TAs', circle: 'circle-success', icon: 'bi-person-gear' },
|
||
{ key: 'admins', label: 'Admins', circle: 'circle-warning', icon: 'bi-shield-check' },
|
||
{ key: 'parents', label: 'Parents', circle: 'circle-light', icon: 'bi-people' },
|
||
] as const
|
||
|
||
type CountKey = (typeof STAT_DEF)[number]['key']
|
||
|
||
function formatStat(
|
||
n: number | undefined,
|
||
numberFmt: Intl.NumberFormat,
|
||
): string {
|
||
return typeof n === 'number' && Number.isFinite(n) ? numberFmt.format(n) : '—'
|
||
}
|
||
|
||
export function HomePage() {
|
||
const { token, user } = useAuth()
|
||
const numberFmt = useMemo(() => new Intl.NumberFormat(), [])
|
||
const [counts, setCounts] = useState<AdministratorDashboardMetricsResponse['counts']>({})
|
||
const [showTop, setShowTop] = useState(false)
|
||
|
||
const mayLoadMetrics = Boolean(token && userMayLoadAdministratorHomeMetrics(user))
|
||
|
||
useEffect(() => {
|
||
if (!mayLoadMetrics) {
|
||
setCounts({})
|
||
return
|
||
}
|
||
|
||
let cancelled = false
|
||
const loadStats = async () => {
|
||
try {
|
||
const data = await fetchAdministratorDashboardMetrics()
|
||
if (!cancelled) setCounts(data.counts ?? {})
|
||
} catch {
|
||
if (!cancelled) setCounts({})
|
||
}
|
||
}
|
||
void loadStats()
|
||
const interval = window.setInterval(() => void loadStats(), 60_000)
|
||
const onVisibility = () => {
|
||
if (document.visibilityState === 'visible') void loadStats()
|
||
}
|
||
document.addEventListener('visibilitychange', onVisibility)
|
||
return () => {
|
||
cancelled = true
|
||
window.clearInterval(interval)
|
||
document.removeEventListener('visibilitychange', onVisibility)
|
||
}
|
||
}, [mayLoadMetrics])
|
||
|
||
useEffect(() => {
|
||
const onScroll = () => setShowTop(window.scrollY > 100)
|
||
window.addEventListener('scroll', onScroll, { passive: true })
|
||
onScroll()
|
||
return () => window.removeEventListener('scroll', onScroll)
|
||
}, [])
|
||
|
||
function countFor(key: CountKey): string {
|
||
return formatStat(counts?.[key], numberFmt)
|
||
}
|
||
|
||
return (
|
||
<div className="home-landing flex-grow-1">
|
||
<div
|
||
id="homeMainCarousel"
|
||
className="carousel slide carousel-fade home-carousel"
|
||
data-bs-ride="carousel"
|
||
>
|
||
<div className="carousel-indicators">
|
||
{CAROUSEL_SLIDES.map((_, i) => (
|
||
<button
|
||
key={i}
|
||
type="button"
|
||
data-bs-target="#homeMainCarousel"
|
||
data-bs-slide-to={i}
|
||
className={i === 0 ? 'active' : undefined}
|
||
aria-current={i === 0 ? 'true' : undefined}
|
||
aria-label={`Slide ${i + 1}`}
|
||
/>
|
||
))}
|
||
</div>
|
||
<div className="carousel-inner">
|
||
{CAROUSEL_SLIDES.map((slide, i) => (
|
||
<div key={slide.image} className={`carousel-item${i === 0 ? ' active' : ''}`}>
|
||
<img src={slide.image} className="d-block w-100" alt={slide.alt} />
|
||
<div className="carousel-caption">
|
||
<h2 className="mb-3">{slide.title}</h2>
|
||
<p className="mb-0">{slide.body}</p>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<button
|
||
className="carousel-control-prev"
|
||
type="button"
|
||
data-bs-target="#homeMainCarousel"
|
||
data-bs-slide="prev"
|
||
>
|
||
<span className="carousel-control-prev-icon" aria-hidden />
|
||
<span className="visually-hidden">Previous</span>
|
||
</button>
|
||
<button
|
||
className="carousel-control-next"
|
||
type="button"
|
||
data-bs-target="#homeMainCarousel"
|
||
data-bs-slide="next"
|
||
>
|
||
<span className="carousel-control-next-icon" aria-hidden />
|
||
<span className="visually-hidden">Next</span>
|
||
</button>
|
||
</div>
|
||
|
||
<section className="text-center my-3">
|
||
<a
|
||
href="/account_creation_guide.pdf"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="text-decoration-none"
|
||
style={{ fontWeight: 900, color: '#000' }}
|
||
>
|
||
<h3 className="fw-bold my-0">
|
||
<span className="text-decoration-underline">
|
||
Join Al Rahma family today! Click here for a quick guide on how to create an account.
|
||
</span>
|
||
</h3>
|
||
</a>
|
||
</section>
|
||
|
||
<div className="container-xxl py-3 home-section-xl content-section">
|
||
<div className="container">
|
||
<div className="home-stats">
|
||
<h2 className="d-flex justify-content-center p-md-1 fs-3 fw-bold green-title mb-0">
|
||
Active Participants
|
||
</h2>
|
||
<br />
|
||
<div className="stats-row">
|
||
{STAT_DEF.map((s) => (
|
||
<div key={s.key} className={`stat-circle ${s.circle}`}>
|
||
<div className="stat-icon">
|
||
<i className={`bi ${s.icon}`} aria-hidden />
|
||
</div>
|
||
<div className="stat-value">{countFor(s.key)}</div>
|
||
<div className="stat-title">{s.label}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="container-xxl py-3 home-section-xl content-section">
|
||
<div className="container">
|
||
<div className="row bg-light squared align-items-center g-0">
|
||
<div className="col-lg-6">
|
||
<div className="d-flex flex-column justify-content-center p-4 p-md-5">
|
||
<h2 className="mb-4 fs-2 fw-bold green-title">
|
||
Faith, Education and Community for Over 30 Years
|
||
</h2>
|
||
<p className="mb-4">
|
||
The Islamic Society of Greater Lowell (ISGL), established in 1993 in Chelmsford,
|
||
Massachusetts, serves as both a mosque and a vibrant community center. It provides a
|
||
place of worship for Allah (SWT), offers Islamic education and facilitates religious
|
||
and social activities for Muslims in the Merrimack Valley. As a non-profit,
|
||
charitable, and tax-exempt organization, ISGL also provides essential services such as
|
||
marriage ceremonies, burial arrangements and community support while promoting a
|
||
greater understanding of Islam in America.
|
||
</p>
|
||
<p className="mb-4">
|
||
Under the umbrella of ISGL, Al Rahma Sunday School has been serving the community for
|
||
over thirty years. Throughout the decades, it has provided students with a strong
|
||
foundation in Islamic Studies and Quran, fostering both knowledge and character. With
|
||
its dedicated teachers and well-rounded academic program, the school continues to
|
||
guide generations of Muslim youth in their faith, values and practice of Islam.
|
||
</p>
|
||
<p>For more information, click below to visit ISGL official website.</p>
|
||
<div>
|
||
<a
|
||
className="btn btn-home-cta px-4 py-2 fw-semibold"
|
||
href="https://isgl.org"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
>
|
||
ISGL Official Website
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="col-lg-6 image-column pe-lg-3">
|
||
<div className="position-relative h-100 w-100">
|
||
<img
|
||
className="w-100 h-100 rounded-end"
|
||
src="/images/isgl_home.png"
|
||
alt="ISGL"
|
||
style={{ objectFit: 'cover', minHeight: 280 }}
|
||
onError={(e) => {
|
||
const img = e.currentTarget
|
||
img.onerror = null
|
||
img.src = '/images/call-to-action.jpg'
|
||
}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="container-xxl py-3 home-section-xl content-section">
|
||
<div className="container">
|
||
<div className="row bg-light squared align-items-center g-0">
|
||
<div className="col-lg-6">
|
||
<div className="d-flex flex-column justify-content-center p-4 p-md-5">
|
||
<h2 className="mb-4 fs-2 fw-bold green-title">Our Mission</h2>
|
||
<p>
|
||
Al Rahma Sunday School's mission is to make the Masjid a central part of our
|
||
Muslim children's lives from a very young age. We are dedicated to offering a
|
||
strong, well-rounded curriculum in Quran, Arabic and Islamic Studies. Our goal is to
|
||
deliver the highest quality education to help instill Islamic values, beliefs and
|
||
practices in each student. Additionally, we regularly organize engaging and fun events
|
||
throughout the year to make the learning experience more enjoyable and spiritually
|
||
enriching.
|
||
</p>
|
||
<p className="mb-0">
|
||
We deeply appreciate your continued support and cooperation in helping us fulfill this
|
||
mission. Together, we can provide a strong Islamic foundation for our children and shape
|
||
a future generation that is confident in their faith and rooted in Islamic values.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div className="col-lg-6 about-img py-4 py-lg-0">
|
||
<div className="row g-2 justify-content-center">
|
||
<div className="col-12 text-center">
|
||
<img
|
||
className="img-fluid w-75 rounded-circle bg-light p-3"
|
||
src="/images/about-1.jpg"
|
||
alt=""
|
||
/>
|
||
</div>
|
||
<div className="col-6 text-start" style={{ marginTop: '-150px' }}>
|
||
<img
|
||
className="img-fluid w-100 rounded-circle bg-light p-3"
|
||
src="/images/about-2.jpg"
|
||
alt=""
|
||
/>
|
||
</div>
|
||
<div className="col-6 text-end" style={{ marginTop: '-150px' }}>
|
||
<img
|
||
className="img-fluid w-100 rounded-circle bg-light p-3"
|
||
src="/images/about-3.jpg"
|
||
alt=""
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="container-xxl py-3 home-section-xl content-section">
|
||
<div className="container">
|
||
<div className="row bg-light squared align-items-center g-0">
|
||
<div className="col-lg-6">
|
||
<div className="d-flex flex-column justify-content-center p-4 p-md-5">
|
||
<h2 className="mb-4 fs-2 fw-bold green-title">Become A Teacher or Admin</h2>
|
||
<p className="mb-4">
|
||
Volunteering at Al Rahma Sunday School is a deeply fulfilling way to give back to the
|
||
community and help shape the next generation. Whether you're passionate about
|
||
teaching or prefer working behind the scenes, your time and dedication can make a
|
||
lasting difference. Join a team committed to nurturing young minds and strengthening
|
||
their connection to faith.
|
||
</p>
|
||
<p className="mb-4">
|
||
As a volunteer teacher or teacher assistant, you'll have the opportunity to
|
||
inspire, educate, and guide children on their spiritual journey. Teachers play a vital
|
||
role in helping students understand the Quran, Islamic values, and the Arabic language
|
||
in a warm and engaging classroom environment. Teacher assistants support instruction,
|
||
help manage classroom activities, and provide one-on-one assistance to students when
|
||
needed. Whether leading the class or lending a helping hand, your commitment can
|
||
spark a lifelong love for learning and faith in the hearts of our students.
|
||
</p>
|
||
<p className="mb-4">
|
||
Administrative volunteers are essential to the success of our school. From organizing
|
||
materials and managing communications to helping coordinate school events and
|
||
logistics, your work ensures a smooth and efficient learning experience for students
|
||
and teachers alike. If you enjoy structured tasks, planning, or simply being helpful
|
||
in a quiet but powerful way, this role is perfect for you.
|
||
</p>
|
||
<div>
|
||
<Link className="btn btn-home-cta py-3 px-5 mt-1" to="/register">
|
||
Get Started Now <i className="bi bi-arrow-right ms-2" aria-hidden />
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="col-lg-6 image-column pe-lg-3">
|
||
<div className="position-relative h-100 w-100">
|
||
<img
|
||
className="w-100 h-100 rounded-end"
|
||
src="/images/call-to-action.jpg"
|
||
alt=""
|
||
style={{ objectFit: 'cover', minHeight: 280 }}
|
||
onError={(e) => {
|
||
const img = e.currentTarget
|
||
img.onerror = null
|
||
img.src = '/images/isgl_home.png'
|
||
}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="container-xxl py-3 home-section-xl content-section">
|
||
<div className="container">
|
||
<div className="row bg-light squared position-relative align-items-center g-0">
|
||
<div className="col-lg-6">
|
||
<div className="h-100 d-flex flex-column justify-content-center p-4 p-md-5">
|
||
<h2 className="mb-4 fs-2 fw-bold green-title">Our Curriculum and Grade Structure</h2>
|
||
<p>
|
||
Our program spans nine structured grade levels, each building upon the previous
|
||
year's knowledge to ensure a solid foundation in faith, character, and Islamic
|
||
learning.
|
||
</p>
|
||
<p>
|
||
Upon completing the 9th grade, students transition into our three-year Youth Program,
|
||
which emphasizes deeper community engagement, personal development and practical
|
||
application of Islamic principles. Participation in the Youth Program requires students
|
||
to be at least 15 years old, ensuring they are mature enough to benefit from its
|
||
advanced content.
|
||
</p>
|
||
<p>
|
||
Starting this academic year, children must be at least 6 years old by 12-31-2025 to
|
||
enroll in Grade 1. For younger learners, we are pleased to offer our newly established
|
||
Kindergarten class, welcoming children who are at least 5 years old by 12-31-2025. This
|
||
early education program is designed to introduce young minds to the basics of Islamic
|
||
teachings in a warm, age-appropriate environment.
|
||
</p>
|
||
<div>
|
||
<Link className="btn btn-home-cta py-3 px-5 mt-3" to="/register">
|
||
Get Started Now <i className="bi bi-arrow-right ms-2" aria-hidden />
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="col-lg-6 text-start py-4 py-lg-0">
|
||
<div className="image-container mx-auto mx-lg-0">
|
||
<img
|
||
src="/images/12.jpeg"
|
||
className="overlapping-image image1"
|
||
alt="Students and activities"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<button
|
||
type="button"
|
||
className={`btn btn-home-cta btn-lg rounded-1 back-to-top${showTop ? ' visible' : ''}`}
|
||
aria-label="Back to top"
|
||
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
|
||
>
|
||
<i className="bi bi-arrow-up" aria-hidden />
|
||
</button>
|
||
</div>
|
||
)
|
||
}
|