human hand off bug
This commit is contained in:
parent
2855a20140
commit
962f991580
11 changed files with 226 additions and 34 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import { useState } from "react";
|
||||
import { changePassword } from "../api";
|
||||
import PasswordField from "./PasswordField";
|
||||
|
||||
function validateForm(form) {
|
||||
const errors = {};
|
||||
|
|
@ -79,8 +80,7 @@ export default function ChangePasswordModal({ account, onComplete }) {
|
|||
<form className="change-password-form" onSubmit={handleSubmit} noValidate>
|
||||
<label>
|
||||
Current password
|
||||
<input
|
||||
type="password"
|
||||
<PasswordField
|
||||
autoComplete="current-password"
|
||||
value={form.current_password}
|
||||
onChange={(event) => updateField("current_password", event.target.value)}
|
||||
|
|
@ -94,8 +94,7 @@ export default function ChangePasswordModal({ account, onComplete }) {
|
|||
|
||||
<label>
|
||||
New password
|
||||
<input
|
||||
type="password"
|
||||
<PasswordField
|
||||
autoComplete="new-password"
|
||||
value={form.new_password}
|
||||
onChange={(event) => updateField("new_password", event.target.value)}
|
||||
|
|
@ -109,8 +108,7 @@ export default function ChangePasswordModal({ account, onComplete }) {
|
|||
|
||||
<label>
|
||||
Confirm new password
|
||||
<input
|
||||
type="password"
|
||||
<PasswordField
|
||||
autoComplete="new-password"
|
||||
value={form.confirm_password}
|
||||
onChange={(event) => updateField("confirm_password", event.target.value)}
|
||||
|
|
|
|||
95
frontend/src/components/ForgotPasswordPage.jsx
Normal file
95
frontend/src/components/ForgotPasswordPage.jsx
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { ArrowRight, Mail } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { resetPassword } from "../api";
|
||||
import AuthLayout from "./AuthLayout";
|
||||
import PasswordField from "./PasswordField";
|
||||
|
||||
export default function ForgotPasswordPage() {
|
||||
const [status, setStatus] = useState({ loading: false, error: "", done: false });
|
||||
|
||||
async function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
const form = new FormData(event.currentTarget);
|
||||
const password = String(form.get("new_password") || "");
|
||||
const confirm = String(form.get("confirm_password") || "");
|
||||
if (password !== confirm) {
|
||||
setStatus({ loading: false, error: "Passwords do not match.", done: false });
|
||||
return;
|
||||
}
|
||||
setStatus({ loading: true, error: "", done: false });
|
||||
try {
|
||||
await resetPassword({
|
||||
email: form.get("email"),
|
||||
new_password: password,
|
||||
});
|
||||
setStatus({ loading: false, error: "", done: true });
|
||||
} catch (error) {
|
||||
setStatus({ loading: false, error: error.message, done: false });
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthLayout
|
||||
eyebrow="Forgot password"
|
||||
title={<>Set a new password<br /><em>and sign back in.</em></>}
|
||||
description="Enter your account email and choose a new password. Extra email or phone checks are skipped for now."
|
||||
>
|
||||
<>
|
||||
<div className="card-heading">
|
||||
<h2>Reset your password</h2>
|
||||
<p>Use the email you sign in with. No verification code is required yet.</p>
|
||||
</div>
|
||||
{status.done ? (
|
||||
<div className="auth-success">
|
||||
<p>Your password has been updated. You can sign in with it now.</p>
|
||||
<a className="button button-coral auth-submit" href="/login">
|
||||
Back to sign in <ArrowRight size={16} />
|
||||
</a>
|
||||
</div>
|
||||
) : (
|
||||
<form className="auth-form" onSubmit={handleSubmit}>
|
||||
<label>
|
||||
Email address
|
||||
<div className="input-wrap">
|
||||
<Mail size={16} />
|
||||
<input
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="you@gentic.email"
|
||||
required
|
||||
autoComplete="email"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
<label>
|
||||
New password
|
||||
<PasswordField
|
||||
name="new_password"
|
||||
placeholder="8+ characters"
|
||||
minLength={8}
|
||||
required
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
<small className="field-hint">Use uppercase, lowercase, and a number.</small>
|
||||
</label>
|
||||
<label>
|
||||
Confirm new password
|
||||
<PasswordField
|
||||
name="confirm_password"
|
||||
placeholder="Repeat your password"
|
||||
minLength={8}
|
||||
required
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</label>
|
||||
{status.error && <p className="form-error" role="alert">{status.error}</p>}
|
||||
<button className="button button-coral auth-submit" disabled={status.loading}>
|
||||
{status.loading ? "Updating…" : "Update password"} <ArrowRight size={16} />
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
<p className="auth-switch">Remembered it? <a href="/login">Log in</a></p>
|
||||
</>
|
||||
</AuthLayout>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import { ArrowRight, LockKeyhole } from "lucide-react";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { acceptInvite, previewInvite } from "../api";
|
||||
import AuthLayout from "./AuthLayout";
|
||||
import PasswordField from "./PasswordField";
|
||||
|
||||
function inviteTokenFromPath() {
|
||||
const match = window.location.pathname.match(/^\/invite\/([^/]+)/);
|
||||
|
|
@ -89,31 +90,23 @@ export default function InviteAcceptPage() {
|
|||
<form className="auth-form" onSubmit={handleSubmit}>
|
||||
<label>
|
||||
Password
|
||||
<div className="input-wrap">
|
||||
<LockKeyhole size={16} />
|
||||
<input
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="Create a password"
|
||||
required
|
||||
minLength={8}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
<PasswordField
|
||||
name="password"
|
||||
placeholder="Create a password"
|
||||
required
|
||||
minLength={8}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Confirm password
|
||||
<div className="input-wrap">
|
||||
<LockKeyhole size={16} />
|
||||
<input
|
||||
name="confirm_password"
|
||||
type="password"
|
||||
placeholder="Repeat your password"
|
||||
required
|
||||
minLength={8}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
<PasswordField
|
||||
name="confirm_password"
|
||||
placeholder="Repeat your password"
|
||||
required
|
||||
minLength={8}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</label>
|
||||
<p className="auth-switch" style={{ marginTop: 0 }}>
|
||||
Must include uppercase, lowercase, and a number.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { ArrowRight, LockKeyhole, Mail } from "lucide-react";
|
||||
import { ArrowRight, Mail } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { login } from "../api";
|
||||
import AuthLayout from "./AuthLayout";
|
||||
import PasswordField from "./PasswordField";
|
||||
|
||||
export default function LoginPage() {
|
||||
const [status, setStatus] = useState({ loading: false, error: "" });
|
||||
|
|
@ -33,7 +34,18 @@ export default function LoginPage() {
|
|||
<div className="card-heading"><h2>Log in to agmail</h2><p>Enter your account details below.</p></div>
|
||||
<form className="auth-form" onSubmit={handleSubmit}>
|
||||
<label>Email address<div className="input-wrap"><Mail size={16} /><input name="email" type="email" placeholder="you@gentic.email" required autoComplete="email" /></div></label>
|
||||
<label>Password<div className="input-wrap"><LockKeyhole size={16} /><input name="password" type="password" placeholder="Your password" required autoComplete="current-password" /></div></label>
|
||||
<label>
|
||||
<span className="auth-label-row">
|
||||
Password
|
||||
<a className="auth-forgot" href="/forgot-password">Forgot password?</a>
|
||||
</span>
|
||||
<PasswordField
|
||||
name="password"
|
||||
placeholder="Your password"
|
||||
required
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
</label>
|
||||
{status.error && <p className="form-error" role="alert">{status.error}</p>}
|
||||
<button className="button button-coral auth-submit" disabled={status.loading}>
|
||||
{status.loading ? "Signing in…" : "Log in"} <ArrowRight size={16} />
|
||||
|
|
|
|||
41
frontend/src/components/PasswordField.jsx
Normal file
41
frontend/src/components/PasswordField.jsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { Eye, EyeOff, LockKeyhole } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function PasswordField({
|
||||
name,
|
||||
placeholder,
|
||||
autoComplete,
|
||||
required,
|
||||
minLength,
|
||||
value,
|
||||
onChange,
|
||||
showLock = true,
|
||||
className = "input-wrap",
|
||||
}) {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{showLock ? <LockKeyhole size={16} /> : null}
|
||||
<input
|
||||
name={name}
|
||||
type={visible ? "text" : "password"}
|
||||
placeholder={placeholder}
|
||||
required={required}
|
||||
minLength={minLength}
|
||||
autoComplete={autoComplete}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="password-peek"
|
||||
aria-label={visible ? "Hide password" : "Show password"}
|
||||
aria-pressed={visible}
|
||||
onClick={() => setVisible((current) => !current)}
|
||||
>
|
||||
{visible ? <EyeOff size={15} /> : <Eye size={15} />}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import {
|
||||
ArrowRight, Building2, CalendarDays, Eye, LockKeyhole, Mail, Phone, UserRound,
|
||||
ArrowRight, Building2, CalendarDays, Mail, Phone, UserRound,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { register } from "../api";
|
||||
import AuthLayout from "./AuthLayout";
|
||||
import PasswordField from "./PasswordField";
|
||||
|
||||
export default function SignupPage() {
|
||||
const [accountType, setAccountType] = useState("individual");
|
||||
|
|
@ -67,7 +68,7 @@ export default function SignupPage() {
|
|||
</div>
|
||||
<label>Date of birth<div className="input-wrap"><CalendarDays size={16} /><input name="date_of_birth" type="date" required /></div></label>
|
||||
<label>Phone number<div className="input-wrap"><Phone size={16} /><input name="phone_number" type="tel" placeholder="+1 555 123 4567" required /></div></label>
|
||||
<label>Password<div className="input-wrap"><LockKeyhole size={16} /><input name="password" type="password" placeholder="8+ characters" minLength="8" required autoComplete="new-password" /><Eye size={15} /></div><small className="field-hint">Use uppercase, lowercase, and a number.</small></label>
|
||||
<label>Password<PasswordField name="password" placeholder="8+ characters" minLength={8} required autoComplete="new-password" /><small className="field-hint">Use uppercase, lowercase, and a number.</small></label>
|
||||
{status.error && <p className="form-error" role="alert">{status.error}</p>}
|
||||
<button className="button button-coral auth-submit" disabled={status.loading}>
|
||||
{status.loading ? "Creating account…" : "Create account"} <ArrowRight size={16} />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue