use crate::{AuthContext, AuthSession};
use leptos::prelude::*;
use leptos::tachys::view::any_view::IntoAny;
use leptos_router::hooks::use_navigate;
use url::form_urlencoded;
use crate::components::{EmptyState, EmptyStateCallToAction, EMPTYSTATE_SIGNIN_ILLUSTRATION};
use crate::primitives::{Button, ButtonAppearance};
pub fn parse_referer_from_search(search: &str) -> Option<String> {
let trimmed = search.trim_start_matches('?');
if trimmed.is_empty() {
return None;
}
for (key, value) in form_urlencoded::parse(trimmed.as_bytes()) {
if key == "referer" {
return Some(value.into_owned());
}
}
None
}
pub fn sanitize_referer_path(referer: Option<String>) -> String {
referer
.filter(|path| {
path.starts_with('/')
&& !path.starts_with("//")
&& !path.starts_with("/auth/")
&& !path.starts_with("/api/")
&& path != "/home"
&& path != "/home/"
})
.unwrap_or_else(|| "/".to_string())
}
pub fn authenticated_route_condition(
auth: AuthContext,
) -> impl Fn() -> Option<bool> + Clone + 'static {
let session = auth.session();
move || match session.get() {
AuthSession::Authenticated(_) => Some(true),
AuthSession::Anonymous(_) => Some(false),
}
}
#[component]
pub fn RequireAuthenticated(
#[prop(optional, default = false)] requires_email_verification: bool,
children: ChildrenFn,
) -> impl IntoView {
let auth = crate::use_auth_state();
let navigate = use_navigate();
view! {
{move || {
match auth.get() {
AuthSession::Anonymous(_) => {
let nav_signin = navigate.clone();
let nav_signup = navigate.clone();
view! {
<div
data-testid="auth-required-empty-state"
role="status"
aria-live="polite"
>
<EmptyState
message="Sign in required"
description="Sign in or create an account to continue."
illustration_src=EMPTYSTATE_SIGNIN_ILLUSTRATION
illustration_alt="Sign in required"
>
<EmptyStateCallToAction slot:call_to_action>
<Button
appearance=ButtonAppearance::Primary
on_click=Callback::new(move |_| nav_signin(crate::paths::AUTH_SIGNIN, Default::default()))
>
"Sign In"
</Button>
<Button
appearance=ButtonAppearance::Subtle
on_click=Callback::new(move |_| nav_signup(crate::paths::AUTH_SIGNUP, Default::default()))
>
"Sign Up"
</Button>
</EmptyStateCallToAction>
</EmptyState>
</div>
}
.into_any()
}
AuthSession::Authenticated(user) => {
if requires_email_verification && !user.email_verified {
let nav_account = navigate.clone();
view! {
<div
data-testid="email-verification-required-empty-state"
role="status"
aria-live="polite"
>
<EmptyState
message="Email verification required"
description="Verify your email in account settings to continue."
illustration_src=EMPTYSTATE_SIGNIN_ILLUSTRATION
illustration_alt="Email verification required"
>
<EmptyStateCallToAction slot:call_to_action>
<Button
appearance=ButtonAppearance::Primary
on_click=Callback::new(move |_| nav_account(crate::paths::USER_ACCOUNT_SETTINGS, Default::default()))
>
"Account Settings"
</Button>
</EmptyStateCallToAction>
</EmptyState>
</div>
}
.into_any()
} else {
children().into_any()
}
}
}
}}
}
}