Start organizational migration
This commit is contained in:
@@ -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";
|
||||
Reference in New Issue
Block a user