Skip to content

Commit

Permalink
feat: impl stripe frontend logic
Browse files Browse the repository at this point in the history
  • Loading branch information
wiseaidev committed Nov 22, 2024
1 parent 2dcb5f9 commit 0e033c9
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 16 deletions.
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ JWT_SECRET=
GEMINI_API_KEY=
UNSPLASH_API_KEY=
STRIPE_SECRET_KEY=
WEBSITE_URL=https://opensass.org
WEBSITE_URL=https://opensass.org
STRIPE_PRICE_ONE=price_1...
STRIPE_PRICE_TWO=price_1...
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,35 @@ serde = { version = "1.0.215", features = ["derive"] }

dioxus = { version = "0.5", features = ["fullstack", "router", "html"] }
mongodb = { version = "3.1.0", optional = true }
dotenv = { version = "0.15.0" }
chrono = { version = "0.4.38", features = ["serde"] }
bson = { version = "2.13.0", features = ["chrono-0_4"] }
futures-util = { version = "0.3.31" }
jsonwebtoken = { version = "9.3.0", optional = true }
argon2 = { version = "0.5.3", optional = true }
tokio = { version = "1.41.1", optional = true }
uuid = { version = "1.11.0", optional = true }
rand = { version = "0.8.5", optional = true }
axum-extra = { version = "0.9.4", features = ["cookie"], optional = true }
rand_core = { version = "0.6.4", features = ["std"], optional = true }
anyhow = "1.0.93"
time = "0.3.36"
regex = "1.11.1"
getrandom = { version = "0.2.15", features = ["js"] }
gems = { version = "0.0.9", optional = true }
http-api-isahc-client = { version = "0.2.2", optional = true }
axum = { version = "0.7.7", optional = true }
unsplash-api = { version = "0.1.0", optional = true }
gloo-storage = "0.3.0"
tower-http = { version = "0.6.1", features = ["cors"], optional = true }
dioxus-free-icons = { version = "0.8.6", features = ["font-awesome-regular", "font-awesome-brands", "font-awesome-solid"] }
web-sys = { version = "0.3.72", features = ["Selection", "Window"] }
dioxus-web = { version = "0.5.6", features = ["hydrate"], optional = true }
async-stripe = { version = "0.39.1", default-feature = false, features = ["runtime-tokio-hyper-rustls", "billing"], optional = true }
futures-util = { version = "0.3.31" }
dotenv = { version = "0.15.0" }
serde_json = "1.0.133"
anyhow = "1.0.93"
time = "0.3.36"
regex = "1.11.1"
gloo-storage = "0.3.0"

# Debug
dioxus-logger = "0.5.1"
serde_json = "1.0.133"

[features]
default = []
Expand Down
65 changes: 63 additions & 2 deletions src/components/pricing.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
use crate::components::common::header::Header;
use crate::components::toast::manager::ToastManager;
use crate::components::toast::manager::ToastType;
use crate::server::subscription::controller::start_stripe_payment;
use crate::server::subscription::request::StripePaymentRequest;
use crate::theme::Theme;
use chrono::Duration;
use dioxus::prelude::*;
use dioxus_logger::tracing;
use gloo_storage::SessionStorage;
use gloo_storage::Storage;
use std::env;

