From 93591bced3ec481d7377bac58521a90b86dfbf96 Mon Sep 17 00:00:00 2001 From: MaryWylde Date: Thu, 11 Dec 2025 16:37:21 +0400 Subject: [PATCH 1/3] - Move Table static data to data folder - Mark Score Panel image as unoptimized to avoid auto URL generation - Add className to lock body scroll when modal is open - Fix bias count mismatch in Single Persona --- cypress/e2e/uxcg/uxcg-hy.cy.ts | 7 ++- src/components/ScorePanel/ScorePanel.tsx | 1 + src/components/Table/Table.tsx | 54 +++++++++--------------- src/components/UXCGModal/UXCGModal.tsx | 10 ++--- src/data/table/en.ts | 4 ++ src/data/table/hy.ts | 4 ++ src/data/table/ru.ts | 4 ++ src/layouts/UXCPLayout/UXCPLayout.tsx | 42 +++++++++++------- src/lib/uxcat-helpers.ts | 11 +++-- src/styles/globals.scss | 6 ++- 10 files changed, 79 insertions(+), 64 deletions(-) diff --git a/cypress/e2e/uxcg/uxcg-hy.cy.ts b/cypress/e2e/uxcg/uxcg-hy.cy.ts index 4beff6b..634a0eb 100644 --- a/cypress/e2e/uxcg/uxcg-hy.cy.ts +++ b/cypress/e2e/uxcg/uxcg-hy.cy.ts @@ -9,7 +9,10 @@ describe('UXCG Armenian', () => { }); it('Should open the first question and verify URL and content', () => { - cy.get('[data-cy="open-question"]').first().click(); - cy.url().should('include', '/users-do-not-like-our-customer-support'); + cy.get('[data-cy="open-question"]:visible').first().click(); + cy.url({ timeout: 10000 }).should( + 'include', + '/users-do-not-like-our-customer-support', + ); }); }); diff --git a/src/components/ScorePanel/ScorePanel.tsx b/src/components/ScorePanel/ScorePanel.tsx index 6395010..6e5b263 100644 --- a/src/components/ScorePanel/ScorePanel.tsx +++ b/src/components/ScorePanel/ScorePanel.tsx @@ -108,6 +108,7 @@ const ScorePanel: FC = ({ width={110} height={100} alt={'Enlightened'} + unoptimized /> ) : null diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index 813a9cf..e42d847 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -1,16 +1,19 @@ import { FC, memo, useCallback, useEffect, useRef, useState } from 'react'; import { useRouter } from 'next/router'; import cn from 'classnames'; +import Link from 'next/link'; import Tag, { TTag } from '@components/Tag'; import Button from '@components/Button'; +import Search from './TableSearch'; + import type { QuestionType, TagType } from '@local-types/data'; + import { TRouter } from '@local-types/global'; + import tableIntl from '@data/table'; -import Search from './TableSearch'; import styles from './Table.module.scss'; -import Link from 'next/link'; type TableProps = { data: QuestionType[]; @@ -51,10 +54,22 @@ const Table: FC = ({ setSearchValue, setIsQuestionHovered, }) => { + const router = useRouter(); + const { locale } = router as TRouter; + const tableBodyRef = useRef(null); + const [data, setData] = useState(incomingData); const [displayedItems, setDisplayedItems] = useState(data.length); const [isActive, setIsActive] = useState(false); + const { + allQuestionsButtonLabel, + showMoreText, + showLessText, + noResultsText, + labelText, + } = tableIntl[locale]; + const formatName = (number, title) => { return `#${number}. ${title}`; }; @@ -71,20 +86,6 @@ const Table: FC = ({ return () => clearInterval(interval); }, []); - const router = useRouter(); - const { locale } = router as TRouter; - const tableBodyRef = useRef(null); - const labelEn = 'Select your product stage'; - const labelRu = 'Выберите стадию вашего проекта'; - const labelHy = 'Ընտրեք ձեր պրոդուկտի փուլը'; - - const labelLocales = { - en: labelEn, - ru: labelRu, - hy: labelHy, - }; - const label = labelLocales[locale]; - const sortData = useCallback(dataToSort => { const newData = [...dataToSort]; newData.sort((a: any, b: any) => { @@ -96,19 +97,6 @@ const Table: FC = ({ return newData; }, []); - const showMoreTxts = { - en: 'Show more', - ru: 'Показать больше', - hy: 'Ցույց տալ ավելին', - }; - const showLessTxts = { - en: 'Show less', - ru: 'Показать меньше', - hy: 'Ցույց տալ պակաս', - }; - const showMoreText = showMoreTxts[locale]; - const showLessText = showLessTxts[locale]; - const handleChange = useCallback( (value: string) => { onSearch && onSearch(value); @@ -131,7 +119,6 @@ const Table: FC = ({ const findAnswerIndexByBiasNumber = useCallback( (biasNumber: number, questionIndex: number) => { - // HYTranslation TODO const answers = data[questionIndex]?.attributes?.answers.split('\n'); return answers.findIndex((item: string) => { return item.includes(`{{${biasNumber}}}`); @@ -171,7 +158,6 @@ const Table: FC = ({ } }, [showMoreButton, data.length]); - const { allQuestionsButtonLabel } = tableIntl[locale]; return ( <> {withSearch && ( @@ -200,7 +186,7 @@ const Table: FC = ({ [styles.LabelWrapperAnimation]: isActive, })} > - {label} + {labelText} = ({ > {noResults && (
- No results found + {noResultsText}
)} - {/*HYTranslation TODO*/} {data.slice(0, displayedItems).map(({ attributes }, index) => { const { slug } = attributes; const itemTags = JSON.parse(attributes?.tags || '[]'); @@ -249,7 +234,6 @@ const Table: FC = ({ return (
= ({ if (isOpen) { handleHeightCalc(); - if (modalBodyRef.current) { modalBodyRef.current.scrollTo(0, 0); } setActiveTooltipId(null); document.documentElement.style.overflowY = 'hidden'; + document.body.classList.add('hide-body-move'); } else { document.documentElement.style.overflowY = overflowDefaultValue; + document.body.classList.remove('hide-body-move'); } - // @ts-ignore - document.addEventListener('keydown', handleKeyDown); + document.addEventListener('keydown', handleKeyDown as any); return () => { document.documentElement.style.overflowY = overflowDefaultValue; - // @ts-ignore - document.removeEventListener('keydown', handleKeyDown); + document.body.classList.remove('hide-body-move'); + document.removeEventListener('keydown', handleKeyDown as any); }; }, [questionId, isOpen]); diff --git a/src/data/table/en.ts b/src/data/table/en.ts index 6356b58..edcd4b0 100644 --- a/src/data/table/en.ts +++ b/src/data/table/en.ts @@ -1,6 +1,10 @@ const en = { searchPlaceholder: 'Search by your keywords', allQuestionsButtonLabel: 'All questions', + showMoreText: 'Show more', + showLessText: 'Show less', + noResultsText: ' No results found', + labelText: 'Select your product stage', }; export default en; diff --git a/src/data/table/hy.ts b/src/data/table/hy.ts index 0fb2f7b..fff8511 100644 --- a/src/data/table/hy.ts +++ b/src/data/table/hy.ts @@ -1,6 +1,10 @@ const hy = { searchPlaceholder: 'Որոնել ըստ բանալի բառերի', allQuestionsButtonLabel: 'Բոլոր հարցերը', + showMoreText: 'Ցույց տալ ավելին', + showLessText: 'Ցույց տալ պակաս', + noResultsText: 'Արդյունքներ չեն գտնվել', + labelText: 'Ընտրեք ձեր պրոդուկտի փուլը', }; export default hy; diff --git a/src/data/table/ru.ts b/src/data/table/ru.ts index 4519ae7..954aff0 100644 --- a/src/data/table/ru.ts +++ b/src/data/table/ru.ts @@ -1,6 +1,10 @@ const ru = { searchPlaceholder: 'Поиск по вашим словам', allQuestionsButtonLabel: 'Все вопросы', + showMoreText: 'Показать больше', + showLessText: 'Показать меньше', + noResultsText: 'Результаты не найдены', + labelText: 'Выберите стадию вашего проекта', }; export default ru; diff --git a/src/layouts/UXCPLayout/UXCPLayout.tsx b/src/layouts/UXCPLayout/UXCPLayout.tsx index 9be8518..97d5350 100644 --- a/src/layouts/UXCPLayout/UXCPLayout.tsx +++ b/src/layouts/UXCPLayout/UXCPLayout.tsx @@ -221,10 +221,9 @@ const UXCPLayout: FC = ({ ); } else { biasesToSet = biases.filter(persona => - personaDecisionTable?.some(bias => bias.id === persona._id), + personaDecisionTable?.some(bias => bias._id === persona._id), ); } - setSelectedBiases(biasesToSet); } else { setSelectedBiases(incomingBiases); @@ -376,19 +375,24 @@ const UXCPLayout: FC = ({ }; useEffect(() => { - const newDetails = mergeSelectedBiasesWithDetails( - allLangBiases[locale], - selectedBiases, - selectedBiasesDetails, - ); - setSelectedBiasesDetails(newDetails); - }, [biases, selectedBiases, locale, personaDecisionTable]); - - useEffect(() => { + let newDetails = []; if (personaDecisionTable) { setSelectedBiasesDetails(personaDecisionTable); + } else { + newDetails = mergeSelectedBiasesWithDetails( + allLangBiases[locale], + selectedBiases, + selectedBiasesDetails, + ); + setSelectedBiasesDetails(newDetails); } - }, [personaDecisionTable, isSinglePersona]); + }, [ + biases, + selectedBiases, + locale, + personaDecisionTable, + temporarySavedData, + ]); useEffect(() => { if (accountData) { @@ -430,8 +434,16 @@ const UXCPLayout: FC = ({ useEffect(() => { const referrer = document.referrer; - if (referrer.includes('https://accounts.google.com/')) { - setIsGoogleReferrer(true); + + try { + const url = new URL(referrer); + const provider = url.searchParams.get('provider'); + setIsGoogleReferrer( + url.pathname === '/auth' && + (provider === 'google' || provider === 'discord'), + ); + } catch { + setIsGoogleReferrer(false); } }, []); @@ -563,7 +575,7 @@ const UXCPLayout: FC = ({ temporarySavedData={temporarySavedData} setIsChangesUnsaved={setIsChangesUnsaved} setShow={setShow} - show={show} + show={!showLoginModal && show} isGoogleRefferer={isGoogleRefferer} /> diff --git a/src/lib/uxcat-helpers.ts b/src/lib/uxcat-helpers.ts index 4467e65..30a0fdc 100644 --- a/src/lib/uxcat-helpers.ts +++ b/src/lib/uxcat-helpers.ts @@ -73,12 +73,11 @@ export const getNotifiedAchievements = (achievementList, notificationsData) => !!achievementList && achievementList ?.filter(achievement => { - return ( - !!notificationsData && - notificationsData?.some( - notification => notification.achievementName === achievement?.slug, - ) - ); + return !!notificationsData + ? notificationsData?.some( + notification => notification.achievementName === achievement?.slug, + ) + : null; }) ?.map(achievement => { const matchingNotificationId = notificationsData?.find( diff --git a/src/styles/globals.scss b/src/styles/globals.scss index 0754089..f342c3f 100644 --- a/src/styles/globals.scss +++ b/src/styles/globals.scss @@ -41,7 +41,6 @@ html.scroll-style-articles::-webkit-scrollbar-thumb { body { background: linear-gradient(180deg, #f9fafb 0%, #e8e8e8 100%), #f9fafb; color: #252626; - transition: 0.3s; ol { & li { @@ -51,6 +50,11 @@ body { } } +.hide-body-move { + margin-right: 8px; + background-color: rgba(0, 0, 0, 0.35); +} + .Toastify__toast-container { width: unset !important; cursor: default !important; From 86717a1eb9445c53d99a91b24746555743d1f067 Mon Sep 17 00:00:00 2001 From: MaryWylde Date: Fri, 12 Dec 2025 13:54:28 +0400 Subject: [PATCH 2/3] chore: security patch react 19.0.3 --- lib/getArticleRedirects.ts | 63 -------------------------------------- package.json | 4 +-- yarn.lock | 16 +++++----- 3 files changed, 10 insertions(+), 73 deletions(-) delete mode 100644 lib/getArticleRedirects.ts diff --git a/lib/getArticleRedirects.ts b/lib/getArticleRedirects.ts deleted file mode 100644 index ae117b5..0000000 --- a/lib/getArticleRedirects.ts +++ /dev/null @@ -1,63 +0,0 @@ -const oldSlugs = [ - 'table-of-contents', - 'why-study-management', - 'what-is-a-project', - 'project-artifacts-and-their-importance', - 'project-management-environment', - 'philosophies-methodologies-and-frameworks', - 'software-development-life-cycles', - 'scrum-framework-artifacts-rituals-and-roles', - 'project-approval-and-further-workflow', - 'all-about-user-stories', - 'technical-components-of-the-project', - 'client-dev-company-workflow-birds-eye-view', - 'career-path-of-a-manager-and-a-few-universal-tips', - 'uxscience', - 'uxcgstory', - 'uxeducation', - 'overengineering_and_demo_readiness', - 'uxcgdiy', - 'uiux', - 'awareness-test', -]; - -export const getArticleRedirects = async ( - locale: string, -): Promise> => { - const url = `${process.env.NEXT_PUBLIC_STRAPI}/api/articles?locale=${locale}&pagination[pageSize]=100`; - - try { - const response = await fetch(url); - - if (!response.ok) { - throw new Error(`Failed to fetch Strapi articles (${response.status})`); - } - - const json = await response.json(); - const articles = json?.data || []; - - const map: Record = {}; - - articles.forEach(article => { - const { url: oldUrl, newUrl } = article.attributes || {}; - - const oldSlug = oldUrl - ?.replace(/^\/+/, '') - .replace(/\/+$/, '') - ?.toLowerCase(); - const newSlug = newUrl - ?.replace(/^\/+/, '') - .replace(/\/+$/, '') - ?.toLowerCase(); - - if (oldSlug && newSlug && oldSlugs.includes(oldSlug)) { - map[oldSlug] = `articles/${newSlug}`; - } - }); - - return map; - } catch (err: any) { - console.error('❌ Redirect error:', err.message || err); - return {}; - } -}; diff --git a/package.json b/package.json index cab4982..f7a170d 100644 --- a/package.json +++ b/package.json @@ -43,11 +43,11 @@ "next-auth": "4.23.2", "nodemailer": "^6.10.0", "prettier": "^3.6.2", - "react": "19.0.1", + "react": "19.0.3", "react-beautiful-dnd": "13.1.1", "react-confetti": "6.1.0", "react-confetti-explosion": "2.1.2", - "react-dom": "19.0.1", + "react-dom": "19.0.3", "react-ga4": "1.4.1", "react-icalendar-link": "3.0.2", "react-intersection-observer": "^9.16.0", diff --git a/yarn.lock b/yarn.lock index af043bb..69037b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5528,10 +5528,10 @@ react-display-name@^0.2.4: resolved "https://registry.yarnpkg.com/react-display-name/-/react-display-name-0.2.5.tgz#304c7cbfb59ee40389d436e1a822c17fe27936c6" integrity sha512-I+vcaK9t4+kypiSgaiVWAipqHRXYmZIuAiS8vzFvXHHXVigg/sMKwlRgLy6LH2i3rmP+0Vzfl5lFsFRwF1r3pg== -react-dom@19.0.1: - version "19.0.1" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.1.tgz#b856cbfe38e002b485803d5a0692ee600832edbd" - integrity sha512-3TJg51HSbJiLVYCS6vWwWsyqoS36aGEOCmtLLHxROlSZZ5Bk10xpxHFbrCu4DdqgR85DDc9Vucxqhai3g2xjtA== +react-dom@19.0.3: + version "19.0.3" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.3.tgz#517de15717f686dd6e39488434b6dd18f01ef1fb" + integrity sha512-a7ezLfxibhu6fZBVLwy6WEd3Jn/4H8JYVO8K8GtBfRf1Pl+ox7KFoMCzAGlxLZUXo0t44YZShzhhoDH3yMVdxQ== dependencies: scheduler "^0.25.0" @@ -5701,10 +5701,10 @@ react-use@*: ts-easing "^0.2.0" tslib "^2.1.0" -react@19.0.1: - version "19.0.1" - resolved "https://registry.yarnpkg.com/react/-/react-19.0.1.tgz#0fb9523201af5f8c7aee753a825d1d9d2f9769db" - integrity sha512-nVRaZCuEyvu69sWrkdwjP6QY57C+lY+uMNNMyWUFJb9Z/JlaBOQus7mSMfGYsblv7R691u6SSJA/dX9IRnyyLQ== +react@19.0.3: + version "19.0.3" + resolved "https://registry.yarnpkg.com/react/-/react-19.0.3.tgz#dc803a2316a97d8a1619bf460353c8ccdb7d3a60" + integrity sha512-owzQanTgpB8GF7pVL6mUwZZyhKzFePi9++GkFk54i9PRU0jq+z7v9Mwg7PAZJYCiYl5YwcyQGGq5/PLkesd8nw== readdirp@~3.6.0: version "3.6.0" From b4f8f3d2c7389fce2fcda18da5c68926c10b40ff Mon Sep 17 00:00:00 2001 From: MaryWylde Date: Fri, 12 Dec 2025 13:56:40 +0400 Subject: [PATCH 3/3] chore: update uxeducation redirect and add borders to NPS --- src/components/NPS/NPS.module.scss | 3 ++- src/data/toolHeader/en.ts | 2 +- src/data/toolHeader/hy.ts | 2 +- src/data/toolHeader/ru.ts | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/NPS/NPS.module.scss b/src/components/NPS/NPS.module.scss index 7ec38ae..12de9e7 100644 --- a/src/components/NPS/NPS.module.scss +++ b/src/components/NPS/NPS.module.scss @@ -5,7 +5,8 @@ .nps { position: fixed; bottom: 23px; - background-color: #ffffff; + background-color: #fafafa; + border: 1px solid #c4c4c4; left: 50%; border-radius: 4px; padding: 14px 39px 12px 0; diff --git a/src/data/toolHeader/en.ts b/src/data/toolHeader/en.ts index 9e387b6..bc90a7d 100644 --- a/src/data/toolHeader/en.ts +++ b/src/data/toolHeader/en.ts @@ -14,7 +14,7 @@ const en = { items: [ { title: 'Cognitive biases in education', - link: '/uxeducation', + link: '/articles/uxeducation', }, ], }, diff --git a/src/data/toolHeader/hy.ts b/src/data/toolHeader/hy.ts index 951644f..2227813 100644 --- a/src/data/toolHeader/hy.ts +++ b/src/data/toolHeader/hy.ts @@ -14,7 +14,7 @@ const hy = { items: [ { title: 'Կոգնիտիվ հակումները կրթության մեջ', - link: '/uxeducation', + link: '/articles/uxeducation', }, ], }, diff --git a/src/data/toolHeader/ru.ts b/src/data/toolHeader/ru.ts index 5060ee3..0f34cd0 100644 --- a/src/data/toolHeader/ru.ts +++ b/src/data/toolHeader/ru.ts @@ -14,7 +14,7 @@ const ru = { items: [ { title: 'Использование проекта в образовательной системе', - link: '/ru/uxeducation', + link: '/ru/articles/uxeducation', }, ], },