@ -1,4 +1,4 @@
//! Quote detail page — view, edit, delete, and report a single quote.
//! Quote detail page — view, edit, delete, hide, and report a single quote.
use crate ::api ::{ self , ApiError } ;
use crate ::api ::{ self , ApiError } ;
use crate ::components ::auth_modal ::AuthModal ;
use crate ::components ::auth_modal ::AuthModal ;
@ -23,6 +23,8 @@ enum Action {
EditForm ,
EditForm ,
/// Delete auth modal shown.
/// Delete auth modal shown.
DeleteAuth ,
DeleteAuth ,
/// Hide/unhide auth modal shown.
HideAuth ,
/// Report modal shown.
/// Report modal shown.
Report ,
Report ,
}
}
@ -37,10 +39,11 @@ pub struct QuotePageProps {
/// Quote detail page.
/// Quote detail page.
///
///
/// Fetches a quote by ID, renders it with [`QuoteCard`], and provides
/// Fetches a quote by ID, renders it with [`QuoteCard`], and provides
/// Edit, Delete, and Report actions.
/// Edit, Delete, Hide/Unhide, and Report actions.
///
///
/// - Edit: shows auth modal → edit form → `POST /api/quotes/:id` → re-fetch
/// - Edit: shows auth modal → edit form → `POST /api/quotes/:id` → re-fetch
/// - Delete: shows auth modal → `DELETE /api/quotes/:id` → navigate to `/browse`
/// - Delete: shows auth modal → `DELETE /api/quotes/:id` → navigate to `/browse`
/// - Hide/Unhide: shows auth modal → `POST /api/quotes/:id` `{ hidden: bool }` → update state
/// - Report: shows [`ReportModal`] → `POST /api/quotes/:id/report` → success message
/// - Report: shows [`ReportModal`] → `POST /api/quotes/:id/report` → success message
/// - 403 errors clear the stored auth code and display an error message.
/// - 403 errors clear the stored auth code and display an error message.
/// - 404 errors display a user-friendly "not found" message.
/// - 404 errors display a user-friendly "not found" message.
@ -239,6 +242,50 @@ pub fn quote_page(props: &QuotePageProps) -> Html {
} )
} )
} ;
} ;
// --- Hide/unhide auth modal submitted ---
let on_hide_auth = {
let id = id . clone ( ) ;
let action = action . clone ( ) ;
let action_error = action_error . clone ( ) ;
let quote = quote . clone ( ) ;
Callback ::from ( move | code : String | {
let id = id . clone ( ) ;
let action = action . clone ( ) ;
let action_error = action_error . clone ( ) ;
let quote = quote . clone ( ) ;
// Determine the desired hidden state from current quote value.
let new_hidden = ( * quote ) . as_ref ( ) . map ( | q | ! q . hidden ) . unwrap_or ( true ) ;
storage ::set_auth_code ( & id , & code ) ;
spawn_local ( async move {
let input = UpdateQuoteInput {
text : None ,
author : None ,
source : None ,
date : None ,
tags : None ,
hidden : Some ( new_hidden ) ,
} ;
match api ::update_quote ( & id , & input , & code ) . await {
Ok ( updated ) = > {
storage ::set_auth_code ( & id , & code ) ;
quote . set ( Some ( updated ) ) ;
action . set ( Action ::None ) ;
action_error . set ( None ) ;
}
Err ( ApiError ::Server { status : 403 , .. } ) = > {
storage ::clear_auth_code ( & id ) ;
action_error . set ( Some ( "Wrong auth code. Please try again." . to_string ( ) ) ) ;
action . set ( Action ::HideAuth ) ;
}
Err ( e ) = > {
action_error . set ( Some ( e . to_string ( ) ) ) ;
action . set ( Action ::None ) ;
}
}
} ) ;
} )
} ;
let on_cancel = {
let on_cancel = {
let action = action . clone ( ) ;
let action = action . clone ( ) ;
let action_error = action_error . clone ( ) ;
let action_error = action_error . clone ( ) ;
@ -284,6 +331,12 @@ pub fn quote_page(props: &QuotePageProps) -> Html {
} else if let Some ( err ) = ( * error ) . clone ( ) {
} else if let Some ( err ) = ( * error ) . clone ( ) {
< ErrorDisplay message = { err } / >
< ErrorDisplay message = { err } / >
} else if let Some ( q ) = ( * quote ) . clone ( ) {
} else if let Some ( q ) = ( * quote ) . clone ( ) {
if q . hidden {
< div class = "page-quote__hidden-badge" >
{ "Hidden — this quote is not shown in listings" }
< / div >
}
< QuoteCard quote = { q . clone ( ) } / >
< QuoteCard quote = { q . clone ( ) } / >
if let Some ( auth ) = ( * new_auth ) . clone ( ) {
if let Some ( auth ) = ( * new_auth ) . clone ( ) {
@ -339,6 +392,19 @@ pub fn quote_page(props: &QuotePageProps) -> Html {
>
>
{ "Delete" }
{ "Delete" }
< / button >
< / button >
< button
class = "btn btn--muted"
onclick = { {
let action = action . clone ( ) ;
let action_error = action_error . clone ( ) ;
Callback ::from ( move | _ | {
action_error . set ( None ) ;
action . set ( Action ::HideAuth ) ;
} )
} }
>
{ if q . hidden { "Make Public" } else { "Hide" } }
< / button >
< button
< button
class = "btn btn--muted"
class = "btn btn--muted"
onclick = { {
onclick = { {
@ -470,6 +536,14 @@ pub fn quote_page(props: &QuotePageProps) -> Html {
/ >
/ >
}
}
if * action = = Action ::HideAuth {
< AuthModal
on_submit = { on_hide_auth }
on_cancel = { on_cancel . clone ( ) }
prefill = { storage ::get_auth_code ( & id ) }
/ >
}
if * action = = Action ::Report {
if * action = = Action ::Report {
< ReportModal
< ReportModal
on_submit = { on_report_submit }
on_submit = { on_report_submit }