tinkr 0.0.43

Tinkr is a web framework for quickly building full-stack web applications with Leptos.
Documentation
use super::order_details::OrderDetails;
use crate::order::{order_model::OrderFull, view::fetch_user_order};
use crate::{
    RecordId,
    components::{
        Alert, AlertSeverity,
        loading::{LoadingIndicator, LoadingIndicatorSize},
    },
};
use leptos::prelude::*;
use leptos_router::hooks::use_params_map;

#[server]
pub async fn update_order_edit(order: OrderFull) -> Result<(), ServerFnError> {
    let user = crate::session::get_user().await?;

    // Check if user is admin
    if user.is_admin != Some(true) {
        return Err(ServerFnError::ServerError("Unauthorized".into()));
    }

    let db = crate::db::db_init().await?;

    // Check if we need to send tracking email (when order is marked as shipped AND done)
    let should_send_tracking_email = order.shipped && order.done;

    // Update the order
    let _: Option<OrderFull> = db
        .update(("order", order.id.key().to_string()))
        .content(serde_json::json!({
            "courier": order.courier,
            "tracking_number": order.tracking_number,
            "shipped": order.shipped,
            "client_received": order.client_received,
            "done": order.done,
        }))
        .await?;

    // Send tracking email if conditions are met
    if should_send_tracking_email {
        if let Err(e) = send_tracking_email(&order).await {
            // Log error but don't fail the order update
            tracing::error!("Failed to send tracking email: {:?}", e);
        }
    }

    Ok(())
}

#[cfg(feature = "ssr")]
async fn send_tracking_email(order: &OrderFull) -> Result<(), ServerFnError> {
    use crate::email::send_email;
    use crate::email_templates;

    let user_full = if let Some(uf) = &order.user_full {
        uf
    } else {
        return Err(ServerFnError::new("User details missing for order"));
    };

    // Get customer email from user_full (already EmailAddress type)
    let email_addr = user_full.email.clone();

    // Get customer name (name is String, not Option<String>)
    let client_name = user_full.name.clone();

    // Get order reference
    let order_ref = order.order_ref();

    // Prepare email
    let subject = "Your Scratch Fix Pro Order Has Been Shipped!!!";
    let body = email_templates::order_tracking_email(
        &client_name,
        &order_ref,
        &order.courier,
        &order.tracking_number,
    );

    // Send email
    match send_email(email_addr, subject, &body).await {
        Ok(_) => {
            tracing::info!("Tracking email sent successfully for order {}", order_ref);
            Ok(())
        }
        Err(e) => Err(ServerFnError::new(format!("Email send failed: {:?}", e))),
    }
}

#[component]
pub fn OrderEdit() -> impl IntoView {
    let params = use_params_map();
    let order_id = move || params.read().get("order_id").unwrap_or_default();

    let order_resource = Resource::new(
        move || {
            let ordid = order_id();

            RecordId::from_table_key("order", &ordid)
        },
        fetch_user_order,
    );

    let save_action = ServerAction::<UpdateOrderEdit>::new();

    let on_save = Callback::new(move |order: OrderFull| {
        save_action.dispatch(UpdateOrderEdit { order });
    });

    view! {
        <Suspense fallback=move || {
            view! {
                <div class="min-h-screen w-full flex items-center justify-center bg-white dark:bg-neutral-950">
                    <LoadingIndicator size=LoadingIndicatorSize::Huge />
                </div>
            }
        }>

            {move || {
                order_resource
                    .get()
                    .map(|order_result| {
                        match order_result {
                            Ok(order) => {
                                view! {
                                    <>
                                        {save_action
                                            .value()
                                            .get()
                                            .and_then(|result: Result<(), ServerFnError>| {
                                                match result {
                                                    Ok(_) => {
                                                        Some(
                                                            view! {
                                                                <div class="fixed top-4 right-4 bg-green-500 text-white px-4 py-2 rounded shadow-lg">
                                                                    "Order saved successfully!"
                                                                </div>
                                                            }
                                                                .into_any(),
                                                        )
                                                    }
                                                    Err(e) => {
                                                        Some(
                                                            view! {
                                                                <div class="fixed top-4 right-4 bg-red-500 text-white px-4 py-2 rounded shadow-lg">
                                                                    "Error saving order: " {e.to_string()}
                                                                </div>
                                                            }
                                                                .into_any(),
                                                        )
                                                    }
                                                }
                                            })}
                                        <OrderDetails order=order is_admin=true on_save=on_save />
                                    </>
                                }
                                    .into_any()
                            }
                            Err(err) => {
                                view! {
                                    <Alert severity=AlertSeverity::Error>
                                        "Error loading order: " {err.to_string()}
                                    </Alert>
                                }
                                    .into_any()
                            }
                        }
                    })
            }}

        </Suspense>
    }
}