aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/components/organisms/forms/search-form/search-form.module.scss62
-rw-r--r--src/components/organisms/forms/search-form/search-form.stories.tsx43
-rw-r--r--src/components/organisms/forms/search-form/search-form.test.tsx32
-rw-r--r--src/components/organisms/forms/search-form/search-form.tsx113
-rw-r--r--src/components/organisms/layout/no-results.stories.tsx19
-rw-r--r--src/components/organisms/layout/no-results.test.tsx13
-rw-r--r--src/components/organisms/layout/no-results.tsx33
-rw-r--r--src/components/organisms/layout/posts-list.fixture.ts2
-rw-r--r--src/components/organisms/layout/posts-list.stories.tsx7
-rw-r--r--src/components/organisms/layout/posts-list.test.tsx36
-rw-r--r--src/components/organisms/layout/posts-list.tsx82
-rw-r--r--src/components/templates/layout/layout.tsx25
-rw-r--r--src/components/templates/page/page-layout.stories.tsx9
-rw-r--r--src/i18n/en.json12
-rw-r--r--src/i18n/fr.json12
-rw-r--r--src/pages/404.tsx28
-rw-r--r--src/pages/blog/index.tsx1
-rw-r--r--src/pages/blog/page/[number].tsx1
-rw-r--r--src/pages/recherche/index.tsx1
-rw-r--r--src/pages/sujet/[slug].tsx1
-rw-r--r--src/pages/thematique/[slug].tsx1
21 files changed, 295 insertions, 238 deletions
diff --git a/src/components/organisms/forms/search-form/search-form.module.scss b/src/components/organisms/forms/search-form/search-form.module.scss
index 1315fde..db247a2 100644
--- a/src/components/organisms/forms/search-form/search-form.module.scss
+++ b/src/components/organisms/forms/search-form/search-form.module.scss
@@ -1,57 +1,60 @@
@use "../../../../styles/abstracts/functions" as fun;
-@use "../../../../styles/abstracts/mixins" as mix;
-.wrapper {
- display: flex;
- align-items: center;
- position: relative;
+.input {
+ border-right: none;
+}
- @include mix.media("screen") {
- @include mix.dimensions("sm") {
- max-width: 35ch;
- }
- }
+.icon {
+ transform: scale(0.85);
+ transition: all 0.3s ease-in-out 0s;
}
.btn {
- align-self: stretch;
background: var(--color-bg-tertiary);
border: fun.convert-px(2) solid var(--color-border);
- border-left: none;
box-shadow: fun.convert-px(3) fun.convert-px(3) 0 0 var(--color-shadow);
transition: all 0.25s linear 0s;
- &__icon {
- transform: scale(0.85);
- transition: all 0.3s ease-in-out 0s;
+ &:hover,
+ &:focus {
+ .icon {
+ transform: scale(0.85) rotate(20deg) translateX(#{fun.convert-px(2)})
+ translateY(#{fun.convert-px(3)});
+ }
}
&:focus {
- outline: var(--color-primary-light) solid fun.convert-px(3);
+ outline: none;
+ border-color: var(--color-primary);
+ box-shadow: fun.convert-px(3) fun.convert-px(3) 0 0
+ var(--color-primary-dark);
}
&:active {
- outline: none;
+ .icon {
+ transform: scale(0.7);
+ }
}
+}
+
+.wrapper {
+ display: flex;
- &:hover &,
- &:focus & {
- &__icon {
- transform: scale(0.85) rotate(20deg) translateY(#{fun.convert-px(3)});
+ &--no-label {
+ .btn {
+ align-self: stretch;
}
}
- &:active & {
- &__icon {
- transform: scale(0.7);
+ &--has-label {
+ .btn {
+ align-self: flex-end;
+ height: fun.convert-px(52);
}
}
}
.field {
- min-width: 0;
- width: 100%;
-
&:focus-within ~ .btn {
background: var(--color-bg);
border-color: var(--color-primary);
@@ -66,5 +69,10 @@
box-shadow: fun.convert-px(5) fun.convert-px(5) 0 fun.convert-px(1)
var(--color-shadow);
transform: translate(fun.convert-px(-3), fun.convert-px(-3));
+
+ &:focus {
+ box-shadow: fun.convert-px(5) fun.convert-px(5) 0 fun.convert-px(1)
+ var(--color-primary-dark);
+ }
}
}
diff --git a/src/components/organisms/forms/search-form/search-form.stories.tsx b/src/components/organisms/forms/search-form/search-form.stories.tsx
index 971a8ee..d8e4339 100644
--- a/src/components/organisms/forms/search-form/search-form.stories.tsx
+++ b/src/components/organisms/forms/search-form/search-form.stories.tsx
@@ -5,26 +5,9 @@ import { SearchForm } from './search-form';
* SearchForm - Storybook Meta
*/
export default {
- title: 'Organisms/Forms',
+ title: 'Organisms/Forms/Search',
component: SearchForm,
- args: {
- isLabelHidden: false,
- searchPage: '#',
- },
argTypes: {
- className: {
- control: {
- type: 'text',
- },
- description: 'Set additional classnames to the form wrapper.',
- table: {
- category: 'Styles',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
isLabelHidden: {
control: {
type: 'boolean',
@@ -39,16 +22,6 @@ export default {
required: false,
},
},
- searchPage: {
- control: {
- type: 'text',
- },
- description: 'The search results page url.',
- type: {
- name: 'string',
- required: true,
- },
- },
},
} as ComponentMeta<typeof SearchForm>;
@@ -57,9 +30,17 @@ const Template: ComponentStory<typeof SearchForm> = (args) => (
);
/**
- * Forms Stories - Search
+ * SearchForm Stories - Default
+ */
+export const Default = Template.bind({});
+Default.args = {
+ isLabelHidden: false,
+};
+
+/**
+ * SearchForm Stories - With hidden label
*/
-export const Search = Template.bind({});
-Search.args = {
+export const WithHiddenLabel = Template.bind({});
+WithHiddenLabel.args = {
isLabelHidden: true,
};
diff --git a/src/components/organisms/forms/search-form/search-form.test.tsx b/src/components/organisms/forms/search-form/search-form.test.tsx
index 908a8eb..56ba0d7 100644
--- a/src/components/organisms/forms/search-form/search-form.test.tsx
+++ b/src/components/organisms/forms/search-form/search-form.test.tsx
@@ -1,19 +1,39 @@
import { describe, expect, it } from '@jest/globals';
+import { userEvent } from '@testing-library/user-event';
import { render, screen as rtlScreen } from '../../../../../tests/utils';
import { SearchForm } from './search-form';
describe('SearchForm', () => {
- it('renders a search input', () => {
- render(<SearchForm searchPage="#" />);
+ it('renders a search input with a submit button', () => {
+ render(<SearchForm />);
+
expect(
rtlScreen.getByRole('searchbox', { name: 'Search for:' })
).toBeInTheDocument();
- });
-
- it('renders a submit button', () => {
- render(<SearchForm searchPage="#" />);
expect(
rtlScreen.getByRole('button', { name: 'Search' })
).toBeInTheDocument();
});
+
+ it('can submit the form', async () => {
+ const onSubmit = jest.fn((_search: { query?: string }) => undefined);
+ const user = userEvent.setup();
+ const query = 'autem voluptatum eos';
+
+ render(<SearchForm onSubmit={onSubmit} />);
+
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+ expect.assertions(3);
+
+ expect(onSubmit).not.toHaveBeenCalled();
+
+ await user.type(
+ rtlScreen.getByRole('searchbox', { name: 'Search for:' }),
+ query
+ );
+ await user.click(rtlScreen.getByRole('button', { name: 'Search' }));
+
+ expect(onSubmit).toHaveBeenCalledTimes(1);
+ expect(onSubmit).toHaveBeenCalledWith({ query });
+ });
});
diff --git a/src/components/organisms/forms/search-form/search-form.tsx b/src/components/organisms/forms/search-form/search-form.tsx
index 5c685c0..3f16ad0 100644
--- a/src/components/organisms/forms/search-form/search-form.tsx
+++ b/src/components/organisms/forms/search-form/search-form.tsx
@@ -1,20 +1,22 @@
-import { useRouter } from 'next/router';
-import {
- type ChangeEvent,
- type FormEvent,
- forwardRef,
- type ForwardRefRenderFunction,
- useId,
- useState,
- useCallback,
-} from 'react';
+import { forwardRef, type ForwardRefRenderFunction, useId } from 'react';
import { useIntl } from 'react-intl';
-import { Button, Form, Icon, Input, Label } from '../../../atoms';
+import { type FormSubmitHandler, useForm } from '../../../../utils/hooks';
+import {
+ Button,
+ Form,
+ type FormProps,
+ Icon,
+ Input,
+ Label,
+} from '../../../atoms';
import { LabelledField } from '../../../molecules';
import styles from './search-form.module.scss';
-export type SearchFormProps = {
- className?: string;
+export type SearchFormData = { query: string };
+
+export type SearchFormSubmit = FormSubmitHandler<SearchFormData>;
+
+export type SearchFormProps = Omit<FormProps, 'children' | 'onSubmit'> & {
/**
* Should the label be visually hidden?
*
@@ -22,75 +24,80 @@ export type SearchFormProps = {
*/
isLabelHidden?: boolean;
/**
- * The search page url.
+ * A callback function to handle search form submit.
*/
- searchPage: string;
+ onSubmit?: SearchFormSubmit;
};
const SearchFormWithRef: ForwardRefRenderFunction<
HTMLInputElement,
SearchFormProps
-> = ({ className = '', isLabelHidden = false, searchPage }, ref) => {
+> = ({ className = '', isLabelHidden = false, onSubmit, ...props }, ref) => {
const intl = useIntl();
- const fieldLabel = intl.formatMessage({
- defaultMessage: 'Search for:',
- description: 'SearchForm: field accessible label',
- id: 'X8oujO',
- });
- const buttonLabel = intl.formatMessage({
- defaultMessage: 'Search',
- description: 'SearchForm: button accessible name',
- id: 'WMqQrv',
+ const { values, submit, submitStatus, update } = useForm<SearchFormData>({
+ initialValues: { query: '' },
+ submitHandler: onSubmit,
});
-
- const router = useRouter();
- const [value, setValue] = useState<string>('');
-
- const submitHandler = useCallback(
- (e: FormEvent) => {
- e.preventDefault();
- router.push({ pathname: searchPage, query: { s: value } });
- setValue('');
- },
- [router, searchPage, value]
- );
-
- const updateForm = useCallback((e: ChangeEvent<HTMLInputElement>) => {
- setValue(e.target.value);
- }, []);
-
const id = useId();
- const formClass = `${styles.wrapper} ${className}`;
+ const formClass = [
+ styles.wrapper,
+ styles[isLabelHidden ? 'wrapper--no-label' : 'wrapper--has-label'],
+ className,
+ ].join(' ');
+ const labels = {
+ button: intl.formatMessage({
+ defaultMessage: 'Search',
+ description: 'SearchForm: button accessible name',
+ id: 'WMqQrv',
+ }),
+ field: intl.formatMessage({
+ defaultMessage: 'Search for:',
+ description: 'SearchForm: field accessible label',
+ id: 'X8oujO',
+ }),
+ };
return (
- <Form className={formClass} onSubmit={submitHandler}>
+ <Form {...props} className={formClass} onSubmit={submit}>
<LabelledField
className={styles.field}
field={
<Input
- className={styles.field}
- id={`search-form-${id}`}
- name="search-form"
- onChange={updateForm}
+ className={styles.input}
+ id={id}
+ // eslint-disable-next-line react/jsx-no-literals
+ name="query"
+ onChange={update}
ref={ref}
+ // eslint-disable-next-line react/jsx-no-literals
type="search"
- value={value}
+ value={values.query}
/>
}
label={
- <Label htmlFor={`search-form-${id}`} isHidden={isLabelHidden}>
- {fieldLabel}
+ <Label htmlFor={id} isHidden={isLabelHidden}>
+ {labels.field}
</Label>
}
/>
<Button
- aria-label={buttonLabel}
+ aria-label={labels.button}
className={styles.btn}
+ isLoading={submitStatus === 'PENDING'}
+ // eslint-disable-next-line react/jsx-no-literals
kind="neutral"
+ // eslint-disable-next-line react/jsx-no-literals
shape="initial"
type="submit"
>
- <Icon className={styles.btn__icon} shape="magnifying-glass" />
+ <Icon
+ aria-hidden
+ className={styles.icon}
+ // eslint-disable-next-line react/jsx-no-literals
+ shape="magnifying-glass"
+ // eslint-disable-next-line react/jsx-no-literals
+ size="lg"
+ />
</Button>
</Form>
);
diff --git a/src/components/organisms/layout/no-results.stories.tsx b/src/components/organisms/layout/no-results.stories.tsx
index b157572..cfcee83 100644
--- a/src/components/organisms/layout/no-results.stories.tsx
+++ b/src/components/organisms/layout/no-results.stories.tsx
@@ -1,21 +1,10 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
import { NoResults as NoResultsComponent } from './no-results';
export default {
title: 'Organisms/Layout',
component: NoResultsComponent,
- argTypes: {
- searchPage: {
- control: {
- type: 'text',
- },
- description: 'The search results page.',
- type: {
- name: 'string',
- required: true,
- },
- },
- },
+ argTypes: {},
} as ComponentMeta<typeof NoResultsComponent>;
const Template: ComponentStory<typeof NoResultsComponent> = (args) => (
@@ -23,6 +12,4 @@ const Template: ComponentStory<typeof NoResultsComponent> = (args) => (
);
export const NoResults = Template.bind({});
-NoResults.args = {
- searchPage: '#',
-};
+NoResults.args = {};
diff --git a/src/components/organisms/layout/no-results.test.tsx b/src/components/organisms/layout/no-results.test.tsx
index 85f60cf..fdd86f7 100644
--- a/src/components/organisms/layout/no-results.test.tsx
+++ b/src/components/organisms/layout/no-results.test.tsx
@@ -1,15 +1,12 @@
import { describe, expect, it } from '@jest/globals';
-import { render, screen } from '../../../../tests/utils';
+import { render, screen as rtlScreen } from '../../../../tests/utils';
import { NoResults } from './no-results';
describe('NoResults', () => {
- it('renders a no results text', () => {
- render(<NoResults searchPage="#" />);
- expect(screen.getByText(/No results/i)).toBeInTheDocument();
- });
+ it('renders a text with a form', () => {
+ render(<NoResults />);
- it('renders a search form', () => {
- render(<NoResults searchPage="#" />);
- expect(screen.getByRole('searchbox')).toBeInTheDocument();
+ expect(rtlScreen.getByText(/No results/i)).toBeInTheDocument();
+ expect(rtlScreen.getByRole('searchbox')).toBeInTheDocument();
});
});
diff --git a/src/components/organisms/layout/no-results.tsx b/src/components/organisms/layout/no-results.tsx
index b2acf12..f760616 100644
--- a/src/components/organisms/layout/no-results.tsx
+++ b/src/components/organisms/layout/no-results.tsx
@@ -1,16 +1,37 @@
-import { FC } from 'react';
+import { useRouter } from 'next/router';
+import { type FC, useCallback } from 'react';
import { useIntl } from 'react-intl';
-import { SearchForm, type SearchFormProps } from '../forms';
-
-export type NoResultsProps = Pick<SearchFormProps, 'searchPage'>;
+import { ROUTES } from '../../../utils/constants';
+import { SearchForm, type SearchFormSubmit } from '../forms';
/**
* NoResults component
*
* Renders a no results text with a search form.
*/
-export const NoResults: FC<NoResultsProps> = ({ searchPage }) => {
+export const NoResults: FC = () => {
const intl = useIntl();
+ const router = useRouter();
+ const searchSubmitHandler: SearchFormSubmit = useCallback(
+ ({ query }) => {
+ if (!query)
+ return {
+ messages: {
+ error: intl.formatMessage({
+ defaultMessage: 'Query must be longer than one character.',
+ description: 'NoResults: invalid query message',
+ id: 'VkfO7t',
+ }),
+ },
+ validator: (value) => value.query.length > 1,
+ };
+
+ router.push({ pathname: ROUTES.SEARCH, query: { s: query } });
+
+ return undefined;
+ },
+ [intl, router]
+ );
return (
<>
@@ -28,7 +49,7 @@ export const NoResults: FC<NoResultsProps> = ({ searchPage }) => {
id: 'DVBwfu',
})}
</p>
- <SearchForm isLabelHidden searchPage={searchPage} />
+ <SearchForm isLabelHidden onSubmit={searchSubmitHandler} />
</>
);
};
diff --git a/src/components/organisms/layout/posts-list.fixture.ts b/src/components/organisms/layout/posts-list.fixture.ts
index 6109411..dfb0d97 100644
--- a/src/components/organisms/layout/posts-list.fixture.ts
+++ b/src/components/organisms/layout/posts-list.fixture.ts
@@ -59,5 +59,3 @@ export const posts: Post[] = [
url: '#',
},
];
-
-export const searchPage = '#';
diff --git a/src/components/organisms/layout/posts-list.stories.tsx b/src/components/organisms/layout/posts-list.stories.tsx
index d56c7e6..b5af1d3 100644
--- a/src/components/organisms/layout/posts-list.stories.tsx
+++ b/src/components/organisms/layout/posts-list.stories.tsx
@@ -1,6 +1,6 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
import { PostsList } from './posts-list';
-import { posts, searchPage } from './posts-list.fixture';
+import { posts } from './posts-list.fixture';
/**
* PostsList - Storybook Meta
@@ -161,7 +161,6 @@ const Template: ComponentStory<typeof PostsList> = (args) => (
export const Default = Template.bind({});
Default.args = {
posts,
- searchPage,
total: posts.length,
};
@@ -172,7 +171,6 @@ export const ByYears = Template.bind({});
ByYears.args = {
posts,
byYear: true,
- searchPage,
total: posts.length,
};
ByYears.decorators = [
@@ -189,6 +187,5 @@ ByYears.decorators = [
export const NoResults = Template.bind({});
NoResults.args = {
posts: [],
- searchPage,
total: posts.length,
};
diff --git a/src/components/organisms/layout/posts-list.test.tsx b/src/components/organisms/layout/posts-list.test.tsx
index d5273fc..fabf31f 100644
--- a/src/components/organisms/layout/posts-list.test.tsx
+++ b/src/components/organisms/layout/posts-list.test.tsx
@@ -1,47 +1,31 @@
import { describe, expect, it } from '@jest/globals';
-import { render, screen } from '../../../../tests/utils';
+import { render, screen as rtlScreen } from '../../../../tests/utils';
import { PostsList } from './posts-list';
-import { posts, searchPage } from './posts-list.fixture';
+import { posts } from './posts-list.fixture';
describe('PostsList', () => {
it('renders the correct number of posts', () => {
- render(
- <PostsList posts={posts} total={posts.length} searchPage={searchPage} />
- );
- expect(screen.getAllByRole('article')).toHaveLength(posts.length);
+ render(<PostsList posts={posts} total={posts.length} />);
+ expect(rtlScreen.getAllByRole('article')).toHaveLength(posts.length);
});
it('renders the number of loaded posts', () => {
- render(
- <PostsList posts={posts} total={posts.length} searchPage={searchPage} />
- );
+ render(<PostsList posts={posts} total={posts.length} />);
const info = `${posts.length} loaded articles out of a total of ${posts.length}`;
- expect(screen.getByText(info)).toBeInTheDocument();
+ expect(rtlScreen.getByText(info)).toBeInTheDocument();
});
it('renders a load more button', () => {
render(
- <PostsList
- posts={posts}
- total={posts.length}
- showLoadMoreBtn={true}
- searchPage={searchPage}
- />
+ <PostsList posts={posts} total={posts.length} showLoadMoreBtn={true} />
);
expect(
- screen.getByRole('button', { name: /Load more/i })
+ rtlScreen.getByRole('button', { name: /Load more/i })
).toBeInTheDocument();
});
it('renders a search form if no results', () => {
- render(
- <PostsList
- posts={[]}
- total={0}
- showLoadMoreBtn={true}
- searchPage={searchPage}
- />
- );
- expect(screen.getByRole('searchbox')).toBeInTheDocument();
+ render(<PostsList posts={[]} total={0} showLoadMoreBtn={true} />);
+ expect(rtlScreen.getByRole('searchbox')).toBeInTheDocument();
});
});
diff --git a/src/components/organisms/layout/posts-list.tsx b/src/components/organisms/layout/posts-list.tsx
index 30beb50..36d3c87 100644
--- a/src/components/organisms/layout/posts-list.tsx
+++ b/src/components/organisms/layout/posts-list.tsx
@@ -17,7 +17,7 @@ import {
type RenderPaginationItemAriaLabel,
type RenderPaginationLink,
} from '../nav';
-import { NoResults, type NoResultsProps } from './no-results';
+import { NoResults } from './no-results';
import styles from './posts-list.module.scss';
import { Summary, type SummaryProps } from './summary';
@@ -30,45 +30,44 @@ export type Post = Omit<SummaryProps, 'titleLevel'> & {
export type YearCollection = Record<string, Post[]>;
-export type PostsListProps = Pick<PaginationProps, 'siblings'> &
- Pick<NoResultsProps, 'searchPage'> & {
- /**
- * The pagination base url.
- */
- baseUrl?: string;
- /**
- * True to display the posts by year. Default: false.
- */
- byYear?: boolean;
- /**
- * Determine if the data is loading.
- */
- isLoading?: boolean;
- /**
- * Load more button handler.
- */
- loadMore?: () => void;
- /**
- * The current page number. Default: 1.
- */
- pageNumber?: number;
- /**
- * The posts data.
- */
- posts: Post[];
- /**
- * Determine if the load more button should be visible.
- */
- showLoadMoreBtn?: boolean;
- /**
- * The posts heading level (hn).
- */
- titleLevel?: HeadingLevel;
- /**
- * The total posts number.
- */
- total: number;
- };
+export type PostsListProps = Pick<PaginationProps, 'siblings'> & {
+ /**
+ * The pagination base url.
+ */
+ baseUrl?: string;
+ /**
+ * True to display the posts by year. Default: false.
+ */
+ byYear?: boolean;
+ /**
+ * Determine if the data is loading.
+ */
+ isLoading?: boolean;
+ /**
+ * Load more button handler.
+ */
+ loadMore?: () => void;
+ /**
+ * The current page number. Default: 1.
+ */
+ pageNumber?: number;
+ /**
+ * The posts data.
+ */
+ posts: Post[];
+ /**
+ * Determine if the load more button should be visible.
+ */
+ showLoadMoreBtn?: boolean;
+ /**
+ * The posts heading level (hn).
+ */
+ titleLevel?: HeadingLevel;
+ /**
+ * The total posts number.
+ */
+ total: number;
+};
/**
* Create a collection of posts sorted by year.
@@ -101,7 +100,6 @@ export const PostsList: FC<PostsListProps> = ({
loadMore,
pageNumber = 1,
posts,
- searchPage,
showLoadMoreBtn = false,
siblings,
titleLevel,
@@ -305,7 +303,7 @@ export const PostsList: FC<PostsListProps> = ({
);
};
- if (posts.length === 0) return <NoResults searchPage={searchPage} />;
+ if (posts.length === 0) return <NoResults />;
return (
<>
diff --git a/src/components/templates/layout/layout.tsx b/src/components/templates/layout/layout.tsx
index cdbb414..8332ba4 100644
--- a/src/components/templates/layout/layout.tsx
+++ b/src/components/templates/layout/layout.tsx
@@ -1,5 +1,6 @@
/* eslint-disable max-statements */
import NextImage from 'next/image';
+import { useRouter } from 'next/router';
import Script from 'next/script';
import {
type FC,
@@ -46,6 +47,7 @@ import {
SearchForm,
SettingsForm,
type NavbarItems,
+ type SearchFormSubmit,
} from '../../organisms';
import styles from './layout.module.scss';
@@ -85,6 +87,7 @@ export const Layout: FC<LayoutProps> = ({
isHome,
useGrid = false,
}) => {
+ const router = useRouter();
const intl = useIntl();
const { website } = useSettings();
const { baseline, copyright, locales, name, url } = website;
@@ -249,6 +252,26 @@ export const Layout: FC<LayoutProps> = ({
condition: () => isSearchOpen,
delay: 360,
});
+ const searchSubmitHandler: SearchFormSubmit = useCallback(
+ ({ query }) => {
+ if (!query)
+ return {
+ messages: {
+ error: intl.formatMessage({
+ defaultMessage: 'Query must be longer than one character.',
+ description: 'Layout: invalid query message',
+ id: 'C2YcUJ',
+ }),
+ },
+ validator: (value) => value.query.length > 1,
+ };
+
+ router.push({ pathname: ROUTES.SEARCH, query: { s: query } });
+
+ return undefined;
+ },
+ [intl, router]
+ );
useRouteChange(deactivateSearch);
@@ -268,8 +291,8 @@ export const Layout: FC<LayoutProps> = ({
<SearchForm
className={styles.search}
isLabelHidden
+ onSubmit={searchSubmitHandler}
ref={searchInputRef}
- searchPage={ROUTES.SEARCH}
/>
),
icon: 'magnifying-glass',
diff --git a/src/components/templates/page/page-layout.stories.tsx b/src/components/templates/page/page-layout.stories.tsx
index 7977382..05b47da 100644
--- a/src/components/templates/page/page-layout.stories.tsx
+++ b/src/components/templates/page/page-layout.stories.tsx
@@ -373,14 +373,7 @@ Blog.args = {
breadcrumb: postsListBreadcrumb,
title: 'Blog',
headerMeta: [{ id: 'total', label: 'Total:', value: `${posts.length}` }],
- children: (
- <PostsList
- posts={posts}
- byYear={true}
- total={posts.length}
- searchPage="#"
- />
- ),
+ children: <PostsList posts={posts} byYear={true} total={posts.length} />,
widgets: [
<LinksListWidget
heading={
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 4b7e756..4af656b 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -195,6 +195,14 @@
"defaultMessage": "Failed to load.",
"description": "BlogPage: failed to load text"
},
+ "C2YcUJ": {
+ "defaultMessage": "Query must be longer than one character.",
+ "description": "Layout: invalid query message"
+ },
+ "C6oK7h": {
+ "defaultMessage": "Query must be longer than one character.",
+ "description": "404Page: invalid query message"
+ },
"CvOqoh": {
"defaultMessage": "Thematics:",
"description": "ArticlePage: thematics meta label"
@@ -411,6 +419,10 @@
"defaultMessage": "Send",
"description": "ContactForm: send button"
},
+ "VkfO7t": {
+ "defaultMessage": "Query must be longer than one character.",
+ "description": "NoResults: invalid query message"
+ },
"Vmj5cw": {
"defaultMessage": "It is now awaiting moderation.",
"description": "PageLayout: comment awaiting moderation"
diff --git a/src/i18n/fr.json b/src/i18n/fr.json
index 0934548..054068e 100644
--- a/src/i18n/fr.json
+++ b/src/i18n/fr.json
@@ -195,6 +195,14 @@
"defaultMessage": "Échec du chargement.",
"description": "BlogPage: failed to load text"
},
+ "C2YcUJ": {
+ "defaultMessage": "Les mots-clés doivent être plus longs qu'un caractère.",
+ "description": "Layout: invalid query message"
+ },
+ "C6oK7h": {
+ "defaultMessage": "Les mots-clés doivent être plus longs qu'un caractère.",
+ "description": "404Page: invalid query message"
+ },
"CvOqoh": {
"defaultMessage": "Thématiques :",
"description": "ArticlePage: thematics meta label"
@@ -411,6 +419,10 @@
"defaultMessage": "Envoyer",
"description": "ContactForm: send button"
},
+ "VkfO7t": {
+ "defaultMessage": "Les mots-clés doivent être plus longs qu'un caractère.",
+ "description": "NoResults: invalid query message"
+ },
"Vmj5cw": {
"defaultMessage": "Il est maintenant en attente de modération.",
"description": "PageLayout: comment awaiting moderation"
diff --git a/src/pages/404.tsx b/src/pages/404.tsx
index ae6eac5..00e2d5a 100644
--- a/src/pages/404.tsx
+++ b/src/pages/404.tsx
@@ -1,6 +1,8 @@
+/* eslint-disable max-statements */
import type { GetStaticProps } from 'next';
import Head from 'next/head';
-import type { ReactNode } from 'react';
+import { useRouter } from 'next/router';
+import { useCallback, type ReactNode } from 'react';
import { useIntl } from 'react-intl';
import {
getLayout,
@@ -9,6 +11,7 @@ import {
LinksListWidget,
PageLayout,
SearchForm,
+ type SearchFormSubmit,
} from '../components';
import {
getThematicsPreview,
@@ -39,6 +42,7 @@ const Error404Page: NextPageWithLayout<Error404PageProps> = ({
thematicsList,
topicsList,
}) => {
+ const router = useRouter();
const intl = useIntl();
const { website } = useSettings();
const title = intl.formatMessage({
@@ -85,6 +89,26 @@ const Error404Page: NextPageWithLayout<Error404PageProps> = ({
description: 'Error404Page: topics list widget title',
id: 'GVpTIl',
});
+ const searchSubmitHandler: SearchFormSubmit = useCallback(
+ ({ query }) => {
+ if (!query)
+ return {
+ messages: {
+ error: intl.formatMessage({
+ defaultMessage: 'Query must be longer than one character.',
+ description: '404Page: invalid query message',
+ id: 'C6oK7h',
+ }),
+ },
+ validator: (value) => value.query.length > 1,
+ };
+
+ router.push({ pathname: ROUTES.SEARCH, query: { s: query } });
+
+ return undefined;
+ },
+ [intl, router]
+ );
return (
<>
@@ -134,7 +158,7 @@ const Error404Page: NextPageWithLayout<Error404PageProps> = ({
id: 'XKy7rx',
})}
</p>
- <SearchForm isLabelHidden searchPage={ROUTES.SEARCH} />
+ <SearchForm isLabelHidden onSubmit={searchSubmitHandler} />
</PageLayout>
</>
);
diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx
index 5c64e6d..accd314 100644
--- a/src/pages/blog/index.tsx
+++ b/src/pages/blog/index.tsx
@@ -217,7 +217,6 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({
isLoading={isLoadingMore ?? isLoadingInitialData}
loadMore={loadMore}
posts={getPostsList(data)}
- searchPage={ROUTES.SEARCH}
showLoadMoreBtn={hasNextPage}
total={totalArticles}
/>
diff --git a/src/pages/blog/page/[number].tsx b/src/pages/blog/page/[number].tsx
index 58cf7b9..1c723f1 100644
--- a/src/pages/blog/page/[number].tsx
+++ b/src/pages/blog/page/[number].tsx
@@ -213,7 +213,6 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({
byYear={true}
pageNumber={pageNumber}
posts={getPostsList([articles])}
- searchPage={ROUTES.SEARCH}
total={totalArticles}
/>
</PageLayout>
diff --git a/src/pages/recherche/index.tsx b/src/pages/recherche/index.tsx
index 32312ec..12a482d 100644
--- a/src/pages/recherche/index.tsx
+++ b/src/pages/recherche/index.tsx
@@ -241,7 +241,6 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({
isLoading={isLoadingMore ?? isLoadingInitialData}
loadMore={loadMore}
posts={getPostsList(data)}
- searchPage={ROUTES.SEARCH}
showLoadMoreBtn={hasNextPage}
total={totalArticles ?? 0}
/>
diff --git a/src/pages/sujet/[slug].tsx b/src/pages/sujet/[slug].tsx
index 87c3340..9094703 100644
--- a/src/pages/sujet/[slug].tsx
+++ b/src/pages/sujet/[slug].tsx
@@ -228,7 +228,6 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({
baseUrl={postsListBaseUrl}
byYear={true}
posts={getPostsWithUrl(articles)}
- searchPage={ROUTES.SEARCH}
titleLevel={3}
total={articles.length}
/>
diff --git a/src/pages/thematique/[slug].tsx b/src/pages/thematique/[slug].tsx
index 8e21ff6..bb97f47 100644
--- a/src/pages/thematique/[slug].tsx
+++ b/src/pages/thematique/[slug].tsx
@@ -200,7 +200,6 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({
baseUrl={postsListBaseUrl}
byYear={true}
posts={getPostsWithUrl(articles)}
- searchPage={ROUTES.SEARCH}
titleLevel={3}
total={articles.length}
/>