forked from Ugric/Komodo-Hub
Merge pull request 'added programs' (#60) from TommyHEC/Komodo-Hub:main into main
Reviewed-on: Ugric/Komodo-Hub#60
This commit is contained in:
commit
79ecc1777f
9 files changed
+484
-27
No files matched your search
+76
-1
@@ -327,4 +327,79 @@ def mark_submission(submission_id, marks, feedback=""):
|
||||
UPDATE submissions
|
||||
SET marks = ?, feedback = ?, marked_at = ?
|
||||
WHERE submission_id = ?
|
||||
""", (marks, feedback, datetime.now(), submission_id))
|
||||
""", (marks, feedback, datetime.now(), submission_id))
|
||||
|
||||
|
||||
# ── programs ─────────────────────────────────────────────────────────────────
|
||||
|
||||
def create_program(leader_id, title, description=""):
|
||||
with get_db() as conn:
|
||||
conn.execute("""
|
||||
INSERT INTO programs (leader_id, title, description, created_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""", (leader_id, title, description, datetime.now()))
|
||||
|
||||
|
||||
def get_all_programs():
|
||||
with get_db() as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT * FROM programs ORDER BY created_at DESC"
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
def get_program_by_id(program_id):
|
||||
with get_db() as conn:
|
||||
row = conn.execute(
|
||||
"SELECT * FROM programs WHERE program_id = ? LIMIT 1",
|
||||
(program_id,)
|
||||
).fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
|
||||
def get_programs_by_leader(leader_id):
|
||||
with get_db() as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT * FROM programs WHERE leader_id = ? ORDER BY created_at DESC",
|
||||
(leader_id,)
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
def join_program(program_id, user_id):
|
||||
with get_db() as conn:
|
||||
conn.execute("""
|
||||
INSERT OR IGNORE INTO program_enrolments (program_id, user_id, enrolled_at)
|
||||
VALUES (?, ?, ?)
|
||||
""", (program_id, user_id, datetime.now()))
|
||||
|
||||
|
||||
def is_enrolled_in_program(program_id, user_id):
|
||||
with get_db() as conn:
|
||||
row = conn.execute(
|
||||
"SELECT 1 FROM program_enrolments WHERE program_id = ? AND user_id = ?",
|
||||
(program_id, user_id)
|
||||
).fetchone()
|
||||
return row is not None
|
||||
|
||||
|
||||
def get_programs_for_user(user_id):
|
||||
with get_db() as conn:
|
||||
rows = conn.execute("""
|
||||
SELECT p.*
|
||||
FROM programs p
|
||||
JOIN program_enrolments e ON p.program_id = e.program_id
|
||||
WHERE e.user_id = ?
|
||||
ORDER BY e.enrolled_at DESC
|
||||
""", (user_id,)).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
def search_programs(query):
|
||||
with get_db() as conn:
|
||||
rows = conn.execute("""
|
||||
SELECT * FROM programs
|
||||
WHERE instr(LOWER(COALESCE(CAST(title AS TEXT), '')), ?1) > 0
|
||||
OR instr(LOWER(COALESCE(CAST(description AS TEXT), '')), ?1) > 0
|
||||
""", (query.lower(),)).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
+28
-4
@@ -11,7 +11,7 @@ SEED_SPECIES = [
|
||||
"Its dense forest habitat is rapidly disappearing due to agricultural expansion and "
|
||||
"illegal logging. Poaching for the illegal wildlife trade remains a severe threat. "
|
||||
"Conservation efforts include protected national parks, anti-poaching patrols, and "
|
||||
"community engagement programmes."
|
||||
"community engagement programs."
|
||||
),
|
||||
"category": "Mammal",
|
||||
"extinction_risk": "Critically Endangered",
|
||||
@@ -38,7 +38,7 @@ SEED_SPECIES = [
|
||||
"The Bali Myna is a striking white bird endemic to the island of Bali. Fewer than "
|
||||
"100 individuals survive in the wild, making it one of the rarest birds in the world. "
|
||||
"The primary drivers of its decline are the illegal pet trade and loss of its lowland "
|
||||
"forest habitat. Captive breeding and reintroduction programmes have been key "
|
||||
"forest habitat. Captive breeding and reintroduction programs have been key "
|
||||
"strategies in efforts to stabilise and grow its wild population."
|
||||
),
|
||||
"category": "Bird",
|
||||
@@ -82,7 +82,7 @@ SEED_SPECIES = [
|
||||
"The Celebes Crested Macaque is endemic to north-east Sulawesi, recognised by "
|
||||
"its jet-black coat and distinctive crest. Fewer than 5,500 individuals are thought "
|
||||
"to survive. The species is hunted for bushmeat and its forest habitat continues "
|
||||
"to shrink. Conservation programmes focus on community education, law enforcement, "
|
||||
"to shrink. Conservation programs focus on community education, law enforcement, "
|
||||
"and ecotourism as a sustainable alternative income source for local communities."
|
||||
),
|
||||
"category": "Mammal",
|
||||
@@ -108,6 +108,7 @@ def init_database():
|
||||
""")
|
||||
print("Database initialised.")
|
||||
seed_species()
|
||||
init_programs_database()
|
||||
|
||||
|
||||
def seed_species():
|
||||
@@ -198,7 +199,30 @@ def init_classes_database():
|
||||
print("Classes database tables initialised.")
|
||||
|
||||
|
||||
def init_programs_database():
|
||||
with get_db() as conn:
|
||||
conn.execute("""
|
||||
CREATE TABLE IF NOT EXISTS programs (
|
||||
program_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
leader_id INTEGER NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT DEFAULT '',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
conn.execute("""
|
||||
CREATE TABLE IF NOT EXISTS program_enrolments (
|
||||
program_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
enrolled_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (program_id, user_id)
|
||||
)
|
||||
""")
|
||||
print("Programs database tables initialised.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_database()
|
||||
init_accounts_database()
|
||||
init_classes_database()
|
||||
init_classes_database()
|
||||
init_programs_database()
|
||||
@@ -0,0 +1,127 @@
|
||||
from flask import render_template, Blueprint, request, redirect, url_for, flash, g
|
||||
from database.db_commands import (
|
||||
create_program,
|
||||
get_all_programs,
|
||||
get_program_by_id,
|
||||
get_programs_for_user,
|
||||
join_program,
|
||||
is_enrolled_in_program,
|
||||
search_programs,
|
||||
get_programs_by_leader,
|
||||
)
|
||||
|
||||
page = Blueprint("programs", __name__, url_prefix="/programs")
|
||||
|
||||
|
||||
def require_login():
|
||||
if not g.user:
|
||||
flash("You must be logged in to access that page.", "info")
|
||||
return redirect(url_for("home.home"))
|
||||
return None
|
||||
|
||||
@page.route("/")
|
||||
def programs_index():
|
||||
redirect_response = require_login()
|
||||
if redirect_response:
|
||||
return redirect_response
|
||||
|
||||
query = request.args.get("search-query", "").strip()
|
||||
|
||||
if query:
|
||||
results = search_programs(query)
|
||||
else:
|
||||
results = get_all_programs()
|
||||
user_id = g.user["user_id"]
|
||||
for prog in results:
|
||||
prog["enrolled"] = is_enrolled_in_program(prog["program_id"], user_id)
|
||||
|
||||
return render_template(
|
||||
"programs/programs.jinja",
|
||||
programs=results,
|
||||
query=query,
|
||||
)
|
||||
|
||||
@page.route("/create", methods=["GET", "POST"])
|
||||
def create():
|
||||
redirect_response = require_login()
|
||||
if redirect_response:
|
||||
return redirect_response
|
||||
|
||||
if g.user["account_type"] != "community_leader":
|
||||
flash("Only community leaders can create programs.", "info")
|
||||
return redirect(url_for("programs.programs_index"))
|
||||
|
||||
if request.method == "POST":
|
||||
title = request.form.get("title", "").strip()
|
||||
description = request.form.get("description", "").strip()
|
||||
|
||||
if not title:
|
||||
flash("A program title is required.", "info")
|
||||
return render_template("programs/create.jinja")
|
||||
|
||||
create_program(
|
||||
leader_id=g.user["user_id"],
|
||||
title=title,
|
||||
description=description,
|
||||
)
|
||||
flash("Program created successfully.", "info")
|
||||
return redirect(url_for("programs.my_programs"))
|
||||
|
||||
return render_template("programs/create.jinja")
|
||||
|
||||
|
||||
@page.route("/my")
|
||||
def my_programs():
|
||||
redirect_response = require_login()
|
||||
if redirect_response:
|
||||
return redirect_response
|
||||
|
||||
user_id = g.user["user_id"]
|
||||
|
||||
if g.user["account_type"] == "community_leader":
|
||||
programs = get_programs_by_leader(user_id)
|
||||
else:
|
||||
programs = get_programs_for_user(user_id)
|
||||
|
||||
return render_template(
|
||||
"programs/my_programs.jinja",
|
||||
programs=programs,
|
||||
)
|
||||
|
||||
@page.route("/<int:program_id>")
|
||||
def view_program(program_id):
|
||||
redirect_response = require_login()
|
||||
if redirect_response:
|
||||
return redirect_response
|
||||
|
||||
program = get_program_by_id(program_id)
|
||||
if not program:
|
||||
flash("Program not found.", "info")
|
||||
return redirect(url_for("programs.programs_index"))
|
||||
|
||||
enrolled = is_enrolled_in_program(program_id, g.user["user_id"])
|
||||
|
||||
return render_template(
|
||||
"programs/view_program.jinja",
|
||||
program=program,
|
||||
enrolled=enrolled,
|
||||
)
|
||||
|
||||
@page.route("/<int:program_id>/join", methods=["POST"])
|
||||
def join(program_id):
|
||||
redirect_response = require_login()
|
||||
if redirect_response:
|
||||
return redirect_response
|
||||
|
||||
program = get_program_by_id(program_id)
|
||||
if not program:
|
||||
flash("Program not found.", "info")
|
||||
return redirect(url_for("programs.programs_index"))
|
||||
|
||||
if is_enrolled_in_program(program_id, g.user["user_id"]):
|
||||
flash("You are already enrolled in this program.", "info")
|
||||
return redirect(url_for("programs.view_program", program_id=program_id))
|
||||
|
||||
join_program(program_id, g.user["user_id"])
|
||||
flash(f"You have joined \"{program['title']}\".", "info")
|
||||
return redirect(url_for("programs.view_program", program_id=program_id))
|
||||
@@ -11,31 +11,26 @@
|
||||
|
||||
<section style="padding: 0 20px 40px 20px; max-width: 900px; margin: 0 auto;">
|
||||
|
||||
{#Enrolled Programmes (everyone sees this)#}
|
||||
<div class="container-full" style="margin-bottom: 20px;">
|
||||
<h2>Your Programmes</h2>
|
||||
{% if enrolled_programmes %}
|
||||
<div class="species-grid">
|
||||
{% for prog in enrolled_programmes %}
|
||||
<div class="container-lite">
|
||||
<h3>{{ prog.name }}</h3>
|
||||
<p>{{ prog.description }}</p>
|
||||
{{ button(url='/programmes/' ~ prog.id, button_text='View', extra_classes='homepage-btn') }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p style="color: var(--text); opacity: 0.6;">You are not enrolled in any programmes yet.</p>
|
||||
{{ button(url='/programmes', button_text='Browse Programmes', extra_classes='homepage-btn') }}
|
||||
{% endif %}
|
||||
{#Enrolled Programs (everyone except community leaders sees this)#}
|
||||
{% if user.account_type != "community_leader" %}
|
||||
<div class="container-full" style="margin-bottom: 20px">
|
||||
<h2>Programs</h2>
|
||||
<p>Browse and join programs here</p>
|
||||
<div class="homepage-buttons" style="justify-content: flex-start; margin-top: 1rem;">
|
||||
{{ button(url='/programs', button_text='Browse Programs', extra_classes='homepage-btn') }}
|
||||
{{ button(url='/programs/my', button_text='Joined Programs', extra_classes='homepage-btn') }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{#Student#}
|
||||
{% if user.account_type == "student" %}
|
||||
<div class="container-full" style="margin-bottom: 20px;">
|
||||
<h2>Classes</h2>
|
||||
<p>View the classes you have joined.</p>
|
||||
{{ button(url='/classes', button_text='View Classes', extra_classes='homepage-btn') }}
|
||||
<div class="homepage-buttons" style="justify-content: flex-start; margin-top: 1rem;">
|
||||
{{ button(url='/classes', button_text='View Classes', extra_classes='homepage-btn') }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -53,9 +48,12 @@
|
||||
{#Community Leader#}
|
||||
{% if user.account_type == "community_leader" %}
|
||||
<div class="container-full" style="margin-bottom: 20px;">
|
||||
<h2>Programmes</h2>
|
||||
<p>Create a new conservation programme for others to join.</p>
|
||||
{{ button(url='/programmes/create', button_text='Create a Programme', extra_classes='homepage-btn') }}
|
||||
<h2>Programs</h2>
|
||||
<p>Manage your conservation programs or create a new one.</p>
|
||||
<div class="homepage-buttons" style="justify-content: flex-start; margin-top: 1rem;">
|
||||
{{ button(url='/programs/my', button_text='My Programs', extra_classes='homepage-btn') }}
|
||||
{{ button(url='/programs/create', button_text='Create a Program', extra_classes='homepage-btn') }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
{% extends "base.jinja" %}
|
||||
{% block title %}Create a Program{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<section id="hero" style="padding: 40px 20px; text-align: center;">
|
||||
<h1>Create a Program</h1>
|
||||
<p style="max-width: 700px; margin: 0.5rem auto;">
|
||||
Set up a new conservation program for others to discover and join.
|
||||
</p>
|
||||
</section>
|
||||
<section class="form-section">
|
||||
<div class="container">
|
||||
<h2 style="margin-bottom: 1rem;">Program Details</h2>
|
||||
<form action="/programs/create" method="POST" class="form">
|
||||
<div class="form-group">
|
||||
<label for="title">Program Title</label>
|
||||
<input
|
||||
type="text"
|
||||
id="title"
|
||||
name="title"
|
||||
placeholder="e.g. Sumatran Tiger Patrol"
|
||||
required
|
||||
maxlength="120"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="description">Description</label>
|
||||
<textarea
|
||||
id="description"
|
||||
name="description"
|
||||
rows="6"
|
||||
placeholder="Describe the goals, activities, and impact of your program..."
|
||||
maxlength="2000"
|
||||
></textarea>
|
||||
</div>
|
||||
<button type="submit" class="submit-button" style="margin-top: 10px;">
|
||||
Create Program
|
||||
</button>
|
||||
</form>
|
||||
<div style="text-align: center; margin-top: 1rem;">
|
||||
<a href="/programs/my" style="opacity: 0.7; font-size: 0.9rem;">
|
||||
← Back to my programs
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,66 @@
|
||||
{% extends "base.jinja" %}
|
||||
{% block title %}My Programs{% endblock %}
|
||||
{% block content %}
|
||||
{% from 'button.jinja' import button %}
|
||||
|
||||
<section id="hero" style="padding: 40px 20px; text-align: center;">
|
||||
{% if user.account_type == 'community_leader' %}
|
||||
<h1>Your Programs</h1>
|
||||
<p style="max-width: 700px; margin: 0.5rem auto;">
|
||||
Programs you have created and are running.
|
||||
</p>
|
||||
{% else %}
|
||||
<h1>My Programs</h1>
|
||||
<p style="max-width: 700px; margin: 0.5rem auto;">
|
||||
Conservation programs you are enrolled in.
|
||||
</p>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
<section style="padding: 0 20px 60px 20px; max-width: 960px; margin: 0 auto;">
|
||||
|
||||
{% if programs %}
|
||||
<div class="container-full">
|
||||
<h2>Your programs</h2>
|
||||
{% for prog in programs %}
|
||||
<div class="container-lite" style="display: flex; flex-direction: column; gap: 10px; padding: 20px; margin-top: 20px">
|
||||
<h3 style="margin-bottom: 4px;">{{ prog.title }}</h3>
|
||||
<p style="opacity: 0.8; flex: 1;">
|
||||
{{ prog.description if prog.description else 'No description provided.' }}
|
||||
</p>
|
||||
<div style="margin-top: 8px;">
|
||||
<div class="homepage-buttons" style="justify-content: flex-start; margin-top: 1rem;">
|
||||
{{ button(url='/programs/' ~ prog.program_id, button_text='View', extra_classes='homepage-btn') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="container-full" style="text-align: center;">
|
||||
{% if user.account_type == 'community_leader' %}
|
||||
<p style="opacity: 0.7;">You have not created any programs yet.</p>
|
||||
<div class="homepage-buttons" style="justify-content: flex-start; margin-top: 1rem;">
|
||||
{{ button(url='/programs/create', button_text='Create a Program', extra_classes='homepage-btn') }}
|
||||
</div>
|
||||
{% else %}
|
||||
<p style="opacity: 0.7;">You are not enrolled in any programs yet.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Action bar at the bottom #}
|
||||
<div style="margin-top: 24px; display: flex; gap: 12px; flex-wrap: wrap; justify-content: center;">
|
||||
{% if user.account_type == 'community_leader' %}
|
||||
<div class="homepage-buttons" style="justify-content: flex-start; margin-top: 1rem;">
|
||||
{{ button(url='/programs/create', button_text='+ Create New Program', extra_classes='homepage-btn') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="homepage-buttons" style="justify-content: flex-start; margin-top: 1rem;">
|
||||
{{ button(url='/programs', button_text='Browse All Programs', extra_classes='homepage-btn') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,84 @@
|
||||
{% extends "base.jinja" %}
|
||||
{% block title %}Programs{% endblock %}
|
||||
{% block content %}
|
||||
{% from 'button.jinja' import button %}
|
||||
{% from 'search_bar.jinja' import search_bar %}
|
||||
|
||||
<section id="hero" style="padding: 40px 20px; text-align: center;">
|
||||
<h1>Conservation Programs</h1>
|
||||
<p style="max-width: 700px; margin: 0.5rem auto;">
|
||||
Discover and join community-led conservation programs, or search for one that matches your interests.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section style="padding: 0 20px 60px 20px; max-width: 960px; margin: 0 auto;">
|
||||
|
||||
{# Search bar #}
|
||||
<div class="container-full" style="margin-bottom: 20px;">
|
||||
<h2>Search programs...</h2>
|
||||
{{ search_bar('/programs', query) }}
|
||||
|
||||
{% if query %}
|
||||
<p style="text-align: center; opacity: 0.7; margin-top: -1rem;">
|
||||
{{ programs | length }} result{{ 's' if programs | length != 1 }} for
|
||||
<strong>"{{ query }}"</strong>
|
||||
— <a href="/programs">clear</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Results grid #}
|
||||
{% if programs %}
|
||||
<div class="container-full">
|
||||
<h2>Programs...</h2>
|
||||
{% for prog in programs %}
|
||||
<div class="container-lite" style="display: flex; flex-direction: column; gap: 10px; padding: 20px; margin-top: 20px">
|
||||
<h3 style="margin-bottom: 4px;">{{ prog.title }}</h3>
|
||||
<p style="opacity: 0.8; flex: 1;">
|
||||
{{ prog.description if prog.description else 'No description provided.' }}
|
||||
</p>
|
||||
<div style="height: 10px;"></div>
|
||||
<div class="button">
|
||||
{{ button(url='/programs/' ~ prog.program_id, button_text='View', extra_classes='homepage-btn') }}
|
||||
{% if prog.enrolled %}
|
||||
<span class="link_button homepage-btn"
|
||||
style="background-color: var(--accent); cursor: default; text-align: center;">
|
||||
✓ Enrolled
|
||||
</span>
|
||||
<div style="height: 10px;"></div>
|
||||
{% else %}
|
||||
<form action="/programs/{{ prog.program_id }}/join" method="POST" style="display:inline;">
|
||||
<button type="submit" class="link_button homepage-btn"
|
||||
style="border: none; cursor: pointer;">
|
||||
Join
|
||||
</button>
|
||||
</form>
|
||||
<div style="height: 10px;"></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="container-full" style="text-align: center;">
|
||||
<p style="opacity: 0.7;">
|
||||
{% if query %}
|
||||
No programs matched your search.
|
||||
{% else %}
|
||||
There are no programs yet.
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Community leader shortcut #}
|
||||
{% if user and user.account_type == 'community_leader' %}
|
||||
<div class="container-full" style="margin-top: 20px; text-align: center;">
|
||||
<p>Want to run a new conservation program?</p>
|
||||
{{ button(url='/programs/create', button_text='Create a Program', extra_classes='homepage-btn') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,36 @@
|
||||
{% extends "base.jinja" %}
|
||||
{% block title %}{{ program.title }}{% endblock %}
|
||||
{% block content %}
|
||||
{% from 'button.jinja' import button %}
|
||||
|
||||
<section id="hero" style="padding: 40px 20px; text-align: center;">
|
||||
<h1>{{ program.title }}</h1>
|
||||
</section>
|
||||
<section style="padding: 0 20px 60px 20px; max-width: 800px; margin: 0 auto;">
|
||||
<div class="container-full">
|
||||
<p style="opacity: 0.8; font-size: 1.05rem; line-height: 1.7; margin-bottom: 1.5rem;">
|
||||
{{ program.description if program.description else 'No description has been provided for this program.' }}
|
||||
</p>
|
||||
<div class="split"></div>
|
||||
<p style="opacity: 0.6; font-size: 0.85rem; margin-top: 1rem;">
|
||||
Program created on {{ program.created_at[:10] if program.created_at else 'unknown date' }}.
|
||||
</p>
|
||||
<div style="margin-top: 1.5rem; display: flex; gap: 12px; flex-wrap: wrap; align-items: center;">
|
||||
{% if enrolled %}
|
||||
<span class="link_button homepage-btn"
|
||||
style="background-color: var(--accent); cursor: default; text-align: center;">
|
||||
✓ You are enrolled
|
||||
</span>
|
||||
{% else %}
|
||||
<form action="/programs/{{ program.program_id }}/join" method="POST" style="display: contents;">
|
||||
<button type="submit" class="link_button homepage-btn"
|
||||
style="border: none; cursor: pointer;">
|
||||
Join This Program
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{{ button(url='/programs', button_text='← All Programs', extra_classes='homepage-btn') }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -3,7 +3,7 @@
|
||||
{# e.g. for the species search: "/species/search" #}
|
||||
{# used in species.jinja #}
|
||||
<form action="{{destination}}" method="GET" class="search">
|
||||
<input type="text" name="search-query" placeholder="Search all species..." value="{{value}}" class="bar">
|
||||
<input type="text" name="search-query" placeholder="Search..." value="{{value}}" class="bar">
|
||||
<input type="submit" value="Search" class="button">
|
||||
</form>
|
||||
{% endmacro %}
|
||||
Reference in new issue
Block a user