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
-
+
// content for this welcome page
impl IntoView {
}>
-
+
}
}
+
+#[cfg(feature = "ssr")]
+#[derive(Debug, Clone)]
+pub struct AppState {
+ pub saleor_app: std::sync::Arc>,
+ 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! {
- {if errors.len() > 1 {"Errors"} else {"Error"}}
+ {if errors.len() > 1 { "Errors" } else { "Error" }}
{error_code.to_string()}
"Error: " {error_string}
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:
- //
- // 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::>()
+ .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,
+}
+
+#[derive(cynic::QueryFragment, Debug)]
+pub struct ProductUpdated {
+ pub product: Option,
+}
+
+#[derive(cynic::QueryFragment, Debug)]
+pub struct ProductDeleted {
+ pub product: Option,
+}
+
+#[derive(cynic::QueryFragment, Debug)]
+pub struct ProductCreated {
+ pub product: Option,
+}
+
+#[derive(cynic::QueryFragment, Debug)]
+pub struct Product {
+ pub id: cynic::Id,
+ pub slug: String,
+ pub name: String,
+ pub category: Option,
+}
+
+#[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>>,
+ 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,
+}
+
+#[derive(cynic::QueryFragment, Debug)]
+pub struct ProductUpdate {
+ pub errors: Vec,
+ pub product: Option,
+}
+
+#[derive(cynic::QueryFragment, Debug)]
+pub struct Product {
+ pub id: cynic::Id,
+ pub metadata: Vec,
+}
+
+#[derive(cynic::QueryFragment, Debug)]
+pub struct ProductError {
+ pub field: Option,
+ pub message: Option,
+ pub code: ProductErrorCode,
+ pub attributes: Option>,
+ pub values: Option>,
+}
+
+#[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) -> Result, 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,
+ Json(auth_token): Json,
+) -> Result {
+ 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,
+ //Will try to convert req body to ProductUpdated type, else returns 400
+ Json(product): Json,
+) -> Result {
+ 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)",
},
},
},