Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add social login integration #56

Merged
merged 1 commit into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ use crate::{
terms::TermsOfService,
upload::UploadPostPage,
},
state::canisters::{do_canister_auth, Canisters},
state::{
auth::AuthState,
canisters::{do_canister_auth, Canisters},
},
utils::MockPartialEq,
};
use leptos::*;
use leptos_meta::*;
Expand All @@ -27,8 +31,12 @@ pub fn App() -> impl IntoView {
provide_meta_context();
provide_context(Canisters::default());
provide_context(PostViewCtx::default());
let auth = create_rw_signal(None);
provide_context(Resource::local(auth, do_canister_auth));
let auth_state = AuthState::default();
provide_context(auth_state.clone());
provide_context(Resource::local(
move || MockPartialEq(auth_state.identity.get()),
|auth| do_canister_auth(auth.0),
));

view! {
<Stylesheet id="leptos" href="/pkg/hot-or-not-leptos-ssr.css"/>
Expand Down Expand Up @@ -59,7 +67,7 @@ pub fn App() -> impl IntoView {
<Route path="/terms-of-service" view=TermsOfService/>
<Route path="/privacy-policy" view=PrivacyPolicy/>
</Routes>
<AuthProvider auth/>
<AuthProvider/>
</main>
<nav>
<NavBar/>
Expand Down
12 changes: 9 additions & 3 deletions src/component/auth_provider.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
consts::AUTH_URL,
state::auth::{DelegationIdentity, SessionResponse},
state::auth::{auth_state, DelegationIdentity, SessionResponse},
};
use leptos::*;
use leptos_use::{use_event_listener, use_window};
Expand All @@ -20,11 +20,17 @@ pub fn AuthFrame(auth: RwSignal<Option<DelegationIdentity>>) -> impl IntoView {
let identity = res.delegation_identity;
auth.set(Some(identity))
});
view! { <iframe src=AUTH_URL.join("/anonymous_identity").unwrap().to_string()></iframe> }
view! {
<iframe
class="h-0 w-0 hidden"
src=AUTH_URL.join("/anonymous_identity").unwrap().to_string()
></iframe>
}
}

