diff --git a/src/components/Box/Box.tsx b/src/components/Box/Box.tsx index 2d1ced28..defd6825 100644 --- a/src/components/Box/Box.tsx +++ b/src/components/Box/Box.tsx @@ -38,7 +38,7 @@ const Box: FC = ({ setIsSeen }) => { > = ({ return clean; } + // A lone "/" here yields .../ru/ or .../hy/, which 308-redirects to the + // slashless form and makes the canonical point at a redirect. + const canonicalPath = cleanURL(alternateLink).replace(/^\/(?=$|[?#])/, ''); const originalUrl = - process.env.NEXT_PUBLIC_DOMAIN + localePath + cleanURL(alternateLink); + process.env.NEXT_PUBLIC_DOMAIN + localePath + canonicalPath; const favIcon = `${process.env.NEXT_PUBLIC_DOMAIN}${favIconPath}`; const schema = generateSchema( title, diff --git a/src/components/longevity/EnvironmentSubSection/EnvironmentSubSection.tsx b/src/components/longevity/EnvironmentSubSection/EnvironmentSubSection.tsx index ff95eb61..66093335 100644 --- a/src/components/longevity/EnvironmentSubSection/EnvironmentSubSection.tsx +++ b/src/components/longevity/EnvironmentSubSection/EnvironmentSubSection.tsx @@ -25,7 +25,7 @@ const EnvironmentSubSection: FC = ({ })} >
- {''} + {name} {name}

diff --git a/src/pages/404.tsx b/src/pages/404.tsx index b61eddea..d2670b2d 100644 --- a/src/pages/404.tsx +++ b/src/pages/404.tsx @@ -22,7 +22,7 @@ const Custom404: FC = ({ intl, locale }) => { return ( - + Keepsimple | Error Page diff --git a/src/uxcore/components/NewUpdateModal/NewUpdateModal.tsx b/src/uxcore/components/NewUpdateModal/NewUpdateModal.tsx index c3d8bb8d..492d67ce 100644 --- a/src/uxcore/components/NewUpdateModal/NewUpdateModal.tsx +++ b/src/uxcore/components/NewUpdateModal/NewUpdateModal.tsx @@ -1,11 +1,10 @@ +import Button from '@uxcore/components/Button'; +import Modal from '@uxcore/components/Modal'; import Image from 'next/image'; import Link from 'next/link'; import { FC } from 'react'; import ReactMarkdown from 'react-markdown'; -import Button from '@uxcore/components/Button'; -import Modal from '@uxcore/components/Modal'; - import type { NewUpdateModalProps } from './NewUpdateModal.types'; import styles from './NewUpdateModal.module.scss'; diff --git a/src/uxcore/components/NewUpdateModal/NewUpdateModal.types.ts b/src/uxcore/components/NewUpdateModal/NewUpdateModal.types.ts index 43b73f88..7a4d2f94 100644 --- a/src/uxcore/components/NewUpdateModal/NewUpdateModal.types.ts +++ b/src/uxcore/components/NewUpdateModal/NewUpdateModal.types.ts @@ -12,6 +12,7 @@ export interface NewUpdateData { 'Close button text'?: string; 'Frontend modal visibility'?: boolean; 'Appears after x seconds'?: number; + updatedAt?: string; } export interface NewUpdateModalProps { diff --git a/src/uxcore/components/NewUpdateModal/NewUpdateModalContainer.tsx b/src/uxcore/components/NewUpdateModal/NewUpdateModalContainer.tsx index fa2d68a7..8634b581 100644 --- a/src/uxcore/components/NewUpdateModal/NewUpdateModalContainer.tsx +++ b/src/uxcore/components/NewUpdateModal/NewUpdateModalContainer.tsx @@ -5,6 +5,8 @@ import { FC, useEffect, useState } from 'react'; import NewUpdateModal from './NewUpdateModal'; import type { NewUpdateData } from './NewUpdateModal.types'; +const DISMISSED_KEY = 'uxcoreNewUpdateDismissed'; + const NewUpdateModalContainer: FC = () => { const router = useRouter(); const [data, setData] = useState(null); @@ -22,6 +24,11 @@ const NewUpdateModalContainer: FC = () => { if (cancelled || !res || !res['Frontend modal visibility']) return; + // Dismissal is keyed to the CMS updatedAt: closing hides this update + // for the rest of the session, but a newly published one shows again. + if (sessionStorage.getItem(DISMISSED_KEY) === (res.updatedAt ?? 'seen')) + return; + setData(res); const delaySeconds = Number(res['Appears after x seconds']) || 0; @@ -42,9 +49,18 @@ const NewUpdateModalContainer: FC = () => { }; }, [router.locale]); + const handleClose = () => { + setOpen(false); + try { + sessionStorage.setItem(DISMISSED_KEY, data?.updatedAt ?? 'seen'); + } catch { + // Storage can be unavailable (private mode); closing still works. + } + }; + if (!open || !data) return null; - return setOpen(false)} />; + return ; }; export default NewUpdateModalContainer; diff --git a/src/uxcore/components/SeoGenerator/SeoGenerator.tsx b/src/uxcore/components/SeoGenerator/SeoGenerator.tsx index 6be71ffc..555d3773 100644 --- a/src/uxcore/components/SeoGenerator/SeoGenerator.tsx +++ b/src/uxcore/components/SeoGenerator/SeoGenerator.tsx @@ -181,8 +181,11 @@ const SeoGenerator: FC = ({ return clean; } + // A lone "/" here yields .../ru/ or .../hy/, which 308-redirects to the + // slashless form and makes the canonical point at a redirect. + const canonicalPath = cleanURL(alternateLink).replace(/^\/(?=$|[?#])/, ''); const originalUrl = - process.env.NEXT_PUBLIC_DOMAIN + localePath + cleanURL(alternateLink); + process.env.NEXT_PUBLIC_DOMAIN + localePath + canonicalPath; const metaDescription = isBiasHrView ? hrDescriptionRandom[0] : stripHTML(description); diff --git a/src/uxcore/components/_uxcp/CountryBiasMap/BiasPanel/BiasPanel.tsx b/src/uxcore/components/_uxcp/CountryBiasMap/BiasPanel/BiasPanel.tsx index df77c664..bb23c4f2 100644 --- a/src/uxcore/components/_uxcp/CountryBiasMap/BiasPanel/BiasPanel.tsx +++ b/src/uxcore/components/_uxcp/CountryBiasMap/BiasPanel/BiasPanel.tsx @@ -123,6 +123,7 @@ const BiasPanel = forwardRef(
diff --git a/src/uxcore/components/_uxcp/CountryBiasMap/CountryList/CountryList.tsx b/src/uxcore/components/_uxcp/CountryBiasMap/CountryList/CountryList.tsx index 8f9ffb59..43b1ae9d 100644 --- a/src/uxcore/components/_uxcp/CountryBiasMap/CountryList/CountryList.tsx +++ b/src/uxcore/components/_uxcp/CountryBiasMap/CountryList/CountryList.tsx @@ -120,7 +120,7 @@ const CountryList: FC = ({ [styles.CardSelected]: isSelected, })} > - + {c.name} {Math.round(c.confidence * 100)}% diff --git a/src/uxcore/components/_uxcp/CountryBiasMap/CountryMap/CountryMap.tsx b/src/uxcore/components/_uxcp/CountryBiasMap/CountryMap/CountryMap.tsx index 4ac19102..3a8b2ef8 100644 --- a/src/uxcore/components/_uxcp/CountryBiasMap/CountryMap/CountryMap.tsx +++ b/src/uxcore/components/_uxcp/CountryBiasMap/CountryMap/CountryMap.tsx @@ -434,7 +434,11 @@ const CountryMap: FC = ({ }} >
- + {hoveredData.name}
diff --git a/src/uxcore/components/_uxcp/CountryBiasMap/FlagImage/FlagImage.tsx b/src/uxcore/components/_uxcp/CountryBiasMap/FlagImage/FlagImage.tsx index 19d2d9f9..d67d8106 100644 --- a/src/uxcore/components/_uxcp/CountryBiasMap/FlagImage/FlagImage.tsx +++ b/src/uxcore/components/_uxcp/CountryBiasMap/FlagImage/FlagImage.tsx @@ -5,6 +5,7 @@ import styles from './FlagImage.module.scss'; interface FlagImageProps { countryCode: string; + countryName?: string; size?: number; className?: string; } @@ -20,6 +21,7 @@ const codeToEmoji = (code: string): string => { const FlagImage: FC = ({ countryCode, + countryName, size = 20, className = '', }) => { @@ -58,7 +60,7 @@ const FlagImage: FC = ({ srcSet={`https://flagcdn.com/w${cdnW2x}/${code}.png 2x`} width={w} height={size} - alt="" + alt={`Flag of ${countryName || countryCode.toUpperCase()}`} className={cn(styles.Flag, className)} style={{ width: w, height: size }} loading="lazy"