修复 admin 路由切换闪烁,全局替换原生导航为客户端路由
- 新增全局 UserContext,将用户认证状态提升到 App 级别缓存 - 将 /admin 和 /admin/write 改为嵌套路由,AdminLayout 作为共享父布局 - AdminLayout 使用 Outlet 渲染子页面,避免路由切换时重复挂载 - 修复所有原生 <a> 标签导致的整页刷新问题: - Header 导航栏 Logo 和 NavItem - 首页文章卡片、分页按钮 - 归档页文章条目 - 标签页标签云、文章卡片、标签链接 - 登录/注册页面链接 - Dashboard 快捷操作按钮 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5d523fbfc7
commit
61376f6ba9
@ -3,16 +3,36 @@ use dioxus::prelude::*;
|
||||
use crate::api::auth::{get_current_user, logout};
|
||||
use crate::components::header::{Header, NavItemConfig};
|
||||
use crate::components::footer::Footer;
|
||||
use crate::context::UserContext;
|
||||
use crate::router::Route;
|
||||
|
||||
#[component]
|
||||
pub fn AdminLayout(children: Element) -> Element {
|
||||
let user_resource =
|
||||
use_resource(|| async move { get_current_user().await.ok().and_then(|r| r.user) });
|
||||
|
||||
pub fn AdminLayout() -> Element {
|
||||
let mut ctx: UserContext = use_context();
|
||||
let navigator = dioxus::router::navigator();
|
||||
let route = use_route::<Route>();
|
||||
|
||||
// 只在首次挂载时加载用户数据
|
||||
use_effect(move || {
|
||||
if !(ctx.checked)() {
|
||||
(ctx.checked).set(true);
|
||||
spawn(async move {
|
||||
match get_current_user().await {
|
||||
Ok(response) => {
|
||||
if let Some(user) = response.user {
|
||||
ctx.user.set(Some(std::sync::Arc::new(user)));
|
||||
} else {
|
||||
let _ = navigator.push("/login");
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
let _ = navigator.push("/login");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let admin_nav_items = vec![
|
||||
NavItemConfig {
|
||||
href: "/admin",
|
||||
@ -31,12 +51,12 @@ pub fn AdminLayout(children: Element) -> Element {
|
||||
},
|
||||
];
|
||||
|
||||
let nav = navigator;
|
||||
let nav = navigator.clone();
|
||||
let logout_button = rsx! {
|
||||
button {
|
||||
class: "text-sm text-gray-600 dark:text-[#9b9c9d] hover:text-gray-900 dark:hover:text-[#dadadb] transition-colors",
|
||||
onclick: move |_| {
|
||||
let nav = nav;
|
||||
let nav = nav.clone();
|
||||
spawn(async move {
|
||||
let _ = logout().await;
|
||||
let _ = nav.push("/login");
|
||||
@ -46,29 +66,26 @@ pub fn AdminLayout(children: Element) -> Element {
|
||||
}
|
||||
};
|
||||
|
||||
let user_data = user_resource.read().clone();
|
||||
|
||||
let should_redirect = matches!(user_data.as_ref(), Some(None));
|
||||
|
||||
use_effect(move || {
|
||||
if should_redirect {
|
||||
navigator.push("/login");
|
||||
}
|
||||
});
|
||||
|
||||
match user_data.as_ref() {
|
||||
Some(Some(_user)) => {
|
||||
match ((ctx.checked)(), (ctx.user)()) {
|
||||
(true, Some(_)) => {
|
||||
rsx! {
|
||||
div { class: "min-h-screen flex flex-col bg-white dark:bg-[#1d1e20]",
|
||||
Header { nav_items: admin_nav_items, right_content: logout_button }
|
||||
main { class: "flex-1 w-full max-w-5xl mx-auto px-6 py-8",
|
||||
{children}
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
Footer {}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
(true, None) => {
|
||||
rsx! {
|
||||
div { class: "min-h-screen flex items-center justify-center bg-white dark:bg-[#1d1e20]",
|
||||
p { class: "text-gray-600 dark:text-[#9b9c9d]", "未登录,正在跳转..." }
|
||||
}
|
||||
}
|
||||
}
|
||||
(false, _) => {
|
||||
rsx! {
|
||||
div { class: "min-h-screen flex items-center justify-center bg-white dark:bg-[#1d1e20]",
|
||||
p { class: "text-gray-600 dark:text-[#9b9c9d]", "加载中..." }
|
||||
|
||||
@ -15,6 +15,10 @@ pub fn Header(nav_items: Vec<NavItemConfig>, right_content: Element) -> Element
|
||||
a {
|
||||
class: "text-2xl font-bold text-gray-900 dark:text-[#dadadb] hover:opacity-80 transition-opacity",
|
||||
href: "/",
|
||||
onclick: move |evt| {
|
||||
evt.prevent_default();
|
||||
dioxus::router::navigator().push("/");
|
||||
},
|
||||
"Yggdrasil"
|
||||
}
|
||||
div { class: "flex items-center gap-2",
|
||||
@ -43,9 +47,18 @@ fn NavItem(href: &'static str, label: &'static str, is_active: bool) -> Element
|
||||
format!("{} text-gray-600 dark:text-[#9b9c9d] hover:text-gray-900 dark:hover:text-[#dadadb]", base_class)
|
||||
};
|
||||
|
||||
let href = href;
|
||||
rsx! {
|
||||
li {
|
||||
a { class: "{class_str}", href: "{href}", "{label}" }
|
||||
a {
|
||||
class: "{class_str}",
|
||||
href: "{href}",
|
||||
onclick: move |evt| {
|
||||
evt.prevent_default();
|
||||
dioxus::router::navigator().push(href);
|
||||
},
|
||||
"{label}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
10
src/context.rs
Normal file
10
src/context.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use dioxus::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::models::user::User;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct UserContext {
|
||||
pub user: Signal<Option<Arc<User>>>,
|
||||
pub checked: Signal<bool>,
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
mod api;
|
||||
mod auth;
|
||||
mod components;
|
||||
mod context;
|
||||
mod db;
|
||||
mod models;
|
||||
mod pages;
|
||||
|
||||
@ -1,46 +1,45 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::admin_layout::AdminLayout;
|
||||
use crate::pages::home::{Post, POSTS};
|
||||
|
||||
#[component]
|
||||
pub fn AdminPage() -> Element {
|
||||
rsx! {
|
||||
AdminLayout {
|
||||
div { class: "space-y-8",
|
||||
// 统计卡片
|
||||
div { class: "grid grid-cols-1 md:grid-cols-3 gap-6",
|
||||
StatCard { value: POSTS.len().to_string(), label: "文章总数" }
|
||||
StatCard { value: "0".to_string(), label: "草稿数" }
|
||||
StatCard { value: POSTS.len().to_string(), label: "已发布" }
|
||||
}
|
||||
div { class: "space-y-8",
|
||||
// 统计卡片
|
||||
div { class: "grid grid-cols-1 md:grid-cols-3 gap-6",
|
||||
StatCard { value: POSTS.len().to_string(), label: "文章总数" }
|
||||
StatCard { value: "0".to_string(), label: "草稿数" }
|
||||
StatCard { value: POSTS.len().to_string(), label: "已发布" }
|
||||
}
|
||||
|
||||
// 快捷操作
|
||||
div { class: "grid grid-cols-1 md:grid-cols-2 gap-4",
|
||||
a {
|
||||
class: "bg-gray-900 dark:bg-[#dadadb] text-white dark:text-gray-900 rounded-full px-6 py-3 text-center font-medium hover:opacity-80 transition-opacity",
|
||||
href: "/admin/write",
|
||||
"写文章"
|
||||
}
|
||||
button {
|
||||
class: "bg-gray-200 dark:bg-[#333] text-gray-700 dark:text-[#dadadb] rounded-full px-6 py-3 text-center font-medium hover:opacity-80 transition-opacity",
|
||||
onclick: move |_| {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_sys::window().map(|w| w.alert_with_message("开发中").ok());
|
||||
},
|
||||
"管理文章"
|
||||
}
|
||||
// 快捷操作
|
||||
div { class: "grid grid-cols-1 md:grid-cols-2 gap-4",
|
||||
button {
|
||||
class: "bg-gray-900 dark:bg-[#dadadb] text-white dark:text-gray-900 rounded-full px-6 py-3 text-center font-medium hover:opacity-80 transition-opacity cursor-pointer",
|
||||
onclick: move |_| {
|
||||
dioxus::router::navigator().push("/admin/write");
|
||||
},
|
||||
"写文章"
|
||||
}
|
||||
button {
|
||||
class: "bg-gray-200 dark:bg-[#333] text-gray-700 dark:text-[#dadadb] rounded-full px-6 py-3 text-center font-medium hover:opacity-80 transition-opacity",
|
||||
onclick: move |_| {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_sys::window().map(|w| w.alert_with_message("开发中").ok());
|
||||
},
|
||||
"管理文章"
|
||||
}
|
||||
}
|
||||
|
||||
// 最近文章
|
||||
div { class: "mb-8",
|
||||
h2 { class: "text-xl font-bold text-gray-900 dark:text-[#dadadb] mb-4",
|
||||
"最近文章"
|
||||
}
|
||||
div { class: "space-y-0",
|
||||
for post in POSTS.iter().take(5) {
|
||||
RecentPostItem { post: post.clone() }
|
||||
}
|
||||
// 最近文章
|
||||
div { class: "mb-8",
|
||||
h2 { class: "text-xl font-bold text-gray-900 dark:text-[#dadadb] mb-4",
|
||||
"最近文章"
|
||||
}
|
||||
div { class: "space-y-0",
|
||||
for post in POSTS.iter().take(5) {
|
||||
RecentPostItem { post: post.clone() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::admin_layout::AdminLayout;
|
||||
|
||||
#[component]
|
||||
pub fn WritePage() -> Element {
|
||||
let mut title = use_signal(|| "".to_string());
|
||||
@ -36,44 +34,42 @@ pub fn WritePage() -> Element {
|
||||
});
|
||||
|
||||
rsx! {
|
||||
AdminLayout {
|
||||
div { class: "space-y-4",
|
||||
// 标题输入
|
||||
input {
|
||||
class: "w-full text-2xl font-bold bg-transparent border-b border-gray-200 dark:border-[#333] py-2 mb-4 text-gray-900 dark:text-[#dadadb] placeholder-gray-400 dark:placeholder-[#9b9c9d] focus:outline-none",
|
||||
placeholder: "文章标题...",
|
||||
value: "{title}",
|
||||
oninput: move |evt| title.set(evt.value()),
|
||||
}
|
||||
div { class: "space-y-4",
|
||||
// 标题输入
|
||||
input {
|
||||
class: "w-full text-2xl font-bold bg-transparent border-b border-gray-200 dark:border-[#333] py-2 mb-4 text-gray-900 dark:text-[#dadadb] placeholder-gray-400 dark:placeholder-[#9b9c9d] focus:outline-none",
|
||||
placeholder: "文章标题...",
|
||||
value: "{title}",
|
||||
oninput: move |evt| title.set(evt.value()),
|
||||
}
|
||||
|
||||
// Tiptap 编辑器容器
|
||||
div {
|
||||
class: "w-full h-[600px] border border-gray-200 dark:border-[#333] rounded-lg overflow-hidden bg-white dark:bg-[#1e1e1e]",
|
||||
id: "tiptap-editor",
|
||||
}
|
||||
// Tiptap 编辑器容器
|
||||
div {
|
||||
class: "w-full h-[600px] border border-gray-200 dark:border-[#333] rounded-lg overflow-hidden bg-white dark:bg-[#1e1e1e]",
|
||||
id: "tiptap-editor",
|
||||
}
|
||||
|
||||
// 保存按钮
|
||||
button {
|
||||
class: "mt-4 px-6 py-2 bg-gray-900 dark:bg-[#dadadb] text-white dark:text-gray-900 rounded-full font-medium hover:opacity-80 transition-opacity",
|
||||
onclick: move |_| {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let md = js_sys::eval(r#"
|
||||
(function() {
|
||||
var editor = window.TiptapEditor && window.TiptapEditor._instances && window.TiptapEditor._instances.get('tiptap-editor');
|
||||
return editor ? editor.getMarkdown() : (window.__tiptap_content || '');
|
||||
})()
|
||||
"#).ok().and_then(|v| v.as_string()).unwrap_or_default();
|
||||
content.set(md.clone());
|
||||
println!("保存文章: title={}, content_len={}", title(), md.len());
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
println!("保存文章: title={}, content_len={}", title(), content().len());
|
||||
}
|
||||
},
|
||||
"保存草稿"
|
||||
}
|
||||
// 保存按钮
|
||||
button {
|
||||
class: "mt-4 px-6 py-2 bg-gray-900 dark:bg-[#dadadb] text-white dark:text-gray-900 rounded-full font-medium hover:opacity-80 transition-opacity",
|
||||
onclick: move |_| {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let md = js_sys::eval(r#"
|
||||
(function() {
|
||||
var editor = window.TiptapEditor && window.TiptapEditor._instances && window.TiptapEditor._instances.get('tiptap-editor');
|
||||
return editor ? editor.getMarkdown() : (window.__tiptap_content || '');
|
||||
})()
|
||||
"#).ok().and_then(|v| v.as_string()).unwrap_or_default();
|
||||
content.set(md.clone());
|
||||
println!("保存文章: title={}, content_len={}", title(), md.len());
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
println!("保存文章: title={}, content_len={}", title(), content().len());
|
||||
}
|
||||
},
|
||||
"保存草稿"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,6 +211,10 @@ fn ArchiveEntry(post: Post) -> Element {
|
||||
class: "entry-link absolute inset-0 z-10",
|
||||
aria_label: "post link to {post.title}",
|
||||
href: "/post/{post.slug}",
|
||||
onclick: move |evt| {
|
||||
evt.prevent_default();
|
||||
dioxus::router::navigator().push(format!("/post/{}", post.slug).as_str());
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,9 +103,16 @@ fn HomeInfo() -> Element {
|
||||
fn PostEntry(post: Post) -> Element {
|
||||
let tag_items = post.tags.to_vec();
|
||||
|
||||
let post_slug = post.slug;
|
||||
rsx! {
|
||||
article { class: "relative mb-6 p-6 bg-white dark:bg-[#2e2e33] rounded-lg border border-gray-200 dark:border-[#333] hover:-translate-y-0.5 hover:border-gray-300 dark:hover:border-gray-600 transition-all duration-250",
|
||||
a { class: "block group", href: "/post/{post.slug}",
|
||||
a {
|
||||
class: "block group",
|
||||
href: "/post/{post_slug}",
|
||||
onclick: move |evt| {
|
||||
evt.prevent_default();
|
||||
dioxus::router::navigator().push(format!("/post/{}", post_slug).as_str());
|
||||
},
|
||||
h2 { class: "text-2xl font-bold leading-tight text-gray-900 dark:text-[#dadadb] group-hover:opacity-80 transition-opacity",
|
||||
"{post.title}"
|
||||
}
|
||||
@ -131,9 +138,9 @@ fn PostEntry(post: Post) -> Element {
|
||||
fn Pagination() -> Element {
|
||||
rsx! {
|
||||
nav { class: "flex mt-10 mb-6",
|
||||
a {
|
||||
class: "ml-auto inline-flex items-center px-4 py-2 text-sm text-white bg-gray-900 dark:bg-[#dadadb] dark:text-gray-900 rounded-full hover:opacity-80 transition-opacity",
|
||||
href: "/page/2",
|
||||
button {
|
||||
class: "ml-auto inline-flex items-center px-4 py-2 text-sm text-white bg-gray-900 dark:bg-[#dadadb] dark:text-gray-900 rounded-full hover:opacity-80 transition-opacity cursor-pointer",
|
||||
onclick: move |_| { dioxus::router::navigator().push("/page/2"); },
|
||||
"下一页"
|
||||
span { class: "ml-1", "»" }
|
||||
}
|
||||
|
||||
@ -89,9 +89,9 @@ pub fn LoginPage() -> Element {
|
||||
onclick: move |_| on_submit(()),
|
||||
"登录"
|
||||
}
|
||||
a {
|
||||
class: "block w-full py-2 px-4 text-center text-gray-500 dark:text-[#9b9c9d] hover:text-gray-700 dark:hover:text-[#dadadb] font-medium rounded-lg transition-colors",
|
||||
href: "/register",
|
||||
button {
|
||||
class: "block w-full py-2 px-4 text-center text-gray-500 dark:text-[#9b9c9d] hover:text-gray-700 dark:hover:text-[#dadadb] font-medium rounded-lg transition-colors cursor-pointer",
|
||||
onclick: move |_| { dioxus::router::navigator().push("/register"); },
|
||||
"还没有账号?去注册"
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,7 +56,8 @@ pub fn RegisterPage() -> Element {
|
||||
if success() {
|
||||
div { class: "mb-4 p-3 bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300 rounded-lg text-center",
|
||||
"注册成功!"
|
||||
a { class: "block mt-2 text-gray-700 dark:text-[#dadadb] hover:underline", href: "/login",
|
||||
button { class: "block mt-2 text-gray-700 dark:text-[#dadadb] hover:underline cursor-pointer",
|
||||
onclick: move |_| { dioxus::router::navigator().push("/login"); },
|
||||
"去登录"
|
||||
}
|
||||
}
|
||||
@ -125,7 +126,8 @@ pub fn RegisterPage() -> Element {
|
||||
}
|
||||
p { class: "mt-4 text-center text-sm text-gray-500 dark:text-[#9b9c9d]",
|
||||
"已有账号?"
|
||||
a { class: "text-gray-700 dark:text-[#dadadb] hover:underline", href: "/login",
|
||||
button { class: "text-gray-700 dark:text-[#dadadb] hover:underline cursor-pointer",
|
||||
onclick: move |_| { dioxus::router::navigator().push("/login"); },
|
||||
"去登录"
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,13 +70,17 @@ pub fn TagsPage() -> Element {
|
||||
}
|
||||
}
|
||||
ul { class: "flex flex-wrap gap-4 mt-6",
|
||||
for tag in tags.iter() {
|
||||
for (name, count) in tags.into_iter().map(|t| (t.name, t.count)) {
|
||||
li {
|
||||
a {
|
||||
class: "inline-flex items-center px-3 py-1.5 text-base font-medium bg-gray-100 dark:bg-[#2e2e33] text-gray-700 dark:text-[#9b9c9d] rounded-lg hover:bg-gray-200 dark:hover:bg-[#333] transition-colors",
|
||||
href: "/tags/{tag.name}",
|
||||
"{tag.name}"
|
||||
sup { class: "ml-1 text-sm text-gray-500 dark:text-[#9b9c9d]", "{tag.count}" }
|
||||
href: "/tags/{name}",
|
||||
onclick: move |evt| {
|
||||
evt.prevent_default();
|
||||
dioxus::router::navigator().push(format!("/tags/{}", name).as_str());
|
||||
},
|
||||
"{name}"
|
||||
sup { class: "ml-1 text-sm text-gray-500 dark:text-[#9b9c9d]", "{count}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -126,10 +130,17 @@ pub fn TagDetailPage(tag: String) -> Element {
|
||||
#[component]
|
||||
fn TagPostEntry(post: Post) -> Element {
|
||||
let tag_items = post.tags.to_vec();
|
||||
let post_slug = post.slug;
|
||||
|
||||
rsx! {
|
||||
article { class: "relative mb-6 p-6 bg-white dark:bg-[#2e2e33] rounded-lg border border-gray-200 dark:border-[#333] hover:-translate-y-0.5 hover:border-gray-300 dark:hover:border-gray-600 transition-all duration-250",
|
||||
a { class: "block group", href: "/post/{post.slug}",
|
||||
a {
|
||||
class: "block group",
|
||||
href: "/post/{post_slug}",
|
||||
onclick: move |evt| {
|
||||
evt.prevent_default();
|
||||
dioxus::router::navigator().push(format!("/post/{}", post_slug).as_str());
|
||||
},
|
||||
h2 { class: "text-2xl font-bold leading-tight text-gray-900 dark:text-[#dadadb] group-hover:opacity-80 transition-opacity",
|
||||
"{post.title}"
|
||||
}
|
||||
@ -139,15 +150,19 @@ fn TagPostEntry(post: Post) -> Element {
|
||||
div { class: "mt-3 flex items-center gap-3 text-[13px] text-gray-400 dark:text-[#9b9c9d]",
|
||||
span { "{post.date}" }
|
||||
span { "·" }
|
||||
for (i, t) in tag_items.iter().enumerate() {
|
||||
for (i, tag_name) in tag_items.into_iter().enumerate() {
|
||||
if i > 0 {
|
||||
span { "," }
|
||||
}
|
||||
span {
|
||||
a {
|
||||
class: "hover:text-gray-600 dark:hover:text-[#dadadb] transition-colors",
|
||||
href: "/tags/{t}",
|
||||
"{t}"
|
||||
href: "/tags/{tag_name}",
|
||||
onclick: move |evt| {
|
||||
evt.prevent_default();
|
||||
dioxus::router::navigator().push(format!("/tags/{}", tag_name).as_str());
|
||||
},
|
||||
"{tag_name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
use dioxus::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::components::admin_layout::AdminLayout;
|
||||
use crate::context::UserContext;
|
||||
use crate::pages::admin::{AdminPage, WritePage};
|
||||
use crate::pages::archives::ArchivesPage;
|
||||
use crate::pages::home::HomePage;
|
||||
@ -9,6 +12,7 @@ use crate::pages::tags::{TagsPage, TagDetailPage};
|
||||
use crate::theme::{Theme, ThemePreload, use_theme_provider};
|
||||
|
||||
#[derive(Clone, Routable, Debug, PartialEq)]
|
||||
#[rustfmt::skip]
|
||||
pub enum Route {
|
||||
#[route("/")]
|
||||
HomePage {},
|
||||
@ -16,10 +20,16 @@ pub enum Route {
|
||||
LoginPage {},
|
||||
#[route("/register")]
|
||||
RegisterPage {},
|
||||
#[route("/admin")]
|
||||
AdminPage {},
|
||||
#[route("/admin/write")]
|
||||
WritePage {},
|
||||
|
||||
#[nest("/admin")]
|
||||
#[layout(AdminLayout)]
|
||||
#[route("/")]
|
||||
AdminPage {},
|
||||
#[route("/write")]
|
||||
WritePage {},
|
||||
#[end_layout]
|
||||
#[end_nest]
|
||||
|
||||
#[route("/archives")]
|
||||
ArchivesPage {},
|
||||
#[route("/tags")]
|
||||
@ -40,6 +50,10 @@ pub fn AppRouter() -> Element {
|
||||
Theme::Light => "",
|
||||
};
|
||||
|
||||
let user = use_signal(|| None::<Arc<crate::models::user::User>>);
|
||||
let checked = use_signal(|| false);
|
||||
use_context_provider(|| UserContext { user, checked });
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
class: "{theme_class}",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user