use leptos::*; use leptos_meta::*; use leptos_router::*; #[server(GetLinksAction, "/api", "GetJson")] #[tracing::instrument] pub async fn get_links() -> Result, ServerFnError> { crate::models::Link::get_all().await.map_err(|x| { let err = format!("Error while posting a link: {x:?}"); tracing::error!("{err}"); ServerFnError::ServerError("Could not post a link, try again later".into()) }) } #[server(LinkAction, "/api")] pub async fn add_value(name: String, link: String, icon: String) -> Result<(), ServerFnError> { crate::models::Link::insert(name, link, icon) .await .map(|_| ()) .map_err(|x| { let err = format!("Error while posting a comment: {x:?}"); tracing::error!("{err}"); ServerFnError::ServerError("Could not post a comment, try again later".into()) }) } #[component] pub fn Links() -> impl IntoView { let (show_form, set_show_form) = create_signal(false); let (edit_link, set_edit_link) = create_signal::>(None); let (edit, set_edit) = create_signal(false); let link_action = create_server_action::(); let update_action = create_server_action::(); let result = link_action.version(); let update_result = update_action.version(); let links = create_resource( move || (result.get(), update_result.get()), move |_| async move { set_show_form.set(false); set_edit_link.set(None); get_links().await }, ); view! { <ul class="flex gap-5 m-5 flex-wrap justify-center"> <Suspense fallback=move || view! {<p>"Loading Comments from the article"</p> }> <ErrorBoundary fallback=|_| { view! { <p class="error-messages text-xs-center">"Something went wrong."</p>} }> {move || links.get().map(move |x| x.map(move |c| { view! { <For each=move || c.clone().into_iter().enumerate() key=|(i, _)| *i children=move |(_, link)| { let link = create_rw_signal(link); view!{ <Link link edit set_edit_link set_show_form links /> } }/> } }))} </ErrorBoundary> </Suspense> </ul> <div class="flex justify-end gap-5 mx-5"> <button on:click=move |_| {set_edit.set(!edit.get())} class="bg-prim-light hover:bg-prim-lightest rounded-full px-5 py-3 text-second-dark hover:text-third transition-colors">"Modifier"</button> <button on:click=move |_| { set_edit_link.set(None); set_show_form.set(true); } class="bg-prim-light hover:bg-prim-lightest rounded-full px-5 py-3 text-second-dark hover:text-third transition-colors">"Ajouter un lien +"</button> </div> {move || { show_form.get().then(|| { view! { <LinkFormModal link=edit_link.get() set_show=set_show_form add_action=link_action update_action=update_action /> } }) }} } } #[server(MoveLinkAction, "/api")] pub async fn change_position(link_id: String, direction: String) -> Result<(), ServerFnError> { crate::models::Link::move_position(link_id, direction) .await .map(|_| ()) .map_err(|x| { let err = format!("Error while posting a comment: {x:?}"); tracing::error!("{err}"); ServerFnError::ServerError("Could not post a comment, try again later".into()) }) } #[component] fn Link<T: 'static + Clone, S: 'static>( link: RwSignal<crate::models::Link>, edit: ReadSignal<bool>, set_edit_link: WriteSignal<Option<crate::models::Link>>, set_show_form: WriteSignal<bool>, links: Resource<T, S>, ) -> impl IntoView { view! { <li class="mx-auto w-44 lg:w-60"> <a class="bg-prim-light w-full hover:bg-prim-lightest border-b hover:border-third border-transparent text-xl rounded-lg text-center hover:text-third transition-colors px-5 py-4 inline-block" target="_blank" href={move || link.with(|x| x.link.to_string())}> <div class="flex justify-center mb-2"> <img src={move || link.with(|x| x.icon.to_string())} alt={move || link.with(|x| x.name.to_string())} class="block size-32 lg:size-54 object-cover overflow-hidden" /> </div> <span>{move || link.with(|x| x.name.to_string())}</span> </a> {move || { edit.get().then(|| { view! { <div class="bg-third border border-transparent rounded-b-lg flex justify-between"> <MoveButton id=link.with(|x| x.id.to_string()) value="prev".to_string() label="<".to_string() links=links /> <button class="block px-2 py-1 flex-1 hover:bg-third-light" on:click=move |_| { set_edit_link.set(Some(link.get())); set_show_form.set(true); } >"Editer"</button> <DeleteButton id=link.with(|x| x.id.to_string()) links=links /> <MoveButton id=link.with(|x| x.id.to_string()) value="next".to_string() label=">".to_string() links=links /> </div> } }) }} </li> } } #[component] fn MoveButton<T: 'static + Clone, S: 'static>( id: String, value: String, label: String, links: Resource<T, S>, ) -> impl IntoView { let move_link = create_server_action::<MoveLinkAction>(); view! { <ActionForm action=move_link on:submit= move |ev| { let Ok(_) = MoveLinkAction::from_event(&ev) else { return ev.prevent_default(); }; links.refetch(); }> <input name="link_id" type="hidden" value=id /> <input name="direction" type="hidden" value=value /> <button class="block w-12 px-2 py-1 hover:bg-third-light rounded-br-lg">{label}</button> </ActionForm> } } #[server(DeleteLinkAction, "/api")] pub async fn delete_link(link_id: String) -> Result<(), ServerFnError> { crate::models::Link::destroy(link_id) .await .map(|_| ()) .map_err(|x| { let err = format!("Error while posting a comment: {x:?}"); tracing::error!("{err}"); ServerFnError::ServerError("Could not post a comment, try again later".into()) }) } #[component] fn DeleteButton<T: 'static + Clone, S: 'static>( id: String, links: Resource<T, S>, ) -> impl IntoView { let delete_link = create_server_action::<DeleteLinkAction>(); view! { <ActionForm action=delete_link on:submit= move |ev| { let Ok(_) = DeleteLinkAction::from_event(&ev) else { return ev.prevent_default(); }; links.refetch(); }> <input name="link_id" type="hidden" value=id /> <button type="submit" class="block px-2 py-1 flex-1 hover:bg-third-light">"Supp."</button> </ActionForm> } } #[server(UpdateLinkAction, "/api")] pub async fn update_link(id: String, name: String, link: String, icon: String) -> Result<(), ServerFnError> { crate::models::Link::update(id, name, link, icon) .await .map(|_| ()) .map_err(|x| { let err = format!("Error while updating a link: {x:?}"); tracing::error!("{err}"); ServerFnError::ServerError("Could not update the link, try again later".into()) }) } #[component] fn LinkFormModal( link: Option<crate::models::Link>, set_show: WriteSignal<bool>, add_action: Action<LinkAction, Result<(), ServerFnError>>, update_action: Action<UpdateLinkAction, Result<(), ServerFnError>>, ) -> impl IntoView { let is_edit = link.is_some(); let (name, set_name) = create_signal(link.as_ref().map(|x| x.name.clone()).unwrap_or_default()); let (link_url, set_link_url) = create_signal(link.as_ref().map(|x| x.link.clone()).unwrap_or_default()); let (icon, set_icon) = create_signal(link.as_ref().map(|x| x.icon.clone()).unwrap_or_default()); let link_id = link.as_ref().map(|x| x.id.to_string()); view! { <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> <div class="bg-prim-light p-6 rounded-lg w-96"> <h2 class="pb-6 text-2xl text-center"> {if is_edit { "Édition du lien" } else { "Ajout d'un lien" }} </h2> {if is_edit { view! { <ActionForm action=update_action> <input name="id" type="hidden" value={link_id.unwrap_or_default()} /> <LinkFormFields name=name set_name=set_name link_url=link_url set_link_url=set_link_url icon=icon set_icon=set_icon /> <FormButtons set_show=set_show /> </ActionForm> }.into_view() } else { view! { <ActionForm action=add_action> <LinkFormFields name=name set_name=set_name link_url=link_url set_link_url=set_link_url icon=icon set_icon=set_icon /> <FormButtons set_show=set_show /> </ActionForm> }.into_view() }} </div> </div> } } #[component] fn LinkFormFields( name: ReadSignal<String>, set_name: WriteSignal<String>, link_url: ReadSignal<String>, set_link_url: WriteSignal<String>, icon: ReadSignal<String>, set_icon: WriteSignal<String>, ) -> impl IntoView { view! { <div> <label class="block mt-3 mb-1">"Nom"</label> <input type="text" name="name" prop:value=move || name.get() on:input=move |ev| set_name.set(event_target_value(&ev)) class="text-center bg-prim border border-transparent rounded-lg focus:border-third px-2 py-2 w-full" /> </div> <div> <label class="block mt-3 mb-1">"Lien"</label> <input type="url" name="link" prop:value=move || link_url.get() on:input=move |ev| set_link_url.set(event_target_value(&ev)) class="text-center bg-prim border border-transparent rounded-lg focus:border-third px-2 py-2 w-full" /> </div> <div> <label class="block mt-3 mb-1">"Icône"</label> <input type="url" name="icon" placeholder="http..." prop:value=move || icon.get() on:input=move |ev| set_icon.set(event_target_value(&ev)) class="text-center bg-prim border border-transparent rounded-lg focus:border-third px-2 py-2 w-full" /> </div> } } #[component] fn FormButtons(set_show: WriteSignal<bool>) -> impl IntoView { view! { <div class="flex gap-2 mt-5"> <button type="button" on:click=move |_| set_show.set(false) class="bg-prim hover:bg-prim-light rounded-lg transition-colors px-2 py-1 flex-1">"Annuler"</button> <button type="submit" class="bg-third hover:bg-third-light rounded-lg transition-colors px-2 py-1 flex-1">"Valider"</button> </div> } }