#[component]
pub fn AuthProvider(auth: RwSignal<Option<DelegationIdentity>>) -> impl IntoView {
pub fn AuthProvider() -> impl IntoView {
let auth = auth_state().identity;
view! {
<Show when=move || auth.with(|a| a.is_none())>
<AuthFrame auth/>
Expand Down
70 changes: 70 additions & 0 deletions src/component/connect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use leptos::*;
use leptos_use::{
storage::{use_local_storage, StringCodec},
use_event_listener, use_interval_fn, use_window,
};
use reqwest::Url;

use crate::{
consts::{self, ACCOUNT_CONNECTED_STORE},
state::auth::{auth_state, SessionResponse},
};

#[component]
pub fn ConnectLogin() -> impl IntoView {
let (_, write_account_connected, _) =
use_local_storage::<bool, StringCodec>(ACCOUNT_CONNECTED_STORE);
let logging_in = create_rw_signal(false);
let auth = auth_state().identity;
create_effect(move |_| {
if auth.with(|a| a.is_none()) {
return;
}
_ = use_event_listener(use_window(), ev::message, move |msg| {
if Url::parse(&msg.origin())
.map(|u| u.origin() != consts::AUTH_URL.origin())
.unwrap_or_default()
{
return;
}
let data = msg.data().as_string().unwrap();
let res: SessionResponse = serde_json::from_str(&data).unwrap();
let identity = res.delegation_identity;
auth.set(Some(identity));
logging_in.set(false);
write_account_connected.set(true);
});
});

view! {
<button
on:click=move |ev| {
ev.prevent_default();
let window = use_window();
let window = window.as_ref().unwrap();
let target = window
.open_with_url_and_target(consts::AUTH_URL.as_str(), "_blank")
.transpose()
.and_then(|w| w.ok())
.unwrap();
let target_c = target.clone();
_ = use_interval_fn(
move || {
if target_c.closed().unwrap_or_default() {
logging_in.try_set(false);
}
},
500,
);
on_cleanup(move || _ = target.close());
logging_in.set(true);
}

class="font-bold rounded-full bg-orange-600 py-3 w-full text-center text-xl text-white"
disabled=move || logging_in() || auth.with(|a| a.is_none())
>
{move || if logging_in() { "Connecting..." } else { "Social Connect" }}

</button>
}
}
1 change: 1 addition & 0 deletions src/component/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod airdrop_logo;
pub mod auth_provider;
pub mod bullet_loader;
pub mod connect;
pub mod ic_symbol;
pub mod modal;
pub mod nav;
Expand Down
1 change: 1 addition & 0 deletions src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub static CF_BASE_URL: Lazy<Url> =
Lazy::new(|| Url::parse("https://api.cloudflare.com/client/v4/").unwrap());
pub static AUTH_URL: Lazy<Url> =
Lazy::new(|| Url::parse("https://hot-or-not-auth.fly.dev/").unwrap());
pub const ACCOUNT_CONNECTED_STORE: &str = "account-connected";

pub mod social {
pub const TELEGRAM: &str = "https://t.me/+c-LTX0Cp-ENmMzI1";
Expand Down
77 changes: 75 additions & 2 deletions src/page/menu.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
use crate::component::social::*;
use crate::component::{connect::ConnectLogin, social::*};
use crate::consts::social;
use crate::state::auth::account_connected_reader;
use crate::state::canisters::authenticated_canisters;
use crate::try_or_redirect_opt;
use crate::utils::profile::ProfileDetails;
use crate::utils::MockPartialEq;
use leptos::*;
use leptos_icons::*;

Expand Down Expand Up @@ -57,13 +62,81 @@ fn MenuFooter() -> impl IntoView {
}
}

#[component]
fn ProfileLoading() -> impl IntoView {
view! {
<div class="w-48 md:w-36 lg:w-24 aspect-square overflow-clip rounded-full bg-white/20 animate-pulse"></div>
<div class="flex flex-col gap-2 animate-pulse">
<div class="w-64 h-4 bg-white/20 rounded-full"></div>
<div class="w-48 h-3 bg-white/20 rounded-full"></div>
</div>
}
}

#[component]
fn ProfileLoaded(user_details: ProfileDetails) -> impl IntoView {
view! {
<div class="w-48 md:w-36 lg:w-24 aspect-square overflow-clip rounded-full">
<img class="h-full w-full object-cover" src=user_details.profile_pic_or_random()/>
</div>
<div class="flex flex-col">
<span class="text-white text-ellipsis line-clamp-1 text-xl">
{user_details.display_name_or_fallback()}
</span>
<a
class="text-orange-600 text-md"
href=format!("/profile/{}", user_details.username_or_principal())
>
View Profile
</a>
</div>
}
}

#[component]
fn ProfileInfo() -> impl IntoView {
let canisters = authenticated_canisters();
let profile_details = create_resource(
move || MockPartialEq(canisters.get().and_then(|c| c.transpose())),
move |canisters| async move {
let canisters = try_or_redirect_opt!(canisters.0?);
let user = canisters.authenticated_user();
let user_details = user.get_profile_details().await.ok()?;
Some(ProfileDetails::from(user_details))
},
);

view! {
<Suspense fallback=ProfileLoading>
{move || {
profile_details()
.flatten()
.map(|user_details| view! { <ProfileLoaded user_details/> })
.unwrap_or_else(|| view! { <ProfileLoading/> })
}}

</Suspense>
}
}

#[component]
pub fn Menu() -> impl IntoView {
let (is_connected, _) = account_connected_reader();

view! {
<div class="min-h-screen w-full flex flex-col text-white py-2 bg-black items-center divide-y divide-white/10">
<div class="flex flex-col items-center w-full gap-20 pb-16">
<span class="font-bold text-2xl">Menu</span>
<button class="font-bold rounded-full bg-orange-600 py-3 w-2/12">Login</button>
<div class="flex flex-col items-center w-full gap-4">
<div class="flex flex-row justify-center gap-4 items-center px-4 w-full">
<ProfileInfo/>
</div>
<Show when=move || !is_connected()>
<div class="w-2/12">
<ConnectLogin/>
</div>
</Show>
</div>
</div>
<div class="flex flex-col py-12 px-8 gap-8 w-full text-lg">
<MenuItem href="/airdrop" text="Airdrop" icon=icondata::TbMoneybag/>
Expand Down
Loading
Loading