human hand off bug

This commit is contained in:
debu-2 2026-10-06 19:09:02 +05:30
parent 2855a20140
commit 962f991580
11 changed files with 226 additions and 34 deletions

View file

@ -1,4 +1,5 @@
import DashboardPage from "./components/DashboardPage";
import ForgotPasswordPage from "./components/ForgotPasswordPage";
import InviteAcceptPage from "./components/InviteAcceptPage";
import LoginPage from "./components/LoginPage";
import MobileLanding from "./components/MobileLanding";
@ -13,6 +14,9 @@ export default function App() {
if (window.location.pathname === "/login") {
return <LoginPage />;
}
if (window.location.pathname === "/forgot-password") {
return <ForgotPasswordPage />;
}
if (window.location.pathname.startsWith("/invite/")) {
return <InviteAcceptPage />;
}

View file

@ -28,6 +28,8 @@ async function request(path, { method = "GET", payload, token, basicAuth } = {})
export const login = (payload) =>
request("/auth/login", { method: "POST", payload });
export const resetPassword = (payload) =>
request("/auth/reset-password", { method: "POST", payload });
export const previewInvite = (token) =>
request(`/auth/invites/${encodeURIComponent(token)}`);
export const acceptInvite = (token, payload) =>

View file

@ -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)}

View 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>
);
}

View file

@ -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.

View file

@ -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} />

View 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>
);
}

View file

@ -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} />

View file

@ -104,6 +104,11 @@ h1 em { color:var(--coral); font-style:normal; font-weight:500; }
.input-wrap:focus-within { border-color:var(--coral); box-shadow:0 0 0 3px #ff806c18; }
.input-wrap input { min-width:0; flex:1; border:0; outline:0; background:transparent; color:var(--ink); font-size:13px; }
.input-wrap input::placeholder { color:#aaa5ab; }
.password-peek { margin:0; padding:4px; border:0; background:transparent; color:#a19ba2; display:grid; place-items:center; cursor:pointer; }
.password-peek:hover,.password-peek:focus-visible { color:var(--ink); }
.auth-label-row { display:flex; align-items:center; justify-content:space-between; gap:10px; }
.auth-forgot { color:var(--ink); font-size:11px; font-weight:700; text-decoration:none; }
.auth-forgot:hover { color:var(--coral); }
.account-types { display:grid; grid-template-columns:1fr 1fr; gap:10px; }
.type-card { min-height:74px; padding:13px; display:grid; grid-template-columns:20px 1fr; align-items:center; gap:10px; border:1px solid #e5e0dc; border-radius:12px; background:white; cursor:pointer; }
.type-card.selected { border-color:var(--coral); background:#fff6f2; box-shadow:0 0 0 2px #ff806c12; }
@ -499,7 +504,8 @@ h1 em { color:var(--coral); font-style:normal; font-weight:500; }
.change-password-head p { margin:0; font-size:14px; color:#6f6c7b; line-height:1.5; }
.change-password-form { display:grid; gap:14px; }
.change-password-form label { display:grid; gap:6px; font-size:12px; font-weight:600; color:#4a4652; }
.change-password-form input { padding:10px 12px; border:1px solid #E1DFE7; border-radius:10px; font-size:14px; }
.change-password-form .input-wrap { height:44px; margin-top:0; padding:0 12px; }
.change-password-form .input-wrap input { font-size:14px; }
.people-drawer-actions { margin-top:8px; display:flex; justify-content:flex-end; gap:10px; }
.group-avatar { width:32px; height:32px; display:grid; place-items:center; border-radius:50%; font-size:12px; font-weight:700; flex-shrink:0; }
.group-avatar.tone-green { background:#E8F7F3; color:#2d8a72; }