diff --git a/app-template-ui/.neoconf.json b/app-template-ui/.neoconf.json new file mode 100644 index 0000000..86fa32b --- /dev/null +++ b/app-template-ui/.neoconf.json @@ -0,0 +1,12 @@ +{ + "lspconfig": { + "rust_analyzer": { + "rust-analyzer.cargo.features": "all", + "rust-analyzer.rustfmt.overrideCommand": [ + "leptosfmt", + "--stdin", + "--rustfmt" + ] + } + } +} diff --git a/app-template-ui/Cargo.toml b/app-template-ui/Cargo.toml index 4eb10cd..992d272 100644 --- a/app-template-ui/Cargo.toml +++ b/app-template-ui/Cargo.toml @@ -18,6 +18,7 @@ crate-type = ["cdylib", "rlib"] axum = { workspace = true, optional = true } console_error_panic_hook = { workspace = true } leptos = { workspace = true, features = ["nightly"] } +anyhow = { workspace = true, optional = true } leptos_axum = { workspace = true, optional = true } leptos_meta = { workspace = true, features = ["nightly"] } leptos_router = { workspace = true, features = ["nightly"] } @@ -29,13 +30,20 @@ tower = { workspace = true, optional = true } tower-http = { workspace = true, features = ["fs"], optional = true } wasm-bindgen = { workspace = true } tracing = { workspace = true, optional = true } +tracing-subscriber = { workspace = true, optional = true } thiserror = { workspace = true } http = { workspace = true } pulldown-cmark = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -saleor-app-sdk = { workspace = true } +saleor-app-sdk = { workspace = true, features = ["bridge"], optional = true } dotenvy = { workspace = true } +envy = { workspace = true } +cynic = { workspace = true, features = ["http-surf"], optional = true } +surf = { workspace = true, optional = true } + +[build-dependencies] +cynic-codegen = { workspace = true, optional = true } [features] hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"] @@ -45,12 +53,19 @@ ssr = [ "dep:tower", "dep:tower-http", "dep:leptos_axum", + "dep:tracing", + "dep:saleor-app-sdk", + "dep:tracing-subscriber", + "dep:anyhow", + "dep:cynic", + "dep:cynic-codegen", + "dep:surf", "leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr", - "dep:tracing", ] + # Defines a size-optimized profile for the WASM bundle in release mode [profile.wasm-release] inherits = "release" diff --git a/app-template-ui/build.rs b/app-template-ui/build.rs new file mode 100644 index 0000000..1262982 --- /dev/null +++ b/app-template-ui/build.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "ssr")] +fn main() { + cynic_codegen::register_schema("saleor") + .from_sdl_file("../schema.graphql") + .unwrap() + .as_default() + .unwrap(); +} + +#[cfg(not(feature = "ssr"))] +fn main() {} diff --git a/app-template-ui/public/favicon.ico b/app-template-ui/public/favicon.ico index 2ba8527..33e1bd3 100644 Binary files a/app-template-ui/public/favicon.ico and b/app-template-ui/public/favicon.ico differ diff --git a/app-template-ui/public/fonts/Inter-Black.woff2 b/app-template-ui/public/fonts/Inter-Black.woff2 new file mode 100644 index 0000000..68f64c9 Binary files /dev/null and b/app-template-ui/public/fonts/Inter-Black.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-BlackItalic.woff2 b/app-template-ui/public/fonts/Inter-BlackItalic.woff2 new file mode 100644 index 0000000..1c9c7ca Binary files /dev/null and b/app-template-ui/public/fonts/Inter-BlackItalic.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-Bold.woff2 b/app-template-ui/public/fonts/Inter-Bold.woff2 new file mode 100644 index 0000000..2846f29 Binary files /dev/null and b/app-template-ui/public/fonts/Inter-Bold.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-BoldItalic.woff2 b/app-template-ui/public/fonts/Inter-BoldItalic.woff2 new file mode 100644 index 0000000..0b1fe8e Binary files /dev/null and b/app-template-ui/public/fonts/Inter-BoldItalic.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-ExtraBold.woff2 b/app-template-ui/public/fonts/Inter-ExtraBold.woff2 new file mode 100644 index 0000000..c24c2bd Binary files /dev/null and b/app-template-ui/public/fonts/Inter-ExtraBold.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-ExtraBoldItalic.woff2 b/app-template-ui/public/fonts/Inter-ExtraBoldItalic.woff2 new file mode 100644 index 0000000..4a81dc7 Binary files /dev/null and b/app-template-ui/public/fonts/Inter-ExtraBoldItalic.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-ExtraLight.woff2 b/app-template-ui/public/fonts/Inter-ExtraLight.woff2 new file mode 100644 index 0000000..f2ea706 Binary files /dev/null and b/app-template-ui/public/fonts/Inter-ExtraLight.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-ExtraLightItalic.woff2 b/app-template-ui/public/fonts/Inter-ExtraLightItalic.woff2 new file mode 100644 index 0000000..9af717b Binary files /dev/null and b/app-template-ui/public/fonts/Inter-ExtraLightItalic.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-Italic.woff2 b/app-template-ui/public/fonts/Inter-Italic.woff2 new file mode 100644 index 0000000..a619fc5 Binary files /dev/null and b/app-template-ui/public/fonts/Inter-Italic.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-Light.woff2 b/app-template-ui/public/fonts/Inter-Light.woff2 new file mode 100644 index 0000000..bc4be66 Binary files /dev/null and b/app-template-ui/public/fonts/Inter-Light.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-LightItalic.woff2 b/app-template-ui/public/fonts/Inter-LightItalic.woff2 new file mode 100644 index 0000000..842b2df Binary files /dev/null and b/app-template-ui/public/fonts/Inter-LightItalic.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-Medium.woff2 b/app-template-ui/public/fonts/Inter-Medium.woff2 new file mode 100644 index 0000000..f92498a Binary files /dev/null and b/app-template-ui/public/fonts/Inter-Medium.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-MediumItalic.woff2 b/app-template-ui/public/fonts/Inter-MediumItalic.woff2 new file mode 100644 index 0000000..0e3019f Binary files /dev/null and b/app-template-ui/public/fonts/Inter-MediumItalic.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-Regular.woff2 b/app-template-ui/public/fonts/Inter-Regular.woff2 new file mode 100644 index 0000000..6c2b689 Binary files /dev/null and b/app-template-ui/public/fonts/Inter-Regular.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-SemiBold.woff2 b/app-template-ui/public/fonts/Inter-SemiBold.woff2 new file mode 100644 index 0000000..611e90c Binary files /dev/null and b/app-template-ui/public/fonts/Inter-SemiBold.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-SemiBoldItalic.woff2 b/app-template-ui/public/fonts/Inter-SemiBoldItalic.woff2 new file mode 100644 index 0000000..545685b Binary files /dev/null and b/app-template-ui/public/fonts/Inter-SemiBoldItalic.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-Thin.woff2 b/app-template-ui/public/fonts/Inter-Thin.woff2 new file mode 100644 index 0000000..abbc3a5 Binary files /dev/null and b/app-template-ui/public/fonts/Inter-Thin.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-ThinItalic.woff2 b/app-template-ui/public/fonts/Inter-ThinItalic.woff2 new file mode 100644 index 0000000..ab0b200 Binary files /dev/null and b/app-template-ui/public/fonts/Inter-ThinItalic.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-italic.var.woff2 b/app-template-ui/public/fonts/Inter-italic.var.woff2 new file mode 100644 index 0000000..b826d5a Binary files /dev/null and b/app-template-ui/public/fonts/Inter-italic.var.woff2 differ diff --git a/app-template-ui/public/fonts/Inter-roman.var.woff2 b/app-template-ui/public/fonts/Inter-roman.var.woff2 new file mode 100644 index 0000000..6a256a0 Binary files /dev/null and b/app-template-ui/public/fonts/Inter-roman.var.woff2 differ diff --git a/app-template-ui/public/fonts/Inter.var.woff2 b/app-template-ui/public/fonts/Inter.var.woff2 new file mode 100644 index 0000000..365eedc Binary files /dev/null and b/app-template-ui/public/fonts/Inter.var.woff2 differ diff --git a/app-template-ui/public/logo.png b/app-template-ui/public/logo.png new file mode 100644 index 0000000..f591130 Binary files /dev/null and b/app-template-ui/public/logo.png differ diff --git a/app-template-ui/rust-toolchain.toml b/app-template-ui/rust-toolchain.toml new file mode 100644 index 0000000..9a106fc --- /dev/null +++ b/app-template-ui/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly-2024-04-28" +## Toggle to this one for sdk releases +# channel = "stable" +targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] diff --git a/app-template-ui/src/app.rs b/app-template-ui/src/app.rs index 2595fa1..b3afb65 100644 --- a/app-template-ui/src/app.rs +++ b/app-template-ui/src/app.rs @@ -15,10 +15,10 @@ pub fn App() -> impl IntoView { provide_meta_context(); view! { - + // sets the document title - + <Title text="Example UI App template in Rust"/> // content for this welcome page <Router fallback=|| { @@ -28,9 +28,18 @@ pub fn App() -> impl IntoView { }> <main class="p-4 md:p-8 md:px-16"> <Routes> - <Route path="" view=Home/> + <Route path="/" view=Home/> </Routes> </main> </Router> } } + +#[cfg(feature = "ssr")] +#[derive(Debug, Clone)] +pub struct AppState { + pub saleor_app: std::sync::Arc<tokio::sync::Mutex<saleor_app_sdk::SaleorApp>>, + pub config: saleor_app_sdk::config::Config, + pub manifest: saleor_app_sdk::manifest::AppManifest, + pub leptos_options: LeptosOptions, +} diff --git a/app-template-ui/src/components/universal/app_editor.rs b/app-template-ui/src/components/universal/app_editor.rs index 2ffe2aa..8b13789 100644 --- a/app-template-ui/src/components/universal/app_editor.rs +++ b/app-template-ui/src/components/universal/app_editor.rs @@ -1 +1 @@ -use leptos::*; + diff --git a/app-template-ui/src/components/universal/mod.rs b/app-template-ui/src/components/universal/mod.rs index f489bc5..8b13789 100644 --- a/app-template-ui/src/components/universal/mod.rs +++ b/app-template-ui/src/components/universal/mod.rs @@ -1 +1 @@ -pub mod app_editor; + diff --git a/app-template-ui/src/error_template.rs b/app-template-ui/src/error_template.rs index 1e0508d..074d6c2 100644 --- a/app-template-ui/src/error_template.rs +++ b/app-template-ui/src/error_template.rs @@ -1,7 +1,41 @@ -use http::status::StatusCode; use leptos::*; + +#[cfg(feature = "ssr")] +use axum::response::{IntoResponse, Response}; +#[cfg(feature = "ssr")] +use http::header::ToStrError; +use http::status::StatusCode; use thiserror::Error; +/* ERROR STUFF FOR AXUM */ + +#[cfg(feature = "ssr")] +#[derive(Error, Debug)] +pub enum AxumError { + #[error("Error converting something to string, `{0}`")] + ToStrError(#[from] ToStrError), + #[error("Anyhow function error: {0}")] + Anyhow(#[from] anyhow::Error), + #[error("Request is missing header `{0}`")] + MissingHeader(String), + #[error("Internal server error, `{0}`")] + InternalServerError(String), +} + +// Tell axum how to convert `AppError` into a response. +#[cfg(feature = "ssr")] +impl IntoResponse for AxumError { + fn into_response(self) -> Response { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Something went wrong: {:?}", self), + ) + .into_response() + } +} + +/* THIS IS ERROR STUFF FOR LEPTOS */ + #[derive(Clone, Debug, Error)] pub enum AppError { #[error("Not Found")] @@ -52,16 +86,16 @@ pub fn ErrorTemplate( } view! { - <h1>{if errors.len() > 1 {"Errors"} else {"Error"}}</h1> + <h1>{if errors.len() > 1 { "Errors" } else { "Error" }}</h1> <For // a function that returns the items we're iterating over; a signal is fine - each= move || {errors.clone().into_iter().enumerate()} + each=move || { errors.clone().into_iter().enumerate() } // a unique key for each item as a reference key=|(index, _error)| *index // renders each item to a view children=move |error| { let error_string = error.1.to_string(); - let error_code= error.1.status_code(); + let error_code = error.1.status_code(); view! { <h2>{error_code.to_string()}</h2> <p>"Error: " {error_string}</p> diff --git a/app-template-ui/src/lib.rs b/app-template-ui/src/lib.rs index 32befb0..6c06995 100644 --- a/app-template-ui/src/lib.rs +++ b/app-template-ui/src/lib.rs @@ -2,10 +2,11 @@ pub mod app; pub mod error_template; #[cfg(feature = "ssr")] pub mod fileserv; +#[cfg(feature = "ssr")] +pub mod queries; pub mod components; pub mod routes; -pub mod server; #[cfg(feature = "hydrate")] #[wasm_bindgen::prelude::wasm_bindgen] diff --git a/app-template-ui/src/main.rs b/app-template-ui/src/main.rs index 683033f..876e6cf 100644 --- a/app-template-ui/src/main.rs +++ b/app-template-ui/src/main.rs @@ -1,44 +1,133 @@ -pub mod server; -pub mod components; -pub mod routes; +#![allow( + non_upper_case_globals, + clippy::large_enum_variant, + clippy::upper_case_acronyms, + dead_code +)] +#![feature(let_chains)] #[cfg(feature = "ssr")] -pub mod fileserv; +mod fileserv; #[cfg(feature = "ssr")] -pub mod error_template; +mod queries; + +mod app; +mod components; +mod routes; +mod error_template; -#[cfg(feature = "ssr")] -pub mod app; -#[cfg(feature = "ssr")] #[tokio::main] -async fn main() { +#[cfg(feature = "ssr")] +async fn main() -> Result<(), std::io::Error> { - use axum::Router; + use std::sync::Arc; + use axum::{middleware, routing::{post,get}, Router}; use leptos::*; use leptos_axum::{generate_route_list, LeptosRoutes}; - use crate::app::*; - use crate::fileserv::file_and_error_handler; - // Setting get_configuration(None) means we'll be using cargo-leptos's env values - // For deployment these variables are: - // <https://github.com/leptos-rs/start-axum#executing-a-server-on-a-remote-machine-without-the-toolchain> - // Alternately a file can be specified such as Some("Cargo.toml") - // The file would need to be included with the executable when moved to deployment + use app::*; + use fileserv::file_and_error_handler; + use saleor_app_sdk::middleware::verify_webhook_signature::webhook_signature_verifier; + use tokio::sync::Mutex; +use saleor_app_sdk::{ + cargo_info, + config::Config, + manifest::{AppManifestBuilder, AppPermission}, + webhooks::{AsyncWebhookEventType, WebhookManifestBuilder}, + SaleorApp, +}; + + use crate::routes::api::{manifest::manifest, register::register, webhooks::webhooks}; + + //Leptos stuff let conf = get_configuration(None).await.unwrap(); let leptos_options = conf.leptos_options; - let addr = leptos_options.site_addr; let routes = generate_route_list(App); - // build our application with a route - let app = Router::new() + // Saleor stuff + let config = Config::load().unwrap(); + trace_to_std(&config).unwrap(); + let saleor_app = SaleorApp::new(&config).unwrap(); + + let app_manifest = AppManifestBuilder::new(&config, cargo_info!()) + .add_webhook( + WebhookManifestBuilder::new(&config) + .set_query( + r#" + subscription QueryProductsChanged { + event { + ... on ProductUpdated { + product { + ... BaseProduct + } + } + ... on ProductCreated { + product { + ... BaseProduct + } + } + ... on ProductDeleted { + product { + ... BaseProduct + } + } + } + } + + fragment BaseProduct on Product { + id + slug + name + category { + slug + } + } + "#, + ) + .add_async_events(vec![ + AsyncWebhookEventType::ProductCreated, + AsyncWebhookEventType::ProductUpdated, + AsyncWebhookEventType::ProductDeleted, + ]) + .build(), + ) + .add_permission(AppPermission::ManageProducts) + .build(); + + let app_state = AppState{ + manifest: app_manifest, + config: config.clone(), + saleor_app: Arc::new(Mutex::new(saleor_app)), + leptos_options, + }; + + let state_1 = app_state.clone(); + let app = + Router::new() + .layer(middleware::from_fn(webhook_signature_verifier)) + .route("/api/webhooks", post(webhooks)) + .route("/api/register", post(register)) + .route("/api/manifest", get(manifest)) + // .leptos_routes_with_context(&leptos_options, routes,move || provide_context(state_1.clone()) , App) .leptos_routes(&leptos_options, routes, App) .fallback(file_and_error_handler) - .with_state(leptos_options); + .with_state(app_state.clone()); - let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); - logging::log!("listening on http://{}", &addr); - axum::serve(listener, app.into_make_service()) - .await - .unwrap(); + + let listener = tokio::net::TcpListener::bind( + "0.0.0.0:".to_owned() + + config + .app_api_base_url + .split(':') + .collect::<Vec<_>>() + .get(2) + .unwrap_or(&"3000"), + ) + .await?; + tracing::debug!("listening on {}", listener.local_addr()?); + + let _= axum::serve(listener, app.into_make_service()) + .await; + Ok(()) } #[cfg(not(feature = "ssr"))] @@ -47,3 +136,26 @@ pub fn main() { // unless we want this to work with e.g., Trunk for a purely client-side app // see lib.rs for hydration function instead } + + +#[cfg(feature = "ssr")] +use saleor_app_sdk::config::Config; +#[cfg(feature = "ssr")] +pub fn trace_to_std(config: &Config) -> Result<(), envy::Error> { + use tracing::level_filters::LevelFilter; + use tracing_subscriber::EnvFilter; + + let filter = EnvFilter::builder() + .with_default_directive(LevelFilter::DEBUG.into()) + .from_env().unwrap() + .add_directive(format!("{}={}", env!("CARGO_PKG_NAME"), config.log_level).parse().unwrap()); + tracing_subscriber::fmt() + .with_max_level(config.log_level) + .with_env_filter(filter) + .with_target(true) + .compact() + .init(); + Ok(()) +} + + diff --git a/app-template-ui/src/queries/event_products_updated.rs b/app-template-ui/src/queries/event_products_updated.rs new file mode 100644 index 0000000..edd69b7 --- /dev/null +++ b/app-template-ui/src/queries/event_products_updated.rs @@ -0,0 +1,45 @@ +#[cynic::schema("saleor")] +mod schema {} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "Subscription")] +pub struct QueryProductsChanged { + pub event: Option<Event>, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct ProductUpdated { + pub product: Option<Product>, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct ProductDeleted { + pub product: Option<Product>, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct ProductCreated { + pub product: Option<Product>, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct Product { + pub id: cynic::Id, + pub slug: String, + pub name: String, + pub category: Option<Category>, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct Category { + pub slug: String, +} + +#[derive(cynic::InlineFragments, Debug)] +pub enum Event { + ProductUpdated(ProductUpdated), + ProductCreated(ProductCreated), + ProductDeleted(ProductDeleted), + #[cynic(fallback)] + Unknown, +} diff --git a/app-template-ui/src/queries/mod.rs b/app-template-ui/src/queries/mod.rs new file mode 100644 index 0000000..f577554 --- /dev/null +++ b/app-template-ui/src/queries/mod.rs @@ -0,0 +1,2 @@ +pub mod event_products_updated; +pub mod product_metadata_update; diff --git a/app-template-ui/src/queries/product_metadata_update.rs b/app-template-ui/src/queries/product_metadata_update.rs new file mode 100644 index 0000000..8dfa70a --- /dev/null +++ b/app-template-ui/src/queries/product_metadata_update.rs @@ -0,0 +1,75 @@ +#[cynic::schema("saleor")] +mod schema {} + +#[derive(cynic::QueryVariables, Debug)] +pub struct UpdateProductMetadataVariables<'a> { + pub metadata: Option<Vec<MetadataInput<'a>>>, + pub product_id: &'a cynic::Id, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic( + graphql_type = "Mutation", + variables = "UpdateProductMetadataVariables" +)] +pub struct UpdateProductMetadata { + #[arguments(id: $product_id, input: { metadata: $metadata })] + pub product_update: Option<ProductUpdate>, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct ProductUpdate { + pub errors: Vec<ProductError>, + pub product: Option<Product>, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct Product { + pub id: cynic::Id, + pub metadata: Vec<MetadataItem>, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct ProductError { + pub field: Option<String>, + pub message: Option<String>, + pub code: ProductErrorCode, + pub attributes: Option<Vec<cynic::Id>>, + pub values: Option<Vec<cynic::Id>>, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct MetadataItem { + pub key: String, + pub value: String, +} + +#[derive(cynic::Enum, Clone, Copy, Debug)] +pub enum ProductErrorCode { + AlreadyExists, + AttributeAlreadyAssigned, + AttributeCannotBeAssigned, + AttributeVariantsDisabled, + MediaAlreadyAssigned, + DuplicatedInputItem, + GraphqlError, + Invalid, + InvalidPrice, + ProductWithoutCategory, + NotProductsImage, + NotProductsVariant, + NotFound, + Required, + Unique, + VariantNoDigitalContent, + CannotManageProductWithoutVariant, + ProductNotAssignedToChannel, + UnsupportedMediaProvider, + PreorderVariantCannotBeDeactivated, +} + +#[derive(cynic::InputObject, Debug)] +pub struct MetadataInput<'a> { + pub key: &'a str, + pub value: &'a str, +} diff --git a/app-template-ui/src/routes/api/manifest.rs b/app-template-ui/src/routes/api/manifest.rs new file mode 100644 index 0000000..787b366 --- /dev/null +++ b/app-template-ui/src/routes/api/manifest.rs @@ -0,0 +1,8 @@ +use axum::{extract::State, Json}; +use saleor_app_sdk::manifest::AppManifest; + +use crate::{app::AppState, error_template::AxumError}; + +pub fn manifest(State(state): State<AppState>) -> Result<Json<AppManifest>, AxumError> { + Ok(Json(state.manifest)) +} diff --git a/app-template-ui/src/routes/api/mod.rs b/app-template-ui/src/routes/api/mod.rs new file mode 100644 index 0000000..ac2542e --- /dev/null +++ b/app-template-ui/src/routes/api/mod.rs @@ -0,0 +1,3 @@ +pub mod manifest; +pub mod register; +pub mod webhooks; diff --git a/app-template-ui/src/routes/api/register.rs b/app-template-ui/src/routes/api/register.rs new file mode 100644 index 0000000..9e214ba --- /dev/null +++ b/app-template-ui/src/routes/api/register.rs @@ -0,0 +1,41 @@ +use anyhow::Context; +use axum::{ + extract::Json, + extract::State, + http::{HeaderMap, StatusCode}, +}; +use saleor_app_sdk::{AuthData, AuthToken}; +use tracing::{debug, info}; + +use crate::{app::AppState, error_template::AxumError}; + + +pub async fn register( + headers: HeaderMap, + State(state): State<AppState>, + Json(auth_token): Json<AuthToken>, +) -> Result<StatusCode, AxumError> { + debug!( + "/api/register:\nsaleor_api_url:{:?}\nauth_token:{:?}", + headers.get("saleor-api-url"), + auth_token + ); + + if auth_token.auth_token.is_empty() { + return Err(anyhow::anyhow!("missing auth_token").into()); + } + let app = state.saleor_app.lock().await; + let saleor_api_url = headers.get("saleor-api-url").context("missing api field")?; + let saleor_api_url = saleor_api_url.to_str()?.to_owned(); + let auth_data = AuthData { + jwks: None, + token: auth_token.auth_token, + domain: Some(state.config.app_api_base_url), + app_id: state.manifest.id, + saleor_api_url: saleor_api_url.clone(), + }; + app.apl.set(auth_data).await?; + + info!("registered app for{:?}", &saleor_api_url); + Ok(StatusCode::OK) +} diff --git a/app-template-ui/src/routes/api/webhooks.rs b/app-template-ui/src/routes/api/webhooks.rs new file mode 100644 index 0000000..e432bfb --- /dev/null +++ b/app-template-ui/src/routes/api/webhooks.rs @@ -0,0 +1,70 @@ +use axum::{ + extract::{Json, State}, + http::{HeaderMap, StatusCode}, +}; +use cynic::{http::SurfExt, MutationBuilder}; +use saleor_app_sdk::{ + headers::{SALEOR_API_URL_HEADER,SALEOR_EVENT_HEADER}, + webhooks::{ + utils::{get_webhook_event_type, EitherWebhookType}, + AsyncWebhookEventType, + }, +}; + +use tracing::{debug, info}; + +use crate::{ + app::AppState, error_template::AxumError, queries::{event_products_updated::ProductUpdated, product_metadata_update::{MetadataInput, UpdateProductMetadata, UpdateProductMetadataVariables}} }; + +pub async fn webhooks( + headers: HeaderMap, + State(state): State<AppState>, + //Will try to convert req body to ProductUpdated type, else returns 400 + Json(product): Json<ProductUpdated>, +) -> Result<StatusCode, AxumError> { + debug!("/api/webhooks"); + debug!("req: {:?}", product); + debug!("headers: {:?}", headers); + + let url = headers + .get(SALEOR_API_URL_HEADER).ok_or(AxumError::MissingHeader(SALEOR_API_URL_HEADER.to_owned()))?; + let event_type = get_webhook_event_type(&headers).map_err(|_|AxumError::MissingHeader(SALEOR_EVENT_HEADER.to_owned()))?; + if let EitherWebhookType::Async(a) = event_type { + match a { + AsyncWebhookEventType::ProductUpdated + | AsyncWebhookEventType::ProductCreated + | AsyncWebhookEventType::ProductDeleted => { + update_product(product, url.to_str()?, state).await? + } + _ => (), + } + } + + info!("got webhooks!"); + Ok(StatusCode::OK) +} + +async fn update_product( + product: ProductUpdated, + saleor_api_url: &str, + state: AppState, +) -> Result<(), anyhow::Error> { + debug!("Product got changed!"); + if let Some(product) = product.product { + let operation = UpdateProductMetadata::build(UpdateProductMetadataVariables { + product_id: &product.id, + metadata: Some(vec![MetadataInput { + key: "helloloo", + value: "hiiiihii", + }]), + }); + let saleor_app = state.saleor_app.lock().await; + let auth_data = saleor_app.apl.get(saleor_api_url).await?; + let result = surf::post(saleor_api_url) + .header("Authorization", format!("bearer {}", auth_data.token)) + .run_graphql(operation) + .await; + debug!("update product result : {:?}", result); + } + Ok(()) +} diff --git a/app-template-ui/src/routes/mod.rs b/app-template-ui/src/routes/mod.rs index 9b86bcf..c5befc6 100644 --- a/app-template-ui/src/routes/mod.rs +++ b/app-template-ui/src/routes/mod.rs @@ -1 +1,3 @@ +#[cfg(feature = "ssr")] +pub mod api; pub mod home; diff --git a/app-template-ui/src/server/functions.rs b/app-template-ui/src/server/functions.rs deleted file mode 100644 index 2ffe2aa..0000000 --- a/app-template-ui/src/server/functions.rs +++ /dev/null @@ -1 +0,0 @@ -use leptos::*; diff --git a/app-template-ui/src/server/mod.rs b/app-template-ui/src/server/mod.rs deleted file mode 100644 index 014fff1..0000000 --- a/app-template-ui/src/server/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod functions; diff --git a/app-template-ui/style/base.css b/app-template-ui/style/base.css index 175f94e..330d3be 100644 --- a/app-template-ui/style/base.css +++ b/app-template-ui/style/base.css @@ -5,16 +5,18 @@ @tailwind utilities; @layer base { - body { - @apply bg-brand-white; - } - - html { - @apply text-brand-black; + @supports (font-variation-settings: normal) { + body { + font-family: "'Inter var',sans-serif"; + } } ::selection { - @apply bg-brand-sunset-400; + background-color: rgba(255, 255, 255, 0.2); + } + + body { + @apply font-serif text-default1 bg-default1; } h1, @@ -23,22 +25,22 @@ h4, h5, h6 { - @apply font-sans my-4 text-brand-sea-300 text-2xl; + @apply my-4 text-2xl text-default1; } h2 { - @apply text-brand-sea-300 text-xl; + @apply text-xl; } h3, h4, h5, h6 { - @apply text-brand-sea-500 text-base; + @apply text-base; } p { - @apply font-serif my-2 text-base; + @apply my-2 text-base; } input:disabled, @@ -66,13 +68,202 @@ } } -@layer components { - .header-gradient { - background: theme("colors.brand-sea.400"); - background: linear-gradient(25deg, - theme("colors.brand-sunset.500") 0%, - theme("colors.brand-sea.300") 45%, - theme("colors.brand-sea.300") 55%, - theme("colors.brand-sunset.500") 100%); - } +@font-face { + font-family: "Inter"; + src: + local("Inter"), + url("/fonts/Inter-Thin.woff2") format("woff2"); + font-display: swap; + font-style: normal; + font-weight: 100; +} + +@font-face { + font-family: "Inter"; + src: + local("Inter"), + url("/fonts/Inter-ThinItalic.woff2") format("woff2"); + font-display: swap; + font-style: italic; + font-weight: 100; +} + +@font-face { + font-family: "Inter"; + src: + local("Inter"), + url("/fonts/Inter-ExtraLight.woff2") format("woff2"); + font-display: swap; + font-style: normal; + font-weight: 200; +} + +@font-face { + font-family: "Inter"; + src: + local("Inter"), + url("/fonts/Inter-ExtraLightItalic.woff2") format("woff2"); + font-display: swap; + font-style: italic; + font-weight: 200; +} + +@font-face { + font-family: "Inter"; + src: + local("Inter"), + url("/fonts/Inter-Light.woff2") format("woff2"); + font-display: swap; + font-style: normal; + font-weight: 300; +} + +@font-face { + font-family: "Inter"; + src: + local("Inter"), + url("/fonts/Inter-LightItalic.woff2") format("woff2"); + font-display: swap; + font-style: italic; + font-weight: 300; +} + +@font-face { + font-family: "Inter"; + src: + local("Inter"), + url("/fonts/Inter-Regular.woff2") format("woff2"); + font-display: swap; + font-style: normal; + font-weight: 400; +} + +@font-face { + font-family: "Inter"; + src: + local("Inter"), + url("/fonts/Inter-Italic.woff2") format("woff2"); + font-display: swap; + font-style: italic; + font-weight: 400; +} + +@font-face { + font-family: "Inter"; + src: + local("Inter"), + url("/fonts/Inter-Medium.woff2") format("woff2"); + font-display: swap; + font-style: normal; + font-weight: 500; +} + +@font-face { + font-family: "Inter"; + src: + local("Inter"), + url("/fonts/Inter-MediumItalic.woff2") format("woff2"); + font-display: swap; + font-style: italic; + font-weight: 500; +} + +@font-face { + font-family: "Inter"; + src: + local("Inter"), + url("/fonts/Inter-SemiBold.woff2") format("woff2"); + font-display: swap; + font-style: normal; + font-weight: 600; +} + +@font-face { + font-family: "Inter"; + src: + local("Inter"), + url("/fonts/Inter-SemiBoldItalic.woff2") format("woff2"); + font-display: swap; + font-style: italic; + font-weight: 600; +} + +@font-face { + font-family: "Inter"; + src: + local("Inter"), + url("/fonts/Inter-Bold.woff2") format("woff2"); + font-display: swap; + font-style: normal; + font-weight: 700; +} + +@font-face { + font-family: "Inter"; + src: + local("Inter"), + url("/fonts/Inter-BoldItalic.woff2") format("woff2"); + font-display: swap; + font-style: italic; + font-weight: 700; +} + +@font-face { + font-family: "Inter"; + src: + local("Inter"), + url("/fonts/Inter-ExtraBold.woff2") format("woff2"); + font-display: swap; + font-style: normal; + font-weight: 800; +} + +@font-face { + font-family: "Inter"; + src: + local("Inter"), + url("/fonts/Inter-ExtraBoldItalic.woff2") format("woff2"); + font-display: swap; + font-style: italic; + font-weight: 800; +} + +@font-face { + font-family: "Inter"; + src: + local("Inter"), + url("/fonts/Inter-Black.woff2") format("woff2"); + font-display: swap; + font-style: normal; + font-weight: 900; +} + +@font-face { + font-family: "Inter"; + src: + local("Inter"), + url("/fonts/Inter-BlackItalic.woff2") format("woff2"); + font-display: swap; + font-style: italic; + font-weight: 900; +} + +@font-face { + font-family: "Inter var"; + src: + local("Inter"), + url("/fonts/Inter-roman.var.woff2") format("woff2"); + font-display: swap; + font-style: normal; + font-weight: 100 900; +} + +@font-face { + font-family: "Inter var"; + src: + local("Inter"), + url("/fonts/Inter-italic.var.woff2") format("woff2"); + font-display: swap; + font-style: italic; + font-weight: 100 900; } diff --git a/app-template-ui/tailwind.config.js b/app-template-ui/tailwind.config.js index bad3a9a..89e8550 100644 --- a/app-template-ui/tailwind.config.js +++ b/app-template-ui/tailwind.config.js @@ -1,3 +1,4 @@ +/** STYLES TAKEN PARTIALLY FROM SALEORS MACAW-UI**/ /** @type {import('tailwindcss').Config} */ module.exports = { content: { @@ -5,8 +6,7 @@ module.exports = { }, theme: { fontFamily: { - sans: ["Space Grotesk", "sans-serif"], - serif: ["PT Serif", "serif"], + serif: ["Inter", "sans-serif"], }, fontSize: { xs: "0.75rem", @@ -26,30 +26,71 @@ module.exports = { max: "999999px", }, extend: { + backgroundColor: { + accent1: "hsla(215, 100%, 62%, 1)", + accent1Hovered: "hsla(215, 100%, 51%, 0.16)", + accent1Pressed: "hsla(215, 100%, 51%, 0.32)", + buttonCriticalDisabled: "hsla(204, 16%, 94%, 1)", + buttonCriticalPrimary: "hsla(11, 100%, 56%, 1)", + buttonCriticalPrimaryFocused: "hsla(11, 100%, 42%, 1)", + buttonCriticalPrimaryHovered: "hsla(11, 100%, 42%, 1)", + buttonCriticalPrimaryPressed: "hsla(11, 100%, 29%, 1)", + buttonDefaultDisabled: "hsla(211, 32%, 21%, 1)", + buttonDefaultPrimary: "hsla(0, 0%, 100%, 1)", + buttonDefaultPrimaryFocused: "hsla(210, 24%, 86%, 1)", + buttonDefaultPrimaryHovered: "hsla(211, 24%, 86%, 1)", + buttonDefaultPrimaryPressed: "hsla(211, 16%, 68%, 1)", + buttonDefaultSecondary: "hsla(232, 17%, 18%, 1)", + buttonDefaultSecondaryFocused: "hsla(211, 32%, 19%, 1)", + buttonDefaultSecondaryHovered: "hsla(211, 32%, 19%, 1)", + buttonDefaultSecondaryPressed: "hsla(211, 24%, 26%, 1)", + buttonDefaultTertiary: "hsla(180, 4%, 15%, 0)", + buttonDefaultTertiaryFocused: "hsla(0, 0%, 100%, 0.06)", + buttonDefaultTertiaryHovered: "hsla(0, 0%, 100%, 0.06)", + buttonDefaultTertiaryPressed: "hsla(0, 0%, 100%, 0.12)", + critical1: "hsla(11, 100%, 96%, 1)", + critical1Focused: "hsla(11, 100%, 46%, 0.2)", + critical1Hovered: "hsla(11, 100%, 46%, 0.2)", + critical1Pressed: "hsla(11, 100%, 46%, 0.32)", + critical2: "hsla(11, 100%, 56%, 1)", + default1: "hsla(232, 17%, 18%, 1)", + default1Focused: "hsla(0, 0%, 100%, 0.06)", + default1Hovered: "hsla(0, 0%, 100%, 0.06)", + default1Pressed: "hsla(0, 0%, 100%, 0.12)", + default2: "hsla(231, 17%, 16%, 1)", + default3: "hsla(211, 42%, 12%, 1)", + defaultDisabled: "hsla(211, 32%, 21%, 1)", + info1: "hsla(215, 100%, 62%, 1)", + success1: "hsla(173, 100%, 32%, 1)", + warning1: "hsla(42, 100%, 84%, 1)", + }, + borderColor: { + accent1: "hsla(215, 100%, 39%, 1)", + critical1: "hsla(11, 100%, 35%, 1)", + default1: "hsla(210, 32%, 25%, 1)", + default1Focused: "hsla(212, 24%, 32%, 1)", + default1Hovered: "hsla(210, 32%, 25%, 1)", + defaultDisabled: "hsla(231, 18%, 23%, 1)", + default2: "hsla(211, 21%, 39%, 1)", + info1: "hsla(210, 32%, 25%, 1)", + success1: "hsl(173, 79%, 62%, 1)", + warning1: "hsla(36, 44%, 50%, 1)", + }, colors: { - "brand-sea": { - 100: "#C5ECE0", - 200: "#17C3B2", - 300: "#1DA0A8", - 400: "#227C9D", - 500: "#094074", - }, - "brand-sunset": { - 100: "#FEF9EF", - 200: "#FFEED1", - 300: "#FFE2B3", - 400: "#FFD795", - 500: "#FFCB77", - }, - "brand-red": { - 100: "#FED6D0", - 200: "#FEB3B1", - 300: "#FE9092", - 400: "#FE7F83", - 500: "#FE6D73", - }, - "brand-black": "#161a1e", - "brand-white": "#fbfbfb", + accent1: "hsla(215, 100%, 83%, 1)", + buttonCriticalDisabled: "hsla(212, 14%, 67%, 1)", + buttonCriticalPrimary: "hsla(0, 0%, 100%, 1)", + buttonDefaultPrimary: "hsla(212, 44%, 13%, 1)", + buttonDefaultSecondary: "hsla(0, 0%, 100%, 1)", + buttonDefaultTertiary: "hsla(0, 0%, 100%, 1)", + critical1: "hsla(11, 100%, 82%, 1)", + critical2: "hsla(11, 100%, 58%, 1)", + default1: "hsla(0, 0%, 100%, 1)", + default2: "hsla(230, 10%, 53%, 1)", + defaultDisabled: "hsla(212, 19%, 39%, 1)", + info1: "hsla(215, 100%, 83%, 1)", + success1: "hsla(173, 79%, 62%, 1)", + warning1: "hsla(36, 44%, 50%, 1)", }, }, },