Add phone number and address handling
This commit is contained in:
parent
c1c97061eb
commit
7617cb1064
@ -37,6 +37,41 @@ WHERE account_id = $1
|
||||
.await
|
||||
.map_err(|_| Error::AccountAddresses.into())
|
||||
}
|
||||
////
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<model::AccountAddress>")]
|
||||
pub struct FindAccountAddress {
|
||||
pub account_id: model::AccountId,
|
||||
pub address_id: model::AddressId,
|
||||
}
|
||||
|
||||
db_async_handler!(
|
||||
FindAccountAddress,
|
||||
find_account_address,
|
||||
model::AccountAddress,
|
||||
inner_find_account_address
|
||||
);
|
||||
|
||||
pub(crate) async fn find_account_address(
|
||||
msg: FindAccountAddress,
|
||||
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<model::AccountAddress> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, name, email, street, city, country, zip, account_id, is_default
|
||||
FROM account_addresses
|
||||
WHERE account_id = $1 AND id = $2
|
||||
"#,
|
||||
)
|
||||
.bind(msg.account_id)
|
||||
.bind(msg.address_id)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|_| Error::AccountAddresses.into())
|
||||
}
|
||||
|
||||
/////
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<model::AccountAddress>")]
|
||||
|
@ -84,11 +84,18 @@ pub struct CreateOrderAddress {
|
||||
pub zip: model::Zip,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OrderAddressInput {
|
||||
Address(CreateOrderAddress),
|
||||
AccountAddress(model::AddressId),
|
||||
DefaultAccountAddress,
|
||||
}
|
||||
|
||||
#[derive(Message, Debug)]
|
||||
#[rtype(result = "Result<Order>")]
|
||||
pub struct CreateAccountOrder {
|
||||
pub account_id: AccountId,
|
||||
pub create_address: Option<CreateOrderAddress>,
|
||||
pub order_address: OrderAddressInput,
|
||||
}
|
||||
|
||||
order_async_handler!(CreateAccountOrder, create_account_order, Order);
|
||||
@ -115,29 +122,43 @@ pub(crate) async fn create_account_order(
|
||||
Error::ShoppingCart,
|
||||
Error::DatabaseInternal
|
||||
);
|
||||
let address: model::AccountAddress = if let Some(input) = msg.create_address {
|
||||
query_db!(
|
||||
db,
|
||||
database_manager::CreateAccountAddress {
|
||||
name: input.name,
|
||||
email: input.email,
|
||||
street: input.street,
|
||||
city: input.city,
|
||||
country: input.country,
|
||||
zip: input.zip,
|
||||
account_id: None,
|
||||
is_default: true,
|
||||
},
|
||||
Error::InvalidAccountAddress
|
||||
)
|
||||
} else {
|
||||
query_db!(
|
||||
db,
|
||||
database_manager::DefaultAccountAddress {
|
||||
account_id: cart.buyer_id
|
||||
},
|
||||
Error::NoAddress
|
||||
)
|
||||
|
||||
let address: model::AccountAddress = match msg.order_address {
|
||||
OrderAddressInput::Address(input) => {
|
||||
query_db!(
|
||||
db,
|
||||
database_manager::CreateAccountAddress {
|
||||
name: input.name,
|
||||
email: input.email,
|
||||
street: input.street,
|
||||
city: input.city,
|
||||
country: input.country,
|
||||
zip: input.zip,
|
||||
account_id: Some(cart.buyer_id),
|
||||
is_default: true,
|
||||
},
|
||||
Error::InvalidAccountAddress
|
||||
)
|
||||
}
|
||||
OrderAddressInput::AccountAddress(address_id) => {
|
||||
query_db!(
|
||||
db,
|
||||
database_manager::FindAccountAddress {
|
||||
address_id,
|
||||
account_id: cart.buyer_id
|
||||
},
|
||||
Error::NoAddress
|
||||
)
|
||||
}
|
||||
OrderAddressInput::DefaultAccountAddress => {
|
||||
query_db!(
|
||||
db,
|
||||
database_manager::DefaultAccountAddress {
|
||||
account_id: cart.buyer_id
|
||||
},
|
||||
Error::NoAddress
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
query_db!(
|
||||
|
@ -51,6 +51,12 @@ pub enum Error {
|
||||
Token(token_manager::Error),
|
||||
}
|
||||
|
||||
impl From<public::api_v1::Error> for Error {
|
||||
fn from(e: public::api_v1::Error) -> Self {
|
||||
Self::Public(public::Error::ApiV1(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<V1ShoppingCartError> for Error {
|
||||
fn from(sv1: V1ShoppingCartError) -> Self {
|
||||
Self::Public(PublicError::ApiV1(V1Error::ShoppingCart(sv1)))
|
||||
|
@ -21,6 +21,9 @@ pub enum Error {
|
||||
|
||||
#[error("Failed to create order")]
|
||||
AddOrder,
|
||||
|
||||
#[error("Can't place order. Client IP is unknown")]
|
||||
NoIp,
|
||||
}
|
||||
|
||||
pub(crate) fn configure(config: &mut ServiceConfig) {
|
||||
|
@ -236,7 +236,7 @@ pub(crate) async fn create_order(
|
||||
credentials: BearerAuth,
|
||||
payment: Data<Addr<PaymentManager>>,
|
||||
order: Data<Addr<OrderManager>>,
|
||||
) -> routes::Result<HttpResponse> {
|
||||
) -> routes::Result<Json<api::PlaceOrderResult>> {
|
||||
let account_id = credentials
|
||||
.require_user(tm.into_inner())
|
||||
.await?
|
||||
@ -254,7 +254,7 @@ pub(crate) async fn create_order(
|
||||
} = payload;
|
||||
let ip = match req.peer_addr() {
|
||||
Some(ip) => ip,
|
||||
_ => return Ok(HttpResponse::BadRequest().body("No IP")),
|
||||
_ => return Err(super::Error::NoIp.into()),
|
||||
};
|
||||
|
||||
let payment_manager::CreatePaymentResult { redirect_uri, .. } = query_pay!(
|
||||
@ -275,37 +275,43 @@ pub(crate) async fn create_order(
|
||||
routes::Error::Public(PublicError::DatabaseConnection)
|
||||
);
|
||||
|
||||
query_order!(
|
||||
let order_address = match address {
|
||||
api::OrderAddressInput::DefaultAccountAddress => {
|
||||
order_manager::OrderAddressInput::DefaultAccountAddress
|
||||
}
|
||||
api::OrderAddressInput::AccountAddress(id) => {
|
||||
order_manager::OrderAddressInput::AccountAddress(id)
|
||||
}
|
||||
api::OrderAddressInput::Address(api::CreateOrderAddress {
|
||||
name,
|
||||
email,
|
||||
street,
|
||||
city,
|
||||
country,
|
||||
zip,
|
||||
}) => order_manager::OrderAddressInput::Address(order_manager::CreateOrderAddress {
|
||||
name,
|
||||
email: email.clone(),
|
||||
street,
|
||||
city,
|
||||
country,
|
||||
zip,
|
||||
}),
|
||||
};
|
||||
let order: model::Order = query_order!(
|
||||
order,
|
||||
order_manager::CreateAccountOrder {
|
||||
account_id,
|
||||
create_address: address.map(
|
||||
|model::api::CreateOrderAddress {
|
||||
name,
|
||||
email,
|
||||
street,
|
||||
city,
|
||||
country,
|
||||
zip,
|
||||
}| order_manager::CreateOrderAddress {
|
||||
name,
|
||||
email,
|
||||
street,
|
||||
city,
|
||||
country,
|
||||
zip,
|
||||
},
|
||||
),
|
||||
order_address,
|
||||
},
|
||||
PublicError::PlaceOrder,
|
||||
PublicError::DatabaseConnection
|
||||
)?;
|
||||
|
||||
Ok(HttpResponse::SeeOther()
|
||||
.append_header(("Location", redirect_uri.as_str()))
|
||||
.body(format!(
|
||||
"<a href=\"{redirect_uri}\">Go to {redirect_uri}</a>"
|
||||
)))
|
||||
Ok(Json(api::PlaceOrderResult {
|
||||
redirect_uri,
|
||||
order_id: order.id,
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn configure(config: &mut ServiceConfig) {
|
||||
|
2
migrations/20220528115140_add_phone_to_address.sql
Normal file
2
migrations/20220528115140_add_phone_to_address.sql
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE account_addresses
|
||||
ADD COLUMN phone text NOT NULL DEFAULT '';
|
@ -427,6 +427,13 @@ pub struct CreateAccountInput {
|
||||
pub password: Password,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum OrderAddressInput {
|
||||
Address(CreateOrderAddress),
|
||||
AccountAddress(AddressId),
|
||||
DefaultAccountAddress,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct CreateOrderInput {
|
||||
/// Required customer e-mail
|
||||
@ -444,7 +451,7 @@ pub struct CreateOrderInput {
|
||||
pub charge_client: bool,
|
||||
/// User currency
|
||||
pub currency: String,
|
||||
pub address: Option<CreateOrderAddress>,
|
||||
pub address: OrderAddressInput,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@ -512,6 +519,12 @@ pub struct UpdateOrderAddress {
|
||||
pub zip: Zip,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct PlaceOrderResult {
|
||||
pub redirect_uri: String,
|
||||
pub order_id: OrderId,
|
||||
}
|
||||
|
||||
pub mod admin {
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -6,6 +6,7 @@ use seed::fetch::{FetchError, Request};
|
||||
pub mod admin;
|
||||
pub mod public;
|
||||
|
||||
#[must_use]
|
||||
#[derive(Debug)]
|
||||
pub enum NetRes<S> {
|
||||
Success(S),
|
||||
|
@ -1,4 +1,5 @@
|
||||
use model::{AccessTokenString, RefreshTokenString};
|
||||
use model::api::OrderAddressInput;
|
||||
use model::{AccessTokenString, AddressId, RefreshTokenString};
|
||||
use seed::fetch::{Header, Method, Request};
|
||||
|
||||
use crate::api::perform;
|
||||
@ -118,16 +119,28 @@ pub async fn update_cart(
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn place_order(access_token: AccessTokenString) -> NetRes<String> {
|
||||
pub async fn place_account_order(
|
||||
access_token: AccessTokenString,
|
||||
email: String,
|
||||
phone: String,
|
||||
first_name: String,
|
||||
last_name: String,
|
||||
language: String,
|
||||
charge_client: bool,
|
||||
currency: String,
|
||||
address_id: Option<AddressId>,
|
||||
) -> NetRes<model::api::PlaceOrderResult> {
|
||||
let input = model::api::CreateOrderInput {
|
||||
email: "".to_string(),
|
||||
phone: "".to_string(),
|
||||
first_name: "".to_string(),
|
||||
last_name: "".to_string(),
|
||||
language: "".to_string(),
|
||||
charge_client: false,
|
||||
currency: "".to_string(),
|
||||
address: None,
|
||||
email,
|
||||
phone,
|
||||
first_name,
|
||||
last_name,
|
||||
language,
|
||||
charge_client,
|
||||
currency,
|
||||
address: address_id
|
||||
.map(OrderAddressInput::AccountAddress)
|
||||
.unwrap_or(OrderAddressInput::DefaultAccountAddress),
|
||||
};
|
||||
perform(
|
||||
Request::new("/api/v1/order")
|
||||
|
@ -52,6 +52,10 @@ impl I18n {
|
||||
.map(Into::into)
|
||||
.unwrap_or_else(|| key.clone())
|
||||
}
|
||||
|
||||
pub fn current_language(&self) -> &str {
|
||||
self.lang.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Scope<'store, 'lang> {
|
||||
|
@ -178,8 +178,7 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
pages::public::shopping_cart::update(msg, page, &mut orders.proxy(Into::into))
|
||||
}
|
||||
Msg::Public(pages::public::PublicMsg::Checkout(msg)) => {
|
||||
let page = fetch_page!(public model, Checkout);
|
||||
pages::public::checkout::update(msg, page, &mut orders.proxy(Into::into))
|
||||
pages::public::checkout::update(msg, model, &mut orders.proxy(Into::into))
|
||||
}
|
||||
// Admin
|
||||
Msg::Admin(pages::admin::Msg::Landing(msg)) => {
|
||||
|
@ -6,15 +6,24 @@ use crate::{shopping_cart, I18n, Page};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Model {
|
||||
/// URL object for path constructor
|
||||
pub url: Url,
|
||||
/// Access token
|
||||
pub token: Option<String>,
|
||||
/// Current SPA page
|
||||
pub page: Page,
|
||||
/// Logo url form favicon href
|
||||
pub logo: Option<String>,
|
||||
/// Shared data
|
||||
pub shared: crate::shared::Model,
|
||||
/// Translations
|
||||
pub i18n: I18n,
|
||||
/// Shopping cart information
|
||||
pub cart: shopping_cart::ShoppingCart,
|
||||
/// Application config
|
||||
pub config: Config,
|
||||
|
||||
/// Debug only modal
|
||||
#[cfg(debug_assertions)]
|
||||
pub debug_modal: bool,
|
||||
}
|
||||
|
@ -1,30 +1,37 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use model::AccessTokenString;
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
|
||||
use crate::model::Products;
|
||||
use crate::NetRes;
|
||||
use crate::{fetch_page, NetRes};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CheckoutMsg {
|
||||
ProductsFetched(NetRes<model::api::Products>),
|
||||
AddressNameChanged(String),
|
||||
AddressFirstNameChanged(String),
|
||||
AddressLastNameChanged(String),
|
||||
AddressEmailChanged(String),
|
||||
AddressStreetChanged(String),
|
||||
AddressCityChanged(String),
|
||||
AddressCountryChanged(String),
|
||||
AddressZipChanged(String),
|
||||
AddressPhoneChanged(String),
|
||||
PlaceOrder,
|
||||
OrderPlaced(NetRes<model::api::PlaceOrderResult>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AddressForm {
|
||||
pub name: model::Name,
|
||||
pub email: model::Email,
|
||||
pub first_name: model::Name,
|
||||
pub last_name: model::Name,
|
||||
pub street: model::Street,
|
||||
pub city: model::City,
|
||||
pub country: model::Country,
|
||||
pub zip: model::Zip,
|
||||
pub email: model::Email,
|
||||
pub phone: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -47,10 +54,11 @@ pub fn init(_url: Url, orders: &mut impl Orders<crate::Msg>) -> CheckoutPage {
|
||||
|
||||
pub fn page_changed(_url: Url, _model: &mut CheckoutPage) {}
|
||||
|
||||
pub fn update(msg: CheckoutMsg, model: &mut CheckoutPage, _orders: &mut impl Orders<crate::Msg>) {
|
||||
pub fn update(msg: CheckoutMsg, model: &mut crate::Model, orders: &mut impl Orders<crate::Msg>) {
|
||||
match msg {
|
||||
CheckoutMsg::ProductsFetched(NetRes::Success(products)) => {
|
||||
model.products.update(products.0);
|
||||
let page = fetch_page!(public model, Checkout);
|
||||
page.products.update(products.0);
|
||||
}
|
||||
CheckoutMsg::ProductsFetched(NetRes::Error(e)) => {
|
||||
seed::error!("fetch product error", e);
|
||||
@ -58,26 +66,72 @@ pub fn update(msg: CheckoutMsg, model: &mut CheckoutPage, _orders: &mut impl Ord
|
||||
CheckoutMsg::ProductsFetched(NetRes::Http(e)) => {
|
||||
seed::error!("fetch product http", e);
|
||||
}
|
||||
CheckoutMsg::AddressNameChanged(value) => {
|
||||
model.address.name = model::Name::new(value);
|
||||
CheckoutMsg::AddressFirstNameChanged(value) => {
|
||||
let page = fetch_page!(public model, Checkout);
|
||||
page.address.first_name = model::Name::new(value);
|
||||
}
|
||||
CheckoutMsg::AddressLastNameChanged(value) => {
|
||||
let page = fetch_page!(public model, Checkout);
|
||||
page.address.last_name = model::Name::new(value);
|
||||
}
|
||||
CheckoutMsg::AddressEmailChanged(value) => {
|
||||
if let Ok(value) = model::Email::from_str(&value) {
|
||||
model.address.email = value;
|
||||
let page = fetch_page!(public model, Checkout);
|
||||
page.address.email = value;
|
||||
}
|
||||
}
|
||||
CheckoutMsg::AddressPhoneChanged(phone) => {
|
||||
let page = fetch_page!(public model, Checkout);
|
||||
page.address.phone = phone;
|
||||
}
|
||||
CheckoutMsg::AddressStreetChanged(value) => {
|
||||
model.address.street = model::Street::new(value);
|
||||
let page = fetch_page!(public model, Checkout);
|
||||
page.address.street = model::Street::new(value);
|
||||
}
|
||||
CheckoutMsg::AddressCityChanged(value) => {
|
||||
model.address.city = model::City::new(value);
|
||||
let page = fetch_page!(public model, Checkout);
|
||||
page.address.city = model::City::new(value);
|
||||
}
|
||||
CheckoutMsg::AddressCountryChanged(value) => {
|
||||
model.address.country = model::Country::new(value);
|
||||
let page = fetch_page!(public model, Checkout);
|
||||
page.address.country = model::Country::new(value);
|
||||
}
|
||||
CheckoutMsg::AddressZipChanged(value) => {
|
||||
model.address.zip = model::Zip::new(value);
|
||||
let page = fetch_page!(public model, Checkout);
|
||||
page.address.zip = model::Zip::new(value);
|
||||
}
|
||||
CheckoutMsg::PlaceOrder => {
|
||||
if let Some(access_token) = model.token.as_ref().cloned() {
|
||||
let page = fetch_page!(public model, Checkout);
|
||||
let email: String = String::from(page.address.email.as_str());
|
||||
let phone = page.address.phone.clone();
|
||||
|
||||
let first_name: String = String::from(page.address.first_name.as_str());
|
||||
let last_name: String = String::from(page.address.last_name.as_str());
|
||||
let language: String = model.i18n.current_language().to_string();
|
||||
let charge_client = false;
|
||||
let currency = model.config.currency.name.to_string();
|
||||
let address_id = None;
|
||||
|
||||
orders.perform_cmd(async move {
|
||||
crate::api::public::place_account_order(
|
||||
AccessTokenString::new(access_token),
|
||||
email,
|
||||
phone,
|
||||
first_name,
|
||||
last_name,
|
||||
language,
|
||||
charge_client,
|
||||
currency,
|
||||
address_id,
|
||||
)
|
||||
.await
|
||||
});
|
||||
}
|
||||
}
|
||||
CheckoutMsg::OrderPlaced(NetRes::Success(_o)) => {}
|
||||
CheckoutMsg::OrderPlaced(NetRes::Error(_o)) => {}
|
||||
CheckoutMsg::OrderPlaced(NetRes::Http(_o)) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,7 +277,6 @@ mod right_side {
|
||||
use seed::*;
|
||||
|
||||
use crate::pages::public::checkout::{CheckoutMsg, CheckoutPage};
|
||||
use crate::pages::public::sign_up::RegisterMsg;
|
||||
use crate::shopping_cart::CartMsg;
|
||||
use crate::Msg;
|
||||
|
||||
@ -262,10 +315,12 @@ mod right_side {
|
||||
div![C!["text-gray-600 font-semibold text-sm mb-2 ml-1"], model.i18n.t("Contact")],
|
||||
],
|
||||
div![
|
||||
C!["mb-3"],
|
||||
div![
|
||||
address_input(model, "client-name", "text", "Name", CheckoutMsg::AddressNameChanged),
|
||||
],
|
||||
C!["mb-3 inline-block w-1/2 pr-1"],
|
||||
address_input(model, "client-first-name", "text", "First name", CheckoutMsg::AddressFirstNameChanged),
|
||||
],
|
||||
div![
|
||||
C!["mb-3 inline-block -mx-1 pl-1 w-1/2"],
|
||||
address_input(model, "client-last-name", "text", "Last name", CheckoutMsg::AddressLastNameChanged),
|
||||
],
|
||||
div![
|
||||
C!["mb-3"],
|
||||
@ -273,6 +328,12 @@ mod right_side {
|
||||
address_input(model, "client-email", "email", "E-Mail", CheckoutMsg::AddressEmailChanged),
|
||||
],
|
||||
],
|
||||
div![
|
||||
C!["mb-3"],
|
||||
div![
|
||||
address_input(model, "client-phone", "phone", "Phone number", CheckoutMsg::AddressPhoneChanged),
|
||||
],
|
||||
],
|
||||
div![
|
||||
C!["mb-3"],
|
||||
div![C!["text-gray-600 font-semibold text-sm mb-2 ml-1"], model.i18n.t("Address")],
|
||||
|
Loading…
Reference in New Issue
Block a user