Invite people after review, then finish the Rule, with login before inviting and Invite your members on Profile.

This commit is contained in:
adilallo committed 2026-11-06 08:56:13 -07:00
1 parent 88bfccd1e7
commit 70ceb73104
31 files changed
+1388 -401

No files matched your search

@@ -24,6 +24,9 @@ const LoginContainer = memo<LoginProps>(
const backdropRef = useRef<HTMLDivElement>(null);
const previousActiveElementRef = useRef<HTMLElement | null>(null);
const [portalReady, setPortalReady] = useState(() => !usePortal);
const [viewportHeight, setViewportHeight] = useState<number | undefined>(
undefined,
);
// Defer enabling the portal until after the layout commit so we don’t sync-setState
// inside the effect (eslint react-hooks/set-state-in-effect) while still mounting
@@ -36,6 +39,24 @@ const LoginContainer = memo<LoginProps>(
return () => cancelAnimationFrame(id);
}, [usePortal]);
useEffect(() => {
if (!isOpen) return;
const vp = window.visualViewport;
const sync = () => {
const next = vp?.height ?? window.innerHeight;
setViewportHeight(next);
};
sync();
vp?.addEventListener("resize", sync);
vp?.addEventListener("scroll", sync);
window.addEventListener("resize", sync);
return () => {
vp?.removeEventListener("resize", sync);
vp?.removeEventListener("scroll", sync);
window.removeEventListener("resize", sync);
};
}, [isOpen]);
useEffect(() => {
if (!isOpen) return;
if (usePortal && !portalReady) return;
@@ -132,6 +153,7 @@ const LoginContainer = memo<LoginProps>(
portalReady={portalReady}
usePortal={usePortal}
backdropVariant={backdropVariant}
viewportHeight={viewportHeight}
>
{children}
</LoginView>
@@ -33,4 +33,6 @@ export interface LoginViewProps {
portalReady: boolean;
usePortal: boolean;
backdropVariant: LoginBackdropVariant;
/** Visible viewport height on phones so the footer rides above the keyboard. */
viewportHeight?: number;
}
+13 -3
View File
@@ -23,14 +23,21 @@ export function LoginView({
portalReady,
usePortal,
backdropVariant,
viewportHeight,
}: LoginViewProps) {
if (!isOpen) return null;
if (usePortal && !portalReady) return null;
const phoneSheetStyle =
viewportHeight != null
? { height: viewportHeight, maxHeight: viewportHeight }
: undefined;
const content = (
<div
ref={backdropRef}
className={`fixed inset-0 z-[9998] flex flex-col items-center justify-center gap-6 overflow-y-auto px-4 py-8 ${backdropClasses[backdropVariant]}`}
className={`fixed inset-0 z-[9998] flex flex-col items-center gap-6 overflow-y-auto px-4 py-8 max-md:justify-end max-md:overflow-hidden max-md:px-0 max-md:py-0 md:justify-center ${backdropClasses[backdropVariant]}`}
style={phoneSheetStyle}
onPointerDown={(event) => {
if (event.target !== event.currentTarget) return;
onClose();
@@ -43,7 +50,7 @@ export function LoginView({
aria-modal="true"
aria-label={ariaLabel}
aria-labelledby={ariaLabelledBy}
className={`flex min-h-0 max-h-[90vh] w-full max-w-[560px] shrink-0 flex-col overflow-hidden rounded-[var(--radius-500)] bg-[var(--color-surface-default-primary)] shadow-[0px_0px_48px_0px_rgba(0,0,0,0.1)] z-[9999] ${className}`}
className={`flex min-h-0 max-h-[90vh] w-full max-w-[560px] shrink-0 flex-col overflow-hidden rounded-[var(--radius-500)] bg-[var(--color-surface-default-primary)] shadow-[0px_0px_48px_0px_rgba(0,0,0,0.1)] z-[9999] max-md:max-h-full max-md:rounded-b-none ${className}`}
onClick={(e) => e.stopPropagation()}
>
<ModalHeader onClose={onClose} showMoreOptionsButton={false} />
@@ -52,7 +59,10 @@ export function LoginView({
</div>
</div>
{belowCard ? (
<div className="shrink-0" onClick={(e) => e.stopPropagation()}>
<div
className="shrink-0 max-md:w-full max-md:bg-[var(--color-surface-default-primary)] max-md:px-6 max-md:pb-6"
onClick={(e) => e.stopPropagation()}
>
{belowCard}
</div>
) : null}
+51 -35
View File
@@ -44,7 +44,11 @@ function MailIconInline() {
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
export type LoginFormVariant = "default" | "saveProgress" | "keepRule";
export type LoginFormVariant =
| "default"
| "saveProgress"
| "keepRule"
| "invite";
function loginFormHeading(
variant: LoginFormVariant,
@@ -66,6 +70,12 @@ function loginFormHeading(
description: t("keepRuleSubtitle"),
};
}
if (variant === "invite") {
return {
title: t("inviteTitle"),
description: t("inviteSubtitle"),
};
}
return { title: t("title"), description: t("subtitle") };
}
@@ -74,7 +84,11 @@ function shouldAttachCreateFlowDraft(
nextPath: string,
): boolean {
if (variant === "keepRule") return false;
return variant === "saveProgress" || nextPath.includes("syncDraft=1");
return (
variant === "saveProgress" ||
variant === "invite" ||
nextPath.includes("syncDraft=1")
);
}
export type LoginFormProps = {
@@ -266,46 +280,48 @@ export default function LoginForm({
{emailError}
</p>
) : null}
<Button
type="submit"
size="large"
buttonType="filled"
palette="default"
disabled={submitting}
className="w-full !justify-center text-center px-[var(--space-400)] py-[var(--space-300)]"
>
{t("sendMagicLink")}
</Button>
{onDismiss ? (
<div className="flex flex-col gap-4 max-md:sticky max-md:bottom-0 max-md:bg-[var(--color-surface-default-primary)] max-md:pt-2">
<Button
type="button"
type="submit"
size="large"
buttonType="ghost"
buttonType="filled"
palette="default"
disabled={submitting}
onClick={onDismiss}
className="w-full !justify-center text-center px-[var(--space-400)] py-[var(--space-300)]"
>
{t("continueWithoutSaving")}
{t("sendMagicLink")}
</Button>
) : null}
<p className="text-center text-small-paragraph text-[var(--color-content-default-tertiary)]">
{t("legalPrefix")}
<Link
href={tFooter("legal.termsOfServiceHref")}
className="text-[var(--color-content-default-tertiary)] underline decoration-solid underline-offset-2"
>
{tFooter("legal.termsOfService")}
</Link>
{t("legalAnd")}
<Link
href={tFooter("legal.privacyPolicyHref")}
className="text-[var(--color-content-default-tertiary)] underline decoration-solid underline-offset-2"
>
{tFooter("legal.privacyPolicy")}
</Link>
{t("legalSuffix")}
</p>
{onDismiss ? (
<Button
type="button"
size="large"
buttonType="ghost"
palette="default"
disabled={submitting}
onClick={onDismiss}
className="w-full !justify-center text-center px-[var(--space-400)] py-[var(--space-300)]"
>
{t("continueWithoutSaving")}
</Button>
) : null}
<p className="text-center text-small-paragraph text-[var(--color-content-default-tertiary)]">
{t("legalPrefix")}
<Link
href={tFooter("legal.termsOfServiceHref")}
className="text-[var(--color-content-default-tertiary)] underline decoration-solid underline-offset-2"
>
{tFooter("legal.termsOfService")}
</Link>
{t("legalAnd")}
<Link
href={tFooter("legal.privacyPolicyHref")}
className="text-[var(--color-content-default-tertiary)] underline decoration-solid underline-offset-2"
>
{tFooter("legal.privacyPolicy")}
</Link>
{t("legalSuffix")}
</p>
</div>
</form>
) : null}
</div>