# Description of Changes Refactor frontend auth to the shared folder and hook it up to both the portal and editor so they share the same system. Also adds various tasks to help run the portal, including `task dev:portal` to spawn the portal with the backend, and `task dev:portal:proxy` to spawn the editor, portal and backend, and a reverse proxy (at localhost:3000) to allow you to use both at once to simulate how this will actually be deployed, allowing you to check whether the seamless transition between the two actually works.
74 lines
2.4 KiB
TypeScript
74 lines
2.4 KiB
TypeScript
import { useEffect, useRef, useState, type ReactNode } from "react";
|
|
import styles from "@shared/auth/ui/AuthShell.module.css";
|
|
|
|
export interface AuthShellProps {
|
|
children: ReactNode;
|
|
/** Optional panel shown beside the form on wide/tall viewports (the carousel). */
|
|
rightPanel?: ReactNode;
|
|
/** Optional fixed footer slot (the editor passes its legal/cookie footer). */
|
|
footer?: ReactNode;
|
|
}
|
|
|
|
/**
|
|
* The login card shell shared by the editor and the portal: a centered card
|
|
* that expands to two columns (form + right panel) on wide/tall viewports and
|
|
* collapses to a single column otherwise. Purely presentational - callers
|
|
* provide the form (children), the right panel, and an optional footer.
|
|
*/
|
|
export function AuthShell({ children, rightPanel, footer }: AuthShellProps) {
|
|
const cardRef = useRef<HTMLDivElement | null>(null);
|
|
const [hideRightPanel, setHideRightPanel] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const update = () => {
|
|
// Use viewport to avoid hysteresis when the card is already single-column.
|
|
const viewportWidth = window.innerWidth;
|
|
const viewportHeight = window.innerHeight;
|
|
const cardWidthIfTwoCols = Math.min(1180, viewportWidth * 0.96); // min(73.75rem, 96vw)
|
|
const columnWidth = cardWidthIfTwoCols / 2;
|
|
const tooNarrow = columnWidth < 470;
|
|
const tooShort = viewportHeight < 740;
|
|
setHideRightPanel(tooNarrow || tooShort);
|
|
};
|
|
update();
|
|
window.addEventListener("resize", update);
|
|
window.addEventListener("orientationchange", update);
|
|
return () => {
|
|
window.removeEventListener("resize", update);
|
|
window.removeEventListener("orientationchange", update);
|
|
};
|
|
}, []);
|
|
|
|
const showRightPanel = Boolean(rightPanel) && !hideRightPanel;
|
|
|
|
return (
|
|
<div className={styles.authContainer}>
|
|
<div
|
|
ref={cardRef}
|
|
className={`${styles.authCard} ${showRightPanel ? styles.authCardTwoColumns : ""}`}
|
|
>
|
|
<div className={styles.authLeftPanel}>
|
|
<div className={styles.authContent}>{children}</div>
|
|
</div>
|
|
{showRightPanel && rightPanel}
|
|
</div>
|
|
{footer && (
|
|
<div
|
|
style={{
|
|
position: "fixed",
|
|
bottom: 0,
|
|
left: 0,
|
|
right: 0,
|
|
width: "100%",
|
|
zIndex: 10,
|
|
}}
|
|
>
|
|
{footer}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default AuthShell;
|