diff --git a/crates/prime-domain/src/delete_entry.rs b/crates/prime-domain/src/delete_entry.rs new file mode 100644 index 00000000..e8ab79ef --- /dev/null +++ b/crates/prime-domain/src/delete_entry.rs @@ -0,0 +1,18 @@ +use db::DeleteModelError; +use models::{Entry, dvf::RecordId}; + +use crate::PrimeDomainService; + +impl PrimeDomainService { + /// Deletes an [`Entry`]. + pub async fn delete_entry( + &self, + id: RecordId, + ) -> Result>, DeleteModelError> { + self + .entry_repo + .delete_model(id) + .await + .map(|b| b.then_some(id)) + } +} diff --git a/crates/prime-domain/src/lib.rs b/crates/prime-domain/src/lib.rs index 1bec5ab5..e37ebf0f 100644 --- a/crates/prime-domain/src/lib.rs +++ b/crates/prime-domain/src/lib.rs @@ -2,6 +2,7 @@ mod counts; mod create; +mod delete_entry; pub mod download; mod fetch_by_id; mod fetch_by_name; diff --git a/crates/site-app/src/components/copy_button.rs b/crates/site-app/src/components/copy_button.rs index 409ec697..288c6211 100644 --- a/crates/site-app/src/components/copy_button.rs +++ b/crates/site-app/src/components/copy_button.rs @@ -4,11 +4,11 @@ use crate::components::DocumentDuplicateHeroIcon; #[island] pub fn CopyButton(copy_content: String) -> impl IntoView { - let copy_content = Signal::stored(copy_content); + let _copy_content = Signal::stored(copy_content); let on_click = move |_| { #[cfg(feature = "hydrate")] - (leptos_use::use_clipboard().copy)(©_content()) + (leptos_use::use_clipboard().copy)(&_copy_content()) }; const CLASS: &str = "cursor-pointer stroke-[2.0] stroke-base-11/50 \ diff --git a/crates/site-app/src/hooks.rs b/crates/site-app/src/hooks.rs index e071fd3d..110ee4e3 100644 --- a/crates/site-app/src/hooks.rs +++ b/crates/site-app/src/hooks.rs @@ -3,6 +3,7 @@ mod cache_hook; mod create_cache_hook; mod create_org_hook; +mod delete_entry_hook; mod entry_hook; mod login_hook; mod org_hook; @@ -10,6 +11,6 @@ mod signup_hook; // pub use self::cache_hook::*; pub use self::{ - create_cache_hook::*, create_org_hook::*, entry_hook::*, login_hook::*, - org_hook::*, signup_hook::*, + create_cache_hook::*, create_org_hook::*, delete_entry_hook::*, + entry_hook::*, login_hook::*, org_hook::*, signup_hook::*, }; diff --git a/crates/site-app/src/hooks/delete_entry_hook.rs b/crates/site-app/src/hooks/delete_entry_hook.rs new file mode 100644 index 00000000..62e61dfd --- /dev/null +++ b/crates/site-app/src/hooks/delete_entry_hook.rs @@ -0,0 +1,83 @@ +use leptos::{prelude::*, server::ServerAction}; +use models::{dvf::RecordId, Entry}; + +use crate::{hooks::OrgHook, navigation::navigate_to}; + +pub struct DeleteEntryHook { + key: Callback<(), RecordId>, + action: ServerAction, +} + +impl DeleteEntryHook { + pub fn new( + key: impl Fn() -> RecordId + Copy + Send + Sync + 'static, + ) -> Self { + Self { + key: Callback::new(move |()| key()), + action: ServerAction::new(), + } + } + + pub fn show_spinner(&self) -> Signal { + let (pending, value) = (self.action.pending(), self.action.value()); + // show if the action is loading or completed successfully + Signal::derive(move || pending() || matches!(value.get(), Some(Ok(_)))) + } + + pub fn button_text(&self) -> Signal<&'static str> { + let (pending, value) = (self.action.pending(), self.action.value()); + Signal::derive(move || match (value.get(), pending()) { + // if the action is loading at all + (_, true) => "Deleting...", + // if it's completed successfully + (Some(Ok(_)), _) => "Redirecting...", + // any other state + _ => "Delete Entry", + }) + } + + pub fn action_trigger(&self) -> Callback<()> { + let (key, action) = (self.key, self.action); + Callback::new(move |()| { + action.dispatch(DeleteEntry { id: key.run(()) }); + }) + } + + pub fn create_redirect_effect(&self) -> Effect { + let action = self.action; + Effect::new(move || { + if matches!(action.value().get(), Some(Ok(_))) { + let org_hook = OrgHook::new_requested(); + navigate_to(&org_hook.dashboard_url()()); + } + }) + } +} + +#[server(prefix = "/api/sfn")] +async fn delete_entry( + id: RecordId, +) -> Result>, ServerFnError> { + use prime_domain::PrimeDomainService; + + let prime_domain_service = expect_context::(); + + let entry = + prime_domain_service + .fetch_entry_by_id(id) + .await + .map_err(|e| { + tracing::error!("failed to fetch entry to delete: {e}"); + ServerFnError::new("internal error") + })?; + let Some(entry) = entry else { + return Ok(None); + }; + + crate::resources::authorize_for_org(entry.org)?; + + prime_domain_service.delete_entry(id).await.map_err(|e| { + tracing::error!("failed to delete entry: {e}"); + ServerFnError::new("internal error") + }) +} diff --git a/crates/site-app/src/pages/entry.rs b/crates/site-app/src/pages/entry.rs index 52f5fbb3..26934afa 100644 --- a/crates/site-app/src/pages/entry.rs +++ b/crates/site-app/src/pages/entry.rs @@ -1,16 +1,17 @@ +mod action_tile; +mod caches_tile; +mod store_path_tile; +mod title_tile; + use leptos::prelude::*; -use leptos_fetch::QueryClient; use leptos_router::hooks::use_params_map; -use models::{ - dvf::{RecordId, Visibility}, - Cache, Entry, PvCache, StorePath, -}; +use models::{dvf::RecordId, Entry}; -use crate::{ - components::{CacheItemLink, CopyButton, LockClosedHeroIcon}, - hooks::EntryHook, - pages::UnauthorizedPage, +use self::{ + action_tile::ActionTile, caches_tile::CachesTile, + store_path_tile::StorePathTile, title_tile::TitleTile, }; +use crate::{hooks::EntryHook, pages::UnauthorizedPage}; #[component] pub fn EntryPage() -> impl IntoView { @@ -46,9 +47,11 @@ pub fn EntryPage() -> impl IntoView { #[component] fn EntryInner(entry: Entry) -> impl IntoView { view! { -
+
+