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

Airdrop layout #607

Merged
merged 30 commits into from
Jan 22, 2025
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
2 changes: 1 addition & 1 deletion ssr/contracts
1 change: 0 additions & 1 deletion ssr/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ pub fn App() -> impl IntoView {
<Route path="/token/transfer/:token_root" view=TokenTransfer/>
<Route path="/board" view=ICPumpLanding/>
<Route path="/icpump-ai" view=ICPumpAi/>
// <Route path="/test" view=TestIndex/>
</Route>
</Routes>

Expand Down
26 changes: 24 additions & 2 deletions ssr/src/component/spinner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,30 @@ icon_gen!(
"###
);
#[component]
pub fn SpinnerCircle() -> impl IntoView {
pub fn SpinnerCircle(#[prop(optional, default = "")] class: &'static str) -> impl IntoView {
view! {
<Icon icon=SpinnerCircleIcon class="animate-spin w-full h-full"/>
<Icon icon=SpinnerCircleIcon class=format!("animate-spin w-full h-full {}", class)/>
}
}
#[component]
pub fn SpinnerCircleStyled(#[prop(optional, default = "")] class: &'static str) -> impl IntoView {
view! {
<Icon icon=SpinnerCircleStyledIcon class=format!("animate-spin w-full h-full {}", class)/>
}
}
icon_gen!(
SpinnerCircleStyledIcon,
view_box = "0 0 49 48",
r###"
<path opacity="0.38" d="M48.5 24C48.5 37.2548 37.7548 48 24.5 48C11.2452 48 0.5 37.2548 0.5 24C0.5 10.7452 11.2452 0 24.5 0C37.7548 0 48.5 10.7452 48.5 24ZM7.98361 24C7.98361 33.1218 15.3782 40.5164 24.5 40.5164C33.6218 40.5164 41.0164 33.1218 41.0164 24C41.0164 14.8782 33.6218 7.48361 24.5 7.48361C15.3782 7.48361 7.98361 14.8782 7.98361 24Z" fill="#EC55A7"/>
<path d="M44.7582 24C46.8247 24 48.5298 22.3148 48.2089 20.2733C47.9151 18.4041 47.4006 16.5718 46.6731 14.8156C45.467 11.9038 43.6992 9.25804 41.4706 7.02944C39.242 4.80083 36.5962 3.033 33.6844 1.82689C31.9282 1.09944 30.0959 0.584918 28.2267 0.291103C26.1852 -0.0297888 24.5 1.67526 24.5 3.7418C24.5 5.80834 26.1962 7.44147 28.2099 7.90566C29.0997 8.11075 29.973 8.38977 30.8205 8.74084C32.8244 9.57087 34.6452 10.7875 36.1789 12.3211C37.7125 13.8548 38.9291 15.6756 39.7592 17.6794C40.1102 18.527 40.3892 19.4003 40.5943 20.2901C41.0585 22.3038 42.6917 24 44.7582 24Z" fill="url(#paint0_linear_134_12800)"/>
<defs>
<linearGradient id="paint0_linear_134_12800" x1="45.8334" y1="6.33333" x2="17.1668" y2="42.6668" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF78C1"/>
<stop offset="0.509385" stop-color="#E2017B"/>
<stop offset="1" stop-color="#5F0938"/>
</linearGradient>
</defs>

"###
);
22 changes: 10 additions & 12 deletions ssr/src/page/icpump/ai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ pub fn ICPumpAiTokenListing(tokens: Vec<TokenListItem>) -> impl IntoView {

let tokens_view = create_memo(move |_| {
if tokens_len != 0 {
view! {
Some(view! {
{tokens_view_list}
<div class="w-full flex items-center justify-center">
<button
Expand All @@ -300,9 +300,9 @@ pub fn ICPumpAiTokenListing(tokens: Vec<TokenListItem>) -> impl IntoView {
</button>
</div>
}
.into_view()
.into_view())
} else {
view! {<></>}.into_view()
None
}
});

Expand Down Expand Up @@ -490,26 +490,24 @@ pub fn ICPumpAi() -> impl IntoView {
move || {
match page_no.get() {
1 => {
view! {
Some(view! {
<ICPumpAiPage1 query={query} page_no={page_no} search_action={search_action}/>
}.into_view()
}.into_view())
}
2 => {
view! {
Some(view! {
<ICPumpAiPage2 query={query} page_no={page_no}
search_action={search_action} reset_state={reset_state}/>
}.into_view()
}.into_view())
}
3 => {
view! {
Some(view! {
<ICPumpAiPage3 query={query} chat={chat} page_no={page_no}
search_action={search_action} reset_state={reset_state}/>
}.into_view()
}.into_view())
}
_ => {
view! {
<></>
}.into_view()
None
}
}
}
Expand Down
166 changes: 119 additions & 47 deletions ssr/src/page/icpump/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::state::canisters::authenticated_canisters;
use crate::state::canisters::unauth_canisters;
use std::collections::VecDeque;

use candid::Nat;
use candid::Principal;
use codee::string::FromToStringCodec;
use futures::stream::FuturesOrdered;
Expand Down Expand Up @@ -34,12 +35,17 @@ use crate::utils::token::firestore::listen_to_documents;
use crate::utils::token::icpump::get_paginated_token_list;
use crate::utils::token::icpump::TokenListItem;

use crate::component::overlay::ShadowOverlay;
use crate::page::wallet::airdrop::AirdropPopup;
use yral_canisters_common::utils::token::TokenOwner;

pub mod ai;
#[derive(Serialize, Deserialize, Clone, Debug)]
struct ProcessedTokenListResponse {
token_details: TokenListItem,
root: Principal,
is_airdrop_claimed: bool,
token_owner: Option<TokenOwner>,
}

async fn process_token_list_item(
Expand All @@ -56,7 +62,7 @@ async fn process_token_list_item(
.link
.trim_end_matches('/')
.split('/')
.last()
.next_back()
.ok_or(ServerFnError::new("Not root given"))
.unwrap_or_default(),
)
Expand All @@ -66,7 +72,7 @@ async fn process_token_list_item(
.get_token_owner(root_principal)
.await
.unwrap_or_default();
let is_airdrop_claimed = if let Some(token_owner) = token_owner_canister_id {
let is_airdrop_claimed = if let Some(token_owner) = &token_owner_canister_id {
cans.get_airdrop_status(token_owner.canister_id, root_principal, key_principal)
.await
.unwrap_or(true)
Expand All @@ -79,6 +85,7 @@ async fn process_token_list_item(
token_details: token,
root: root_principal,
is_airdrop_claimed,
token_owner: token_owner_canister_id,
}
});
}
Expand Down Expand Up @@ -171,6 +178,7 @@ pub fn ICPumpListingFeed() -> impl IntoView {
details=t.token_details
is_airdrop_claimed=t.is_airdrop_claimed
root=t.root
token_owner=t.token_owner
/>
}
}
Expand All @@ -185,6 +193,7 @@ pub fn ICPumpListingFeed() -> impl IntoView {
details=t.token_details.clone()
is_airdrop_claimed=t.is_airdrop_claimed
root=t.root
token_owner=t.token_owner.clone()
/>
}
})
Expand Down Expand Up @@ -237,12 +246,59 @@ pub fn ICPumpLanding() -> impl IntoView {
}
}

#[component]
pub fn TokenCardFallback() -> impl IntoView {
view! {
<div class="flex flex-col gap-2 pt-3 pb-4 px-3 md:px-4 w-full text-xs rounded-lg bg-neutral-900/90 font-kumbh">
<div class="flex gap-3 items-stretch">
<div class="w-[7rem] h-[7rem] rounded-[4px] shrink-0 bg-white/15 animate-pulse"></div>
<div class="flex flex-col justify-between overflow-hidden w-full gap-2">
<div class="flex flex-col gap-2">
<div class="flex gap-4 justify-between items-center w-full">
<div class="h-7 w-32 bg-white/15 animate-pulse rounded"></div>
<div class="h-7 w-16 bg-white/15 animate-pulse rounded"></div>
</div>
<div class="h-12 w-full bg-white/15 animate-pulse rounded"></div>
</div>
<div class="flex gap-2 justify-between items-center">
<div class="h-5 w-48 bg-white/15 animate-pulse rounded"></div>
<div class="h-5 w-24 bg-white/15 animate-pulse rounded"></div>
</div>
</div>
</div>
<div class="flex gap-4 justify-between items-center p-2">
<div class="flex flex-col items-center gap-1">
<div class="w-[1.875rem] h-[1.875rem] bg-white/15 animate-pulse rounded"></div>
<div class="w-10 h-3 bg-white/15 animate-pulse rounded"></div>
</div>
<div class="flex flex-col items-center gap-1">
<div class="w-[1.875rem] h-[1.875rem] bg-white/15 animate-pulse rounded"></div>
<div class="w-14 h-3 bg-white/15 animate-pulse rounded"></div>
</div>
<div class="flex flex-col items-center gap-1">
<div class="w-[1.875rem] h-[1.875rem] bg-white/15 animate-pulse rounded"></div>
<div class="w-12 h-3 bg-white/15 animate-pulse rounded"></div>
</div>
<div class="flex flex-col items-center gap-1">
<div class="w-[1.875rem] h-[1.875rem] bg-white/15 animate-pulse rounded"></div>
<div class="w-10 h-3 bg-white/15 animate-pulse rounded"></div>
</div>
<div class="flex flex-col items-center gap-1">
<div class="w-[1.875rem] h-[1.875rem] bg-white/15 animate-pulse rounded"></div>
<div class="w-12 h-3 bg-white/15 animate-pulse rounded"></div>
</div>
</div>
</div>
}
}

#[component]
pub fn TokenCard(
details: TokenListItem,
#[prop(optional, default = false)] is_new_token: bool,
root: Principal,
is_airdrop_claimed: bool,
token_owner: Option<TokenOwner>,
) -> impl IntoView {
let show_nsfw = create_rw_signal(false);

Expand All @@ -257,8 +313,46 @@ pub fn TokenCard(
)
};
let pop_up = create_rw_signal(false);
let airdrop_popup = create_rw_signal(false);
let base_url = get_host();

let claimed = create_rw_signal(is_airdrop_claimed);
let buffer_signal = create_rw_signal(false);
let cans_res = authenticated_canisters();
let token_owner_c = token_owner.clone();
let airdrop_action = create_action(move |&()| {
let cans_res = cans_res.clone();
let token_owner_cans_id = token_owner_c.clone().unwrap().canister_id;
airdrop_popup.set(true);
async move {
if claimed.get() && !buffer_signal.get() {
return Ok(());
}
buffer_signal.set(true);
let cans_wire = cans_res.wait_untracked().await?;
let cans = Canisters::from_wire(cans_wire, expect_context())?;
let token_owner = cans.individual_user(token_owner_cans_id).await;

token_owner
.request_airdrop(
root,
None,
Into::<Nat>::into(100u64) * 10u64.pow(8),
cans.user_canister(),
)
.await?;

let user = cans.individual_user(cans.user_canister()).await;
user.add_token(root).await?;

buffer_signal.set(false);
claimed.set(true);
Ok::<_, ServerFnError>(())
}
});

let airdrop_disabled =
Signal::derive(move || token_owner.is_some() && claimed.get() || token_owner.is_none());
view! {
<div
class:tada=is_new_token
Expand All @@ -283,17 +377,17 @@ pub fn TokenCard(
<img
alt=details.token_name.clone()
src=details.logo.clone()
class="w-full h-full object-cover"
class="w-full h-full"
/>
</div>
<div class="flex flex-col justify-between overflow-hidden w-full">
<div class="flex flex-col gap-2">
<div class="flex gap-4 justify-between items-center w-full text-lg">
<span class="font-medium shrink line-clamp-1">{details.name}</span>
<span class="font-medium shrink line-clamp-1">{details.name.clone()}</span>
<span class="font-bold shrink-0">{symbol}</span>
</div>
<span class="text-sm line-clamp-2 text-neutral-400">
{details.description}
{details.description.clone()}
</span>
</div>
<div class="flex gap-2 justify-between items-center text-sm font-medium group-hover:text-white text-neutral-600">
Expand All @@ -310,35 +404,9 @@ pub fn TokenCard(
<ActionButton label="Buy/Sell".to_string() href="#".to_string() disabled=true>
<Icon class="w-full h-full" icon=ArrowLeftRightIcon />
</ActionButton>
{if is_airdrop_claimed {
view! {
<ActionButtonLink
on:click=move |_| {
pop_up.set(true);
share_link
.set(
format!(
"/token/info/{}/{}?airdrop_amt=100",
root,
details.user_id,
),
)
}
label="Airdrop".to_string()
>
<Icon class="h-6 w-6" icon=AirdropIcon />
</ActionButtonLink>
}
} else {
view! {
<ActionButton
href=format!("/token/info/{}/{}?airdrop_amt=100", root, details.user_id)
label="Airdrop".to_string()
>
<Icon class="h-6 w-6" icon=AirdropIcon />
</ActionButton>
}
}}
<ActionButtonLink disabled=airdrop_disabled on:click=move |_|{airdrop_action.dispatch(());} label="Airdrop".to_string()>
<Icon class="h-6 w-6" icon=AirdropIcon />
</ActionButtonLink>
<ActionButton label="Share".to_string() href="#".to_string()>
<Icon
class="w-full h-full"
Expand All @@ -360,6 +428,19 @@ pub fn TokenCard(
show_popup=pop_up
/>
</PopupOverlay>
<ShadowOverlay show=airdrop_popup >
<div class="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 max-w-[560px] max-h-[634px] min-w-[343px] min-h-[480px] backdrop-blur-lg rounded-lg">
<div class="rounded-lg z-[500]">
<AirdropPopup
name=details.name.clone()
logo=details.logo.clone()
buffer_signal
claimed
airdrop_popup
/>
</div>
</div>
</ShadowOverlay>
</div>
}
}
Expand Down Expand Up @@ -436,7 +517,7 @@ pub fn ActionButton(
href: String,
label: String,
children: Children,
#[prop(optional, default = false)] disabled: bool,
#[prop(optional, into)] disabled: MaybeSignal<bool>,
) -> impl IntoView {
view! {
<a
Expand All @@ -445,7 +526,7 @@ pub fn ActionButton(
class=move || {
format!(
"flex flex-col gap-1 justify-center items-center text-xs transition-colors {}",
if !disabled {
if !disabled.get() {
"group-hover:text-white text-neutral-300"
} else {
"group-hover:cursor-default text-neutral-600"
Expand All @@ -466,21 +547,12 @@ pub fn ActionButton(
pub fn ActionButtonLink(
label: String,
children: Children,
#[prop(optional, default = false)] disabled: bool,
#[prop(optional, into)] disabled: MaybeSignal<bool>,
) -> impl IntoView {
view! {
<button
disabled=disabled
class=move || {
format!(
"flex flex-col gap-1 justify-center items-center text-xs transition-colors {}",
if !disabled {
"group-hover:text-white text-neutral-300"
} else {
"group-hover:cursor-default text-neutral-600"
},
)
}
class="flex flex-col gap-1 justify-center items-center text-xs transition-colors disabled:group-hover:text-white disabled:text-neutral-300 enabled:group-hover:cursor-default enabled:text-neutral-600"
>
<div class="w-[1.875rem] h-[1.875rem] flex items-center justify-center">
{children()}
Expand Down
Loading
Loading