Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/npm_and_yarn/sentry-5a70b58919
Browse files Browse the repository at this point in the history
  • Loading branch information
flozia authored Feb 5, 2025
2 parents 92ed431 + b5de02b commit 671e332
Show file tree
Hide file tree
Showing 14 changed files with 232 additions and 36 deletions.
12 changes: 12 additions & 0 deletions locales-pending/emails-all.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@
email-footer-reason-subscriber = You’re receiving this automated email as a subscriber of { -brand-mozilla-monitor }. If you received it in error, no action is required. For more information, please visit <support-link>{ -brand-mozilla } Support</support-link>.
email-footer-reason-subscriber-one-time = You’ve received this one-time automated email because you are subscribed to { -brand-monitor-plus }. You won’t receive any further emails like this. For more information, please visit <support-link>{ -brand-mozilla } Support</support-link>.
# Variables:
# $support_link (string) - The URL the user can visit for support, e.g. "https://support.mozilla.org"
email-footer-support-content-plain = Visit our Support Center for help:
{ $support_link }
# Variables:
# $hibp_link (string) - URL to Have I Been Pwned, e.g. "https://haveibeenpwned.com".
email-footer-source-hibp-plain = Breach data provided by { -brand-HIBP }: { $hibp_link }
## Monthly overview email

email-monthly-free-subject = Your monthly { -brand-monitor } report
Expand Down Expand Up @@ -96,3 +104,7 @@ email-monthly-report-hero-free-no-breaches-heading = Great news!
email-monthly-report-hero-free-no-breaches-body = { -brand-monitor } didn’t find any data exposures to be resolved.
email-monthly-report-hero-free-no-breaches-cta = View your dashboard
email-unsubscribe-link = <link_to_unsub>Unsubscribe from this email</link_to_unsub> anytime.
# Variables:
# $unsub_link (string) - URL to the unsubscribe page, e.g. "https://monitor.mozilla.org/unsubscribe-email/...".
email-unsubscribe-link-plain = Unsubscribe from this email anytime:
{ $unsub_link }
7 changes: 7 additions & 0 deletions locales-pending/emails-plus.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ email-plus-expiration-body-part1 = Your { -brand-monitor-plus } subscription end
# Variables:
# $end_date (string) - The localised date the subscription will expire, e.g. "April 2, 1337".
email-plus-expiration-body-part2-styled = To keep your access, sign in and <renewal-link>renew your subscription</renewal-link> before <b>{ $end_date }</b>. If you need help, <support-link>contact our Support team</support-link>.
# Variables:
# $end_date (string) - The localised date the subscription will expire, e.g. "April 2, 1337".
# $renewal_link (string) - The URL the user can visit to renew their subscription, e.g. "https://monitor.mozilla.com/user/plus-expiration/"
email-plus-expiration-body-part2-plain = To keep your access, sign in and renew your subscription before { $end_date }: { $renewal_link }.
# Variables:
# $support_link (string) - The URL the user can visit to contact support, e.g. "https://support.mozilla.org/questions/new/monitor/form"
email-plus-expiration-body-part3-plain = If you need help, contact our Support team: { $support_link }.
# Variables:
# $end_date (string) - The localised date the subscription will expire, e.g. "April 2, 1337".
Expand Down
32 changes: 16 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
"@sentry/utils": "^8.54.0",
"@stripe/stripe-js": "^5.6.0",
"@types/jsdom": "^21.1.7",
"@types/node": "^22.12.0",
"@types/node": "^22.13.0",
"@types/react": "19.0.3",
"@types/react-dom": "19.0.2",
"canvas-confetti": "^1.9.3",
Expand Down Expand Up @@ -119,7 +119,7 @@
"devDependencies": {
"@faker-js/faker": "^9.4.0",
"@google-cloud/bigquery": "^7.9.1",
"@playwright/test": "^1.50.0",
"@playwright/test": "^1.50.1",
"@storybook/addon-a11y": "^8.5.3",
"@storybook/addon-actions": "^8.5.3",
"@storybook/addon-essentials": "^8.5.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ import { isEligibleForPremium } from "../../../../../functions/universal/premium
import { MonthlyActivityFreeEmail } from "../../../../../../emails/templates/monthlyActivityFree/MonthlyActivityFreeEmail";
import { getMonthlyActivityFreeUnsubscribeLink } from "../../../../../../app/functions/cronjobs/unsubscribeLinks";
import { getScanResultsWithBroker } from "../../../../../../db/tables/onerep_scans";
import { UpcomingExpirationEmail } from "../../../../../../emails/templates/upcomingExpiration/UpcomingExpirationEmail";
import {
getUnstyledUpcomingExpirationEmail,
UpcomingExpirationEmail,
} from "../../../../../../emails/templates/upcomingExpiration/UpcomingExpirationEmail";
import { CONST_DAY_MILLISECONDS } from "../../../../../../constants";

async function getAdminSubscriber(): Promise<SubscriberRow | null> {
const session = await getServerSession();
Expand All @@ -66,6 +70,7 @@ async function send(
emailAddress: string,
subject: string,
template: ReactNode,
plaintextVersion?: string,
) {
const subscriber = await getAdminSubscriber();
if (!subscriber) {
Expand All @@ -85,6 +90,7 @@ async function send(
emailAddress,
"Test email: " + subject,
await renderEmail(template),
plaintextVersion,
);
}

Expand Down Expand Up @@ -301,8 +307,13 @@ export async function triggerPlusExpirationEmail(emailAddress: string) {
<UpcomingExpirationEmail
subscriber={sanitizeSubscriberRow(subscriber)}
// Always pretend that the user's account expires in 7 days for the test email:
expirationDate={new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)}
expirationDate={new Date(Date.now() + 7 * CONST_DAY_MILLISECONDS)}
l10n={l10n}
/>,
getUnstyledUpcomingExpirationEmail({
subscriber: sanitizeSubscriberRow(subscriber),
expirationDate: new Date(Date.now() + 7 * CONST_DAY_MILLISECONDS),
l10n: l10n,
}),
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getServerSession } from "../../../../../../functions/server/getServerSe
import { logger } from "../../../../../../functions/server/logging";
import {
isOnerepScanResultForSubscriber,
markOnerepScanResultAsResolved,
setOnerepScanResultManualResolution,
} from "../../../../../../../db/tables/onerep_scans";
import { markQaCustomBrokerAsResolved } from "../../../../../../../db/tables/qa_customs";
import { isAdmin } from "../../../../../utils/auth";
Expand Down Expand Up @@ -56,14 +56,73 @@ export async function POST(
subscriberId: session.user.subscriber.id,
});
if (!isAllowedToResolve) {
return new NextResponse<ResolveScanResultResponse>(
JSON.stringify({ success: false, message: "Unauthorized" }),
{ status: 403 },
);
}

try {
await setOnerepScanResultManualResolution(scanResultId, true);
return new NextResponse<ResolveScanResultResponse>(
JSON.stringify({ success: true }),
{ status: 200 },
);
} catch (e) {
logger.error(e);
return new NextResponse<ResolveScanResultResponse>(
JSON.stringify({
success: false,
message: "Something went wrong, please try again.",
}),
{ status: 500 },
);
}
}

