tinkr 0.0.43

Tinkr is a web framework for quickly building full-stack web applications with Leptos.
Documentation
#[cfg(feature = "ssr")]
use crate::AppError;
use leptos::prelude::*;
use partial_struct::Partial;
use serde::{Deserialize, Serialize};

use crate::{
    Datetime, RecordId,
    session::get_user,
    user::{AdapterUser, DeliveryDetails},
};

#[cfg(feature = "ssr")]
use crate::db_init;

use crate::{
    cart::cart::{CartItemWithProduct, ProductMini, SHIPPING_COST},
    product::product::get_product_from_id,
};

use crate::payments::payment::Payment;

#[derive(Debug, Clone, Serialize, Deserialize, Partial, PartialEq)]
#[partial(
    "OrderCreate",
    derive(PartialEq, Clone, Debug, Serialize, Deserialize),
    omit(id, user_full, payment)
)]
#[partial(
    "OrderRow",
    derive(PartialEq, Clone, Debug, Serialize, Deserialize),
    omit(user_full, delivery_details)
)]
pub struct OrderFull {
    pub id: RecordId,
    pub client_received: bool,
    pub courier: String,
    pub created_at: Datetime,
    pub done: bool,
    pub items: Vec<OrderItem>,
    pub paid: bool,
    pub shipped: bool,
    pub total: String,
    pub tracking_number: String,
    pub user: RecordId,
    pub user_full: Option<AdapterUser>,
    pub delivery_details: Option<DeliveryDetails>,
    pub payment: Option<Payment>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct OrderItem {
    pub date_added: Datetime,
    // pub id: RecordId,
    pub name: String,
    pub price: f64,
    pub product: Option<RecordId>,
    pub variant: String,
}

#[component]
pub fn OrderItemView(item: OrderItem) -> impl IntoView {
    let models_res = LocalResource::new(move || get_product_from_id(item.product.clone()));

    move || {
        let name = item.name.clone();

        let product = models_res.get();

        let product = match product {
            Some(Ok(p)) => p,
            _ => return view! { <div>"Loading..."</div> }.into_any(),
        };

        view! {
            <div class="flex justify-between py-2 border-b border-neutral-200 dark:border-neutral-700">
                <Transition>
                    <ProductMini
                        background=product.colour.clone()
                        brand_name=product.brand_name.clone()
                        model_name=product.model_name.clone()
                        product_name=product.product_name.clone()
                        product_code=product.code.clone()
                        product_variant=name.clone()
                    />
                </Transition>
            </div>
        }.into_any()
    }
}

impl OrderFull {
    pub fn order_ref(&self) -> String {
        self.id.key().to_string()
    }

    pub fn format_created_at(&self) -> String {
        "todo".to_string()
    }

    pub fn created_at_ago(&self) -> String {
        // let (_, relative) = crate::date_utils::format_datetime_local(&self.created_at);
        // relative
        "todo".to_string()
    }

    pub fn format_total(&self) -> String {
        format!("R {}", self.total)
    }

    #[cfg(feature = "ssr")]
    pub async fn create_from_cart() -> Result<RecordId, AppError> {
        use crate::order::order_model::OrderItem;

        let user = get_user().await?;
        let db = db_init().await?;

        println!("Creating order for user: {:?}", user);

        // Get cart items
        let cart_items: Vec<OrderItem> = db
            .query("SELECT * FROM cart_item WHERE user = $user;")
            .bind(("user", user.id.clone()))
            .await?
            .take(0)?;

        println!("Cart items: {:?}", cart_items);

        if cart_items.is_empty() {
            return Err(AppError::new("Cart is empty"));
        }

        // Calculate total
        let items_total: f64 = cart_items.iter().map(|i| i.price).sum();
        let total_with_shipping = items_total + SHIPPING_COST;

        // in future we could store multiple delivery addresses per user and have them select when placing an order
        let delivery_details: DeliveryDetails = user.clone().into();

        let neworder: OrderCreate = OrderCreate {
            client_received: false,
            courier: "no courier".to_string(),
            created_at: surrealdb::Datetime::from(chrono::Utc::now()),
            done: false,
            paid: false,
            shipped: false,
            items: cart_items,
            total: total_with_shipping.to_string(),
            tracking_number: "no Tracking Number".to_string(),
            user: user.id.clone(),
            // save delivery details at time of order
            delivery_details: Some(delivery_details),
        };

        let mut result = db
            .query("CREATE order CONTENT $order;")
            .bind(("order", neworder))
            .await?;

        let created_order: Option<OrderRow> = result.take(0)?;

        let order_id = match created_order {
            Some(order) => order.id,
            None => return Err(AppError::new("Failed to create order")),
        };

        // Delete cart items
        db.query("DELETE cart_item WHERE user = $user")
            .bind(("user", user.id))
            .await?;

        Ok(order_id)
    }
}

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

    Ok(output)
}