Add Profile and Submission report logic

Implement Follow and Block logic
This commit is contained in:
asonix 2021-01-14 20:42:30 -06:00
parent 29bdf064e9
commit f350d718ac
9 changed files with 780 additions and 52 deletions

View file

@ -7,7 +7,7 @@ use actix_session::Session;
use actix_web::{web, HttpRequest, HttpResponse, Scope};
use hyaenidae_accounts::User;
use hyaenidae_profiles::store::{File, FileStore, ProfileStore, Submission};
use hyaenidae_toolkit::Button;
use hyaenidae_toolkit::{Button, TextInput};
use std::collections::HashMap;
use uuid::Uuid;
@ -83,8 +83,347 @@ pub(super) fn scope() -> Scope {
.service(web::resource("/done").route(web::get().to(done))),
)
.route("/drafts", web::get().to(drafts_page))
.route("/@{handle}@{domain}", web::get().to(handle_view))
.route("/#{id}", web::get().to(id_view))
.service(
web::scope("/@{handle}@{domain}")
.route("", web::get().to(handle_view))
.service(
web::resource("/follow")
.route(web::get().to(route_to_profile_page))
.route(web::post().to(follow)),
)
.service(
web::resource("/unfollow")
.route(web::get().to(route_to_profile_page))
.route(web::post().to(unfollow)),
)
.service(
web::resource("/block")
.route(web::get().to(route_to_profile_page))
.route(web::post().to(block)),
)
.service(
web::resource("/unblock")
.route(web::get().to(route_to_profile_page))
.route(web::post().to(unblock)),
)
.service(
web::resource("/report")
.route(web::get().to(report_page))
.route(web::post().to(report)),
)
.route("/report-success", web::get().to(report_success_page)),
)
.route("/id/{id}", web::get().to(id_view))
}
fn to_profile_page(handle: &str, domain: &str) -> HttpResponse {
crate::redirect(&format!("/profiles/@{}@{}", handle, domain))
}
async fn route_to_profile_page(handle: web::Path<(String, String)>) -> HttpResponse {
let (handle, domain) = handle.into_inner();
to_profile_page(&handle, &domain)
}
async fn follow(
handle: web::Path<(String, String)>,
self_profile: Profile,
state: web::Data<State>,
) -> Result<HttpResponse, Error> {
use hyaenidae_profiles::apub::actions::CreateFollowRequest;
let (handle, domain) = handle.into_inner();
let profile = profile_from_handle(handle.clone(), domain.clone(), &state).await?;
let self_id = self_profile.id();
let profile_id = profile.id();
let blocks = state.profiles.store.view.blocks.clone();
let is_blocked =
web::block(move || Ok(blocks.by_forward(self_id, profile_id)?.is_some())).await?;
if is_blocked {
return Ok(crate::to_404());
}
let follow_requests = state.profiles.store.view.follow_requests.clone();
let follows = state.profiles.store.view.follows.clone();
let follow_exists = web::block(move || {
let exists = follow_requests.by_forward(profile_id, self_id)?.is_some()
|| follows.by_forward(profile_id, self_id)?.is_some();
Ok(exists)
})
.await?;
if follow_exists {
return Ok(to_profile_page(&handle, &domain));
}
state
.profiles
.run(CreateFollowRequest::from_profiles(
profile.id(),
self_profile.id(),
))
.await?;
Ok(to_profile_page(&handle, &domain))
}
async fn unfollow(
handle: web::Path<(String, String)>,
self_profile: Profile,
state: web::Data<State>,
) -> Result<HttpResponse, Error> {
use hyaenidae_profiles::apub::actions::{UndoFollow, UndoFollowRequest};
let (handle, domain) = handle.into_inner();
let profile = profile_from_handle(handle.clone(), domain.clone(), &state).await?;
let self_id = self_profile.id();
let profile_id = profile.id();
let blocks = state.profiles.store.view.blocks.clone();
let is_blocked =
web::block(move || Ok(blocks.by_forward(self_id, profile_id)?.is_some())).await?;
if is_blocked {
return Ok(crate::to_404());
}
let follow_requests = state.profiles.store.view.follow_requests.clone();
let follows = state.profiles.store.view.follows.clone();
let (follow_request, follow) = web::block(move || {
let follow_request = follow_requests.by_forward(profile_id, self_id)?;
let follow = follows.by_forward(profile_id, self_id)?;
Ok((follow_request, follow))
})
.await?;
if let Some(follow_request) = follow_request {
state
.profiles
.run(UndoFollowRequest::from_id(follow_request))
.await?;
}
if let Some(follow) = follow {
state.profiles.run(UndoFollow::from_id(follow)).await?;
}
Ok(to_profile_page(&handle, &domain))
}
async fn block(
handle: web::Path<(String, String)>,
self_profile: Profile,
state: web::Data<State>,
) -> Result<HttpResponse, Error> {
use hyaenidae_profiles::apub::actions::CreateBlock;
let (handle, domain) = handle.into_inner();
let profile = profile_from_handle(handle.clone(), domain.clone(), &state).await?;
let self_id = self_profile.id();
let profile_id = profile.id();
let blocks = state.profiles.store.view.blocks.clone();
let is_blocked =
web::block(move || Ok(blocks.by_forward(self_id, profile_id)?.is_some())).await?;
if is_blocked {
return Ok(crate::to_404());
}
let blocks = state.profiles.store.view.blocks.clone();
let block_exists =
web::block(move || Ok(blocks.by_forward(profile_id, self_id)?.is_some())).await?;
if block_exists {
return Ok(to_profile_page(&handle, &domain));
}
state
.profiles
.run(CreateBlock::from_profiles(profile.id(), self_profile.id()))
.await?;
Ok(to_profile_page(&handle, &domain))
}
async fn unblock(
handle: web::Path<(String, String)>,
self_profile: Profile,
state: web::Data<State>,
) -> Result<HttpResponse, Error> {
use hyaenidae_profiles::apub::actions::DeleteBlock;
let (handle, domain) = handle.into_inner();
let profile = profile_from_handle(handle.clone(), domain.clone(), &state).await?;
let self_id = self_profile.id();
let profile_id = profile.id();
let blocks = state.profiles.store.view.blocks.clone();
let block = web::block(move || Ok(blocks.by_forward(profile_id, self_id)?)).await?;
if let Some(block) = block {
state.profiles.run(DeleteBlock::from_id(block)).await?;
}
Ok(to_profile_page(&handle, &domain))
}
pub struct ReportView {
pub(crate) profile: Profile,
pub(crate) input: TextInput,
}
impl ReportView {
fn new(profile: Profile, dark: bool) -> Self {
let mut input = TextInput::new("body");
input
.title("Report")
.placeholder("Type your report info here")
.textarea()
.dark(dark);
ReportView { profile, input }
}
}
async fn profile_from_id(id: Uuid, state: &State) -> Result<Profile, Error> {
let profile_store = state.profiles.store.profiles.clone();
let file_store = state.profiles.store.files.clone();
let profile = web::block(move || {
let profile = Profile::from_stores(id, &profile_store, &file_store)?;
Ok(profile)
})
.await?;
Ok(profile)
}
async fn profile_from_handle(
handle: String,
domain: String,
state: &State,
) -> Result<Profile, Error> {
let profile_store = state.profiles.store.profiles.clone();
let file_store = state.profiles.store.files.clone();
let profile = web::block(move || {
let id = profile_store.by_handle(&handle, &domain)?.req()?;
let profile = Profile::from_stores(id, &profile_store, &file_store)?;
Ok(profile)
})
.await?;
Ok(profile)
}
async fn report_page(
self_profile: Profile,
handle: web::Path<(String, String)>,
nav_state: NavState,
state: web::Data<State>,
) -> Result<HttpResponse, Error> {
let (handle, domain) = handle.into_inner();
let profile = profile_from_handle(handle, domain, &state).await?;
let blocks = state.profiles.store.view.blocks.clone();
let self_id = self_profile.id();
let profile_id = profile.id();
let is_blocked =
web::block(move || Ok(blocks.by_forward(self_id, profile_id)?.is_some())).await?;
if is_blocked {
return Ok(crate::to_404());
}
let view = ReportView::new(profile, nav_state.dark());
crate::rendered(HttpResponse::Ok(), |cursor| {
crate::templates::profiles::report(cursor, &view, &nav_state)
})
}
const MAX_REPORT_LEN: usize = 1000;
#[derive(Clone, Debug, serde::Deserialize)]
struct ReportForm {
body: String,
}
async fn report(
self_profile: Profile,
handle: web::Path<(String, String)>,
form: web::Form<ReportForm>,
nav_state: NavState,
state: web::Data<State>,
) -> Result<HttpResponse, Error> {
use hyaenidae_profiles::apub::actions::CreateReport;
let (handle, domain) = handle.into_inner();
let profile = profile_from_handle(handle, domain, &state).await?;
let form = form.into_inner();
let error = if form.body.len() > MAX_REPORT_LEN {
format!("Must be shorter than {} characters", MAX_REPORT_LEN)
} else if form.body.trim().is_empty() {
format!("Must be present")
} else {
let res = state
.profiles
.run(CreateReport::from_profile(
profile.id(),
self_profile.id(),
Some(form.body.clone()),
))
.await;
match res {
Ok(_) => return Ok(to_report_success_page(profile.full_handle())),
Err(e) => e.to_string(),
}
};
let mut view = ReportView::new(profile, nav_state.dark());
view.input.error_opt(Some(error)).value(&form.body);
crate::rendered(HttpResponse::BadRequest(), |cursor| {
crate::templates::profiles::report(cursor, &view, &nav_state)
})
}
async fn report_success_page(
self_profile: Profile,
handle: web::Path<(String, String)>,
nav_state: NavState,
state: web::Data<State>,
) -> Result<HttpResponse, Error> {
let (handle, domain) = handle.into_inner();
let profile = profile_from_handle(handle, domain, &state).await?;
let blocks = state.profiles.store.view.blocks.clone();
let self_id = self_profile.id();
let profile_id = profile.id();
let is_blocked =
web::block(move || Ok(blocks.by_forward(self_id, profile_id)?.is_some())).await?;
if is_blocked {
return Ok(crate::to_404());
}
let view = ReportView::new(profile, nav_state.dark());
crate::rendered(HttpResponse::Ok(), |cursor| {
crate::templates::profiles::report_success(cursor, &view, &nav_state)
})
}
fn to_report_success_page(full_handle: String) -> HttpResponse {
redirect(&format!("/profiles/{}/report-success", full_handle))
}
fn to_create() -> HttpResponse {
@ -270,10 +609,34 @@ impl Profile {
format!("/profiles/{}", self.full_handle())
}
pub(crate) fn report_path(&self) -> String {
format!("/profiles/{}/report", self.full_handle())
}
pub(crate) fn follow_path(&self) -> String {
format!("/profiles/{}/follow", self.full_handle())
}
pub(crate) fn unfollow_path(&self) -> String {
format!("/profiles/{}/unfollow", self.full_handle())
}
pub(crate) fn block_path(&self) -> String {
format!("/profiles/{}/block", self.full_handle())
}
pub(crate) fn unblock_path(&self) -> String {
format!("/profiles/{}/unblock", self.full_handle())
}
fn refresh(self, state: &State) -> Result<Self, Error> {
Self::from_id(self.inner.id(), state)
}
fn is_suspended(&self) -> bool {
self.inner.is_suspended()
}
pub(crate) fn id(&self) -> Uuid {
self.inner.id()
}
@ -291,7 +654,11 @@ impl Profile {
}
pub(crate) fn description(&self) -> Option<&str> {
self.inner.description()
if self.is_suspended() {
Some("Profile Suspended")
} else {
self.inner.description()
}
}
pub(crate) fn name(&self) -> String {
@ -550,17 +917,19 @@ pub(crate) fn build_submissions(
async fn id_view(
req: HttpRequest,
user: Option<User>,
profile: Option<Profile>,
self_profile: Option<Profile>,
id: web::Path<Uuid>,
page: Option<web::Query<SubmissionPage>>,
nav_state: NavState,
state: web::Data<State>,
) -> Result<HttpResponse, Error> {
let profile = profile_from_id(id.into_inner(), &state).await?;
do_public_view(
req.uri().path(),
user,
self_profile,
profile,
id.into_inner(),
page.map(|q| q.into_inner()),
nav_state,
&state,
@ -571,26 +940,20 @@ async fn id_view(
async fn handle_view(
req: HttpRequest,
user: Option<User>,
profile: Option<Profile>,
self_profile: Option<Profile>,
handle: web::Path<(String, String)>,
page: Option<web::Query<SubmissionPage>>,
nav_state: NavState,
state: web::Data<State>,
) -> Result<HttpResponse, Error> {
let (handle, domain) = handle.into_inner();
let profile_id = state
.profiles
.store
.profiles
.by_handle(&handle, &domain)?
.req()?;
let profile = profile_from_handle(handle, domain, &state).await?;
do_public_view(
req.uri().path(),
user,
self_profile,
profile,
profile_id,
page.map(|q| q.into_inner()),
nav_state,
&state,
@ -603,13 +966,17 @@ pub struct ProfileView {
pub(crate) submissions: Vec<SubmissionView>,
nav: Vec<Button>,
pub(crate) viewer: Option<Uuid>,
pub(crate) is_self: bool,
buttons: Vec<Button>,
}
impl ProfileView {
pub(crate) fn nav(&self) -> Vec<&Button> {
self.nav.iter().collect()
}
pub(crate) fn buttons(&self) -> Vec<&Button> {
self.buttons.iter().collect()
}
}
pub struct SubmissionView {
@ -787,6 +1154,107 @@ impl SubmissionView {
}
}
async fn profile_buttons(
profile: &Profile,
self_profile: Option<&Profile>,
is_drafts: bool,
dark: bool,
state: &State,
) -> Result<Vec<Button>, Error> {
let mut buttons = vec![];
if profile.is_suspended() {
return Ok(buttons);
}
let is_follow_requested = if let Some(self_profile) = self_profile {
let follow_requests = state.profiles.store.view.follow_requests.clone();
let self_id = self_profile.id();
let profile_id = profile.id();
web::block(move || Ok(follow_requests.by_forward(profile_id, self_id)?.is_some())).await?
} else {
false
};
let is_follower = if let Some(self_profile) = self_profile {
let follows = state.profiles.store.view.follows.clone();
let self_id = self_profile.id();
let profile_id = profile.id();
web::block(move || Ok(follows.by_forward(profile_id, self_id)?.is_some())).await?
} else {
false
};
let is_blocking = if let Some(self_profile) = self_profile {
let blocks = state.profiles.store.view.blocks.clone();
let self_id = self_profile.id();
let profile_id = profile.id();
web::block(move || Ok(blocks.by_forward(profile_id, self_id)?.is_some())).await?
} else {
false
};
if self_profile
.map(|p| p.id() == profile.id())
.unwrap_or(false)
{
let drafts = if is_drafts {
let view = Button::secondary("View Profile");
view.href(&profile.view_path()).dark(dark);
view
} else {
let drafts = Button::secondary("View Drafts");
drafts.href("/profiles/drafts").dark(dark);
drafts
};
let edit = Button::secondary("Edit Profile");
let switch = Button::secondary("Switch Profile");
edit.href("/profiles/current").dark(dark);
switch.href("/profiles/change").dark(dark);
buttons.push(drafts);
buttons.push(edit);
buttons.push(switch);
} else if is_follower || is_follow_requested {
let unfollow = Button::secondary("Unfollow");
let block = Button::secondary("Block");
let report = Button::primary_outline("Report");
unfollow.form(&profile.unfollow_path()).dark(dark);
block.form(&profile.block_path()).dark(dark);
report.href(&profile.report_path()).dark(dark);
buttons.push(unfollow);
buttons.push(block);
buttons.push(report);
} else if is_blocking {
let unblock = Button::secondary("Unblock");
let report = Button::primary_outline("Report");
unblock.form(&profile.unblock_path()).dark(dark);
report.href(&profile.report_path()).dark(dark);
buttons.push(unblock);
buttons.push(report);
} else if self_profile.is_some() {
let follow = Button::secondary("Follow");
let block = Button::secondary("Block");
let report = Button::primary_outline("Report");
follow.form(&profile.follow_path()).dark(dark);
block.form(&profile.block_path()).dark(dark);
report.href(&profile.report_path()).dark(dark);
buttons.push(follow);
buttons.push(block);
buttons.push(report);
}
Ok(buttons)
}
async fn drafts_page(
req: HttpRequest,
profile: Profile,
@ -806,12 +1274,14 @@ async fn drafts_page(
&state,
);
let buttons = profile_buttons(&profile, Some(&profile), true, nav_state.dark(), &state).await?;
let view = ProfileView {
profile,
submissions: agg.submissions,
nav: agg.nav,
viewer: viewed_by,
is_self: true,
buttons,
};
crate::rendered(HttpResponse::Ok(), |cursor| {
@ -823,21 +1293,29 @@ async fn do_public_view(
base_url: &str,
user: Option<User>,
self_profile: Option<Profile>,
profile_id: Uuid,
profile: Profile,
page: Option<SubmissionPage>,
nav_state: NavState,
state: &State,
) -> Result<HttpResponse, Error> {
let profile = Profile::from_id(profile_id, state)?;
if profile.login_required() && user.is_none() {
return Ok(crate::to_404());
}
let is_self = self_profile
.as_ref()
.map(|p| p.id() == profile_id)
.unwrap_or(false);
let is_blocked = if let Some(self_profile) = &self_profile {
let blocks = state.profiles.store.view.blocks.clone();
let self_id = self_profile.id();
let profile_id = profile.id();
web::block(move || Ok(blocks.by_forward(self_id, profile_id)?.is_some())).await?
} else {
false
};
if is_blocked {
return Ok(crate::to_404());
}
let viewed_by = self_profile.as_ref().map(|p| p.id());
let agg = build_submissions(
base_url,
@ -850,12 +1328,21 @@ async fn do_public_view(
state,
);
let buttons = profile_buttons(
&profile,
self_profile.as_ref(),
false,
nav_state.dark(),
state,
)
.await?;
let view = ProfileView {
profile,
submissions: agg.submissions,
nav: agg.nav,
viewer: viewed_by,
is_self,
buttons,
};
crate::rendered(HttpResponse::Ok(), |cursor| {
@ -1145,7 +1632,7 @@ pub struct HandleForm {
}
async fn new_handle(_: User, nav_state: NavState) -> Result<HttpResponse, Error> {
let mut handle_input = hyaenidae_toolkit::TextInput::new("handle");
let mut handle_input = TextInput::new("handle");
handle_input
.placeholder("Handle")
.title("Handle")
@ -1198,7 +1685,7 @@ async fn create_handle(
}
};
let mut handle_input = hyaenidae_toolkit::TextInput::new("handle");
let mut handle_input = TextInput::new("handle");
handle_input
.placeholder("Handle")
.title("Handle")
@ -1218,7 +1705,7 @@ pub struct BioForm {
}
async fn new_bio(profile: Profile, nav_state: NavState) -> Result<HttpResponse, Error> {
let mut display_name = hyaenidae_toolkit::TextInput::new("display_name");
let mut display_name = TextInput::new("display_name");
display_name
.title("Display Name")
.placeholder("Display Name")
@ -1227,7 +1714,7 @@ async fn new_bio(profile: Profile, nav_state: NavState) -> Result<HttpResponse,
display_name.value(text);
}
let mut description = hyaenidae_toolkit::TextInput::new("description");
let mut description = TextInput::new("description");
description
.title("Description")
.placeholder("Description")
@ -1280,7 +1767,7 @@ async fn create_bio(
}
}
let mut display_name = hyaenidae_toolkit::TextInput::new("display_name");
let mut display_name = TextInput::new("display_name");
display_name
.title("Display Name")
.placeholder("Display Name")
@ -1288,7 +1775,7 @@ async fn create_bio(
.error_opt(display_name_error)
.dark(nav_state.dark());
let mut description = hyaenidae_toolkit::TextInput::new("description");
let mut description = TextInput::new("description");
description
.title("Description")
.placeholder("Description")

View file

@ -23,6 +23,12 @@ pub(super) fn scope() -> Scope {
.service(
web::scope("/{submission_id}")
.route("", web::get().to(submission_page))
.service(
web::resource("/report")
.route(web::get().to(report_page))
.route(web::post().to(report)),
)
.route("/report-success", web::get().to(report_success_page))
.service(
web::resource("/comment")
.route(web::get().to(route_to_update_page))
@ -56,6 +62,145 @@ pub(super) fn scope() -> Scope {
)
}
pub struct ReportView {
pub(crate) submission: Submission,
pub(crate) profile: Profile,
pub(crate) input: TextInput,
}
impl ReportView {
async fn build(submission_id: Uuid, dark: bool, state: &State) -> Result<Self, Error> {
let submission_store = state.profiles.store.submissions.clone();
let file_store = state.profiles.store.files.clone();
let submission = web::block(move || {
Submission::from_stores(submission_id, &submission_store, &file_store)
})
.await?;
let profile_id = submission.profile_id();
let profile_store = state.profiles.store.profiles.clone();
let file_store = state.profiles.store.files.clone();
let profile =
web::block(move || Profile::from_stores(profile_id, &profile_store, &file_store))
.await?;
let mut input = TextInput::new("body");
input
.title("Report")
.placeholder("Say why you are reporting this submission")
.textarea()
.dark(dark);
Ok(ReportView {
submission,
profile,
input,
})
}
pub(crate) fn tiles(&self) -> Vec<crate::profiles::SubmissionView> {
self.submission
.files()
.iter()
.map(|f| {
crate::profiles::SubmissionView::from_parts(
&self.submission.inner,
&self.profile,
f,
)
})
.collect()
}
}
async fn report_page(
submission_id: web::Path<Uuid>,
profile: Profile,
nav_state: NavState,
state: web::Data<State>,
) -> Result<HttpResponse, Error> {
let view = ReportView::build(submission_id.into_inner(), nav_state.dark(), &state).await?;
if let Some(res) = can_view(Some(&profile), &view.profile, &view.submission, &state).await? {
return Ok(res);
}
crate::rendered(HttpResponse::Ok(), |cursor| {
crate::templates::submissions::report(cursor, &view, &nav_state)
})
}
#[derive(Clone, Debug, serde::Deserialize)]
struct ReportForm {
body: String,
}
const MAX_REPORT_LEN: usize = 1000;
async fn report(
form: web::Form<ReportForm>,
submission_id: web::Path<Uuid>,
profile: Profile,
nav_state: NavState,
state: web::Data<State>,
) -> Result<HttpResponse, Error> {
let mut view = ReportView::build(submission_id.into_inner(), nav_state.dark(), &state).await?;
if let Some(res) = can_view(Some(&profile), &view.profile, &view.submission, &state).await? {
return Ok(res);
}
let form = form.into_inner();
let error = if form.body.len() > MAX_REPORT_LEN {
format!("Must be shorter than {} characters", MAX_REPORT_LEN)
} else if form.body.trim().is_empty() {
format!("Must be present")
} else {
use hyaenidae_profiles::apub::actions::CreateReport;
let res = state
.profiles
.run(CreateReport::from_submission(
view.submission.id(),
view.profile.id(),
Some(form.body.clone()),
))
.await;
match res {
Ok(_) => return Ok(to_report_success_page(view.submission.id())),
Err(e) => e.to_string(),
}
};
view.input.error_opt(Some(error)).value(&form.body);
crate::rendered(HttpResponse::BadRequest(), |cursor| {
crate::templates::submissions::report(cursor, &view, &nav_state)
})
}
async fn report_success_page(
submission_id: web::Path<Uuid>,
profile: Profile,
nav_state: NavState,
state: web::Data<State>,
) -> Result<HttpResponse, Error> {
let view = ReportView::build(submission_id.into_inner(), nav_state.dark(), &state).await?;
if let Some(res) = can_view(Some(&profile), &view.profile, &view.submission, &state).await? {
return Ok(res);
}
crate::rendered(HttpResponse::Ok(), |cursor| {
crate::templates::submissions::report_success(cursor, &view, &nav_state)
})
}
fn to_report_success_page(submission_id: Uuid) -> HttpResponse {
crate::redirect(&format!("/submissions/{}/report-success", submission_id))
}
pub struct SubmissionState {
pub(crate) submission: Submission,
pub(crate) title: TextInput,
@ -177,6 +322,10 @@ impl Submission {
format!("/submissions/{}", self.id())
}
pub(crate) fn report_path(&self) -> String {
format!("/submissions/{}/report", self.id())
}
pub(crate) fn profile_id(&self) -> Uuid {
self.inner.profile_id()
}
@ -407,6 +556,9 @@ fn submission_nav(
nav.push(button);
}
let btn = Button::primary_outline("Report");
btn.href(&submission.report_path()).dark(nav_state.dark());
nav.push(btn);
if file_num < file_count.saturating_sub(1) {
let button = Button::secondary("Next");
button

View file

@ -4,7 +4,7 @@
@use crate::templates::layouts::home;
@use crate::templates::profiles::view as view_profile;
@use hyaenidae_toolkit::{templates::button_group, Button};
@use hyaenidae_toolkit::{templates::{card, card_body, card_title}, Card};
@use hyaenidae_toolkit::{templates::{card, card_body, card_section, card_title}, Card};
@use hyaenidae_toolkit::{templates::link, Link};
@use hyaenidae_toolkit::templates::select;
@use hyaenidae_toolkit::templates::text_input;
@ -15,7 +15,9 @@
@:card(Card::full_width().dark(nav_state.dark()), {
@:card_title({ Reported Item })
@if let Some(profile) = view.profile() {
@:view_profile(profile, nav_state.dark())
@:card_section({
@:view_profile(profile, nav_state.dark())
})
}
@if let Some(cv) = view.comment() {
@:card_body({

View file

@ -1,23 +1,17 @@
@use crate::{templates::{layouts::home, profiles::{submission_tile, view}}, nav::NavState, profiles::ProfileView};
@use hyaenidae_toolkit::{templates::button_group, Button};
@use hyaenidae_toolkit::templates::button_group;
@use hyaenidae_toolkit::{templates::{card, card_body, card_section, card_title}, Card};
@(pview: &ProfileView, nav_state: &NavState)
@:home(&pview.profile.name(), pview.profile.description().unwrap_or(&format!("{}'s profile on Hyaenidae", pview.profile.name())), nav_state, {}, {
@:card(Card::full_width().dark(nav_state.dark()), { @:view(&pview.profile, nav_state.dark()) })
@if pview.is_self {
@:card(Card::full_width().dark(nav_state.dark()), {
@:card_title({ Profile Actions })
@:card_body({
@:button_group(&[
&Button::secondary("View Profile").href(&pview.profile.view_path()).dark(nav_state.dark()),
&Button::secondary("Edit Profile").href("/profiles/current").dark(nav_state.dark()),
&Button::secondary("Switch Profile").href("/profiles/change").dark(nav_state.dark()),
])
})
@:card(Card::full_width().dark(nav_state.dark()), {
@:card_title({ Profile Actions })
@:card_body({
@:button_group(&pview.buttons())
})
}
})
@:card(Card::full_width().dark(nav_state.dark()), {
@:card_title({ Drafts })
@:card_section({

View file

@ -1,20 +1,16 @@
@use crate::{templates::{layouts::home, profiles::{submission_tile, view}}, nav::NavState, profiles::ProfileView};
@use hyaenidae_toolkit::{templates::button_group, Button};
@use hyaenidae_toolkit::templates::button_group;
@use hyaenidae_toolkit::{templates::{card, card_body, card_section, card_title}, Card};
@(pview: &ProfileView, nav_state: &NavState)
@:home(&pview.profile.name(), pview.profile.description().unwrap_or(&format!("{}'s profile on Hyaenidae", pview.profile.name())), nav_state, {}, {
@:card(Card::full_width().dark(nav_state.dark()), { @:view(&pview.profile, nav_state.dark()) })
@if pview.is_self {
@if !pview.buttons().is_empty() {
@:card(Card::full_width().dark(nav_state.dark()), {
@:card_title({ Profile Actions })
@:card_body({
@:button_group(&[
&Button::secondary("View Drafts").href("/profiles/drafts").dark(nav_state.dark()),
&Button::secondary("Edit Profile").href("/profiles/current").dark(nav_state.dark()),
&Button::secondary("Switch Profile").href("/profiles/change").dark(nav_state.dark()),
])
@:button_group(&pview.buttons())
})
})
}

View file

@ -0,0 +1,28 @@
@use crate::{templates::{layouts::home, profiles::view}, nav::NavState, profiles::ReportView};
@use hyaenidae_toolkit::{templates::button_group, Button};
@use hyaenidae_toolkit::{templates::{card, card_body, card_title}, Card};
@use hyaenidae_toolkit::templates::text_input;
@(rview: &ReportView, nav_state: &NavState)
@:home("Report Profile", rview.profile.description().unwrap_or(&format!("Report {}'s profile on Hyaenidae", rview.profile.name())), nav_state, {}, {
@:card(Card::full_width().dark(nav_state.dark()), { @:view(&rview.profile, nav_state.dark()) })
@:card(Card::full_width().dark(nav_state.dark()), {
<form method="POST" action="@rview.profile.report_path()">
@:card_title({ Report Profile })
@:card_body({
<p>
Please include any relevant information for moderators to act on this report.
</p>
@:text_input(&rview.input)
})
@:card_body({
@:button_group(&[
Button::primary("Report").dark(nav_state.dark()),
Button::secondary("Back to Profile").href(&rview.profile.view_path()).dark(nav_state.dark()),
])
})
</form>
})
})

View file

@ -0,0 +1,18 @@
@use crate::profiles::ReportView;
@use crate::nav::NavState;
@use crate::templates::layouts::home;
@use hyaenidae_toolkit::{templates::button_group, Button};
@use hyaenidae_toolkit::{templates::{card, card_title, card_body}, Card};
@(view: &ReportView, nav_state: &NavState)
@:home("Profile Reported", &format!("Reported {}", view.profile.name()), nav_state, {}, {
@:card(Card::full_width().dark(nav_state.dark()), {
@:card_title({ Profile Reported })
@:card_body({
@:button_group(&[
Button::secondary("Back to Profile").href(&view.profile.view_path()).dark(nav_state.dark()),
])
})
})
})

View file

@ -0,0 +1,34 @@
@use crate::templates::admin::submission_box;
@use crate::templates::layouts::home;
@use crate::{nav::NavState, submissions::ReportView};
@use hyaenidae_toolkit::{templates::button_group, Button};
@use hyaenidae_toolkit::{templates::{card, card_body, card_title}, Card};
@use hyaenidae_toolkit::templates::text_input;
@(view: &ReportView, nav_state: &NavState)
@:home("Report Submission", view.submission.description().unwrap_or(&format!("Report {}'s
submission on Hyaenidae", view.profile.name())), nav_state, {}, {
@:card(&Card::full_width().dark(nav_state.dark()), {
@:card_title({
Report @view.submission.title()
})
@:submission_box(&view.submission, &view.profile, &view.tiles(), nav_state.dark())
})
@:card(Card::full_width().dark(nav_state.dark()), {
<form method="POST" action="@view.submission.report_path()">
@:card_title({ Report Submission })
@:card_body({
<p>
Please include any relevant information for moderators to act on this report.
</p>
@:text_input(&view.input)
})
@:card_body({
@:button_group(&[
Button::primary("Report").dark(nav_state.dark()),
Button::secondary("Back to Submission").href(&view.submission.view_path()).dark(nav_state.dark()),
])
})
})
})

View file

@ -0,0 +1,17 @@
@use crate::templates::layouts::home;
@use crate::{nav::NavState, submissions::ReportView};
@use hyaenidae_toolkit::{templates::button_group, Button};
@use hyaenidae_toolkit::{templates::{card, card_body, card_title}, Card};
@(view: &ReportView, nav_state: &NavState)
@:home("Submission Reported", &format!("Reported {}'s submission", view.profile.name()), nav_state, {}, {
@:card(&Card::full_width().dark(nav_state.dark()), {
@:card_title({ Submission Reported })
@:card_body({
@:button_group(&[
Button::secondary("Back to Submission").href(&view.submission.view_path()).dark(nav_state.dark()),
])
})
})
})