Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 89 additions & 25 deletions components/CustomHead.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,53 @@
/** biome-ignore-all lint/security/noDangerouslySetInnerHtml: Used as expected */
import Head from 'next/head';
import { useRouter } from 'next/router';
import { type Messages, useTranslations } from 'next-intl';
import type { ReactElement } from 'react';
import { NON_LOCALIZED_STRING } from '@/constants/localization';
import METADATA, { type IMetadata } from '@/constants/metadata';
import { isLocal } from '@/utils/links';

export type MetadataLocaleKey = keyof Messages['metadata'];
interface Props {
title?: string;
localeKey?: MetadataLocaleKey;
metadata?: IMetadata;
structuredData?: Array<string>;
}

export default function CustomHead(props: Props): ReactElement {
const router = useRouter();
const { title, metadata, structuredData } = props;
const pageTitle =
title && title.length > 0 ? `${title} - Session Private Messenger` : METADATA.TITLE;
const t = useTranslations('metadata');
const tFeature = useTranslations('feature');

const { localeKey, metadata, structuredData } = props;

// TODO: we can probably use the locale defaults as the initialized vars
let title = '';
let description = '';

const localeArgs = {
appName: NON_LOCALIZED_STRING.appName,
appNamePossessive: NON_LOCALIZED_STRING.appNamePossessive,
featureCommunity: tFeature('community'),
};

if (localeKey) {
const titleKey = `${localeKey}.title` as const;
const pageTitle = t.has(titleKey) ? t(titleKey, localeArgs) : undefined;

title =
pageTitle && localeKey !== 'default'
? t('default.titleLayout', { ...localeArgs, title: pageTitle })
: t('default.title', localeArgs);

const descriptionKey = `${localeKey}.description` as const;
description = t(t.has(descriptionKey) ? descriptionKey : 'default.description', localeArgs);
} else {
title = props.title || t('default.title', localeArgs);
description = metadata?.DESCRIPTION || t('default.description', localeArgs);
}

const pageUrl = `${METADATA.HOST_URL}${router.asPath}`;
const imageALT = metadata?.OG_IMAGE?.ALT ?? METADATA.OG_IMAGE.ALT;
let imageWidth = metadata?.OG_IMAGE?.WIDTH ?? METADATA.OG_IMAGE.WIDTH;
Expand All @@ -37,6 +69,7 @@ export default function CustomHead(props: Props): ReactElement {
}

const tags = metadata?.TAGS ? metadata?.TAGS : METADATA.TAGS;

const renderTags = (() => {
const keywords = <meta key="keywords" name="keywords" content={tags.join(' ')} />;
if (metadata?.TYPE !== 'article') return keywords;
Expand All @@ -63,6 +96,7 @@ export default function CustomHead(props: Props): ReactElement {
</>
);
})();

const renderLdJSON = (() => {
const ldjson = `{
"@context": "https://schema.org",
Expand All @@ -72,27 +106,27 @@ export default function CustomHead(props: Props): ReactElement {
"@id": "${METADATA.HOST_URL}/#website",
"url": "${pageUrl}",
"name": "${METADATA.SITE_NAME}",
"description": "${METADATA.DESCRIPTION}"
"description": "${t('default.description', localeArgs)}"
},
{
"@type": "ImageObject",
"@id": "${pageUrl}#primaryimage",
"url": "${imageUrl}",
"width": "${imageWidth}",
"height": "${imageHeight}",
"height": "${imageHeight}"
},
{
"@type": "WebPage",
"@id": "${pageUrl}#webpage",
"url": "${pageUrl}",
"inLanguage": "${METADATA.LOCALE}",
"name": "${pageTitle}",
"name": "${title}",
"isPartOf": { "@id": "${METADATA.HOST_URL}/#website" },
"primaryImageOfPage": {
"@id": "${pageUrl}#primaryimage"
},
"datePublished": "${metadata?.PUBLISHED_TIME ?? ''}",
"description": "${METADATA.DESCRIPTION}"
"description": "${description}"
}
]
}`;
Expand All @@ -104,20 +138,47 @@ export default function CustomHead(props: Props): ReactElement {
/>
);
})();

// Generate localized page variants if localeKey is defined
const renderLocalizedVariants = (() => {
if (!localeKey || !router.locales) return null;

const currentPathWithoutLocale = router.asPath.replace(`/${router.locale}`, '') || '/';

return router.locales.map((locale) => {
const localizedPath =
locale === router.defaultLocale
? currentPathWithoutLocale
: `/${locale}${currentPathWithoutLocale}`;

const href = `${METADATA.HOST_URL}${localizedPath}`;

return <link key={`hreflang-${locale}`} rel="alternate" hrefLang={locale} href={href} />;
});
})();

// Add x-default hreflang for the default locale
const renderDefaultHreflang = (() => {
if (!localeKey || !router.defaultLocale) return null;

const currentPathWithoutLocale = router.asPath.replace(`/${router.locale}`, '') || '/';
const defaultHref = `${METADATA.HOST_URL}${currentPathWithoutLocale}`;

return (
<link key="hreflang-x-default" rel="alternate" hrefLang="x-default" href={defaultHref} />
);
})();

return (
<Head>
<title key={pageTitle}>{pageTitle}</title>
<title key={title}>{title}</title>
<meta key="utf-8" charSet="utf-8" />
<meta
key="viewport"
name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover"
/>
<meta
key="description"
name="description"
content={metadata?.DESCRIPTION ?? METADATA.DESCRIPTION}
/>
<meta key="description" name="description" content={description} />
<meta
key="robots"
name="robots"
Expand All @@ -129,13 +190,9 @@ export default function CustomHead(props: Props): ReactElement {
content="index,follow,max-snippet:-1,max-image-preview:large,max-video-preview:-1"
/>
<meta key="og:url" property="og:url" content={pageUrl} />
<meta key="og:title" property="og:title" content={pageTitle} />
<meta key="og:title" property="og:title" content={title} />
<meta key="og:type" property="og:type" content={metadata?.TYPE ?? METADATA.OG_TYPE} />
<meta
key="og:description"
property="og:description"
content={metadata?.DESCRIPTION ?? METADATA.DESCRIPTION}
/>
<meta key="og:description" property="og:description" content={description} />
<meta key="og:image" property="og:image" content={imageUrl} />
<meta key="og:image:secure_url" property="og:image:secure_url" content={imageUrl}></meta>
<meta key="og:image:alt" property="og:image:alt" content={imageALT} />
Expand All @@ -144,12 +201,8 @@ export default function CustomHead(props: Props): ReactElement {
<meta key="og:locale" property="og:locale" content={METADATA.LOCALE} />
<meta key="og:site_name" property="og:site_name" content={METADATA.SITE_NAME} />
<meta key="twitter:card" name="twitter:card" content="summary_large_image" />
<meta key="twitter:title" name="twitter:title" content={pageTitle} />
<meta
key="twitter:description"
name="twitter:description"
content={metadata?.DESCRIPTION ?? METADATA.DESCRIPTION}
/>
<meta key="twitter:title" name="twitter:title" content={title} />
<meta key="twitter:description" name="twitter:description" content={description} />
<meta key="twitter:image" name="twitter:image" content={imageUrl} />
<meta key="twitter:site" name="twitter:site" content={METADATA.HOST_URL} />
<meta key="twitter:creator" name="twitter:creator" content={METADATA.TWITTER_CREATOR} />
Expand All @@ -159,6 +212,8 @@ export default function CustomHead(props: Props): ReactElement {
name="msapplication-TileColor"
content={METADATA.MSAPPLICATION_TILECOLOR}
/>
{renderLocalizedVariants}
{renderDefaultHreflang}
<meta key="theme-color" name="theme-color" content={METADATA.THEME_COLOR} />
{renderTags}
<link key="canonical" rel="canonical" href={`${METADATA.HOST_URL}${router.asPath}`} />
Expand Down Expand Up @@ -218,6 +273,15 @@ export default function CustomHead(props: Props): ReactElement {
}}
/>
))}
{process.env.NEXT_PUBLIC_TRANSLATION_MODE === 'true' ? (
<>
<script type="text/javascript">
{`var _jipt = [];
_jipt.push(['project', 'getsession-website']);`}
</script>
<script type="text/javascript" src="//cdn.crowdin.com/jipt/jipt.js"></script>
</>
) : null}
</Head>
);
}
81 changes: 52 additions & 29 deletions components/RedirectPage.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,60 @@
import classNames from 'classnames';
import { useRouter } from 'next/router';
import { useTranslations } from 'next-intl';
import { useEffect } from 'react';
import Container from '@/components/Container';
import type { MetadataLocaleKey } from '@/components/CustomHead';
import Layout from '@/components/ui/Layout';
import type { IMetadata } from '@/constants/metadata';

