Skip to content

Commit ee0e088

Browse files
tegiozcynthia-sg
andauthored
Improve moderation capabilities (#238)
Closes #235 Signed-off-by: Sergio Castaño Arteaga <[email protected]> Signed-off-by: Cintia Sánchez García <[email protected]> Co-authored-by: Cintia Sánchez García <[email protected]>
1 parent b4c756f commit ee0e088

File tree

10 files changed

+397
-276
lines changed

10 files changed

+397
-276
lines changed

gitjobs-server/src/db/dashboard/moderator.rs

+10-7
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ use async_trait::async_trait;
66
use tracing::{instrument, trace};
77
use uuid::Uuid;
88

9-
use crate::{PgDB, templates::dashboard::moderator::jobs::JobSummary};
9+
use crate::{
10+
PgDB,
11+
templates::dashboard::{employer::jobs::JobStatus, moderator::jobs::JobSummary},
12+
};
1013

1114
/// Trait that defines some database operations used in the moderator
1215
/// dashboard.
@@ -15,8 +18,8 @@ pub(crate) trait DBDashBoardModerator {
1518
/// Approve job.
1619
async fn approve_job(&self, job_id: &Uuid, reviewer: &Uuid) -> Result<()>;
1720

18-
/// List moderation pending jobs.
19-
async fn list_moderation_pending_jobs(&self) -> Result<Vec<JobSummary>>;
21+
/// List jobs that match the given status.
22+
async fn list_jobs_for_moderation(&self, status: JobStatus) -> Result<Vec<JobSummary>>;
2023

2124
/// Reject job.
2225
async fn reject_job(&self, job_id: &Uuid, reviewer: &Uuid, review_notes: Option<&String>) -> Result<()>;
@@ -47,8 +50,8 @@ impl DBDashBoardModerator for PgDB {
4750
}
4851

4952
#[instrument(skip(self), err)]
50-
async fn list_moderation_pending_jobs(&self) -> Result<Vec<JobSummary>> {
51-
trace!("db: list moderation pending jobs");
53+
async fn list_jobs_for_moderation(&self, status: JobStatus) -> Result<Vec<JobSummary>> {
54+
trace!("db: list jobs for moderation");
5255

5356
let db = self.pool.get().await?;
5457
let jobs = db
@@ -78,10 +81,10 @@ impl DBDashBoardModerator for PgDB {
7881
from job j
7982
join employer e on j.employer_id = e.employer_id
8083
left join member m on e.member_id = m.member_id
81-
where j.status = 'pending-approval'
84+
where j.status = $1
8285
order by j.created_at desc;
8386
",
84-
&[],
87+
&[&status.to_string()],
8588
)
8689
.await?
8790
.into_iter()

gitjobs-server/src/handlers/dashboard/moderator/home.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ use crate::{
1919
handlers::error::HandlerError,
2020
templates::{
2121
PageId,
22-
dashboard::moderator::{
23-
home::{self, Content, Tab},
24-
jobs,
22+
dashboard::{
23+
employer::jobs::JobStatus,
24+
moderator::{
25+
home::{self, Content, Tab},
26+
jobs,
27+
},
2528
},
2629
},
2730
};
@@ -44,8 +47,12 @@ pub(crate) async fn page(
4447
// Prepare content for the selected tab
4548
let tab: Tab = query.get("tab").unwrap_or(&String::new()).parse().unwrap_or_default();
4649
let content = match tab {
50+
Tab::LiveJobs => {
51+
let jobs = db.list_jobs_for_moderation(JobStatus::Published).await?;
52+
Content::LiveJobs(jobs::LivePage { jobs })
53+
}
4754
Tab::PendingJobs => {
48-
let jobs = db.list_moderation_pending_jobs().await?;
55+
let jobs = db.list_jobs_for_moderation(JobStatus::PendingApproval).await?;
4956
Content::PendingJobs(jobs::PendingPage { jobs })
5057
}
5158
};

gitjobs-server/src/handlers/dashboard/moderator/jobs.rs

+16-4
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,29 @@ use crate::{
1616
db::DynDB,
1717
handlers::error::HandlerError,
1818
templates::{
19-
dashboard::{employer, moderator::jobs},
19+
dashboard::{
20+
employer::{self, jobs::JobStatus},
21+
moderator::jobs,
22+
},
2023
helpers::option_is_none_or_default,
2124
},
2225
};
2326

2427
// Pages handlers.
2528

29+
/// Handler that returns the live jobs page.
30+
#[instrument(skip_all, err)]
31+
pub(crate) async fn live_page(State(db): State<DynDB>) -> Result<impl IntoResponse, HandlerError> {
32+
let jobs = db.list_jobs_for_moderation(JobStatus::Published).await?;
33+
let template = jobs::LivePage { jobs };
34+
35+
Ok(Html(template.render()?))
36+
}
37+
2638
/// Handler that returns the pending jobs page.
2739
#[instrument(skip_all, err)]
2840
pub(crate) async fn pending_page(State(db): State<DynDB>) -> Result<impl IntoResponse, HandlerError> {
29-
let jobs = db.list_moderation_pending_jobs().await?;
41+
let jobs = db.list_jobs_for_moderation(JobStatus::PendingApproval).await?;
3042
let template = jobs::PendingPage { jobs };
3143

3244
Ok(Html(template.render()?))
@@ -63,7 +75,7 @@ pub(crate) async fn approve(
6375

6476
Ok((
6577
StatusCode::NO_CONTENT,
66-
[("HX-Trigger", "refresh-pending-jobs-table")],
78+
[("HX-Trigger", "refresh-moderator-table")],
6779
)
6880
.into_response())
6981
}
@@ -87,7 +99,7 @@ pub(crate) async fn reject(
8799

88100
Ok((
89101
StatusCode::NO_CONTENT,
90-
[("HX-Trigger", "refresh-pending-jobs-table")],
102+
[("HX-Trigger", "refresh-moderator-table")],
91103
)
92104
.into_response())
93105
}

gitjobs-server/src/router.rs

+1
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ fn setup_moderator_dashboard_router(state: &State) -> Router<State> {
255255
// Setup router
256256
Router::new()
257257
.route("/", get(dashboard::moderator::home::page))
258+
.route("/jobs/live", get(dashboard::moderator::jobs::live_page))
258259
.route("/jobs/pending", get(dashboard::moderator::jobs::pending_page))
259260
.route("/jobs/{job_id}/approve", put(dashboard::moderator::jobs::approve))
260261
.route("/jobs/{job_id}/reject", put(dashboard::moderator::jobs::reject))

gitjobs-server/src/templates/dashboard/moderator/home.rs

+8
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,16 @@ pub(crate) struct Page {
2424
/// Content section.
2525
#[derive(Debug, Clone, Serialize, Deserialize)]
2626
pub(crate) enum Content {
27+
LiveJobs(jobs::LivePage),
2728
PendingJobs(jobs::PendingPage),
2829
}
2930

3031
impl Content {
32+
/// Check if the content is the live jobs page.
33+
fn is_live_jobs(&self) -> bool {
34+
matches!(self, Content::LiveJobs(_))
35+
}
36+
3137
/// Check if the content is the pending jobs page.
3238
fn is_pending_jobs(&self) -> bool {
3339
matches!(self, Content::PendingJobs(_))
@@ -37,6 +43,7 @@ impl Content {
3743
impl std::fmt::Display for Content {
3844
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3945
match self {
46+
Content::LiveJobs(template) => write!(f, "{}", template.render()?),
4047
Content::PendingJobs(template) => write!(f, "{}", template.render()?),
4148
}
4249
}
@@ -46,6 +53,7 @@ impl std::fmt::Display for Content {
4653
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, strum::Display, strum::EnumString)]
4754
#[strum(serialize_all = "kebab-case")]
4855
pub(crate) enum Tab {
56+
LiveJobs,
4957
#[default]
5058
PendingJobs,
5159
}

gitjobs-server/src/templates/dashboard/moderator/jobs.rs

+7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ use crate::templates::{
1414

1515
// Pages templates.
1616

17+
/// Live jobs page template.
18+
#[derive(Debug, Clone, Template, Serialize, Deserialize)]
19+
#[template(path = "dashboard/moderator/live_jobs.html")]
20+
pub(crate) struct LivePage {
21+
pub jobs: Vec<JobSummary>,
22+
}
23+
1724
/// Pending jobs page template.
1825
#[derive(Debug, Clone, Template, Serialize, Deserialize)]
1926
#[template(path = "dashboard/moderator/pending_jobs.html")]

gitjobs-server/templates/dashboard/moderator/home.html

+4-3
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,16 @@
1313
<div class="max-h-full w-full flex flex-col space-y-5 mb-5 overflow-y-auto px-3 lg:px-5">
1414
<div class="leading-10 grid gap-y-0.5">
1515
{% call dashboard_macros::menu_title(text = "Jobs", extra_styles = "py-1.5") %}
16-
{% call dashboard_macros::menu_item(name = "Pending", icon = "tasks", is_active = content.is_pending_jobs(), href = "/dashboard/moderator?tab=jobs") -%}
16+
{% call dashboard_macros::menu_item(name = "Pending", icon = "tasks", is_active = content.is_pending_jobs(), href = "/dashboard/moderator?tab=pending-jobs") -%}
17+
{% call dashboard_macros::menu_item(name = "Live", icon = "live", is_active = content.is_live_jobs(), href = "/dashboard/moderator?tab=live-jobs") -%}
1718
</div>
1819
</div>
1920
{% endblock menu -%}
2021

2122
{% block dashboard_main -%}
2223
<div id="dashboard-content"
23-
hx-get="/dashboard/moderator/jobs/pending"
24-
hx-trigger="refresh-pending-jobs-table"
24+
hx-get="{%- if content.is_pending_jobs() -%}/dashboard/moderator/jobs/pending{%- else -%}/dashboard/moderator/jobs/live{%- endif -%}"
25+
hx-trigger="refresh-moderator-table"
2526
class="p-4 sm:p-6 lg:p-12">
2627
{# Content -#}
2728
{{ content|safe }}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{% import "macros.html" as macros -%}
2+
{% import "dashboard/moderator/moderator_macros.html" as moderator_macros -%}
3+
4+
<div class="flex items-center">
5+
{# Mobile filters button -#}
6+
<div class="flex shrink-0 me-3 md:me-6 lg:hidden">
7+
<button id="open-menu-button"
8+
class="btn-primary-outline group size-[40px] p-0 items-center flex justify-center">
9+
<div class="svg-icon size-4 icon-menu group-hover:bg-white shrink-0"></div>
10+
</button>
11+
<script type="module">
12+
import {
13+
open
14+
} from '/static/js/dashboard/base.js';
15+
16+
const openMenuButton = document.getElementById('open-menu-button');
17+
openMenuButton.addEventListener('click', open);
18+
</script>
19+
</div>
20+
{# End mobile filters button -#}
21+
{% call macros::form_title(title = "Live jobs") -%}
22+
</div>
23+
24+
{# Live jobs Table -#}
25+
{% call moderator_macros::jobs_table(jobs = jobs) -%}
26+
{# End live jobs Table -#}
27+
28+
{# Mobile live jobs cards -#}
29+
<div class="flex flex-col space-y-4 md:hidden mt-6">
30+
{% if jobs.is_empty() -%}
31+
<div class="border border-primary-500 rounded-lg mt-10 p-5 bg-primary-50/20"
32+
role="alert">
33+
<div class="text-lg mb-6">
34+
<div>There are no live jobs at the moment.</div>
35+
</div>
36+
<p class="text-stone-700">Thanks for checking, please come back later :)</p>
37+
</div>
38+
{% else -%}
39+
{% for job in jobs -%}
40+
{% call moderator_macros::mobile_job_card(job = job) -%}
41+
{% endfor -%}
42+
{% endif -%}
43+
</div>
44+
{# End mobile live jobs cards -#}
45+
46+
{# Preview modal -#}
47+
{% call moderator_macros::preview_modal() -%}
48+
{# End preview modal -#}
49+
50+
{# Reject modal -#}
51+
{% call moderator_macros::reject_modal() -%}
52+
{# End reject modal -#}

0 commit comments

Comments
 (0)