Invite people after review, then finish the Rule, with login before inviting and Invite your members on Profile.
This commit is contained in:
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;
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user