diff --git a/client/src/App.js b/client/src/App.js
index fe6b1b1b..163608e7 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -43,9 +43,9 @@ function App() {
-
-
-
+
+
+
-
-
-
+
+
+
diff --git a/client/src/api-calls/steps.js b/client/src/api-calls/steps.js
index 9839ba8f..bf3adb3d 100644
--- a/client/src/api-calls/steps.js
+++ b/client/src/api-calls/steps.js
@@ -12,9 +12,11 @@ const editStep = async ({ id, form, options } = {}) => {
return { error: err };
}
};
-const getStepById = async (id, { options } = {}) => {
+const getStepById = async ({ id, lng, forPublic, options = {} } = {}) => {
try {
- const { data } = await axios.get(`${STEPS_BASE}/${id}`);
+ const { data } = await axios.get(`${STEPS_BASE}/${id}`, {
+ params: { lng, forPublic },
+ });
return { data };
} catch (error) {
const err = handleError(error, options);
diff --git a/client/src/context/steps.js b/client/src/context/steps.js
index a640654f..5bdc089b 100644
--- a/client/src/context/steps.js
+++ b/client/src/context/steps.js
@@ -1,4 +1,5 @@
import { createContext, useState, useContext, useEffect } from 'react';
+import { useLocation } from 'react-router-dom';
import { message } from 'antd';
import { useTranslation } from 'react-i18next';
import { useLanguage } from '../helpers';
@@ -86,12 +87,17 @@ const StepsProvider = ({ children, ...props }) => {
const [justCompletedId, setJustCompletedId] = useState('');
const [loadingSteps, setLoadingSteps] = useState(false);
const [stepsError, setStepsError] = useState('');
+ const location = useLocation();
+
+ const adminPages = location?.pathname?.includes('/admin/');
useEffect(() => {
let mounted = true;
async function fetchData() {
setLoadingSteps(true);
- const { data: newSteps, error } = await Steps.getStepsContent({ lng });
+ const { data: newSteps, error } = await Steps.getStepsContent({
+ lng: adminPages ? 'en' : lng,
+ });
if (mounted) {
let updatedSteps = [];
if (error) {
@@ -115,7 +121,7 @@ const StepsProvider = ({ children, ...props }) => {
return () => {
mounted = false;
};
- }, [lng]);
+ }, [lng, adminPages]);
useEffect(() => {
const updatedSteps = steps.map((step) => {
diff --git a/client/src/pages/Admin/StepForm/index.js b/client/src/pages/Admin/StepForm/index.js
index 93eb90d2..72d054e2 100644
--- a/client/src/pages/Admin/StepForm/index.js
+++ b/client/src/pages/Admin/StepForm/index.js
@@ -75,7 +75,11 @@ const StepForm = () => {
useEffect(() => {
const getStepData = async () => {
setState({ loading: true });
- const { error, data } = await Steps.getStepById(stepId);
+ const { error, data } = await Steps.getStepById({
+ id: stepId,
+ lng: 'en',
+ forPublic: false,
+ });
setState({ loading: false });
if (error) {
diff --git a/client/src/pages/Home/LandingContent.js b/client/src/pages/Home/LandingContent.js
index d872724e..e1424481 100644
--- a/client/src/pages/Home/LandingContent.js
+++ b/client/src/pages/Home/LandingContent.js
@@ -37,7 +37,7 @@ const LandingContent = ({ uniqueSlug }) => {
useEffect(() => {
let mounted = true;
async function fetchData() {
- const hideMessage = message.loading('Loading...');
+ const hideMessage = message.loading('Loading...', 0);
const { data, error } = await LandingPage.getLandingPageContent({
lng,
forPublic: true,
diff --git a/client/src/pages/Step/index.js b/client/src/pages/Step/index.js
index 3f5ff244..304e82a2 100644
--- a/client/src/pages/Step/index.js
+++ b/client/src/pages/Step/index.js
@@ -1,8 +1,9 @@
-import { useState } from 'react';
+import { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import B from '../../constants/benefit-calculator';
import { common } from '../../constants';
import { useTranslation } from 'react-i18next';
+import { message } from 'antd';
import {
TextWithIcon,
@@ -16,8 +17,9 @@ import {
} from '../../components';
import { useSteps } from '../../context/steps';
import { navRoutes as n, types } from '../../constants';
-
+import * as Steps from '../../api-calls/steps';
import * as S from './style';
+import { useLanguage } from '../../helpers';
import { usePublicOrg } from '../../context/public-org';
@@ -25,14 +27,33 @@ const { Row, Col } = Grid;
const { Tips, Checklist } = Cards;
const Step = () => {
+ const [step, setStep] = useState([]);
const [stuck, setStuck] = useState(false);
const { publicOrg } = usePublicOrg();
const params = useParams();
const navigate = useNavigate();
- const { steps: fullSteps, checkUncheckItem, markAsComplete } = useSteps();
+ const { checkUncheckItem, markAsComplete } = useSteps();
const { t } = useTranslation();
+ const { lng } = useLanguage();
+
+ useEffect(() => {
+ const getSteps = async () => {
+ const hideMessage = message.loading('Loading...', 0);
+
+ const { data, error } = await Steps.getStepById({
+ id: params.id,
+ lng,
+ forPublic: true,
+ });
- const step = fullSteps.find((s) => s.id === Number(params.id));
+ hideMessage();
+ if (error) {
+ navigate(n.GENERAL.NOT_FOUND);
+ }
+ setStep(data);
+ };
+ getSteps();
+ }, [lng, navigate, params.id]);
if (!step) {
navigate(n.GENERAL.NOT_FOUND);
@@ -47,7 +68,7 @@ const Step = () => {
};
const checkItem = (itemTitle) => {
- const foundItem = step.checklist.find((c) => c.title === itemTitle);
+ const foundItem = step?.checklist?.find((c) => c.title === itemTitle);
return foundItem?.isChecked;
};
diff --git a/server/src/database/init/migrations.sql b/server/src/database/init/migrations.sql
index 82cf1fdb..b33589eb 100644
--- a/server/src/database/init/migrations.sql
+++ b/server/src/database/init/migrations.sql
@@ -12,4 +12,5 @@ INSERT INTO "migrations" ("name") VALUES
('/20221124140906-remove-backup-email-unique-constraint'),
('/20221201042815-add-deleted-status-for-organisations'),
('/20221214090033-add-languages-tables'),
- ('/20221214173721-convert-json-to-jsonb');
+ ('/20221214173721-convert-json-to-jsonb'),
+ ('/20230125051040-add-all-fields-translated-to-steps-i18n');
diff --git a/server/src/database/migrations/20230125051040-add-all-fields-translated-to-steps-i18n.js b/server/src/database/migrations/20230125051040-add-all-fields-translated-to-steps-i18n.js
new file mode 100644
index 00000000..0f8dab9c
--- /dev/null
+++ b/server/src/database/migrations/20230125051040-add-all-fields-translated-to-steps-i18n.js
@@ -0,0 +1,58 @@
+let dbm;
+let type;
+let seed;
+const fs = require('fs');
+const path = require('path');
+
+let Promise;
+
+/**
+ * We receive the dbmigrate dependency from dbmigrate initially.
+ * This enables us to not have to rely on NODE_PATH.
+ */
+exports.setup = function (options, seedLink) {
+ dbm = options.dbmigrate;
+ type = dbm.dataType;
+ seed = seedLink;
+ Promise = options.Promise;
+};
+
+exports.up = function (db) {
+ const filePath = path.join(
+ __dirname,
+ 'sqls',
+ '20230125051040-add-all-fields-translated-to-steps-i18n-up.sql',
+ );
+ return new Promise(function (resolve, reject) {
+ fs.readFile(filePath, { encoding: 'utf-8' }, function (err, data) {
+ if (err) return reject(err);
+ console.log(`received data: ${data}`);
+
+ resolve(data);
+ });
+ }).then(function (data) {
+ return db.runSql(data);
+ });
+};
+
+exports.down = function (db) {
+ const filePath = path.join(
+ __dirname,
+ 'sqls',
+ '20230125051040-add-all-fields-translated-to-steps-i18n-down.sql',
+ );
+ return new Promise(function (resolve, reject) {
+ fs.readFile(filePath, { encoding: 'utf-8' }, function (err, data) {
+ if (err) return reject(err);
+ console.log(`received data: ${data}`);
+
+ resolve(data);
+ });
+ }).then(function (data) {
+ return db.runSql(data);
+ });
+};
+
+exports._meta = {
+ version: 1,
+};
diff --git a/server/src/database/migrations/sqls/20230125051040-add-all-fields-translated-to-steps-i18n-down.sql b/server/src/database/migrations/sqls/20230125051040-add-all-fields-translated-to-steps-i18n-down.sql
new file mode 100644
index 00000000..4926e4f9
--- /dev/null
+++ b/server/src/database/migrations/sqls/20230125051040-add-all-fields-translated-to-steps-i18n-down.sql
@@ -0,0 +1,6 @@
+BEGIN;
+
+ALTER TABLE "steps_i18n"
+ DROP COLUMN "all_fields_translated";
+
+COMMIT;
\ No newline at end of file
diff --git a/server/src/database/migrations/sqls/20230125051040-add-all-fields-translated-to-steps-i18n-up.sql b/server/src/database/migrations/sqls/20230125051040-add-all-fields-translated-to-steps-i18n-up.sql
new file mode 100644
index 00000000..d5d23796
--- /dev/null
+++ b/server/src/database/migrations/sqls/20230125051040-add-all-fields-translated-to-steps-i18n-up.sql
@@ -0,0 +1,9 @@
+BEGIN;
+
+ALTER TABLE "steps_i18n"
+ ADD COLUMN "all_fields_translated" BOOLEAN DEFAULT FALSE;
+
+UPDATE "steps_i18n"
+ SET "all_fields_translated" = TRUE;
+
+COMMIT;
\ No newline at end of file
diff --git a/server/src/database/models/steps-i18n/schema.sql b/server/src/database/models/steps-i18n/schema.sql
index b985c81a..db582270 100644
--- a/server/src/database/models/steps-i18n/schema.sql
+++ b/server/src/database/models/steps-i18n/schema.sql
@@ -14,6 +14,7 @@ CREATE TABLE "steps_i18n" (
"what_you_will_need_to_know" JSONB[],
"top_tip" TEXT,
"other_tips" TEXT[],
+ "all_fields_translated" BOOLEAN DEFAULT FALSE,
"created_at" TIMESTAMP NOT NULL DEFAULT NOW(),
"updated_at" TIMESTAMP NOT NULL DEFAULT NOW()
);
diff --git a/server/src/modules/step/controllers/get-step.js b/server/src/modules/step/controllers/get-step.js
index c5483aae..c586e735 100644
--- a/server/src/modules/step/controllers/get-step.js
+++ b/server/src/modules/step/controllers/get-step.js
@@ -3,8 +3,12 @@ import * as steps from '../use-cases';
const getStep = async (req, res, next) => {
try {
const { id } = req.params;
- const { lng } = req.query;
- const step = await steps.getStep({ id, lng });
+ const { lng, forPublic } = req.query;
+ const step = await steps.getStep({
+ id,
+ lng,
+ forPublic: forPublic === 'true',
+ });
res.json(step);
} catch (error) {
diff --git a/server/src/modules/step/model/find.js b/server/src/modules/step/model/find.js
index 0a7371f9..f6378149 100644
--- a/server/src/modules/step/model/find.js
+++ b/server/src/modules/step/model/find.js
@@ -8,27 +8,12 @@ const getSteps = async (lng) => {
s.step_order,
s.title AS "s_en.title",
s.description AS "s_en.description",
- s.page_title AS "s_en.page_title",
- s.page_description AS "s_en.page_description",
- s.how_long_does_it_take AS "s_en.how_long_does_it_take",
- s.where_do_you_need_to_go AS "s_en.where_do_you_need_to_go",
- s.things_you_will_need AS "s_en.things_you_will_need",
- s.what_you_will_need_to_know AS "s_en.what_you_will_need_to_know",
- s.top_tip AS "s_en.top_tip",
- s.other_tips AS "s_en.other_tips",
s_i18n.title AS "s_i18n.title",
s_i18n.description AS "s_i18n.description",
- s_i18n.page_title AS "s_i18n.page_title",
- s_i18n.page_description AS "s_i18n.page_description",
- s_i18n.how_long_does_it_take AS "s_i18n.how_long_does_it_take",
- s_i18n.where_do_you_need_to_go AS "s_i18n.where_do_you_need_to_go",
- s_i18n.things_you_will_need AS "s_i18n.things_you_will_need",
- s_i18n.what_you_will_need_to_know AS "s_i18n.what_you_will_need_to_know",
- s_i18n.top_tip AS "s_i18n.top_tip",
- s_i18n.other_tips AS "s_i18n.other_tips",
s.is_optional,
+ s_i18n.all_fields_translated,
s_i18n.language_code
FROM steps AS s
LEFT JOIN steps_i18n AS s_i18n
@@ -67,16 +52,19 @@ const getStepById = async (id, lng) => {
s.top_tip AS "s_en.top_tip",
s.other_tips AS "s_en.other_tips",
- s_i18n.title AS "s_i18n.title",
- s_i18n.description AS "s_i18n.description",
- s_i18n.page_title AS "s_i18n.page_title",
- s_i18n.page_description AS "s_i18n.page_description",
- s_i18n.how_long_does_it_take AS "s_i18n.how_long_does_it_take",
- s_i18n.where_do_you_need_to_go AS "s_i18n.where_do_you_need_to_go",
- s_i18n.things_you_will_need AS "s_i18n.things_you_will_need",
- s_i18n.what_you_will_need_to_know AS "s_i18n.what_you_will_need_to_know",
- s_i18n.top_tip AS "s_i18n.top_tip",
- s_i18n.other_tips AS "s_i18n.other_tips",
+ COALESCE(s_i18n.title, s.title) AS "s_i18n.title",
+ COALESCE(s_i18n.description, s.description) AS "s_i18n.description",
+ COALESCE(s_i18n.page_title, s.page_title) AS "s_i18n.page_title",
+ COALESCE(s_i18n.page_description, s.page_description) AS "s_i18n.page_description",
+ COALESCE(s_i18n.how_long_does_it_take, s.how_long_does_it_take) AS "s_i18n.how_long_does_it_take",
+ COALESCE(s_i18n.where_do_you_need_to_go, s.where_do_you_need_to_go) AS "s_i18n.where_do_you_need_to_go",
+ COALESCE(s_i18n.things_you_will_need, s.things_you_will_need) AS "s_i18n.things_you_will_need",
+ COALESCE(s_i18n.what_you_will_need_to_know, s.what_you_will_need_to_know) AS "s_i18n.what_you_will_need_to_know",
+ COALESCE(s_i18n.top_tip, s.top_tip) AS "s_i18n.top_tip",
+ COALESCE(s_i18n.other_tips, s.other_tips) AS "s_i18n.other_tips",
+
+ s_i18n.language_code,
+ s_i18n.all_fields_translated,
s.is_optional
FROM steps AS s
LEFT JOIN steps_i18n AS s_i18n
diff --git a/server/src/modules/step/use-cases/get-step.js b/server/src/modules/step/use-cases/get-step.js
index 8bdd62ff..228bc2ed 100644
--- a/server/src/modules/step/use-cases/get-step.js
+++ b/server/src/modules/step/use-cases/get-step.js
@@ -1,21 +1,40 @@
import * as Steps from '../model';
-// import translateSteps from '../../../services/translation/translate-steps';
-// import * as Translation from '../../translations/model';
+import translateSteps from '../../../services/translation/translate-steps';
+import * as Translation from '../../translations/model';
-const getStep = async ({ id /* lng */ }) => {
- const step = await Steps.getStepById(id, 'en');
- return step;
- // return step as is for now, because we don't use this route for public
- // const [stepT] = await translateSteps({
- // lng,
- // steps: [step],
- // });
+const getStep = async ({ id, lng, forPublic }) => {
+ const step = await Steps.getStepById(id, lng);
+ if (!forPublic) {
+ return step;
+ }
- // if (!stepT.isTranslated) {
- // Translation.createStepI18n({ stepId: stepT.id, ...stepT });
- // }
+ const [stepT] = await translateSteps({
+ lng,
+ steps: [step],
+ });
- // return stepT;
+ if (!stepT.isTranslated || !step.allFieldsTranslated) {
+ Translation.createStepI18n({
+ stepId: stepT.id,
+ ...stepT,
+ allFieldsTranslated: true,
+ });
+ }
+
+ return {
+ ...stepT,
+ id: step.id,
+ checklist: [
+ stepT.thingsYouWillNeed.map((item) => ({
+ ...item,
+ stage: 'thingsYouWillNeed',
+ })),
+ stepT.whatYouWillNeedToKnow.map((item) => ({
+ ...item,
+ stage: 'whatYouWillNeedToKnow',
+ })),
+ ].flat(),
+ };
};
export default getStep;
diff --git a/server/src/modules/step/use-cases/get-steps.js b/server/src/modules/step/use-cases/get-steps.js
index 79997faf..73511ece 100644
--- a/server/src/modules/step/use-cases/get-steps.js
+++ b/server/src/modules/step/use-cases/get-steps.js
@@ -11,24 +11,20 @@ const getSteps = async ({ lng }) => {
stepsT.forEach((c) => {
if (!c.isTranslated) {
- Translation.createStepI18n({ stepId: c.id, ...c });
+ Translation.createStepI18n({
+ stepId: c.id,
+ title: c.title,
+ description: c.description,
+ languageCode: c.languageCode,
+ allFieldsTranslated: false,
+ });
}
});
- const stepsTWithChecklist = stepsT.map((step, index) => {
+ const stepsTWithChecklist = stepsT.map((step) => {
return {
...step,
- id: index + 1,
- checklist: [
- step.thingsYouWillNeed.map((item) => ({
- ...item,
- stage: 'thingsYouWillNeed',
- })),
- step.whatYouWillNeedToKnow.map((item) => ({
- ...item,
- stage: 'whatYouWillNeedToKnow',
- })),
- ].flat(),
+ id: step.id,
};
});
diff --git a/server/src/modules/translations/model/create.js b/server/src/modules/translations/model/create.js
index 5456fcc5..2451960d 100644
--- a/server/src/modules/translations/model/create.js
+++ b/server/src/modules/translations/model/create.js
@@ -72,6 +72,7 @@ const createStepI18n = async ({
whatYouWillNeedToKnow,
topTip,
otherTips,
+ allFieldsTranslated,
}) => {
const sql = `
INSERT INTO steps_i18n (
@@ -86,7 +87,8 @@ const createStepI18n = async ({
things_you_will_need,
what_you_will_need_to_know,
top_tip,
- other_tips
+ other_tips,
+ all_fields_translated
) VALUES(
$1,
$2,
@@ -99,9 +101,21 @@ const createStepI18n = async ({
$9,
$10::jsonb[],
$11,
- $12
+ $12,
+ $13
)
- ON CONFLICT (step_id, language_code) DO NOTHING
+ ON CONFLICT (step_id, language_code) DO UPDATE SET
+ title = COALESCE($3, steps_i18n.title),
+ description = COALESCE($4, steps_i18n.description),
+ page_title = COALESCE($5, steps_i18n.page_title),
+ page_description = COALESCE($6, steps_i18n.page_description),
+ how_long_does_it_take = COALESCE($7, steps_i18n.how_long_does_it_take),
+ where_do_you_need_to_go = COALESCE($8, steps_i18n.where_do_you_need_to_go),
+ things_you_will_need = COALESCE($9, steps_i18n.things_you_will_need),
+ what_you_will_need_to_know = COALESCE($10, steps_i18n.what_you_will_need_to_know),
+ top_tip = COALESCE($11, steps_i18n.top_tip),
+ other_tips = COALESCE($12, steps_i18n.other_tips),
+ all_fields_translated = COALESCE($13, steps_i18n.all_fields_translated)
RETURNING *
`;
@@ -118,6 +132,7 @@ const createStepI18n = async ({
whatYouWillNeedToKnow,
topTip,
otherTips,
+ allFieldsTranslated,
];
const res = await query(sql, values);
diff --git a/server/src/services/translation/translate-steps.js b/server/src/services/translation/translate-steps.js
index cb88e8b5..0cf8a7a0 100644
--- a/server/src/services/translation/translate-steps.js
+++ b/server/src/services/translation/translate-steps.js
@@ -16,9 +16,10 @@ const translateSteps = async ({ lng, steps }) => {
topTip,
otherTips,
id,
+ allFieldsTranslated,
} = step;
- if (languageCode === lng || lng === 'en') {
+ if ((languageCode === lng && allFieldsTranslated) || lng === 'en') {
return {
...step,
languageCode: lng,
diff --git a/server/src/services/translation/translation-api.js b/server/src/services/translation/translation-api.js
index dbf11b4a..8305380b 100644
--- a/server/src/services/translation/translation-api.js
+++ b/server/src/services/translation/translation-api.js
@@ -30,7 +30,8 @@ const translateText = async ({ text, sourceLang, targetLang }) => {
const translationData = await translateAWS.translateText(params).promise();
return translationData.TranslatedText;
} catch (error) {
- throw new Error('translateText API error :>> ', error);
+ error.extraData = params;
+ throw error;
}
};