LoginForm takes an email and a password, keeps one signed in on a box, and signs in on Enter once the address reads and a password is typed; a failed try says why above Sign in. SignupForm ticks a new password's rules as they hold and waits for all of them and the terms. OAuthButtons offer each provider, one spinning while the rest rest. MagicLinkForm and ForgotPassword send a link to an address and, once sent, say where it went and offer it again. TwoFactorInput takes a six-digit code at once and, after one fails, clears the boxes through forms::PinInput's new attempt, keeping its focus. The Account chapter splits into sign-in, profile and access, and billing and team. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
178 lines
6.5 KiB
Rust
178 lines
6.5 KiB
Rust
use ely_gpui_component::{
|
|
account::{ForgotPassword, LoginForm, MagicLinkForm, OAuthButtons, SignupForm, TwoFactorInput},
|
|
feedback::InlineMessage,
|
|
layout::{Card, CardHeader},
|
|
primitives::{Divider, Severity},
|
|
};
|
|
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
|
|
|
use crate::{
|
|
probe::probe,
|
|
ui::{change, keep, section},
|
|
};
|
|
|
|
/// The sign-in demo: the provider on its way, and why the last try failed.
|
|
#[derive(Default)]
|
|
struct SignIn {
|
|
provider: Option<SharedString>,
|
|
error: Option<SharedString>,
|
|
}
|
|
|
|
pub fn sign_in(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let state = keep("account-sign-in", SignIn::default, window, cx);
|
|
let now = state.read(cx);
|
|
let (provider, error) = (now.provider.clone(), now.error.clone());
|
|
let [picked, tried] = [(); 2].map(|_| state.clone());
|
|
let mut providers = OAuthButtons::new(
|
|
"account-oauth",
|
|
[
|
|
("github", "GitHub"),
|
|
("google", "Google"),
|
|
("apple", "Apple"),
|
|
],
|
|
)
|
|
.on_pick(move |key, _, cx| change(&picked, cx, |state| state.provider = Some(key.clone())));
|
|
if let Some(provider) = provider {
|
|
providers = providers.busy(provider);
|
|
}
|
|
let mut form = LoginForm::new("account-login")
|
|
.on_submit(move |_, _, cx| {
|
|
change(&tried, cx, |state| {
|
|
state.error = Some("No account uses that email and password.".into())
|
|
})
|
|
})
|
|
.on_forgot(|_, _| log::info!("gallery: forgot password"))
|
|
.on_sign_up(|_, _| log::info!("gallery: sign up"));
|
|
if let Some(error) = error {
|
|
form = form.error(error);
|
|
}
|
|
section(
|
|
"LoginForm · OAuthButtons",
|
|
"Sign in with a provider or with an email and a password. Sign in rests until the address reads and a password is typed; Enter in a field signs in, and a failed try says why above the button.",
|
|
cx,
|
|
)
|
|
.child(probe(
|
|
"account-login",
|
|
div().w(px(380.)).child(
|
|
Card::new()
|
|
.header(CardHeader::new("Sign in to Ely").description("Welcome back."))
|
|
.child(div().flex().flex_col().gap_5().child(providers).child(Divider::horizontal().label("or")).child(form)),
|
|
),
|
|
))
|
|
}
|
|
|
|
/// The sign-up demo: why the last try failed.
|
|
#[derive(Default)]
|
|
struct SignUp {
|
|
error: Option<SharedString>,
|
|
}
|
|
|
|
pub fn sign_up(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let state = keep("account-sign-up", SignUp::default, window, cx);
|
|
let error = state.read(cx).error.clone();
|
|
let tried = state.clone();
|
|
let mut form = SignupForm::new("account-signup")
|
|
.terms("I agree to the terms of service")
|
|
.on_submit(move |signup, _, cx| {
|
|
let taken = format!("An account already uses {}.", signup.email);
|
|
change(&tried, cx, |state| state.error = Some(taken.into()))
|
|
})
|
|
.on_sign_in(|_, _| log::info!("gallery: sign in"));
|
|
if let Some(error) = error {
|
|
form = form.error(error);
|
|
}
|
|
section(
|
|
"SignupForm",
|
|
"A name, an email and a new password, whose rules tick as they hold. Create account waits for every rule and the terms.",
|
|
cx,
|
|
)
|
|
.child(div().w(px(380.)).child(Card::new().header(CardHeader::new("Create an account")).child(form)))
|
|
}
|
|
|
|
/// The email demos: whether each link went.
|
|
#[derive(Default)]
|
|
struct ByEmail {
|
|
magic: bool,
|
|
reset: bool,
|
|
}
|
|
|
|
pub fn by_email(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let state = keep("account-by-email", ByEmail::default, window, cx);
|
|
let now = state.read(cx);
|
|
let (magic, reset) = (now.magic, now.reset);
|
|
let [sent, other, asked, back] = [(); 4].map(|_| state.clone());
|
|
section(
|
|
"MagicLinkForm · ForgotPassword",
|
|
"A link by email: one that signs in, one that sets a new password. Once sent, each says where it went and offers it again.",
|
|
cx,
|
|
)
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.flex_wrap()
|
|
.gap_6()
|
|
.child(probe(
|
|
"account-magic",
|
|
div().w(px(360.)).child(
|
|
Card::new().header(CardHeader::new("Sign in by email")).child(
|
|
MagicLinkForm::new("account-magic")
|
|
.sent(magic)
|
|
.on_send(move |_, _, cx| change(&sent, cx, |state| state.magic = true))
|
|
.on_other(move |_, cx| change(&other, cx, |state| state.magic = false)),
|
|
),
|
|
),
|
|
))
|
|
.child(
|
|
div().w(px(360.)).child(
|
|
Card::new().header(CardHeader::new("Reset your password")).child(
|
|
ForgotPassword::new("account-forgot")
|
|
.sent(reset)
|
|
.on_send(move |_, _, cx| change(&asked, cx, |state| state.reset = true))
|
|
.on_back(move |_, cx| change(&back, cx, |state| state.reset = false)),
|
|
),
|
|
),
|
|
),
|
|
)
|
|
}
|
|
|
|
/// The code the demo takes.
|
|
const CODE: &str = "246810";
|
|
|
|
/// The second-factor demo: codes that failed, and whether one passed.
|
|
#[derive(Default)]
|
|
struct Factor {
|
|
failed: usize,
|
|
passed: bool,
|
|
}
|
|
|
|
pub fn second_factor(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let state = keep("account-factor", Factor::default, window, cx);
|
|
let now = state.read(cx);
|
|
let (failed, passed) = (now.failed, now.passed);
|
|
let checked = state.clone();
|
|
section(
|
|
"TwoFactorInput",
|
|
format!("Six boxes for an authenticator's code; a full code goes at once. A code that fails clears the boxes and says so. The demo takes {CODE}."),
|
|
cx,
|
|
)
|
|
.child(probe(
|
|
"account-factor",
|
|
div().w(px(380.)).child(
|
|
Card::new()
|
|
.header(CardHeader::new("Two-step sign-in"))
|
|
.child(
|
|
TwoFactorInput::new("account-factor")
|
|
.failed(failed)
|
|
.on_code(move |code, _, cx| {
|
|
change(&checked, cx, |state| match code.as_ref() == CODE {
|
|
true => state.passed = true,
|
|
false => state.failed += 1,
|
|
})
|
|
})
|
|
.on_recovery(|_, _| log::info!("gallery: recovery code")),
|
|
)
|
|
.children(passed.then(|| InlineMessage::new(Severity::Success, "Signed in."))),
|
|
),
|
|
))
|
|
}
|