Merged PR 1674: Language switching issues

Related work items: #16185, #16630
This commit is contained in:
Robert Bond
2025-06-13 12:55:16 +00:00
11 changed files with 251 additions and 112 deletions
+3
View File
@@ -36,3 +36,6 @@ pages/polling.js
# dev apis to no include
pages/api/endpoint/getaccounts_api.js
# certs
certificates/*
+16
View File
@@ -2144,3 +2144,19 @@ export const createCRMTask = async (formValues) => {
consoleLogger(error);
}
};
export const getPreferredLanguage = async (email) => {
return axios
.get(
BASE_URL +
"/api/endpoint/getpreferredlanguage_api?emailAddress=" +
email
)
.then((res) => {
return res.data;
})
.catch((error) => {
consoleLogger(error);
return error.response;
});
};
+34 -11
View File
@@ -4,6 +4,7 @@ import { useRouter } from "next/router";
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { updateAccount } from "../../actions";
import { setCookie } from "nookies";
const PersonalDetailsComplete = (props) => {
const {
@@ -17,14 +18,39 @@ const PersonalDetailsComplete = (props) => {
const router = useRouter();
const { locale } = router;
// let contactid = props.accountDetails.loggedinUserId;
// let updateBody = props.props.props.form.personalDetailsForm.values;
// let prefLang =
// props.props.props.form.personalDetailsForm.values
// .pinswg_preferredlanguage;
const { loggedinUserId } = props.accountDetails || {};
const formValues = props.props?.props?.form?.personalDetailsForm?.values;
const prefLang = formValues?.pinswg_preferredlanguage;
useEffect(() => {
let contactid = props.accountDetails.loggedinUserId;
let updateBody = props.props.props.form.personalDetailsForm.values;
updateAccount(contactid, updateBody);
}, [
props.accountDetails.loggedinUserId,
props.props.props.form.personalDetailsForm.values,
]);
// let contactid = props.accountDetails.loggedinUserId;
// let updateBody = props.props.props.form.personalDetailsForm.values;
// let prefLang =
// props.props.props.form.personalDetailsForm.values
// .pinswg_preferredlanguage;
if (!loggedinUserId || !formValues) return;
console.log("=============== pref lang", prefLang);
const userLang = prefLang == "846040000" ? "cy" : "en";
setCookie(null, "pedw_locale", userLang, {
path: "/",
maxAge: 60 * 60 * 24 * 365,
});
console.log("Set cookie to:", userLang);
async function doUpdate() {
const response = await updateAccount(loggedinUserId, formValues);
}
doUpdate();
}, [loggedinUserId, formValues, prefLang]);
return (
<>
@@ -33,10 +59,7 @@ const PersonalDetailsComplete = (props) => {
{/* <p className="govuk-body">{t("account:account-complete-para-2")}</p> */}
<p className="govuk-body">
<Link
href={"/myportal?q=" + props.accountDetails.loggedinUserId}
className="govuk-link"
>
<Link href={"/"} className="govuk-link">
{" "}
{t("account:account-complete-return-to-portal-link")}
</Link>
+29 -78
View File
@@ -14,80 +14,9 @@ import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { PrismaClient } from "@prisma/client";
import NextAuth from "next-auth";
import EmailProvider from "next-auth/providers/email";
import { consoleLogger } from "../../../actions";
import { consoleLogger, getPreferredLanguage } from "../../../actions";
const prisma = new PrismaClient();
// For more information on each option (and a full list of options) go to
// https://next-auth.js.org/configuration/options
// export default NextAuth({
// // https://next-auth.js.org/configuration/providers
// providers: [
// EmailProvider({
// // server: {
// // host: process.env.EMAIL_SERVER_HOST,
// // port: process.env.EMAIL_SERVER_PORT,
// // auth: {
// // user: process.env.EMAIL_SERVER_USER,
// // pass: process.env.EMAIL_SERVER_PASSWORD,
// // },
// // },
// // from: process.env.EMAIL_FROM,
// maxAge: 10 * 60, // Magic links are valid for 10 min only
// async sendVerificationRequest({
// identifier: email,
// url,
// provider: { server, from },
// }) {
// const { host } = new URL(url);
// const templateId = "b1b5704b-9bb8-4deb-a75c-d887ca902661";
// const emailAddress = email;
// const personalisation = {
// "emailAddress": email,
// "signInLink": url,
// "linkExpiry": 10 * 60,
// };
// const reference = "PEDW-SIGNIN";
// var NotifyClient =
// require("notifications-node-client").NotifyClient;
// const notifyClient = new NotifyClient(
// process.env.NOTIFY_API_KEY
// );
// await notifyClient
// .sendEmail(templateId, emailAddress, {
// personalisation: personalisation,
// reference: reference,
// // emailReplyToId: emailReplyToId,
// })
// //.then((response) => console.log(response))
// .catch((error) => consoleLogger(err));
// },
// }),
// ],
// adapter: PrismaAdapter(prisma),
// secret: process.env.SECRET,
// session: {
// jwt: true,
// pages: {},
// theme: "dark",
// debug: true,
// },
// pages: {
// signIn: "/auth/signin",
// //signOut: "/auth/signout",
// error: "/auth/error", // Error code passed in query string as ?error=
// verifyRequest: "/auth/verify-request", // (used for check email message)
// newUser: "/account/register", // New users will be directed here on first sign in (leave the property out if not of interest)
// },
// callbacks: {
// session: async (session, user) => {
// //console.log(user);
// return Promise.resolve(session);
// },
// },
// });
const parseUrl = (url) => {
const defaultUrl = new URL(
@@ -169,7 +98,9 @@ const authOptions = (locale, req, res) => {
"&token=" +
searchParams.get("token") +
"&email=" +
encodeURIComponent(email)
encodeURIComponent(email) +
"&locale=" +
effectiveLocale
: url;
let formURL = appendParamsAndPathToNewUrl(url, newURL);
@@ -191,23 +122,43 @@ const authOptions = (locale, req, res) => {
const { host, port, protocol, token, searchParams } =
new URL(url);
const baseDomain = `${protocol}//${host}`;
const templateId = "b1b5704b-9bb8-4deb-a75c-d887ca902661";
const templateIdcy = "0614ce53-cd5f-421f-a1a5-8c8486a9113a";
const emailAddress = email;
const queryLocale = req?.query?.locale || req?.body?.locale;
const cookieLocale = req.cookies["pedw_locale"];
let locale = queryLocale || cookieLocale || "en";
console.log(
"============================================================\n",
"verification url----: " + url + "\n",
"============================================================\n"
);
const prefLang = await getPreferredLanguage(emailAddress);
const registeredPrefLanguage =
prefLang.value.length > 0
? prefLang.value[0].pinswg_preferredlanguage ===
846040000
? "cy"
: "en"
: "en";
const effectiveLocale = registeredPrefLanguage ?? locale;
let newURL =
locale == "cy"
? process.env.CY_API_ROOT +
effectiveLocale == "cy"
? baseDomain +
"/api/auth/callback/email?callbackUrl=" +
encodeURIComponent(process.env.CY_API_ROOT) +
encodeURIComponent(baseDomain + "/cy") +
"&token=" +
searchParams.get("token") +
"&email=" +
encodeURIComponent(email)
encodeURIComponent(email) +
"&locale=" +
effectiveLocale
: url;
let formURL = appendParamsAndPathToNewUrl(url, newURL);
@@ -240,7 +191,7 @@ const authOptions = (locale, req, res) => {
await notifyClient
.sendEmail(
locale == "cy" ? templateIdcy : templateId,
effectiveLocale == "cy" ? templateIdcy : templateId,
emailAddress,
{
personalisation: personalisation,
@@ -0,0 +1,73 @@
/**
* @swagger
* /api/endpoint/getemailaccountcheck_api:
* get:
* tags: [Login]
* description: Returns portal login
* summary: Phase 2
* parameters:
* - name: emailAddress
* in: query
* required: true
* schema:
* type: string
* responses:
* 200:
* description: hello world
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import _ from "lodash";
import {
azureHeaders,
consoleLogger,
getToken,
getPreferredLanguage,
} from "../../../actions";
const WORDKEY = process.env.HASHKEY;
const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
const hashAPIPath = (queryPath) => {
var hashlink = CryptoJS.HmacSHA256(
queryPath,
CryptoJS.enc.Hex.parse(WORDKEY)
);
hashlink = hashlink.toString(CryptoJS.enc.Hex);
return (queryPath.indexOf("?") > -1 ? "&hash=" : "?hash=") + hashlink;
};
export default async function ApiProxy(req, res) {
var emailAddress = req.query.emailAddress;
var token = await getToken();
var queryUrl =
"contacts?$filter=emailaddress1 eq '" +
emailAddress +
"'&$count=true&$select=pinswg_preferredlanguage,contactid";
console.log(queryUrl);
// var queryUrl = "contacts/?$count=true&$select=emailaddress1, contactid";
var apiResponse = _.isEmpty(req.query)
? res.status(400).json()
: typeof emailAddress != "undefined" && emailAddress.length > 0
? axios
.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeaders(token.access_token)
)
.then(({ data }) => {
res.status(200).json(data);
})
.catch((error) => {
consoleLogger(error);
res.status(400).json(error);
})
: res.status(400).json();
return apiResponse;
}
+10
View File
@@ -7,6 +7,8 @@ import CookieBanner from "../../components/cookieBanner";
import Footer from "../../components/footer";
import Header from "../../components/header";
import ServiceBanner from "../../components/servicebanner";
import { destroyCookie, setCookie } from "nookies";
import { useEffect } from "react";
import { getCsrfToken } from "next-auth/react";
@@ -16,6 +18,14 @@ const Error = (props) => {
const { error } = useRouter().query;
useEffect(() => {
window.localStorage.clear(),
destroyCookie(null, "next-auth.csrf-token", { path: "/" }),
destroyCookie(null, "next-auth.callback-url", { path: "/" }),
destroyCookie(null, "pedw_locale", { path: "/" }),
destroyCookie(null, "pinsUser", { path: "/" });
}, []);
const errors = {
Signin: t("auth:auth-error-Signin-label"),
OAuthSignin: t("auth:auth-error-OAuthSignin-label"),
+31
View File
@@ -7,6 +7,8 @@ import CookieBanner from "../../components/cookieBanner";
import Footer from "../../components/footer";
import Header from "../../components/header";
import ServiceBanner from "../../components/servicebanner";
import { getPreferredLanguage } from "../../actions";
import { setCookie } from "nookies";
const SignIn = (props) => {
let { t, lang } = useTranslation();
@@ -25,6 +27,35 @@ const SignIn = (props) => {
const handleSubmit = async (event) => {
event.preventDefault();
setButtonDisabled(true);
const email = event.target.email.value;
try {
const res = await getPreferredLanguage(encodeURIComponent(email));
const data = await res;
const userLang =
data.value.length > 0
? data.value[0].pinswg_preferredlanguage === 846040000
? "cy"
: "en"
: "en";
// Modify callbackUrl hidden input to add/update lang param
const url = new URL(event.target.callbackUrl.value);
url.searchParams.set("locale", userLang);
setCookie(null, "pedw_locale", userLang, {
path: "/",
maxAge: 60 * 60 * 24 * 365,
});
event.target.callbackUrl.value = url.toString();
} catch (error) {
console.error("Failed to get preferred language", error);
}
// Submit form after updating callbackUrl
event.target.submit();
};
+2 -2
View File
@@ -15,7 +15,7 @@ import {
touch,
SubmissionError,
} from "redux-form";
import { connect, submitError } from "react-redux";
import { connect } from "react-redux";
import { createCRMTask, sendEmail } from "../actions";
import { useState } from "react";
@@ -527,7 +527,7 @@ export default connect(
destroyOnUnmount: false,
touchOnBlur: true,
touchOnChange: true,
onSubmitFail: (errors, dispatch, subnittError, props) => {
onSubmitFail: (errors, dispatch, submitError, props) => {
if (errors) {
// Touch all fields with errors so validation messages appear
const errorFields = Object.keys(errors);
+2 -2
View File
@@ -15,7 +15,7 @@ import {
touch,
SubmissionError,
} from "redux-form";
import { connect, submitError } from "react-redux";
import { connect } from "react-redux";
import { createCRMTask, sendEmail } from "../../actions";
import { useState } from "react";
@@ -528,7 +528,7 @@ export default connect(
destroyOnUnmount: false,
touchOnBlur: true,
touchOnChange: true,
onSubmitFail: (errors, dispatch, subnittError, props) => {
onSubmitFail: (errors, dispatch, submitError, props) => {
if (errors) {
// Touch all fields with errors so validation messages appear
const errorFields = Object.keys(errors);
+1 -3
View File
@@ -222,9 +222,7 @@ export const getServerSideProps = wrapper.getServerSideProps(
// Determine preferred locale (cookie overrides)
let preferredLocale =
accountDetails.pinswg_preferredlanguage === "846040000"
? "cy"
: "en";
accountDetails.pinswg_preferredlanguage === 846040000 ? "cy" : "en";
if (cookies.pedw_locale) {
preferredLocale = cookies.pedw_locale;
}
+50 -16
View File
@@ -1,5 +1,8 @@
const next = require("next");
const { createServer } = require("http");
const { createServer: createHttpServer } = require("http");
const { createServer: createHttpsServer } = require("https");
const fs = require("fs");
const path = require("path");
const appInsights = require("applicationinsights");
const initAppInsights = (instrumentationKey) => {
@@ -26,44 +29,75 @@ const startServer = async (config) => {
};
const app = next(serverOptions);
await app.prepare(); // prepare BEFORE starting the server
await app.prepare();
const handleNextRequests = app.getRequestHandler();
const srv = createServer((req, res) => {
const httpsOptions = {
key: fs.readFileSync(
path.join(__dirname, "certificates", "cert-key.pem")
),
cert: fs.readFileSync(path.join(__dirname, "certificates", "cert.pem")),
};
// HTTPS server on port 3000
const httpsServer = createHttpsServer(httpsOptions, (req, res) => {
if (config.useAppInsights) {
appInsights.defaultClient.trackNodeHttpRequest({
request: req,
response: res,
});
}
handleNextRequests(req, res);
});
await new Promise((resolve, reject) => {
srv.on("error", reject);
srv.on("listening", () => resolve());
srv.listen(config.port, config.hostname);
// HTTP server on port 3001 for redirect
const httpServer = createHttpServer((req, res) => {
const host = req.headers.host?.split(":")[0] || "localhost";
res.writeHead(301, {
Location: `https://${host}:3000${req.url}`,
});
res.end();
});
await new Promise((resolve, reject) => {
httpsServer.on("error", reject);
httpsServer.on("listening", () => {
console.log(`HTTPS server listening on port ${config.port}`);
resolve();
});
httpsServer.listen(config.port, config.hostname);
});
await new Promise((resolve, reject) => {
httpServer.on("error", reject);
httpServer.on("listening", () => {
console.log(
`HTTP server listening on port ${config.httpPort} (redirecting to HTTPS)`
);
resolve();
});
httpServer.listen(config.httpPort, config.hostname);
});
// Graceful shutdown handling
process.on("SIGTERM", () => {
console.log("Received SIGTERM, shutting down gracefully...");
srv.close(() => {
console.log("Server closed.");
process.exit(0);
httpsServer.close(() => {
httpServer.close(() => {
console.log("Servers closed.");
process.exit(0);
});
});
});
return app;
};
// App configuration
const startTime = Date.now();
const serverConfig = {
hostname: "0.0.0.0", // Required for Azure App Service
port: parseInt(process.env.PORT, 10) || 3000,
hostname: "0.0.0.0",
port: 3000, // HTTPS port
httpPort: 3001, // HTTP redirect port
env: process.env.APP_ENV || process.env.NODE_ENV || "production",
useAppInsights: initAppInsights(
process.env.NEXT_PUBLIC_APPINSIGHTS_INSTRUMENTATIONKEY
@@ -73,7 +107,7 @@ const serverConfig = {
startServer(serverConfig)
.then(() => {
console.log(
`Server ready on http://${serverConfig.hostname}:${serverConfig.port} [${serverConfig.env}]`
`Server ready on https://${serverConfig.hostname}:${serverConfig.port} [${serverConfig.env}]`
);
if (serverConfig.useAppInsights) {