diff --git a/src/pages/Settings/components/SettingsPageBreadcrumbs.tsx b/src/pages/Settings/components/SettingsPageBreadcrumbs.tsx
new file mode 100644
index 0000000..8ccfb52
--- /dev/null
+++ b/src/pages/Settings/components/SettingsPageBreadcrumbs.tsx
@@ -0,0 +1,42 @@
+import { useLocation } from 'react-router-dom';
+
+import { BaseComponentProps } from 'common/utils/types';
+import Breadcrumbs from 'common/components/Breadcrumbs/Breadcrumbs';
+
+/**
+ * The `SettingsPageBreadcrumbs` component renders the `Breadcrumbs` for the settings
+ * family of pages.
+ */
+const SettingsPageBreadcrumbs = ({
+ className,
+ testId = 'page-settings-breadcrumbs',
+}: BaseComponentProps): JSX.Element => {
+ const location = useLocation();
+ const pathElements = location.pathname.split('/');
+
+ return (
+
+
+
+ Home
+
+
+
+ Settings
+
+ {!!pathElements[3] && (
+ <>
+
+
+
+ {pathElements[3].replace('-', ' ')}
+
+
+ >
+ )}
+
+
+ );
+};
+
+export default SettingsPageBreadcrumbs;
diff --git a/src/pages/Settings/components/__tests__/SettingsPageBreadcrumbs.test.tsx b/src/pages/Settings/components/__tests__/SettingsPageBreadcrumbs.test.tsx
new file mode 100644
index 0000000..a9b9400
--- /dev/null
+++ b/src/pages/Settings/components/__tests__/SettingsPageBreadcrumbs.test.tsx
@@ -0,0 +1,41 @@
+import { describe, expect, it } from 'vitest';
+import { Navigate, Route, Routes } from 'react-router-dom';
+
+import { render, screen } from 'test/test-utils';
+
+import SettingsPageBreadcrumbs from '../SettingsPageBreadcrumbs';
+
+describe('SettingsPageBreadcrumbs', () => {
+ it('should render successfully', async () => {
+ // ARRANGE
+ render(
+
+ } />
+ } />
+ } />
+ ,
+ );
+ await screen.findByTestId('page-settings-breadcrumbs');
+
+ // ASSERT
+ expect(screen.getByTestId('page-settings-breadcrumbs')).toBeDefined();
+ });
+
+ it('should render third path element breadcrumb', async () => {
+ // ARRANGE
+ render(
+
+ } />
+ } />
+ } />
+ ,
+ );
+ await screen.findByTestId('page-settings-breadcrumbs-page-appearance');
+
+ // ASSERT
+ expect(screen.getByTestId('page-settings-breadcrumbs-page-appearance')).toBeDefined();
+ expect(screen.getByTestId('page-settings-breadcrumbs-page-appearance')).toHaveTextContent(
+ /appearance/i,
+ );
+ });
+});
diff --git a/src/pages/Tasks/TasksPage.tsx b/src/pages/Tasks/TasksPage.tsx
index afe22e2..b30e47f 100644
--- a/src/pages/Tasks/TasksPage.tsx
+++ b/src/pages/Tasks/TasksPage.tsx
@@ -5,6 +5,7 @@ import { useNavigate } from 'react-router-dom';
import { PropsWithTestId } from 'common/utils/types';
import { useGetCurrentUser } from 'common/api/useGetCurrentUser';
import Page from 'common/components/Page/Page';
+import TasksPageBreadcrumbs from './components/TasksPageBreadcrumbs';
import UserInfo from './components/UserInfo';
import Card from 'common/components/Card/Card';
import FAIcon from 'common/components/Icon/FAIcon';
@@ -24,6 +25,8 @@ const TasksPage = ({ testId = 'page-tasks' }: PropsWithTestId): JSX.Element => {
return (
+
+
{/* page heading */}
{t('tasks', { ns: 'tasks' })}
diff --git a/src/pages/Tasks/components/TasksPageBreadcrumbs.tsx b/src/pages/Tasks/components/TasksPageBreadcrumbs.tsx
new file mode 100644
index 0000000..4e148f0
--- /dev/null
+++ b/src/pages/Tasks/components/TasksPageBreadcrumbs.tsx
@@ -0,0 +1,77 @@
+import { useLocation, useParams } from 'react-router-dom';
+import toNumber from 'lodash/toNumber';
+
+import { BaseComponentProps } from 'common/utils/types';
+import { useGetTask } from '../api/useGetTask';
+import Breadcrumbs from 'common/components/Breadcrumbs/Breadcrumbs';
+import LoaderSkeleton from 'common/components/Loader/LoaderSkeleton';
+
+/**
+ * The `TasksPageBreadcrumbs` component renders the `Breadcrumbs` for the tasks
+ * family of pages.
+ */
+const TasksPageBreadcrumbs = ({
+ className,
+ testId = 'page-tasks-breadcrumbs',
+}: BaseComponentProps): JSX.Element => {
+ const location = useLocation();
+ const params = useParams();
+ const pathElements = location.pathname.split('/');
+
+ const hasTask = !!params.taskId;
+ const hasTaskAdd = pathElements.includes('add');
+ const hasTaskEdit = pathElements.includes('edit');
+
+ const { data: task, isLoading: isLoadingTask } = useGetTask({ taskId: toNumber(params.taskId) });
+
+ return (
+
+
+
+
+ Home
+
+
+
+
+
+ Tasks
+
+
+ {hasTaskAdd && (
+ <>
+
+
+ Add
+
+ >
+ )}
+ {hasTask && (
+ <>
+
+
+ {!!task && (
+
+ {task.title}
+
+ )}
+ {isLoadingTask && (
+
+ )}
+
+ >
+ )}
+ {hasTaskEdit && (
+ <>
+
+
+ Edit
+
+ >
+ )}
+
+
+ );
+};
+
+export default TasksPageBreadcrumbs;
diff --git a/src/pages/Tasks/components/__tests__/TasksPageBreadcrumbs.test.tsx b/src/pages/Tasks/components/__tests__/TasksPageBreadcrumbs.test.tsx
new file mode 100644
index 0000000..24d5820
--- /dev/null
+++ b/src/pages/Tasks/components/__tests__/TasksPageBreadcrumbs.test.tsx
@@ -0,0 +1,101 @@
+import { describe, expect, it } from 'vitest';
+import { Navigate, Route, Routes } from 'react-router-dom';
+
+import { render, screen } from 'test/test-utils';
+
+import TasksPageBreadcrumbs from '../TasksPageBreadcrumbs';
+
+describe('TasksPageBreadcrumbs', () => {
+ it('should render successfully', async () => {
+ // ARRANGE
+ render();
+ await screen.findByTestId('page-tasks-breadcrumbs');
+
+ // ASSERT
+ expect(screen.getByTestId('page-tasks-breadcrumbs')).toBeDefined();
+ });
+
+ it('should display breadcrumbs for a task', async () => {
+ // ARRANGE
+ render(
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ ,
+ );
+ await screen.findByTestId('page-tasks-breadcrumbs-link-task');
+
+ // ASSERT
+ expect(screen.getByTestId('page-tasks-breadcrumbs-link-home')).toBeDefined();
+ expect(screen.getByTestId('page-tasks-breadcrumbs-link-tasks')).toBeDefined();
+ expect(screen.queryByTestId('page-tasks-breadcrumbs-page-tasks-add')).toBeNull();
+ expect(screen.getByTestId('page-tasks-breadcrumbs-link-task')).toBeDefined();
+ expect(screen.queryByTestId('page-tasks-breadcrumbs-page-task-edit')).toBeNull();
+ });
+
+ it('should display breadcrumbs for the task list', async () => {
+ // ARRANGE
+ render(
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ ,
+ );
+ await screen.findByTestId('page-tasks-breadcrumbs-link-tasks');
+
+ // ASSERT
+ expect(screen.getByTestId('page-tasks-breadcrumbs-link-home')).toBeDefined();
+ expect(screen.getByTestId('page-tasks-breadcrumbs-link-tasks')).toBeDefined();
+ expect(screen.queryByTestId('page-tasks-breadcrumbs-page-tasks-add')).toBeNull();
+ expect(screen.queryByTestId('page-tasks-breadcrumbs-link-task')).toBeNull();
+ expect(screen.queryByTestId('page-tasks-breadcrumbs-page-task-edit')).toBeNull();
+ });
+
+ it('should display breadcrumbs for task add', async () => {
+ // ARRANGE
+ render(
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ ,
+ );
+ await screen.findByTestId('page-tasks-breadcrumbs-page-task-add');
+
+ // ASSERT
+ expect(screen.getByTestId('page-tasks-breadcrumbs-link-home')).toBeDefined();
+ expect(screen.getByTestId('page-tasks-breadcrumbs-link-tasks')).toBeDefined();
+ expect(screen.getByTestId('page-tasks-breadcrumbs-page-task-add')).toBeDefined();
+ expect(screen.queryByTestId('page-tasks-breadcrumbs-link-task')).toBeNull();
+ expect(screen.queryByTestId('page-tasks-breadcrumbs-page-task-edit')).toBeNull();
+ });
+
+ it('should display breadcrumbs for task edit', async () => {
+ // ARRANGE
+ render(
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ ,
+ );
+ await screen.findByTestId('page-tasks-breadcrumbs-link-task');
+
+ // ASSERT
+ expect(screen.getByTestId('page-tasks-breadcrumbs-link-home')).toBeDefined();
+ expect(screen.getByTestId('page-tasks-breadcrumbs-link-tasks')).toBeDefined();
+ expect(screen.queryByTestId('page-tasks-breadcrumbs-page-task-add')).toBeNull();
+ expect(screen.getByTestId('page-tasks-breadcrumbs-link-task')).toBeDefined();
+ expect(screen.getByTestId('page-tasks-breadcrumbs-page-task-edit')).toBeDefined();
+ });
+});