export default function RedirectPage() {
type RedirectPageProps = {
redirectUrl: string;
localeKey?: MetadataLocaleKey;
metadata?: IMetadata;
};

export default function RedirectPage({ redirectUrl, localeKey, metadata }: RedirectPageProps) {
const router = useRouter();
const t = useTranslations('redirect');

useEffect(() => {
router.push(redirectUrl);
}, [router, redirectUrl]);

return (
<section>
<Container
heights={{
small: '100vh - 108px',
medium: '50vh',
large: '40vh',
huge: '50vh',
enormous: '50vh',
}}
classes={classNames(
'py-16 px-2 mx-auto text-center',
'md:flex md:flex-col md:justify-center md:items-center'
)}
>
<h1 className={classNames('mb-8 font-bold text-5xl text-primary-dark')}>Redirecting...</h1>
<p className={classNames('font-medium text-gray text-xl', 'lg:text-2xl')}>
Click{' '}
<button
type="button"
className="font-semibold text-primary-dark"
onClick={() => router.back()}
>
here
</button>{' '}
to return to the previous page.
</p>
</Container>
</section>
<Layout localeKey={localeKey} metadata={metadata}>
<section>
<Container
heights={{
small: '100vh - 108px',
medium: '50vh',
large: '40vh',
huge: '50vh',
enormous: '50vh',
}}
classes={classNames(
'py-16 px-2 mx-auto text-center',
'md:flex md:flex-col md:justify-center md:items-center'
)}
>
<h1 className={classNames('mb-8 font-bold text-5xl text-primary-dark')}>
{t('heading')}
</h1>
<p className={classNames('font-medium text-gray text-xl', 'lg:text-2xl')}>
{t.rich('content', {
button: (chunk) => (
<button
type="button"
className="font-semibold text-primary-dark"
onClick={() => router.back()}
>
{chunk}
</button>
),
})}
</p>
</Container>
</section>
</Layout>
);
}
Loading