diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index 4b00749f..92f6d86b 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -72,7 +72,7 @@ jobs: -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info -Dsonar.typescript.tsconfigPaths=tsconfig.json -Dsonar.sources=src/ - -Dsonar.exclusions=src/__test__/** + -Dsonar.exclusions=src/__test__/**,src/amplifyconfiguration.*,src/**/*.scss,src/**/*.css,src/**/*.d.*,src/setupTests.* -Dsonar.tests=src/__test__/ -Dsonar.project.monorepo.enabled=true sonar_token: ${{ secrets.SONAR_TOKEN_FRONTEND }} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9212ab8e..9724ce05 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,7 +13,6 @@ "@carbon/icons-react": "^11.50.1", "@carbon/pictograms-react": "^11.49.0", "@carbon/react": "^1.27.0", - "@redux-devtools/extension": "^3.3.0", "@tanstack/react-query": "^5.50.1", "@types/node": "^22.0.0", "@vitejs/plugin-react": "^4.0.4", @@ -30,10 +29,7 @@ "react-esri-leaflet": "^2.0.1", "react-hash-string": "^1.0.0", "react-leaflet": "^4.2.1", - "react-redux": "^9.0.0", "react-router-dom": "^6.10.0", - "redux": "^5.0.0", - "redux-thunk": "^3.0.0", "vite": "^5.0.0", "vite-plugin-svgr": "^4.0.0", "vite-tsconfig-paths": "^5.0.0", @@ -3122,19 +3118,6 @@ "react-dom": "^18.0.0" } }, - "node_modules/@redux-devtools/extension": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/extension/-/extension-3.3.0.tgz", - "integrity": "sha512-X34S/rC8S/M1BIrkYD1mJ5f8vlH0BDqxXrs96cvxSBo4FhMdbhU+GUGsmNYov1xjSyLMHgo8NYrUG8bNX7525g==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.2", - "immutable": "^4.3.4" - }, - "peerDependencies": { - "redux": "^3.1.0 || ^4.0.0 || ^5.0.0" - } - }, "node_modules/@remix-run/router": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz", @@ -5226,7 +5209,7 @@ "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@types/qs": { @@ -5247,7 +5230,7 @@ "version": "18.3.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -5330,12 +5313,6 @@ "@types/geojson": "*" } }, - "node_modules/@types/use-sync-external-store": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", - "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==", - "license": "MIT" - }, "node_modules/@types/uuid": { "version": "9.0.8", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", @@ -6657,7 +6634,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/d3": { @@ -11265,29 +11242,6 @@ "react-dom": "^18.0.0" } }, - "node_modules/react-redux": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", - "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", - "license": "MIT", - "dependencies": { - "@types/use-sync-external-store": "^0.0.3", - "use-sync-external-store": "^1.0.0" - }, - "peerDependencies": { - "@types/react": "^18.2.25", - "react": "^18.0", - "redux": "^5.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "redux": { - "optional": true - } - } - }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -11356,21 +11310,6 @@ "node": ">=8" } }, - "node_modules/redux": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" - }, - "node_modules/redux-thunk": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", - "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", - "license": "MIT", - "peerDependencies": { - "redux": "^5.0.0" - } - }, "node_modules/reflect.getprototypeof": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", @@ -12756,15 +12695,6 @@ "react-dom": ">=16.8.0" } }, - "node_modules/use-sync-external-store": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", - "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/utrie": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 276e9e7b..7772c0a6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,11 +9,10 @@ "@carbon/icons-react": "^11.50.1", "@carbon/pictograms-react": "^11.49.0", "@carbon/react": "^1.27.0", - "@redux-devtools/extension": "^3.3.0", "@tanstack/react-query": "^5.50.1", "@types/node": "^22.0.0", "@vitejs/plugin-react": "^4.0.4", - "@vitejs/plugin-react-swc": "^3.3.2", + "@vitejs/plugin-react-swc": "^3.3.2", "aws-amplify": "^6.7.0", "axios": "^1.6.8", "jspdf": "^2.5.2", @@ -26,10 +25,7 @@ "react-esri-leaflet": "^2.0.1", "react-hash-string": "^1.0.0", "react-leaflet": "^4.2.1", - "react-redux": "^9.0.0", "react-router-dom": "^6.10.0", - "redux": "^5.0.0", - "redux-thunk": "^3.0.0", "vite": "^5.0.0", "vite-plugin-svgr": "^4.0.0", "vite-tsconfig-paths": "^5.0.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 1895b049..3a618220 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,7 +1,6 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import './custom.scss'; import Landing from "./screens/Landing"; -import Help from "./screens/Help"; import SideLayout from './layouts/SideLayout'; import ProtectedRoute from './routes/ProtectedRoute'; import Opening from './screens/Opening'; @@ -33,10 +32,6 @@ const router = createBrowserRouter([ { path: "/silviculture-search", element: } /> - }, - { - path: "/help", - element: } /> } ] }, diff --git a/frontend/src/__test__/actions/userAction.test.ts b/frontend/src/__test__/actions/userAction.test.ts deleted file mode 100644 index 592f409f..00000000 --- a/frontend/src/__test__/actions/userAction.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { getUserDetails, setClientRoles } from '../../actions/userAction'; -import { - USER_DETAILS_REQUEST, -USER_DETAILS_SUCCESS, -USER_DETAILS_FAIL, -SET_CLIENT_ROLES -} from '../../constants/userConstants'; -import { useGetAuth } from '../../contexts/AuthProvider'; -import { AppDispatch } from '../../store'; -import { UserClientRolesType } from '../../types/UserRoleType'; - - - -vi.mock('../../contexts/AuthProvider', () => ({ -useGetAuth: vi.fn(), -})); - -describe('userAction', () => { -describe('getUserDetails', () => { - it('should dispatch USER_DETAILS_REQUEST and USER_DETAILS_SUCCESS with user data when successful', async () => { - const mockDispatch = vi.fn(); - const mockIsLoggedIn = true; - const mockUser = { firstName: 'John', lastName: 'Doe' }; - const mockUserJSON = JSON.stringify(mockUser); - - (useGetAuth as vi.Mock).mockReturnValue({ isLoggedIn: mockIsLoggedIn }); - localStorage.setItem('famLoginUser', mockUserJSON); - - await getUserDetails()(mockDispatch as unknown as AppDispatch); - - expect(mockDispatch).toHaveBeenCalledWith({ type: USER_DETAILS_REQUEST }); - expect(mockDispatch).toHaveBeenCalledWith({ - type: USER_DETAILS_SUCCESS, - payload: { ...mockUser, isLoggedIn: mockIsLoggedIn }, - }); - }); - - it('should dispatch USER_DETAILS_FAIL with error when an error occurs', async () => { - const mockDispatch = vi.fn(); - const mockError = new Error('Test error'); - - (useGetAuth as vi.Mock).mockImplementation(() => { - throw mockError; - }); - - await getUserDetails()(mockDispatch as unknown as AppDispatch); - - expect(mockDispatch).toHaveBeenCalledWith({ type: USER_DETAILS_REQUEST }); - expect(mockDispatch).toHaveBeenCalledWith({ - type: USER_DETAILS_FAIL, - payload: { error: mockError }, - }); - }); - - it('should handle missing user data in localStorage', async () => { - const mockDispatch = vi.fn(); - const mockIsLoggedIn = true; - - (useGetAuth as vi.Mock).mockReturnValue({ isLoggedIn: mockIsLoggedIn }); - localStorage.removeItem('famLoginUser'); - - await getUserDetails()(mockDispatch as unknown as AppDispatch); - - expect(mockDispatch).toHaveBeenCalledWith({ type: USER_DETAILS_REQUEST }); - expect(mockDispatch).toHaveBeenCalledWith({ - type: USER_DETAILS_SUCCESS, - payload: { isLoggedIn: mockIsLoggedIn }, - }); - }); -}); - -describe('setClientRoles', () => { - it('should dispatch SET_CLIENT_ROLES with client roles', () => { - const mockDispatch = vi.fn(); - const mockClientRoles: UserClientRolesType[] = [{ clientId: '123', roles: ['admin'] }]; - - setClientRoles(mockClientRoles)(mockDispatch as unknown as AppDispatch); - - expect(mockDispatch).toHaveBeenCalledWith({ - type: SET_CLIENT_ROLES, - payload: mockClientRoles, - }); - }); -}); -}); \ No newline at end of file diff --git a/frontend/src/__test__/components/BCHeaderwSide.test.tsx b/frontend/src/__test__/components/BCHeaderwSide.test.tsx index c3a49c78..5a920686 100644 --- a/frontend/src/__test__/components/BCHeaderwSide.test.tsx +++ b/frontend/src/__test__/components/BCHeaderwSide.test.tsx @@ -5,9 +5,6 @@ import { BrowserRouter } from 'react-router-dom'; import BCHeaderwSide from '../../components/BCHeaderwSide'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import {leftMenu } from '../../components/BCHeaderwSide/constants'; -import * as redux from 'react-redux'; -import { Provider } from 'react-redux'; -import store from '../../store'; import { UserClientRolesType } from '../../types/UserRoleType'; import '@testing-library/jest-dom'; import { AuthProvider } from '../../contexts/AuthProvider'; @@ -31,12 +28,10 @@ const renderComponent = () => { render( - - - - - - + + + + ); @@ -65,9 +60,6 @@ const state = { }, }; -vi.spyOn(redux, 'useSelector') - .mockImplementation((callback) => callback(state)); - describe('BCHeaderwSide', () => { it('should renders the component', () => { renderComponent(); @@ -88,12 +80,6 @@ describe('BCHeaderwSide', () => { // expect(screen.queryByText('My Profile')).not.toBeVisible(); }); - // it('renders the correct number of top-level menu items', () => { - // renderComponent(); - // const menuItems = screen.getAllByRole('button', { name: /.*Category.*/ }); - // expect(menuItems).toHaveLength(leftMenu.length); - // }); - it('renders the correct menu item names', () => { renderComponent(); leftMenu.forEach(item => { diff --git a/frontend/src/__test__/components/OpeningHistory.test.tsx b/frontend/src/__test__/components/OpeningHistory.test.tsx index e65b5a08..e5be3a72 100644 --- a/frontend/src/__test__/components/OpeningHistory.test.tsx +++ b/frontend/src/__test__/components/OpeningHistory.test.tsx @@ -2,49 +2,32 @@ import React from 'react'; import { render, act } from '@testing-library/react'; import { describe, it, expect, vi } from 'vitest'; import OpeningHistory from '../../components/OpeningHistory'; -import History from '../../types/History'; -import { deleteOpeningFavorite } from '../../services/OpeningFavoriteService'; - -const mockHistories: History[] = [ - { - id: 1, - steps: [], - }, - { - id: 2, - steps: [ - { step: 1, status: 'complete', description: 'Step 1', subtitle: 'Completed' }, - { step: 2, status: 'invalid', description: 'Step 2', subtitle: 'Invalid' }, - { step: 3, status: 'disabled', description: 'Step 3', subtitle: 'Disabled' }, - ], - }, -]; +import { NotificationProvider } from '../../contexts/NotificationProvider'; +import { deleteOpeningFavorite, fetchOpeningTrends } from '../../services/OpeningFavoriteService'; vi.mock('../../services/OpeningFavoriteService', () => ({ deleteOpeningFavorite: vi.fn(), + fetchOpeningTrends: vi.fn(), })); describe('OpeningHistory Component', () => { it('renders correctly with given histories', async () => { - let getByText; + (fetchOpeningTrends as vi.Mock).mockReturnValueOnce(Promise.resolve([1, 2])); + let container; await act(async () => { - ({ getByText } = render( )); + ({ container } = render()); }); // Check for the presence of Opening Ids - expect(getByText('Opening Id 1')).toBeInTheDocument(); - expect(getByText('Opening Id 2')).toBeInTheDocument(); - - // Check for the presence of step descriptions - expect(getByText('Step 1')).toBeInTheDocument(); - expect(getByText('Step 2')).toBeInTheDocument(); - expect(getByText('Step 3')).toBeInTheDocument(); + expect(container.querySelector('div[data-id="1"').innerHTML).toContain('Opening Id 1'); + expect(container.querySelector('div[data-id="2"').innerHTML).toContain('Opening Id 2'); }); it('renders correctly with empty histories', async () => { + (fetchOpeningTrends as vi.Mock).mockReturnValueOnce(Promise.resolve([])); let container; await act(async () => { - ({ container } = render( )); + ({ container } = render()); }); // Select the div with the specific class @@ -57,13 +40,29 @@ describe('OpeningHistory Component', () => { // check if when clicked on the FavoriteButton, the deleteOpeningFavorite function is called it('should call deleteOpeningFavorite when FavoriteButton is clicked', async () => { + (fetchOpeningTrends as vi.Mock).mockReturnValueOnce(Promise.resolve([1, 2])); let container; await act(async () => { - ({ container } = render( )); + ({ container } = render()); }); const favoriteButton = container.querySelector('.favorite-icon button') - favoriteButton && favoriteButton.click(); + await act(async () => favoriteButton && favoriteButton.click()); + + expect(deleteOpeningFavorite).toHaveBeenCalled(); + }); + + it('should call deleteOpeningFavorite and handle error when FavoriteButton is clicked', async () => { + (fetchOpeningTrends as vi.Mock).mockReturnValueOnce(Promise.resolve([1, 2])); + (deleteOpeningFavorite as vi.Mock).mockRejectedValueOnce(new Error('Failed to delete favorite')); + let container; + await act(async () => { + ({ container } = render()); + }); + + const favoriteButton = container.querySelector('.favorite-icon button') + await act(async () => favoriteButton && favoriteButton.click()); + expect(deleteOpeningFavorite).toHaveBeenCalled(); }); }); diff --git a/frontend/src/__test__/components/OpeningMetricsTab.test.tsx b/frontend/src/__test__/components/OpeningMetricsTab.test.tsx index 75a27d69..08a02b41 100644 --- a/frontend/src/__test__/components/OpeningMetricsTab.test.tsx +++ b/frontend/src/__test__/components/OpeningMetricsTab.test.tsx @@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import React from 'react'; import { render, act, waitFor, fireEvent, screen } from '@testing-library/react'; import OpeningMetricsTab from '../../components/OpeningMetricsTab'; +import { NotificationProvider } from '../../contexts/NotificationProvider'; import { fetchOpeningTrends } from '../../services/OpeningFavoriteService'; import { fetchFreeGrowingMilestones, fetchOpeningsPerYear, fetchRecentOpenings } from '../../services/OpeningService'; @@ -46,7 +47,7 @@ describe('OpeningMetricsTab', () => { it('should render the OpeningMetricsTab component with all sections', async () => { - await act(async () => render()); + await act(async () => render()); expect(screen.getByText('Dashboard')).toBeInTheDocument(); expect(screen.getByText('Manage and track silvicultural information about openings')).toBeInTheDocument(); @@ -64,7 +65,7 @@ describe('OpeningMetricsTab', () => { await act(async () => { - render(); + render(); }); await waitFor(() => { @@ -84,7 +85,7 @@ describe('OpeningMetricsTab', () => { delete window.location; window.location = { search: '?scrollTo=trackOpenings' } as any; - await act(async () => render()); + await act(async () => render()); expect(mockScrollIntoView).toHaveBeenCalledWith({ behavior: 'smooth' }); @@ -100,7 +101,7 @@ describe('OpeningMetricsTab', () => { delete window.location; window.location = { search: '' } as any; - await act(async () => render()); + await act(async () => render()); expect(mockScrollIntoView).not.toHaveBeenCalled(); diff --git a/frontend/src/__test__/components/OpeningsTab.test.tsx b/frontend/src/__test__/components/OpeningsTab.test.tsx index 0b5cd8d1..10bd1b32 100644 --- a/frontend/src/__test__/components/OpeningsTab.test.tsx +++ b/frontend/src/__test__/components/OpeningsTab.test.tsx @@ -1,99 +1,51 @@ -import React from "react"; -import { render, screen } from "@testing-library/react"; -import "@testing-library/jest-dom"; -import { describe, expect, it, vi } from "vitest"; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import OpeningsTab from "../../../src/components/OpeningsTab"; -import { Provider } from "react-redux"; -import store from "../../store"; -import {useUserRecentOpeningQuery} from "../../../src/services/queries/search/openingQueries"; -import { MemoryRouter } from "react-router-dom"; -import PaginationContext from "../../contexts/PaginationContext"; - -// Mocking useUserRecentOpeningQuery to return the necessary functions and state -vi.mock("../../../src/services/queries/search/openingQueries", () => ({ - useUserRecentOpeningQuery: vi.fn(), +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import React from 'react'; +import { render, act, waitFor, screen } from '@testing-library/react'; +import OpeningsTab from '../../components/OpeningsTab'; +import { AuthProvider } from '../../contexts/AuthProvider'; +import { getWmsLayersWhitelistUsers } from '../../services/SecretsService'; +import { fetchRecentOpenings } from '../../services/OpeningService'; +import { RecentOpening } from '../../types/RecentOpening'; +import PaginationProvider from '../../contexts/PaginationProvider'; + + +vi.mock('../../services/SecretsService', () => ({ + getWmsLayersWhitelistUsers: vi.fn() })); - -const paginationValueMock = { - getCurrentData: () => [], - currentPage: 0, - totalPages: 0, - handlePageChange: vi.fn(), - handleItemsPerPageChange: vi.fn(), - itemsPerPage: 5, - totalResultItems:100, - setTotalResultItems:vi.fn(), - setPageData: vi.fn(), - setInitialItemsPerPage: vi.fn(), +vi.mock('../../services/OpeningService', async () => { + const actual = await vi.importActual('../../services/OpeningService'); + return { + ...actual, + fetchRecentOpenings: vi.fn(), }; +}); -describe("OpeningsTab", () => { - const queryClient = new QueryClient(); - const showSpatial = false; - const setShowSpatial = vi.fn(); - - it("renders the component successfully", () => { - (useUserRecentOpeningQuery as jest.Mock).mockReturnValue({ data: [], isFetching: false }); - render( - - - - - - - - - - ); - const searchInput = screen.getByText(/Track the history of openings you have looked at and check spatial information by selecting the openings in the table below/i); - expect(searchInput).toBeInTheDocument(); - }); - it("shows the spatial area with Hide Spatial Button", () => { - (useUserRecentOpeningQuery as jest.Mock).mockReturnValue({ data: [], isFetching: false }); - render( - - - - - - - - - - ); - const hideSpatialButton = screen.getByRole('button', { name: /Hide Spatial/i }); - expect(hideSpatialButton).toBeInTheDocument(); +const rows: RecentOpening[] = [{ + id: '123', + openingId: '123', + fileId: '1', + cuttingPermit: '1', + timberMark: '1', + cutBlock: '1', + grossAreaHa: 1, + statusDesc: 'Approved', + categoryDesc: 'Another:Another', + disturbanceStart: '1', + entryTimestamp: '1', + updateTimestamp: '1', +}]; + +describe('Openings Tab test',() => { + + it('should render properly',async () =>{ + (getWmsLayersWhitelistUsers as vi.Mock).mockResolvedValue([{userName: 'TEST'}]); + (fetchRecentOpenings as vi.Mock).mockResolvedValue(rows); + await act(async () => { + render(); + }); + expect(screen.getByText('Recent openings')).toBeInTheDocument(); }); - - it("shows table skeleton when the data is loading", () => { - (useUserRecentOpeningQuery as jest.Mock).mockReturnValue({ data: [], isFetching: true }); - - render( - - - - - - - - - - ); - console.log(screen.debug()); - expect.poll(() => document.querySelector('--cds-skeleton')).toBeTruthy(); - }); - -}); +}); \ No newline at end of file diff --git a/frontend/src/__test__/components/ThemeToggle.test.tsx b/frontend/src/__test__/components/ThemeToggle.test.tsx new file mode 100644 index 00000000..41cdceea --- /dev/null +++ b/frontend/src/__test__/components/ThemeToggle.test.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import ThemeToggle from '../../components/ThemeToggle'; + +describe('Theme toggle component tests', () => { + it('should render correctly', () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it('should toggle theme when button is clicked', () => { + const { container } = render(); + const button = container.querySelector('button'); + button?.click(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/frontend/src/__test__/components/__snapshots__/ThemeToggle.test.tsx.snap b/frontend/src/__test__/components/__snapshots__/ThemeToggle.test.tsx.snap new file mode 100644 index 00000000..2c112feb --- /dev/null +++ b/frontend/src/__test__/components/__snapshots__/ThemeToggle.test.tsx.snap @@ -0,0 +1,61 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Theme toggle component tests > should render correctly 1`] = ` +
+
+
+ +
+
+
+`; + +exports[`Theme toggle component tests > should toggle theme when button is clicked 1`] = ` +
+
+
+ +
+
+
+`; diff --git a/frontend/src/__test__/contexts/AuthProvider.test.tsx b/frontend/src/__test__/contexts/AuthProvider.test.tsx index 3430ccca..7106ad23 100644 --- a/frontend/src/__test__/contexts/AuthProvider.test.tsx +++ b/frontend/src/__test__/contexts/AuthProvider.test.tsx @@ -91,7 +91,7 @@ describe('AuthProvider', () => { it('should handle login correctly', async () => { setAuthCookies(sampleAuthToken); const provider = 'idir'; - const envProvider = 'TEST-IDIR'; + const envProvider = `${env.VITE_ZONE ?? 'DEV'}-IDIR`; const TestComponent = () => { const { login } = useGetAuth(); diff --git a/frontend/src/__test__/contexts/NotificationProvider.test.tsx b/frontend/src/__test__/contexts/NotificationProvider.test.tsx new file mode 100644 index 00000000..320bda9b --- /dev/null +++ b/frontend/src/__test__/contexts/NotificationProvider.test.tsx @@ -0,0 +1,128 @@ +import React from 'react'; +import { render, screen, act } from '@testing-library/react'; +import { describe, it, expect, vi } from 'vitest'; +import { NotificationProvider, useNotification } from '../../contexts/NotificationProvider'; +import { NotificationContent } from '../../types/NotificationType'; +import { ActionableNotification } from '@carbon/react'; + +// Mock the ActionableNotification component +vi.mock('@carbon/react', () => ({ + ActionableNotification: ({ title, subtitle, onClose, onActionButtonClick }: any) => ( +
+
{title}
+
{subtitle}
+ + +
+ ) +})); + +const onCloseMock = vi.fn(); + +const TestComponent = () => { + const { displayNotification } = useNotification(); + return ( + + ); +}; + +describe('NotificationProvider', () => { + it('should display notification when displayNotification is called', () => { + render( + + + + ); + + act(() => { + screen.getByText('Show Notification').click(); + }); + + expect(screen.getByText('Test Title')).toBeInTheDocument(); + expect(screen.getByText('Test Subtitle')).toBeInTheDocument(); + expect(screen.getByText('Close')).toBeInTheDocument(); + expect(screen.getByText('Action')).toBeInTheDocument(); + }); + + it('should hide notification when close button is clicked', () => { + render( + + + + ); + + act(() => { + screen.getByText('Show Notification').click(); + }); + + act(() => { + screen.getByText('Close').click(); + }); + + expect(screen.queryByText('Test Title')).not.toBeInTheDocument(); + expect(screen.queryByText('Test Subtitle')).not.toBeInTheDocument(); + }); + + it('should call onClose and hide notification when action button is clicked', () => { + + + render( + + + + ); + + act(() => { + screen.getByText('Show Notification').click(); + }); + + act(() => { + screen.getByText('Action').click(); + }); + + expect(onCloseMock).toHaveBeenCalled(); + expect(screen.queryByText('Test Title')).not.toBeInTheDocument(); + expect(screen.queryByText('Test Subtitle')).not.toBeInTheDocument(); + }); + + it('should hide notification after dismissIn time', async () => { + vi.useFakeTimers(); // Use fake timers + + await act(async () => render( + + + + )); + + await act(async () => { + screen.getByText('Show Notification').click(); + }); + + expect(screen.getByText('Test Title')).toBeInTheDocument(); + expect(screen.getByText('Test Subtitle')).toBeInTheDocument(); + + await act(async () => vi.advanceTimersByTime(1111)); // Fast-forward time by 1 second + + expect(screen.queryByText('Test Title')).not.toBeInTheDocument(); + expect(screen.queryByText('Test Subtitle')).not.toBeInTheDocument + + }); + + + afterEach(() => { + vi.useRealTimers(); // Restore real timers after each test + }); +}); \ No newline at end of file diff --git a/frontend/src/__test__/screens/Help.test.tsx b/frontend/src/__test__/screens/Help.test.tsx deleted file mode 100644 index ff823dc2..00000000 --- a/frontend/src/__test__/screens/Help.test.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { render, screen, act } from '@testing-library/react'; -import { MemoryRouter } from 'react-router-dom'; -import Help from '../../screens/Help'; -import { describe, expect, it } from 'vitest'; - -describe('Help component test cases', () => { - it('should renders the help page header', () => { - render(, { wrapper: MemoryRouter }); - const headerElement = screen.getByText('Help Page'); - expect(headerElement).not.toBeNull(); - }); - - it('should renders the help page content', () => { - render(, { wrapper: MemoryRouter }); - const contentElement = screen.getByText(/Welcome to the Help Page/i); - expect(contentElement).not.toBeNull(); - }); - - it('should navigates back to home page on button click', () => { - act(() => { - render(, { wrapper: MemoryRouter }); - }); - - act(() => { - const backButton = screen.getByRole('button', { name: /Back to Home/i }); - backButton.click(); - }); - - // Add assertions to test if navigation to the home page occurs - expect(location.pathname).toBe('/'); - }); -}); diff --git a/frontend/src/__test__/screens/Opening.test.tsx b/frontend/src/__test__/screens/Opening.test.tsx index 8e4d3970..863c0d76 100644 --- a/frontend/src/__test__/screens/Opening.test.tsx +++ b/frontend/src/__test__/screens/Opening.test.tsx @@ -3,12 +3,13 @@ import { describe, expect, it, vi } from 'vitest'; import { render, waitFor, act } from '@testing-library/react'; import Opening from '../../screens/Opening'; import PaginationContext from '../../contexts/PaginationContext'; +import { NotificationProvider } from '../../contexts/NotificationProvider'; import { BrowserRouter } from 'react-router-dom'; -import * as redux from 'react-redux'; import { RecentOpening } from '../../types/RecentOpening'; import { getWmsLayersWhitelistUsers } from '../../services/SecretsService'; import { fetchFreeGrowingMilestones, fetchOpeningsPerYear, fetchRecentOpenings } from '../../services/OpeningService'; import { fetchOpeningTrends } from '../../services/OpeningFavoriteService'; +import { AuthProvider } from '../../contexts/AuthProvider'; const data = { "activityType": "Update", @@ -44,8 +45,6 @@ const state = { } }; -vi.spyOn(redux, 'useSelector') - .mockImplementation((callback) => callback(state)); const rows: RecentOpening[] = [{ id: '123', @@ -96,7 +95,11 @@ describe('Opening screen test cases', () => { const { getByTestId } = render( - + + + + + ); @@ -117,7 +120,11 @@ describe('Opening screen test cases', () => { ({ container } = render( - + + + + + )); @@ -152,7 +159,11 @@ describe('Opening screen test cases', () => { ({ container, getByText } = render( - + + + + + )); @@ -171,7 +182,11 @@ describe('Opening screen test cases', () => { ({ container, getByText } = render( - + + + + + )); diff --git a/frontend/src/__test__/views/LoginOrgSelection.test.tsx b/frontend/src/__test__/views/LoginOrgSelection.test.tsx deleted file mode 100644 index a003fdc7..00000000 --- a/frontend/src/__test__/views/LoginOrgSelection.test.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import { describe, expect, it, vi } from 'vitest'; -import { Provider } from 'react-redux'; -import store from '../../store'; -import LoginOrgSelection from '../../views/LoginOrgSelection'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { UserClientRolesType } from '../../types/UserRoleType'; -import * as redux from 'react-redux'; - -vi.mock('../../services/TestService', () => ({ - getForestClientByNumberOrAcronym: vi.fn(() => [ - { - clientNumber: '00012797', - clientName: 'MINISTRY OF FORESTS', - legalFirstName: '', - legalMiddleName: '', - clientStatusCode: { code: 'ACT', description: 'Active' }, - clientTypeCode: { code: 'F', description: 'Ministry of Forests and Range' }, - acronym: 'MOF' - }, - ]), -})); - -const clientRoles: UserClientRolesType[] = [ - { - clientId: '00012797', - roles: ['ONE', 'TWO', 'THREE'], - clientName: 'MINISTRY OF FORESTS' - } -]; - -const state = { - userDetails: { - id: 1, - name: 'User', - user: { - firstName: 'John', - lastName: 'Doe', - providerUsername: 'johndoe123', - clientRoles: clientRoles - }, - loading: false, - error: null - }, -}; - -vi.spyOn(redux, 'useSelector') - .mockImplementation((callback) => callback(state)); - -describe('LoginOrgSelection', () => { - it('renders organization selection view with user details', () => { - const qc = new QueryClient(); - - render( - - - - - - ); - - // Check if elements are rendered correctly - expect(screen.getByText('Organization selection')).toBeDefined(); - expect(screen.getByText('John Doe (johndoe123) select which organization you\'re representing.')).toBeDefined(); - }); -}); diff --git a/frontend/src/actions/selectedClientRolesActions.ts b/frontend/src/actions/selectedClientRolesActions.ts deleted file mode 100644 index aa0ec954..00000000 --- a/frontend/src/actions/selectedClientRolesActions.ts +++ /dev/null @@ -1,16 +0,0 @@ -// selectedClientRolesActions.js -import { SET_SELECTED_CLIENT_ROLES } from '../constants/selectedClientRolesConstants'; -import { AppDispatch } from '../store'; -import { UserClientRolesType } from '../types/UserRoleType'; - -export interface SetSelectedClientRolesAction { - type: typeof SET_SELECTED_CLIENT_ROLES; - payload: UserClientRolesType; -} - -export const setSelectedClientRoles = (selectedClientRoles: UserClientRolesType) => (dispatch: AppDispatch) => { - dispatch({ - type: SET_SELECTED_CLIENT_ROLES, - payload: selectedClientRoles - }); -}; diff --git a/frontend/src/actions/userAction.ts b/frontend/src/actions/userAction.ts deleted file mode 100644 index 87d6369d..00000000 --- a/frontend/src/actions/userAction.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { - USER_DETAILS_REQUEST, - USER_DETAILS_SUCCESS, - USER_DETAILS_FAIL, - SET_CLIENT_ROLES -} from '../constants/userConstants'; -import { AppDispatch } from '../store'; -import { UserClientRolesType } from '../types/UserRoleType'; -import { useGetAuth } from '../contexts/AuthProvider'; - -const FAM_LOGIN_USER = 'famLoginUser'; - -export const getUserDetails = () => async (dispatch: AppDispatch) => { - try { - dispatch({ - type: USER_DETAILS_REQUEST - }); - //first call the isCurrent and only after that extract the JSON - const { isLoggedIn } = useGetAuth(); - - const userJSON = localStorage.getItem(FAM_LOGIN_USER); // Retrieve the JSON string from local storage - const user = userJSON ? JSON.parse(userJSON) : null; // Parse the JSON string to a JavaScript object - - dispatch({ - type: USER_DETAILS_SUCCESS, - payload: { ...user, isLoggedIn } - }); - } catch (error) { - dispatch({ - type: USER_DETAILS_FAIL, - payload: { error: error } - }); - } -}; - -export const setClientRoles = (clientRoles:UserClientRolesType[]) => (dispatch: AppDispatch) => { - dispatch({ - type: SET_CLIENT_ROLES, - payload: clientRoles - }); -}; diff --git a/frontend/src/amplifyconfiguration.ts b/frontend/src/amplifyconfiguration.ts index 31a482f0..0598eda2 100644 --- a/frontend/src/amplifyconfiguration.ts +++ b/frontend/src/amplifyconfiguration.ts @@ -3,7 +3,8 @@ import { env } from './env'; const ZONE = env.VITE_ZONE.toLocaleLowerCase(); const redirectUri = window.location.origin; const logoutDomain = `https://logon${ZONE === "prod"?'':'test'}7.gov.bc.ca`; -const retUrl = `https://${ZONE === "prod" ? "loginproxy" :ZONE === "test" ? "test.loginproxy": "dev.loginproxy"}.gov.bc.ca/auth/realms/standard/protocol/openid-connect/logout`; +const returnUrlHost = ZONE === "prod" ? "loginproxy" :ZONE === "test" ? "test.loginproxy": "dev.loginproxy"; +const retUrl = `https://${returnUrlHost}.gov.bc.ca/auth/realms/standard/protocol/openid-connect/logout`; const redirectSignOut = env.VITE_REDIRECT_SIGN_OUT && env.VITE_REDIRECT_SIGN_OUT.trim() !== "" diff --git a/frontend/src/components/ActionsTable/index.tsx b/frontend/src/components/ActionsTable/index.tsx index 30203d40..0dc84b85 100644 --- a/frontend/src/components/ActionsTable/index.tsx +++ b/frontend/src/components/ActionsTable/index.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Table, TableHead, @@ -14,8 +13,8 @@ import { ActivityTagFileFormatEnum, ActivityTagTypeEnum } from '../../types/Acti import { ITableHeader } from '../../types/TableHeader'; interface IActionsTable { - rows: RecentAction[]; - headers: ITableHeader[]; + readonly rows: RecentAction[]; + readonly headers: ITableHeader[]; } /** @@ -68,8 +67,8 @@ function ActionsTable(props: IActionsTable): JSX.Element { - {props.rows.map((row: RecentAction, idx: number) => ( - + {props.rows.map((row: RecentAction) => ( + {headerKeys.map((key: string) => ( {key === "statusCode" ? ( diff --git a/frontend/src/components/BCHeader/index.tsx b/frontend/src/components/BCHeader/index.tsx index c54f62ad..36a68854 100644 --- a/frontend/src/components/BCHeader/index.tsx +++ b/frontend/src/components/BCHeader/index.tsx @@ -1,5 +1,4 @@ import React from "react"; -import { Link } from "react-router-dom"; import { useThemePreference } from "../../utils/ThemePreference"; import { toggleTheme } from "../../utils/ThemeFunction"; import { @@ -16,7 +15,7 @@ import { SideNavItems, HeaderSideNavItems, } from '@carbon/react'; -import { NavLink } from "react-router-dom"; +import { NavLink , Link} from "react-router-dom"; import * as Icons from '@carbon/icons-react'; import './BCHeader.scss' import { HeaderContainerProps } from "./definitions"; @@ -26,78 +25,76 @@ const BCHeader: React.FC = () => { const { theme, setTheme } = useThemePreference(); return ( - <> - ( -
- - - - SILVA - - - Link 1 - Link 2 - Link 3 - - Sub-link 1 - Sub-link 2 - Sub-link 3 - - - + ( +
+ + + + SILVA + + + Link 1 + Link 2 + Link 3 + + Sub-link 1 + Sub-link 2 + Sub-link 3 + + + - {toggleTheme(theme,setTheme)}} - > - {/* Must have a child component */} - <>{theme === 'g10'?:} - + {toggleTheme(theme,setTheme)}} + > + {/* Must have a child component */} + <>{theme === 'g10'?:} + - - - - - - + - + aria-label="Help" + > + + + + + + - - - - - Link 1 - Link 2 - Link 3 - - Sub-link 1 - Sub-link 2 - Sub-link 3 - - - - -
- )} - /> - +
+ + + + Link 1 + Link 2 + Link 3 + + Sub-link 1 + Sub-link 2 + Sub-link 3 + + + + +
+ )} + /> ); }; diff --git a/frontend/src/components/OpeningHistory/index.tsx b/frontend/src/components/OpeningHistory/index.tsx index cad8b441..936968d4 100644 --- a/frontend/src/components/OpeningHistory/index.tsx +++ b/frontend/src/components/OpeningHistory/index.tsx @@ -1,3 +1,4 @@ +import React, { useState, useEffect } from "react"; import { ProgressIndicator, ProgressStep @@ -7,31 +8,55 @@ import History from '../../types/History'; import statusClass from '../../utils/HistoryStatus'; import FavoriteButton from '../FavoriteButton'; import './styles.scss'; +import { useNotification } from '../../contexts/NotificationProvider'; +import { fetchOpeningTrends, deleteOpeningFavorite } from "../../services/OpeningFavoriteService"; -import { deleteOpeningFavorite } from '../../services/OpeningFavoriteService'; -interface OpeningHistoryProps { - histories: History[]; -} +const OpeningHistory: React.FC = () => { + const { displayNotification } = useNotification(); + const [histories, setHistories] = useState([]); -const handleFavoriteChange = async (newStatus: boolean, openingId: number) => { - try { - if(!newStatus){ - await deleteOpeningFavorite(openingId); + const loadTrends = async () => { + const history = await fetchOpeningTrends(); + setHistories(history?.map(item => ({ id: item, steps: [] })) || []); + }; + + useEffect(() => { loadTrends(); },[]); + + + const handleFavoriteChange = async (newStatus: boolean, openingId: number) => { + try { + if(!newStatus){ + await deleteOpeningFavorite(openingId); + displayNotification({ + title: 'Favorite Removed', + subTitle: `Opening Id ${openingId} removed from favorites`, + type: 'success', + dismissIn: 8000, + onClose: () => {} + }); + loadTrends(); + } + } catch (error) { + displayNotification({ + title: 'Error', + subTitle: `Failed to update favorite status for ${openingId}`, + type: 'error', + dismissIn: 8000, + onClose: () => {} + }); } - } catch (error) { - console.error(`Failed to update favorite status for ${openingId}`); - } -}; + }; + + return ( -const OpeningHistory = ({ histories }: OpeningHistoryProps) => (
{histories.map((history, index) => (
-
+
(
); +}; export default OpeningHistory; diff --git a/frontend/src/components/OpeningMetricsTab/index.tsx b/frontend/src/components/OpeningMetricsTab/index.tsx index 44282295..7325af4b 100644 --- a/frontend/src/components/OpeningMetricsTab/index.tsx +++ b/frontend/src/components/OpeningMetricsTab/index.tsx @@ -1,18 +1,15 @@ -import React, { useRef, useEffect, useState } from "react"; +import React, { useRef, useEffect } from "react"; import './styles.scss'; import SectionTitle from "../SectionTitle"; import BarChartGrouped from "../BarChartGrouped"; import ChartContainer from "../ChartContainer"; import DoughnutChartView from "../DoughnutChartView"; import OpeningHistory from "../OpeningHistory"; -import History from "../../types/History"; import MyRecentActions from "../MyRecentActions"; -import { fetchOpeningTrends } from "../../services/OpeningFavoriteService"; const OpeningMetricsTab: React.FC = () => { const trackOpeningRef = useRef(null); - const [submissionTrends, setSubmissionTrends] = useState([]); - + // Optional: Scroll to "Track Openings" when this component mounts useEffect(() => { @@ -23,13 +20,6 @@ const OpeningMetricsTab: React.FC = () => { trackOpeningRef.current.scrollIntoView({ behavior: "smooth" }); } - const loadTrends = async () => { - const response = await fetchOpeningTrends(); - setSubmissionTrends(response.map(item => ({ id: item, steps: [] }))); - }; - - loadTrends(); - }, []); return ( @@ -48,10 +38,8 @@ const OpeningMetricsTab: React.FC = () => {
{/* Add ref here to scroll */} - - + +
diff --git a/frontend/src/components/OpeningsTab/index.tsx b/frontend/src/components/OpeningsTab/index.tsx index 6b71fff0..b72bff80 100644 --- a/frontend/src/components/OpeningsTab/index.tsx +++ b/frontend/src/components/OpeningsTab/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { Button } from '@carbon/react'; +import { Button, InlineNotification } from '@carbon/react'; import './styles.scss' import { Location } from '@carbon/icons-react'; import OpeningsMap from '../OpeningsMap'; @@ -7,71 +7,53 @@ import OpeningScreenDataTable from '../OpeningScreenDataTable/index'; import { columns } from '../Dashboard/Opening/RecentOpeningsDataTable/testData'; import SectionTitle from '../SectionTitle'; import TableSkeleton from '../TableSkeleton'; -import { InlineNotification } from '@carbon/react'; import { RecentOpening } from '../../types/RecentOpening'; -import { useSelector } from 'react-redux'; -import { RootState } from '../../store'; import { generateHtmlFile } from './layersGenerator'; import { getWmsLayersWhitelistUsers, WmsLayersWhitelistUser } from '../../services/SecretsService'; -import { useUserRecentOpeningQuery } from '../../services/queries/search/openingQueries'; -import RecentOpeningsDataTable from '../Dashboard/Opening/RecentOpeningsDataTable'; -import { ITableHeader } from '../../types/TableHeader'; +import { useGetAuth } from '../../contexts/AuthProvider'; interface Props { showSpatial: boolean; - setShowSpatial: Function; + setShowSpatial: (show: boolean) => void; } const OpeningsTab: React.FC = ({ showSpatial, setShowSpatial }) => { const [loadId, setLoadId] = useState(null); const [openingPolygonNotFound, setOpeningPolygonNotFound] = useState(false); - const [wmsUsersWhitelist, setWmsUsersWhitelist] = useState([]); - const userDetails = useSelector((state: RootState) => state.userDetails); - const { data, isFetching } = useUserRecentOpeningQuery(10); - const [headers, setHeaders] = useState(columns); + const [wmsUsersWhitelist, setWmsUsersWhitelist] = useState([]); + const { user } = useGetAuth(); - + useEffect(() => { + const fetchData = async () => { + try { + const rows: RecentOpening[] = await fetchRecentOpenings(); + setOpeningRows(rows); + setLoading(false); + setError(null); + } catch (error) { + console.error('Error fetching recent openings:', error); + setLoading(false); + setError('Failed to fetch recent openings'); + } + }; - useEffect(() => {}, [loadId, openingPolygonNotFound, wmsUsersWhitelist]); + const fetchAllowedPeople = async () => { + try { + const usersList: WmsLayersWhitelistUser[] = await getWmsLayersWhitelistUsers(); + setWmsUsersWhitelist(usersList); + } catch (error) { + console.error('Error fetching recent openings:', error); + } + }; - const toggleSpatial = () => { - setShowSpatial((prevShowSpatial :boolean) => !prevShowSpatial); - }; + fetchData(); + fetchAllowedPeople(); + }, []); - const onClickFn = () => { - const allowed: string[] = wmsUsersWhitelist.map((user: WmsLayersWhitelistUser) => user.userName); - const { userName } = userDetails.user; - if (allowed.includes(userName)) { - const newWindow = window.open(); - if (newWindow) { - newWindow.document.body.innerHTML = generateHtmlFile(); - } - } - }; + useEffect(() => {}, [loadId, openingPolygonNotFound, wmsUsersWhitelist]); - const handleCheckboxChange = (columnKey: string) => { - if(columnKey === "select-default"){ - //set to the deafult - setHeaders(columns) - } - else if(columnKey === "select-all"){ - setHeaders((prevHeaders) => - prevHeaders.map((header) => ({ - ...header, - selected: true, // Select all headers - })) - ); - } - else{ - setHeaders((prevHeaders) => - prevHeaders.map((header) => - header.key === columnKey - ? { ...header, selected: !header.selected } - : header - ) - ); - } - + const toggleSpatial = () => { + setShowSpatial(!showSpatial); }; return ( @@ -81,7 +63,6 @@ const OpeningsTab: React.FC = ({ showSpatial, setShowSpatial }) => { - -
-
-
- - ); - }; - -export default Help; \ No newline at end of file diff --git a/frontend/src/screens/Landing/index.tsx b/frontend/src/screens/Landing/index.tsx index 22d0668f..4c9416a9 100644 --- a/frontend/src/screens/Landing/index.tsx +++ b/frontend/src/screens/Landing/index.tsx @@ -25,58 +25,56 @@ const Landing: React.FC = () => { } return ( - <> -
-
-
-
- -
+
+
+
+
+ +
- {/* Welcome - Title and Subtitle */} -
-

Welcome to SILVA

-

- Plan, report, and analyze your silviculture activities -

+ {/* Welcome - Title and Subtitle */} +
+

Welcome to SILVA

+

+ Plan, report, and analyze your silviculture activities +

+
+ {/* Button Group */} +
+
+
- {/* Button Group */} -
-
- -
-
- -
+
+
+
+
+
+
+ {View}
-
- +
); }; diff --git a/frontend/src/screens/Opening/index.tsx b/frontend/src/screens/Opening/index.tsx index b5220d72..7ecdc3be 100644 --- a/frontend/src/screens/Opening/index.tsx +++ b/frontend/src/screens/Opening/index.tsx @@ -2,11 +2,13 @@ import React, {useEffect, useState} from "react"; import FavouriteCard from "../../components/FavouriteCard"; import PageTitle from "../../components/PageTitle"; import './Opening.scss' -import { TabList } from "@carbon/react"; -import { Tabs } from "@carbon/react"; -import { Tab } from "@carbon/react"; -import { TabPanels } from "@carbon/react"; -import { TabPanel } from "@carbon/react"; +import { + TabList, + Tabs, + Tab, + TabPanels, + TabPanel +} from "@carbon/react"; import OpeningsTab from "../../components/OpeningsTab"; import OpeningMetricsTab from "../../components/OpeningMetricsTab"; diff --git a/frontend/src/screens/SilvicultureSearch/index.tsx b/frontend/src/screens/SilvicultureSearch/index.tsx index faa59ccb..c50dae59 100644 --- a/frontend/src/screens/SilvicultureSearch/index.tsx +++ b/frontend/src/screens/SilvicultureSearch/index.tsx @@ -1,14 +1,15 @@ import React from "react"; import PageTitle from "../../components/PageTitle"; import './SilvicultureSearch.scss' -import { TabList } from "@carbon/react"; -import { Tabs } from "@carbon/react"; -import { Tab } from "@carbon/react"; -import { TabPanels } from "@carbon/react"; -import { TabPanel } from "@carbon/react"; +import { + TabList, + Tabs, + Tab, + TabPanels, + TabPanel +} from "@carbon/react"; import * as Icons from '@carbon/icons-react'; import OpeningsSearchTab from "../../components/SilvicultureSearch/Openings/OpeningsSearchTab"; -import { OpeningsSearchProvider } from "../../contexts/search/OpeningsSearch"; const SilvicultureSearch: React.FC = () => { @@ -26,9 +27,9 @@ const SilvicultureSearch: React.FC = () => {
Openings
-
Activities
-
Stocking standards
-
Standard units
+
Activities
+
Stocking standards
+
Standard units
diff --git a/frontend/src/store.ts b/frontend/src/store.ts deleted file mode 100644 index edba420c..00000000 --- a/frontend/src/store.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { createStore, combineReducers, applyMiddleware } from 'redux' -import { thunk } from 'redux-thunk' -import { composeWithDevTools } from '@redux-devtools/extension' -import { userDetailsReducer } from './reducers/userReducer' -import { UserClientRolesType } from './types/UserRoleType' -import { selectedClientRolesReducer } from './reducers/selectedClientRolesReducer' -import { FamLoginUser } from './services/AuthService' - -const reducer = combineReducers({ - userDetails: userDetailsReducer, - selectedClientRoles: selectedClientRolesReducer -}); - -const FAM_LOGIN_USER = 'famLoginUser'; - - -const userInfoFromStorage = JSON.parse(localStorage.getItem(FAM_LOGIN_USER) as string) as - | FamLoginUser - | undefined - | null; - -const selectedClientRolesFromStorage = JSON.parse(localStorage.getItem('selectedClientRoles') as string) as - | UserClientRolesType - | undefined - | null; - -// set the initial state -const initialState: object = { - userDetails: { - user: { - ...userInfoFromStorage, - isLoggedIn: !!userInfoFromStorage?.authToken - }, - loading: true, - error: false - }, - selectedClientRoles: selectedClientRolesFromStorage -}; - -const middleware = [thunk]; - -const store = createStore( - reducer, - initialState, - composeWithDevTools(applyMiddleware(...middleware)) -); - -export type RootState = ReturnType; -export type AppDispatch = typeof store.dispatch; - -export default store; diff --git a/frontend/src/types/NotificationType.ts b/frontend/src/types/NotificationType.ts new file mode 100644 index 00000000..0e0e3bd5 --- /dev/null +++ b/frontend/src/types/NotificationType.ts @@ -0,0 +1,8 @@ +export interface NotificationContent { + title: string; + subTitle: string; + buttonLabel?: string; + dismissIn?: number; + type: 'info' | 'error' | 'success' | 'warning' | 'info-square' | 'error-square' | 'warning-alt'; + onClose: () => void; +} \ No newline at end of file diff --git a/frontend/src/utils/ThemePreference.tsx b/frontend/src/utils/ThemePreference.tsx index fb95ab1e..bddecb21 100644 --- a/frontend/src/utils/ThemePreference.tsx +++ b/frontend/src/utils/ThemePreference.tsx @@ -20,7 +20,7 @@ function useThemePreference() { } interface ThemePreferenceProps { - children?: ReactNode + readonly children?: ReactNode } /** diff --git a/frontend/src/views/LoginOrgSelection/index.tsx b/frontend/src/views/LoginOrgSelection/index.tsx deleted file mode 100644 index 901782e2..00000000 --- a/frontend/src/views/LoginOrgSelection/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { useEffect } from 'react'; -import { FlexGrid, Row, Column } from '@carbon/react'; -import OrganizationSelection from '../../components/OrganizationSelection'; -import { RootState } from '../../store'; -import { useSelector } from 'react-redux'; -import './styles.scss'; - -/** - * Renders the organization selection view after login. - * - * This view allows users to select which organization they are representing. - * It displays the user's name and username, prompting them to choose an organization. - * - * @returns {JSX.Element} The rendered LoginOrgSelection component. - */ -function LoginOrgSelection (): JSX.Element { - const userDetails = useSelector((state: RootState) => state.userDetails); - const user = userDetails.user; - - return ( - - - -
-

- Organization selection -

-

- {`${user?.firstName} ${user?.lastName} ${user.providerUsername ? "("+user.providerUsername+")": ""} select which organization you're representing.`} -

-
- -
-
-
- ); -}; - -export default LoginOrgSelection; diff --git a/frontend/src/views/LoginOrgSelection/styles.scss b/frontend/src/views/LoginOrgSelection/styles.scss deleted file mode 100644 index db688f12..00000000 --- a/frontend/src/views/LoginOrgSelection/styles.scss +++ /dev/null @@ -1,48 +0,0 @@ -@use '@carbon/type'; -@use '@bcgov-nr/nr-theme/design-tokens/variables.scss' as vars; - -.login-org-selection-grid { - background: lightgray; // temporary change for demo linear-gradient(180deg, #E0EFFF 0%, #F2EEDB 100%); - height: 100vh; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - margin-inline: 0; - max-inline-size: none; - - .row-container { - max-width: none; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - } - - .col-container { - display: flex; - flex-direction: column; - padding: min(4.63%, 3rem) min(4.63%, 4rem); - border-radius: 0.5rem; - background: var(--bx-layer-02); - /* Light Theme/Shadows/Menu */ - box-shadow: 0rem 0.125rem 0.375rem 0rem rgba(0, 0, 0, 0.30); - min-height: 50vh; - } - - .title-section { - text-align: left; - margin-bottom: 2.5rem; - - .title-text { - margin-bottom: 1rem; - font-size: 2.625rem; - } - - .subtitle-text { - @include type.type-style('body-01'); - color: var(--#{vars.$bcgov-prefix}-text-secondary); - font-size: 1rem; - } - } -}