diff --git a/web/.eslintrc.js b/web/.eslintrc.js
index b87ebf3..c8cc932 100644
--- a/web/.eslintrc.js
+++ b/web/.eslintrc.js
@@ -47,5 +47,33 @@ module.exports = {
"react/jsx-props-no-spreading": 0,
"react-hooks/exhaustive-deps": "off",
"@typescript-eslint/no-non-null-assertion": 0,
+ "import/order": [
+ "error",
+ {
+ groups: [
+ "builtin",
+ "external",
+ "internal",
+ "parent",
+ "sibling",
+ "index",
+ "object",
+ "type",
+ ],
+ pathGroups: [
+ {
+ pattern: "react",
+ group: "external",
+ position: "before",
+ },
+ ],
+ pathGroupsExcludedImportTypes: ["react"],
+ "newlines-between": "always",
+ alphabetize: {
+ order: "asc",
+ caseInsensitive: true,
+ },
+ },
+ ],
},
-};
\ No newline at end of file
+};
diff --git a/web/config-overrides.js b/web/config-overrides.js
index 9c6f894..dba303c 100644
--- a/web/config-overrides.js
+++ b/web/config-overrides.js
@@ -4,7 +4,7 @@ module.exports = function override(config) {
...config.resolve,
alias: {
...config.alias,
-"@components": path.resolve(__dirname, "src/components"),
+ "@components": path.resolve(__dirname, "src/components"),
"@consts": path.resolve(__dirname, "src/consts"),
"@interfaces": path.resolve(__dirname, "src/interfaces"),
"@janush-types": path.resolve(__dirname, "src/types"),
@@ -13,6 +13,7 @@ module.exports = function override(config) {
"@layouts": path.resolve(__dirname, "src/layouts"),
"@routing": path.resolve(__dirname, "src/routing"),
"@themes": path.resolve(__dirname, "src/themes"),
+ "@validations": path.resolve(__dirname, "src/validations"),
},
};
return config;
diff --git a/web/package.json b/web/package.json
index ab79a95..0161724 100644
--- a/web/package.json
+++ b/web/package.json
@@ -10,13 +10,14 @@
"@mui/icons-material": "^5.0.0-rc.1",
"@mui/material": "^5.0.0-rc.1",
"@mui/styles": "^5.0.0-rc.1",
+ "@mui/x-data-grid": "^5.10.0",
"aws-amplify": "^4.2.10",
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
"react-hook-form": "^7.15.4",
- "react-router-dom": "^5.3.0",
+ "react-router-dom": "^6.3.0",
"react-scripts": "4.0.3",
"web-vitals": "^2.1.4",
"yup": "^0.32.9"
@@ -103,4 +104,4 @@
"html"
]
}
-}
\ No newline at end of file
+}
diff --git a/web/src/App.test.tsx b/web/src/App.test.tsx
index e7c16bf..20a1d2f 100644
--- a/web/src/App.test.tsx
+++ b/web/src/App.test.tsx
@@ -1,4 +1,5 @@
import { render } from "@testing-library/react";
+
import App from "./App";
describe("", () => {
diff --git a/web/src/App.tsx b/web/src/App.tsx
index 5571fd0..855bb19 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -1,11 +1,11 @@
import React from "react";
import { Providers } from "@features/Providers/Providers";
-import { Routes } from "@routing/Routes";
+import { Router } from "@routing/Routes";
const App: React.VFC = () => (
-
+
);
diff --git a/web/src/components/AuthBottomBar/AuthBottomBar.test.tsx b/web/src/components/AuthBottomBar/AuthBottomBar.test.tsx
index 8cd82df..172d235 100644
--- a/web/src/components/AuthBottomBar/AuthBottomBar.test.tsx
+++ b/web/src/components/AuthBottomBar/AuthBottomBar.test.tsx
@@ -1,7 +1,6 @@
-import { MemoryRouter } from "react-router-dom";
-import { render } from "@testing-library/react";
-
import { AuthBottomBar } from "@components/AuthBottomBar/AuthBottomBar";
+import { render } from "@testing-library/react";
+import { MemoryRouter } from "react-router-dom";
describe("", () => {
it("should render properly", () => {
diff --git a/web/src/components/AuthBottomBar/AuthBottomBar.tsx b/web/src/components/AuthBottomBar/AuthBottomBar.tsx
index 6cc180c..6c7d090 100644
--- a/web/src/components/AuthBottomBar/AuthBottomBar.tsx
+++ b/web/src/components/AuthBottomBar/AuthBottomBar.tsx
@@ -1,7 +1,7 @@
import React from "react";
-import { Box, Button, Grid, Typography } from "@mui/material";
import { Link } from "@components/Link/Link";
+import { Box, Button, Grid, Typography } from "@mui/material";
interface Props {
buttonText: string;
diff --git a/web/src/components/Button/Button.tsx b/web/src/components/Button/Button.tsx
new file mode 100644
index 0000000..fd36a4e
--- /dev/null
+++ b/web/src/components/Button/Button.tsx
@@ -0,0 +1,38 @@
+import { FC } from "react";
+
+import { Button as MuiButton, Theme, ButtonProps } from "@mui/material";
+import { useTheme } from "@mui/material/styles";
+import { rgbaColors } from "@themes/palette";
+
+export const Button: FC = ({ children, ...buttonProps }) => {
+ const theme = useTheme();
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/web/src/components/ConfirmationDialog/ConfirmationDialog.tsx b/web/src/components/ConfirmationDialog/ConfirmationDialog.tsx
new file mode 100644
index 0000000..f1a1bdf
--- /dev/null
+++ b/web/src/components/ConfirmationDialog/ConfirmationDialog.tsx
@@ -0,0 +1,85 @@
+import { FC, ReactNode } from "react";
+
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Theme,
+ Button,
+} from "@mui/material";
+import { useTheme } from "@mui/material/styles";
+
+interface Props {
+ children: ReactNode;
+ isOpen: boolean;
+ onSubmit: () => void;
+ onCancelClick: () => void;
+ submitButtonTitle: string;
+}
+
+const ConfirmationDialogTitle: FC = ({ children }) => {
+ const theme = useTheme();
+
+ return (
+
+ {children}
+
+ );
+};
+
+const ConfirmationDialogContent: FC = ({ children }) => {
+ const theme = useTheme();
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const ConfirmationDialog = ({
+ children,
+ isOpen,
+ onSubmit,
+ onCancelClick,
+ submitButtonTitle,
+}: Props) => {
+ const theme = useTheme();
+
+ return (
+
+ );
+};
+
+ConfirmationDialog.Title = ConfirmationDialogTitle;
+ConfirmationDialog.Content = ConfirmationDialogContent;
diff --git a/web/src/components/EmailField/EmailField.tsx b/web/src/components/EmailField/EmailField.tsx
index a468d5f..507f300 100644
--- a/web/src/components/EmailField/EmailField.tsx
+++ b/web/src/components/EmailField/EmailField.tsx
@@ -1,8 +1,7 @@
+import { TextField } from "@components/TextField/TextField";
import { Mail } from "@mui/icons-material";
import { InputAdornment, StandardTextFieldProps } from "@mui/material";
-import { TextField } from "@components/TextField/TextField";
-
interface Props extends StandardTextFieldProps {
errorMessage?: string | undefined;
}
diff --git a/web/src/components/ErrorNotification/ErrorNotification.tsx b/web/src/components/ErrorNotification/ErrorNotification.tsx
new file mode 100644
index 0000000..9824a26
--- /dev/null
+++ b/web/src/components/ErrorNotification/ErrorNotification.tsx
@@ -0,0 +1,37 @@
+import { VFC } from "react";
+
+import { Theme, Snackbar, Alert } from "@mui/material";
+import { useTheme } from "@mui/material/styles";
+
+interface Props {
+ content?: string;
+ show: boolean;
+ onClose: () => void;
+}
+
+export const ErrorNotification: VFC = ({ content, show, onClose }) => {
+ const theme = useTheme();
+
+ return (
+
+
+ {content || "Something went wrong. Please try again."}
+
+
+ );
+};
diff --git a/web/src/components/FormInput/FormInput.tsx b/web/src/components/FormInput/FormInput.tsx
new file mode 100644
index 0000000..2a60e3a
--- /dev/null
+++ b/web/src/components/FormInput/FormInput.tsx
@@ -0,0 +1,20 @@
+import { VFC } from "react";
+
+import { TextField, StandardTextFieldProps } from "@mui/material";
+
+interface Props extends StandardTextFieldProps {
+ errorMessage?: string | undefined;
+}
+
+export const FormInput: VFC = ({
+ errorMessage,
+ ...restProps
+}: Props) => (
+
+);
diff --git a/web/src/components/Link/Link.tsx b/web/src/components/Link/Link.tsx
index db5ed47..ad1dce5 100644
--- a/web/src/components/Link/Link.tsx
+++ b/web/src/components/Link/Link.tsx
@@ -1,6 +1,7 @@
import React from "react";
-import { Link as RouterLink } from "react-router-dom";
+
import { Link as MuiLink, LinkProps } from "@mui/material";
+import { Link as RouterLink } from "react-router-dom";
type Props = Omit & {
to: string;
diff --git a/web/src/components/ListElement/ListElement.tsx b/web/src/components/ListElement/ListElement.tsx
new file mode 100644
index 0000000..e4e0882
--- /dev/null
+++ b/web/src/components/ListElement/ListElement.tsx
@@ -0,0 +1,29 @@
+import { VFC } from "react";
+
+import { Box, Typography, Theme } from "@mui/material";
+import { useTheme } from "@mui/material/styles";
+
+interface Props {
+ label: string;
+ value: string;
+}
+
+export const ListElement: VFC = ({ label, value }) => {
+ const theme = useTheme();
+
+ return (
+
+
+ {label}
+
+
+ {value}
+
+
+ );
+};
diff --git a/web/src/components/PasswordField/PasswordField.test.tsx b/web/src/components/PasswordField/PasswordField.test.tsx
index bfcbe62..85b3f4c 100644
--- a/web/src/components/PasswordField/PasswordField.test.tsx
+++ b/web/src/components/PasswordField/PasswordField.test.tsx
@@ -1,8 +1,9 @@
import React from "react";
+
+import { ThemeProvider } from "@features/ThemeProvider/ThemeProvider";
import { fireEvent, render } from "@testing-library/react";
import { useForm } from "react-hook-form";
-import { ThemeProvider } from "@features/ThemeProvider/ThemeProvider";
import { PasswordField } from "./PasswordField";
const setup = () => {
diff --git a/web/src/components/PasswordField/PasswordField.tsx b/web/src/components/PasswordField/PasswordField.tsx
index 825b55d..34b0941 100644
--- a/web/src/components/PasswordField/PasswordField.tsx
+++ b/web/src/components/PasswordField/PasswordField.tsx
@@ -1,4 +1,6 @@
import React, { useState } from "react";
+
+import { TextField } from "@components/TextField/TextField";
import { Lock, Visibility, VisibilityOff } from "@mui/icons-material";
import {
StandardTextFieldProps,
@@ -8,8 +10,6 @@ import {
Tooltip,
} from "@mui/material";
import { useTheme } from "@mui/material/styles";
-
-import { TextField } from "@components/TextField/TextField";
import { formDataTestId } from "@utils/formDataTestId/formDataTestId";
interface Props extends StandardTextFieldProps {
diff --git a/web/src/components/Select/Select.tsx b/web/src/components/Select/Select.tsx
new file mode 100644
index 0000000..a03f4e7
--- /dev/null
+++ b/web/src/components/Select/Select.tsx
@@ -0,0 +1,29 @@
+import { FC } from "react";
+
+import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
+import { Select as MuiSelect, MenuItem, SelectProps } from "@mui/material";
+
+interface Option {
+ name: string;
+ label: string;
+}
+
+interface Props extends SelectProps {
+ options: Option[];
+}
+
+export const Select: FC = ({ options, ...selectProps }) => {
+ return (
+
+ {options.map((option) => (
+
+ ))}
+
+ );
+};
diff --git a/web/src/components/Table/Table.tsx b/web/src/components/Table/Table.tsx
new file mode 100644
index 0000000..a167339
--- /dev/null
+++ b/web/src/components/Table/Table.tsx
@@ -0,0 +1,28 @@
+import { ReactNode } from "react";
+
+import {
+ Table as MuiTable,
+ TableContainer,
+ TableHead,
+ TableBody,
+ Paper,
+} from "@mui/material";
+
+import { TableCell } from "./TableCell";
+import { TableRow } from "./TableRow";
+
+interface Props {
+ children: ReactNode;
+ dataTestId?: string;
+}
+
+export const Table = ({ children, ...restProps }: Props) => (
+
+ {children}
+
+);
+
+Table.TableCell = TableCell;
+Table.TableBody = TableBody;
+Table.TableHead = TableHead;
+Table.TableRow = TableRow;
diff --git a/web/src/components/Table/TableCell.tsx b/web/src/components/Table/TableCell.tsx
new file mode 100644
index 0000000..036a466
--- /dev/null
+++ b/web/src/components/Table/TableCell.tsx
@@ -0,0 +1,30 @@
+import { FC, ReactNode } from "react";
+
+import {
+ TableCell as MuiTableCell,
+ TableCellProps,
+ Theme,
+} from "@mui/material";
+import { useTheme } from "@mui/material/styles";
+
+interface Props extends TableCellProps {
+ children: ReactNode;
+}
+
+export const TableCell: FC = ({ children, ...tableCellProps }) => {
+ const theme = useTheme();
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/web/src/components/Table/TableRow.tsx b/web/src/components/Table/TableRow.tsx
new file mode 100644
index 0000000..7cd35e7
--- /dev/null
+++ b/web/src/components/Table/TableRow.tsx
@@ -0,0 +1,11 @@
+import { ReactNode, FC } from "react";
+
+import { TableRow as MuiTableRow, TableRowProps } from "@mui/material";
+
+interface Props extends TableRowProps {
+ children: ReactNode;
+}
+
+export const TableRow: FC = ({ children, ...rowProps }) => (
+ {children}
+);
diff --git a/web/src/components/icons/ArrowLeftIcon/ArrowLeftIcon.tsx b/web/src/components/icons/ArrowLeftIcon/ArrowLeftIcon.tsx
new file mode 100644
index 0000000..b75fb13
--- /dev/null
+++ b/web/src/components/icons/ArrowLeftIcon/ArrowLeftIcon.tsx
@@ -0,0 +1,18 @@
+import { SVGProps } from "react";
+
+export const ArrowLeftIcon = (props: SVGProps) => (
+
+);
diff --git a/web/src/components/icons/SearchIcon/SearchIcon.tsx b/web/src/components/icons/SearchIcon/SearchIcon.tsx
new file mode 100644
index 0000000..d1bc30b
--- /dev/null
+++ b/web/src/components/icons/SearchIcon/SearchIcon.tsx
@@ -0,0 +1,17 @@
+import { SVGProps } from "react";
+
+export const SearchIcon = (props: SVGProps) => (
+
+);
diff --git a/web/src/features/Providers/Providers.tsx b/web/src/features/Providers/Providers.tsx
index 763c7f2..657d891 100644
--- a/web/src/features/Providers/Providers.tsx
+++ b/web/src/features/Providers/Providers.tsx
@@ -1,5 +1,4 @@
import React from "react";
-import { BrowserRouter as Router } from "react-router-dom";
import { SuspenseProvider } from "@features/SuspenseProvider/SuspenseProvider";
import { ThemeProvider } from "@features/ThemeProvider/ThemeProvider";
@@ -9,9 +8,7 @@ export const Providers: React.FC = ({ children }) => {
return (
-
- {children}
-
+ {children}
);
diff --git a/web/src/features/SuspenseProvider/SuspenseProvider.tsx b/web/src/features/SuspenseProvider/SuspenseProvider.tsx
index 7582246..8497252 100644
--- a/web/src/features/SuspenseProvider/SuspenseProvider.tsx
+++ b/web/src/features/SuspenseProvider/SuspenseProvider.tsx
@@ -1,4 +1,5 @@
import React from "react";
+
import { LinearProgress } from "@mui/material";
import { useStyles } from "./styles";
diff --git a/web/src/features/ThemeProvider/ThemeProvider.tsx b/web/src/features/ThemeProvider/ThemeProvider.tsx
index f3ab6c3..5f33f61 100644
--- a/web/src/features/ThemeProvider/ThemeProvider.tsx
+++ b/web/src/features/ThemeProvider/ThemeProvider.tsx
@@ -2,7 +2,6 @@ import React from "react";
import { createTheme, CssBaseline, Theme, useMediaQuery } from "@mui/material";
import { ThemeProvider as MuiThemeProvider } from "@mui/material/styles";
-
import { getPalette } from "@themes/palette";
export interface ThemeContextValue {
diff --git a/web/src/features/UserProvider/UserProvider.tsx b/web/src/features/UserProvider/UserProvider.tsx
index 9190144..670ffda 100644
--- a/web/src/features/UserProvider/UserProvider.tsx
+++ b/web/src/features/UserProvider/UserProvider.tsx
@@ -1,9 +1,9 @@
import React, { useEffect, useState } from "react";
-import { Auth, Hub } from "aws-amplify";
import { User } from "@interfaces/User";
import { HubEvent } from "@janush-types/enums/HubEvent";
import { Nullable } from "@janush-types/useful";
+import { Auth, Hub } from "aws-amplify";
export interface UserContextValue {
user?: Nullable;
diff --git a/web/src/index.tsx b/web/src/index.tsx
index 5f6a44d..c1a7289 100644
--- a/web/src/index.tsx
+++ b/web/src/index.tsx
@@ -1,10 +1,11 @@
import React from "react";
+
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
-import reportWebVitals from "./reportWebVitals";
import { configureAws } from "./awsConfig";
+import reportWebVitals from "./reportWebVitals";
configureAws();
diff --git a/web/src/layouts/AuthLayout/AuthLayout.tsx b/web/src/layouts/AuthLayout/AuthLayout.tsx
index 779cda3..ed1e283 100644
--- a/web/src/layouts/AuthLayout/AuthLayout.tsx
+++ b/web/src/layouts/AuthLayout/AuthLayout.tsx
@@ -1,4 +1,5 @@
import React from "react";
+
import { Box, Container, ContainerProps } from "@mui/material";
import { useStyles } from "./styles";
diff --git a/web/src/layouts/Logo/Logo.tsx b/web/src/layouts/Logo/Logo.tsx
index 582bbb0..11c4ce9 100644
--- a/web/src/layouts/Logo/Logo.tsx
+++ b/web/src/layouts/Logo/Logo.tsx
@@ -1,4 +1,5 @@
import React from "react";
+
import { Typography } from "@mui/material";
import { Link } from "react-router-dom";
diff --git a/web/src/layouts/Modals/FormModalLayout/FormModalLayout.tsx b/web/src/layouts/Modals/FormModalLayout/FormModalLayout.tsx
new file mode 100644
index 0000000..1a58be1
--- /dev/null
+++ b/web/src/layouts/Modals/FormModalLayout/FormModalLayout.tsx
@@ -0,0 +1,75 @@
+import { FC } from "react";
+
+import { Button } from "@components/Button/Button";
+import CloseIcon from "@mui/icons-material/Close";
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Theme,
+} from "@mui/material";
+import IconButton from "@mui/material/IconButton";
+import { useTheme } from "@mui/material/styles";
+import { rgbaColors } from "@themes/palette";
+
+interface Props {
+ isOpen: boolean;
+ title: string;
+ buttonTitle: string;
+ isButtonDisabled?: boolean;
+ onSubmit?: () => void;
+ onModalClose: () => void;
+}
+
+export const FormModalLayout: FC = ({
+ children,
+ isOpen,
+ title,
+ buttonTitle,
+ isButtonDisabled = false,
+ onSubmit,
+ onModalClose,
+}) => {
+ const theme = useTheme();
+
+ return (
+
+ );
+};
diff --git a/web/src/layouts/PageLayout/PageLayout.test.tsx b/web/src/layouts/PageLayout/PageLayout.test.tsx
index 4bdbf78..f1a53f5 100644
--- a/web/src/layouts/PageLayout/PageLayout.test.tsx
+++ b/web/src/layouts/PageLayout/PageLayout.test.tsx
@@ -1,7 +1,7 @@
import React from "react";
-import { render } from "@testing-library/react";
import { PageLayout } from "@layouts/PageLayout/PageLayout";
+import { render } from "@testing-library/react";
describe("", () => {
it("should render", async () => {
diff --git a/web/src/layouts/PageLayout/PageLayout.tsx b/web/src/layouts/PageLayout/PageLayout.tsx
index 41fb75b..c29616b 100644
--- a/web/src/layouts/PageLayout/PageLayout.tsx
+++ b/web/src/layouts/PageLayout/PageLayout.tsx
@@ -1,4 +1,5 @@
import React from "react";
+
import { Box, BoxProps, Container, ContainerProps } from "@mui/material";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
diff --git a/web/src/layouts/TopAppBar/TopAppBar.tsx b/web/src/layouts/TopAppBar/TopAppBar.tsx
index fd60b66..f2ba5d1 100644
--- a/web/src/layouts/TopAppBar/TopAppBar.tsx
+++ b/web/src/layouts/TopAppBar/TopAppBar.tsx
@@ -1,13 +1,17 @@
-import React from "react";
-import { AppBar, Toolbar, Button } from "@mui/material";
+import { VFC } from "react";
+import { useUserContext } from "@features/UserProvider/useUserContext";
import { Logo } from "@layouts/Logo/Logo";
+import { AppBar, Toolbar, Button } from "@mui/material";
+import { Paths } from "@routing/paths";
import { Auth } from "aws-amplify";
-import { useUserContext } from "@features/UserProvider/useUserContext";
import { NavLink } from "react-router-dom";
-import { Paths } from "@routing/paths";
-export const TopAppBar: React.VFC = () => {
+interface Props {
+ showLogo?: boolean;
+}
+
+export const TopAppBar: VFC = ({ showLogo = true }) => {
const { user } = useUserContext();
const signOut = async () => {
@@ -19,8 +23,8 @@ export const TopAppBar: React.VFC = () => {
};
return (
-
-
+
+ {showLogo && }
{user ? (
+ }
+ >
+
+ setIsCreateGroupModalOpen(false)}
+ variant={GroupModalVariant.Create}
+ />
+
+ );
+};
+
+export default Groups;
diff --git a/web/src/routing/routes/UsersAdministration/Groups/GroupsTable/GroupsTable.tsx b/web/src/routing/routes/UsersAdministration/Groups/GroupsTable/GroupsTable.tsx
new file mode 100644
index 0000000..d50e133
--- /dev/null
+++ b/web/src/routing/routes/UsersAdministration/Groups/GroupsTable/GroupsTable.tsx
@@ -0,0 +1,73 @@
+import { VFC } from "react";
+
+import { Table } from "@components/Table/Table";
+import { Group } from "@janush-types/group";
+import { Theme } from "@mui/material";
+import { useTheme } from "@mui/material/styles";
+import { rgbaColors } from "@themes/palette";
+import { useNavigate } from "react-router-dom";
+
+const columns = [
+ { name: "name", label: "Name" },
+ { name: "description", label: "Description" },
+ { name: "members", label: "Members" },
+ { name: "lastModified", label: "Last Modified" },
+];
+
+interface Props {
+ data: Group[];
+}
+
+const { TableHead, TableRow, TableCell, TableBody } = Table;
+
+const GroupsTable: VFC = ({ data }) => {
+ const navigate = useNavigate();
+ const theme = useTheme();
+
+ const onRowClick = (group: Group) => {
+ navigate(group.id, { state: { group } });
+ };
+
+ return (
+
+
+
+ {columns.map((column) => (
+ {column.label}
+ ))}
+
+
+
+ {data.length ? (
+ data.map((item) => (
+ onRowClick(item)}
+ >
+ {item.name}
+ {item.description}
+ {item.members}
+
+ {item.lastModified}
+
+
+ ))
+ ) : (
+
+
+ No results
+
+
+ )}
+
+
+ );
+};
+
+export default GroupsTable;
diff --git a/web/src/routing/routes/UsersAdministration/Groups/Modals/AddToGroupModal/AddToGroupModal.tsx b/web/src/routing/routes/UsersAdministration/Groups/Modals/AddToGroupModal/AddToGroupModal.tsx
new file mode 100644
index 0000000..d8fc2dd
--- /dev/null
+++ b/web/src/routing/routes/UsersAdministration/Groups/Modals/AddToGroupModal/AddToGroupModal.tsx
@@ -0,0 +1,69 @@
+import { VFC } from "react";
+
+import { Select } from "@components/Select/Select";
+import { FormModalLayout } from "@layouts/Modals/FormModalLayout/FormModalLayout";
+import { Box, Typography, Theme } from "@mui/material";
+import { useTheme } from "@mui/material/styles";
+import { useForm, Controller } from "react-hook-form";
+
+const groupOptions = [
+ { name: "group1", label: "Group1" },
+ { name: "group2", label: "Group2" },
+ { name: "group3", label: "Group3" },
+];
+
+interface FormData {
+ group: string;
+}
+
+interface Props {
+ isOpen: boolean;
+ onModalClose: () => void;
+}
+
+export const AddToGroupModal: VFC = ({ ...props }) => {
+ const theme = useTheme();
+
+ const { control, formState, handleSubmit } = useForm({
+ mode: "onChange",
+ defaultValues: {
+ group: "group1",
+ },
+ });
+
+ return (
+
+
+ b.szurek@codeandpepper.com
+
+
+ Group
+ (
+
+ )}
+ />
+
+
+ );
+};
diff --git a/web/src/routing/routes/UsersAdministration/Groups/Modals/GroupModal/GroupModal.tsx b/web/src/routing/routes/UsersAdministration/Groups/Modals/GroupModal/GroupModal.tsx
new file mode 100644
index 0000000..3e90d9a
--- /dev/null
+++ b/web/src/routing/routes/UsersAdministration/Groups/Modals/GroupModal/GroupModal.tsx
@@ -0,0 +1,118 @@
+import { VFC } from "react";
+
+import { FormInput } from "@components/FormInput/FormInput";
+import { Select } from "@components/Select/Select";
+import { yupResolver } from "@hookform/resolvers/yup";
+import { FormModalLayout } from "@layouts/Modals/FormModalLayout/FormModalLayout";
+import { Box, Typography, Theme } from "@mui/material";
+import { useTheme } from "@mui/material/styles";
+import { emailSchema } from "@validations/UserValidation";
+import { useForm, Controller } from "react-hook-form";
+
+export enum GroupModalVariant {
+ Create = "create",
+ Edit = "edit",
+}
+
+const groupRoleOptions = [
+ { name: "groupRole1", label: "GroupRole1" },
+ { name: "groupRole2", label: "GroupRole2" },
+ { name: "groupRole3", label: "GroupRole3" },
+];
+
+interface FormData {
+ name: string;
+ description: string;
+ groupRole: string;
+}
+
+interface Props {
+ variant: GroupModalVariant;
+ isOpen: boolean;
+ onModalClose: () => void;
+}
+
+export const GroupModal: VFC = ({ variant, ...restProps }) => {
+ const theme = useTheme();
+
+ const { control, formState, handleSubmit } = useForm({
+ mode: "onChange",
+ resolver: yupResolver(emailSchema),
+ defaultValues: {
+ name: "",
+ description: "",
+ groupRole: "groupRole1",
+ },
+ });
+
+ return (
+
+ (
+
+ )}
+ />
+ (
+
+ )}
+ />
+
+
+ {variant === GroupModalVariant.Create
+ ? "Select a group role"
+ : "Group role"}
+
+ (
+
+ )}
+ />
+
+
+ );
+};
diff --git a/web/src/routing/routes/UsersAdministration/Users/Modals/UserModal/UserModal.tsx b/web/src/routing/routes/UsersAdministration/Users/Modals/UserModal/UserModal.tsx
new file mode 100644
index 0000000..2fb19f8
--- /dev/null
+++ b/web/src/routing/routes/UsersAdministration/Users/Modals/UserModal/UserModal.tsx
@@ -0,0 +1,149 @@
+import { VFC } from "react";
+
+import { FormInput } from "@components/FormInput/FormInput";
+import { yupResolver } from "@hookform/resolvers/yup";
+import { FormModalLayout } from "@layouts/Modals/FormModalLayout/FormModalLayout";
+import { Box, Checkbox, FormControlLabel } from "@mui/material";
+import { emailSchema } from "@validations/UserValidation";
+import { useForm, Controller } from "react-hook-form";
+
+export enum UserModalVariant {
+ Create = "create",
+ Edit = "edit",
+}
+
+interface FormData {
+ email: string;
+ phoneNumber: string;
+ isEmailVerified: boolean;
+ password: string;
+ shouldResendVerificationEmail: boolean;
+ shouldResendInvitationEmail: boolean;
+}
+
+interface Props {
+ isOpen: boolean;
+ onModalClose: () => void;
+ variant: UserModalVariant;
+}
+
+export const UserModal: VFC = ({ variant, ...restProps }) => {
+ const { control, formState, handleSubmit } = useForm({
+ mode: "onChange",
+ resolver: yupResolver(emailSchema),
+ defaultValues: {
+ email: "",
+ phoneNumber: "",
+ isEmailVerified: true,
+ password: "",
+ shouldResendVerificationEmail: false,
+ shouldResendInvitationEmail: false,
+ },
+ });
+
+ return (
+
+ (
+
+ )}
+ />
+ (
+
+ )}
+ />
+ {variant === UserModalVariant.Edit && (
+ (
+
+ )}
+ />
+ )}
+ {variant === UserModalVariant.Create && (
+ (
+ }
+ label="Mark email as verified"
+ sx={{
+ mt: 2,
+ "& .MuiFormControlLabel-label": {
+ fontSize: "12px",
+ },
+ }}
+ />
+ )}
+ />
+ )}
+ {variant === UserModalVariant.Edit && (
+
+ (
+ }
+ label="Resend verification email"
+ sx={{
+ ml: 1,
+ mt: 2,
+ "& .MuiFormControlLabel-label": {
+ fontSize: "12px",
+ },
+ }}
+ />
+ )}
+ />
+ (
+ }
+ label="Resend invitation email"
+ sx={{
+ ml: 1,
+ "& .MuiFormControlLabel-label": {
+ fontSize: "12px",
+ },
+ }}
+ />
+ )}
+ />
+
+ )}
+
+ );
+};
diff --git a/web/src/routing/routes/UsersAdministration/Users/UserDetails/UserDetails.tsx b/web/src/routing/routes/UsersAdministration/Users/UserDetails/UserDetails.tsx
new file mode 100644
index 0000000..58889b5
--- /dev/null
+++ b/web/src/routing/routes/UsersAdministration/Users/UserDetails/UserDetails.tsx
@@ -0,0 +1,114 @@
+import { useState, VFC, FC } from "react";
+
+import { Button } from "@components/Button/Button";
+import { ConfirmationDialog } from "@components/ConfirmationDialog/ConfirmationDialog";
+import { ListElement } from "@components/ListElement/ListElement";
+import { User } from "@janush-types/user";
+import { UsersAdministrationLayout } from "@layouts/UsersAdministrationLayout/UsersAdministrationLayout";
+import { Box, Typography, Theme, ButtonProps } from "@mui/material";
+import { useTheme } from "@mui/material/styles";
+import { AddToGroupModal } from "@routing/routes/UsersAdministration/Groups/Modals/AddToGroupModal/AddToGroupModal";
+import { rgbaColors } from "@themes/palette";
+import { useLocation, useNavigate } from "react-router-dom";
+
+import { UserModal, UserModalVariant } from "../Modals/UserModal/UserModal";
+
+interface LocationState {
+ user: User;
+}
+
+const ButtonWrapper: FC = ({ children, ...buttonProps }) => (
+
+ {children}
+
+);
+
+const UserDetails: VFC = () => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+
+ const location = useLocation();
+ // TODO: change to get user data from backend
+ const { user } = location.state as LocationState;
+
+ const [isEditUserModalOpen, setIsEditUserModalOpen] = useState(false);
+ const [isAddToGroupModalOpen, setIsAddToGroupModalOpen] = useState(false);
+ const [isRemoveUserModalOpen, setIsRemoveUserModalOpen] = useState(false);
+
+ return (
+ navigate(-1)}
+ buttons={
+ <>
+ setIsEditUserModalOpen(true)}>
+ Edit
+
+ setIsAddToGroupModalOpen(true)}>
+ Add to group
+
+ {/* TODO: Handle enable button while implementing backend */}
+ Enable
+ {/* TODO: Handle disable button while implementing backend */}
+ Disable
+ setIsRemoveUserModalOpen(true)}>
+ Remove
+
+ >
+ }
+ >
+
+
+ Account Details
+
+
+
+
+
+
+
+
+ {/* TODO: set value from server */}
+
+
+ setIsEditUserModalOpen(false)}
+ variant={UserModalVariant.Edit}
+ />
+ setIsAddToGroupModalOpen(false)}
+ />
+ setIsRemoveUserModalOpen(false)}
+ // TODO: Add function for submitting behavior while backend implementation
+ onSubmit={() => null}
+ submitButtonTitle="Remove user"
+ >
+ Removing user
+
+ Are you sure you want to remove this user?
+
+
+
+ );
+};
+
+export default UserDetails;
diff --git a/web/src/routing/routes/UsersAdministration/Users/Users.tsx b/web/src/routing/routes/UsersAdministration/Users/Users.tsx
new file mode 100644
index 0000000..a21d409
--- /dev/null
+++ b/web/src/routing/routes/UsersAdministration/Users/Users.tsx
@@ -0,0 +1,209 @@
+import { useState, VFC } from "react";
+
+import { Button } from "@components/Button/Button";
+import { SearchIcon } from "@components/icons/SearchIcon/SearchIcon";
+import { Select } from "@components/Select/Select";
+import { User } from "@janush-types/user";
+import { UsersAdministrationLayout } from "@layouts/UsersAdministrationLayout/UsersAdministrationLayout";
+import { Box, Typography } from "@mui/material";
+import Input from "@mui/material/Input";
+import InputAdornment from "@mui/material/InputAdornment";
+import { rgbaColors } from "@themes/palette";
+import { getOptionsFromEnum } from "@utils/getOptionsFromEnum/getOptionsFromEnum";
+import { useNavigate } from "react-router-dom";
+
+import { UserModal, UserModalVariant } from "./Modals/UserModal/UserModal";
+import UsersTable from "./UsersTable/UsersTable";
+
+enum SearchBy {
+ ID = "id",
+ Email = "email",
+ Access = "access",
+ Status = "status",
+}
+
+enum Status {
+ All = "all",
+ Confirmed = "confirmed",
+ Unconfirmed = "unconfirmed",
+}
+
+enum Access {
+ All = "all",
+ Enabled = "enabled",
+ Disabled = "disabled",
+}
+
+interface Search {
+ searchBy: SearchBy;
+ searchFor: string;
+}
+
+// TODO: Remove it after getting data from backend
+const data: User[] = [
+ {
+ id: "1c4a9a8d-b652-421a-b130-9ad680029521",
+ email: "b.szurek@codeandpepper.com",
+ phoneNumber: "+48 666 777 888",
+ access: true,
+ status: true,
+ lastModified: "15-06-2021",
+ },
+ {
+ id: "1c4a9a8d-b652-421a-b130-9ad680029522",
+ email: "b.szurek@codeandpepper.com",
+ phoneNumber: "+48 666 777 888",
+ access: false,
+ status: false,
+ lastModified: "15-06-2021",
+ },
+ {
+ id: "1c4a9a8d-b652-421a-b130-9ad680029523",
+ email: "b.szurek@codeandpepper.com",
+ phoneNumber: "+48 666 777 888",
+ access: true,
+ status: false,
+ lastModified: "15-06-2021",
+ },
+];
+
+const Users: VFC = () => {
+ const [search, setSearch] = useState({
+ searchBy: SearchBy.Email,
+ searchFor: "",
+ });
+ const [status, setStatus] = useState(Status.All);
+ const [access, setAccess] = useState(Access.All);
+ const [isCreateUserModalOpen, setIsCreateUserModalOpen] = useState(false);
+
+ const navigate = useNavigate();
+
+ const onTableRowClick = (user: User) => {
+ navigate(user.id, { state: { user } });
+ };
+
+ return (
+
+ setIsCreateUserModalOpen(true)}>
+ Create user
+
+
+ Add to group
+
+
+ Enable
+
+
+ Disable
+
+ >
+ }
+ >
+
+
+
+
+
+ {`Showing ${data.length} of ${data.length}`}
+ {` (selected 0)`}
+
+
+
+
+ setIsCreateUserModalOpen(false)}
+ variant={UserModalVariant.Create}
+ />
+
+ );
+};
+
+export default Users;
diff --git a/web/src/routing/routes/UsersAdministration/Users/UsersTable/UsersTable.tsx b/web/src/routing/routes/UsersAdministration/Users/UsersTable/UsersTable.tsx
new file mode 100644
index 0000000..8707dbc
--- /dev/null
+++ b/web/src/routing/routes/UsersAdministration/Users/UsersTable/UsersTable.tsx
@@ -0,0 +1,102 @@
+import { VFC, FC } from "react";
+
+import { Table } from "@components/Table/Table";
+import { User } from "@janush-types/user";
+import { Typography, Theme, Checkbox } from "@mui/material";
+import { useTheme } from "@mui/material/styles";
+import { rgbaColors } from "@themes/palette";
+
+const columns = [
+ { name: "checkbox", label: "" },
+ { name: "id", label: "ID" },
+ { name: "email", label: "Email" },
+ { name: "phoneNumber", label: "Phone Number" },
+ { name: "access", label: "Access" },
+ { name: "status", label: "Status" },
+ { name: "lastmodified", label: "Last Modified" },
+];
+
+interface Props {
+ data: User[];
+ onRowClick: (user: User) => void;
+}
+
+const { TableHead, TableRow, TableCell, TableBody } = Table;
+
+const TableCellStyled: FC = ({ children }) => (
+ {children}
+);
+
+const UsersTable: VFC = ({ data, onRowClick }) => {
+ const theme = useTheme();
+
+ return (
+
+
+
+ {columns.map((column) => (
+ {column.label}
+ ))}
+
+
+
+ {data.length ? (
+ data.map((item) => (
+ onRowClick(item)}
+ >
+
+ e.stopPropagation()} />
+
+ {item.id}
+ {item.email}
+ {item.phoneNumber}
+
+
+ {item.access ? "Enabled" : "Disabled"}
+
+
+
+
+ {item.access ? "Confirmed" : "Unconfirmed"}
+
+
+ {item.lastModified}
+
+ ))
+ ) : (
+
+
+ No results
+
+
+ )}
+
+
+ );
+};
+
+export default UsersTable;
diff --git a/web/src/routing/routes/VerifyEmail/VerifyEmail.test.tsx b/web/src/routing/routes/VerifyEmail/VerifyEmail.test.tsx
index d691da7..4cb2915 100644
--- a/web/src/routing/routes/VerifyEmail/VerifyEmail.test.tsx
+++ b/web/src/routing/routes/VerifyEmail/VerifyEmail.test.tsx
@@ -1,4 +1,7 @@
import React from "react";
+
+import { ThemeProvider } from "@features/ThemeProvider/ThemeProvider";
+import { Paths } from "@routing/paths";
import {
act,
fireEvent,
@@ -7,12 +10,10 @@ import {
waitFor,
} from "@testing-library/react";
import { Auth } from "aws-amplify";
+import { MemoryHistory } from "history";
import { MemoryRouter, Route, Router } from "react-router-dom";
-import { ThemeProvider } from "@features/ThemeProvider/ThemeProvider";
import VerifyEmail from "./VerifyEmail";
-import { Paths } from "@routing/paths";
-import { MemoryHistory } from "history";
const setupHistory: MemoryHistory = {
push: jest.fn(),
diff --git a/web/src/routing/routes/VerifyEmail/VerifyEmail.tsx b/web/src/routing/routes/VerifyEmail/VerifyEmail.tsx
index c5dfd01..f59160b 100644
--- a/web/src/routing/routes/VerifyEmail/VerifyEmail.tsx
+++ b/web/src/routing/routes/VerifyEmail/VerifyEmail.tsx
@@ -1,15 +1,15 @@
import React, { useState } from "react";
-import { Box, Button, Container, Grid, Typography } from "@mui/material";
-import { Auth } from "aws-amplify";
-import { Helmet } from "react-helmet";
-import { Redirect, useLocation } from "react-router-dom";
import { Link } from "@components/Link/Link";
import { AuthLayout } from "@layouts/AuthLayout/AuthLayout";
+import { Box, Button, Container, Grid, Typography } from "@mui/material";
import { Paths } from "@routing/paths";
+import { VerifyEmailForm } from "@routing/routes/VerifyEmail/VerifyEmailView/VerifyEmailForm";
+import { Auth } from "aws-amplify";
+import { Helmet } from "react-helmet";
+import { useLocation, Navigate } from "react-router-dom";
import { useStyles } from "./styles";
-import { VerifyEmailForm } from "@routing/routes/VerifyEmail/VerifyEmailView/VerifyEmailForm";
interface LocationState {
email?: string;
@@ -17,7 +17,9 @@ interface LocationState {
const VerifyEmail: React.VFC = () => {
const classes = useStyles();
- const { state } = useLocation();
+
+ const location = useLocation();
+ const state = location.state as LocationState;
const [disabled, setDisabled] = useState(false);
@@ -31,7 +33,7 @@ const VerifyEmail: React.VFC = () => {
}
}
- if (!state?.email) return ;
+ if (!state?.email) return ;
return (
diff --git a/web/src/routing/routes/VerifyEmail/VerifyEmailView/VerifyEmailForm.tsx b/web/src/routing/routes/VerifyEmail/VerifyEmailView/VerifyEmailForm.tsx
index 23b4e56..beaa61d 100644
--- a/web/src/routing/routes/VerifyEmail/VerifyEmailView/VerifyEmailForm.tsx
+++ b/web/src/routing/routes/VerifyEmail/VerifyEmailView/VerifyEmailForm.tsx
@@ -1,12 +1,13 @@
-import React, { useState, VFC } from "react";
+import { useState, VFC } from "react";
+
+import { Form } from "@components/Form/Form";
import { TextField } from "@components/TextField/TextField";
import { Button } from "@mui/material";
-import { Form } from "@components/Form/Form";
-import { Controller, useForm } from "react-hook-form";
-import { Auth } from "aws-amplify";
-import { useHistory } from "react-router-dom";
import { Paths } from "@routing/paths";
import { isCognitoError } from "@utils/isCognitoError/isCognitoError";
+import { Auth } from "aws-amplify";
+import { Controller, useForm } from "react-hook-form";
+import { useNavigate } from "react-router-dom";
interface IProps {
email: string;
}
@@ -16,7 +17,7 @@ interface IVerifyEmailState {
}
export const VerifyEmailForm: VFC = ({ email }) => {
- const history = useHistory();
+ const navigate = useNavigate();
const [message, setMessage] = useState("");
const { handleSubmit, control } = useForm();
@@ -24,7 +25,7 @@ export const VerifyEmailForm: VFC = ({ email }) => {
try {
await Auth.confirmSignUp(email, code);
- history.push(Paths.SIGN_IN_PATH);
+ navigate(Paths.SIGN_IN_PATH);
} catch (err: unknown) {
if (isCognitoError(err)) {
setMessage(err.message);
diff --git a/web/src/themes/palette.ts b/web/src/themes/palette.ts
index 9420b30..eb0ea38 100644
--- a/web/src/themes/palette.ts
+++ b/web/src/themes/palette.ts
@@ -5,6 +5,13 @@ const lightPalette = createPalette({
main: "#3F51B5",
light: "#B6BDE3",
},
+ secondary: {
+ main: "#F7F7F7",
+ dark: "#666666",
+ },
+ error: {
+ main: "#B00020",
+ },
});
const darkPalette = createPalette({
@@ -12,7 +19,25 @@ const darkPalette = createPalette({
main: "#3F51B5",
light: "#B6BDE3",
},
+ secondary: {
+ main: "#F7F7F7",
+ dark: "#666666",
+ },
+ error: {
+ main: "#B00020",
+ },
});
export const getPalette = (darkMode: boolean): Palette =>
darkMode ? darkPalette : lightPalette;
+
+export const rgbaColors = {
+ grey: {
+ lightest: "rgba(0, 0, 0, 0.05)",
+ lighter: "rgba(0, 0, 0, 0.12)",
+ light: "rgba(0, 0, 0, 0.15)",
+ main: "rgba(0, 0, 0, 0.38)",
+ dark: "rgba(0, 0, 0, 0.6)",
+ darkest: "rgba(63, 81, 181, 0.05)",
+ },
+};
diff --git a/web/src/types/group.ts b/web/src/types/group.ts
new file mode 100644
index 0000000..8003403
--- /dev/null
+++ b/web/src/types/group.ts
@@ -0,0 +1,7 @@
+export interface Group {
+ id: string;
+ name: string;
+ description: string;
+ members: number;
+ lastModified: string;
+}
diff --git a/web/src/types/user.ts b/web/src/types/user.ts
new file mode 100644
index 0000000..122dbc3
--- /dev/null
+++ b/web/src/types/user.ts
@@ -0,0 +1,8 @@
+export interface User {
+ id: string;
+ email: string;
+ phoneNumber: string;
+ access: boolean;
+ status: boolean;
+ lastModified: string;
+}
diff --git a/web/src/utils/checkIsThisCurrentTab/checkIsThisCurrentTab.ts b/web/src/utils/checkIsThisCurrentTab/checkIsThisCurrentTab.ts
new file mode 100644
index 0000000..032bfe2
--- /dev/null
+++ b/web/src/utils/checkIsThisCurrentTab/checkIsThisCurrentTab.ts
@@ -0,0 +1,6 @@
+import { Location } from "react-router-dom";
+
+export const checkIsThisCurrentTab = (
+ location: Location,
+ tabName: string
+): boolean => location.pathname.split("/")[2] === tabName;
diff --git a/web/src/utils/getOptionsFromEnum/getOptionsFromEnum.ts b/web/src/utils/getOptionsFromEnum/getOptionsFromEnum.ts
new file mode 100644
index 0000000..86591b8
--- /dev/null
+++ b/web/src/utils/getOptionsFromEnum/getOptionsFromEnum.ts
@@ -0,0 +1,5 @@
+export const getOptionsFromEnum = (passedEnum: any) =>
+ Object.keys(passedEnum).map((item) => ({
+ name: item.toLocaleLowerCase(),
+ label: item,
+ }));
diff --git a/web/src/utils/isCognitoError/isCognitoError.test.ts b/web/src/utils/isCognitoError/isCognitoError.test.ts
index 73eb00b..44a507e 100644
--- a/web/src/utils/isCognitoError/isCognitoError.test.ts
+++ b/web/src/utils/isCognitoError/isCognitoError.test.ts
@@ -1,4 +1,5 @@
import { CognitoErrorType } from "@janush-types/enums/Cognito";
+
import { isCognitoError } from "./isCognitoError";
describe("isCognitoError", () => {
diff --git a/web/src/validations/UserValidation.ts b/web/src/validations/UserValidation.ts
new file mode 100644
index 0000000..079da83
--- /dev/null
+++ b/web/src/validations/UserValidation.ts
@@ -0,0 +1,11 @@
+import * as yup from "yup";
+
+export const baseSchema = {
+ firstName: yup.string().max(30).required().label("First name"),
+ lastName: yup.string().max(30).required().label("Last name"),
+};
+
+export const emailSchema = yup.object({
+ ...baseSchema,
+ email: yup.string().email().required().label("Email address"),
+});
diff --git a/web/tsconfig.paths.json b/web/tsconfig.paths.json
index b46c425..540e56d 100644
--- a/web/tsconfig.paths.json
+++ b/web/tsconfig.paths.json
@@ -2,15 +2,36 @@
"compilerOptions": {
"baseUrl": ".",
"paths": {
-"@components/*": ["./src/components/*"],
- "@consts/*": ["./src/consts/*"],
- "@interfaces/*": ["./src/interfaces/*"],
- "@janush-types/*": ["./src/types/*"],
- "@utils/*": ["./src/utils/*"],
- "@features/*": ["./src/features/*"],
- "@layouts/*": ["./src/layouts/*"],
- "@routing/*": ["./src/routing/*"],
- "@themes/*": ["./src/themes/*"],
+ "@components/*": [
+ "./src/components/*"
+ ],
+ "@consts/*": [
+ "./src/consts/*"
+ ],
+ "@interfaces/*": [
+ "./src/interfaces/*"
+ ],
+ "@janush-types/*": [
+ "./src/types/*"
+ ],
+ "@utils/*": [
+ "./src/utils/*"
+ ],
+ "@features/*": [
+ "./src/features/*"
+ ],
+ "@layouts/*": [
+ "./src/layouts/*"
+ ],
+ "@routing/*": [
+ "./src/routing/*"
+ ],
+ "@themes/*": [
+ "./src/themes/*"
+ ],
+ "@validations/*": [
+ "./src/validations/*"
+ ],
}
}
-}
+}
\ No newline at end of file