#[derive(Props, Clone, PartialEq)]
struct PricingOption {
Expand All @@ -9,11 +18,15 @@ struct PricingOption {
description: &'static str,
features: Vec<&'static str>,
highlight: bool,
plan_id: Option<&'static str>,
}

#[component]
pub fn Pricing() -> Element {
let dark_mode = use_context::<Signal<Theme>>();
let navigator = use_navigator();
let mut toasts_manager = use_context::<Signal<ToastManager>>();

let pricing_options = vec![
PricingOption {
title: "Free",
Expand All @@ -25,6 +38,7 @@ pub fn Pricing() -> Element {
"Google Gemini Pro (limited features)",
],
highlight: false,
plan_id: None,
},
PricingOption {
title: "Monthly",
Expand All @@ -37,6 +51,7 @@ pub fn Pricing() -> Element {
"Priority customer support",
],
highlight: true,
plan_id: Some(env::var("STRIPE_PRICE_ONE").expect("STRIPE_PRICE_ONE must be set.")),
},
PricingOption {
title: "Yearly",
Expand All @@ -51,8 +66,49 @@ pub fn Pricing() -> Element {
"Priority support",
],
highlight: false,
plan_id: Some(env::var("STRIPE_PRICE_TWO").expect("STRIPE_PRICE_TWO must be set.")),
},
];
let handle_plan_selection = move |plan: (Option<&'static str>, &'static str)| {
if let Some(plan_id) = plan.0 {
spawn({
let plan_title = plan.1.to_string();
async move {
match start_stripe_payment(StripePaymentRequest {
plan_id: plan_id.to_string(),
})
.await
{
Ok(response) => {
SessionStorage::set("stripe", response.data.clone())
.expect("Session storage failed");
SessionStorage::set("method", "stripe")
.expect("Session storage failed");
SessionStorage::set("plan", &plan_title)
.expect("Session storage failed");
toasts_manager.set(
toasts_manager()
.add_toast(
"Info".into(),
"Stripe payment initiation success!".into(),
ToastType::Info,
Some(Duration::seconds(5)),
)
.clone(),
);
navigator.push(response.data);
}
Err(err) => {
tracing::error!("Stripe payment initiation failed: {:?}", err);
}
}
}
});
} else {
navigator.push("/login");
tracing::info!("Free plan selected.");
}
};

rsx! {
section {
Expand Down Expand Up @@ -95,8 +151,13 @@ pub fn Pricing() -> Element {
}
},

button { class: format!("mt-6 w-full py-2 rounded-md font-semibold {}",
if option.highlight { "bg-blue-500 ttext-gray-700 hover:bg-blue-600" } else { "bg-gray-300 text-gray-700 hover:bg-gray-400" }),
button {
class: format!("mt-6 w-full py-2 rounded-md font-semibold {}",
if option.highlight { "bg-blue-500 text-white hover:bg-blue-600" } else { "bg-gray-300 text-gray-700 hover:bg-gray-400" }),
onclick: move |e: Event<MouseData>| {
e.stop_propagation();
handle_plan_selection((option.plan_id, option.title));
},
"Select Plan"
}
}
Expand Down
11 changes: 6 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@ use aibook::router::Route;
use aibook::theme::ThemeProvider;
use dioxus::prelude::*;
use dioxus_logger::tracing;
use dotenv::dotenv;

fn main() {
dotenv().ok();
dioxus_logger::init(tracing::Level::INFO).expect("failed to init logger");
tracing::info!("starting app");

#[cfg(feature = "web")]
{
dioxus_logger::init(tracing::Level::INFO).expect("failed to init logger");
tracing::info!("starting app");
let config = dioxus_web::Config::new().hydrate(true);

LaunchBuilder::new().with_cfg(config).launch(App);
Expand All @@ -25,9 +22,13 @@ fn main() {
use axum::http::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE};
use axum::http::Method;
use axum::{Extension, Router};
use dotenv::dotenv;
use std::sync::Arc;
use tower_http::cors::{Any, CorsLayer};

dotenv().ok();
dioxus_logger::init(tracing::Level::INFO).expect("failed to init logger");

#[derive(Clone)]
#[allow(dead_code)]
pub struct AppState {
Expand Down
1 change: 1 addition & 0 deletions src/pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ pub(crate) mod dashboard;
pub(crate) mod home;
pub(crate) mod login;
pub(crate) mod signup;
pub(crate) mod success;
52 changes: 52 additions & 0 deletions src/pages/success.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use dioxus::prelude::*;

#[component]
pub fn SuccessPage() -> Element {
rsx! {
section {
class: "min-h-screen flex flex-col justify-center items-center bg-gradient-to-br from-blue-500 via-indigo-600 to-purple-700 text-white",

div {
class: "text-center space-y-8 justify-center ",

div {
class: "flex justify-center items-center mx-auto w-24 h-24 bg-white rounded-full text-green-500 shadow-lg animate-bounce",
svg {
xmlns: "http://www.w3.org/2000/svg",
class: "h-12 w-12",
fill: "none",
view_box: "0 0 24 24",
stroke: "currentColor",
stroke_width: "2",
path {
stroke_linecap: "round",
stroke_linejoin: "round",
d: "M5 13l4 4L19 7",
}
}
},

h1 {
class: "text-4xl font-bold tracking-tight",
"Payment Successful!"
},

p {
class: "text-lg font-light text-gray-200 max-w-md mx-auto",
"Thank you for your payment. Your subscription is now active, and you can enjoy all the premium features we offer. A confirmation email has been sent to your registered email address."
},

a {
href: "/dashboard",
class: "inline-block px-8 py-3 bg-green-500 text-white font-semibold text-lg rounded-md shadow-md hover:bg-green-600 transition duration-300 ease-in-out",
"Go to Dashboard"
}
},

footer {
class: "mt-10 text-gray-300 text-sm",
"© 2024 AIBook. All rights reserved."
}
}
}
}
3 changes: 3 additions & 0 deletions src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::pages::dashboard::Dashboard;
use crate::pages::home::Home;
use crate::pages::login::Login;
use crate::pages::signup::Register;
use crate::pages::success::SuccessPage;
use dioxus::prelude::*;

#[derive(Clone, Routable, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
Expand All @@ -28,4 +29,6 @@ pub enum Route {
EditBook { id: String },
#[route("/dashboard")]
Dashboard {},
#[route("/success")]
SuccessPage {},
}
2 changes: 1 addition & 1 deletion src/server/subscription/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ pub async fn update_subscription(
}

#[server]
pub async fn stripe_payment(
pub async fn start_stripe_payment(
req: StripePaymentRequest,
) -> Result<SuccessResponse<String>, ServerFnError> {
let stripe_client = get_stripe().await.lock().await;
Expand Down

0 comments on commit 0e033c9

Please sign in to comment.