Start organizational migration

This commit is contained in:
adilallo
2026-02-05 18:21:56 -07:00
parent 69074b23f3
commit db3c0274f6
161 changed files with 145 additions and 145 deletions
@@ -0,0 +1,118 @@
"use client";
import { memo } from "react";
import { useTranslation } from "../../../contexts/MessagesContext";
import { useAnalytics } from "../../../hooks";
import AskOrganizerView from "./AskOrganizer.view";
import type {
AskOrganizerProps,
AskOrganizerVariant,
} from "./AskOrganizer.types";
import { normalizeAskOrganizerVariant } from "../../../../lib/propNormalization";
const VARIANT_STYLES: Record<
"centered" | "left-aligned" | "compact" | "inverse",
{ container: string; buttonContainer: string }
> = {
centered: {
container: "text-center",
buttonContainer: "flex justify-center",
},
"left-aligned": {
container: "text-left",
buttonContainer: "flex justify-start",
},
compact: {
container: "text-center",
buttonContainer: "flex justify-center",
},
inverse: {
container: "text-center",
buttonContainer: "flex justify-center",
},
};
const AskOrganizerContainer = memo<AskOrganizerProps>(
({
title,
subtitle,
description,
buttonText,
buttonHref,
className = "",
variant: variantProp = "centered",
onContactClick,
}) => {
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
const variant = normalizeAskOrganizerVariant(variantProp) as AskOrganizerVariant;
const t = useTranslation();
const defaultButtonText = buttonText ?? t("askOrganizer.buttonText");
const defaultButtonHref = buttonHref ?? t("askOrganizer.buttonHref");
const { trackEvent, trackCustomEvent } = useAnalytics();
const resolvedVariant: AskOrganizerVariant = variant ?? "centered";
const styles = VARIANT_STYLES[resolvedVariant] ?? VARIANT_STYLES.centered;
const sectionPadding =
resolvedVariant === "compact"
? "py-[var(--spacing-scale-016)] px-[var(--spacing-scale-016)] md:py-[var(--spacing-scale-032)] md:px-[var(--spacing-scale-032)]"
: "py-[var(--spacing-scale-032)] px-[var(--spacing-scale-032)] md:py-[var(--spacing-scale-096)] md:px-[var(--spacing-scale-064)]";
const contentGap =
resolvedVariant === "compact"
? "gap-[var(--spacing-scale-020)]"
: "gap-[var(--spacing-scale-040)]";
const labelledBy = title ? "ask-organizer-headline" : undefined;
const handleContactClick = (
event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>,
) => {
trackEvent({
event: "contact_button_click",
category: "engagement",
label: "ask_organizer",
component: "AskOrganizer",
variant: resolvedVariant,
});
trackCustomEvent(
"contact_button_click",
{
component: "AskOrganizer",
variant: resolvedVariant,
buttonText: defaultButtonText,
buttonHref: defaultButtonHref,
},
onContactClick as
| ((_data: Record<string, unknown>) => void)
| undefined,
);
// Preserve existing button behavior (no preventDefault here)
// while still tracking analytics.
return event;
};
return (
<AskOrganizerView
title={title}
subtitle={subtitle}
description={description}
buttonText={defaultButtonText}
buttonHref={defaultButtonHref}
className={className}
sectionPadding={sectionPadding}
contentGap={`${contentGap} ${styles.container}`}
buttonContainerClass={styles.buttonContainer}
variant={resolvedVariant}
labelledBy={labelledBy}
onContactClick={handleContactClick}
/>
);
},
);
AskOrganizerContainer.displayName = "AskOrganizer";
export default AskOrganizerContainer;
@@ -0,0 +1,50 @@
import type React from "react";
export type AskOrganizerVariant =
| "centered"
| "left-aligned"
| "compact"
| "inverse"
| "Centered"
| "Left-Aligned"
| "Compact"
| "Inverse";
export interface AskOrganizerProps {
title?: string;
subtitle?: string;
description?: string;
buttonText?: string;
buttonHref?: string;
className?: string;
/**
* Ask organizer variant. Accepts both lowercase and PascalCase (case-insensitive).
* Figma uses PascalCase, codebase uses lowercase - both are supported.
*/
variant?: AskOrganizerVariant;
onContactClick?: (_data: {
event: string;
component: string;
variant: string;
buttonText: string;
buttonHref: string;
timestamp: string;
}) => void;
}
export interface AskOrganizerViewProps {
title?: string;
subtitle?: string;
description?: string;
buttonText: string;
buttonHref: string;
className: string;
sectionPadding: string;
contentGap: string;
buttonContainerClass: string;
variant: AskOrganizerVariant;
labelledBy?: string;
onContactClick: (
_event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>,
) => void;
}
@@ -0,0 +1,61 @@
"use client";
import { useTranslation } from "../../../contexts/MessagesContext";
import ContentLockup from "../../type/ContentLockup";
import Button from "../../buttons/Button";
import type { AskOrganizerViewProps } from "./AskOrganizer.types";
function AskOrganizerView({
title,
subtitle,
description,
buttonText,
buttonHref,
className,
sectionPadding,
contentGap,
buttonContainerClass,
variant,
labelledBy,
onContactClick,
}: AskOrganizerViewProps) {
const t = useTranslation();
const ariaLabel = t("askOrganizer.ariaLabel");
return (
<section
className={`${sectionPadding} ${className}`}
aria-labelledby={labelledBy}
aria-label={labelledBy ? undefined : ariaLabel}
tabIndex={-1}
>
<div className={`flex flex-col ${contentGap}`}>
{/* Content Lockup */}
<ContentLockup
title={title}
subtitle={subtitle}
description={description}
variant={variant === "inverse" ? "ask-inverse" : "ask"}
alignment={variant === "left-aligned" ? "left" : "center"}
titleId={labelledBy}
/>
{/* Button */}
<div className={buttonContainerClass}>
<Button
href={buttonHref}
size="large"
variant={variant === "inverse" ? "filled-inverse" : "filled"}
className="xl:!px-[var(--spacing-scale-020)] xl:!py-[var(--spacing-scale-012)] xl:!text-[24px] xl:!leading-[28px]"
onClick={onContactClick}
ariaLabel={ariaLabel}
>
{buttonText}
</Button>
</div>
</div>
</section>
);
}
export default AskOrganizerView;
@@ -0,0 +1,2 @@
export { default } from "./AskOrganizer.container";
export * from "./AskOrganizer.types";