mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-26 05:11:09 +00:00
Use auth header
This commit is contained in:
parent
d9020a15d4
commit
66f89d3bfd
22 changed files with 61 additions and 77 deletions
|
@ -12,20 +12,12 @@ use crate::{
|
||||||
search_article,
|
search_article,
|
||||||
},
|
},
|
||||||
instance::{follow_instance, get_instance, resolve_instance},
|
instance::{follow_instance, get_instance, resolve_instance},
|
||||||
user::{
|
user::{get_user, login_user, logout_user, my_profile, register_user, validate},
|
||||||
get_user,
|
|
||||||
login_user,
|
|
||||||
logout_user,
|
|
||||||
my_profile,
|
|
||||||
register_user,
|
|
||||||
validate,
|
|
||||||
AUTH_COOKIE,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
database::IbisData,
|
database::IbisData,
|
||||||
error::MyResult,
|
error::MyResult,
|
||||||
},
|
},
|
||||||
common::LocalUserView,
|
common::{LocalUserView, AUTH_COOKIE},
|
||||||
};
|
};
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
@ -78,8 +70,15 @@ async fn auth(
|
||||||
mut request: Request<Body>,
|
mut request: Request<Body>,
|
||||||
next: Next,
|
next: Next,
|
||||||
) -> Result<Response, StatusCode> {
|
) -> Result<Response, StatusCode> {
|
||||||
if let Some(auth) = jar.get(AUTH_COOKIE) {
|
let auth = request
|
||||||
if let Ok(user) = validate(auth.value(), &data).await {
|
.headers()
|
||||||
|
.get(AUTH_COOKIE)
|
||||||
|
.map(|h| h.to_str().ok())
|
||||||
|
.flatten()
|
||||||
|
.or(jar.get(AUTH_COOKIE).map(|c| c.value()));
|
||||||
|
|
||||||
|
if let Some(auth) = auth {
|
||||||
|
if let Ok(user) = validate(auth, &data).await {
|
||||||
request.extensions_mut().insert(user);
|
request.extensions_mut().insert(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ use crate::{
|
||||||
LoginUserForm,
|
LoginUserForm,
|
||||||
Notification,
|
Notification,
|
||||||
RegisterUserForm,
|
RegisterUserForm,
|
||||||
|
AUTH_COOKIE,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
|
@ -34,8 +35,6 @@ use jsonwebtoken::{
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use time::{Duration, OffsetDateTime};
|
use time::{Duration, OffsetDateTime};
|
||||||
|
|
||||||
pub static AUTH_COOKIE: &str = "auth";
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct Claims {
|
struct Claims {
|
||||||
/// person.username
|
/// person.username
|
||||||
|
|
|
@ -13,6 +13,7 @@ use crate::{
|
||||||
DbInstance,
|
DbInstance,
|
||||||
DbPerson,
|
DbPerson,
|
||||||
EditVersion,
|
EditVersion,
|
||||||
|
AUTH_COOKIE,
|
||||||
MAIN_PAGE_NAME,
|
MAIN_PAGE_NAME,
|
||||||
},
|
},
|
||||||
frontend::app::App,
|
frontend::app::App,
|
||||||
|
@ -22,7 +23,7 @@ use activitypub_federation::{
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
http_signatures::generate_actor_keypair,
|
http_signatures::generate_actor_keypair,
|
||||||
};
|
};
|
||||||
use api::{api_routes, user::AUTH_COOKIE};
|
use api::api_routes;
|
||||||
use assets::file_and_error_handler;
|
use assets::file_and_error_handler;
|
||||||
use axum::{
|
use axum::{
|
||||||
body::Body,
|
body::Body,
|
||||||
|
|
|
@ -21,6 +21,8 @@ use {
|
||||||
|
|
||||||
pub const MAIN_PAGE_NAME: &str = "Main_Page";
|
pub const MAIN_PAGE_NAME: &str = "Main_Page";
|
||||||
|
|
||||||
|
pub static AUTH_COOKIE: &str = "auth";
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Auth(pub Option<String>);
|
pub struct Auth(pub Option<String>);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use std::sync::LazyLock;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{
|
common::{
|
||||||
newtypes::ArticleId,
|
newtypes::ArticleId,
|
||||||
|
@ -32,11 +30,10 @@ use crate::{
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use reqwest::{Client, RequestBuilder, StatusCode};
|
use reqwest::{Client, RequestBuilder, StatusCode};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::sync::LazyLock;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub static CLIENT: LazyLock<ApiClient> = LazyLock::new(|| {
|
pub static CLIENT: LazyLock<ApiClient> = LazyLock::new(|| ApiClient::new(Client::new(), None));
|
||||||
ApiClient::new(Client::new(), None)
|
|
||||||
});
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ApiClient {
|
pub struct ApiClient {
|
||||||
|
@ -276,13 +273,13 @@ where
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
{
|
{
|
||||||
use crate::common::Auth;
|
use crate::common::{Auth, AUTH_COOKIE};
|
||||||
use leptos::use_context;
|
use leptos::use_context;
|
||||||
use reqwest::header::HeaderName;
|
use reqwest::header::HeaderName;
|
||||||
|
|
||||||
let auth = use_context::<Auth>();
|
let auth = use_context::<Auth>();
|
||||||
if let Some(Auth(Some(auth))) = auth {
|
if let Some(Auth(Some(auth))) = auth {
|
||||||
req = req.header(HeaderName::from_static("auth"), auth);
|
req = req.header(HeaderName::from_static(AUTH_COOKIE), auth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let res = req.send().await?;
|
let res = req.send().await?;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
common::LocalUserView,
|
common::LocalUserView,
|
||||||
frontend::{
|
frontend::{
|
||||||
|
api::CLIENT,
|
||||||
components::nav::Nav,
|
components::nav::Nav,
|
||||||
dark_mode::DarkMode,
|
dark_mode::DarkMode,
|
||||||
pages::{
|
pages::{
|
||||||
|
@ -38,7 +39,6 @@ use leptos::{
|
||||||
};
|
};
|
||||||
use leptos_meta::{provide_meta_context, *};
|
use leptos_meta::{provide_meta_context, *};
|
||||||
use leptos_router::{Route, Router, Routes};
|
use leptos_router::{Route, Router, Routes};
|
||||||
use crate::frontend::api::CLIENT;
|
|
||||||
|
|
||||||
// https://book.leptos.dev/15_global_state.html
|
// https://book.leptos.dev/15_global_state.html
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -47,7 +47,6 @@ pub struct GlobalState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalState {
|
impl GlobalState {
|
||||||
|
|
||||||
pub fn update_my_profile() {
|
pub fn update_my_profile() {
|
||||||
create_local_resource(
|
create_local_resource(
|
||||||
move || (),
|
move || (),
|
||||||
|
@ -74,9 +73,7 @@ impl GlobalState {
|
||||||
#[component]
|
#[component]
|
||||||
pub fn App() -> impl IntoView {
|
pub fn App() -> impl IntoView {
|
||||||
provide_meta_context();
|
provide_meta_context();
|
||||||
let global_state = GlobalState {
|
let global_state = GlobalState { my_profile: None };
|
||||||
my_profile: None,
|
|
||||||
};
|
|
||||||
// Load user profile in case we are already logged in
|
// Load user profile in case we are already logged in
|
||||||
GlobalState::update_my_profile();
|
GlobalState::update_my_profile();
|
||||||
provide_context(create_rw_signal(global_state));
|
provide_context(create_rw_signal(global_state));
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{validation::can_edit_article, ArticleView, GetInstance},
|
common::{validation::can_edit_article, ArticleView, GetInstance},
|
||||||
frontend::{
|
frontend::{
|
||||||
|
api::CLIENT,
|
||||||
app::GlobalState,
|
app::GlobalState,
|
||||||
article_link,
|
article_link,
|
||||||
article_title,
|
article_title,
|
||||||
components::instance_follow_button::InstanceFollowButton,
|
components::instance_follow_button::InstanceFollowButton,
|
||||||
},
|
},
|
||||||
};use crate::frontend::api::CLIENT;
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::*;
|
use leptos_router::*;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
use crate::frontend::api::CLIENT;
|
||||||
use leptos::{component, *};
|
use leptos::{component, *};
|
||||||
use url::Url;use crate::frontend::api::CLIENT;
|
use url::Url;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ConnectView<T: Clone + 'static, R: 'static>(res: Resource<T, R>) -> impl IntoView {
|
pub fn ConnectView<T: Clone + 'static, R: 'static>(res: Resource<T, R>) -> impl IntoView {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{newtypes::InstanceId, DbInstance, FollowInstance},
|
common::{newtypes::InstanceId, DbInstance, FollowInstance},
|
||||||
frontend::app::GlobalState,
|
frontend::{api::CLIENT, app::GlobalState},
|
||||||
};use crate::frontend::api::CLIENT;
|
};
|
||||||
use leptos::{component, *};
|
use leptos::{component, *};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
|
@ -11,10 +11,7 @@ pub fn InstanceFollowButton(instance: DbInstance) -> impl IntoView {
|
||||||
let instance_id = *instance_id;
|
let instance_id = *instance_id;
|
||||||
async move {
|
async move {
|
||||||
let form = FollowInstance { id: instance_id };
|
let form = FollowInstance { id: instance_id };
|
||||||
CLIENT
|
CLIENT.follow_instance(form).await.unwrap();
|
||||||
.follow_instance(form)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
GlobalState::update_my_profile();
|
GlobalState::update_my_profile();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::frontend::{app::GlobalState, dark_mode::DarkMode};
|
use crate::frontend::{api::CLIENT, app::GlobalState, dark_mode::DarkMode};
|
||||||
use leptos::{component, use_context, view, IntoView, RwSignal, SignalWith, *};
|
use leptos::{component, use_context, view, IntoView, RwSignal, SignalWith, *};
|
||||||
use leptos_router::*;use crate::frontend::api::CLIENT;
|
use leptos_router::*;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Nav() -> impl IntoView {
|
pub fn Nav() -> impl IntoView {
|
||||||
|
@ -21,12 +21,7 @@ pub fn Nav() -> impl IntoView {
|
||||||
);
|
);
|
||||||
let notification_count = create_resource(
|
let notification_count = create_resource(
|
||||||
|| (),
|
|| (),
|
||||||
move |_| async move {
|
move |_| async move { CLIENT.notifications_count().await.unwrap_or_default() },
|
||||||
CLIENT
|
|
||||||
.notifications_count()
|
|
||||||
.await
|
|
||||||
.unwrap_or_default()
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let (search_query, set_search_query) = create_signal(String::new());
|
let (search_query, set_search_query) = create_signal(String::new());
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{newtypes::ArticleId, ForkArticleForm, ProtectArticleForm},
|
common::{newtypes::ArticleId, ForkArticleForm, ProtectArticleForm},
|
||||||
frontend::{
|
frontend::{
|
||||||
|
api::CLIENT,
|
||||||
app::GlobalState,
|
app::GlobalState,
|
||||||
article_link,
|
article_link,
|
||||||
components::article_nav::{ActiveTab, ArticleNav},
|
components::article_nav::{ActiveTab, ArticleNav},
|
||||||
pages::article_resource,
|
pages::article_resource,
|
||||||
DbArticle,
|
DbArticle,
|
||||||
},
|
},
|
||||||
};use crate::frontend::api::CLIENT;
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::Redirect;
|
use leptos_router::Redirect;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
common::CreateArticleForm,
|
common::CreateArticleForm,
|
||||||
frontend::components::editor::EditorView,
|
frontend::{api::CLIENT, components::editor::EditorView},
|
||||||
};use crate::frontend::api::CLIENT;
|
};
|
||||||
use html::Textarea;
|
use html::Textarea;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::Redirect;
|
use leptos_router::Redirect;
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{newtypes::ConflictId, ApiConflict, ArticleView, EditArticleForm, Notification},
|
common::{newtypes::ConflictId, ApiConflict, ArticleView, EditArticleForm, Notification},
|
||||||
frontend::{
|
frontend::{
|
||||||
|
api::CLIENT,
|
||||||
components::{
|
components::{
|
||||||
article_nav::{ActiveTab, ArticleNav},
|
article_nav::{ActiveTab, ArticleNav},
|
||||||
editor::EditorView,
|
editor::EditorView,
|
||||||
},
|
},
|
||||||
pages::article_resource,
|
pages::article_resource,
|
||||||
},
|
},
|
||||||
};use crate::frontend::api::CLIENT;
|
};
|
||||||
use html::Textarea;
|
use html::Textarea;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::use_params_map;
|
use leptos_router::use_params_map;
|
||||||
|
@ -89,9 +90,7 @@ pub fn EditArticle() -> impl IntoView {
|
||||||
resolve_conflict_id,
|
resolve_conflict_id,
|
||||||
};
|
};
|
||||||
set_wait_for_response.update(|w| *w = true);
|
set_wait_for_response.update(|w| *w = true);
|
||||||
let res = CLIENT
|
let res = CLIENT.edit_article_with_conflict(&form).await;
|
||||||
.edit_article_with_conflict(&form)
|
|
||||||
.await;
|
|
||||||
set_wait_for_response.update(|w| *w = false);
|
set_wait_for_response.update(|w| *w = false);
|
||||||
match res {
|
match res {
|
||||||
Ok(Some(conflict)) => {
|
Ok(Some(conflict)) => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
common::ListArticlesForm,
|
common::ListArticlesForm,
|
||||||
frontend::{article_link, article_title, components::connect::ConnectView},
|
frontend::{api::CLIENT, article_link, article_title, components::connect::ConnectView},
|
||||||
};use crate::frontend::api::CLIENT;
|
};
|
||||||
use html::Input;
|
use html::Input;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{utils::http_protocol_str, DbInstance, ListArticlesForm},
|
common::{utils::http_protocol_str, DbInstance, ListArticlesForm},
|
||||||
frontend::{
|
frontend::{
|
||||||
|
api::CLIENT,
|
||||||
article_link,
|
article_link,
|
||||||
article_title,
|
article_title,
|
||||||
components::instance_follow_button::InstanceFollowButton,
|
components::instance_follow_button::InstanceFollowButton,
|
||||||
},
|
},
|
||||||
};use crate::frontend::api::CLIENT;
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::use_params_map;
|
use leptos_router::use_params_map;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -16,10 +17,7 @@ pub fn InstanceDetails() -> impl IntoView {
|
||||||
let hostname = move || params.get().get("hostname").cloned().unwrap();
|
let hostname = move || params.get().get("hostname").cloned().unwrap();
|
||||||
let instance_profile = create_resource(hostname, move |hostname| async move {
|
let instance_profile = create_resource(hostname, move |hostname| async move {
|
||||||
let url = Url::parse(&format!("{}://{hostname}", http_protocol_str())).unwrap();
|
let url = Url::parse(&format!("{}://{hostname}", http_protocol_str())).unwrap();
|
||||||
CLIENT
|
CLIENT.resolve_instance(url).await.unwrap()
|
||||||
.resolve_instance(url)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::frontend::components::connect::ConnectView;
|
use crate::frontend::{api::CLIENT, components::connect::ConnectView};
|
||||||
use leptos::*;use crate::frontend::api::CLIENT;
|
use leptos::*;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ListInstances() -> impl IntoView {
|
pub fn ListInstances() -> impl IntoView {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
common::LoginUserForm,
|
common::LoginUserForm,
|
||||||
frontend::{app::GlobalState, components::credentials::*},
|
frontend::{api::CLIENT, app::GlobalState, components::credentials::*},
|
||||||
};
|
};
|
||||||
use leptos::*;use crate::frontend::api::CLIENT;
|
use leptos::*;
|
||||||
use leptos_router::Redirect;
|
use leptos_router::Redirect;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use crate::common::{ArticleView, GetArticleForm, MAIN_PAGE_NAME};
|
use crate::{
|
||||||
|
common::{ArticleView, GetArticleForm, MAIN_PAGE_NAME},
|
||||||
|
frontend::api::CLIENT,
|
||||||
|
};
|
||||||
use leptos::{create_resource, Resource, SignalGet};
|
use leptos::{create_resource, Resource, SignalGet};
|
||||||
use leptos_router::use_params_map;use crate::frontend::api::CLIENT;
|
use leptos_router::use_params_map;
|
||||||
|
|
||||||
pub(crate) mod article;
|
pub(crate) mod article;
|
||||||
pub(crate) mod diff;
|
pub(crate) mod diff;
|
||||||
|
|
|
@ -8,12 +8,7 @@ use leptos::*;
|
||||||
pub fn Notifications() -> impl IntoView {
|
pub fn Notifications() -> impl IntoView {
|
||||||
let notifications = create_local_resource(
|
let notifications = create_local_resource(
|
||||||
move || {},
|
move || {},
|
||||||
|_| async move {
|
|_| async move { CLIENT.notifications_list().await.unwrap() },
|
||||||
CLIENT
|
|
||||||
.notifications_list()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{LocalUserView, RegisterUserForm},
|
common::{LocalUserView, RegisterUserForm},
|
||||||
frontend::{app::GlobalState, components::credentials::*, error::MyResult},
|
frontend::{api::CLIENT, app::GlobalState, components::credentials::*, error::MyResult},
|
||||||
};
|
};
|
||||||
use leptos::{logging::log, *};use crate::frontend::api::CLIENT;
|
use leptos::{logging::log, *};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Register() -> impl IntoView {
|
pub fn Register() -> impl IntoView {
|
||||||
|
@ -17,8 +17,7 @@ pub fn Register() -> impl IntoView {
|
||||||
log!("Try to register new account for {}", credentials.username);
|
log!("Try to register new account for {}", credentials.username);
|
||||||
async move {
|
async move {
|
||||||
set_wait_for_response.update(|w| *w = true);
|
set_wait_for_response.update(|w| *w = true);
|
||||||
let result: MyResult<LocalUserView> =
|
let result: MyResult<LocalUserView> = CLIENT.register(credentials).await;
|
||||||
CLIENT.register(credentials).await;
|
|
||||||
set_wait_for_response.update(|w| *w = false);
|
set_wait_for_response.update(|w| *w = false);
|
||||||
match result {
|
match result {
|
||||||
Ok(res) => {
|
Ok(res) => {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{DbArticle, DbInstance, SearchArticleForm},
|
common::{DbArticle, DbInstance, SearchArticleForm},
|
||||||
frontend::{article_link, article_title},
|
frontend::{api::CLIENT, article_link, article_title},
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::use_query_map;
|
use leptos_router::use_query_map;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;use crate::frontend::api::CLIENT;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Default, Clone, Deserialize, Serialize, Debug)]
|
#[derive(Default, Clone, Deserialize, Serialize, Debug)]
|
||||||
struct SearchResults {
|
struct SearchResults {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{DbPerson, GetUserForm},
|
common::{DbPerson, GetUserForm},
|
||||||
frontend::user_title,
|
frontend::{api::CLIENT, user_title},
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::use_params_map;use crate::frontend::api::CLIENT;
|
use leptos_router::use_params_map;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn UserProfile() -> impl IntoView {
|
pub fn UserProfile() -> impl IntoView {
|
||||||
|
|
Loading…
Reference in a new issue