Skip to content

Commit

Permalink
Changed Routes to specify current languages routes instead of anything (
Browse files Browse the repository at this point in the history
#7382)

* Changed Routes to specify current languages routes instead of anything to properly throw 404s

* hydratating atoms store on SSR
  • Loading branch information
konzz authored Oct 23, 2024
1 parent 39be959 commit 14af369
Show file tree
Hide file tree
Showing 11 changed files with 73 additions and 63 deletions.
16 changes: 9 additions & 7 deletions app/react/App/App.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
/* eslint-disable import/no-named-as-default */
import React, { useState, useMemo } from 'react';
import PropTypes from 'prop-types';
import { Outlet, useLocation, useParams } from 'react-router-dom';
import { useSetAtom } from 'jotai';
import { useAtom } from 'jotai';
import Notifications from 'app/Notifications';
import Cookiepopup from 'app/App/Cookiepopup';
import { TranslateForm, t } from 'app/I18N';
import { Icon } from 'UI';
import { socket } from 'app/socket';
import { NotificationsContainer } from 'V2/Components/UI';
import { Matomo } from 'app/V2/Components/Analitycs';
import { Matomo, CleanInsights } from 'app/V2/Components/Analitycs';
import { settingsAtom } from 'V2/atoms/settingsAtom';
import Confirm from './Confirm';
import { Menu } from './Menu';
import { AppMainContext } from './AppMainContext';
import SiteName from './SiteName';
import GoogleAnalytics from './GoogleAnalytics';
import { CleanInsights } from 'app/V2/Components/Analitycs';
import 'react-widgets/dist/css/react-widgets.css';
import 'bootstrap/dist/css/bootstrap.css';
import 'nprogress/nprogress.css';
Expand All @@ -27,13 +27,15 @@ import 'flowbite';
const App = ({ customParams }) => {
const [showMenu, setShowMenu] = useState(false);
const [confirmOptions, setConfirmOptions] = useState({});
const setSettings = useSetAtom(settingsAtom);
const [settings, setSettings] = useAtom(settingsAtom);

const location = useLocation();
const params = useParams();
const sharedId = params.sharedId || customParams?.sharedId;

const possibleLanguages = settings.languages?.map(l => l.key) || [];
const shouldAddAppClassName =
['/', `/${params.lang}/`].includes(location.pathname) ||
['/', ...possibleLanguages.map(lang => `/${lang}/`)].includes(location.pathname) ||
location.pathname.match(/\/page\/.*\/.*/g) ||
location.pathname.match(/\/entity\/.*/g);

Expand All @@ -58,8 +60,8 @@ const App = ({ customParams }) => {

const appClassName = shouldAddAppClassName && sharedId ? `pageId_${sharedId}` : '';

socket.on('updateSettings', settings => {
setSettings(settings);
socket.on('updateSettings', _settings => {
setSettings(_settings);
});

return (
Expand Down
2 changes: 1 addition & 1 deletion app/react/Documents/components/DocumentSidePanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,7 @@ DocumentSidePanel.propTypes = {
search: PropTypes.string,
}).isRequired,
navigate: PropTypes.func.isRequired,
selectedDocument: PropTypes.instanceOf(Immutable),
selectedDocument: PropTypes.instanceOf(Immutable.Map),
// relationships v2
newRelationshipsEnabled: PropTypes.bool,
};
Expand Down
13 changes: 9 additions & 4 deletions app/react/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -214,20 +214,25 @@ const getRoutesLayout = (
</Route>
);

const languageLayout = (langKey: string, layout: React.JSX.Element) => (
<Route key={langKey} path={langKey}>
{layout}
<Route path="*" element={<GeneralError />} />
</Route>
);

const getRoutes = (
settings: ClientSettings | undefined,
userId: string | undefined,
headers?: IncomingHttpHeaders
) => {
const { element, parameters } = getIndexElement(settings, userId);
const layout = getRoutesLayout(settings, element, headers);
const languageKeys = settings?.languages?.map(lang => lang.key) || [];
return createRoutesFromElements(
<Route path="/" element={<App customParams={parameters} />}>
{layout}
<Route path="/:lang">
{layout}
<Route path="*" element={<GeneralError />} />
</Route>
{languageKeys.map(langKey => languageLayout(langKey, layout))}
<Route path="*" element={<GeneralError />} />
</Route>
);
Expand Down
18 changes: 9 additions & 9 deletions app/react/V2/Routes/Settings/Pages/PageEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const pageEditorLoader =
(headers?: IncomingHttpHeaders): LoaderFunction =>
async ({ params }) => {
if (params.sharedId) {
const page = await pagesAPI.getBySharedId(params.sharedId, params.lang || 'en', headers);
const page = await pagesAPI.getBySharedId(params.sharedId, headers);

return page;
}
Expand Down Expand Up @@ -136,8 +136,8 @@ const PageEditor = () => {
<Tabs.Tab id="Basic" label={<Translate>Basic</Translate>}>
<form>
<input className="hidden" {...register('sharedId')} />
<div className="flex flex-col gap-4 max-w-2xl">
<div className="flex gap-4 items-center">
<div className="flex flex-col max-w-2xl gap-4">
<div className="flex items-center gap-4">
<Translate className="font-bold">
Enable this page to be used as an entity view page:
</Translate>
Expand All @@ -162,7 +162,7 @@ const PageEditor = () => {
: ''
}
label={<Translate>URL</Translate>}
className="mb-4 w-full"
className="w-full mb-4"
id="page-url"
/>

Expand All @@ -184,9 +184,9 @@ const PageEditor = () => {
</Tabs.Tab>

<Tabs.Tab id="Code" key="html" label={<Translate>Markdown</Translate>}>
<div className="flex flex-col gap-2 h-full">
<div className="flex flex-col h-full gap-2">
<HTMLNotification />
<div className="pt-2 h-full">
<div className="h-full pt-2">
<CodeEditor
language="html"
intialValue={page.metadata?.content}
Expand All @@ -206,9 +206,9 @@ const PageEditor = () => {
</Tabs.Tab>

<Tabs.Tab id="Advanced" label={<Translate>Javascript</Translate>}>
<div className="flex flex-col gap-2 h-full">
<div className="flex flex-col h-full gap-2">
<JSNotification />
<div className="pt-2 h-full">
<div className="h-full pt-2">
<CodeEditor
language="javascript"
intialValue={page.metadata?.script}
Expand All @@ -230,7 +230,7 @@ const PageEditor = () => {
</SettingsContent.Body>

<SettingsContent.Footer>
<div className="flex gap-2 justify-end">
<div className="flex justify-end gap-2">
<Link to="/settings/pages">
<Button styling="light" disabled={isSubmitting}>
<Translate>Cancel</Translate>
Expand Down
11 changes: 5 additions & 6 deletions app/react/V2/Routes/Settings/Pages/PagesList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable react/no-multi-comp */
import React, { useState } from 'react';
import { Link, LoaderFunction, useLoaderData, useParams, useRevalidator } from 'react-router-dom';
import { Link, LoaderFunction, useLoaderData, useRevalidator } from 'react-router-dom';
import { createColumnHelper } from '@tanstack/react-table';
import { IncomingHttpHeaders } from 'http';
import { useSetAtom } from 'jotai';
Expand All @@ -26,8 +26,8 @@ type TablePage = Page & { rowId: string };

const pagesListLoader =
(headers?: IncomingHttpHeaders): LoaderFunction =>
async ({ params }) =>
(await pagesAPI.get(params.lang || 'en', headers)).map(page => ({ ...page, rowId: page._id }));
async () =>
(await pagesAPI.get(headers)).map(page => ({ ...page, rowId: page._id }));

const deletionNotification: (hasErrors: boolean) => notificationAtomType = hasErrors => ({
type: !hasErrors ? 'success' : 'error',
Expand All @@ -45,7 +45,6 @@ const PagesList = () => {
const [showModal, setShowModal] = useState(false);
const pages = useLoaderData() as TablePage[];
const revalidator = useRevalidator();
const params = useParams();
const setNotifications = useSetAtom(notificationAtom);

const deleteSelected = async () => {
Expand Down Expand Up @@ -112,7 +111,7 @@ const PagesList = () => {
</SettingsContent.Body>
<SettingsContent.Footer className={selectedPages.length ? 'bg-primary-50' : ''}>
{selectedPages.length > 0 && (
<div className="flex gap-2 items-center">
<div className="flex items-center gap-2">
<Button
type="button"
onClick={confirmDeletion}
Expand All @@ -128,7 +127,7 @@ const PagesList = () => {
{selectedPages.length === 0 && (
<div className="flex justify-between w-full">
<div className="flex gap-2">
<Link to={`/${params.lang || 'en'}/settings/pages/page`}>
<Link to="/settings/pages/page">
<Button styling="solid" color="primary" type="button">
<Translate>Add page</Translate>
</Button>
Expand Down
12 changes: 4 additions & 8 deletions app/react/V2/Routes/Settings/Pages/components/PageListTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,13 @@ const ActionCell = ({ cell }: CellContext<TablePage, string>) => {
const isEntityView = cell.row.original.entityView;

return (
<div className="flex gap-2 justify-end">
<Link
to={`/${cell.row.original.language}/${pageUrl}`}
target="_blank"
aria-disabled={isEntityView}
>
<div className="flex justify-end gap-2">
<Link to={`/${pageUrl}`} target="_blank" aria-disabled={isEntityView}>
<Button styling="light" disabled={isEntityView}>
<Translate>View</Translate>
</Button>
</Link>
<Link to={`/${cell.row.original.language}/settings/pages/page/${cell.getValue()}`}>
<Link to={`/settings/pages/page/${cell.getValue()}`}>
<Button styling="light">
<Translate>Edit</Translate>
</Button>
Expand All @@ -54,7 +50,7 @@ const UrlCell = ({ cell }: CellContext<TablePage, string>) => {
};

const List = ({ items }: { items: TablePage[] }) => (
<ul className="flex flex-wrap gap-8 max-w-md list-disc list-inside">
<ul className="flex flex-wrap max-w-md gap-8 list-disc list-inside">
{items.map(item => (
<li key={item._id}>{item.title}</li>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import { useSetAtom, useAtomValue } from 'jotai';
import { Translate } from 'app/I18N';
import * as relationshipTypesAPI from 'app/V2/api/relationshiptypes';
import { Template } from 'app/apiResponseTypes';
import { notificationAtom, templatesAtom } from 'app/V2/atoms';
import { relationshipTypesAtom } from 'app/V2/atoms/relationshipTypes';
import { notificationAtom, templatesAtom, relationshipTypesAtom } from 'app/V2/atoms';
import { Button, Table, Sidepanel, ConfirmationModal } from 'app/V2/Components/UI';
import { SettingsContent } from 'app/V2/Components/Layouts/SettingsContent';
import { columns, Relationships, TableRelationshipType } from './components/TableComponents';
Expand Down Expand Up @@ -134,7 +133,7 @@ const RelationshipTypes = () => {
</SettingsContent.Body>
<SettingsContent.Footer className={selectedItems.length ? 'bg-primary-50' : ''}>
{selectedItems.length > 0 && (
<div className="flex gap-2 items-center">
<div className="flex items-center gap-2">
<Button
type="button"
onClick={() => setShowConfirmationModal(true)}
Expand Down Expand Up @@ -182,7 +181,7 @@ const RelationshipTypes = () => {
header={<Translate>Delete</Translate>}
warningText={<Translate>Do you want to delete the following items?</Translate>}
body={
<ul className="flex flex-wrap gap-8 max-w-md list-disc list-inside">
<ul className="flex flex-wrap max-w-md gap-8 list-disc list-inside">
{selectedItems.map(item => (
<li key={item.name}>{item.name}</li>
))}
Expand Down
16 changes: 8 additions & 8 deletions app/react/V2/api/pages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,25 @@ import { RequestParams } from 'app/utils/RequestParams';
import { Page } from 'V2/shared/types';
import { FetchResponseError } from 'shared/JSONRequest';

const get = async (language: string, headers?: IncomingHttpHeaders): Promise<Page[]> => {
const get = async (headers?: IncomingHttpHeaders): Promise<Page[]> => {
try {
const requestParams = new RequestParams({}, headers);
api.locale(language);
if (headers && headers['Content-Language']) {
api.locale(headers['Content-Language']);
}
const response = await api.get('pages', requestParams);
return response.json;
} catch (e) {
return e;
}
};

const getBySharedId = async (
sharedId: string,
language: string,
headers?: IncomingHttpHeaders
): Promise<Page> => {
const getBySharedId = async (sharedId: string, headers?: IncomingHttpHeaders): Promise<Page> => {
try {
const requestParams = new RequestParams({ sharedId }, headers);
api.locale(language);
if (headers && headers['Content-Language']) {
api.locale(headers['Content-Language']);
}
const response = await api.get('page', requestParams);
return response.json;
} catch (e) {
Expand Down
3 changes: 2 additions & 1 deletion app/react/V2/atoms/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { atomStore } from './store';
export { atomStore, hydrateAtomStore } from './store';
export { notificationAtom } from './notificationAtom';
export { settingsAtom } from './settingsAtom';
export { templatesAtom } from './templatesAtom';
Expand All @@ -7,5 +7,6 @@ export { thesauriAtom } from './thesauriAtom';
export { globalMatomoAtom } from './globalMatomoAtom';
export { ciMatomoActiveAtom } from './ciMatomoActiveAtom';
export { userAtom } from './userAtom';
export { relationshipTypesAtom } from './relationshipTypes';
export type { AtomStoreData } from './store';
export type { notificationAtomType } from './notificationAtom';
23 changes: 12 additions & 11 deletions app/react/V2/atoms/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,18 @@ declare global {

const atomStore = createStore();

if (isClient && window.__atomStoreData__) {
const { globalMatomo, locale, settings, thesauri, templates, user, ciMatomoActive } =
window.__atomStoreData__;
const hydrateAtomStore = (data: AtomStoreData) => {
if (data.ciMatomoActive) atomStore.set(ciMatomoActiveAtom, data.ciMatomoActive);
if (data.globalMatomo) atomStore.set(globalMatomoAtom, { ...data.globalMatomo });
if (data.settings) atomStore.set(settingsAtom, data.settings);
if (data.thesauri) atomStore.set(thesauriAtom, data.thesauri);
if (data.templates) atomStore.set(templatesAtom, data.templates);
atomStore.set(userAtom, data.user);
atomStore.set(translationsAtom, { locale: data.locale || 'en' });
};

if (ciMatomoActive) atomStore.set(ciMatomoActiveAtom, ciMatomoActive);
if (globalMatomo) atomStore.set(globalMatomoAtom, { ...globalMatomo });
if (settings) atomStore.set(settingsAtom, settings);
if (thesauri) atomStore.set(thesauriAtom, thesauri);
if (templates) atomStore.set(templatesAtom, templates);
atomStore.set(userAtom, user);
atomStore.set(translationsAtom, { locale: locale || 'en' });
if (isClient && window.__atomStoreData__) {
hydrateAtomStore(window.__atomStoreData__);

//sync deprecated redux store
atomStore.sub(settingsAtom, () => {
Expand All @@ -62,4 +63,4 @@ if (isClient && window.__atomStoreData__) {
}

export type { AtomStoreData };
export { atomStore };
export { atomStore, hydrateAtomStore };
15 changes: 11 additions & 4 deletions app/react/entry-server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import CustomProvider from './App/Provider';
import Root from './App/Root';
import RouteHandler from './App/RouteHandler';
import { ErrorBoundary } from './V2/Components/ErrorHandling';
import { atomStore } from './V2/atoms';
import { atomStore, hydrateAtomStore } from './V2/atoms';
import { I18NUtils, t, Translate } from './I18N';
import { IStore } from './istore';
import { getRoutes } from './Routes';
Expand Down Expand Up @@ -110,7 +110,7 @@ const getAssets = async () => {

const prepareStores = async (req: ExpressRequest, settings: ClientSettings, language?: string) => {
const locale = I18NUtils.getLocale(language, settings.languages, req.cookies);

api.locale(locale);
const headers = {
'Content-Language': locale,
Cookie: `connect.sid=${req.cookies['connect.sid']}`,
Expand Down Expand Up @@ -270,7 +270,14 @@ const EntryServer = async (req: ExpressRequest, res: Response) => {
const { connection, ...headers } = req.headers;
const routes = getRoutes(settings, req.user && req.user._id, headers);
const matched = matchRoutes(routes, req.path);
const language = matched ? matched[0].params.lang : req.language;
const lastRouteMatched = matched ? matched[matched.length - 1] : null;
//extract the language from the route pathName, i.e /en/library
const pathPossibleLanguage = lastRouteMatched?.pathname.split('/')[1] || '';

const languageKeys = (settings?.languages?.map(lang => lang.key) as string[]) || [];
const language = languageKeys.includes(pathPossibleLanguage)
? pathPossibleLanguage
: req.language;
const isCatchAll = matched ? matched[matched.length - 1].route.path === '*' : true;

const { reduxState, atomStoreData, staticHandleContext, router } = await getSSRProperties(
Expand All @@ -287,7 +294,7 @@ const EntryServer = async (req: ExpressRequest, res: Response) => {
matched
);
resetTranslations();

hydrateAtomStore(atomStoreData);
const componentHtml = ReactDOMServer.renderToString(
<ReduxProvider store={initialStore as any}>
<CustomProvider initialData={initialState} user={req.user} language={initialState.locale}>
Expand Down

0 comments on commit 14af369

Please sign in to comment.