fix home page and add career page
Deploy to Shared Hosting / Shared hosting deploy (push) Failing after 50s
Tests / PHPUnit (push) Successful in 1m24s

This commit is contained in:
root
2026-09-01 00:44:04 -04:00
parent 6ae90d757b
commit 5b11e2d859
8 changed files with 1318 additions and 689 deletions
+296
View File
@@ -0,0 +1,296 @@
# Job Postings Feature — Implementation Plan
## 1. Overview
This feature has two sides:
- **Admin side** — create/manage job position templates, publish open positions, review applicant submissions.
- **Client side** — browse open positions, view details, and submit an application (with resume upload) that triggers a confirmation email.
---
## 2. Data Model
### JobTemplate
| Field | Type | Notes |
|---|---|---|
| `template_id` | UUID | Primary key |
| `title` | string | |
| `description` | text | |
| `department` | string | |
| `location` | string | |
| `employment_type` | string | e.g. full-time, part-time, contract |
| `requirements` | text | |
| `salary_range` | string | optional |
| `version` | int | increments on each edit |
| `is_active` | bool | soft-archive instead of delete |
| `created_by` | user ref | |
| `created_at` / `updated_at` | timestamp | |
### JobPosition
| Field | Type | Notes |
|---|---|---|
| `position_id` | UUID | **Unique ID per job, required** |
| `template_id` | UUID (nullable) | set if created from a template |
| `title`, `description`, `department`, `location`, `employment_type`, `requirements`, `salary_range` | — | copied from template at creation time (not a live reference) |
| `status` | enum | draft / open / closed / filled |
| `created_at` / `updated_at` | timestamp | |
| `posted_by` | user ref | |
### Application
| Field | Type | Notes |
|---|---|---|
| `application_id` | UUID | |
| `position_id` | FK | which job they applied to |
| `first_name`, `last_name` | string | required |
| `email` | string | required, validated |
| `phone` | string | required |
| `resume_file_url` | string | pointer to stored file |
| `submitted_at` | timestamp | |
| `status` | enum | new / reviewed / contacted / rejected / hired |
| `admin_notes` | text | for admin follow-up tracking |
---
## 3. Admin Side
### 3.1 Create Open Position
- Form fields: title, description, department, location, employment type, requirements, salary range.
- Option to start **blank** or **from a template**.
- On save: generate unique `position_id`, default status `draft` → publish sets `open`.
### 3.2 Job Templates (CRUD)
- List / create / edit / archive templates.
- **Editing behavior**: support both
- **Overwrite** current version, or
- **Save as new version** (keeps history)
- Version history viewable/revertible.
- Archiving a template never affects positions already created from it (fields are copied, not linked live).
### 3.3 Create Position from Template
- Admin selects a template → fields pre-fill a new position form → admin edits as needed → save generates a new `position_id`.
### 3.4 Review Applications
- Dashboard listing all submissions, filterable by position, status, or date.
- Detail view: applicant info, resume preview/download, position applied to.
- Status + notes fields so admin can track follow-up (contacted, rejected, hired).
- Nice-to-have: CSV export, direct email link to applicant.
---
## 4. Client Side
### 4.1 Open Positions Listing
- Public page listing all positions where `status = open`.
- Card/list view: title, department, location, short summary.
### 4.2 Position Detail Page
- Full description, requirements, etc.
- "Apply" call-to-action.
### 4.3 Application Form
- Fields: First Name, Last Name, Email, Phone, Resume upload (PDF/DOC).
- Client-side validation: required fields, email format, file type/size limits.
- Submits to create an `Application` tied to `position_id`.
### 4.4 Confirmation Email
- Sent automatically to the applicant's email on successful submission.
- References the position title and sets expectation of review/follow-up.
- Optional: parallel notification email to admin/HR inbox.
---
## 5. API Endpoints (suggested)
**Admin**
```
POST /admin/templates
PUT /admin/templates/:id
GET /admin/templates
POST /admin/positions (optional template_id)
PUT /admin/positions/:id
GET /admin/positions
GET /admin/applications (filter by position, status)
PATCH /admin/applications/:id (update status/notes)
```
**Client (public)**
```
GET /positions (open only)
GET /positions/:id
POST /positions/:id/apply (multipart form, includes resume)
```
---
## 6. Infrastructure Notes
- **File storage**: resumes go to object storage (S3 or equivalent); store the file URL/key on the Application record, not the binary in the database.
- **Email**: use a transactional email provider (SendGrid, SES, Postmark) for confirmation + admin notification emails, driven by templates.
- **Auth**: admin routes require authenticated/role-gated access; client routes remain public.
---
## 7. Database Migrations
Assumes PostgreSQL syntax (adjust types for MySQL/SQLite as needed). Each migration is additive and ordered so foreign keys resolve correctly.
### Migration 001 — create `job_templates`
```sql
-- up
CREATE TABLE job_templates (
template_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title VARCHAR(255) NOT NULL,
description TEXT,
department VARCHAR(255),
location VARCHAR(255),
employment_type VARCHAR(50),
requirements TEXT,
salary_range VARCHAR(100),
version INT NOT NULL DEFAULT 1,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_by UUID REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_job_templates_active ON job_templates (is_active);
-- down
DROP TABLE IF EXISTS job_templates;
```
### Migration 002 — create `job_template_versions` (version history)
```sql
-- up
CREATE TABLE job_template_versions (
version_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
template_id UUID NOT NULL REFERENCES job_templates(template_id) ON DELETE CASCADE,
version INT NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
department VARCHAR(255),
location VARCHAR(255),
employment_type VARCHAR(50),
requirements TEXT,
salary_range VARCHAR(100),
saved_by UUID REFERENCES users(id),
saved_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_template_versions_template_id ON job_template_versions (template_id);
-- down
DROP TABLE IF EXISTS job_template_versions;
```
### Migration 003 — create `job_positions`
```sql
-- up
CREATE TYPE position_status AS ENUM ('draft', 'open', 'closed', 'filled');
CREATE TABLE job_positions (
position_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
template_id UUID REFERENCES job_templates(template_id) ON DELETE SET NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
department VARCHAR(255),
location VARCHAR(255),
employment_type VARCHAR(50),
requirements TEXT,
salary_range VARCHAR(100),
status position_status NOT NULL DEFAULT 'draft',
posted_by UUID REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_job_positions_status ON job_positions (status);
-- down
DROP TABLE IF EXISTS job_positions;
DROP TYPE IF EXISTS position_status;
```
### Migration 004 — create `applications`
```sql
-- up
CREATE TYPE application_status AS ENUM ('new', 'reviewed', 'contacted', 'rejected', 'hired');
CREATE TABLE applications (
application_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
position_id UUID NOT NULL REFERENCES job_positions(position_id) ON DELETE CASCADE,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL,
phone VARCHAR(30) NOT NULL,
resume_file_url TEXT NOT NULL,
status application_status NOT NULL DEFAULT 'new',
admin_notes TEXT,
submitted_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_applications_position_id ON applications (position_id);
CREATE INDEX idx_applications_status ON applications (status);
CREATE INDEX idx_applications_email ON applications (email);
-- down
DROP TABLE IF EXISTS applications;
DROP TYPE IF EXISTS application_status;
```
### Migration 005 — updated_at auto-touch triggers (optional, Postgres)
```sql
-- up
CREATE OR REPLACE FUNCTION set_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_job_templates_updated_at
BEFORE UPDATE ON job_templates
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
CREATE TRIGGER trg_job_positions_updated_at
BEFORE UPDATE ON job_positions
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
-- down
DROP TRIGGER IF EXISTS trg_job_templates_updated_at ON job_templates;
DROP TRIGGER IF EXISTS trg_job_positions_updated_at ON job_positions;
DROP FUNCTION IF EXISTS set_updated_at();
```
### Migration order & notes
1. `job_templates` → 2. `job_template_versions` → 3. `job_positions` → 4. `applications` → 5. triggers.
2. `users` table is assumed to already exist (for `created_by` / `posted_by`); drop those FK constraints if no auth/user table exists yet.
3. Run each migration's `up` in order; `down` scripts reverse in the opposite order for rollback.
4. If using a migration tool (Knex, Prisma, Sequelize, Alembic, Rails ActiveRecord, etc.), split each numbered migration above into that tool's file format/naming convention — the SQL logic stays the same.
---
## 8. Build Phases
| Phase | Scope |
|---|---|
| 1 | Data models + admin CRUD for Positions (no templates yet) |
| 2 | Template CRUD + versioning + "create position from template" |
| 3 | Client-facing listing + detail pages |
| 4 | Application form + file upload + submission handling |
| 5 | Confirmation email + admin notification email |
| 6 | Admin review dashboard (applications list, status, notes) |
| 7 | Polish: validation, admin auth/permissions, testing |
---
## 9. Open Questions
- How is admin access authenticated/restricted (login system, roles)?
- Should duplicate applications (same email + position) be blocked or allowed?
- What resume file types/size limits are acceptable?
- Should closed positions remain visible (marked "closed") or disappear from the client list entirely?
+52
View File
@@ -0,0 +1,52 @@
# Scope Discipline Rules
These rules override any general instinct to "improve while I'm in there." Follow them on every task, no exceptions.
## Before touching any code
1. Restate the task in one sentence: what behavior must change, and what the expected outcome is.
2. Identify the smallest set of files/functions responsible for that behavior. This is your **allowed scope**. Everything else is **protected**.
3. Read the relevant code before editing it. Do not edit based on assumptions about how it probably works.
## The hard rule: ask before expanding scope
If, while working, you find that:
- a file outside your allowed scope needs to change,
- a dependency needs to be added/updated,
- a test needs modification,
- an unrelated bug is blocking you,
- or a "cleaner" implementation would touch more than the minimum,
**stop and ask me before making that change.** Explain:
- what you were trying to do,
- why the fix requires going outside the original scope,
- exactly what you want to change and where.
Wait for my answer. Do not proceed on your own judgment, even if you're confident it's correct or trivial.
This applies even to small things (renaming a variable for clarity, fixing a typo in an unrelated comment, reformatting a block you had to scroll past). If it's not required to satisfy the request, ask first.
## While editing
- Make the fewest-line, fewest-file change that correctly satisfies the request.
- Preserve existing naming, structure, patterns, and formatting. Match the codebase's existing style, don't impose your own.
- Never run project-wide formatters/linters-with-autofix/import-organizers as a side effect of a small change.
- Never touch tests except to add new ones that validate the requested behavior — and only after confirming that's in scope.
- Treat any uncommitted/staged changes already in the working tree as off-limits. Don't revert, reset, or absorb them into your edit.
## Before reporting done
Review your own diff, file by file, line by line. For anything you can't justify with "this was required by the explicit request," revert it.
Then report:
- **Files changed** — list, with a one-line reason each tied directly to the request.
- **Scope confirmation** — explicitly state: "No files, dependencies, tests, or config outside this list were modified."
- **Anything you noticed but didn't touch** — unrelated bugs, tech debt, cleanup opportunities. Mention them, don't fix them.
## If the task genuinely can't be done without expanding scope
Say so plainly, explain what would need to change and why, and wait for confirmation. Don't silently do the bigger version, and don't pretend a partial/incorrect fix is complete.
---
**Default when uncertain: don't make the change, ask instead.**
+1
View File
@@ -222,6 +222,7 @@ $routes->post('/user/store', 'View\UserController::store');
$routes->get('/thankyou', 'View\UserController::thankyou'); // Thank you page route
$routes->get('/', 'View\UserController::home'); // Home page route
$routes->get('/about', 'View\UserController::about'); // About page route
$routes->get('/careers', 'View\UserController::careers'); // Careers page route
$routes->get('/classes', 'View\UserController::classes'); // Classes page route
$routes->get('/contact', 'View\UserController::contact'); // Contact Us page route
$routes->post('/user/login', 'AuthController::login');
+6
View File
@@ -117,6 +117,12 @@ class UserController extends BaseController
return view('/about');
}
// Method to show the careers page
public function careers()
{
return view('/careers');
}
// Method to show the classes page
public function classes()
{
+382
View File
@@ -0,0 +1,382 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Careers | Al Rahma Sunday School</title>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="Volunteer careers and openings at Al Rahma Sunday School" name="description">
<link href="<?= base_url('assets/images/favicon.ico') ?>" rel="icon">
<link href="<?= base_url('assets/boot_css/bootstrap.min.css') ?>" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Amiri:wght@400;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css" rel="stylesheet">
<link href="<?= base_url('css/style.css') ?>" rel="stylesheet">
<style>
:root {
--ink: #16262B;
--ink-soft: #3E4E51;
--paper: #F3EFE3;
--paper-deep: #E9E3D2;
--sage: #E7EEE6;
--primary: #0B5D52;
--primary-dark: #073F38;
--accent: #C6963C;
--accent-soft: #EFE1BC;
--line: rgba(22, 38, 43, 0.12);
}
* {
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
color: var(--ink);
background-color: var(--paper);
overflow-x: hidden;
line-height: 1.6;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Amiri', serif;
font-weight: 700;
color: var(--ink);
line-height: 1.2;
}
p {
color: var(--ink-soft);
}
.rule {
width: 56px;
height: 3px;
background-color: var(--accent);
border: none;
margin: 0 0 1.25rem;
}
.btn-brand {
display: inline-flex;
align-items: center;
gap: 0.6rem;
background-color: var(--primary);
color: #fff;
border: none;
border-radius: 2px;
padding: 0.85rem 1.75rem;
font-weight: 600;
font-size: 0.95rem;
text-decoration: none;
transition: background-color 0.2s ease;
}
.btn-brand:hover {
background-color: var(--primary-dark);
color: #fff;
}
.btn-brand-sm {
display: inline-flex;
align-items: center;
gap: 0.5rem;
background-color: var(--primary);
color: #fff;
border: none;
border-radius: 2px;
padding: 0.6rem 1.25rem;
font-weight: 600;
font-size: 0.9rem;
text-decoration: none;
}
.btn-brand-sm:hover {
background-color: var(--primary-dark);
color: #fff;
}
.btn-brand-outline {
display: inline-flex;
align-items: center;
gap: 0.5rem;
border: 1.5px solid var(--ink);
color: var(--ink);
border-radius: 999px;
padding: 0.5rem 1.25rem;
font-weight: 600;
font-size: 0.9rem;
text-decoration: none;
}
.btn-brand-outline:hover {
background-color: var(--ink);
color: var(--paper);
}
.btn-brand-danger {
display: inline-flex;
align-items: center;
gap: 0.5rem;
border: 1.5px solid #a33;
color: #a33;
border-radius: 999px;
padding: 0.5rem 1.25rem;
font-weight: 600;
font-size: 0.9rem;
text-decoration: none;
}
.btn-brand-danger:hover {
background-color: #a33;
color: #fff;
}
/* Navbar */
.navbar {
background-color: var(--paper) !important;
border-bottom: 1px solid var(--line);
padding-top: 0.6rem;
padding-bottom: 0.6rem;
}
.navbar .nav-link {
color: var(--ink-soft);
font-weight: 500;
}
.navbar .nav-link:hover {
color: var(--primary);
}
.navbar-text {
color: var(--ink-soft);
font-size: 0.9rem;
}
/* Hero */
.careers-hero {
position: relative;
background: linear-gradient(rgba(7, 63, 56, .88), rgba(7, 63, 56, .88)), url("<?= base_url('images/call-to-action.jpg') ?>") center center / cover no-repeat;
min-height: 320px;
display: flex;
align-items: center;
color: var(--paper);
overflow: hidden;
}
.careers-hero::before {
content: "";
position: absolute;
inset: 0;
opacity: 0.14;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='84' height='84' viewBox='0 0 84 84'%3E%3Cg fill='none' stroke='%23C6963C' stroke-width='1'%3E%3Cpath d='M42 2 L62 22 L42 42 L22 22 Z'/%3E%3Cpath d='M0 42 L20 22 L40 42 L20 62 Z'/%3E%3Cpath d='M42 42 L62 22 L84 42 L62 62 Z'/%3E%3Cpath d='M42 42 L62 62 L42 84 L22 62 Z'/%3E%3C/g%3E%3C/svg%3E");
background-size: 84px 84px;
pointer-events: none;
}
.careers-hero .container {
position: relative;
}
.careers-hero h1 {
color: var(--paper);
font-size: 2.6rem;
margin-bottom: 1rem;
}
.careers-hero p.lead {
color: #DCE6DD;
font-size: 1.1rem;
max-width: 56ch;
}
/* Culture strip */
.culture-item {
background: #ffffff;
border-top: 3px solid var(--accent);
padding: 1.75rem 1.5rem;
height: 100%;
}
.culture-item h3 {
font-size: 1.25rem;
margin-bottom: 0.6rem;
}
.culture-item p {
margin-bottom: 0;
}
/* Openings */
.openings-section {
background-color: var(--sage);
}
.openings-intro h2 {
font-size: 2rem;
}
.opening-card {
border: 1px solid var(--line);
background: #ffffff;
height: 100%;
padding: 1.75rem;
position: relative;
}
.opening-card h3 {
color: var(--ink);
font-size: 1.3rem;
margin-bottom: 0.4rem;
}
.opening-meta {
color: var(--primary);
font-weight: 600;
font-size: 0.85rem;
letter-spacing: 0.01em;
margin-bottom: 1rem;
}
.opening-list {
padding-left: 1.1rem;
margin-bottom: 1.5rem;
color: var(--ink-soft);
}
.opening-list li {
margin-bottom: 0.4rem;
}
.opening-list li::marker {
color: var(--accent);
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light sticky-top px-4 px-lg-5">
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 56px; width: 56px; border-radius: 50%; object-fit: contain; background-color: #fff;">
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<div class="navbar-nav mx-auto">
<a href="<?= base_url('/') ?>" class="nav-item nav-link">Home Page</a>
</div>
<div class="d-flex align-items-center">
<?php if (session()->get('is_logged_in')): ?>
<span class="navbar-text me-3">Welcome, <?= esc(session()->get('user_name')) ?></span>
<a href="<?= base_url('/dashboard') ?>" class="btn-brand-outline me-2">Dashboard <i class="fa fa-tachometer-alt"></i></a>
<a href="<?= base_url('/logout') ?>" class="btn-brand-danger">Logout <i class="fa fa-sign-out-alt"></i></a>
<?php else: ?>
<a href="<?= base_url('/login') ?>" class="btn-brand-outline me-2">Login <i class="fa fa-arrow-right"></i></a>
<a href="<?= base_url('/register') ?>" class="btn-brand-sm">Register <i class="fa fa-arrow-right"></i></a>
<?php endif; ?>
</div>
</div>
</nav>
<header class="careers-hero">
<div class="container py-5">
<div class="col-lg-8">
<h1>Open Positions</h1>
<p class="lead mb-4">Serve with Al Rahma Sunday School and help students grow in Quran, Arabic, Islamic Studies and character.</p>
<a class="btn-brand" href="#openings">View Openings <i class="fa fa-arrow-down"></i></a>
</div>
</div>
</header>
<main>
<section class="container-xxl py-5">
<div class="container">
<div class="row g-4">
<div class="col-md-4">
<div class="culture-item">
<h3>Faith-Centered Work</h3>
<p>Support a school community focused on Islamic learning, strong character and service.</p>
</div>
</div>
<div class="col-md-4">
<div class="culture-item">
<h3>Supportive Team</h3>
<p>Collaborate with teachers, assistants and administrators committed to student success.</p>
</div>
</div>
<div class="col-md-4">
<div class="culture-item">
<h3>Sunday Schedule</h3>
<p>Volunteer in a structured weekend program serving families in the Greater Lowell community.</p>
</div>
</div>
</div>
</div>
</section>
<section id="openings" class="openings-section py-5">
<div class="container">
<div class="text-center openings-intro mb-5">
<hr class="rule mx-auto">
<h2>Current Open Positions</h2>
<p class="mb-0">Review the available roles below and apply by creating an account.</p>
</div>
<div class="row g-4">
<div class="col-lg-4">
<article class="opening-card">
<h3>Volunteer Teacher</h3>
<div class="opening-meta">Instruction &middot; Chelmsford, MA &middot; Part-time volunteer</div>
<p>Lead Quran, Arabic or Islamic Studies lessons in a warm classroom environment.</p>
<ul class="opening-list">
<li>Prepare weekly lessons and classroom activities.</li>
<li>Guide students with patience and clear communication.</li>
<li>Partner with school administration and families when needed.</li>
</ul>
<a class="btn-brand-sm" href="<?= base_url('/register') ?>">Apply Now <i class="fa fa-arrow-right"></i></a>
</article>
</div>
<div class="col-lg-4">
<article class="opening-card">
<h3>Teacher Assistant</h3>
<div class="opening-meta">Classroom Support &middot; Chelmsford, MA &middot; Part-time volunteer</div>
<p>Support teachers and help students stay engaged throughout Sunday classes.</p>
<ul class="opening-list">
<li>Assist with activities, materials and classroom routines.</li>
<li>Provide individual support to students during lessons.</li>
<li>Help maintain a respectful and focused learning environment.</li>
</ul>
<a class="btn-brand-sm" href="<?= base_url('/register') ?>">Apply Now <i class="fa fa-arrow-right"></i></a>
</article>
</div>
<div class="col-lg-4">
<article class="opening-card">
<h3>Administrative Volunteer</h3>
<div class="opening-meta">Operations &middot; Chelmsford, MA &middot; Part-time volunteer</div>
<p>Help the school operate smoothly through weekly administrative and event support.</p>
<ul class="opening-list">
<li>Support communications, supplies and weekly coordination.</li>
<li>Help organize school events and program logistics.</li>
<li>Assist staff with structured tasks before or during Sunday school.</li>
</ul>
<a class="btn-brand-sm" href="<?= base_url('/register') ?>">Apply Now <i class="fa fa-arrow-right"></i></a>
</article>
</div>
</div>
</div>
</section>
</main>
<?php include(__DIR__ . '/partials/footer.php'); ?>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
+480 -637
View File
File diff suppressed because it is too large Load Diff
+37 -52
View File
@@ -1,21 +1,3 @@
<?php
$userRole = session()->get('role'); // assuming you store it as 'role'
$quickTourUrl = base_url('/help_center'); // default
switch ($userRole) {
case 'parent':
$quickTourUrl = base_url('/parent');
break;
case 'teacher':
$quickTourUrl = base_url('/teacher');
break;
case 'teacher_assistant':
$quickTourUrl = base_url('/teacher');
break;
}
?>
<link rel="stylesheet" href="<?= base_url('assets/css/landing_page.css') ?>">
<footer class="footer mt-auto custom-footer text-white py-4">
<div class="container">
<div class="row align-items-start justify-content-between">
@@ -48,47 +30,50 @@ switch ($userRole) {
<li>
<a href="/account_creation_guide.pdf" target="_blank" rel="noopener noreferrer"
class="pdf-link" data-filename="account_creation_guide.pdf">
<i class="fas fa-file-pdf"></i>How To Create An Account
<i class="fas fa-file-pdf me-1"></i>How To Create An Account
</a>
</li>
</ul>
</div>
</div>
</div>
<br>
<p class="text-center text-white">© 2026 Al Rahma Sunday School by ISGL. All Rights Reserved.</p>
<div class="row">
<div class="col-12">
<p class="rights-line">© 2026 Al Rahma Sunday School by ISGL. All Rights Reserved.</p>
</div>
</div>
</footer>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Check if PDF files exist and add error handling
document.querySelectorAll('.pdf-link').forEach(link => {
const pdfUrl = link.getAttribute('href');
<style>
.custom-footer {
background-color: var(--ink, #16262B);
color: #CFC9B7;
}
// Test if the PDF exists
fetch(pdfUrl, {
method: 'HEAD'
})
.then(response => {
if (!response.ok) {
// File doesn't exist or is inaccessible
link.style.opacity = '0.7';
link.title = 'File might be temporarily unavailable';
console.warn('PDF might be missing:', pdfUrl);
.custom-footer .info-list li {
margin-bottom: 0.5rem;
color: #CFC9B7;
}
// Modify click behavior to show helpful message
link.addEventListener('click', function(e) {
if (!confirm('The PDF file might be temporarily unavailable. Try to open it anyway?')) {
e.preventDefault();
}
});
}
})
.catch(error => {
console.error('Error checking PDF:', pdfUrl, error);
link.style.opacity = '0.7';
link.title = 'File check failed';
});
});
});
</script>
.custom-footer .info-list i {
color: var(--accent, #C6963C);
}
.custom-footer .pdf-link {
color: #CFC9B7;
text-decoration: none;
}
.custom-footer .pdf-link:hover {
color: var(--accent, #C6963C);
text-decoration: underline;
}
.custom-footer .rights-line {
text-align: center;
color: #7FBFA0;
margin: 1.5rem auto 0;
max-width: none;
width: 100%;
}
</style>
+64
View File
@@ -0,0 +1,64 @@
# Careers Section (Home Page) — Implementation Plan
## 1. Goals & Scope
- Attract candidates and showcase company culture directly from the home page
- Careers lives as a **section on the home page**, not a standalone `/careers` page
- Section should link out to a full job detail view or an ATS for the actual application step
- Decide: in-house application handling vs. linking out to an ATS (Greenhouse, Lever, Workable, etc.)
## 2. Content Structure (Home Page Section)
- **Section heading/hero** — tagline + short pitch on why to work here
- **Culture/values snippet** — a few photos or highlights, benefits teaser (health, remote work, PTO, equity, etc.)
- **Open positions preview** — short list (e.g., top 35) with title, department, location, type
- **"View all openings" CTA** — links to full listing (could be a modal, an anchor-expanded list, or an external ATS board)
- **Application CTA** — "Apply Now" per role, linking to a form, email, or ATS
- **Optional extras** — employee testimonials, office photos/video, perks grid
## 3. Data Model
Fields per job posting:
```
title
department
location
employment_type
description
requirements
salary_range (optional)
posted_date
status (open/closed)
apply_link
```
Store in a CMS (Sanity, Contentful, Strapi) or a simple JSON/database table if no CMS exists.
## 4. Technical Approach
- **Static list** — hardcode JSON if roles change rarely
- **CMS-driven** — non-technical team can add/remove postings without code
- **ATS integration** — pull live listings via API (Greenhouse/Lever both offer public job board APIs) for least maintenance
- **Application handling** — form → email service (e.g., SendGrid) or direct link to ATS application page
## 5. Pages / Routes
- `/` (home page) — Careers section embedded, e.g. `#careers` anchor for nav linking
- `/careers/[job-slug]` — individual job detail page (linked from the home section)
- `/careers/apply/[job-slug]` — application form (optional)
- Consider adding "Careers" to the main nav, scrolling/linking to the `#careers` section
## 6. Design Considerations
- Keep the section concise — it's part of the home page, not the main focus, so limit to a handful of featured/open roles
- Mobile-responsive job cards, and ensure the section doesn't overload home page load time
- Clear, prominent "View openings" / "Apply" CTAs
- SEO: `JobPosting` schema.org structured data on the linked job detail pages so roles surface in Google Jobs search
- Make sure the section is reachable via nav (e.g., "Careers" nav item scrolls to the section or links to `/#careers`)
## 7. Build Order
1. Design mockups/wireframe for the home page section (placement, hero, mini job list)
2. Set up data source (CMS or ATS API) for job postings
3. Build the home page Careers section + "view all/apply" links
4. Build job detail page(s) linked from the section
5. Wire up application flow
6. Add SEO schema markup on job detail pages
7. Test on mobile; submit a test application
8. Launch and monitor applications
## Open Questions
- What is the site built with (React/Next.js, WordPress, Webflow, plain HTML)?
- Use an existing ATS (Greenhouse/Lever) or handle applications via email/form?