agentic email invite html

This commit is contained in:
debu-2 2026-10-05 02:59:10 +05:30
parent 04a033e099
commit a679775f57
3 changed files with 168 additions and 102 deletions

View file

@ -1,4 +1,4 @@
import { Plus } from "lucide-react"; import { Info, Lock, Mail, Plus, User, UsersRound } from "lucide-react";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { createOrgMember, createOrgSection, listOrgSections } from "../api"; import { createOrgMember, createOrgSection, listOrgSections } from "../api";
@ -6,21 +6,30 @@ const EMAIL_LOCAL_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,62}[A-Za-z0-9])?$/;
function RequiredLabel({ children }) { function RequiredLabel({ children }) {
return ( return (
<span className="org-setup-label-text"> <>
{children} {children}
<span className="org-setup-required" aria-hidden="true">*</span> <span className="add-user-required" aria-hidden="true">*</span>
</span> </>
); );
} }
function SetupField({ label, error, children, hint }) { function SetupField({ label, error, children, hint }) {
return ( return (
<label className={`org-setup-field${error ? " has-error" : ""}`}> <div className={`add-user-field${error ? " has-error" : ""}`}>
{label} {label && <span className="add-user-label">{label}</span>}
{children} {children}
{hint && <small className="org-setup-field-hint">{hint}</small>} {hint && <small className="add-user-hint">{hint}</small>}
{error && <span className="org-setup-field-error" role="alert">{error}</span>} {error && <span className="add-user-error" role="alert">{error}</span>}
</label> </div>
);
}
function InputWrap({ icon: Icon, children }) {
return (
<div className="add-user-input-wrap">
<Icon size={18} className="add-user-field-icon" aria-hidden="true" />
{children}
</div>
); );
} }
@ -195,32 +204,36 @@ export default function AddEmployeeForm({
} }
const formClass = variant === "inline" const formClass = variant === "inline"
? "org-setup-form employee-add-form" ? "org-setup-form employee-add-form employee-add-form-inline"
: "people-drawer-form employee-add-form"; : "people-drawer-form employee-add-form";
return ( return (
<form className={formClass} onSubmit={handleSubmit} noValidate> <form className={formClass} onSubmit={handleSubmit} noValidate>
<div className={variant === "inline" ? "org-setup-form-shell" : undefined}> <div className={variant === "inline" ? "org-setup-form-shell" : "add-user-form-body"}>
<div className="people-drawer-row"> <div className="add-user-row">
<SetupField <SetupField
label={<RequiredLabel>First name</RequiredLabel>} label={<RequiredLabel>First name</RequiredLabel>}
error={fieldErrors.first_name} error={fieldErrors.first_name}
> >
<input <InputWrap icon={User}>
value={form.first_name} <input
onChange={(event) => updateField("first_name", event.target.value)} value={form.first_name}
placeholder="Rahul" onChange={(event) => updateField("first_name", event.target.value)}
/> placeholder="Rahul"
/>
</InputWrap>
</SetupField> </SetupField>
<SetupField <SetupField
label={<RequiredLabel>Last name</RequiredLabel>} label={<RequiredLabel>Last name</RequiredLabel>}
error={fieldErrors.last_name} error={fieldErrors.last_name}
> >
<input <InputWrap icon={User}>
value={form.last_name} <input
onChange={(event) => updateField("last_name", event.target.value)} value={form.last_name}
placeholder="Sharma" onChange={(event) => updateField("last_name", event.target.value)}
/> placeholder="Sharma"
/>
</InputWrap>
</SetupField> </SetupField>
</div> </div>
@ -229,48 +242,41 @@ export default function AddEmployeeForm({
error={fieldErrors.email_local} error={fieldErrors.email_local}
hint="This will be their login email address on your company domain." hint="This will be their login email address on your company domain."
> >
<div className="org-setup-email-field"> <div className="add-user-email-field">
<input <InputWrap icon={Mail}>
value={form.email_local} <input
onChange={(event) => updateField("email_local", event.target.value)} value={form.email_local}
placeholder="rahul" onChange={(event) => updateField("email_local", event.target.value)}
/> placeholder="rahul"
/>
</InputWrap>
<span>@{companyDomain}</span> <span>@{companyDomain}</span>
</div> </div>
</SetupField> </SetupField>
<SetupField
label={<RequiredLabel>Invitation email</RequiredLabel>}
error={fieldErrors.invitation_email}
hint={`Personal email where aGentic Email sends the setup invite from @${platformDomain}.`}
>
<input
type="email"
value={form.invitation_email}
onChange={(event) => updateField("invitation_email", event.target.value)}
placeholder="rahul@gmail.com"
disabled={!form.send_invitation}
/>
</SetupField>
<SetupField <SetupField
label="Role" label="Role"
hint="Users can access email. User Admins can manage people." hint="User can access email. User Admin can manage people."
> >
<select <InputWrap icon={Info}>
value={form.org_role} <select
onChange={(event) => updateField("org_role", event.target.value)} value={form.org_role}
> onChange={(event) => updateField("org_role", event.target.value)}
<option value="user">User</option> >
<option value="admin">User Admin</option> <option value="user">User</option>
</select> <option value="admin">User Admin</option>
</select>
</InputWrap>
</SetupField> </SetupField>
<fieldset className="people-groups-field"> <fieldset className="add-user-section-field">
<legend>Section</legend> <legend className="add-user-label">
<p className="org-setup-field-hint people-section-hint"> <UsersRound size={18} className="add-user-field-icon add-user-legend-icon" aria-hidden="true" />
Department or team. Group emails are managed separately under Groups. Section
</p> </legend>
<small className="add-user-hint add-user-hint-tight">
Add to a department or team (optional). Group emails are managed under Groups.
</small>
<div className="people-groups-options"> <div className="people-groups-options">
{sectionsLoading ? ( {sectionsLoading ? (
<span className="people-section-loading">Loading sections…</span> <span className="people-section-loading">Loading sections…</span>
@ -311,56 +317,80 @@ export default function AddEmployeeForm({
</button> </button>
</div> </div>
)} )}
{sectionError && <span className="org-setup-field-error" role="alert">{sectionError}</span>} {sectionError && <span className="add-user-error" role="alert">{sectionError}</span>}
</fieldset> </fieldset>
<div className="add-user-section-divider" aria-hidden="true" />
<h3 className="add-user-section-title">Account setup</h3>
<SetupField
label={form.send_invitation ? <RequiredLabel>Invitation email</RequiredLabel> : "Invitation email"}
error={fieldErrors.invitation_email}
hint={`Personal email where aGentic Email sends the setup invite from @${platformDomain}.`}
>
<InputWrap icon={Mail}>
<input
type="email"
value={form.invitation_email}
onChange={(event) => updateField("invitation_email", event.target.value)}
placeholder="rahul@gmail.com"
disabled={!form.send_invitation}
/>
</InputWrap>
</SetupField>
<SetupField <SetupField
label={<RequiredLabel>Initial password</RequiredLabel>} label={<RequiredLabel>Initial password</RequiredLabel>}
error={fieldErrors.password} error={fieldErrors.password}
hint="Must include uppercase, lowercase, and a number." hint="Must include uppercase, lowercase, and a number."
> >
<input <InputWrap icon={Lock}>
type="password" <input
value={form.password} type="password"
onChange={(event) => updateField("password", event.target.value)} value={form.password}
placeholder="Set an initial password" onChange={(event) => updateField("password", event.target.value)}
/> placeholder="Set an initial password"
/>
</InputWrap>
</SetupField> </SetupField>
<label className="people-toggle-row"> <div className="add-user-checkboxes">
<span> <label className="add-user-checkbox">
<strong>Send invitation email</strong> <input
<small>Invite is sent from @{platformDomain} to the personal email above.</small> type="checkbox"
</span> checked={form.send_invitation}
<input onChange={(event) => updateField("send_invitation", event.target.checked)}
type="checkbox" />
checked={form.send_invitation} <span>
onChange={(event) => updateField("send_invitation", event.target.checked)} <strong>Send invitation email</strong>
/> <small>They will receive an activation link to set their password.</small>
</label> </span>
</label>
<label className="people-toggle-row"> <label className="add-user-checkbox">
<span> <input
<strong>Require password change on first login</strong> type="checkbox"
<small>Employee must choose a new password before using aGentic Email.</small> checked={form.force_password_change}
</span> onChange={(event) => updateField("force_password_change", event.target.checked)}
<input />
type="checkbox" <span>
checked={form.force_password_change} <strong>Require password change on first login</strong>
onChange={(event) => updateField("force_password_change", event.target.checked)} <small>User must create a new password when they sign in for the first time.</small>
/> </span>
</label> </label>
</div>
{status.error && <p className="form-error" role="alert">{status.error}</p>} {status.error && <p className="form-error" role="alert">{status.error}</p>}
<footer className={variant === "inline" ? "org-setup-actions" : "people-drawer-actions"}> <footer className={variant === "inline" ? "org-setup-actions" : "add-user-actions"}>
{onCancel && ( {onCancel && (
<button type="button" className="button button-outline" onClick={onCancel}> <button type="button" className="add-user-button add-user-button-cancel" onClick={onCancel}>
Cancel Cancel
</button> </button>
)} )}
<button <button
className="button button-purple org-setup-submit" className="add-user-button add-user-button-submit"
type="submit" type="submit"
disabled={!formValid || status.loading} disabled={!formValid || status.loading}
> >

View file

@ -4,6 +4,7 @@ import {
MoreHorizontal, MoreHorizontal,
Plus, Plus,
Search, Search,
UserPlus,
Upload, Upload,
X, X,
XCircle, XCircle,
@ -65,7 +66,12 @@ function AddUserPanel({ companyDomain, onClose, onCreated }) {
onClick={(event) => event.stopPropagation()} onClick={(event) => event.stopPropagation()}
> >
<header className="people-drawer-head"> <header className="people-drawer-head">
<h2 id="add-user-title">Add user</h2> <div className="people-drawer-title">
<span className="people-drawer-title-icon" aria-hidden="true">
<UserPlus size={18} />
</span>
<h2 id="add-user-title">Add user</h2>
</div>
<button type="button" className="people-drawer-close" aria-label="Close" onClick={onClose}> <button type="button" className="people-drawer-close" aria-label="Close" onClick={onClose}>
<X size={18} /> <X size={18} />
</button> </button>

View file

@ -223,8 +223,7 @@ h1 em { color:var(--coral); font-style:normal; font-weight:500; }
.org-setup-summary { margin:0 0 24px; padding:0; list-style:none; display:grid; gap:10px; } .org-setup-summary { margin:0 0 24px; padding:0; list-style:none; display:grid; gap:10px; }
.org-setup-summary li { display:flex; align-items:center; gap:8px; color:#3d3948; font-size:13px; font-weight:500; } .org-setup-summary li { display:flex; align-items:center; gap:8px; color:#3d3948; font-size:13px; font-weight:500; }
.org-setup-summary svg { color:#7357F6; flex-shrink:0; } .org-setup-summary svg { color:#7357F6; flex-shrink:0; }
.employee-add-form .people-drawer-row { display:grid; grid-template-columns:1fr 1fr; gap:16px; } .employee-add-form .people-groups-options { margin-top:2px; }
.employee-add-form .people-groups-field { margin:0; padding:0; border:0; }
.employee-added-list { margin:0 0 20px; padding:0; max-width:720px; list-style:none; display:grid; gap:8px; } .employee-added-list { margin:0 0 20px; padding:0; max-width:720px; list-style:none; display:grid; gap:8px; }
.employee-added-list li { padding:12px 14px; display:grid; grid-template-columns:1fr auto auto; gap:12px; align-items:center; border:1px solid #ECE9F2; border-radius:10px; background:#FAFAFC; font-size:13px; } .employee-added-list li { padding:12px 14px; display:grid; grid-template-columns:1fr auto auto; gap:12px; align-items:center; border:1px solid #ECE9F2; border-radius:10px; background:#FAFAFC; font-size:13px; }
.employee-added-list strong { color:#1a1628; } .employee-added-list strong { color:#1a1628; }
@ -390,16 +389,50 @@ h1 em { color:var(--coral); font-style:normal; font-weight:500; }
.people-row-menu { width:30px; height:30px; display:grid; place-items:center; border:0; border-radius:8px; background:transparent; color:#8d8792; cursor:pointer; } .people-row-menu { width:30px; height:30px; display:grid; place-items:center; border:0; border-radius:8px; background:transparent; color:#8d8792; cursor:pointer; }
.people-row-menu:hover { background:#F6F2FF; color:#7357F6; } .people-row-menu:hover { background:#F6F2FF; color:#7357F6; }
.people-drawer-scrim { position:fixed; inset:0; z-index:40; display:flex; justify-content:flex-end; background:rgba(20,18,30,0.28); backdrop-filter:blur(2px); } .people-drawer-scrim { position:fixed; inset:0; z-index:40; display:flex; justify-content:flex-end; background:rgba(20,18,30,0.28); backdrop-filter:blur(2px); }
.people-drawer { width:min(440px,100%); height:100%; padding:24px 24px 28px; display:flex; flex-direction:column; background:#fff; box-shadow:-12px 0 40px rgba(20,18,30,0.12); overflow:auto; } .people-drawer { width:min(500px,100%); height:100%; padding:24px; display:flex; flex-direction:column; background:#fff; box-shadow:-12px 0 40px rgba(20,18,30,0.12); overflow:auto; }
.people-drawer-head { margin-bottom:20px; display:flex; align-items:center; justify-content:space-between; gap:12px; } .people-drawer-head { margin-bottom:24px; display:flex; align-items:center; justify-content:space-between; gap:12px; flex-shrink:0; }
.people-drawer-head h2 { margin:0; font:700 22px/1.2 "DM Sans"; color:#1a1628; } .people-drawer-title { display:flex; align-items:center; gap:10px; }
.people-drawer-close { width:34px; height:34px; display:grid; place-items:center; border:0; border-radius:8px; background:#F5F3F8; color:#6f6c7b; cursor:pointer; } .people-drawer-title-icon { width:32px; height:32px; display:grid; place-items:center; border-radius:50%; background:#F6F2FF; color:#7357F6; }
.people-drawer-form { display:grid; gap:16px; } .people-drawer-head h2 { margin:0; font:700 18px/1.2 "DM Sans"; color:#1a1628; }
.people-drawer-close { width:34px; height:34px; display:grid; place-items:center; border:0; border-radius:10px; background:#F4F4F6; color:#6f6c7b; cursor:pointer; }
.people-drawer-form { flex:1; min-height:0; }
.add-user-form-body { display:grid; gap:20px; }
.add-user-row { display:grid; grid-template-columns:1fr 1fr; gap:16px; }
.add-user-field { display:grid; gap:6px; }
.add-user-label { display:inline-flex; align-items:center; gap:6px; font-size:12px; font-weight:600; color:#4a4652; line-height:1.2; }
.add-user-required { margin-left:4px; color:#FF6B54; font-size:12px; font-weight:600; }
.add-user-hint { margin:2px 0 0; color:#6B7280; font-size:11px; font-weight:400; line-height:1.45; }
.add-user-hint-tight { margin-top:0; margin-bottom:10px; }
.add-user-error { color:#c44b38; font-size:11px; font-weight:400; line-height:1.35; }
.add-user-input-wrap { min-height:44px; padding:0 14px; display:flex; align-items:center; gap:10px; border:1px solid #E1DFE7; border-radius:10px; background:#fff; transition:border-color .15s,box-shadow .15s; }
.add-user-input-wrap:focus-within { border-color:#7357F6; box-shadow:0 0 0 3px rgba(115,87,246,0.10); }
.add-user-field.has-error .add-user-input-wrap,.add-user-field.has-error .add-user-email-field .add-user-input-wrap { border-color:#e15b45; box-shadow:none; }
.add-user-field-icon { flex-shrink:0; color:#A1A1AA; }
.add-user-legend-icon { margin-right:2px; }
.add-user-input-wrap input,.add-user-input-wrap select { width:100%; min-width:0; height:42px; padding:0; border:0; outline:0; background:transparent; font-size:13px; color:#1a1628; }
.add-user-input-wrap select { cursor:pointer; }
.add-user-email-field { display:grid; grid-template-columns:1fr auto; gap:10px; align-items:center; }
.add-user-email-field>span { color:#6B7280; font-size:13px; font-weight:600; white-space:nowrap; }
.add-user-section-field { margin:0; padding:0; border:0; display:grid; gap:0; }
.add-user-section-field legend { margin-bottom:6px; padding:0; }
.add-user-section-divider { height:1px; margin:4px 0; background:#EEEEEE; }
.add-user-section-title { margin:0; font-size:12px; font-weight:700; letter-spacing:.02em; text-transform:uppercase; color:#6B7280; }
.add-user-checkboxes { display:grid; gap:14px; }
.add-user-checkbox { display:grid !important; grid-template-columns:18px 1fr; gap:12px; align-items:start; cursor:pointer; }
.add-user-checkbox input { width:18px; height:18px; margin:2px 0 0; accent-color:#7357F6; cursor:pointer; }
.add-user-checkbox strong { display:block; margin-bottom:4px; font-size:13px; font-weight:600; color:#1a1628; line-height:1.3; }
.add-user-checkbox small { display:block; color:#6B7280; font-size:11px; font-weight:400; line-height:1.45; }
.add-user-actions { margin-top:4px; padding-top:4px; display:flex; justify-content:flex-end; align-items:center; gap:10px; }
.add-user-button { min-height:42px; padding:0 18px; border-radius:10px; font-size:13px; font-weight:700; cursor:pointer; transition:filter .15s,transform .15s; }
.add-user-button:disabled { opacity:.5; cursor:not-allowed; }
.add-user-button-cancel { border:1px solid #E1DFE7; background:#fff; color:#4a4652; }
.add-user-button-cancel:hover { background:#FAFAFC; }
.add-user-button-submit { border:0; background:#7357F6; color:#fff; box-shadow:0 4px 14px rgba(115,87,246,0.24); }
.add-user-button-submit:hover:not(:disabled) { filter:brightness(1.04); }
.add-user-button-submit:active:not(:disabled) { transform:scale(0.99); }
.employee-add-form-inline .add-user-row,.employee-add-form-inline .add-user-form-body { max-width:720px; }
.employee-add-form-inline .add-user-input-wrap input,.employee-add-form-inline .add-user-input-wrap select { height:40px; }
.people-drawer-row { display:grid; grid-template-columns:1fr 1fr; gap:12px; } .people-drawer-row { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
.people-drawer-form label,.people-groups-field { display:grid; gap:6px; font-size:12px; font-weight:600; color:#4a4652; }
.people-drawer-form label span,.people-groups-field legend span { color:#e15b45; }
.people-drawer-form input,.people-drawer-form select { height:42px; padding:0 12px; border:1px solid #E1DFE7; border-radius:10px; background:#fff; font-size:13px; color:#1a1628; }
.people-drawer-form small { color:#8d8792; font-weight:500; line-height:1.4; }
.people-email-field { display:grid; grid-template-columns:1fr auto; gap:8px; align-items:center; } .people-email-field { display:grid; grid-template-columns:1fr auto; gap:8px; align-items:center; }
.people-email-field span { color:#8d8792; font-size:13px; font-weight:600; } .people-email-field span { color:#8d8792; font-size:13px; font-weight:600; }
.people-groups-field { margin:0; padding:0; border:0; } .people-groups-field { margin:0; padding:0; border:0; }
@ -421,9 +454,6 @@ h1 em { color:var(--coral); font-style:normal; font-weight:500; }
.change-password-form { display:grid; gap:14px; } .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 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 { padding:10px 12px; border:1px solid #E1DFE7; border-radius:10px; font-size:14px; }
.people-toggle-row { display:flex !important; align-items:flex-start; justify-content:space-between; gap:12px; }
.people-toggle-row strong { display:block; margin-bottom:2px; font-size:13px; color:#1a1628; }
.people-toggle-row input { width:18px; height:18px; margin-top:4px; accent-color:#7357F6; }
.people-drawer-actions { margin-top:8px; display:flex; justify-content:flex-end; gap:10px; } .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 { 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; } .group-avatar.tone-green { background:#E8F7F3; color:#2d8a72; }