diff --git a/api.planx.uk/modules/send/email/index.ts b/api.planx.uk/modules/send/email/index.ts index eabe6b0342..6793f52750 100644 --- a/api.planx.uk/modules/send/email/index.ts +++ b/api.planx.uk/modules/send/email/index.ts @@ -11,7 +11,7 @@ import { export const sendToEmail: SendIntegrationController = async ( req, res, - next, + next ) => { req.setTimeout(120 * 1000); // Temporary bump to address submission timeouts @@ -33,7 +33,7 @@ export const sendToEmail: SendIntegrationController = async ( // Get the applicant email and flow slug associated with the session const { email, flow } = await getSessionEmailDetailsById(sessionId); const flowName = flow.name; - const serviceURL = `${process.env.EDITOR_URL_EXT}/${localAuthority}/${flow.slug}`; + const serviceURL = `${process.env.EDITOR_URL_EXT}/${localAuthority}/${flow.slug}/${sessionId}`; // Prepare email template const config: EmailSubmissionNotifyConfig = { @@ -42,7 +42,7 @@ export const sendToEmail: SendIntegrationController = async ( sessionId, applicantEmail: email, // downloadLink: `${process.env.API_URL_EXT}/download-application-files/${sessionId}?email=${teamSettings.submissionEmail}&localAuthority=${localAuthority}`, - downloadLink: `${serviceURL}/verify-email`, + downloadLink: `${serviceURL}/verify-email`, // redirect to verify email before download ...teamSettings, }, }; @@ -51,7 +51,7 @@ export const sendToEmail: SendIntegrationController = async ( const response = await sendEmail( "submit", teamSettings.submissionEmail, - config, + config ); // Mark session as submitted so that reminder and expiry emails are not triggered @@ -63,7 +63,7 @@ export const sendToEmail: SendIntegrationController = async ( localAuthority, teamSettings.submissionEmail, config, - response, + response ); return res.status(200).send({ diff --git a/api.planx.uk/modules/send/email/service.ts b/api.planx.uk/modules/send/email/service.ts index 67425a1509..38bf6b2b6e 100644 --- a/api.planx.uk/modules/send/email/service.ts +++ b/api.planx.uk/modules/send/email/service.ts @@ -1,9 +1,9 @@ -import { gql } from "graphql-request"; -import { $api } from "../../../client/index.js"; import type { Session, TeamContactSettings, } from "@opensystemslab/planx-core/types"; +import { gql } from "graphql-request"; +import { $api } from "../../../client/index.js"; import type { EmailSubmissionNotifyConfig } from "../../../types.js"; interface GetTeamEmailSettings { @@ -12,9 +12,7 @@ interface GetTeamEmailSettings { }[]; } -export async function getTeamEmailSettings(localAuthority: string) { - const response = await $api.client.request( - gql` +export const TEAM_EMAIL_SETTINGS_QUERY = ` query GetTeamEmailSettings($slug: String) { teams(where: { slug: { _eq: $slug } }) { teamSettings: team_settings { @@ -26,10 +24,14 @@ export async function getTeamEmailSettings(localAuthority: string) { } } } - `, + `; + +export async function getTeamEmailSettings(localAuthority: string) { + const response = await $api.client.request( + gql`${TEAM_EMAIL_SETTINGS_QUERY}`, { slug: localAuthority, - }, + } ); return response?.teams[0]; @@ -50,7 +52,7 @@ export async function getSessionData(sessionId: string) { `, { id: sessionId, - }, + } ); return response?.session?.data; @@ -81,12 +83,12 @@ export async function getSessionEmailDetailsById(sessionId: string) { `, { id: sessionId, - }, + } ); if (!response.session) throw Error( - `Cannot find session ${sessionId} in GetSessionEmailDetails query`, + `Cannot find session ${sessionId} in GetSessionEmailDetails query` ); return response.session; @@ -106,7 +108,7 @@ export async function insertAuditEntry( sendEmailResponse: { message: string; expiryDate?: string; - }, + } ) { const response = await $api.client.request( gql` @@ -136,7 +138,7 @@ export async function insertAuditEntry( recipient: recipient, request: notifyRequest, response: sendEmailResponse, - }, + } ); return response?.application?.id; diff --git a/editor.planx.uk/src/pages/VerifyEmail/VerifyEmail.stories.tsx b/editor.planx.uk/src/pages/VerifyEmail/VerifyEmail.stories.tsx index 69b1520157..f460fc667b 100644 --- a/editor.planx.uk/src/pages/VerifyEmail/VerifyEmail.stories.tsx +++ b/editor.planx.uk/src/pages/VerifyEmail/VerifyEmail.stories.tsx @@ -13,5 +13,5 @@ export default meta; type Story = StoryObj; export const Basic = { - render: () => , + render: () => , }; diff --git a/editor.planx.uk/src/pages/VerifyEmail/VerifyEmail.tsx b/editor.planx.uk/src/pages/VerifyEmail/VerifyEmail.tsx index 253325b1bf..4cedd007b7 100644 --- a/editor.planx.uk/src/pages/VerifyEmail/VerifyEmail.tsx +++ b/editor.planx.uk/src/pages/VerifyEmail/VerifyEmail.tsx @@ -1,30 +1,46 @@ +import { gql, useQuery } from "@apollo/client"; import Box from "@mui/material/Box"; import Container from "@mui/material/Container"; import Typography from "@mui/material/Typography"; import Card from "@planx/components/shared/Preview/Card"; import { CardHeader } from "@planx/components/shared/Preview/CardHeader/CardHeader"; import { useFormik } from "formik"; -import React from "react"; +import React, { useMemo } from "react"; import InputLabel from "ui/public/InputLabel"; import Input from "ui/shared/Input/Input"; import InputRow from "ui/shared/InputRow"; import { object, string } from "yup"; +import { TEAM_EMAIL_SETTINGS_QUERY } from "./../../../../api.planx.uk/modules/send/email/service"; const verifyEmailSchema = object({ email: string().email("Invalid email").required("Email address required"), }); -// interface VerifyEmailProps { -// handleSubmit: (email: string) => void; -// } +interface VerifyEmailProps { + params: Record; +} -// export const VerifyEmail = ({ -// handleSubmit, -// }: VerifyEmailProps): JSX.Element => { +export const VerifyEmail = ({ params }: VerifyEmailProps): JSX.Element => { + const { sessionId, team } = params; + + const { data, loading, error } = useQuery( + gql` + ${TEAM_EMAIL_SETTINGS_QUERY} + `, + { + variables: { slug: team }, + } + ); + + const teamEmail = useMemo( + () => data?.teamSettings.submissionEmail || null, + [data] + ); -export const VerifyEmail = (): JSX.Element => { const handleSubmit = (email: string) => { - console.log("submitting!", email); + console.log("need to validate this email!", email); + const url = `${process.env.API_URL_EXT}/download-application-files/${sessionId}?email=${teamEmail}&localAuthority=${team}`; + window.open(url, "_blank"); }; const formik = useFormik({ diff --git a/editor.planx.uk/src/routes/index.tsx b/editor.planx.uk/src/routes/index.tsx index c45b2fd6a5..b2acee95d7 100644 --- a/editor.planx.uk/src/routes/index.tsx +++ b/editor.planx.uk/src/routes/index.tsx @@ -2,11 +2,11 @@ import { compose, lazy, map, mount, redirect, route, withView } from "navi"; import { loadingView } from "pages/layout/LoadingLayout"; import * as React from "react"; +import { VerifyEmail } from "pages/VerifyEmail/VerifyEmail"; import { client } from "../lib/graphql"; import ErrorPage from "../pages/ErrorPage"; import Login from "../pages/Login"; import { isPreviewOnlyDomain, makeTitle } from "./utils"; -import { VerifyEmail } from "pages/VerifyEmail/VerifyEmail"; type RoutingContext = { currentUser?: any; @@ -23,17 +23,19 @@ const editorRoutes = mount({ ? redirect( req.params.redirectTo ? decodeURIComponent(req.params.redirectTo) - : "/", + : "/" ) : route({ title: makeTitle("Login"), view: , - }), + }) ), - ":team/:flow/verify-email": route({ - title: makeTitle("Verify your email"), - view: , + ":team/:flow/:sessionId/verify-email": map((req) => { + return route({ + title: makeTitle("Verify your email"), + view: , + }); }), "/logout": map((): any => { @@ -47,7 +49,7 @@ const editorRoutes = mount({ document.cookie = cookieString; // remove jwt cookie for planx domains (staging and production) document.cookie = cookieString.concat( - ` domain=.${window.location.host};`, + ` domain=.${window.location.host};` ); // redirect to editor landing page with no jwt cookie set window.location.href = "/"; @@ -59,32 +61,32 @@ const editorRoutes = mount({ ? lazy(() => import("./authenticated")) : redirect(`/login/?redirectTo=${encodeURIComponent(req.originalUrl)}`, { exact: false, - }), + }) ), }); const loadPayRoutes = () => compose( withView(loadingView), - lazy(() => import("./pay")), + lazy(() => import("./pay")) ); const loadPublishedRoutes = () => compose( withView(loadingView), - lazy(() => import("./published")), + lazy(() => import("./published")) ); const loadPreviewRoutes = () => compose( withView(loadingView), - lazy(() => import("./preview")), + lazy(() => import("./preview")) ); const loadDraftRoutes = () => compose( withView(loadingView), - lazy(() => import("./draft")), + lazy(() => import("./draft")) ); export default isPreviewOnlyDomain @@ -100,8 +102,8 @@ export default isPreviewOnlyDomain "/canterbury/find-out-if-you-need-planning-permission/preview": map( async (req) => redirect( - `/canterbury/find-out-if-you-need-planning-permission/published${req?.search}`, - ), + `/canterbury/find-out-if-you-need-planning-permission/published${req?.search}` + ) ), // temporary redirect while Canterbury works with internal IT to update advertised service links "/:team/:flow/preview": loadPreviewRoutes(), // loads current draft flow and latest published external portals, or throws Not Found if any external portal is unpublished "/:team/:flow/draft": loadDraftRoutes(), // loads current draft flow and draft external portals