├── app
├── .env.example
├── src
│ ├── .env.example
│ ├── react-app-env.d.ts
│ ├── pages
│ │ ├── Login
│ │ │ ├── index.tsx
│ │ │ └── Login.tsx
│ │ ├── Register
│ │ │ ├── index.tsx
│ │ │ └── Register.tsx
│ │ ├── NotFound
│ │ │ └── index.tsx
│ │ └── Contacts
│ │ │ ├── UploadProfile.tsx
│ │ │ ├── ContactForm.tsx
│ │ │ └── index.tsx
│ ├── asset
│ │ └── cm.png
│ ├── components
│ │ ├── layout
│ │ │ ├── index.tsx
│ │ │ ├── AppLayout.tsx
│ │ │ └── Profile.tsx
│ │ ├── Custom
│ │ │ ├── CSearch
│ │ │ │ └── index.tsx
│ │ │ ├── CSpin
│ │ │ │ └── index.tsx
│ │ │ ├── index.tsx
│ │ │ ├── CInput
│ │ │ │ └── index.tsx
│ │ │ ├── CTitle
│ │ │ │ └── index.tsx
│ │ │ ├── CInputPassword
│ │ │ │ └── index.tsx
│ │ │ ├── CButton
│ │ │ │ └── index.tsx
│ │ │ ├── CInputNumber
│ │ │ │ └── index.tsx
│ │ │ ├── CFormItem
│ │ │ │ └── index.tsx
│ │ │ └── CTable
│ │ │ │ └── index.tsx
│ │ └── TopHeader
│ │ │ └── index.tsx
│ ├── utils
│ │ ├── getFirstLetter.ts
│ │ ├── reduxHooks
│ │ │ └── index.tsx
│ │ └── axios
│ │ │ └── index.tsx
│ ├── App.tsx
│ ├── redux
│ │ ├── types
│ │ │ ├── index.ts
│ │ │ ├── usersTypes.ts
│ │ │ └── contactsTypes.ts
│ │ ├── sagas
│ │ │ ├── rootSaga.tsx
│ │ │ ├── usersSaga.ts
│ │ │ └── contactsSaga.ts
│ │ ├── reducers
│ │ │ ├── index.ts
│ │ │ ├── contactsReducer.ts
│ │ │ └── usersReducer.ts
│ │ ├── store.tsx
│ │ └── actions
│ │ │ ├── userAction.ts
│ │ │ └── contactsAction.ts
│ ├── setupTests.ts
│ ├── App.test.tsx
│ ├── constants
│ │ └── Color.ts
│ ├── reportWebVitals.ts
│ ├── routes
│ │ ├── routes.ts
│ │ └── index.tsx
│ ├── App.css
│ ├── index.tsx
│ ├── interfaces
│ │ └── index.ts
│ └── logo.svg
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── index.html
├── .gitignore
├── tsconfig.json
├── package.json
└── README.md
├── server
├── .gitignore
├── index.js
├── model
│ ├── user.js
│ └── contact.js
├── config
│ └── database.js
├── package.json
├── middleware
│ └── auth.js
├── app.js
└── yarn.lock
├── .github
└── workflows
│ └── node.js.yml
└── README.md
/app/.env.example:
--------------------------------------------------------------------------------
1 | REACT_APP_API_HOST = 'http://localhost:4001/'
--------------------------------------------------------------------------------
/app/src/.env.example:
--------------------------------------------------------------------------------
1 | REACT_APP_API_HOST = 'http://localhost:4001/'
--------------------------------------------------------------------------------
/app/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/app/src/pages/Login/index.tsx:
--------------------------------------------------------------------------------
1 | import Login from "./Login";
2 |
3 | export { Login };
4 |
--------------------------------------------------------------------------------
/app/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/app/src/pages/Register/index.tsx:
--------------------------------------------------------------------------------
1 | import Register from "./Register";
2 |
3 | export { Register };
4 |
--------------------------------------------------------------------------------
/app/src/asset/cm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudiprajkunwar/contact-manager/HEAD/app/src/asset/cm.png
--------------------------------------------------------------------------------
/app/src/components/layout/index.tsx:
--------------------------------------------------------------------------------
1 | import AppLayout from "./AppLayout";
2 |
3 | export { AppLayout };
4 |
--------------------------------------------------------------------------------
/app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudiprajkunwar/contact-manager/HEAD/app/public/favicon.ico
--------------------------------------------------------------------------------
/app/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudiprajkunwar/contact-manager/HEAD/app/public/logo192.png
--------------------------------------------------------------------------------
/app/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudiprajkunwar/contact-manager/HEAD/app/public/logo512.png
--------------------------------------------------------------------------------
/app/src/utils/getFirstLetter.ts:
--------------------------------------------------------------------------------
1 | export const getFirstLetter = (data: string) => {
2 | if (!data) {
3 | return;
4 | }
5 | return data.charAt(0).toUpperCase();
6 | };
7 |
--------------------------------------------------------------------------------
/app/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "antd/dist/antd.css";
3 | import Routes from "./routes";
4 |
5 | function App() {
6 | return ;
7 | }
8 |
9 | export default App;
10 |
--------------------------------------------------------------------------------
/app/src/redux/types/index.ts:
--------------------------------------------------------------------------------
1 | import { contacts } from "./contactsTypes";
2 | import { users } from "./usersTypes";
3 |
4 | const types = {
5 | ...users,
6 | ...contacts,
7 | };
8 |
9 | export default types;
10 |
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | # Include your project-specific ignores in this file
2 | # Read about how to use .gitignore: https://help.github.com/articles/ignoring-files
3 |
4 | # Dependency directories
5 | node_modules
6 |
7 | # Dotenv configuration
8 | .env
9 |
--------------------------------------------------------------------------------
/app/src/redux/sagas/rootSaga.tsx:
--------------------------------------------------------------------------------
1 | import { all } from "redux-saga/effects";
2 | import { contactsSaga } from "./contactsSaga";
3 |
4 | import { usersSaga } from "./usersSaga";
5 |
6 | export default function* RootSaga() {
7 | yield all([usersSaga(), contactsSaga()]);
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/app/src/redux/reducers/index.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import { usersReducer } from "./usersReducer";
3 | import { contactsReducer } from "./contactsReducer";
4 |
5 | export const RootReducer = combineReducers({
6 | users: usersReducer,
7 | contacts: contactsReducer,
8 | });
9 |
--------------------------------------------------------------------------------
/app/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | render();
7 | const linkElement = screen.getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | const http = require("http");
2 | const app = require("./app");
3 | const server = http.createServer(app);
4 |
5 | const { API_PORT } = process.env;
6 | const port = process.env.PORT || API_PORT;
7 |
8 | // server listening
9 | server.listen(port, () => {
10 | console.log(`Server running on port ${port}`);
11 | });
12 |
--------------------------------------------------------------------------------
/app/src/components/Custom/CSearch/index.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from "antd";
2 | import * as React from "react";
3 | import { SearchProps } from "antd/es/input";
4 |
5 | const { Search } = Input;
6 |
7 | type CSearchProps = SearchProps;
8 |
9 | export const CSearch: React.FC = (props) => {
10 | return ;
11 | };
12 |
--------------------------------------------------------------------------------
/app/src/constants/Color.ts:
--------------------------------------------------------------------------------
1 | export interface ColorProps {
2 | primary: string;
3 | lightBlue: string;
4 | textDark: string;
5 | textLight: string;
6 | textGrey: string;
7 | }
8 |
9 | export const Color: ColorProps = {
10 | primary: "#0257FF",
11 | lightBlue: "#3B4CB8",
12 | textDark: "#2E2E2E",
13 | textLight: "#f9fcff",
14 | textGrey: "#595959",
15 | };
16 |
--------------------------------------------------------------------------------
/app/src/components/Custom/CSpin/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Spin, SpinProps } from "antd";
3 | import styled from "@emotion/styled";
4 |
5 | const Spinner = styled(Spin)`
6 | position: absolute;
7 | top: 50%;
8 | left: 50%;
9 | transform: translate(-50%, -50%);
10 | `;
11 | export const CSpin = (props: SpinProps) => {
12 | return ;
13 | };
14 |
--------------------------------------------------------------------------------
/app/src/utils/reduxHooks/index.tsx:
--------------------------------------------------------------------------------
1 | import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
2 | import { AppDispatch, RootState } from "../../redux/store";
3 |
4 | // Use throughout your app instead of plain `useDispatch` and `useSelector`
5 | export const useAppDispatch = () => useDispatch();
6 | export const useAppSelector: TypedUseSelectorHook = useSelector;
7 |
--------------------------------------------------------------------------------
/app/src/components/Custom/index.tsx:
--------------------------------------------------------------------------------
1 | export { CButton } from "./CButton";
2 | export { CFormItem } from "./CFormItem";
3 | export { CInput } from "./CInput";
4 | export { CInputPassword } from "./CInputPassword";
5 | export { CTable } from "./CTable";
6 | export { CTitle } from "./CTitle";
7 | export { CSpin } from "./CSpin";
8 | export { CSearch } from "./CSearch";
9 | export { CInputNumber } from "./CInputNumber";
10 |
--------------------------------------------------------------------------------
/server/model/user.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | const Schema = mongoose.Schema;
3 |
4 | const userSchema = new Schema(
5 | {
6 | full_name: { type: String, default: null },
7 | email: { type: String, unique: true },
8 | password: { type: String },
9 | token: { type: String },
10 | },
11 | { versionKey: false }
12 | );
13 |
14 | module.exports = mongoose.model("user", userSchema);
15 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .env
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/app/src/redux/types/usersTypes.ts:
--------------------------------------------------------------------------------
1 | export const users = {
2 | SIGNIN_USER: "SIGNIN_USER",
3 | SIGNIN_USER_SUCCESS: "SIGNIN_USER_SUCCESS",
4 |
5 | SIGNUP_USER: "SIGNUP_USER",
6 | SIGNUP_USER_SUCCESS: "SIGNUP_USER_SUCCESS",
7 |
8 | SIGNOUT_USER: "SIGNOUT_USER",
9 |
10 | GET_USER_DETAIL: "GET_USER_DETAIL",
11 | GET_USER_DETAIL_SUCCESS: "GET_USER_DETAIL_SUCCESS",
12 |
13 | USER_REQUEST_FAILED: "USER_REQUEST_FAILED",
14 | };
15 |
--------------------------------------------------------------------------------
/app/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/app/src/utils/axios/index.tsx:
--------------------------------------------------------------------------------
1 | import axios, { AxiosInstance } from "axios";
2 | import Cookies from "js-cookie";
3 |
4 | const Axios: AxiosInstance = axios.create({
5 | baseURL: process.env.REACT_APP_API_HOST,
6 | });
7 |
8 | // Add a request interceptor
9 | Axios.interceptors.request.use(function (config: any) {
10 | const token = `Bearer ${Cookies.get("token")}`;
11 | console.log(config, "con");
12 | config.headers.Authorization = token;
13 |
14 | return config;
15 | });
16 |
17 | export default Axios;
18 |
--------------------------------------------------------------------------------
/app/src/redux/store.tsx:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, createStore } from "redux";
2 | import createSagaMiddleware from "redux-saga";
3 | import { RootReducer } from "./reducers";
4 | import RootSaga from "./sagas/rootSaga";
5 |
6 | const sagaMiddleware = createSagaMiddleware();
7 |
8 | const middleware = [sagaMiddleware];
9 |
10 | const store = createStore(RootReducer, applyMiddleware(...middleware));
11 |
12 | export type RootState = ReturnType;
13 | export type AppDispatch = typeof store.dispatch;
14 |
15 | sagaMiddleware.run(RootSaga);
16 | export default store;
17 |
--------------------------------------------------------------------------------
/server/model/contact.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | const Schema = mongoose.Schema;
3 |
4 | const contactSchema = new mongoose.Schema(
5 | {
6 | user_id: { type: Schema.Types.ObjectId, ref: "user" },
7 | full_name: { type: String, default: null },
8 | address: { type: String, default: null },
9 | email: { type: String },
10 | phone: { type: Number, default: null },
11 | favourite: { type: Number, default: 0 },
12 | image: { type: String },
13 | },
14 | { versionKey: false }
15 | );
16 |
17 | module.exports = mongoose.model("contact", contactSchema);
18 |
--------------------------------------------------------------------------------
/app/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/server/config/database.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const { MONGO_URI } = process.env;
4 |
5 | exports.connect = () => {
6 | // Connecting to the database
7 | mongoose
8 | .connect(MONGO_URI, {
9 | useNewUrlParser: true,
10 | useUnifiedTopology: true,
11 | // useCreateIndex: true,
12 | // useFindAndModify: false,
13 | })
14 | .then(() => {
15 | console.log("Successfully connected to database");
16 | })
17 | .catch((error) => {
18 | console.log("database connection failed. exiting now...");
19 | console.error(error);
20 | process.exit(1);
21 | });
22 | };
23 |
--------------------------------------------------------------------------------
/app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node index.js",
8 | "dev": "nodemon index.js",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "bcryptjs": "^2.4.3",
16 | "body-parser": "^1.19.1",
17 | "cors": "^2.8.5",
18 | "dotenv": "^11.0.0",
19 | "express": "^4.17.2",
20 | "jsonwebtoken": "^8.5.1",
21 | "mongoose": "^6.1.6"
22 | },
23 | "devDependencies": {
24 | "nodemon": "^2.0.15"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/redux/types/contactsTypes.ts:
--------------------------------------------------------------------------------
1 | export const contacts = {
2 | GETALL_CONTACTS: "GETALL_CONTACTS",
3 | GETALL_CONTACTS_SUCCESS: "GETALL_CONTACTS_SUCCESS",
4 |
5 | CREATE_CONTACT: "CREATE_CONTACT",
6 | CREATE_CONTACT_SUCCESS: "CREATE_CONTACT_SUCCESS",
7 |
8 | UPDATE_CONTACT: "UPDATE_CONTACT",
9 | UPDATE_CONTACT_SUCCESS: "UPDATE_CONTACT_SUCCESS",
10 |
11 | UPDATE_FAVOURITE_CONTACT: "UPDATE_FAVOURITE_CONTACT",
12 | UPDATE_FAVOURITE_CONTACT_SUCCESS: "UPDATE_FAVOURITE_CONTACT_SUCCESS",
13 |
14 | DELETE_CONTACT: "DELETE_CONTACT",
15 | DELETE_CONTACT_SUCCESS: "DELETE_CONTACT_SUCCESS",
16 |
17 | CONTACTS_REQUEST_FAILED: "CONTACTS_REQUEST_FAILED",
18 | };
19 |
--------------------------------------------------------------------------------
/app/src/routes/routes.ts:
--------------------------------------------------------------------------------
1 | import { IRoutesType } from "../interfaces";
2 | import Contacts from "../pages/Contacts";
3 | import ContactForm from "../pages/Contacts/ContactForm";
4 | import NotFound from "../pages/NotFound";
5 |
6 | export const routers: IRoutesType[] = [
7 | {
8 | path: "/contact",
9 | title: "Contact",
10 | component: Contacts,
11 | },
12 | {
13 | path: "/contact/add",
14 | title: "AddContact",
15 | component: ContactForm,
16 | },
17 | {
18 | path: "/contact/edit",
19 | title: "EditContact",
20 | component: ContactForm,
21 | },
22 | {
23 | path: "*",
24 | title: "notFouind",
25 | component: NotFound,
26 | },
27 | ];
28 |
--------------------------------------------------------------------------------
/server/middleware/auth.js:
--------------------------------------------------------------------------------
1 | const jwt = require("jsonwebtoken");
2 |
3 | const config = process.env;
4 |
5 | const verifyToken = (req, res, next) => {
6 | const bearerHeader = req.headers["authorization"];
7 |
8 | if (!bearerHeader) {
9 | // Forbidden
10 | res.status(403).send("A token is required for authentication");
11 | }
12 |
13 | try {
14 | const bearer = bearerHeader.split(" ");
15 | const bearerToken = bearer[1];
16 |
17 | const decoded = jwt.verify(bearerToken, config.TOKEN_KEY);
18 | req.user = decoded;
19 | } catch (err) {
20 | return res.status(401).send("Invalid Token");
21 | }
22 | return next();
23 | };
24 |
25 | module.exports = verifyToken;
26 |
--------------------------------------------------------------------------------
/app/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import ReactDOM from "react-dom";
4 | import { Provider } from "react-redux";
5 | import reportWebVitals from "./reportWebVitals";
6 | import { BrowserRouter } from "react-router-dom";
7 |
8 | import App from "./App";
9 | import store from "./redux/store";
10 |
11 | ReactDOM.render(
12 | //
13 |
14 |
15 |
16 |
17 | ,
18 | // ,
19 | document.getElementById("root")
20 | );
21 |
22 | // If you want to start measuring performance in your app, pass a function
23 | // to log results (for example: reportWebVitals(console.log))
24 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
25 | reportWebVitals();
26 |
--------------------------------------------------------------------------------
/app/src/pages/NotFound/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import { Link } from "react-router-dom";
4 |
5 | import { Color } from "../../constants/Color";
6 |
7 | const Wrapper = styled("div")`
8 | width: 100%;
9 | height: 100%;
10 | `;
11 |
12 | const Title = styled("h2")`
13 | font-size: 50px;
14 | font-weight: 700;
15 | color: ${Color.textDark};
16 | text-align: center;
17 | `;
18 |
19 | const Container = styled.div`
20 | height: 60vh;
21 | `;
22 |
23 | const UnderConstruction = () => {
24 | return (
25 |
26 |
27 |
28 | Page Not Found
29 | 🏠
30 |
31 |
32 |
33 | );
34 | };
35 | export default UnderConstruction;
36 |
--------------------------------------------------------------------------------
/app/src/components/Custom/CInput/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Input } from "antd";
3 | import { InputProps } from "antd/lib/input";
4 | import styled from "@emotion/styled";
5 | import { Color } from "../../../constants/Color";
6 |
7 | const StyledInput = styled(Input)`
8 | border: 1px solid #e9e9e9;
9 | box-shadow: 0px 4px 8px #2c27380a;
10 | padding: 16px;
11 | height: 37px;
12 | border-radius: 6px;
13 |
14 | :hover {
15 | background: #ffffff 0% 0% no-repeat padding-box;
16 | box-shadow: 0px 4px 8px #1890ff33;
17 | border: 1px solid ${Color.primary};
18 | }
19 | ::placeholder {
20 | color: ${Color.textGrey};
21 | font-size: 13px;
22 | }
23 | :focus {
24 | box-shadow: none !important;
25 | }
26 | `;
27 | export const CInput = React.forwardRef((props: InputProps, ref: any) => {
28 | return ;
29 | });
30 |
--------------------------------------------------------------------------------
/app/src/components/Custom/CTitle/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Typography } from "antd";
3 | import styled from "@emotion/styled";
4 | import { Color } from "../../../constants/Color";
5 | import { TitleProps } from "antd/lib/typography/Title";
6 |
7 | const { Title } = Typography;
8 |
9 | type Props = TitleProps & {
10 | children: any;
11 | color?: string;
12 | fontWeight?: number;
13 | textalign?: string;
14 | margin?: string;
15 | };
16 |
17 | const StyledTitle = styled(Title)`
18 | &.ant-typography {
19 | color: ${(props) => props.color || Color.textDark};
20 | font-weight: ${(props) => props.fontWeight};
21 | text-align: ${(props) => props.textalign};
22 | margin: ${(props) => props.margin};
23 | }
24 | `;
25 | export const CTitle = (props: Props) => {
26 | const { children, ...rest } = props;
27 | return {children};
28 | };
29 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | name: Contact-manager CI
2 |
3 | on:
4 | push:
5 | branches: ["master"]
6 | pull_request:
7 | branches: ["master"]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | node-version: [18.x]
16 |
17 | steps:
18 | - uses: actions/checkout@v4
19 |
20 | # Setup Node.js
21 | - name: Use Node.js ${{ matrix.node-version }}
22 | uses: actions/setup-node@v4
23 | with:
24 | node-version: ${{ matrix.node-version }}
25 |
26 | # Install dependencies and build the frontend (app)
27 | - name: Install dependencies (Frontend)
28 | run: yarn install --frozen-lockfile
29 | working-directory: app
30 |
31 | - name: Build frontend
32 | run: yarn build
33 | working-directory: app
34 |
35 | # Install dependencies and build the backend (server)
36 | - name: Install dependencies (Backend)
37 | run: yarn install --frozen-lockfile
38 | working-directory: server
39 |
--------------------------------------------------------------------------------
/app/src/components/Custom/CInputPassword/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Input } from "antd";
3 | import { InputProps } from "antd/lib/input";
4 | import styled from "@emotion/styled";
5 | import { Color } from "../../../constants/Color";
6 |
7 | const StyledInput = styled(Input.Password)`
8 | border: 1px solid #e9e9e9;
9 | box-shadow: 0px 4px 8px #2c27380a;
10 | padding: 12px 16px;
11 | height: 37px;
12 | border-radius: 6px;
13 | :hover {
14 | background: #ffffff 0% 0% no-repeat padding-box;
15 | box-shadow: 0px 4px 8px #1890ff33;
16 | border: 1px solid ${Color.primary};
17 | }
18 | .ant-input {
19 | :focus {
20 | box-shadow: none !important;
21 | border-color: transparent;
22 | }
23 | ::placeholder {
24 | color: ${Color.textGrey};
25 | font-size: 13px;
26 | }
27 | :focus {
28 | box-shadow: none !important;
29 | }
30 | }
31 | `;
32 |
33 | export const CInputPassword = React.forwardRef(
34 | (props: InputProps, ref: any) => {
35 | return ;
36 | }
37 | );
38 |
--------------------------------------------------------------------------------
/app/src/components/Custom/CButton/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { Button } from "antd";
4 | import styled from "@emotion/styled";
5 | import { ButtonProps } from "antd/lib/button";
6 |
7 | import { Color } from "../../../constants/Color";
8 |
9 | type Props = ButtonProps & {
10 | children: any;
11 | backgroundcolor?: string;
12 | color?: string;
13 | };
14 |
15 | const StyledButton = styled(Button)`
16 | width: 110px;
17 | background: ${(props) => props.backgroundcolor || Color.lightBlue};
18 | color: ${(props) => props.color || "#fff"};
19 | font-size: 12px;
20 | font-weight: 600;
21 | height: 35px;
22 | box-shadow: 0px 4px 8px #2c273814;
23 | border-radius: 6px;
24 | &:hover,
25 | &:focus {
26 | background: ${(props) => props.backgroundcolor || Color.lightBlue};
27 | color: ${(props) => props.color || "#fff"};
28 | border-color: ${(props) => props.color || "#fff"};
29 | }
30 | `;
31 |
32 | export const CButton = (props: Props) => {
33 | const { children, ...rest } = props;
34 | return {children};
35 | };
36 |
--------------------------------------------------------------------------------
/app/src/interfaces/index.ts:
--------------------------------------------------------------------------------
1 | export interface IRoutesType {
2 | path: string;
3 | title: string;
4 | component: React.FC;
5 | }
6 |
7 | export interface ILogin {
8 | email: string;
9 | password: string;
10 | }
11 |
12 | export interface IRegister {
13 | full_name: string;
14 | email: string;
15 | password: string;
16 | }
17 |
18 | export interface ISignupUser extends IRegister {
19 | onSuccess: () => void;
20 | }
21 |
22 | export interface ISigninUser extends ILogin {
23 | onSuccess: () => void;
24 | }
25 |
26 | export interface IContact {
27 | _id: string;
28 | user_id: string;
29 | full_name: string;
30 | address: string;
31 | email: string;
32 | phone: number;
33 | favourite: number;
34 | image: string;
35 | }
36 |
37 | export interface IUser {
38 | _id: string;
39 | full_name: string;
40 | email: string;
41 | }
42 |
43 | export interface IParamsUserId {
44 | user_id: string;
45 | }
46 | export interface IUserID {
47 | params: IParamsUserId;
48 | }
49 |
50 | export interface IDeleteContact {
51 | user_id: string;
52 | _id: string;
53 | onSuccess?: () => void;
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/components/Custom/CInputNumber/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import styled from "@emotion/styled";
4 | import { InputNumber, InputNumberProps } from "antd";
5 |
6 | import { Color } from "../../../constants/Color";
7 |
8 | const StyledInput = styled(InputNumber)`
9 | border: 1px solid #e9e9e9;
10 | box-shadow: 0px 4px 8px #2c27380a;
11 | height: 37px;
12 | border-radius: 6px;
13 | width: 100%;
14 |
15 | .ant-input-number-handler-wrap {
16 | display: none;
17 | }
18 | .ant-input-number-input-wrap {
19 | height: 100%;
20 |
21 | input {
22 | height: 100%;
23 | ::placeholder {
24 | color: ${Color.textGrey};
25 | font-size: 13px;
26 | }
27 | :focus {
28 | box-shadow: none !important;
29 | }
30 | }
31 | }
32 | :hover {
33 | background: #ffffff 0% 0% no-repeat padding-box;
34 | box-shadow: 0px 4px 8px #1890ff33;
35 | border: 1px solid ${Color.primary};
36 | }
37 | `;
38 | export const CInputNumber = React.forwardRef(
39 | (props: InputNumberProps, ref: any) => {
40 | return ;
41 | }
42 | );
43 |
--------------------------------------------------------------------------------
/app/src/components/Custom/CFormItem/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import { Form } from "antd";
4 | import { FormItemProps } from "antd/lib/form";
5 | import { Color } from "../../../constants/Color";
6 |
7 | type Props = FormItemProps & {
8 | children: any;
9 | };
10 | const FormItem = styled(Form.Item)`
11 | margin-bottom: 15px;
12 | .ant-form-item-label {
13 | line-height: 30px;
14 | padding: 0;
15 |
16 | label {
17 | color: ${Color.textGrey};
18 | font-weight: 500;
19 | font-size: 12px;
20 | ::after {
21 | display: none;
22 | }
23 | }
24 | }
25 | .ant-form-explain {
26 | font-size: 12px;
27 | transform: translateY(5px);
28 | }
29 | .has-error .ant-select-open .ant-select-selection,
30 | .has-error .ant-select-focused .ant-select-selection {
31 | box-shadow: none !important;
32 | }
33 | .ant-select-selection--multiple > ul > li,
34 | .ant-select-selection--multiple .ant-select-selection__rendered > ul > li {
35 | margin-top: 7px !important;
36 | }
37 | `;
38 |
39 | export const CFormItem = (props: Props) => {
40 | const { children, ...rest } = props;
41 | return {children};
42 | };
43 |
--------------------------------------------------------------------------------
/app/src/redux/reducers/contactsReducer.ts:
--------------------------------------------------------------------------------
1 | import types from "../types";
2 | import { AnyAction } from "redux";
3 |
4 | const initialState = {
5 | isLoading: false,
6 | data: [],
7 | error: null,
8 | };
9 |
10 | export const contactsReducer = (state = initialState, action: AnyAction) => {
11 | switch (action.type) {
12 | case types.GETALL_CONTACTS:
13 | case types.CREATE_CONTACT:
14 | case types.UPDATE_CONTACT:
15 | case types.DELETE_CONTACT:
16 | case types.UPDATE_FAVOURITE_CONTACT:
17 | return {
18 | ...state,
19 | isLoading: true,
20 | };
21 |
22 | case types.GETALL_CONTACTS_SUCCESS:
23 | case types.CREATE_CONTACT_SUCCESS:
24 | return {
25 | ...state,
26 | isLoading: false,
27 | data: action.payload,
28 | error: null,
29 | };
30 |
31 | case types.UPDATE_FAVOURITE_CONTACT_SUCCESS:
32 | case types.DELETE_CONTACT_SUCCESS:
33 | return {
34 | ...state,
35 | loading: false,
36 | error: null,
37 | };
38 |
39 | case types.CONTACTS_REQUEST_FAILED:
40 | return {
41 | ...state,
42 | error: action.error,
43 | isLoading: false,
44 | };
45 |
46 | default:
47 | return { ...state };
48 | }
49 | };
50 |
--------------------------------------------------------------------------------
/app/src/redux/reducers/usersReducer.ts:
--------------------------------------------------------------------------------
1 | import types from "../types";
2 | import { AnyAction } from "redux";
3 |
4 | const initialState = {
5 | isLoading: false,
6 | data: [],
7 | error: null,
8 | };
9 |
10 | export const usersReducer = (state = initialState, action: AnyAction) => {
11 | switch (action.type) {
12 | case types.SIGNIN_USER:
13 | case types.SIGNUP_USER:
14 | case types.GET_USER_DETAIL:
15 | return {
16 | ...state,
17 | isLoading: true,
18 | };
19 |
20 | case types.SIGNIN_USER_SUCCESS:
21 | case types.SIGNUP_USER_SUCCESS:
22 | return {
23 | ...state,
24 | isLoading: false,
25 | error: null,
26 | data: action.payload,
27 | };
28 |
29 | case types.GET_USER_DETAIL_SUCCESS:
30 | return {
31 | ...state,
32 | isLoading: false,
33 | data: action.payload,
34 | };
35 |
36 | case types.USER_REQUEST_FAILED:
37 | return {
38 | ...state,
39 | error: action.error,
40 | isLoading: false,
41 | };
42 |
43 | case types.SIGNOUT_USER:
44 | return {
45 | ...state,
46 | error: null,
47 | isLoading: false,
48 | };
49 |
50 | default:
51 | return { ...state };
52 | }
53 | };
54 |
--------------------------------------------------------------------------------
/app/src/redux/actions/userAction.ts:
--------------------------------------------------------------------------------
1 | import types from "../types";
2 | import {
3 | IParamsUserId,
4 | ISigninUser,
5 | ISignupUser,
6 | IUser,
7 | IUserID,
8 | } from "../../interfaces";
9 |
10 | // sign in user
11 | export function signinUser(payload: ISigninUser) {
12 | return {
13 | type: types.SIGNIN_USER,
14 | payload,
15 | };
16 | }
17 |
18 | export function signinUserSuccess(payload: IParamsUserId) {
19 | return {
20 | type: types.SIGNIN_USER_SUCCESS,
21 | payload,
22 | };
23 | }
24 |
25 | // sign up user
26 | export function signupUser(payload: ISignupUser) {
27 | return {
28 | type: types.SIGNUP_USER,
29 | payload,
30 | };
31 | }
32 |
33 | export function signupUserSuccess() {
34 | return {
35 | type: types.SIGNUP_USER_SUCCESS,
36 | };
37 | }
38 |
39 | // get user detail
40 | export function getUserDetail(payload: IUserID) {
41 | return {
42 | type: types.GET_USER_DETAIL,
43 | payload,
44 | };
45 | }
46 |
47 | export function getUserDetailSuccess(payload: IUser) {
48 | return {
49 | type: types.GET_USER_DETAIL_SUCCESS,
50 | payload,
51 | };
52 | }
53 |
54 | // request use failed
55 | export function UserRequestFailed(payload: string) {
56 | return {
57 | type: types.USER_REQUEST_FAILED,
58 | payload,
59 | };
60 | }
61 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # contact-manager
2 |
3 | ## App
4 | Open [https://contact-manager-app-244k.onrender.com](https://contact-manager-app-244k.onrender.com) to view it in the browser.
5 |
6 | ## Setup Environment
7 |
8 | Create a `.env` file from `.env.example`.
9 |
10 | ## Available Scripts
11 |
12 | In the project directory, you can run:
13 |
14 | ### `yarn start`
15 |
16 | Runs the app in the development mode.
17 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
18 |
19 | The page will reload if you make edits.
20 |
21 | ### `npm run build`
22 |
23 | Builds the app for production to the `build` folder.
24 | It correctly bundles React in production mode and optimizes the build for the best performance.
25 |
26 | The build is minified and the filenames include the hashes.
27 | Your app is ready to be deployed!
28 |
29 | ## Server
30 |
31 | ## Setup Environment
32 |
33 | Create a `.env` file from `.env.example`.
34 |
35 | ## Available Scripts
36 |
37 | In the project directory, you can run:
38 |
39 | ### `yarn run start`
40 |
41 | Runs the app in the development mode.
42 | Open [http://localhost:4001](http://localhost:4001) to view it in the browser.
43 |
44 | The page will reload if you make edits.
45 |
46 | ## Database(MongoDb)
47 |
48 | Database is hosted in [http:cloud.mongodb.com ](http:cloud.mongodb.com)
49 |
--------------------------------------------------------------------------------
/app/src/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Redirect, Route, Switch, withRouter } from "react-router";
3 |
4 | import moment from "moment";
5 | import jwt from "jwt-decode";
6 | import Cookies from "js-cookie";
7 |
8 | import { Login } from "../pages/Login";
9 | import { Register } from "../pages/Register";
10 | import { AppLayout } from "../components/layout";
11 |
12 | function Routes() {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | }
24 |
25 | function auth() {
26 | const token = Cookies.get("token");
27 | if (token) {
28 | const userDetail: any = jwt(token);
29 | const isExpired = moment.unix(userDetail.exp).diff(moment(), "seconds");
30 | if (isExpired > 0) {
31 | return true;
32 | }
33 | }
34 | return false;
35 | }
36 |
37 | function PrivateRoute({ children, ...rest }: any) {
38 | return (
39 |
42 | auth() ? (
43 | children
44 | ) : (
45 |
51 | )
52 | }
53 | />
54 | );
55 | }
56 |
57 | export default withRouter(Routes);
58 |
--------------------------------------------------------------------------------
/app/src/components/layout/AppLayout.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Layout } from "antd";
3 | import styled from "@emotion/styled";
4 | import { Route, Switch } from "react-router";
5 |
6 | import Profile from "./Profile";
7 | import { routers } from "../../routes/routes";
8 | import { IRoutesType } from "../../interfaces";
9 | import contactmanager from "../../asset/cm.png";
10 |
11 | const { Header, Content } = Layout;
12 |
13 | const Logo = styled.img``;
14 |
15 | const PrimaryHeader = styled(Header)`
16 | background: #ffffff 0% 0% no-repeat padding-box;
17 | display: flex;
18 | justify-content: space-between;
19 | height: 60px;
20 | line-height: 60px;
21 | padding: 0 20px;
22 | `;
23 | const StyledLayout = styled(Layout)`
24 | background: #f9fcff 0% 0% no-repeat padding-box;
25 | height: 100vh;
26 | `;
27 |
28 | export const TableWrapper = styled.div`
29 | padding: 16px 200px;
30 | `;
31 |
32 | const AppLayout: React.FC = () => {
33 | return (
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | {routers.map((item: IRoutesType) => (
45 |
51 | ))}
52 |
53 |
54 |
55 |
56 | );
57 | };
58 |
59 | export default AppLayout;
60 |
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@ant-design/icons": "^4.7.0",
7 | "@azure/storage-blob": "^12.8.0",
8 | "@emotion/react": "^11.7.1",
9 | "@emotion/styled": "^11.6.0",
10 | "@redux-saga/types": "^1.1.0",
11 | "@testing-library/jest-dom": "^5.14.1",
12 | "@testing-library/react": "^12.0.0",
13 | "@testing-library/user-event": "^13.2.1",
14 | "@types/jest": "^27.0.1",
15 | "@types/js-cookie": "^3.0.1",
16 | "@types/node": "^16.7.13",
17 | "@types/react": "^17.0.20",
18 | "@types/react-dom": "^17.0.9",
19 | "@types/react-redux": "^7.1.22",
20 | "@types/react-router": "5.1.16",
21 | "@types/react-router-dom": "5.1.8",
22 | "antd": "^4.18.2",
23 | "antd-img-crop": "^4.1.0",
24 | "axios": "^0.24.0",
25 | "js-cookie": "^3.0.1",
26 | "jwt-decode": "^3.1.2",
27 | "moment": "^2.29.1",
28 | "react": "^17.0.2",
29 | "react-dom": "^17.0.2",
30 | "react-redux": "^7.2.6",
31 | "react-router-dom": "5.2.0",
32 | "react-scripts": "5.0.0",
33 | "redux-saga": "^1.1.3",
34 | "typescript": "^4.4.2",
35 | "web-vitals": "^2.1.0"
36 | },
37 | "scripts": {
38 | "start": "react-scripts start",
39 | "build": "react-scripts build",
40 | "test": "react-scripts test",
41 | "eject": "react-scripts eject"
42 | },
43 | "eslintConfig": {
44 | "extends": [
45 | "react-app",
46 | "react-app/jest"
47 | ]
48 | },
49 | "browserslist": {
50 | "production": [
51 | ">0.2%",
52 | "not dead",
53 | "not op_mini all"
54 | ],
55 | "development": [
56 | "last 1 chrome version",
57 | "last 1 firefox version",
58 | "last 1 safari version"
59 | ]
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/redux/actions/contactsAction.ts:
--------------------------------------------------------------------------------
1 | import types from "../types";
2 | import { IContact, IDeleteContact, IUserID } from "../../interfaces";
3 |
4 | export function getAllContacts(payload: IUserID) {
5 | return {
6 | type: types.GETALL_CONTACTS,
7 | payload,
8 | };
9 | }
10 |
11 | export function getAllContactsSuccess(payload: IContact) {
12 | return {
13 | type: types.GETALL_CONTACTS_SUCCESS,
14 | payload,
15 | };
16 | }
17 |
18 | export function createContacts(payload: IContact) {
19 | return {
20 | type: types.CREATE_CONTACT,
21 | payload,
22 | };
23 | }
24 |
25 | export function createContactsSuccess(payload: any) {
26 | return {
27 | type: types.DELETE_CONTACT_SUCCESS,
28 | payload,
29 | };
30 | }
31 |
32 | export function deleteContact(payload: any) {
33 | return {
34 | type: types.DELETE_CONTACT,
35 | payload,
36 | };
37 | }
38 |
39 | export function deleteContactSuccess(payload: IDeleteContact) {
40 | return {
41 | type: types.DELETE_CONTACT_SUCCESS,
42 | payload,
43 | };
44 | }
45 |
46 | export function updateFavouriteContact(payload: any) {
47 | return {
48 | type: types.UPDATE_FAVOURITE_CONTACT,
49 | payload,
50 | };
51 | }
52 |
53 | export function updateFavouriteContactSuccess(payload: IDeleteContact) {
54 | return {
55 | type: types.UPDATE_FAVOURITE_CONTACT_SUCCESS,
56 | payload,
57 | };
58 | }
59 |
60 | export function updateContact(payload: IContact) {
61 | return {
62 | type: types.UPDATE_CONTACT,
63 | payload,
64 | };
65 | }
66 |
67 | export function updateContactSuccess(payload: IContact) {
68 | return {
69 | type: types.UPDATE_CONTACT_SUCCESS,
70 | payload,
71 | };
72 | }
73 |
74 | export function contactsRequestFailed(payload: string) {
75 | return {
76 | type: types.CONTACTS_REQUEST_FAILED,
77 | payload,
78 | };
79 | }
80 |
--------------------------------------------------------------------------------
/app/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | Contact Manager
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/components/TopHeader/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import styled from "@emotion/styled";
4 | import { Col, Row, Space } from "antd";
5 |
6 | import { Color } from "../../constants/Color";
7 | import { CButton, CTitle } from "../../components/Custom";
8 |
9 | type Props = {
10 | title: string;
11 | save?: string;
12 | cancel?: string;
13 | onSave?: () => void;
14 | };
15 |
16 | const Wrapper = styled.section`
17 | padding: 16px;
18 | line-height: 28px;
19 | `;
20 |
21 | const CancelButton = styled(CButton)`
22 | height: 30px;
23 | border: 1px solid #2e2e2e;
24 | border-radius: 6px;
25 | background: #f9fcff;
26 | :hover {
27 | box-shadow: inset 0px 0px 1px 0 #2e2e2e;
28 | }
29 | `;
30 |
31 | const SaveButton = styled(CButton)`
32 | height: 30px;
33 | `;
34 |
35 | const PageTitle = styled(CTitle)`
36 | letter-spacing: 0px;
37 | font-size: 18px;
38 | `;
39 |
40 | const TopHeader: React.FC = ({ title, save, cancel, onSave }) => {
41 | return (
42 |
43 |
44 |
45 |
46 | {title}
47 |
48 |
49 |
50 |
51 | {cancel && (
52 | window.history.back()}
57 | >
58 | {cancel}
59 |
60 | )}
61 | {save && (
62 |
63 | {save}
64 |
65 | )}
66 |
67 |
68 |
69 |
70 | );
71 | };
72 |
73 | export default TopHeader;
74 |
--------------------------------------------------------------------------------
/app/src/components/Custom/CTable/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Table } from "antd";
3 | import styled from "@emotion/styled";
4 | import { Color } from "../../../constants/Color";
5 |
6 | const StyledTable = styled(Table)`
7 | width: 100%;
8 | .ant-table {
9 | background: #ffffff 0% 0% no-repeat padding-box;
10 | box-shadow: 0px 25px 40px #0000000d;
11 | border-radius: 10px;
12 | }
13 | table {
14 | thead {
15 | tr {
16 | background: #fdfdfd;
17 | th {
18 | background: #ffffff;
19 | letter-spacing: 0.14px;
20 | color: ${Color.textDark};
21 | font-size: 13px;
22 | font-weight: 500;
23 | &:before {
24 | display: none;
25 | }
26 | }
27 | }
28 | }
29 | tbody {
30 | .ant-table-row {
31 | color: ${Color.textDark};
32 | font-weight: 500;
33 | &:last-child {
34 | td {
35 | &:first-of-type {
36 | border-top-left-radius: 10px !important;
37 | border-bottom-left-radius: 10px !important;
38 | }
39 | &:last-child {
40 | border-top-right-radius: 10px !important;
41 | border-bottom-right-radius: 10px !important;
42 | }
43 | }
44 | }
45 | td {
46 | background: #fff;
47 | padding: 8px 16px;
48 | font-size: 12px;
49 | :hover {
50 | background: #2e4dd40d !important;
51 | }
52 | }
53 | :hover {
54 | cursor: pointer;
55 | }
56 | }
57 | }
58 | }
59 | .ant-table-empty {
60 | .ant-table-placeholder {
61 | .ant-table-cell {
62 | border-bottom: none;
63 | }
64 | :hover {
65 | .ant-table-cell {
66 | border-bottom: none;
67 | }
68 | }
69 | }
70 | }
71 | `;
72 |
73 | export const CTable = React.forwardRef((props: any, ref: any) => {
74 | return ;
75 | });
76 |
--------------------------------------------------------------------------------
/app/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `yarn start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `yarn test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `yarn build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `yarn eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
--------------------------------------------------------------------------------
/app/src/redux/sagas/usersSaga.ts:
--------------------------------------------------------------------------------
1 | import Cookies from "js-cookie";
2 | import { notification } from "antd";
3 | import { AxiosResponse } from "axios";
4 | import { call, put, takeEvery } from "redux-saga/effects";
5 |
6 | import types from "../types";
7 | import { AnyAction } from "redux";
8 | import Axios from "../../utils/axios";
9 | import {
10 | getUserDetailSuccess,
11 | signinUserSuccess,
12 | signupUserSuccess,
13 | UserRequestFailed,
14 | } from "../actions/userAction";
15 |
16 | function* signinUser(action: AnyAction) {
17 | try {
18 | const response: AxiosResponse = yield call(() =>
19 | Axios.post("signin/", action.payload)
20 | );
21 | Cookies.set("token", response.data.token);
22 | Cookies.set("userId", response.data._id);
23 | yield put(signinUserSuccess(response.data));
24 | yield call(action.payload.onSuccess);
25 | } catch (error: any) {
26 | yield put(UserRequestFailed(error));
27 | notification.error({
28 | message: "Sign in Failed",
29 | description: error.response.data.detail,
30 | duration: 2,
31 | });
32 | }
33 | }
34 |
35 | function* signupUser(action: AnyAction) {
36 | try {
37 | yield call(() =>
38 | Axios.post("signup/", action.payload)
39 | );
40 | yield put(signupUserSuccess());
41 | yield call(action.payload.onSuccess);
42 | } catch (error: any) {
43 | yield put(UserRequestFailed(error));
44 | notification.error({
45 | message: "Sign up Failed",
46 | description: error.response.data.detail,
47 | duration: 2,
48 | });
49 | }
50 | }
51 |
52 | function* getUserDetail(action: AnyAction) {
53 | try {
54 | const response: AxiosResponse = yield call(() =>
55 | Axios.get("user-detail/", action.payload)
56 | );
57 | yield put(getUserDetailSuccess(response && response.data));
58 | } catch (error: any) {
59 | yield put(UserRequestFailed(error));
60 | notification.error({
61 | message: "loading user detail failed",
62 | description: error.response.data.detail,
63 | duration: 2,
64 | });
65 | }
66 | }
67 |
68 | export function* usersSaga() {
69 | yield takeEvery(types.SIGNIN_USER, signinUser);
70 | yield takeEvery(types.SIGNUP_USER, signupUser);
71 | yield takeEvery(types.GET_USER_DETAIL, getUserDetail);
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/pages/Contacts/UploadProfile.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 |
3 | import ImgCrop from "antd-img-crop";
4 | import styled from "@emotion/styled";
5 | import { message, Upload } from "antd";
6 | import { PlusOutlined, LoadingOutlined } from "@ant-design/icons";
7 |
8 | const UploadPic = styled(Upload)`
9 | .ant-upload {
10 | position: absolute;
11 | top: 50%;
12 | left: 50%;
13 | transform: translate(-50%, -50%);
14 | width: 200px;
15 | height: 200px;
16 | border: 1px solid #ffffff;
17 | box-shadow: inset 0px 0px 19px #2c2e320d;
18 | background: white;
19 | }
20 | `;
21 |
22 | const ProfilePic = styled.img`
23 | width: 100%;
24 | height: 100%;
25 | `;
26 |
27 | type uploadProfileProps = {
28 | imageurl: string;
29 | setImageurl: React.Dispatch>;
30 | };
31 |
32 | const UploadProfile = (props: uploadProfileProps) => {
33 | const { imageurl, setImageurl } = props;
34 |
35 | const [loading, setLoading] = useState(false);
36 |
37 | function beforeUpload(file: any) {
38 | const isJpgOrPng = file.type === "image/jpeg" || file.type === "image/png";
39 | if (!isJpgOrPng) {
40 | message.error("You can only upload JPG/PNG file!");
41 | }
42 | const isLt2M = file.size / 1024 / 1024 < 10;
43 | if (!isLt2M) {
44 | message.error("Image must smaller than 10MB!");
45 | }
46 | return isJpgOrPng && isLt2M;
47 | }
48 |
49 | const handleChange = (info: any) => {
50 | if (info.file.status === "uploading") {
51 | setLoading(true);
52 | return;
53 | }
54 | if (info.file.status === "done") {
55 | const displayImg = info.file.response.data.image.url;
56 | setImageurl(displayImg);
57 | setLoading(false);
58 | }
59 | };
60 |
61 | const uploadButton = (
62 |
63 | {loading ?
:
}
64 |
Upload
65 |
66 | );
67 |
68 | return (
69 |
70 |
79 | {imageurl ? : uploadButton}
80 |
81 |
82 | );
83 | };
84 |
85 | export default UploadProfile;
86 |
--------------------------------------------------------------------------------
/app/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/pages/Login/Login.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import styled from "@emotion/styled";
4 | import { Link } from "react-router-dom";
5 | import { useForm } from "antd/lib/form/Form";
6 | import { Form, notification } from "antd";
7 |
8 | import { useHistory } from "react-router";
9 | import { ILogin } from "../../interfaces";
10 | import { signinUser } from "../../redux/actions/userAction";
11 | import {
12 | CInput,
13 | CFormItem,
14 | CInputPassword,
15 | CButton,
16 | } from "../../components/Custom";
17 | import { useAppDispatch, useAppSelector } from "../../utils/reduxHooks";
18 |
19 | const Wrapper = styled.div`
20 | position: absolute;
21 | top: 50%;
22 | left: 50%;
23 | transform: translate(-50%, -50%);
24 | width: 20%;
25 | box-shadow: 0 2px 4px rgb(0 0 0 / 10%), 0 8px 16px rgb(0 0 0 / 10%);
26 | padding: 20px;
27 | border-radius: 8px;
28 | .ant-form-item-control-input-content {
29 | text-align: center;
30 | }
31 | `;
32 |
33 | const Register = styled.div`
34 | padding-top: 10px;
35 | `;
36 |
37 | const Login = () => {
38 | const [form] = useForm();
39 | const dispatch = useAppDispatch();
40 | const history = useHistory();
41 | const { isLoading } = useAppSelector((state) => state.users);
42 |
43 | const onFinish = (values: ILogin) => {
44 | dispatch(
45 | signinUser({
46 | ...values,
47 | onSuccess: () => {
48 | notification.success({
49 | message: "Success",
50 | description: "Login successfully",
51 | duration: 2,
52 | });
53 | history.push("/contact");
54 | },
55 | })
56 | );
57 | };
58 |
59 | return (
60 |
61 |
85 |
86 | );
87 | };
88 |
89 | export default Login;
90 |
--------------------------------------------------------------------------------
/app/src/components/layout/Profile.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 |
3 | import Cookies from "js-cookie";
4 | import styled from "@emotion/styled";
5 | import { useHistory } from "react-router";
6 | import { LogoutOutlined } from "@ant-design/icons";
7 | import { Avatar, Button, Col, Dropdown, Menu, Row } from "antd";
8 |
9 | import { Color } from "../../constants/Color";
10 |
11 | import { getFirstLetter } from "../../utils/getFirstLetter";
12 | import { getUserDetail } from "../../redux/actions/userAction";
13 | import { useAppDispatch, useAppSelector } from "../../utils/reduxHooks";
14 |
15 | const FullName = styled("p")`
16 | font-size: 14px;
17 | font-weight: 600;
18 | letter-spacing: 0px;
19 | color: ${Color.textDark};
20 | margin-bottom: 0;
21 | line-height: 25px;
22 | text-transform: capitalize;
23 | `;
24 |
25 | const Designation = styled("p")`
26 | font-size: 13px;
27 | letter-spacing: 0px;
28 | color: ${Color.textGrey};
29 | margin-bottom: 0;
30 | line-height: 12px;
31 | `;
32 |
33 | const ProfileSection = () => {
34 | const history = useHistory();
35 | const dispatch = useAppDispatch();
36 | const { data } = useAppSelector((state) => state.users);
37 | const userId: any = Cookies.get("userId");
38 |
39 | useEffect(() => {
40 | dispatch(getUserDetail({ params: { user_id: userId } }));
41 | }, [dispatch, userId]);
42 |
43 | const logOut = () => {
44 | Cookies.remove("token");
45 | Cookies.remove("userId");
46 |
47 | dispatch({
48 | type: "SIGNOUT_USER",
49 | });
50 | history.push("/");
51 | };
52 | const menu = (
53 |
61 | );
62 | return (
63 |
64 |
65 | {data?.full_name}
66 | {data?.email}
67 |
68 |
69 |
75 |
82 | {getFirstLetter(data?.full_name)}
83 |
84 |
85 |
86 |
87 | );
88 | };
89 |
90 | export default ProfileSection;
91 |
--------------------------------------------------------------------------------
/app/src/pages/Register/Register.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import styled from "@emotion/styled";
4 | import { Link } from "react-router-dom";
5 | import { Form, notification } from "antd";
6 | import { useHistory } from "react-router";
7 | import { useForm } from "antd/lib/form/Form";
8 |
9 | import { IRegister } from "../../interfaces";
10 | import { signupUser } from "../../redux/actions/userAction";
11 | import { useAppDispatch, useAppSelector } from "../../utils/reduxHooks";
12 | import {
13 | CInput,
14 | CFormItem,
15 | CInputPassword,
16 | CButton,
17 | } from "../../components/Custom";
18 |
19 | const Wrapper = styled.div`
20 | position: absolute;
21 | top: 50%;
22 | left: 50%;
23 | transform: translate(-50%, -50%);
24 | width: 20%;
25 | box-shadow: 0 2px 4px rgb(0 0 0 / 10%), 0 8px 16px rgb(0 0 0 / 10%);
26 | padding: 20px;
27 | border-radius: 8px;
28 | .ant-form-item-control-input-content {
29 | text-align: center;
30 | }
31 | `;
32 |
33 | const Login = styled.div`
34 | padding-top: 10px;
35 | `;
36 | const Register = () => {
37 | const [form] = useForm();
38 | const dispatch = useAppDispatch();
39 | const history = useHistory();
40 |
41 | const { isLoading } = useAppSelector((state) => state.users);
42 |
43 | const onFinish = (values: IRegister) => {
44 | dispatch(
45 | signupUser({
46 | ...values,
47 | onSuccess: () => {
48 | notification.success({
49 | message: "Success",
50 | description: "Register successfully",
51 | duration: 2,
52 | });
53 | history.push("/");
54 | },
55 | })
56 | );
57 | };
58 |
59 | return (
60 |
61 |
104 |
105 | );
106 | };
107 |
108 | export default Register;
109 |
--------------------------------------------------------------------------------
/app/src/redux/sagas/contactsSaga.ts:
--------------------------------------------------------------------------------
1 | import { notification } from "antd";
2 | import { AxiosResponse } from "axios";
3 | import { call, put, takeEvery } from "redux-saga/effects";
4 |
5 | import types from "../types";
6 | import { AnyAction } from "redux";
7 | import Axios from "../../utils/axios";
8 | import {
9 | contactsRequestFailed,
10 | createContactsSuccess,
11 | deleteContactSuccess,
12 | getAllContactsSuccess,
13 | updateContactSuccess,
14 | updateFavouriteContactSuccess,
15 | } from "../actions/contactsAction";
16 |
17 | function* getAllContacts(action: AnyAction) {
18 | try {
19 | const response: AxiosResponse = yield call(() =>
20 | Axios.get("fetch-contacts/", action.payload)
21 | );
22 | yield put(getAllContactsSuccess(response.data));
23 | } catch (error: any) {
24 | yield put(contactsRequestFailed(error));
25 | notification.error({
26 | message: "Loading contacts failed.",
27 | description:
28 | error.response && error.response.data && error.response.data.detail,
29 | duration: 2,
30 | });
31 | }
32 | }
33 |
34 | function* createContacts(action: AnyAction) {
35 | try {
36 | const response: AxiosResponse = yield call(() =>
37 | Axios.post("create-contact/", action.payload)
38 | );
39 | yield put(createContactsSuccess(response && response.data));
40 | yield call(action.payload.onSuccess);
41 | } catch (error: any) {
42 | console.log(error);
43 | yield put(contactsRequestFailed(error));
44 | notification.error({
45 | message: "creating contacts failed.",
46 | description:
47 | error.response && error.response.data && error.response.data.detail,
48 | duration: 2,
49 | });
50 | }
51 | }
52 |
53 | function* updateContact(action: AnyAction) {
54 | try {
55 | const response: AxiosResponse = yield call(() =>
56 | Axios.put("update-contact/", action.payload)
57 | );
58 | yield put(updateContactSuccess(response.data));
59 | yield call(action.payload.onSuccess);
60 | } catch (error: any) {
61 | console.log(error);
62 | yield put(contactsRequestFailed(error));
63 | notification.error({
64 | message: "updating contacts failed.",
65 | description:
66 | error.response && error.response.data && error.response.data.detail,
67 | duration: 2,
68 | });
69 | }
70 | }
71 |
72 | function* deleteContact(action: AnyAction) {
73 | try {
74 | const response: AxiosResponse = yield call(() =>
75 | Axios.delete("remove-contact/", action.payload)
76 | );
77 | yield put(deleteContactSuccess(response.data));
78 | yield call(action.payload.onSuccess);
79 | } catch (error: any) {
80 | console.log(error);
81 | yield put(contactsRequestFailed(error));
82 | notification.error({
83 | message: "Deleting contacts failed.",
84 | description:
85 | error.response && error.response.data && error.response.data.detail,
86 | duration: 2,
87 | });
88 | }
89 | }
90 |
91 | function* updateFavouriteContact(action: AnyAction) {
92 | try {
93 | const response: AxiosResponse = yield call(() =>
94 | Axios.put("favourite/", action.payload)
95 | );
96 | yield put(updateFavouriteContactSuccess(response.data));
97 | yield call(action.payload.onSuccess);
98 | } catch (error: any) {
99 | yield put(contactsRequestFailed(error));
100 | notification.error({
101 | message: "Updating Favourite contacts failed.",
102 | description:
103 | error.response && error.response.data && error.response.data.detail,
104 | duration: 2,
105 | });
106 | }
107 | }
108 |
109 | export function* contactsSaga() {
110 | yield takeEvery(types.GETALL_CONTACTS, getAllContacts);
111 | yield takeEvery(types.CREATE_CONTACT, createContacts);
112 | yield takeEvery(types.DELETE_CONTACT, deleteContact);
113 | yield takeEvery(types.UPDATE_CONTACT, updateContact);
114 | yield takeEvery(types.UPDATE_FAVOURITE_CONTACT, updateFavouriteContact);
115 | }
116 |
--------------------------------------------------------------------------------
/app/src/pages/Contacts/ContactForm.tsx:
--------------------------------------------------------------------------------
1 | import Cookies from "js-cookie";
2 | import { useHistory } from "react-router";
3 | import React, { useEffect, useState } from "react";
4 |
5 | import styled from "@emotion/styled";
6 | import { Col, Form, notification, Row } from "antd";
7 |
8 | import {
9 | createContacts,
10 | updateContact,
11 | } from "../../redux/actions/contactsAction";
12 | import UploadProfile from "./UploadProfile";
13 | import TopHeader from "../../components/TopHeader";
14 | import { useAppDispatch } from "../../utils/reduxHooks";
15 | import { CFormItem, CInput, CInputNumber } from "../../components/Custom";
16 |
17 | const StyledForm = styled(Form)`
18 | .ant-form-item-label {
19 | padding: 0;
20 | }
21 | `;
22 |
23 | const FormWrapper = styled(Row)`
24 | background: #ffffff 0% 0% no-repeat padding-box;
25 | border-radius: 15px;
26 | box-shadow: 0px 4px 8px #0062ff0d;
27 | margin: 0 16px;
28 | padding: 20px;
29 | `;
30 |
31 | const UploadCol = styled(Col)`
32 | position: relative;
33 | `;
34 |
35 | const ContactForm = (props: any) => {
36 | const [imageurl, setImageurl] = useState("");
37 | const dispatch = useAppDispatch();
38 | const history = useHistory();
39 | const userId = Cookies.get("userId");
40 | const [form] = Form.useForm();
41 |
42 | const contactDetail = props.location.state;
43 |
44 | useEffect(() => {
45 | form.setFieldsValue({ ...contactDetail });
46 | }, [contactDetail, form]);
47 |
48 | const saveForm = (values: any) => {
49 | const data = { ...values, user_id: userId, image: imageurl };
50 |
51 | dispatch(
52 | createContacts({
53 | ...data,
54 | onSuccess: () => {
55 | notification.success({
56 | message: "Success",
57 | description: "Contact created successfully",
58 | duration: 2,
59 | });
60 | history.goBack();
61 | },
62 | })
63 | );
64 | };
65 |
66 | const EditForm = (values: any) => {
67 | const data = {
68 | ...values,
69 | user_id: userId,
70 | _id: contactDetail._id,
71 | image: imageurl,
72 | };
73 |
74 | dispatch(
75 | updateContact({
76 | ...data,
77 | onSuccess: () => {
78 | notification.success({
79 | message: "Success",
80 | description: "Edited successfully",
81 | duration: 2,
82 | });
83 | history.push("/contact");
84 | },
85 | })
86 | );
87 | };
88 | return (
89 |
95 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
117 |
118 |
119 |
120 |
121 |
122 |
137 |
138 |
139 |
140 |
141 |
142 |
153 |
154 |
155 |
156 |
157 |
158 |
168 |
169 |
170 |
171 |
172 |
173 |
174 | );
175 | };
176 |
177 | export default ContactForm;
178 |
--------------------------------------------------------------------------------
/app/src/pages/Contacts/index.tsx:
--------------------------------------------------------------------------------
1 | import Cookies from "js-cookie";
2 | import styled from "@emotion/styled";
3 | import { useHistory } from "react-router-dom";
4 | import React, { useEffect, useMemo } from "react";
5 |
6 | import {
7 | Tooltip,
8 | Card,
9 | List,
10 | Avatar,
11 | Row,
12 | Col,
13 | Popconfirm,
14 | notification,
15 | Rate,
16 | Empty,
17 | } from "antd";
18 | import { EditOutlined, DeleteOutlined } from "@ant-design/icons";
19 |
20 | import { IContact } from "../../interfaces";
21 | import { CTable } from "../../components/Custom";
22 | import TopHeader from "../../components/TopHeader";
23 | import {
24 | deleteContact,
25 | getAllContacts,
26 | updateFavouriteContact,
27 | } from "../../redux/actions/contactsAction";
28 | import { useAppDispatch, useAppSelector } from "../../utils/reduxHooks";
29 |
30 | export const TableWrapper = styled.div`
31 | padding: 16px 0px;
32 | `;
33 |
34 | const StyledCard = styled(Card)`
35 | border-radius: 20px;
36 | margin: 10px;
37 | `;
38 |
39 | const Icons = styled(Row)`
40 | justify-content: space-evenly;
41 | `;
42 |
43 | const Edit = styled(EditOutlined)`
44 | font-size: 18px;
45 | `;
46 |
47 | const Delete = styled(DeleteOutlined)`
48 | font-size: 18px;
49 | `;
50 |
51 | const ListItem = styled(List.Item)`
52 | .ant-list-item-meta-avatar {
53 | align-self: center;
54 | .ant-avatar {
55 | color: rgb(245, 106, 0);
56 | background-color: rgb(253, 227, 207);
57 | }
58 | }
59 | `;
60 |
61 | const Contacts = () => {
62 | const history = useHistory();
63 | const dispatch = useAppDispatch();
64 | const { isLoading, data } = useAppSelector((state) => state.contacts);
65 |
66 | const userId: any = Cookies.get("userId");
67 |
68 | const filterData: any = useMemo(() => {
69 | const favouriteData = data
70 | .filter((item: IContact) => item.favourite === 1)
71 | .sort();
72 |
73 | const normalData = data
74 | .filter((item: IContact) => item.favourite === 0)
75 | .sort();
76 |
77 | return [...favouriteData, ...normalData];
78 | }, [data]);
79 |
80 | useEffect(() => {
81 | dispatch(getAllContacts({ params: { user_id: userId } }));
82 | }, [dispatch, userId]);
83 |
84 | const OnDelete = (id: string, userId: string) => {
85 | const rawData = {
86 | _id: id,
87 | user_id: userId,
88 | };
89 |
90 | dispatch(
91 | deleteContact({
92 | params: { ...rawData },
93 | onSuccess: () => {
94 | notification.success({
95 | message: "Success",
96 | description: "Deleted contact successfully",
97 | duration: 2,
98 | });
99 | dispatch(getAllContacts({ params: { user_id: userId } }));
100 | },
101 | })
102 | );
103 | };
104 |
105 | const onClickFavorite = (value: number, id: string, userId: string) => {
106 | const data = {
107 | user_id: userId,
108 | contact_id: id,
109 | favourite: value,
110 | };
111 |
112 | const favMessage = value ? "added" : "removed";
113 | dispatch(
114 | updateFavouriteContact({
115 | ...data,
116 | onSuccess: () => {
117 | notification.success({
118 | message: "Success",
119 | description: `Favourite contact ${favMessage} successfully`,
120 | duration: 2,
121 | });
122 | dispatch(getAllContacts({ params: { user_id: userId } }));
123 | },
124 | })
125 | );
126 | };
127 |
128 | const columns = [
129 | {
130 | title: "",
131 | key: "fav",
132 | width: 30,
133 | render: (record: IContact) => {
134 | return (
135 |
139 | onClickFavorite(value, record._id, userId)
140 | }
141 | />
142 | );
143 | },
144 | },
145 | {
146 | title: "Contacts",
147 | key: "contacts",
148 | width: 300,
149 | render: (record: IContact) => {
150 | return (
151 |
152 | }
154 | title={record.full_name}
155 | description={record.email}
156 | />
157 |
158 | );
159 | },
160 | },
161 |
162 | {
163 | title: "Phone Number",
164 | dataIndex: "phone",
165 | key: "phone",
166 | },
167 | {
168 | title: "Address",
169 | dataIndex: "address",
170 | key: "address",
171 | },
172 |
173 | {
174 | title: "",
175 | key: "icons",
176 | render: (record: IContact) => {
177 | return (
178 |
179 |
180 |
181 | history.push("/contact/edit", record)} />
182 |
183 |
184 |
185 | OnDelete(record._id, userId)}
190 | >
191 |
192 |
193 |
194 |
195 | );
196 | },
197 | },
198 | ];
199 |
200 | let locale = {
201 | emptyText: (
202 |
206 | ),
207 | };
208 |
209 | return (
210 |
211 |
history.push("/contact/add")}
215 | />
216 |
217 |
218 |
219 |
227 |
228 |
229 |
230 | );
231 | };
232 |
233 | export default Contacts;
234 |
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | require("dotenv").config();
2 | require("./config/database").connect();
3 | const express = require("express");
4 | const cors = require("cors");
5 |
6 | // importing user context
7 | const User = require("./model/user");
8 | const Contact = require("./model/contact");
9 |
10 | const bcrypt = require("bcryptjs");
11 | const jwt = require("jsonwebtoken");
12 | const auth = require("./middleware/auth");
13 |
14 | const app = express();
15 | app.use(cors());
16 |
17 | app.use(express.json());
18 |
19 | // Signup
20 | app.post("/signup", async (req, res) => {
21 | // Our signup logic starts here
22 | try {
23 | // Get user input
24 | const { full_name, email, password } = req.body;
25 |
26 | // Validate user input
27 | if (!(email && password && full_name)) {
28 | return res.status(400).send({ detail: "All input is required" });
29 | }
30 |
31 | // check if user already exist
32 | // Validate if user exist in our database
33 | const oldUser = await User.findOne({ email });
34 |
35 | if (oldUser) {
36 | return res
37 | .status(409)
38 | .send({ detail: "User Already Exist. Please Sign in" });
39 | }
40 |
41 | //Encrypt user password
42 | encryptedPassword = await bcrypt.hash(password, 10);
43 |
44 | // Create user in our database
45 | const user = await User.create({
46 | full_name,
47 | email: email.toLowerCase(), // sanitize: convert email to lowercase
48 | password: encryptedPassword,
49 | });
50 |
51 | // Create token
52 | const token = jwt.sign(
53 | { user_id: user._id, email },
54 | process.env.TOKEN_KEY,
55 | {
56 | expiresIn: "2h",
57 | }
58 | );
59 |
60 | // save user token
61 | user.token = token;
62 |
63 | // return new user
64 | res.status(201).json(user);
65 | } catch (err) {
66 | console.log(err);
67 | }
68 |
69 | // Our signup logic ends here
70 | });
71 |
72 | // Signin
73 | app.post("/signin", async (req, res) => {
74 | // Our Sign in logic starts here
75 | try {
76 | // Get user input
77 | const { email, password } = req.body;
78 |
79 | // Validate user input
80 | if (!(email && password)) {
81 | res.status(400).send({ detail: "All input is required" });
82 | }
83 | // Validate if user exist in our database
84 | const user = await User.findOne({ email });
85 |
86 | if (user && (await bcrypt.compare(password, user.password))) {
87 | // Create token
88 | const token = jwt.sign(
89 | { user_id: user._id, email },
90 | process.env.TOKEN_KEY,
91 | {
92 | expiresIn: "2h",
93 | }
94 | );
95 |
96 | // save user token
97 | user.token = token;
98 |
99 | // user
100 | res.status(200).json(user);
101 | } else {
102 | res.status(400).send({ detail: "Invalid Credentials" });
103 | }
104 | } catch (err) {
105 | console.log(err);
106 | }
107 | // Our sign in logic ends here
108 | });
109 |
110 | app.post("/create-contact", auth, async (req, res) => {
111 | try {
112 | const { full_name, email, phone, address, user_id, image } = req.body;
113 |
114 | if (!(email && full_name && phone && address)) {
115 | return res.status(400).send({ detail: "All input is required" });
116 | }
117 |
118 | if (!image) {
119 | return res.status(400).send({ detail: "Please upload image" });
120 | }
121 |
122 | const oldContact = await Contact.findOne({ phone, user_id });
123 |
124 | if (oldContact) {
125 | return res
126 | .status(409)
127 | .send({ detail: "Contact number Exist with different name." });
128 | }
129 |
130 | // Create Contact in our database
131 | const contact = await Contact.create({
132 | user_id,
133 | full_name,
134 | phone,
135 | email: email.toLowerCase(), // sanitize: convert email to lowercase
136 | address,
137 | image,
138 | });
139 |
140 | res.status(201).json(contact);
141 | } catch (err) {
142 | console.log(err);
143 | }
144 | });
145 |
146 | // add favourite
147 | app.put("/favourite", auth, async (req, res) => {
148 | const { contact_id, user_id, favourite } = req.body;
149 |
150 | try {
151 | const userId = await Contact.findOne({ user_id, contact_id });
152 |
153 | if (userId) {
154 | const contact = await Contact.findByIdAndUpdate(contact_id, {
155 | favourite,
156 | });
157 |
158 | return res.status(200).json(contact);
159 | }
160 | } catch (error) {
161 | console.log(error);
162 | }
163 | });
164 |
165 | // get all contacts
166 | app.get("/fetch-contacts", auth, async (req, res) => {
167 | try {
168 | const { user_id } = req.query;
169 |
170 | const user = await Contact.findOne({ user_id });
171 |
172 | const contact = await Contact.find({ user_id });
173 |
174 | res.status(200).json(contact);
175 | } catch (err) {
176 | console.log(err);
177 | }
178 | });
179 |
180 | // get all contacts
181 | app.get("/user-detail", auth, async (req, res) => {
182 | try {
183 | const { user_id } = req.query;
184 |
185 | const user = await User.findById(user_id);
186 |
187 | // Check if account exists
188 | if (!user) {
189 | return res.status(404).send({ detail: "User does not exist" });
190 | }
191 |
192 | const userDetail = {
193 | id: user._id,
194 | full_name: user.full_name,
195 | email: user.email,
196 | favrite: user.favrite,
197 | };
198 |
199 | res.status(200).json(userDetail);
200 | } catch (err) {
201 | console.log(err);
202 | }
203 | });
204 |
205 | app.delete("/remove-contact", auth, async (req, res) => {
206 | const { _id, user_id } = req.query;
207 |
208 | const oldContact = await Contact.findOne({ _id, user_id });
209 |
210 | // Check if account exists
211 | if (!oldContact) {
212 | return res.status(404).send({ detail: "User does not exist" });
213 | }
214 |
215 | try {
216 | if (_id && user_id) {
217 | const contact = await Contact.deleteOne({ _id, user_id });
218 | return res.status(200).json(contact);
219 | }
220 | } catch (error) {
221 | console.log(error);
222 | }
223 | });
224 |
225 | app.put("/update-contact", auth, async (req, res) => {
226 | try {
227 | const { full_name, email, phone, address, user_id, _id, image } = req.body;
228 |
229 | const contactData = { full_name, email, phone, address, image };
230 |
231 | if (!(email && full_name && phone && address)) {
232 | return res.status(400).send({ detail: "All input is required" });
233 | }
234 |
235 | if (!image) {
236 | return res.status(400).send({ detail: "Please upload image" });
237 | }
238 |
239 | const oldContact = await Contact.findOne({
240 | ...contactData,
241 | });
242 |
243 | if (oldContact) {
244 | return res.status(409).send({ detail: "Already Updated." });
245 | }
246 |
247 | const contact = await Contact.findByIdAndUpdate(_id, contactData);
248 |
249 | res.status(200).json(contact);
250 | } catch (err) {
251 | console.log(err);
252 | }
253 | });
254 |
255 | module.exports = app;
256 |
--------------------------------------------------------------------------------
/server/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@sindresorhus/is@^0.14.0":
6 | version "0.14.0"
7 | resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
8 | integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
9 |
10 | "@szmarczak/http-timer@^1.1.2":
11 | version "1.1.2"
12 | resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
13 | integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==
14 | dependencies:
15 | defer-to-connect "^1.0.1"
16 |
17 | "@types/node@*":
18 | version "17.0.8"
19 | resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.8.tgz#50d680c8a8a78fe30abe6906453b21ad8ab0ad7b"
20 | integrity sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==
21 |
22 | "@types/node@< 17.0.6":
23 | version "17.0.5"
24 | resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.5.tgz#57ca67ec4e57ad9e4ef5a6bab48a15387a1c83e0"
25 | integrity sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==
26 |
27 | "@types/webidl-conversions@*":
28 | version "6.1.1"
29 | resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz#e33bc8ea812a01f63f90481c666334844b12a09e"
30 | integrity sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==
31 |
32 | "@types/whatwg-url@^8.2.1":
33 | version "8.2.1"
34 | resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-8.2.1.tgz#f1aac222dab7c59e011663a0cb0a3117b2ef05d4"
35 | integrity sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==
36 | dependencies:
37 | "@types/node" "*"
38 | "@types/webidl-conversions" "*"
39 |
40 | abbrev@1:
41 | version "1.1.1"
42 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
43 | integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
44 |
45 | accepts@~1.3.7:
46 | version "1.3.7"
47 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
48 | integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
49 | dependencies:
50 | mime-types "~2.1.24"
51 | negotiator "0.6.2"
52 |
53 | ansi-align@^3.0.0:
54 | version "3.0.1"
55 | resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59"
56 | integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==
57 | dependencies:
58 | string-width "^4.1.0"
59 |
60 | ansi-regex@^5.0.1:
61 | version "5.0.1"
62 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
63 | integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
64 |
65 | ansi-styles@^4.0.0, ansi-styles@^4.1.0:
66 | version "4.3.0"
67 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
68 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
69 | dependencies:
70 | color-convert "^2.0.1"
71 |
72 | anymatch@~3.1.2:
73 | version "3.1.2"
74 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
75 | integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
76 | dependencies:
77 | normalize-path "^3.0.0"
78 | picomatch "^2.0.4"
79 |
80 | array-flatten@1.1.1:
81 | version "1.1.1"
82 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
83 | integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
84 |
85 | balanced-match@^1.0.0:
86 | version "1.0.2"
87 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
88 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
89 |
90 | base64-js@^1.3.1:
91 | version "1.5.1"
92 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
93 | integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
94 |
95 | bcryptjs@^2.4.3:
96 | version "2.4.3"
97 | resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb"
98 | integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=
99 |
100 | binary-extensions@^2.0.0:
101 | version "2.2.0"
102 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
103 | integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
104 |
105 | body-parser@1.19.1, body-parser@^1.19.1:
106 | version "1.19.1"
107 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.1.tgz#1499abbaa9274af3ecc9f6f10396c995943e31d4"
108 | integrity sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==
109 | dependencies:
110 | bytes "3.1.1"
111 | content-type "~1.0.4"
112 | debug "2.6.9"
113 | depd "~1.1.2"
114 | http-errors "1.8.1"
115 | iconv-lite "0.4.24"
116 | on-finished "~2.3.0"
117 | qs "6.9.6"
118 | raw-body "2.4.2"
119 | type-is "~1.6.18"
120 |
121 | boxen@^5.0.0:
122 | version "5.1.2"
123 | resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50"
124 | integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==
125 | dependencies:
126 | ansi-align "^3.0.0"
127 | camelcase "^6.2.0"
128 | chalk "^4.1.0"
129 | cli-boxes "^2.2.1"
130 | string-width "^4.2.2"
131 | type-fest "^0.20.2"
132 | widest-line "^3.1.0"
133 | wrap-ansi "^7.0.0"
134 |
135 | brace-expansion@^1.1.7:
136 | version "1.1.11"
137 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
138 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
139 | dependencies:
140 | balanced-match "^1.0.0"
141 | concat-map "0.0.1"
142 |
143 | braces@~3.0.2:
144 | version "3.0.2"
145 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
146 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
147 | dependencies:
148 | fill-range "^7.0.1"
149 |
150 | bson@^4.2.2, bson@^4.6.0:
151 | version "4.6.1"
152 | resolved "https://registry.yarnpkg.com/bson/-/bson-4.6.1.tgz#2b5da517539bb0f7f3ffb54ac70a384ca899641c"
153 | integrity sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw==
154 | dependencies:
155 | buffer "^5.6.0"
156 |
157 | buffer-equal-constant-time@1.0.1:
158 | version "1.0.1"
159 | resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
160 | integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
161 |
162 | buffer@^5.6.0:
163 | version "5.7.1"
164 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
165 | integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
166 | dependencies:
167 | base64-js "^1.3.1"
168 | ieee754 "^1.1.13"
169 |
170 | bytes@3.1.1:
171 | version "3.1.1"
172 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a"
173 | integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==
174 |
175 | cacheable-request@^6.0.0:
176 | version "6.1.0"
177 | resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
178 | integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==
179 | dependencies:
180 | clone-response "^1.0.2"
181 | get-stream "^5.1.0"
182 | http-cache-semantics "^4.0.0"
183 | keyv "^3.0.0"
184 | lowercase-keys "^2.0.0"
185 | normalize-url "^4.1.0"
186 | responselike "^1.0.2"
187 |
188 | camelcase@^6.2.0:
189 | version "6.3.0"
190 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
191 | integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
192 |
193 | chalk@^4.1.0:
194 | version "4.1.2"
195 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
196 | integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
197 | dependencies:
198 | ansi-styles "^4.1.0"
199 | supports-color "^7.1.0"
200 |
201 | chokidar@^3.5.2:
202 | version "3.5.2"
203 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
204 | integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
205 | dependencies:
206 | anymatch "~3.1.2"
207 | braces "~3.0.2"
208 | glob-parent "~5.1.2"
209 | is-binary-path "~2.1.0"
210 | is-glob "~4.0.1"
211 | normalize-path "~3.0.0"
212 | readdirp "~3.6.0"
213 | optionalDependencies:
214 | fsevents "~2.3.2"
215 |
216 | ci-info@^2.0.0:
217 | version "2.0.0"
218 | resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
219 | integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
220 |
221 | cli-boxes@^2.2.1:
222 | version "2.2.1"
223 | resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
224 | integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==
225 |
226 | clone-response@^1.0.2:
227 | version "1.0.2"
228 | resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
229 | integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=
230 | dependencies:
231 | mimic-response "^1.0.0"
232 |
233 | color-convert@^2.0.1:
234 | version "2.0.1"
235 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
236 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
237 | dependencies:
238 | color-name "~1.1.4"
239 |
240 | color-name@~1.1.4:
241 | version "1.1.4"
242 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
243 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
244 |
245 | concat-map@0.0.1:
246 | version "0.0.1"
247 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
248 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
249 |
250 | configstore@^5.0.1:
251 | version "5.0.1"
252 | resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96"
253 | integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==
254 | dependencies:
255 | dot-prop "^5.2.0"
256 | graceful-fs "^4.1.2"
257 | make-dir "^3.0.0"
258 | unique-string "^2.0.0"
259 | write-file-atomic "^3.0.0"
260 | xdg-basedir "^4.0.0"
261 |
262 | content-disposition@0.5.4:
263 | version "0.5.4"
264 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
265 | integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
266 | dependencies:
267 | safe-buffer "5.2.1"
268 |
269 | content-type@~1.0.4:
270 | version "1.0.4"
271 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
272 | integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
273 |
274 | cookie-signature@1.0.6:
275 | version "1.0.6"
276 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
277 | integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
278 |
279 | cookie@0.4.1:
280 | version "0.4.1"
281 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
282 | integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
283 |
284 | cors@^2.8.5:
285 | version "2.8.5"
286 | resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
287 | integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
288 | dependencies:
289 | object-assign "^4"
290 | vary "^1"
291 |
292 | crypto-random-string@^2.0.0:
293 | version "2.0.0"
294 | resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
295 | integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
296 |
297 | debug@2.6.9:
298 | version "2.6.9"
299 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
300 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
301 | dependencies:
302 | ms "2.0.0"
303 |
304 | debug@4.x:
305 | version "4.3.3"
306 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
307 | integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
308 | dependencies:
309 | ms "2.1.2"
310 |
311 | debug@^3.2.7:
312 | version "3.2.7"
313 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
314 | integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
315 | dependencies:
316 | ms "^2.1.1"
317 |
318 | decompress-response@^3.3.0:
319 | version "3.3.0"
320 | resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
321 | integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=
322 | dependencies:
323 | mimic-response "^1.0.0"
324 |
325 | deep-extend@^0.6.0:
326 | version "0.6.0"
327 | resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
328 | integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
329 |
330 | defer-to-connect@^1.0.1:
331 | version "1.1.3"
332 | resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591"
333 | integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==
334 |
335 | denque@^2.0.1:
336 | version "2.0.1"
337 | resolved "https://registry.yarnpkg.com/denque/-/denque-2.0.1.tgz#bcef4c1b80dc32efe97515744f21a4229ab8934a"
338 | integrity sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==
339 |
340 | depd@~1.1.2:
341 | version "1.1.2"
342 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
343 | integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
344 |
345 | destroy@~1.0.4:
346 | version "1.0.4"
347 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
348 | integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
349 |
350 | dot-prop@^5.2.0:
351 | version "5.3.0"
352 | resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
353 | integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==
354 | dependencies:
355 | is-obj "^2.0.0"
356 |
357 | dotenv@^11.0.0:
358 | version "11.0.0"
359 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-11.0.0.tgz#ee37feddf8ada6d348a79e198312d4a8abfd1c1e"
360 | integrity sha512-Fp/b504Y5W+e+FpCxTFMUZ7ZEQkQYF0rx+KZtmwixJxGQbLHrhCwo3FjZgNC8vIfrSi29PABNbMoCGD9YoiXbQ==
361 |
362 | duplexer3@^0.1.4:
363 | version "0.1.4"
364 | resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
365 | integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
366 |
367 | ecdsa-sig-formatter@1.0.11:
368 | version "1.0.11"
369 | resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
370 | integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
371 | dependencies:
372 | safe-buffer "^5.0.1"
373 |
374 | ee-first@1.1.1:
375 | version "1.1.1"
376 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
377 | integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
378 |
379 | emoji-regex@^8.0.0:
380 | version "8.0.0"
381 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
382 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
383 |
384 | encodeurl@~1.0.2:
385 | version "1.0.2"
386 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
387 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
388 |
389 | end-of-stream@^1.1.0:
390 | version "1.4.4"
391 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
392 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
393 | dependencies:
394 | once "^1.4.0"
395 |
396 | escape-goat@^2.0.0:
397 | version "2.1.1"
398 | resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
399 | integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==
400 |
401 | escape-html@~1.0.3:
402 | version "1.0.3"
403 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
404 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
405 |
406 | etag@~1.8.1:
407 | version "1.8.1"
408 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
409 | integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
410 |
411 | express@^4.17.2:
412 | version "4.17.2"
413 | resolved "https://registry.yarnpkg.com/express/-/express-4.17.2.tgz#c18369f265297319beed4e5558753cc8c1364cb3"
414 | integrity sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==
415 | dependencies:
416 | accepts "~1.3.7"
417 | array-flatten "1.1.1"
418 | body-parser "1.19.1"
419 | content-disposition "0.5.4"
420 | content-type "~1.0.4"
421 | cookie "0.4.1"
422 | cookie-signature "1.0.6"
423 | debug "2.6.9"
424 | depd "~1.1.2"
425 | encodeurl "~1.0.2"
426 | escape-html "~1.0.3"
427 | etag "~1.8.1"
428 | finalhandler "~1.1.2"
429 | fresh "0.5.2"
430 | merge-descriptors "1.0.1"
431 | methods "~1.1.2"
432 | on-finished "~2.3.0"
433 | parseurl "~1.3.3"
434 | path-to-regexp "0.1.7"
435 | proxy-addr "~2.0.7"
436 | qs "6.9.6"
437 | range-parser "~1.2.1"
438 | safe-buffer "5.2.1"
439 | send "0.17.2"
440 | serve-static "1.14.2"
441 | setprototypeof "1.2.0"
442 | statuses "~1.5.0"
443 | type-is "~1.6.18"
444 | utils-merge "1.0.1"
445 | vary "~1.1.2"
446 |
447 | fill-range@^7.0.1:
448 | version "7.0.1"
449 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
450 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
451 | dependencies:
452 | to-regex-range "^5.0.1"
453 |
454 | finalhandler@~1.1.2:
455 | version "1.1.2"
456 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
457 | integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
458 | dependencies:
459 | debug "2.6.9"
460 | encodeurl "~1.0.2"
461 | escape-html "~1.0.3"
462 | on-finished "~2.3.0"
463 | parseurl "~1.3.3"
464 | statuses "~1.5.0"
465 | unpipe "~1.0.0"
466 |
467 | forwarded@0.2.0:
468 | version "0.2.0"
469 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
470 | integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
471 |
472 | fresh@0.5.2:
473 | version "0.5.2"
474 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
475 | integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
476 |
477 | fsevents@~2.3.2:
478 | version "2.3.2"
479 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
480 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
481 |
482 | get-stream@^4.1.0:
483 | version "4.1.0"
484 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
485 | integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
486 | dependencies:
487 | pump "^3.0.0"
488 |
489 | get-stream@^5.1.0:
490 | version "5.2.0"
491 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
492 | integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
493 | dependencies:
494 | pump "^3.0.0"
495 |
496 | glob-parent@~5.1.2:
497 | version "5.1.2"
498 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
499 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
500 | dependencies:
501 | is-glob "^4.0.1"
502 |
503 | global-dirs@^3.0.0:
504 | version "3.0.0"
505 | resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686"
506 | integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==
507 | dependencies:
508 | ini "2.0.0"
509 |
510 | got@^9.6.0:
511 | version "9.6.0"
512 | resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
513 | integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==
514 | dependencies:
515 | "@sindresorhus/is" "^0.14.0"
516 | "@szmarczak/http-timer" "^1.1.2"
517 | cacheable-request "^6.0.0"
518 | decompress-response "^3.3.0"
519 | duplexer3 "^0.1.4"
520 | get-stream "^4.1.0"
521 | lowercase-keys "^1.0.1"
522 | mimic-response "^1.0.1"
523 | p-cancelable "^1.0.0"
524 | to-readable-stream "^1.0.0"
525 | url-parse-lax "^3.0.0"
526 |
527 | graceful-fs@^4.1.2:
528 | version "4.2.9"
529 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96"
530 | integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==
531 |
532 | has-flag@^3.0.0:
533 | version "3.0.0"
534 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
535 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
536 |
537 | has-flag@^4.0.0:
538 | version "4.0.0"
539 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
540 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
541 |
542 | has-yarn@^2.1.0:
543 | version "2.1.0"
544 | resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77"
545 | integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==
546 |
547 | http-cache-semantics@^4.0.0:
548 | version "4.1.0"
549 | resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
550 | integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
551 |
552 | http-errors@1.8.1:
553 | version "1.8.1"
554 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c"
555 | integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==
556 | dependencies:
557 | depd "~1.1.2"
558 | inherits "2.0.4"
559 | setprototypeof "1.2.0"
560 | statuses ">= 1.5.0 < 2"
561 | toidentifier "1.0.1"
562 |
563 | iconv-lite@0.4.24:
564 | version "0.4.24"
565 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
566 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
567 | dependencies:
568 | safer-buffer ">= 2.1.2 < 3"
569 |
570 | ieee754@^1.1.13:
571 | version "1.2.1"
572 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
573 | integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
574 |
575 | ignore-by-default@^1.0.1:
576 | version "1.0.1"
577 | resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
578 | integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk=
579 |
580 | import-lazy@^2.1.0:
581 | version "2.1.0"
582 | resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
583 | integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=
584 |
585 | imurmurhash@^0.1.4:
586 | version "0.1.4"
587 | resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
588 | integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
589 |
590 | inherits@2.0.4:
591 | version "2.0.4"
592 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
593 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
594 |
595 | ini@2.0.0:
596 | version "2.0.0"
597 | resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5"
598 | integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==
599 |
600 | ini@~1.3.0:
601 | version "1.3.8"
602 | resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
603 | integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
604 |
605 | ipaddr.js@1.9.1:
606 | version "1.9.1"
607 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
608 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
609 |
610 | is-binary-path@~2.1.0:
611 | version "2.1.0"
612 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
613 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
614 | dependencies:
615 | binary-extensions "^2.0.0"
616 |
617 | is-ci@^2.0.0:
618 | version "2.0.0"
619 | resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
620 | integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==
621 | dependencies:
622 | ci-info "^2.0.0"
623 |
624 | is-extglob@^2.1.1:
625 | version "2.1.1"
626 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
627 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
628 |
629 | is-fullwidth-code-point@^3.0.0:
630 | version "3.0.0"
631 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
632 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
633 |
634 | is-glob@^4.0.1, is-glob@~4.0.1:
635 | version "4.0.3"
636 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
637 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
638 | dependencies:
639 | is-extglob "^2.1.1"
640 |
641 | is-installed-globally@^0.4.0:
642 | version "0.4.0"
643 | resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520"
644 | integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==
645 | dependencies:
646 | global-dirs "^3.0.0"
647 | is-path-inside "^3.0.2"
648 |
649 | is-npm@^5.0.0:
650 | version "5.0.0"
651 | resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8"
652 | integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==
653 |
654 | is-number@^7.0.0:
655 | version "7.0.0"
656 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
657 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
658 |
659 | is-obj@^2.0.0:
660 | version "2.0.0"
661 | resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982"
662 | integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==
663 |
664 | is-path-inside@^3.0.2:
665 | version "3.0.3"
666 | resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
667 | integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
668 |
669 | is-typedarray@^1.0.0:
670 | version "1.0.0"
671 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
672 | integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
673 |
674 | is-yarn-global@^0.3.0:
675 | version "0.3.0"
676 | resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
677 | integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==
678 |
679 | json-buffer@3.0.0:
680 | version "3.0.0"
681 | resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
682 | integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
683 |
684 | jsonwebtoken@^8.5.1:
685 | version "8.5.1"
686 | resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
687 | integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
688 | dependencies:
689 | jws "^3.2.2"
690 | lodash.includes "^4.3.0"
691 | lodash.isboolean "^3.0.3"
692 | lodash.isinteger "^4.0.4"
693 | lodash.isnumber "^3.0.3"
694 | lodash.isplainobject "^4.0.6"
695 | lodash.isstring "^4.0.1"
696 | lodash.once "^4.0.0"
697 | ms "^2.1.1"
698 | semver "^5.6.0"
699 |
700 | jwa@^1.4.1:
701 | version "1.4.1"
702 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
703 | integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
704 | dependencies:
705 | buffer-equal-constant-time "1.0.1"
706 | ecdsa-sig-formatter "1.0.11"
707 | safe-buffer "^5.0.1"
708 |
709 | jws@^3.2.2:
710 | version "3.2.2"
711 | resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
712 | integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
713 | dependencies:
714 | jwa "^1.4.1"
715 | safe-buffer "^5.0.1"
716 |
717 | kareem@2.3.3:
718 | version "2.3.3"
719 | resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.3.tgz#a4432d7965a5bb06fc2b4eeae71317344c9a756a"
720 | integrity sha512-uESCXM2KdtOQ8LOvKyTUXEeg0MkYp4wGglTIpGcYHvjJcS5sn2Wkfrfit8m4xSbaNDAw2KdI9elgkOxZbrFYbg==
721 |
722 | keyv@^3.0.0:
723 | version "3.1.0"
724 | resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"
725 | integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==
726 | dependencies:
727 | json-buffer "3.0.0"
728 |
729 | latest-version@^5.1.0:
730 | version "5.1.0"
731 | resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face"
732 | integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==
733 | dependencies:
734 | package-json "^6.3.0"
735 |
736 | lodash.includes@^4.3.0:
737 | version "4.3.0"
738 | resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
739 | integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
740 |
741 | lodash.isboolean@^3.0.3:
742 | version "3.0.3"
743 | resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
744 | integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
745 |
746 | lodash.isinteger@^4.0.4:
747 | version "4.0.4"
748 | resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
749 | integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=
750 |
751 | lodash.isnumber@^3.0.3:
752 | version "3.0.3"
753 | resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
754 | integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
755 |
756 | lodash.isplainobject@^4.0.6:
757 | version "4.0.6"
758 | resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
759 | integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
760 |
761 | lodash.isstring@^4.0.1:
762 | version "4.0.1"
763 | resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
764 | integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
765 |
766 | lodash.once@^4.0.0:
767 | version "4.1.1"
768 | resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
769 | integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
770 |
771 | lowercase-keys@^1.0.0, lowercase-keys@^1.0.1:
772 | version "1.0.1"
773 | resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
774 | integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
775 |
776 | lowercase-keys@^2.0.0:
777 | version "2.0.0"
778 | resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
779 | integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
780 |
781 | lru-cache@^6.0.0:
782 | version "6.0.0"
783 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
784 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
785 | dependencies:
786 | yallist "^4.0.0"
787 |
788 | make-dir@^3.0.0:
789 | version "3.1.0"
790 | resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
791 | integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
792 | dependencies:
793 | semver "^6.0.0"
794 |
795 | media-typer@0.3.0:
796 | version "0.3.0"
797 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
798 | integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
799 |
800 | memory-pager@^1.0.2:
801 | version "1.5.0"
802 | resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5"
803 | integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==
804 |
805 | merge-descriptors@1.0.1:
806 | version "1.0.1"
807 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
808 | integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
809 |
810 | methods@~1.1.2:
811 | version "1.1.2"
812 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
813 | integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
814 |
815 | mime-db@1.51.0:
816 | version "1.51.0"
817 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c"
818 | integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==
819 |
820 | mime-types@~2.1.24:
821 | version "2.1.34"
822 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24"
823 | integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==
824 | dependencies:
825 | mime-db "1.51.0"
826 |
827 | mime@1.6.0:
828 | version "1.6.0"
829 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
830 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
831 |
832 | mimic-response@^1.0.0, mimic-response@^1.0.1:
833 | version "1.0.1"
834 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
835 | integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
836 |
837 | minimatch@^3.0.4:
838 | version "3.0.4"
839 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
840 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
841 | dependencies:
842 | brace-expansion "^1.1.7"
843 |
844 | minimist@^1.2.0:
845 | version "1.2.5"
846 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
847 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
848 |
849 | mongodb-connection-string-url@^2.3.2:
850 | version "2.4.1"
851 | resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.4.1.tgz#6b3c6c40133a0ad059fe9a0abda64b2a1cb4e8b4"
852 | integrity sha512-d5Kd2bVsKcSA7YI/yo57fSTtMwRQdFkvc5IZwod1RRxJtECeWPPSo7zqcUGJELifRA//Igs4spVtYAmvFCatug==
853 | dependencies:
854 | "@types/whatwg-url" "^8.2.1"
855 | whatwg-url "^11.0.0"
856 |
857 | mongodb@4.2.2:
858 | version "4.2.2"
859 | resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-4.2.2.tgz#cd70568bd96003877e35358ad17a0c5de35c6dfd"
860 | integrity sha512-zt8rCTnTKyMQppyt63qMnrLM5dbADgUk18ORPF1XbtHLIYCyc9hattaYHi0pqMvNxDpgGgUofSVzS+UQErgTug==
861 | dependencies:
862 | bson "^4.6.0"
863 | denque "^2.0.1"
864 | mongodb-connection-string-url "^2.3.2"
865 | optionalDependencies:
866 | saslprep "^1.0.3"
867 |
868 | mongoose@^6.1.6:
869 | version "6.1.6"
870 | resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-6.1.6.tgz#47903ef821b63fc5ef80a259c2331cabf994c957"
871 | integrity sha512-wvNRJ547x/Bn3EMhwbVInNsEp2OdlMxk4Q/vfgAkw8OI+giZQ72S90ZH0H6VzzIhs4lCU9SwXHYa2O0sPicnEQ==
872 | dependencies:
873 | "@types/node" "< 17.0.6"
874 | bson "^4.2.2"
875 | kareem "2.3.3"
876 | mongodb "4.2.2"
877 | mpath "0.8.4"
878 | mquery "4.0.0"
879 | ms "2.1.2"
880 | regexp-clone "1.0.0"
881 | sift "13.5.2"
882 | sliced "1.0.1"
883 |
884 | mpath@0.8.4:
885 | version "0.8.4"
886 | resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.8.4.tgz#6b566d9581621d9e931dd3b142ed3618e7599313"
887 | integrity sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==
888 |
889 | mquery@4.0.0:
890 | version "4.0.0"
891 | resolved "https://registry.yarnpkg.com/mquery/-/mquery-4.0.0.tgz#6c62160ad25289e99e0840907757cdfd62bde775"
892 | integrity sha512-nGjm89lHja+T/b8cybAby6H0YgA4qYC/lx6UlwvHGqvTq8bDaNeCwl1sY8uRELrNbVWJzIihxVd+vphGGn1vBw==
893 | dependencies:
894 | debug "4.x"
895 | regexp-clone "^1.0.0"
896 | sliced "1.0.1"
897 |
898 | ms@2.0.0:
899 | version "2.0.0"
900 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
901 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
902 |
903 | ms@2.1.2:
904 | version "2.1.2"
905 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
906 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
907 |
908 | ms@2.1.3, ms@^2.1.1:
909 | version "2.1.3"
910 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
911 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
912 |
913 | negotiator@0.6.2:
914 | version "0.6.2"
915 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
916 | integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
917 |
918 | nodemon@^2.0.15:
919 | version "2.0.15"
920 | resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.15.tgz#504516ce3b43d9dc9a955ccd9ec57550a31a8d4e"
921 | integrity sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA==
922 | dependencies:
923 | chokidar "^3.5.2"
924 | debug "^3.2.7"
925 | ignore-by-default "^1.0.1"
926 | minimatch "^3.0.4"
927 | pstree.remy "^1.1.8"
928 | semver "^5.7.1"
929 | supports-color "^5.5.0"
930 | touch "^3.1.0"
931 | undefsafe "^2.0.5"
932 | update-notifier "^5.1.0"
933 |
934 | nopt@~1.0.10:
935 | version "1.0.10"
936 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
937 | integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=
938 | dependencies:
939 | abbrev "1"
940 |
941 | normalize-path@^3.0.0, normalize-path@~3.0.0:
942 | version "3.0.0"
943 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
944 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
945 |
946 | normalize-url@^4.1.0:
947 | version "4.5.1"
948 | resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
949 | integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==
950 |
951 | object-assign@^4:
952 | version "4.1.1"
953 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
954 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
955 |
956 | on-finished@~2.3.0:
957 | version "2.3.0"
958 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
959 | integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
960 | dependencies:
961 | ee-first "1.1.1"
962 |
963 | once@^1.3.1, once@^1.4.0:
964 | version "1.4.0"
965 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
966 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
967 | dependencies:
968 | wrappy "1"
969 |
970 | p-cancelable@^1.0.0:
971 | version "1.1.0"
972 | resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
973 | integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
974 |
975 | package-json@^6.3.0:
976 | version "6.5.0"
977 | resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0"
978 | integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==
979 | dependencies:
980 | got "^9.6.0"
981 | registry-auth-token "^4.0.0"
982 | registry-url "^5.0.0"
983 | semver "^6.2.0"
984 |
985 | parseurl@~1.3.3:
986 | version "1.3.3"
987 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
988 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
989 |
990 | path-to-regexp@0.1.7:
991 | version "0.1.7"
992 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
993 | integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
994 |
995 | picomatch@^2.0.4, picomatch@^2.2.1:
996 | version "2.3.1"
997 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
998 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
999 |
1000 | prepend-http@^2.0.0:
1001 | version "2.0.0"
1002 | resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
1003 | integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
1004 |
1005 | proxy-addr@~2.0.7:
1006 | version "2.0.7"
1007 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
1008 | integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
1009 | dependencies:
1010 | forwarded "0.2.0"
1011 | ipaddr.js "1.9.1"
1012 |
1013 | pstree.remy@^1.1.8:
1014 | version "1.1.8"
1015 | resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
1016 | integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==
1017 |
1018 | pump@^3.0.0:
1019 | version "3.0.0"
1020 | resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
1021 | integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
1022 | dependencies:
1023 | end-of-stream "^1.1.0"
1024 | once "^1.3.1"
1025 |
1026 | punycode@^2.1.1:
1027 | version "2.1.1"
1028 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
1029 | integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
1030 |
1031 | pupa@^2.1.1:
1032 | version "2.1.1"
1033 | resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62"
1034 | integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==
1035 | dependencies:
1036 | escape-goat "^2.0.0"
1037 |
1038 | qs@6.9.6:
1039 | version "6.9.6"
1040 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee"
1041 | integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==
1042 |
1043 | range-parser@~1.2.1:
1044 | version "1.2.1"
1045 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
1046 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
1047 |
1048 | raw-body@2.4.2:
1049 | version "2.4.2"
1050 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.2.tgz#baf3e9c21eebced59dd6533ac872b71f7b61cb32"
1051 | integrity sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==
1052 | dependencies:
1053 | bytes "3.1.1"
1054 | http-errors "1.8.1"
1055 | iconv-lite "0.4.24"
1056 | unpipe "1.0.0"
1057 |
1058 | rc@^1.2.8:
1059 | version "1.2.8"
1060 | resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
1061 | integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
1062 | dependencies:
1063 | deep-extend "^0.6.0"
1064 | ini "~1.3.0"
1065 | minimist "^1.2.0"
1066 | strip-json-comments "~2.0.1"
1067 |
1068 | readdirp@~3.6.0:
1069 | version "3.6.0"
1070 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
1071 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
1072 | dependencies:
1073 | picomatch "^2.2.1"
1074 |
1075 | regexp-clone@1.0.0, regexp-clone@^1.0.0:
1076 | version "1.0.0"
1077 | resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-1.0.0.tgz#222db967623277056260b992626354a04ce9bf63"
1078 | integrity sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==
1079 |
1080 | registry-auth-token@^4.0.0:
1081 | version "4.2.1"
1082 | resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250"
1083 | integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==
1084 | dependencies:
1085 | rc "^1.2.8"
1086 |
1087 | registry-url@^5.0.0:
1088 | version "5.1.0"
1089 | resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009"
1090 | integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==
1091 | dependencies:
1092 | rc "^1.2.8"
1093 |
1094 | responselike@^1.0.2:
1095 | version "1.0.2"
1096 | resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
1097 | integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=
1098 | dependencies:
1099 | lowercase-keys "^1.0.0"
1100 |
1101 | safe-buffer@5.2.1, safe-buffer@^5.0.1:
1102 | version "5.2.1"
1103 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
1104 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
1105 |
1106 | "safer-buffer@>= 2.1.2 < 3":
1107 | version "2.1.2"
1108 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
1109 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
1110 |
1111 | saslprep@^1.0.3:
1112 | version "1.0.3"
1113 | resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226"
1114 | integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==
1115 | dependencies:
1116 | sparse-bitfield "^3.0.3"
1117 |
1118 | semver-diff@^3.1.1:
1119 | version "3.1.1"
1120 | resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b"
1121 | integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==
1122 | dependencies:
1123 | semver "^6.3.0"
1124 |
1125 | semver@^5.6.0, semver@^5.7.1:
1126 | version "5.7.1"
1127 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
1128 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
1129 |
1130 | semver@^6.0.0, semver@^6.2.0, semver@^6.3.0:
1131 | version "6.3.0"
1132 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
1133 | integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
1134 |
1135 | semver@^7.3.4:
1136 | version "7.3.5"
1137 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
1138 | integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
1139 | dependencies:
1140 | lru-cache "^6.0.0"
1141 |
1142 | send@0.17.2:
1143 | version "0.17.2"
1144 | resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820"
1145 | integrity sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==
1146 | dependencies:
1147 | debug "2.6.9"
1148 | depd "~1.1.2"
1149 | destroy "~1.0.4"
1150 | encodeurl "~1.0.2"
1151 | escape-html "~1.0.3"
1152 | etag "~1.8.1"
1153 | fresh "0.5.2"
1154 | http-errors "1.8.1"
1155 | mime "1.6.0"
1156 | ms "2.1.3"
1157 | on-finished "~2.3.0"
1158 | range-parser "~1.2.1"
1159 | statuses "~1.5.0"
1160 |
1161 | serve-static@1.14.2:
1162 | version "1.14.2"
1163 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.2.tgz#722d6294b1d62626d41b43a013ece4598d292bfa"
1164 | integrity sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==
1165 | dependencies:
1166 | encodeurl "~1.0.2"
1167 | escape-html "~1.0.3"
1168 | parseurl "~1.3.3"
1169 | send "0.17.2"
1170 |
1171 | setprototypeof@1.2.0:
1172 | version "1.2.0"
1173 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
1174 | integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
1175 |
1176 | sift@13.5.2:
1177 | version "13.5.2"
1178 | resolved "https://registry.yarnpkg.com/sift/-/sift-13.5.2.tgz#24a715e13c617b086166cd04917d204a591c9da6"
1179 | integrity sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==
1180 |
1181 | signal-exit@^3.0.2:
1182 | version "3.0.6"
1183 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af"
1184 | integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==
1185 |
1186 | sliced@1.0.1:
1187 | version "1.0.1"
1188 | resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41"
1189 | integrity sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=
1190 |
1191 | sparse-bitfield@^3.0.3:
1192 | version "3.0.3"
1193 | resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11"
1194 | integrity sha1-/0rm5oZWBWuks+eSqzM004JzyhE=
1195 | dependencies:
1196 | memory-pager "^1.0.2"
1197 |
1198 | "statuses@>= 1.5.0 < 2", statuses@~1.5.0:
1199 | version "1.5.0"
1200 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
1201 | integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
1202 |
1203 | string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.2:
1204 | version "4.2.3"
1205 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
1206 | integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
1207 | dependencies:
1208 | emoji-regex "^8.0.0"
1209 | is-fullwidth-code-point "^3.0.0"
1210 | strip-ansi "^6.0.1"
1211 |
1212 | strip-ansi@^6.0.0, strip-ansi@^6.0.1:
1213 | version "6.0.1"
1214 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
1215 | integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
1216 | dependencies:
1217 | ansi-regex "^5.0.1"
1218 |
1219 | strip-json-comments@~2.0.1:
1220 | version "2.0.1"
1221 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
1222 | integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
1223 |
1224 | supports-color@^5.5.0:
1225 | version "5.5.0"
1226 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
1227 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
1228 | dependencies:
1229 | has-flag "^3.0.0"
1230 |
1231 | supports-color@^7.1.0:
1232 | version "7.2.0"
1233 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
1234 | integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
1235 | dependencies:
1236 | has-flag "^4.0.0"
1237 |
1238 | to-readable-stream@^1.0.0:
1239 | version "1.0.0"
1240 | resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771"
1241 | integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==
1242 |
1243 | to-regex-range@^5.0.1:
1244 | version "5.0.1"
1245 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
1246 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
1247 | dependencies:
1248 | is-number "^7.0.0"
1249 |
1250 | toidentifier@1.0.1:
1251 | version "1.0.1"
1252 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
1253 | integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
1254 |
1255 | touch@^3.1.0:
1256 | version "3.1.0"
1257 | resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b"
1258 | integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==
1259 | dependencies:
1260 | nopt "~1.0.10"
1261 |
1262 | tr46@^3.0.0:
1263 | version "3.0.0"
1264 | resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9"
1265 | integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==
1266 | dependencies:
1267 | punycode "^2.1.1"
1268 |
1269 | type-fest@^0.20.2:
1270 | version "0.20.2"
1271 | resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
1272 | integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
1273 |
1274 | type-is@~1.6.18:
1275 | version "1.6.18"
1276 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
1277 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
1278 | dependencies:
1279 | media-typer "0.3.0"
1280 | mime-types "~2.1.24"
1281 |
1282 | typedarray-to-buffer@^3.1.5:
1283 | version "3.1.5"
1284 | resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
1285 | integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
1286 | dependencies:
1287 | is-typedarray "^1.0.0"
1288 |
1289 | undefsafe@^2.0.5:
1290 | version "2.0.5"
1291 | resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
1292 | integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
1293 |
1294 | unique-string@^2.0.0:
1295 | version "2.0.0"
1296 | resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d"
1297 | integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==
1298 | dependencies:
1299 | crypto-random-string "^2.0.0"
1300 |
1301 | unpipe@1.0.0, unpipe@~1.0.0:
1302 | version "1.0.0"
1303 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
1304 | integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
1305 |
1306 | update-notifier@^5.1.0:
1307 | version "5.1.0"
1308 | resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9"
1309 | integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==
1310 | dependencies:
1311 | boxen "^5.0.0"
1312 | chalk "^4.1.0"
1313 | configstore "^5.0.1"
1314 | has-yarn "^2.1.0"
1315 | import-lazy "^2.1.0"
1316 | is-ci "^2.0.0"
1317 | is-installed-globally "^0.4.0"
1318 | is-npm "^5.0.0"
1319 | is-yarn-global "^0.3.0"
1320 | latest-version "^5.1.0"
1321 | pupa "^2.1.1"
1322 | semver "^7.3.4"
1323 | semver-diff "^3.1.1"
1324 | xdg-basedir "^4.0.0"
1325 |
1326 | url-parse-lax@^3.0.0:
1327 | version "3.0.0"
1328 | resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
1329 | integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=
1330 | dependencies:
1331 | prepend-http "^2.0.0"
1332 |
1333 | utils-merge@1.0.1:
1334 | version "1.0.1"
1335 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
1336 | integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
1337 |
1338 | vary@^1, vary@~1.1.2:
1339 | version "1.1.2"
1340 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
1341 | integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
1342 |
1343 | webidl-conversions@^7.0.0:
1344 | version "7.0.0"
1345 | resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
1346 | integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
1347 |
1348 | whatwg-url@^11.0.0:
1349 | version "11.0.0"
1350 | resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018"
1351 | integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==
1352 | dependencies:
1353 | tr46 "^3.0.0"
1354 | webidl-conversions "^7.0.0"
1355 |
1356 | widest-line@^3.1.0:
1357 | version "3.1.0"
1358 | resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca"
1359 | integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==
1360 | dependencies:
1361 | string-width "^4.0.0"
1362 |
1363 | wrap-ansi@^7.0.0:
1364 | version "7.0.0"
1365 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
1366 | integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
1367 | dependencies:
1368 | ansi-styles "^4.0.0"
1369 | string-width "^4.1.0"
1370 | strip-ansi "^6.0.0"
1371 |
1372 | wrappy@1:
1373 | version "1.0.2"
1374 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
1375 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
1376 |
1377 | write-file-atomic@^3.0.0:
1378 | version "3.0.3"
1379 | resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8"
1380 | integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==
1381 | dependencies:
1382 | imurmurhash "^0.1.4"
1383 | is-typedarray "^1.0.0"
1384 | signal-exit "^3.0.2"
1385 | typedarray-to-buffer "^3.1.5"
1386 |
1387 | xdg-basedir@^4.0.0:
1388 | version "4.0.0"
1389 | resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
1390 | integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
1391 |
1392 | yallist@^4.0.0:
1393 | version "4.0.0"
1394 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
1395 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
1396 |
--------------------------------------------------------------------------------