export async function PUT(
req: NextRequest,
): Promise<NextResponse<ResolveScanResultResponse>> {
// const scanResultId = Number.parseInt(params.onerepScanResultId, 10);
// const { resolved } = body;
const session = await getServerSession();

if (!session?.user?.subscriber) {
return new NextResponse<ResolveScanResultResponse>(
JSON.stringify({ success: false, message: "Unauthenticated" }),
{ status: 401 },
);
}

const { resolved, scanResultId } = await req.json();

if (typeof scanResultId !== "number" || Number.isNaN(scanResultId)) {
return new NextResponse<ResolveScanResultResponse>(
JSON.stringify({ success: false, message: "Invalid scan result ID" }),
{ status: 400 },
);
}

if (typeof resolved !== "boolean") {
return new NextResponse<ResolveScanResultResponse>(
JSON.stringify({ success: false, message: "Invalid resolution value" }),
{ status: 400 },
);
}

const isAllowedToResolve = await isOnerepScanResultForSubscriber({
onerepScanResultId: scanResultId,
subscriberId: session.user.subscriber.id,
});
if (!isAllowedToResolve) {
return new NextResponse<ResolveScanResultResponse>(
JSON.stringify({ success: false, message: "Unauthorized" }),
{ status: 403 },
);
}

try {
await markOnerepScanResultAsResolved(scanResultId);
await setOnerepScanResultManualResolution(scanResultId, resolved);
return new NextResponse<ResolveScanResultResponse>(
JSON.stringify({ success: true }),
{ status: 200 },
Expand Down
7 changes: 4 additions & 3 deletions src/db/tables/onerep_scans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,15 +311,16 @@ async function isOnerepScanResultForSubscriber(params: {
return typeof result?.onerep_scan_result_id === "number";
}

async function markOnerepScanResultAsResolved(
async function setOnerepScanResultManualResolution(
onerepScanResultId: number,
resolved: boolean,
): Promise<void> {
logger.info("scan_resolved", {
onerepScanResultId,
});
await knex("onerep_scan_results")
.update({
manually_resolved: true,
manually_resolved: resolved,
// @ts-ignore knex.fn.now() results in it being set to a date,
// even if it's not typed as a JS date object:
updated_at: knex.fn.now(),
Expand Down Expand Up @@ -496,7 +497,7 @@ export {
addOnerepScanResults,
getScansCount,
isOnerepScanResultForSubscriber,
markOnerepScanResultAsResolved,
setOnerepScanResultManualResolution,
getScansCountForProfile,
deleteScansForProfile,
deleteScanResultsForProfile,
Expand Down
43 changes: 43 additions & 0 deletions src/emails/components/EmailFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,46 @@ export const RedesignedEmailFooter = (props: Props) => {
</mj-wrapper>
);
};

export const getUnstyledRedesignedEmailFooter = (props: Props): string => {
const l10n = props.l10n;
const supportLinkUrlObject = new URL(CONST_URL_SUMO_MONITOR_SUPPORT_CENTER);
supportLinkUrlObject.searchParams.set("utm_medium", "product-email");
supportLinkUrlObject.searchParams.set("utm_source", "monitor-product");
supportLinkUrlObject.searchParams.set("utm_campaign", props.utm_campaign);
supportLinkUrlObject.searchParams.set("utm_content", "support-center");

const separator = "-".repeat(30);

return `
${separator}
${l10n.getString("email-footer-support-heading")}
${l10n.getString("email-footer-support-content-plain", { support_link: supportLinkUrlObject.href })}
${separator}
Mozilla Corporation
149 New Montgomery St, 4th Floor, San Francisco, CA 94105
${l10n.getString("email-footer-trigger-transactional")}
${
// We don't have emails yet that send both a plaintext version and an unsubscribe link:
/* c8 ignore next 7 */
typeof props.unsubscribeLink !== "undefined"
? "\n" +
l10n.getString("email-unsubscribe-link-plain", {
unsub_link: props.unsubscribeLink,
}) +
"\n"
: ""
}
${l10n.getString("email-footer-source-hibp-plain", { hibp_link: "https://haveibeenpwned.com" })}
${l10n.getString("terms-of-service")}:
${CONST_URL_TERMS}
${l10n.getString("email-footer-meta-privacy-notice")}:
${CONST_URL_PRIVACY_POLICY}
`;
};
4 changes: 2 additions & 2 deletions src/emails/components/EmailHeader.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
import type { Meta, StoryObj } from "@storybook/react";
import { FC } from "react";
import { StorybookEmailRenderer } from "../StorybookEmailRenderer";
import { EmailHeader, Props } from "./EmailHeader";
import { EmailHeader, getUnstyledEmailHeader, Props } from "./EmailHeader";
import { getL10n } from "../../app/functions/l10n/storybookAndJest";

const meta: Meta<FC<Props>> = {
title: "Emails/Components/Header",
component: (props: Props) => (
<StorybookEmailRenderer plainTextVersion={null}>
<StorybookEmailRenderer plainTextVersion={getUnstyledEmailHeader(props)}>
<mjml>
<mj-body>
<EmailHeader {...props} />
Expand Down
9 changes: 9 additions & 0 deletions src/emails/components/EmailHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,12 @@ export const EmailHeader = (props: Props) => {
</mj-section>
);
};

export const getUnstyledEmailHeader = (props: Props) => {
const l10n = props.l10n;

return `\
${l10n.getString("public-nav-name")}
${"-".repeat(30)}
`;
};
10 changes: 8 additions & 2 deletions src/emails/components/RedesignedEmailFooter.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@
import type { Meta, StoryObj } from "@storybook/react";
import { FC } from "react";
import { StorybookEmailRenderer } from "../StorybookEmailRenderer";
import { RedesignedEmailFooter, Props } from "./EmailFooter";
import {
RedesignedEmailFooter,
Props,
getUnstyledRedesignedEmailFooter,
} from "./EmailFooter";
import { getL10n } from "../../app/functions/l10n/storybookAndJest";

const meta: Meta<FC<Props>> = {
title: "Emails/Components/Redesigned footer",
component: (props: Props) => (
<StorybookEmailRenderer plainTextVersion={null}>
<StorybookEmailRenderer
plainTextVersion={getUnstyledRedesignedEmailFooter(props)}
>
<mjml>
<mj-body>
<RedesignedEmailFooter {...props} />
Expand Down
Loading

0 comments on commit 671e332

Please sign in to comment.