Skip to content

Commit

Permalink
merge: main -> mntor-3825
Browse files Browse the repository at this point in the history
  • Loading branch information
flozia committed Jan 8, 2025
2 parents bd0974e + d91f5a5 commit d8f3fa0
Show file tree
Hide file tree
Showing 17 changed files with 517 additions and 34 deletions.
2 changes: 1 addition & 1 deletion config/nimbus.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ features:
value: { "enabled": true }
- channel: production
value: { "enabled": true }
landing-page-redesign-plus-users-experiment:
landing-page-redesign-plus-eligible-experiment:
description: Landing page redesign
variables:
enabled:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export default async function DashboardPage({ params, searchParams }: Props) {
totalNumberOfPerformedScans={profileStats?.total}
isNewUser={isNewUser}
elapsedTimeInDaysSinceInitialScan={elapsedTimeInDaysSinceInitialScan}
experimentData={experimentData}
experimentData={experimentData["Features"]}
activeTab={activeTab}
hasFirstMonitoringScan={hasFirstMonitoringScan}
signInCount={signInCount}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export default async function SettingsPage({ params, searchParams }: Props) {
yearlySubscriptionUrl={`${yearlySubscriptionUrl}&${additionalSubplatParams.toString()}`}
subscriptionBillingAmount={getSubscriptionBillingAmount()}
enabledFeatureFlags={enabledFeatureFlags}
experimentData={experimentData}
experimentData={experimentData["Features"]}
lastScanDate={lastOneRepScan?.created_at}
isMonthlySubscriber={isMonthlySubscriber}
activeTab={activeTab}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export default async function Onboarding({ params, searchParams }: Props) {
breachesTotalCount={allBreachesCount}
stepId={firstSlug === FreeScanSlug ? "enterInfo" : "getStarted"}
previousRoute={previousRoute}
experimentData={experimentData}
experimentData={experimentData["Features"]}
/>
);
}
2 changes: 1 addition & 1 deletion src/app/(proper_react)/(redesign)/(public)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default async function Layout(props: { children: ReactNode }) {
l10n={getL10n()}
countryCode={countryCode}
enabledFeatureFlags={enabledFeatureFlags}
experimentData={experimentData}
experimentData={experimentData["Features"]}
>
{props.children}
</PublicShell>
Expand Down
20 changes: 11 additions & 9 deletions src/app/(proper_react)/(redesign)/(public)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,39 +59,41 @@ export default async function Page({ searchParams }: Props) {
oneRepActivations > monthlySubscribersQuota;
return (
<AccountsMetricsFlowProvider
enabled={experimentData["landing-page-free-scan-cta"].enabled}
enabled={experimentData["Features"]["landing-page-free-scan-cta"].enabled}
metricsFlowParams={{
entrypoint: CONST_URL_MONITOR_LANDING_PAGE_ID,
entrypoint_experiment: "landing-page-free-scan-cta",
entrypoint_variation:
experimentData["landing-page-free-scan-cta"].variant,
experimentData["Features"]["landing-page-free-scan-cta"].variant,
form_type:
experimentData["landing-page-free-scan-cta"].variant ===
experimentData["Features"]["landing-page-free-scan-cta"].variant ===
"ctaWithEmail"
? "email"
: "button",
service: process.env.OAUTH_CLIENT_ID as string,
}}
>
{enabledFeatureFlags.includes("LandingPageRedesign") &&
experimentData["landing-page-redesign-plus-eligible-experiment"]
.enabled &&
experimentData["landing-page-redesign-plus-eligible-experiment"]
.variant === "redesign" ? (
experimentData["Features"][
"landing-page-redesign-plus-eligible-experiment"
].enabled &&
experimentData["Features"][
"landing-page-redesign-plus-eligible-experiment"
].variant === "redesign" ? (
<LandingViewRedesign
eligibleForPremium={eligibleForPremium}
l10n={getL10n()}
countryCode={countryCode}
scanLimitReached={scanLimitReached}
experimentData={experimentData}
experimentData={experimentData["Features"]}
/>
) : (
<LandingView
eligibleForPremium={eligibleForPremium}
l10n={getL10n()}
countryCode={countryCode}
scanLimitReached={scanLimitReached}
experimentData={experimentData}
experimentData={experimentData["Features"]}
/>
)}
</AccountsMetricsFlowProvider>
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/v1/user/welcome-scan/create/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export async function POST(
previewMode: searchParams.get("nimbus_preview") === "true",
});
const optionalInfoExperimentData =
experimentData["welcome-scan-optional-info"];
experimentData["Features"]["welcome-scan-optional-info"];

const profileData: CreateProfileRequest = {
first_name: firstName,
Expand Down
47 changes: 44 additions & 3 deletions src/app/components/client/InputField.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,36 @@
flex-direction: column;
position: relative;

.inputFieldWrapper {
position: relative;
}

.floatingLabel {
background: $color-white;
border-radius: $border-radius-sm;
color: $color-black;
display: inline-block;
// Add a bit more space: The next spacing step is too much.
left: calc($spacing-sm * 1.5);
line-height: 1em;
padding: 0 calc($spacing-xs * 0.5);
pointer-events: none;
position: absolute;
top: 50%;
transform-origin: top left;
transform: translate(-0.05em, -50%) scale(1);
transition: transform 0.2s ease-in-out;
user-select: none;
}

.inputField {
border: 1px solid $color-grey-30;
border-radius: $border-radius-sm;
color: $color-black;
// Add a bit more vertical space: The next spacing step is too much.
padding: calc($spacing-sm * 1.5) $spacing-md;
// Add a bit more space: The next spacing step is too much.
padding: calc($spacing-sm * 1.5);
width: 100%;

&::placeholder,
&.noValue {
color: $color-grey-40;
}
Expand All @@ -39,6 +61,25 @@
}
}

&:has(.floatingLabel) {
::placeholder {
@include visually-hidden;
}
.inputField {
// Move the value string off-center.
padding: calc($spacing-md * 1.25) calc($spacing-sm * 1.5) $spacing-xs;
}
}

&:focus-within,
&:not(:has(:placeholder-shown)) {
.floatingLabel {
color: $color-grey-40;
// Make the floating label visually align with the input value.
transform: translate(-0.05em, -115%) scale(0.75);
}
}

.inputLabel {
font-weight: 600;
margin-bottom: $spacing-sm;
Expand Down
27 changes: 27 additions & 0 deletions src/app/components/client/InputField.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { expect } from "@jest/globals";
import { render } from "@testing-library/react";
import { composeStory } from "@storybook/react";
import { axe } from "jest-axe";
import Meta, {
TextInputFieldEmpty,
TextInputFieldEmptyFloatingLabel,
TextInputFieldFilled,
TextInputFieldFilledFloatingLabel,
} from "./stories/InputField.stories";

describe("InputField", () => {
test.each([
TextInputFieldEmpty,
TextInputFieldFilled,
TextInputFieldEmptyFloatingLabel,
TextInputFieldFilledFloatingLabel,
])("passes the axe accessibility test suite for %s", async (component) => {
const ComposedInput = composeStory(component, Meta);
const { container } = render(<ComposedInput hasFloatingLabel />);
expect(await axe(container)).toHaveNoViolations();
});
});
14 changes: 8 additions & 6 deletions src/app/components/client/InputField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useL10n } from "../../hooks/l10n";
export const InputField = (
props: AriaTextFieldProps & {
iconButton?: ReactNode;
hasFloatingLabel?: boolean;
},
) => {
const { isRequired, label, isInvalid, value, description } = props;
Expand All @@ -29,13 +30,14 @@ export const InputField = (
return (
<div className={styles.input}>
{label && (
<label {...labelProps} className={styles.inputLabel}>
{label}
{
// TODO: Add unit test when changing this code:
/* c8 ignore next */
isRequired ? <span aria-hidden="true">*</span> : ""
<label
{...labelProps}
className={
props.hasFloatingLabel ? styles.floatingLabel : styles.inputLabel
}
>
{label}
{isRequired ? <span aria-hidden="true">*</span> : ""}
</label>
)}
<input
Expand Down
19 changes: 19 additions & 0 deletions src/app/components/client/stories/InputField.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,22 @@ export const DateInputFieldInvalidWithMessage: Story = {
errorMessage: "Select a date",
},
};

export const TextInputFieldEmptyFloatingLabel: Story = {
args: {
label: "Text input floating label",
placeholder: "Type here",
type: "text",
hasFloatingLabel: true,
},
};

export const TextInputFieldFilledFloatingLabel: Story = {
args: {
label: "Text input floating label",
placeholder: "Type here",
type: "text",
value: "Input is filled",
hasFloatingLabel: true,
},
};
11 changes: 4 additions & 7 deletions src/app/functions/server/getExperiments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ export async function getExperiments(params: {
locale: string;
countryCode: string;
previewMode: boolean;
}): Promise<ExperimentData["Features"]> {
}): Promise<ExperimentData> {
if (["local"].includes(process.env.APP_ENV ?? "local")) {
return localExperimentData["Features"];
return localExperimentData;
}

if (!process.env.NIMBUS_SIDECAR_URL) {
Expand Down Expand Up @@ -74,13 +74,10 @@ export async function getExperiments(params: {
experimentData = json;
}

return (
(experimentData as ExperimentData["Features"]) ??
defaultExperimentData["Features"]
);
return (experimentData as ExperimentData) ?? defaultExperimentData;
} catch (ex) {
logger.error("Could not connect to Cirrus", { serverUrl, ex });
captureException(ex);
return defaultExperimentData["Features"];
return defaultExperimentData;
}
}
24 changes: 23 additions & 1 deletion src/app/hooks/useGlean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import EventMetricType from "@mozilla/glean/private/metrics/event";
import type { GleanMetricMap } from "../../telemetry/generated/_map";
import { useSession } from "next-auth/react";
import { hasPremium } from "../functions/universal/user";
import { useExperiments } from "../../contextProviders/experiments";

export const useGlean = () => {
const session = useSession();
const experimentData = useExperiments();
const isPremiumUser = hasPremium(session.data?.user);

const record = useCallback(
Expand All @@ -37,10 +39,30 @@ export const useGlean = () => {
? "Plus"
: "Free";

// Record the `nimbus_*` keys on all events.
// `nimbus_*` is set on every metric, but it's too much work for TypeScript
// to infer that — hence the type assertion.
if (experimentData) {
(data as GleanMetricMap["button"]["click"]).nimbus_user_id =
experimentData["Enrollments"]["nimbus_user_id"];
(data as GleanMetricMap["button"]["click"]).nimbus_app_id =
experimentData["Enrollments"]["app_id"];
(data as GleanMetricMap["button"]["click"]).nimbus_experiment =
experimentData["Enrollments"]["experiment"];
(data as GleanMetricMap["button"]["click"]).nimbus_branch =
experimentData["Enrollments"]["branch"];
(data as GleanMetricMap["button"]["click"]).nimbus_experiment_type =
experimentData["Enrollments"]["experiment_type"];
(data as GleanMetricMap["button"]["click"]).nimbus_is_preview =
experimentData["Enrollments"]["is_preview"].toString();
} else {
console.warn("No experiment data available for Glean");
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
mod[event].record(data as any);
},
[isPremiumUser],
[isPremiumUser, experimentData],
);

return record;
Expand Down
30 changes: 28 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import { GoogleAnalyticsWorkaround } from "./components/client/GoogleAnalyticsWo
import StripeScript from "./components/client/StripeScript";
import { GleanScript } from "./components/client/GleanScript";
import { getExperimentationId } from "./functions/server/getExperimentationId";
import { getExperiments } from "./functions/server/getExperiments";
import { getCountryCode } from "./functions/server/getCountryCode";
import { ExperimentsProvider } from "../contextProviders/experiments";
import * as Sentry from "@sentry/nextjs";

const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });

Expand Down Expand Up @@ -54,6 +58,26 @@ export default async function RootLayout({
const nonce = headers().get("x-nonce") ?? "";
const currentLocale = getLocale(getL10nBundles());
const session = await getServerSession();
const headersList = headers();
const countryCode = getCountryCode(headersList);

// Check for Nimbus preview mode. Note that this requires a full page reload
// to activate: https://nextjs.org/docs/app/api-reference/file-conventions/layout#caveats
const nimbusPreviewMode = headers().get("x-nimbus-preview-mode");
const experimentationId = getExperimentationId(session?.user ?? null);
const experimentData = await getExperiments({
experimentationId: experimentationId,
countryCode: countryCode,
locale: currentLocale,
previewMode: nimbusPreviewMode === "true",
});

const nimbus_user_id = experimentData["Enrollments"].nimbus_user_id;
if (nimbus_user_id !== experimentationId) {
Sentry.captureMessage(
`Nimbus user ID from Cirrus: [${nimbus_user_id}] did not match experimentationId: [${experimentationId}]`,
);
}

return (
<html lang={currentLocale}>
Expand All @@ -64,12 +88,14 @@ export default async function RootLayout({
data-ga4-measurement-id={CONST_GA4_MEASUREMENT_ID}
data-node-env={process.env.NODE_ENV}
>
<SessionProvider session={session}>{children}</SessionProvider>
<ExperimentsProvider experimentData={experimentData}>
<SessionProvider session={session}>{children}</SessionProvider>
</ExperimentsProvider>
</body>
<StripeScript />
<GleanScript
channel={process.env.APP_ENV ?? ""}
experimentationId={getExperimentationId(session?.user ?? null)}
experimentationId={experimentationId}
/>
{headers().get("DNT") !== "1" && (
<GoogleAnalyticsWorkaround
Expand Down
Loading

0 comments on commit d8f3fa0

Please sign in to comment.