mirror of
https://github.com/rjNemo/devbook_ts
synced 2026-06-06 02:36:39 +00:00
revive ci; merge master
This commit is contained in:
commit
27a0c5addf
65 changed files with 3846 additions and 154 deletions
22
.github/workflows/deploy.yml
vendored
22
.github/workflows/deploy.yml
vendored
|
|
@ -5,6 +5,15 @@ jobs:
|
|||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
REACT_APP_STORAGE_BUCKET: ${{ secrets.REACT_APP_STORAGE_BUCKET }}
|
||||
REACT_APP_PROJECT_ID: ${{ secrets.REACT_APP_PROJECT_ID }}
|
||||
REACT_APP_MSG_SENDER_ID: ${{ secrets.REACT_APP_MSG_SENDER_ID }}
|
||||
REACT_APP_MEASUREMENT_ID: ${{ secrets.REACT_APP_MEASUREMENT_ID }}
|
||||
REACT_APP_DB_URL: ${{ secrets.REACT_APP_DB_URL }}
|
||||
REACT_APP_AUTH_DOMAIN: ${{ secrets.REACT_APP_AUTH_DOMAIN }}
|
||||
REACT_APP_APP_ID: ${{ secrets.REACT_APP_APP_ID }}
|
||||
REACT_APP_API_KEY: ${{ secrets.REACT_APP_API_KEY }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install dependencies
|
||||
|
|
@ -16,3 +25,16 @@ jobs:
|
|||
with:
|
||||
start: yarn start
|
||||
wait-on: 'http://localhost:3000'
|
||||
release:
|
||||
if: ${{ github.ref == 'master' }}
|
||||
needs: test
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build
|
||||
run: yarn build
|
||||
- name: Install Firebase CLI tools
|
||||
run: yarn global add firebase-tools
|
||||
- name: Deploy
|
||||
run: firebase deploy --token ${{ secrets.FIREBASE_TOKEN }}
|
||||
|
|
|
|||
9
.gitignore
vendored
9
.gitignore
vendored
|
|
@ -8,6 +8,8 @@
|
|||
# testing
|
||||
/coverage
|
||||
/cypress/integration/examples
|
||||
/cypress/fixtures/
|
||||
/cypress/screenshots/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
|
@ -24,3 +26,10 @@
|
|||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
.env
|
||||
.firebase
|
||||
firebase.json
|
||||
firestore.indexes.json
|
||||
*rules
|
||||
.firebaserc
|
||||
21
README.md
21
README.md
|
|
@ -1,7 +1,11 @@
|
|||
# DevBook
|
||||
|
||||

|
||||
|
||||
Social App for connecting with developers & tech enthusiasts.
|
||||
|
||||
🕸 [Check it out!](https://devbook.onrender.com/)
|
||||
|
||||
## Tests
|
||||
|
||||
### End-to-end
|
||||
|
|
@ -16,11 +20,26 @@ yarn cypress
|
|||
|
||||
and edit your E2E test cases in `cypress/integration/` folder.
|
||||
|
||||
### Unit
|
||||
|
||||
Open test runner with:
|
||||
|
||||
```sh
|
||||
yarn test
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
We use Github Actions to check push and pull requests.
|
||||
|
||||
The application is deployed on [Render](https://render.com) cloud platform.
|
||||
It watches git `master` branch and automatic deploy if the diff passes the tests.
|
||||
|
||||
## Built With
|
||||
|
||||
- [ReactJs](https://reactjs.org/) - A JavaScript library for building user interfaces
|
||||
- [Redux](https://redux.js.org/) - A predictable statea container for JavaScript apps
|
||||
- [Firebase](https://fiirebase.google.com/) - Firebase helps mobile and web app teams succeed
|
||||
- [Firebase](https://firebase.google.com/) - Firebase helps mobile and web app teams succeed
|
||||
|
||||
## Versioning
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
6
cypress/integration/landing.spec.js
Normal file
6
cypress/integration/landing.spec.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
describe('Landing page', () => {
|
||||
it('contains app name', () => {
|
||||
cy.visit('/');
|
||||
cy.get('h1').contains('DevBook');
|
||||
});
|
||||
});
|
||||
6
cypress/integration/layout.spec.js
Normal file
6
cypress/integration/layout.spec.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
describe('App Layout', () => {
|
||||
it('contains a navbar', () => {
|
||||
cy.visit('/');
|
||||
cy.get('nav');
|
||||
});
|
||||
});
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
describe('smoke', () => {
|
||||
it('sees learn', () => {
|
||||
cy.visit('/');
|
||||
cy.get('a').contains('Learn');
|
||||
});
|
||||
});
|
||||
117
firestore.indexes.json
Normal file
117
firestore.indexes.json
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
{
|
||||
"indexes": [
|
||||
{
|
||||
"collectionGroup": "restaurants",
|
||||
"queryScope": "COLLECTION",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"order": "ASCENDING"
|
||||
},
|
||||
{
|
||||
"fieldPath": "avgRating",
|
||||
"order": "DESCENDING"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"collectionGroup": "restaurants",
|
||||
"queryScope": "COLLECTION",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"order": "ASCENDING"
|
||||
},
|
||||
{
|
||||
"fieldPath": "numRatings",
|
||||
"order": "DESCENDING"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"collectionGroup": "restaurants",
|
||||
"queryScope": "COLLECTION",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"order": "ASCENDING"
|
||||
},
|
||||
{
|
||||
"fieldPath": "price",
|
||||
"order": "ASCENDING"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"collectionGroup": "restaurants",
|
||||
"queryScope": "COLLECTION",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "city",
|
||||
"order": "ASCENDING"
|
||||
},
|
||||
{
|
||||
"fieldPath": "avgRating",
|
||||
"order": "DESCENDING"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"collectionGroup": "restaurants",
|
||||
"queryScope": "COLLECTION",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "city",
|
||||
"order": "ASCENDING"
|
||||
},
|
||||
{
|
||||
"fieldPath": "numRatings",
|
||||
"order": "DESCENDING"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"collectionGroup": "restaurants",
|
||||
"queryScope": "COLLECTION",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "city",
|
||||
"order": "ASCENDING"
|
||||
},
|
||||
{
|
||||
"fieldPath": "price",
|
||||
"order": "ASCENDING"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"collectionGroup": "restaurants",
|
||||
"queryScope": "COLLECTION",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "price",
|
||||
"order": "ASCENDING"
|
||||
},
|
||||
{
|
||||
"fieldPath": "avgRating",
|
||||
"order": "DESCENDING"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"collectionGroup": "restaurants",
|
||||
"queryScope": "COLLECTION",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "price",
|
||||
"order": "ASCENDING"
|
||||
},
|
||||
{
|
||||
"fieldPath": "numRatings",
|
||||
"order": "DESCENDING"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"fieldOverrides": []
|
||||
}
|
||||
20
package.json
20
package.json
|
|
@ -3,6 +3,13 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.13.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.28",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.13.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.13.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.13.0",
|
||||
"@fortawesome/react-fontawesome": "^0.1.9",
|
||||
"@reduxjs/toolkit": "^1.3.6",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
|
|
@ -10,11 +17,20 @@
|
|||
"@types/node": "^12.0.0",
|
||||
"@types/react": "^16.9.0",
|
||||
"@types/react-dom": "^16.9.0",
|
||||
"@types/react-redux": "^7.1.8",
|
||||
"@types/react-router-dom": "^5.1.5",
|
||||
"cypress": "^4.5.0",
|
||||
"firebase": "^7.14.3",
|
||||
"moment": "^2.25.3",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-google-button": "^0.7.1",
|
||||
"react-redux": "^7.2.0",
|
||||
"react-redux-firebase": "^3.4.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "3.4.1",
|
||||
"typescript": "~3.7.2"
|
||||
"redux-firestore": "^0.13.0",
|
||||
"typescript": "^3.9.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
|
@ -38,4 +54,4 @@
|
|||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,40 +4,18 @@
|
|||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="theme-color" content="#17a2b8" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
content="Create developer profiles, portfolio, share and get help from other
|
||||
devs"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
<title>DevBook</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"short_name": "DevBook",
|
||||
"name": "DevBook | Social App for devs",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
|
|
@ -20,6 +20,6 @@
|
|||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
"theme_color": "#17a2b8",
|
||||
"background_color": "#000000"
|
||||
}
|
||||
38
src/App.css
38
src/App.css
|
|
@ -1,38 +0,0 @@
|
|||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,5 @@
|
|||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import {render} from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
const { getByText } = render(<App />);
|
||||
const linkElement = getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
test('to pass ci', () => {});
|
||||
|
|
|
|||
64
src/App.tsx
64
src/App.tsx
|
|
@ -1,26 +1,48 @@
|
|||
import React from 'react';
|
||||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
import React, {FC} from 'react';
|
||||
// Routing
|
||||
import {BrowserRouter} from 'react-router-dom';
|
||||
import Router from './router/Router';
|
||||
import NavBar from './components/NavBar';
|
||||
// Redux
|
||||
import {Provider, useSelector} from 'react-redux';
|
||||
import store from './store';
|
||||
// Firebase
|
||||
import {ReactReduxFirebaseProvider, isLoaded} from 'react-redux-firebase';
|
||||
import rrfProps from './store/firebase/config';
|
||||
import {selectAuthState} from './store/firebase';
|
||||
|
||||
function App() {
|
||||
/**
|
||||
* Main App container
|
||||
* Redux provides state management
|
||||
* RRF to bind to Firebase
|
||||
* */
|
||||
const App: FC = () => {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
<Provider store={store}>
|
||||
<ReactReduxFirebaseProvider {...rrfProps}>
|
||||
<BrowserRouter>
|
||||
<AuthApp />
|
||||
</BrowserRouter>
|
||||
</ReactReduxFirebaseProvider>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Display a loading screen while fetching authentication state
|
||||
*/
|
||||
const AuthApp: FC = () => {
|
||||
const auth = useSelector(selectAuthState);
|
||||
if (!isLoaded(auth)) {
|
||||
// TODO: insert Splash Screen here
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<NavBar />
|
||||
<Router />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
|
|
|||
16
src/components/Alert.tsx
Normal file
16
src/components/Alert.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import React, {FC} from 'react';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
interface IProps {
|
||||
text: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
const Alert: FC<IProps> = ({text, color = 'danger'}) => (
|
||||
<div className={`alert alert-${color}`}>
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} /> {text}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Alert;
|
||||
38
src/components/DevProfile.tsx
Normal file
38
src/components/DevProfile.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import React, {FC} from 'react';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faCheck} from '@fortawesome/free-solid-svg-icons';
|
||||
import {DevSummary} from '../models/Dev';
|
||||
|
||||
/**
|
||||
* Present a dev profile succintly. Redirect to dev profile on click.
|
||||
* @param props DevSummary object
|
||||
*/
|
||||
const DevProfile: FC<DevSummary> = ({
|
||||
id,
|
||||
displayName,
|
||||
picture,
|
||||
description,
|
||||
location,
|
||||
skills,
|
||||
}) => (
|
||||
<div className="profile bg-light">
|
||||
<img src={picture} alt={displayName} className="round-img" />
|
||||
<div>
|
||||
<h2>{displayName}</h2>
|
||||
<p>{description}</p>
|
||||
<p>{location}</p>
|
||||
<a href="profile.html" className="btn btn-primary">
|
||||
View Profile
|
||||
</a>
|
||||
</div>
|
||||
<ul>
|
||||
{skills.map((s, i) => (
|
||||
<li className="text-primary" key={i}>
|
||||
<FontAwesomeIcon icon={faCheck} /> {s}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default DevProfile;
|
||||
18
src/components/FormHeader.tsx
Normal file
18
src/components/FormHeader.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import React, {FC} from 'react';
|
||||
import Header from './Header';
|
||||
|
||||
interface IProps {
|
||||
title: string;
|
||||
lead: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
/** Header component displayed on form pages */
|
||||
const FormHeader: FC<IProps> = props => (
|
||||
<>
|
||||
<Header {...props} />
|
||||
<small>* marks required fields</small>
|
||||
</>
|
||||
);
|
||||
|
||||
export default FormHeader;
|
||||
55
src/components/Header.tsx
Normal file
55
src/components/Header.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import React, {FC} from 'react';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {
|
||||
faUser,
|
||||
faCodeBranch,
|
||||
faGraduationCap,
|
||||
faExclamationTriangle,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import {faConnectdevelop} from '@fortawesome/free-brands-svg-icons';
|
||||
|
||||
interface IProps {
|
||||
title: string;
|
||||
lead: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Header component
|
||||
* @param title of the page
|
||||
* @param lead description of the content
|
||||
* @param icon to display (optional and default to faUser)
|
||||
*/
|
||||
const Header: FC<IProps> = ({title, lead, icon = 'faUser'}) => {
|
||||
const RenderIcon = (icon: string) => {
|
||||
if (icon === 'faUser') {
|
||||
return <FontAwesomeIcon icon={faUser} />;
|
||||
}
|
||||
if (icon === 'connectdevelop') {
|
||||
return <FontAwesomeIcon icon={faConnectdevelop} />;
|
||||
}
|
||||
if (icon === 'code-branch') {
|
||||
return <FontAwesomeIcon icon={faCodeBranch} />;
|
||||
}
|
||||
if (icon === 'code') {
|
||||
return null;
|
||||
}
|
||||
if (icon === 'graduation-cap') {
|
||||
return <FontAwesomeIcon icon={faGraduationCap} />;
|
||||
}
|
||||
if (icon === 'not-found') {
|
||||
return <FontAwesomeIcon icon={faExclamationTriangle} />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1 className="large text-primary">{title}</h1>
|
||||
<p className="lead">
|
||||
{RenderIcon(icon)} {lead}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
92
src/components/NavBar.tsx
Normal file
92
src/components/NavBar.tsx
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import React, {FC} from 'react';
|
||||
// Routing
|
||||
import {Link} from 'react-router-dom';
|
||||
import Routes from '../constants/routes';
|
||||
//Redux
|
||||
import {WithFirebaseProps} from 'react-redux-firebase';
|
||||
import {enhance} from '../store/firebase';
|
||||
// Style
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faCode, faSignOutAlt, faUser} from '@fortawesome/free-solid-svg-icons';
|
||||
// Typing
|
||||
import User from '../models/User';
|
||||
import Dev from '../models/Dev';
|
||||
|
||||
interface IProps extends Dev, WithFirebaseProps<User> {
|
||||
isEmpty: boolean;
|
||||
isLoaded: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main Navbar serves navigation Routes.
|
||||
*/
|
||||
const NavBar: FC<IProps> = ({firebase, isEmpty, isLoaded, isActive}) => {
|
||||
const publicLinks = (
|
||||
<ul data-testid="publicLinks">
|
||||
<li>
|
||||
<Link to={Routes.DEVELOPERS} data-testid="devsLink">
|
||||
Developers
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to={Routes.SIGN_UP} data-testid="signupLink">
|
||||
Register
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to={Routes.SIGN_IN} data-testid="loginLink">
|
||||
Login
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
|
||||
const privateLinks = (
|
||||
<ul data-testid="privateLinks">
|
||||
<li>
|
||||
<Link to={Routes.DEVELOPERS} data-testid="devsLink">
|
||||
Developers
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to={Routes.POSTS} data-testid="postsLink">
|
||||
Posts
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to={Routes.DASHBOARD} data-testid="dashboardLink">
|
||||
<FontAwesomeIcon icon={faUser} />
|
||||
<span className="hide-sm"> Dashboard</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
to={Routes.SIGN_IN}
|
||||
data-testid="logoutLink"
|
||||
onClick={() => firebase.logout()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faSignOutAlt} />
|
||||
<span className="hide-sm"> Log out</span>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
|
||||
/** Display appropriated links after loading given authenticated prop */
|
||||
const RenderLinks =
|
||||
isLoaded && !isEmpty && isActive ? privateLinks : publicLinks;
|
||||
|
||||
return (
|
||||
<nav className="navbar bg-dark">
|
||||
<h1>
|
||||
<Link to={Routes.LANDING} data-testid="homeLink">
|
||||
<FontAwesomeIcon icon={faCode} /> DevBook
|
||||
</Link>
|
||||
</h1>
|
||||
{RenderLinks}
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
/** connect HOC subscribes to the store */
|
||||
export default enhance(NavBar);
|
||||
19
src/constants/routes.ts
Normal file
19
src/constants/routes.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Register all Routes here for easy future modification.
|
||||
* Paths must start with '/'
|
||||
*/
|
||||
enum Routes {
|
||||
LANDING = '/',
|
||||
SIGN_UP = '/signup',
|
||||
SIGN_IN = '/signin',
|
||||
DEVELOPERS = '/developers',
|
||||
PROFILE = '/profile',
|
||||
EDIT_PROFILE = '/edit-profile',
|
||||
DASHBOARD = '/dashboard',
|
||||
ADD_EXPERIENCE = '/add-experience',
|
||||
ADD_EDUCATION = '/add-education',
|
||||
POST = '/post',
|
||||
POSTS = '/posts',
|
||||
}
|
||||
|
||||
export default Routes;
|
||||
12
src/constants/statuses.ts
Normal file
12
src/constants/statuses.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
const Statuses: string[] = [
|
||||
'Developer',
|
||||
'Junior Developer',
|
||||
'Senior Developer',
|
||||
'Manager',
|
||||
'Student or Learning',
|
||||
'Instructor or Teacher',
|
||||
'Intern',
|
||||
'Other',
|
||||
];
|
||||
|
||||
export default Statuses;
|
||||
34
src/hooks/index.ts
Normal file
34
src/hooks/index.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import {useState, ChangeEvent} from 'react';
|
||||
|
||||
/**
|
||||
* provide onChange handler and reset function
|
||||
* T is the initFormData object type
|
||||
*
|
||||
* @param initFormData initial state of the form
|
||||
* @returns formData object,
|
||||
* @returns handleChange function to pass to input tag
|
||||
* @returns resetForm function to revert to initFormData
|
||||
* */
|
||||
const useForm = <T>(initFormData: T) => {
|
||||
const [formData, setFormData] = useState<T>(initFormData);
|
||||
|
||||
/** update each input state value onChange */
|
||||
const handleChange = (
|
||||
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,
|
||||
): void =>
|
||||
setFormData({
|
||||
...formData,
|
||||
[e.target.name]: e.target.value,
|
||||
});
|
||||
|
||||
/** clean form after successful submition */
|
||||
const resetForm = () => setFormData(initFormData);
|
||||
|
||||
// /** update checkboxes TODO: do it better ...*/
|
||||
const handleCheckboxesChange = (e: ChangeEvent<HTMLInputElement>): void =>
|
||||
setFormData({...formData, [e.target.name]: e.target.checked});
|
||||
|
||||
return {formData, handleChange, handleCheckboxesChange, resetForm};
|
||||
};
|
||||
|
||||
export default useForm;
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import './static/css/style.min.css';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
|
||||
|
|
@ -8,10 +8,7 @@ ReactDOM.render(
|
|||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
document.getElementById('root'),
|
||||
);
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||
serviceWorker.unregister();
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
||||
<g fill="#61DAFB">
|
||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
||||
<path d="M520.5 78.1z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
149
src/models/Dev.ts
Normal file
149
src/models/Dev.ts
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
import Education from '../types/Education';
|
||||
import Experience from '../types/Experience';
|
||||
import Links from '../types/Links';
|
||||
import Repo from '../types/Repo';
|
||||
|
||||
/** Shorter dev interface */
|
||||
export interface DevSummary {
|
||||
id: string;
|
||||
displayName: string;
|
||||
picture: string;
|
||||
description: string;
|
||||
location: string;
|
||||
skills: string[];
|
||||
}
|
||||
|
||||
/** Full developer profile information.
|
||||
* @extends DevSummary to avoid duplication
|
||||
*/
|
||||
interface Dev extends DevSummary {
|
||||
isActive: boolean;
|
||||
bio: string;
|
||||
status: string;
|
||||
company: string;
|
||||
links: Links;
|
||||
experiences: Experience[];
|
||||
educations: Education[];
|
||||
repos: Repo[];
|
||||
}
|
||||
|
||||
/** create profile tagline */
|
||||
export const getDescription = (status: string, company: string) =>
|
||||
`${status} at ${company}`;
|
||||
|
||||
/** blank Dev serve as placeholder when initializing a new profile */
|
||||
export const blankDev: Dev = {
|
||||
id: '42',
|
||||
isActive: true,
|
||||
displayName: '',
|
||||
status: 'Developer',
|
||||
company: '',
|
||||
picture: '',
|
||||
description: '',
|
||||
location: '',
|
||||
skills: [],
|
||||
links: {
|
||||
website: '',
|
||||
instagram: '',
|
||||
facebook: '',
|
||||
linkedin: '',
|
||||
twitter: '',
|
||||
github: '',
|
||||
youtube: '',
|
||||
},
|
||||
bio: '',
|
||||
experiences: [],
|
||||
educations: [],
|
||||
repos: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* sample Dev for development and tests
|
||||
*/
|
||||
export const dummyDev: Dev = {
|
||||
id: '0',
|
||||
isActive: true,
|
||||
displayName: 'John Doe',
|
||||
status: 'Developer',
|
||||
company: 'Microsoft',
|
||||
picture:
|
||||
'https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?s=200',
|
||||
description: 'Developer at Microsoft',
|
||||
location: 'Seattle, WA',
|
||||
skills: ['HTML', 'CSS', 'JavaScript', 'Python'],
|
||||
links: {
|
||||
website: '#',
|
||||
instagram: 'http://insta.com',
|
||||
facebook: '#',
|
||||
linkedin: '#',
|
||||
twitter: '#',
|
||||
github: '#',
|
||||
youtube: '#',
|
||||
},
|
||||
bio:
|
||||
'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Blanditiis unde quae vero enim adipisci voluptas magni sapiente reprehenderit error minima.',
|
||||
experiences: [
|
||||
{
|
||||
id: 1,
|
||||
company: 'Microsoft',
|
||||
from: new Date(2011, 10),
|
||||
to: 'Current',
|
||||
position: 'Senior Developer',
|
||||
location: 'USA',
|
||||
description:
|
||||
'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Voluptas corrupti rem eius, accusantium ipsum vel eveniet magnam voluptatum? Minus, voluptatum!',
|
||||
},
|
||||
{
|
||||
id: 0,
|
||||
company: 'Sun Microsystems',
|
||||
location: 'USA',
|
||||
from: new Date(2004, 10),
|
||||
to: new Date(2010, 11),
|
||||
position: 'System Admin',
|
||||
description:
|
||||
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Repellendus at rem totam sed qui! Quas.',
|
||||
},
|
||||
],
|
||||
educations: [
|
||||
{
|
||||
id: 0,
|
||||
school: 'University of Washington',
|
||||
from: new Date(1993, 9),
|
||||
to: new Date(1999, 6),
|
||||
degree: 'Master',
|
||||
field: 'Computer Science',
|
||||
description:
|
||||
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Repellendus at rem totam sed qui! Quas.',
|
||||
},
|
||||
],
|
||||
repos: [
|
||||
{
|
||||
name: 'Repo #1',
|
||||
description:
|
||||
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Fugit,deserunt.',
|
||||
link: '#',
|
||||
stars: 42,
|
||||
watchers: 2,
|
||||
forks: 4,
|
||||
},
|
||||
{
|
||||
name: 'Repo #2',
|
||||
description:
|
||||
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Fugit,deserunt.',
|
||||
link: '#',
|
||||
stars: 21,
|
||||
watchers: 1,
|
||||
forks: 2,
|
||||
},
|
||||
{
|
||||
name: 'Repo #3',
|
||||
description:
|
||||
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Fugit,deserunt.',
|
||||
link: '#',
|
||||
stars: 50,
|
||||
watchers: 32,
|
||||
forks: 12,
|
||||
},
|
||||
],
|
||||
};
|
||||
export default Dev;
|
||||
47
src/models/Post.ts
Normal file
47
src/models/Post.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import Comment from '../types/Comment';
|
||||
|
||||
/**
|
||||
* Post send by a dev
|
||||
*/
|
||||
interface Post {
|
||||
id: string;
|
||||
userID: string;
|
||||
name: string;
|
||||
text: string;
|
||||
picture: string;
|
||||
likes: string[];
|
||||
comments: Comment[];
|
||||
// date: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* sample Post for development and tests
|
||||
*/
|
||||
export const dummyPost: Post = {
|
||||
id: '12',
|
||||
userID: '42',
|
||||
picture:
|
||||
'https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?s=200',
|
||||
name: 'John Doe',
|
||||
text:
|
||||
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Sint possimus corporis sunt necessitatibus! Minus nesciunt soluta suscipit nobis. Amet accusamus distinctio cupiditate blanditiis dolor? Illo perferendis eveniet cum cupiditate aliquam?',
|
||||
comments: [
|
||||
{
|
||||
picture:
|
||||
'https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?s=200',
|
||||
name: 'John Doe',
|
||||
text:
|
||||
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Sintpossimus corporis sunt necessitatibus! Minus nesciunt solutasuscipit nobis. Amet accusamus distinctio cupiditate blanditiis dolor? Illo perferendis eveniet cum cupiditate aliquam?',
|
||||
},
|
||||
{
|
||||
picture:
|
||||
'https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?s=200',
|
||||
name: 'Ruidy Nemo',
|
||||
text:
|
||||
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Sintpossimus corporis sunt necessitatibus! Minus nesciunt solutasuscipit nobis. Amet accusamus distinctio cupiditate blanditiis dolor? Illo perferendis eveniet cum cupiditate aliquam?',
|
||||
},
|
||||
],
|
||||
likes: ['0', '42'],
|
||||
};
|
||||
|
||||
export default Post;
|
||||
20
src/models/User.ts
Normal file
20
src/models/User.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
interface User {
|
||||
displayName: string;
|
||||
email: string;
|
||||
avatarUrl: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
/** User constructor */
|
||||
export const newUser = (
|
||||
displayName: string,
|
||||
email: string,
|
||||
avatarUrl: string = '',
|
||||
): User => ({
|
||||
displayName,
|
||||
email,
|
||||
avatarUrl,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
|
||||
export default User;
|
||||
188
src/pages/AddEducation.tsx
Normal file
188
src/pages/AddEducation.tsx
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
import React, {FC, useState, FormEvent} from 'react';
|
||||
// Routing
|
||||
import {Link} from 'react-router-dom';
|
||||
import Routes from '../constants/routes';
|
||||
// Redux
|
||||
import {WithFirebaseProps} from 'react-redux-firebase';
|
||||
import {enhance} from '../store/firebase';
|
||||
// Style
|
||||
import FormHeader from '../components/FormHeader';
|
||||
import Alert from '../components/Alert';
|
||||
// Typing
|
||||
import Dev from '../models/Dev';
|
||||
import User from '../models/User';
|
||||
import IAlert, {formAlert} from '../types/Alert';
|
||||
import Education from '../types/Education';
|
||||
import {parseDate} from '../types/TimePeriod';
|
||||
// Form
|
||||
import useForm from '../hooks';
|
||||
|
||||
interface FormData {
|
||||
school: string;
|
||||
degree: string;
|
||||
field: string;
|
||||
from: string;
|
||||
to: string;
|
||||
current: boolean;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface IProps extends Dev, WithFirebaseProps<User> {}
|
||||
/**
|
||||
* Form to add an Education step to Profile
|
||||
*/
|
||||
const AddEducation: FC<IProps> = ({firebase, educations}) => {
|
||||
const [alert, setAlert] = useState<IAlert>(formAlert);
|
||||
|
||||
const initFormData: FormData = {
|
||||
school: '',
|
||||
degree: '',
|
||||
field: '',
|
||||
from: '',
|
||||
to: '',
|
||||
current: false,
|
||||
description: '',
|
||||
};
|
||||
const {formData, handleChange, handleCheckboxesChange, resetForm} = useForm<
|
||||
FormData
|
||||
>(initFormData);
|
||||
|
||||
const isDisabled: boolean = formData.school === '' || formData.degree === '';
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>): void => {
|
||||
e.preventDefault();
|
||||
const makeEducation = ({
|
||||
school,
|
||||
degree,
|
||||
from,
|
||||
field,
|
||||
to,
|
||||
current,
|
||||
description,
|
||||
}: FormData): Education => {
|
||||
if (current) to = 'Current';
|
||||
const newEdu: Education = {
|
||||
id: educations.length,
|
||||
school,
|
||||
degree,
|
||||
field,
|
||||
from: parseDate(from),
|
||||
to: parseDate(to),
|
||||
description,
|
||||
};
|
||||
return newEdu;
|
||||
};
|
||||
const newEdu = makeEducation(formData);
|
||||
|
||||
try {
|
||||
firebase.updateProfile(
|
||||
{educations: [...educations, newEdu]},
|
||||
{useSet: true, merge: true},
|
||||
);
|
||||
setAlert({
|
||||
show: true,
|
||||
color: 'success',
|
||||
text:
|
||||
'Profile successfully updated. You may continue or go back to your dashboard.',
|
||||
});
|
||||
resetForm();
|
||||
} catch (err) {
|
||||
setAlert({...alert, show: true});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="container">
|
||||
<FormHeader
|
||||
title="Add Your Education"
|
||||
lead="Add any school, bootcamp, etc that
|
||||
you have attended"
|
||||
icon="graduation-cap"
|
||||
/>
|
||||
|
||||
<form className="form" onSubmit={handleSubmit}>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="* School or Bootcamp"
|
||||
name="school"
|
||||
value={formData.school}
|
||||
onChange={handleChange}
|
||||
required
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="* Degree or Certificate"
|
||||
name="degree"
|
||||
value={formData.degree}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Field Of Study"
|
||||
name="field"
|
||||
value={formData.field}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<h4>From Date</h4>
|
||||
<input
|
||||
type="date"
|
||||
name="from"
|
||||
value={formData.from}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<h4>To Date</h4>
|
||||
<input
|
||||
type="date"
|
||||
name="to"
|
||||
value={formData.to}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<p>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="current"
|
||||
checked={formData.current}
|
||||
onChange={handleCheckboxesChange}
|
||||
/>{' '}
|
||||
Current School
|
||||
</p>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<textarea
|
||||
name="description"
|
||||
cols={30}
|
||||
rows={5}
|
||||
placeholder="Program Description"
|
||||
value={formData.description}
|
||||
onChange={handleChange}
|
||||
></textarea>
|
||||
</div>
|
||||
{alert.show && <Alert text={alert.text} color={alert.color} />}
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-primary my-1"
|
||||
value="Submit"
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
<Link className="btn btn-light my-1" to={Routes.DASHBOARD}>
|
||||
Go Back
|
||||
</Link>
|
||||
</form>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default enhance(AddEducation);
|
||||
189
src/pages/AddExperience.tsx
Normal file
189
src/pages/AddExperience.tsx
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
import React, {FC, useState, FormEvent} from 'react';
|
||||
// Routing
|
||||
import {Link} from 'react-router-dom';
|
||||
import Routes from '../constants/routes';
|
||||
// Redux
|
||||
import {WithFirebaseProps} from 'react-redux-firebase';
|
||||
import {enhance} from '../store/firebase';
|
||||
// Style
|
||||
import FormHeader from '../components/FormHeader';
|
||||
import Alert from '../components/Alert';
|
||||
// Typing
|
||||
import Dev from '../models/Dev';
|
||||
import User from '../models/User';
|
||||
import IAlert, {formAlert} from '../types/Alert';
|
||||
import Experience from '../types/Experience';
|
||||
import {parseDate} from '../types/TimePeriod';
|
||||
// Form
|
||||
import useForm from '../hooks';
|
||||
|
||||
interface FormData {
|
||||
position: string;
|
||||
company: string;
|
||||
location: string;
|
||||
from: string;
|
||||
to: string;
|
||||
current: boolean;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface IProps extends Dev, WithFirebaseProps<User> {}
|
||||
|
||||
/**
|
||||
* Form to add an Experience step to Profile
|
||||
*/
|
||||
const AddExperience: FC<IProps> = ({firebase, experiences}) => {
|
||||
const [alert, setAlert] = useState<IAlert>(formAlert);
|
||||
|
||||
const initFormData: FormData = {
|
||||
position: '',
|
||||
company: '',
|
||||
location: '',
|
||||
from: '',
|
||||
to: '',
|
||||
current: false,
|
||||
description: '',
|
||||
};
|
||||
const {formData, handleChange, handleCheckboxesChange, resetForm} = useForm<
|
||||
FormData
|
||||
>(initFormData);
|
||||
|
||||
const isDisabled: boolean =
|
||||
formData.position === '' || formData.company === '';
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>): void => {
|
||||
e.preventDefault();
|
||||
const makeExperience = ({
|
||||
position,
|
||||
company,
|
||||
from,
|
||||
location,
|
||||
to,
|
||||
current,
|
||||
description,
|
||||
}: FormData): Experience => {
|
||||
if (current) to = 'Current';
|
||||
const newExp: Experience = {
|
||||
id: experiences.length,
|
||||
position,
|
||||
company,
|
||||
location,
|
||||
from: parseDate(from),
|
||||
to: parseDate(to),
|
||||
description,
|
||||
};
|
||||
return newExp;
|
||||
};
|
||||
const newExp = makeExperience(formData);
|
||||
|
||||
try {
|
||||
firebase.updateProfile(
|
||||
{experiences: [...experiences, newExp]},
|
||||
{useSet: true, merge: true},
|
||||
);
|
||||
setAlert({
|
||||
show: true,
|
||||
color: 'success',
|
||||
text:
|
||||
'Profile successfully updated. You may continue or go back to your dashboard.',
|
||||
});
|
||||
resetForm();
|
||||
} catch (err) {
|
||||
setAlert({...alert, show: true});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="container">
|
||||
<FormHeader
|
||||
title="Add An Experience"
|
||||
lead="Add any developer/programming
|
||||
positions that you have had in the past"
|
||||
icon="code-branch"
|
||||
/>
|
||||
|
||||
<form className="form" onSubmit={handleSubmit}>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="* Job Title"
|
||||
name="position"
|
||||
required
|
||||
value={formData.position}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="* Company"
|
||||
name="company"
|
||||
required
|
||||
value={formData.company}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Location"
|
||||
name="location"
|
||||
value={formData.location}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<h4>From Date</h4>
|
||||
<input
|
||||
type="date"
|
||||
name="from"
|
||||
value={formData.from}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<h4>To Date</h4>
|
||||
<input
|
||||
type="date"
|
||||
name="to"
|
||||
value={formData.to}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<p>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="current"
|
||||
checked={formData.current}
|
||||
onChange={handleCheckboxesChange}
|
||||
/>{' '}
|
||||
Current Job
|
||||
</p>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<textarea
|
||||
name="description"
|
||||
cols={30}
|
||||
rows={5}
|
||||
placeholder="Job Description"
|
||||
value={formData.description}
|
||||
onChange={handleChange}
|
||||
></textarea>
|
||||
</div>
|
||||
{alert.show && <Alert text={alert.text} color={alert.color} />}
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-primary my-1"
|
||||
value="Submit"
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
<Link className="btn btn-light my-1" to={Routes.DASHBOARD}>
|
||||
Go Back
|
||||
</Link>
|
||||
</form>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default enhance(AddExperience);
|
||||
146
src/pages/Dashboard.tsx
Normal file
146
src/pages/Dashboard.tsx
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
import React, {FC, MouseEvent} from 'react';
|
||||
// Redux
|
||||
import {WithFirebaseProps} from 'react-redux-firebase';
|
||||
import {enhance} from '../store/firebase';
|
||||
// Routing
|
||||
import {Link} from 'react-router-dom';
|
||||
import Routes from '../constants/routes';
|
||||
// Style
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {
|
||||
faUserCircle,
|
||||
faGraduationCap,
|
||||
faUserSlash,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import {faBlackTie} from '@fortawesome/free-brands-svg-icons';
|
||||
import Header from '../components/Header';
|
||||
// Types
|
||||
import Dev from '../models/Dev';
|
||||
import User from '../models/User';
|
||||
import Experience from '../types/Experience';
|
||||
import {getTimePeriod} from '../types/TimePeriod';
|
||||
import Education from '../types/Education';
|
||||
|
||||
interface IProps extends Dev, WithFirebaseProps<User> {}
|
||||
/**
|
||||
* Main page from which a Dev can peek and edit its own profile.
|
||||
*/
|
||||
const Dashboard: FC<IProps> = ({
|
||||
firebase,
|
||||
displayName,
|
||||
experiences,
|
||||
educations,
|
||||
}) => {
|
||||
/** turns account to inactive then logs user out */
|
||||
const deleteAccount = () => {
|
||||
firebase.updateProfile({isActive: false}, {useSet: true, merge: true});
|
||||
firebase.logout();
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param id key of the entry to remove
|
||||
* @param entries array of credential educations
|
||||
*/
|
||||
const deleteEduEntry = (id: number, entries: Education[]) => (
|
||||
e: MouseEvent<HTMLButtonElement>,
|
||||
) => {
|
||||
firebase.updateProfile({
|
||||
educations: entries.filter((e: Education) => e.id !== id),
|
||||
});
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param id key of the entry to remove
|
||||
* @param entries array of credential experiences
|
||||
*/
|
||||
const deleteExpEntry = (id: number, entries: Experience[]) => (
|
||||
e: MouseEvent<HTMLButtonElement>,
|
||||
) => {
|
||||
firebase.updateProfile({
|
||||
experiences: entries.filter((e: Experience) => e.id !== id),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="container">
|
||||
<Header title="Dashboard" lead={`Welcome ${displayName}`} />
|
||||
<div className="dash-buttons">
|
||||
<Link to={Routes.EDIT_PROFILE} className="btn btn-light">
|
||||
<FontAwesomeIcon icon={faUserCircle} /> Edit Profile
|
||||
</Link>
|
||||
<Link to={Routes.ADD_EXPERIENCE} className="btn btn-light">
|
||||
<FontAwesomeIcon icon={faBlackTie} /> Add Experience
|
||||
</Link>
|
||||
<Link to={Routes.ADD_EDUCATION} className="btn btn-light">
|
||||
<FontAwesomeIcon icon={faGraduationCap} /> Add Education
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<h2 className="my-2">Experience Credentials</h2>
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Company</th>
|
||||
<th className="hide-sm">Title</th>
|
||||
<th className="hide-sm">Years</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{experiences?.map((exp: Experience) => (
|
||||
<tr key={exp.id}>
|
||||
<td>{exp.company}</td>
|
||||
<td className="hide-sm">{exp.position}</td>
|
||||
<td className="hide-sm">{getTimePeriod(exp.from, exp.to)}</td>
|
||||
<td>
|
||||
<button
|
||||
className="btn btn-danger"
|
||||
onClick={deleteExpEntry(exp.id, experiences)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 className="my-2">Education Credentials</h2>
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>School</th>
|
||||
<th className="hide-sm">Degree</th>
|
||||
<th className="hide-sm">Years</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{educations?.map((edu: Education, i: number) => (
|
||||
<tr key={edu.id}>
|
||||
<td>{edu.school}</td>
|
||||
<td className="hide-sm">{edu.degree}</td>
|
||||
<td className="hide-sm">{getTimePeriod(edu.from, edu.to)}</td>
|
||||
<td>
|
||||
<button
|
||||
className="btn btn-danger"
|
||||
onClick={deleteEduEntry(edu.id, educations)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="my-2">
|
||||
<button className="btn btn-danger" onClick={deleteAccount}>
|
||||
<FontAwesomeIcon icon={faUserSlash} /> Delete my Account
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default enhance(Dashboard);
|
||||
49
src/pages/Developers.tsx
Normal file
49
src/pages/Developers.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import React, {FC} from 'react';
|
||||
import Header from '../components/Header';
|
||||
import DevProfile from '../components/DevProfile';
|
||||
import {DevSummary} from '../models/Dev';
|
||||
|
||||
/**
|
||||
* Developers list page
|
||||
*/
|
||||
// const Developers: FC<DevSummary[]> = (developers) => {
|
||||
const Developers: FC = () => {
|
||||
const developers: DevSummary[] = [
|
||||
{
|
||||
id: '0',
|
||||
displayName: 'John Doe',
|
||||
picture:
|
||||
'https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?s=200',
|
||||
description: 'Developer at Microsoft',
|
||||
location: 'Seattle, WA',
|
||||
skills: ['HTML', 'CSS', 'JavaScript', 'Python'],
|
||||
},
|
||||
{
|
||||
id: '42',
|
||||
displayName: 'Ruidy Nemausat',
|
||||
picture:
|
||||
'https://lh3.googleusercontent.com/a-/AOh14GhncH95MWKwPR3TRKy4eVd4n6w0-fobe4dhiam2xA',
|
||||
description: 'Fullstack Engineer at DESY',
|
||||
location: 'Hamburg, DE',
|
||||
skills: ['React', 'TypeScript', 'Redux', 'Nodejs'],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="container">
|
||||
<Header
|
||||
title="Developers"
|
||||
lead="Browse and connect with developers"
|
||||
icon="connectdevelop"
|
||||
/>
|
||||
<div className="profiles">
|
||||
{developers.map(dev => (
|
||||
// use spread operator to pass props
|
||||
<DevProfile key={dev.id} {...dev} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Developers;
|
||||
313
src/pages/EditProfile.tsx
Normal file
313
src/pages/EditProfile.tsx
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
import React, {FC, useState} from 'react';
|
||||
import {Link} from 'react-router-dom';
|
||||
import Routes from '../constants/routes';
|
||||
// Redux
|
||||
import {enhance} from '../store/firebase';
|
||||
import {WithFirebaseProps} from 'react-redux-firebase';
|
||||
// Style
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {
|
||||
faTwitter,
|
||||
faFacebook,
|
||||
faYoutube,
|
||||
faLinkedin,
|
||||
faInstagram,
|
||||
} from '@fortawesome/free-brands-svg-icons';
|
||||
import FormHeader from '../components/FormHeader';
|
||||
import Alert from '../components/Alert';
|
||||
import Statuses from '../constants/statuses';
|
||||
// Form
|
||||
import useForm from '../hooks';
|
||||
// Typing
|
||||
import Dev from '../models/Dev';
|
||||
import User from '../models/User';
|
||||
import Links from '../types/Links';
|
||||
import IAlert, {formAlert} from '../types/Alert';
|
||||
|
||||
interface FormData {
|
||||
status: string;
|
||||
company: string;
|
||||
website: string;
|
||||
location: string;
|
||||
skills: string;
|
||||
github: string;
|
||||
bio: string;
|
||||
facebook: string;
|
||||
linkedin: string;
|
||||
instagram: string;
|
||||
twitter: string;
|
||||
youtube: string;
|
||||
}
|
||||
|
||||
interface IProps extends Dev, WithFirebaseProps<User> {}
|
||||
|
||||
/**
|
||||
* Form to update dev's personal information.
|
||||
*/
|
||||
const EditProfile: FC<IProps> = ({
|
||||
firebase,
|
||||
status,
|
||||
skills,
|
||||
company,
|
||||
links,
|
||||
location,
|
||||
bio,
|
||||
}) => {
|
||||
const [showLinks, setShowLinks] = useState(false);
|
||||
const [alert, setAlert] = useState<IAlert>(formAlert);
|
||||
|
||||
const initFormData = {
|
||||
status: status ?? 'Developer',
|
||||
company: company,
|
||||
location: location ?? '',
|
||||
bio: bio ?? '',
|
||||
skills: skills?.toString() ?? '',
|
||||
website: links?.website ?? '',
|
||||
github: links?.github ?? '',
|
||||
facebook: links?.facebook ?? '',
|
||||
linkedin: links?.linkedin ?? '',
|
||||
instagram: links?.instagram ?? '',
|
||||
twitter: links?.twitter ?? '',
|
||||
youtube: links?.youtube ?? '',
|
||||
};
|
||||
|
||||
const {formData, handleChange} = useForm<FormData>(initFormData);
|
||||
|
||||
/** construct profile object from formData */
|
||||
const makeProfile = ({
|
||||
status,
|
||||
company,
|
||||
location,
|
||||
bio,
|
||||
website,
|
||||
instagram,
|
||||
facebook,
|
||||
linkedin,
|
||||
twitter,
|
||||
github,
|
||||
youtube,
|
||||
skills,
|
||||
}: FormData) => {
|
||||
const newLinks: Links = {
|
||||
website,
|
||||
instagram,
|
||||
facebook,
|
||||
linkedin,
|
||||
twitter,
|
||||
github,
|
||||
youtube,
|
||||
};
|
||||
const newSkills: string[] = skills?.split(',');
|
||||
return {
|
||||
status,
|
||||
company,
|
||||
location,
|
||||
bio,
|
||||
links: newLinks,
|
||||
skills: newSkills,
|
||||
};
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
|
||||
e.preventDefault();
|
||||
const updatedDev = makeProfile(formData);
|
||||
try {
|
||||
firebase.updateProfile(updatedDev, {useSet: true, merge: true});
|
||||
setAlert({
|
||||
show: true,
|
||||
color: 'success',
|
||||
text:
|
||||
'Profile successfully updated. You may go back to your dashboard.',
|
||||
});
|
||||
} catch (err) {
|
||||
setAlert({...alert, show: true});
|
||||
}
|
||||
};
|
||||
|
||||
const isDisabled: boolean = formData.status === '' || formData.skills === '';
|
||||
|
||||
const toggleSocialLinks = () => setShowLinks(!showLinks);
|
||||
return (
|
||||
<section className="container">
|
||||
<FormHeader
|
||||
title="Edit your profile"
|
||||
lead="Let's get some information to make your profile stand out"
|
||||
/>
|
||||
|
||||
<form className="form" onSubmit={handleSubmit}>
|
||||
<div className="form-group">
|
||||
<select
|
||||
name="status"
|
||||
required
|
||||
onChange={handleChange}
|
||||
defaultValue={formData.status}
|
||||
>
|
||||
<option disabled>* Select Professional Status</option>
|
||||
{Statuses.map((s: string, i: number) => (
|
||||
<option value={s} key={i}>
|
||||
{s}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<small className="form-text">
|
||||
Give us an idea of where you are at in your career
|
||||
</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Company"
|
||||
name="company"
|
||||
value={formData.company}
|
||||
// value={variable}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<small className="form-text">
|
||||
Could be your own company or one you work for
|
||||
</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Website"
|
||||
name="website"
|
||||
value={formData.website}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<small className="form-text">
|
||||
Could be your own or a company website
|
||||
</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Location"
|
||||
name="location"
|
||||
value={formData.location}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<small className="form-text">
|
||||
City & state suggested (eg. Boston, MA)
|
||||
</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="* Skills"
|
||||
name="skills"
|
||||
required
|
||||
value={formData.skills}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<small className="form-text">
|
||||
Please use comma separated values (eg. HTML,CSS,JavaScript,PHP)
|
||||
</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Github Username"
|
||||
name="github"
|
||||
value={formData.github}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<small className="form-text">
|
||||
If you want your latest repos and a Github link, include your
|
||||
username
|
||||
</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<textarea
|
||||
placeholder="A short bio of yourself"
|
||||
name="bio"
|
||||
value={formData.bio}
|
||||
onChange={handleChange}
|
||||
></textarea>
|
||||
<small className="form-text">Tell us a little about yourself</small>
|
||||
</div>
|
||||
|
||||
<div className="my-2">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-light"
|
||||
onClick={toggleSocialLinks}
|
||||
>
|
||||
{showLinks ? 'Hide' : 'Add'} Social Network Links
|
||||
</button>
|
||||
<span>Optional</span>
|
||||
</div>
|
||||
|
||||
{showLinks && (
|
||||
<>
|
||||
<div className="form-group social-input">
|
||||
<FontAwesomeIcon icon={faFacebook} size="2x" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Facebook URL"
|
||||
name="facebook"
|
||||
value={formData.facebook}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group social-input">
|
||||
<FontAwesomeIcon icon={faInstagram} size="2x" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Instagram URL"
|
||||
name="instagram"
|
||||
value={formData.instagram}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group social-input">
|
||||
<FontAwesomeIcon icon={faLinkedin} size="2x" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Linkedin URL"
|
||||
name="linkedin"
|
||||
value={formData.linkedin}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group social-input">
|
||||
<FontAwesomeIcon icon={faTwitter} size="2x" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Twitter URL"
|
||||
name="twitter"
|
||||
value={formData.twitter}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group social-input">
|
||||
<FontAwesomeIcon icon={faYoutube} size="2x" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="YouTube URL"
|
||||
name="youtube"
|
||||
value={formData.youtube}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{alert.show && <Alert text={alert.text} color={alert.color} />}
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-primary my-1"
|
||||
value="Submit"
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
<Link to={Routes.DASHBOARD} className="btn btn-light my-1">
|
||||
Go Back
|
||||
</Link>
|
||||
</form>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default enhance(EditProfile);
|
||||
31
src/pages/Landing.tsx
Normal file
31
src/pages/Landing.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import React, {FC} from 'react';
|
||||
import {Link} from 'react-router-dom';
|
||||
import Routes from '../constants/routes';
|
||||
import Header from '../components/Header';
|
||||
|
||||
/**
|
||||
* Landing page
|
||||
*/
|
||||
const Landing: FC = () => (
|
||||
<section className="landing">
|
||||
<div className="dark-overlay">
|
||||
<div className="landing-inner">
|
||||
<Header
|
||||
title="DevBook"
|
||||
lead="Create developer profiles, portfolio, share and get help from other devs"
|
||||
icon="code"
|
||||
/>
|
||||
<div className="buttons">
|
||||
<Link to={Routes.SIGN_UP} className="btn btn-primary">
|
||||
Sign up
|
||||
</Link>
|
||||
<Link to={Routes.SIGN_IN} className="btn btn-light">
|
||||
Login
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
export default Landing;
|
||||
28
src/pages/NotFound.tsx
Normal file
28
src/pages/NotFound.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import React, {FC} from 'react';
|
||||
import {Link} from 'react-router-dom';
|
||||
import Header from '../components/Header';
|
||||
import Routes from '../constants/routes';
|
||||
|
||||
const NotFound: FC = () => (
|
||||
<section className="not-found">
|
||||
<div className="dark-overlay">
|
||||
<div className="landing-inner">
|
||||
<Header
|
||||
title="Nothing Here"
|
||||
lead="Sorry the page requested does not exist."
|
||||
icon="not-found"
|
||||
/>
|
||||
<div className="buttons">
|
||||
<Link to={Routes.SIGN_UP} className="btn btn-primary">
|
||||
Sign up
|
||||
</Link>
|
||||
<Link to={Routes.SIGN_IN} className="btn btn-light">
|
||||
Login
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
export default NotFound;
|
||||
59
src/pages/Post.tsx
Normal file
59
src/pages/Post.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import React, {FC} from 'react';
|
||||
import Post, {dummyPost as post} from '../models/Post';
|
||||
import Comment from '../types/Comment';
|
||||
|
||||
/**
|
||||
* Display a Post and the related comments. Shows a form to add a comment.
|
||||
*/
|
||||
const PostPage: FC<Post> = () => (
|
||||
<section className="container">
|
||||
<a href="posts.html" className="btn btn-light">
|
||||
Back To Posts
|
||||
</a>
|
||||
|
||||
<div className="post bg-white p-1 my-1">
|
||||
<div>
|
||||
<a href="profile.html">
|
||||
<img className="round-img" src={post.picture} alt={post.name} />
|
||||
<h4>{post.name}</h4>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<p className="my-1">{post.text}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="post-form">
|
||||
<div className="post-form-header bg-primary">
|
||||
<h3>Leave A Comment</h3>
|
||||
</div>
|
||||
<form className="form my-1">
|
||||
<textarea
|
||||
name="text"
|
||||
cols={30}
|
||||
rows={5}
|
||||
placeholder="Comment on this post"
|
||||
></textarea>
|
||||
<input type="submit" className="btn btn-dark my-1" value="Submit" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="posts">
|
||||
{post.comments.map((c: Comment, i: number) => (
|
||||
<div className="post bg-white p-1 my-1" key={i}>
|
||||
<div>
|
||||
<a href="profile.html">
|
||||
<img className="round-img" src={c.picture} alt={c.name} />
|
||||
<h4>{c.name}</h4>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<p className="my-1">{c.text}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
export default PostPage;
|
||||
58
src/pages/Posts.tsx
Normal file
58
src/pages/Posts.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import React, {FC} from 'react';
|
||||
import Post, {dummyPost as post} from '../models/Post';
|
||||
import Header from '../components/Header';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faThumbsUp, faThumbsDown} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
/**
|
||||
* A Dev's Posts list
|
||||
*/
|
||||
const Posts: FC = () => {
|
||||
const posts: Post[] = [post, post];
|
||||
|
||||
return (
|
||||
<section className="container">
|
||||
<Header title="Posts" lead="Welcome to the community" />
|
||||
<div className="post-form">
|
||||
<div className="post-form-header bg-primary">
|
||||
<h3>Say Something</h3>
|
||||
</div>
|
||||
|
||||
<form className="form my-1">
|
||||
<textarea cols={30} rows={5} placeholder="Create a post"></textarea>
|
||||
<input type="submit" value="Submit" className="btn btn-dark my-1" />
|
||||
<div className="posts">
|
||||
{posts.map((post: Post) => (
|
||||
<div className="post bg-white p-1 my-1" key={post.id}>
|
||||
<div>
|
||||
<a href="profile.html">
|
||||
<img
|
||||
src={post.picture}
|
||||
alt={post.name}
|
||||
className="round-img"
|
||||
/>
|
||||
<h4>{post.name}</h4>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<p className="my-1">{post.text}</p>
|
||||
<button className="btn btn-light">
|
||||
<FontAwesomeIcon icon={faThumbsUp} /> {post.likes.length}
|
||||
</button>
|
||||
<button className="btn btn-light">
|
||||
<FontAwesomeIcon icon={faThumbsDown} />
|
||||
</button>
|
||||
<a href="post.html" className="btn btn-primary">
|
||||
Discussion
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Posts;
|
||||
159
src/pages/Profile.tsx
Normal file
159
src/pages/Profile.tsx
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import React, {FC} from 'react';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {
|
||||
faGithub,
|
||||
faFacebook,
|
||||
faInstagram,
|
||||
faLinkedin,
|
||||
faTwitter,
|
||||
} from '@fortawesome/free-brands-svg-icons';
|
||||
import {
|
||||
faGlobe,
|
||||
IconDefinition,
|
||||
faCheck,
|
||||
faStar,
|
||||
faEye,
|
||||
faCodeBranch,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import Dev, {dummyDev as dev} from '../models/Dev';
|
||||
import Experience from '../types/Experience';
|
||||
import {getTimePeriod} from '../types/TimePeriod';
|
||||
import Education from '../types/Education';
|
||||
import Repo from '../types/Repo';
|
||||
|
||||
/**
|
||||
* Dev personal profile as seen by other people.
|
||||
*/
|
||||
const Profile: FC<Dev> = () => {
|
||||
/** return the icon corresponding to the social name */
|
||||
const renderSocialIcon = (name: string): IconDefinition => {
|
||||
switch (name) {
|
||||
case 'facebook':
|
||||
return faFacebook;
|
||||
case 'github':
|
||||
return faGithub;
|
||||
case 'instagram':
|
||||
return faInstagram;
|
||||
case 'linkedin':
|
||||
return faLinkedin;
|
||||
case 'twitter':
|
||||
return faTwitter;
|
||||
default:
|
||||
return faGlobe;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="container">
|
||||
<a href="profiles.html" className="btn">
|
||||
Back to profiles
|
||||
</a>
|
||||
|
||||
<div className="profile-grid my-1">
|
||||
<div className="profile-top bg-primary p-2">
|
||||
<img
|
||||
src="https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?s=200"
|
||||
alt="Some guy"
|
||||
className="round-img my-1"
|
||||
/>
|
||||
<h1 className="large">{dev.displayName}</h1>
|
||||
<p className="lead">{dev.description}</p>
|
||||
<p>{dev.location}</p>
|
||||
<div className="icons my-1">
|
||||
{Object.entries(dev.links).map(([icon, webAddress], i: number) => (
|
||||
<a href={webAddress} key={i}>
|
||||
<FontAwesomeIcon icon={renderSocialIcon(icon)} size="2x" />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="profile-about bg-light p-2">
|
||||
<h2 className="text-primary">{`${dev.displayName}'s Bio`}</h2>
|
||||
<p>{dev.bio}</p>
|
||||
<div className="line"></div>
|
||||
<h2 className="text-primary">Skill Set</h2>
|
||||
<div className="skills">
|
||||
{dev.skills.map((s: string, i: number) => (
|
||||
<div className="p-1" key={i}>
|
||||
<FontAwesomeIcon icon={faCheck} /> {s}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="profile-exp bg-white p-2">
|
||||
<h2 className="text-primary">Experiences</h2>
|
||||
{dev.experiences.map((exp: Experience, i: number) => (
|
||||
<div key={i}>
|
||||
<h3>{exp.company}</h3>
|
||||
<p>{getTimePeriod(exp.from, exp.to)}</p>
|
||||
<p>
|
||||
<strong>Position: </strong>
|
||||
{exp.position}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Description: </strong>
|
||||
{exp.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="profile-edu bg-white p-2">
|
||||
<h2 className="text-primary">Education</h2>
|
||||
{dev.educations.map((edu: Education, i: number) => (
|
||||
<div key={i}>
|
||||
<h3>{edu.school}</h3>
|
||||
<p>{getTimePeriod(edu.from, edu.to)}</p>
|
||||
<p>
|
||||
<strong>Degree: </strong>
|
||||
{edu.degree}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Field: </strong>
|
||||
{edu.field}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Description: </strong>
|
||||
{edu.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="profile-github">
|
||||
<h2 className="text-primary my-1">
|
||||
<FontAwesomeIcon icon={faGithub} /> GitHub Repos
|
||||
</h2>
|
||||
|
||||
{dev.repos.map((r: Repo, i: number) => (
|
||||
<div className="repo bg-white my-1 p-1">
|
||||
<div>
|
||||
<h4>
|
||||
<a href={r.link}>{r.name}</a>
|
||||
</h4>
|
||||
<p>{r.description}</p>
|
||||
</div>
|
||||
<div>
|
||||
<ul>
|
||||
<li className="badge badge-primary">
|
||||
<FontAwesomeIcon icon={faStar} /> Stars: 42
|
||||
</li>
|
||||
<li className="badge badge-dark">
|
||||
<FontAwesomeIcon icon={faEye} /> Watchers: 2
|
||||
</li>
|
||||
<li className="badge badge-light">
|
||||
<FontAwesomeIcon icon={faCodeBranch} /> Forks: 4
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Profile;
|
||||
109
src/pages/SignIn.tsx
Normal file
109
src/pages/SignIn.tsx
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
import React, {FC, useState} from 'react';
|
||||
// Redux
|
||||
import {WithFirebaseProps} from 'react-redux-firebase';
|
||||
import {enhance} from '../store/firebase';
|
||||
// Routing
|
||||
import {Link, Redirect} from 'react-router-dom';
|
||||
import Routes from '../constants/routes';
|
||||
// Style
|
||||
import GoogleButton from 'react-google-button';
|
||||
import Header from '../components/Header';
|
||||
import Alert from '../components/Alert';
|
||||
// Typing
|
||||
import User from '../models/User';
|
||||
// Form
|
||||
import useForm from '../hooks';
|
||||
import Dev from '../models/Dev';
|
||||
|
||||
interface InitFormData {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface IProps extends Dev, WithFirebaseProps<User> {
|
||||
isEmpty: boolean;
|
||||
isLoaded: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign in form
|
||||
*/
|
||||
const SignIn: FC<IProps> = ({firebase, isEmpty, isLoaded, isActive}) => {
|
||||
const [error, setError] = useState<any>(null);
|
||||
|
||||
// handle form data
|
||||
const initFormData: InitFormData = {
|
||||
email: '',
|
||||
password: '',
|
||||
};
|
||||
const {formData, handleChange, resetForm} = useForm<InitFormData>(
|
||||
initFormData,
|
||||
);
|
||||
const {email, password} = formData;
|
||||
|
||||
// prevent submitting invalid forms
|
||||
const isDisabled: boolean = email === '' || password === '';
|
||||
|
||||
/** create user with password */
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
firebase
|
||||
.login({email, password})
|
||||
.then(() => resetForm())
|
||||
.catch(err => setError(err));
|
||||
};
|
||||
|
||||
const loginWithGoogle = () =>
|
||||
firebase.login({provider: 'google', type: 'popup'});
|
||||
|
||||
// redirect to dashboard if connected
|
||||
if (isLoaded && !isEmpty && isActive) {
|
||||
return <Redirect to={Routes.DASHBOARD} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="container">
|
||||
{error && <Alert text={error?.message} />}
|
||||
<Header title="Sign In" lead="Sign into your account" />
|
||||
<GoogleButton type="light" className="my-1" onClick={loginWithGoogle} />
|
||||
<form onSubmit={handleSubmit} className="form">
|
||||
<div className="form-group">
|
||||
<input
|
||||
name="email"
|
||||
value={email}
|
||||
onChange={handleChange}
|
||||
placeholder="Email Address"
|
||||
type="email"
|
||||
required
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
name="password"
|
||||
value={password}
|
||||
onChange={handleChange}
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
minLength={6}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="submit"
|
||||
value="Login"
|
||||
className="btn btn-primary"
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</form>
|
||||
<p className="my-1">
|
||||
Don't have an account? <Link to={Routes.SIGN_UP}>Sign up</Link>
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
/** subscribe to store and firebase */
|
||||
export default enhance(SignIn);
|
||||
166
src/pages/SignUp.tsx
Normal file
166
src/pages/SignUp.tsx
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
import React, {FC, useState} from 'react';
|
||||
// Routing
|
||||
import {Link, Redirect} from 'react-router-dom';
|
||||
import Routes from '../constants/routes';
|
||||
// Redux
|
||||
import {WithFirebaseProps} from 'react-redux-firebase';
|
||||
import {enhance} from '../store/firebase';
|
||||
import User, {newUser} from '../models/User';
|
||||
// Style
|
||||
import GoogleButton from 'react-google-button';
|
||||
import Alert from '../components/Alert';
|
||||
import Header from '../components/Header';
|
||||
// Form
|
||||
import useForm from '../hooks';
|
||||
import Dev, {blankDev} from '../models/Dev';
|
||||
|
||||
// extends withFirebaseProps type to ad profile info
|
||||
interface IProps extends Dev, WithFirebaseProps<User> {
|
||||
isEmpty: boolean;
|
||||
isLoaded: boolean;
|
||||
}
|
||||
|
||||
interface InitFormData {
|
||||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
password2: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign up form recieves firebase from withFirebase HOC
|
||||
*/
|
||||
const SignUp: FC<IProps> = ({firebase, isEmpty, isLoaded, isActive}) => {
|
||||
const [error, setError] = useState<any>(null);
|
||||
|
||||
// handle form data
|
||||
const initFormData: InitFormData = {
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password2: '',
|
||||
};
|
||||
|
||||
const {formData, handleChange, resetForm} = useForm<InitFormData>(
|
||||
initFormData,
|
||||
);
|
||||
|
||||
const {name, email, password, password2} = formData;
|
||||
|
||||
// prevent submitting invalid forms
|
||||
const isDisabled: boolean = name === '' || email === '' || password === '';
|
||||
|
||||
/** create user with password */
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
// pass the info to store into the second argument
|
||||
firebase
|
||||
.createUser({email, password}, newUser(name, email))
|
||||
.then(() => {
|
||||
firebase.updateProfile(blankDev, {useSet: true, merge: true});
|
||||
resetForm();
|
||||
})
|
||||
.catch(err => setError(err));
|
||||
};
|
||||
|
||||
const loginWithGoogle = () =>
|
||||
firebase
|
||||
.login({provider: 'google', type: 'popup'})
|
||||
.then(() => {
|
||||
// updateProfile only if user does not already exists in db
|
||||
const email = firebase.auth().currentUser?.email;
|
||||
let exists: boolean = false;
|
||||
firebase
|
||||
.firestore()
|
||||
.collection('users/')
|
||||
.where('email', '==', email)
|
||||
.get()
|
||||
.then(docs =>
|
||||
docs.forEach(doc => {
|
||||
exists = doc.data().isActive !== undefined;
|
||||
}),
|
||||
)
|
||||
.then(() => {
|
||||
if (!exists)
|
||||
firebase.updateProfile(blankDev, {useSet: true, merge: true});
|
||||
});
|
||||
})
|
||||
.catch(err => setError(err));
|
||||
|
||||
// redirect to dashboard if connected
|
||||
if (isLoaded && !isEmpty && isActive) {
|
||||
return <Redirect to={Routes.DASHBOARD} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="container">
|
||||
{error && <Alert text={error?.message} />}
|
||||
<Header title="Sign Up" lead="Create your account" />
|
||||
<GoogleButton type="light" className="my-1" onClick={loginWithGoogle} />
|
||||
<form className="form" onSubmit={handleSubmit}>
|
||||
<div className="form-group">
|
||||
<input
|
||||
name="name"
|
||||
value={name}
|
||||
onChange={handleChange}
|
||||
placeholder="Name"
|
||||
type="text"
|
||||
required
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<input
|
||||
name="email"
|
||||
value={email}
|
||||
onChange={handleChange}
|
||||
placeholder="Email Address"
|
||||
type="email"
|
||||
required
|
||||
/>
|
||||
<small className="form-text">
|
||||
This site uses Gravatar, so use a Gravatar email.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<input
|
||||
name="password"
|
||||
value={password}
|
||||
onChange={handleChange}
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
minLength={6}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<input
|
||||
name="password2"
|
||||
value={password2}
|
||||
onChange={handleChange}
|
||||
placeholder="Confirm Password"
|
||||
type="password"
|
||||
minLength={6}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="submit"
|
||||
value="Register"
|
||||
className="btn btn-primary"
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</form>
|
||||
<p className="my-1">
|
||||
Already have an account? <Link to={Routes.SIGN_IN}>Sign in</Link>
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
/** subscribe to store and firebase */
|
||||
export default enhance(SignUp);
|
||||
7
src/pages/__tests__/SignUp.spec.tsx
Normal file
7
src/pages/__tests__/SignUp.spec.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import SignUp from '../SignUp';
|
||||
|
||||
describe('Signup Page', () => {
|
||||
it('calls loadUser function', () => {});
|
||||
it('redirects to dashboard if signed up', () => {});
|
||||
it('call signup function on click', () => {});
|
||||
});
|
||||
50
src/router/PrivateRoute.tsx
Normal file
50
src/router/PrivateRoute.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import React, {FC} from 'react';
|
||||
// Routing
|
||||
import {Route, Redirect} from 'react-router-dom';
|
||||
import Routes from '../constants/routes';
|
||||
// Redux
|
||||
import {isLoaded, isEmpty} from 'react-redux-firebase';
|
||||
import {useSelector} from 'react-redux';
|
||||
import {RootState} from '../store';
|
||||
|
||||
interface IProps {
|
||||
exact?: boolean;
|
||||
path: string;
|
||||
component: React.FC<any>;
|
||||
}
|
||||
/**
|
||||
* Redirects to the login screen if you're not authenticated yet or
|
||||
* if auth is not loaded yet
|
||||
*/
|
||||
const PrivateRoute: FC<IProps> = ({
|
||||
component: Component,
|
||||
exact,
|
||||
path,
|
||||
...rest
|
||||
}) => {
|
||||
const auth = useSelector((state: RootState) => state.firebase.auth);
|
||||
const profile = useSelector((state: RootState) => state.firebase.profile);
|
||||
const isActive = profile.isActive;
|
||||
return (
|
||||
<Route
|
||||
exact={exact}
|
||||
path={path}
|
||||
{...rest}
|
||||
render={({location, ...rest}) =>
|
||||
isLoaded(auth) && !isEmpty(auth) && isActive ? (
|
||||
<Component {...rest} />
|
||||
) : (
|
||||
<Redirect
|
||||
to={{
|
||||
pathname: Routes.SIGN_IN,
|
||||
state: {from: location},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/** subscribe to store and firebase */
|
||||
export default PrivateRoute;
|
||||
40
src/router/Router.tsx
Normal file
40
src/router/Router.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import React, {FC} from 'react';
|
||||
import {Switch, Route} from 'react-router-dom';
|
||||
import Landing from '../pages/Landing';
|
||||
import SignUp from '../pages/SignUp';
|
||||
import SignIn from '../pages/SignIn';
|
||||
import Developers from '../pages/Developers';
|
||||
import Profile from '../pages/Profile';
|
||||
import EditProfile from '../pages/EditProfile';
|
||||
import Dashboard from '../pages/Dashboard';
|
||||
import AddExperience from '../pages/AddExperience';
|
||||
import AddEducation from '../pages/AddEducation';
|
||||
import PostPage from '../pages/Post';
|
||||
import Posts from '../pages/Posts';
|
||||
import NotFound from '../pages/NotFound';
|
||||
import Routes from '../constants/routes';
|
||||
import PrivateRoute from './PrivateRoute';
|
||||
|
||||
/** Register navigation paths accessible */
|
||||
const Router: FC = () => (
|
||||
<Switch>
|
||||
<Route exact path={Routes.LANDING} component={Landing} />
|
||||
<Route exact path={Routes.SIGN_UP} component={SignUp} />
|
||||
<Route exact path={Routes.SIGN_IN} component={SignIn} />
|
||||
<Route exact path={Routes.DEVELOPERS} component={Developers} />
|
||||
<Route exact path={Routes.PROFILE} component={Profile} />
|
||||
<PrivateRoute exact path={Routes.EDIT_PROFILE} component={EditProfile} />
|
||||
<PrivateRoute exact path={Routes.DASHBOARD} component={Dashboard} />
|
||||
<PrivateRoute
|
||||
exact
|
||||
path={Routes.ADD_EXPERIENCE}
|
||||
component={AddExperience}
|
||||
/>
|
||||
<PrivateRoute exact path={Routes.ADD_EDUCATION} component={AddEducation} />
|
||||
<PrivateRoute exact path={Routes.POST} component={PostPage} />
|
||||
<PrivateRoute exact path={Routes.POSTS} component={Posts} />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
export default Router;
|
||||
21
src/services/firebase/index.ts
Normal file
21
src/services/firebase/index.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//Firebase
|
||||
import firebase from 'firebase/app';
|
||||
import 'firebase/auth';
|
||||
import 'firebase/firestore';
|
||||
|
||||
const CONFIG = {
|
||||
apiKey: process.env.REACT_APP_API_KEY,
|
||||
authDomain: process.env.REACT_APP_AUTH_DOMAIN,
|
||||
databaseURL: process.env.REACT_APP_DB_URL,
|
||||
projectId: process.env.REACT_APP_PROJECT_ID,
|
||||
storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
|
||||
messagingSenderId: process.env.REACT_APP_MSG_SENDER_ID,
|
||||
appId: process.env.REACT_APP_APP_ID,
|
||||
measurementId: process.env.REACT_APP_MEASUREMENT_ID,
|
||||
};
|
||||
|
||||
// initialize firebase services
|
||||
firebase.initializeApp(CONFIG);
|
||||
firebase.firestore();
|
||||
|
||||
export default firebase;
|
||||
1
src/static/css/style.min.css
vendored
Normal file
1
src/static/css/style.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
src/static/img/404.jpg
Normal file
BIN
src/static/img/404.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 MiB |
BIN
src/static/img/showcase.jpg
Normal file
BIN
src/static/img/showcase.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 300 KiB |
39
src/static/scss/_config.scss
Normal file
39
src/static/scss/_config.scss
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
$primary-color: #17a2b8;
|
||||
$dark-color: #343a40;
|
||||
$light-color: #f4f4f4;
|
||||
$danger-color: #dc3545;
|
||||
$success-color: #28a745;
|
||||
$dark: #333;
|
||||
$max-width: 1100px;
|
||||
|
||||
// set text color based on background
|
||||
@function set-text-color($color) {
|
||||
@if (lightness($color) >60) {
|
||||
@return $dark;
|
||||
} @else {
|
||||
@return white;
|
||||
}
|
||||
}
|
||||
// set background and text color
|
||||
@mixin set-background($color) {
|
||||
background-color: $color;
|
||||
color: set-text-color($color);
|
||||
}
|
||||
|
||||
// margin and padding
|
||||
$spaceamounts: (1, 2, 3, 4, 5);
|
||||
|
||||
@each $space in $spaceamounts {
|
||||
.m-#{$space} {
|
||||
margin: #{$space}rem;
|
||||
}
|
||||
.my-#{$space} {
|
||||
margin: #{$space}rem 0;
|
||||
}
|
||||
.p-#{$space} {
|
||||
padding: #{$space}rem;
|
||||
}
|
||||
.py-#{$space} {
|
||||
padding: #{$space}rem 0;
|
||||
}
|
||||
}
|
||||
52
src/static/scss/_form.scss
Normal file
52
src/static/scss/_form.scss
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
.form {
|
||||
&-group {
|
||||
margin: 1.2rem 0;
|
||||
}
|
||||
&-text {
|
||||
display: block;
|
||||
margin-top: 0.3rem;
|
||||
color: #888;
|
||||
}
|
||||
input[type='text'],
|
||||
input[type='email'],
|
||||
input[type='password'],
|
||||
input[type='date'],
|
||||
select,
|
||||
textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.4rem;
|
||||
font-size: 1.2rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 0.3rem;
|
||||
}
|
||||
|
||||
input[type='submit'] {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.social-input {
|
||||
display: flex;
|
||||
|
||||
svg {
|
||||
padding: 0.5rem;
|
||||
width: 3rem;
|
||||
|
||||
&.fa-twitter {
|
||||
color: #38a1f3;
|
||||
}
|
||||
&.fa-facebook {
|
||||
color: #3b5998;
|
||||
}
|
||||
&.fa-instagram {
|
||||
color: #3f729b;
|
||||
}
|
||||
&.fa-youtube {
|
||||
color: #c4302b;
|
||||
}
|
||||
&.fa-linkedin {
|
||||
color: #0077b5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
76
src/static/scss/_mobile.scss
Normal file
76
src/static/scss/_mobile.scss
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
@media (max-width: 700px) {
|
||||
.hide-sm {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin-top: 8rem;
|
||||
}
|
||||
|
||||
// Text Styles
|
||||
.x-large {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.large {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.lead {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
// Navbar
|
||||
.navbar {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
|
||||
ul {
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.dash-buttons a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
// Profiles
|
||||
.profile {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
|
||||
ul {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-grid {
|
||||
grid-template-areas:
|
||||
"top"
|
||||
"about"
|
||||
"exp"
|
||||
"edu"
|
||||
"github";
|
||||
}
|
||||
|
||||
.profile-about {
|
||||
.skills {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.post {
|
||||
grid-template-columns: 1fr;
|
||||
a,
|
||||
button {
|
||||
padding: 0.3rem 0.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
183
src/static/scss/_utils.scss
Normal file
183
src/static/scss/_utils.scss
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
@import '_config';
|
||||
|
||||
// Backgrounds
|
||||
|
||||
.bg {
|
||||
&-primary {
|
||||
@include set-background($primary-color);
|
||||
}
|
||||
&-light {
|
||||
@include set-background($light-color);
|
||||
border: #ccc 1px solid;
|
||||
}
|
||||
&-dark {
|
||||
@include set-background($dark-color);
|
||||
}
|
||||
&-danger {
|
||||
@include set-background($danger-color);
|
||||
}
|
||||
&-success {
|
||||
@include set-background($success-color);
|
||||
}
|
||||
&-white {
|
||||
@include set-background(white);
|
||||
border: #ccc 1px solid;
|
||||
}
|
||||
}
|
||||
|
||||
// dark overlay
|
||||
.dark-overlay {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
// Text-styles
|
||||
.x-large {
|
||||
font-size: 4rem;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.large {
|
||||
font-size: 3rem;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.lead {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
// Buttons
|
||||
.btn {
|
||||
display: inline-block;
|
||||
background-color: $light-color;
|
||||
color: $dark;
|
||||
padding: 0.4rem 1.3rem;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
margin-right: 0.5rem;
|
||||
outline: none;
|
||||
transition: all 0.3s ease-in;
|
||||
|
||||
&.btn-primary {
|
||||
@include set-background($primary-color);
|
||||
&:hover {
|
||||
background: lighten($primary-color, 5%);
|
||||
}
|
||||
}
|
||||
&.btn-dark {
|
||||
@include set-background($dark-color);
|
||||
&:hover {
|
||||
background: lighten($dark-color, 5%);
|
||||
}
|
||||
}
|
||||
&.btn-light {
|
||||
@include set-background($light-color);
|
||||
&:hover {
|
||||
background: lighten($dark-color, 20%);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
&.btn-danger {
|
||||
@include set-background($danger-color);
|
||||
&:hover {
|
||||
background: lighten($danger-color, 5%);
|
||||
}
|
||||
}
|
||||
&.btn-success {
|
||||
@include set-background($success-color);
|
||||
&:hover {
|
||||
background: lighten($success-color, 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Container
|
||||
.container {
|
||||
max-width: $max-width;
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
padding: 0 2rem;
|
||||
margin-top: 5rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
// Alerts
|
||||
.alert {
|
||||
padding: 0.8rem;
|
||||
margin: 1rem;
|
||||
opacity: 0.9;
|
||||
background-color: $light-color;
|
||||
color: $dark;
|
||||
|
||||
&.alert-primary {
|
||||
@include set-background($primary-color);
|
||||
}
|
||||
&.alert-dark {
|
||||
@include set-background($dark-color);
|
||||
}
|
||||
&.alert-success {
|
||||
@include set-background($success-color);
|
||||
}
|
||||
&.alert-danger {
|
||||
@include set-background($danger-color);
|
||||
}
|
||||
}
|
||||
|
||||
.round-img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.line {
|
||||
height: 1px;
|
||||
background: #ccc;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
// badge
|
||||
.badge {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.1rem;
|
||||
text-align: center;
|
||||
margin: 0.3rem;
|
||||
background: $light-color;
|
||||
color: $dark;
|
||||
border-radius: 1rem;
|
||||
|
||||
&.badge-primary {
|
||||
@include set-background($primary-color);
|
||||
}
|
||||
&.badge-dark {
|
||||
@include set-background($dark-color);
|
||||
}
|
||||
&.badge-success {
|
||||
@include set-background($success-color);
|
||||
}
|
||||
&.badge-danger {
|
||||
@include set-background($danger-color);
|
||||
}
|
||||
}
|
||||
|
||||
// Table
|
||||
.table {
|
||||
th,
|
||||
td {
|
||||
padding: 1rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background: $light-color;
|
||||
}
|
||||
}
|
||||
198
src/static/scss/style.scss
Normal file
198
src/static/scss/style.scss
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
@import '_config';
|
||||
@import '_utils';
|
||||
@import '_form';
|
||||
@import '_mobile';
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
background-color: white;
|
||||
color: $dark;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// Navbar
|
||||
|
||||
.navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.7rem 2rem;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
border-bottom: solid 1px $primary-color;
|
||||
opacity: 0.9;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
padding: 0.45rem;
|
||||
margin: 0 0.25rem;
|
||||
|
||||
&:hover {
|
||||
color: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// landing
|
||||
.landing {
|
||||
position: relative;
|
||||
background: url('../img/showcase.jpg') no-repeat center center/cover;
|
||||
height: 100vh;
|
||||
|
||||
&-inner {
|
||||
color: white;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
// profiles
|
||||
.profile {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 4fr 2fr;
|
||||
grid-gap: 2rem;
|
||||
padding: 1rem;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// Profile Page
|
||||
|
||||
.profile-grid {
|
||||
display: grid;
|
||||
grid-template-areas: 'top top' 'about about' 'exp edu' 'github github';
|
||||
grid-gap: 1rem;
|
||||
|
||||
.profile-top {
|
||||
grid-area: top;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.icons a {
|
||||
color: white;
|
||||
margin: 0 0.3rem;
|
||||
|
||||
&:hover {
|
||||
color: $dark-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-about {
|
||||
grid-area: about;
|
||||
text-align: center;
|
||||
|
||||
.skills {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.profile-exp {
|
||||
grid-area: exp;
|
||||
}
|
||||
.profile-edu {
|
||||
grid-area: edu;
|
||||
}
|
||||
|
||||
.profile-exp,
|
||||
.profile-edu {
|
||||
& > div {
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: #ccc 1px dotted;
|
||||
|
||||
&:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-github {
|
||||
grid-area: github;
|
||||
|
||||
.repo {
|
||||
display: flex;
|
||||
|
||||
& > div:first-child {
|
||||
flex: 7;
|
||||
flex-basis: 70%;
|
||||
}
|
||||
& > div:last-child {
|
||||
flex: 3;
|
||||
flex-basis: 30%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Posts
|
||||
.post-form-header {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.post {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 4fr;
|
||||
grid-gap: 2rem;
|
||||
align-items: center;
|
||||
|
||||
& > div:first-child {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
.not-found {
|
||||
position: relative;
|
||||
background: url('../img/404.jpg') no-repeat center center/cover;
|
||||
height: 100vh;
|
||||
}
|
||||
36
src/store/auth/index.ts
Normal file
36
src/store/auth/index.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Redux
|
||||
import {createSlice} from '@reduxjs/toolkit';
|
||||
// Typing
|
||||
import User from '../../models/User';
|
||||
|
||||
interface SliceState {
|
||||
isAuthenticated: boolean;
|
||||
loading: boolean;
|
||||
user: User | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const initialState: SliceState = {
|
||||
isAuthenticated: false,
|
||||
loading: true,
|
||||
user: null,
|
||||
error: null,
|
||||
};
|
||||
|
||||
const authSlice = createSlice({
|
||||
name: 'auth',
|
||||
initialState,
|
||||
reducers: {},
|
||||
});
|
||||
|
||||
// export actions
|
||||
export const {} = authSlice.actions;
|
||||
|
||||
// export selectors
|
||||
// export const selectAuthState = (state: RootState) => {
|
||||
// const {isAuthenticated, loading} = state.auth;
|
||||
// return {isAuthenticated, loading};
|
||||
// };
|
||||
|
||||
// export reducer
|
||||
export default authSlice.reducer;
|
||||
27
src/store/firebase/config.ts
Normal file
27
src/store/firebase/config.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Redux
|
||||
import {createFirestoreInstance} from 'redux-firestore';
|
||||
import store from '..';
|
||||
// Firebase
|
||||
import firebase from '../../services/firebase';
|
||||
// Typing
|
||||
import Dev from '../../models/Dev';
|
||||
|
||||
// react-redux-firebase config
|
||||
const RRF_CONFIG = {
|
||||
userProfile: 'users',
|
||||
useFirestoreForProfile: true,
|
||||
};
|
||||
// object required by RRFProvider
|
||||
const rrfProps = {
|
||||
firebase,
|
||||
config: RRF_CONFIG,
|
||||
dispatch: store.dispatch,
|
||||
createFirestoreInstance,
|
||||
};
|
||||
|
||||
// Firestore Schema
|
||||
export interface Schema {
|
||||
devs: Dev;
|
||||
}
|
||||
|
||||
export default rrfProps;
|
||||
13
src/store/firebase/index.ts
Normal file
13
src/store/firebase/index.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import {FC} from 'react';
|
||||
// Redux
|
||||
import {compose} from '@reduxjs/toolkit';
|
||||
import {connect} from 'react-redux';
|
||||
import {withFirebase} from 'react-redux-firebase';
|
||||
import {RootState} from '..';
|
||||
|
||||
/** export firebase authentication */
|
||||
export const selectAuthState = (state: RootState) => state.firebase.auth;
|
||||
/** export current user profile */
|
||||
export const selectProfile = (state: RootState) => state.firebase.profile;
|
||||
/** subscribe to firebase and profile */
|
||||
export const enhance = compose<FC>(connect(selectProfile), withFirebase);
|
||||
22
src/store/index.ts
Normal file
22
src/store/index.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// Redux
|
||||
import {configureStore} from '@reduxjs/toolkit';
|
||||
// Firebase
|
||||
import {firebaseReducer, FirebaseReducer} from 'react-redux-firebase';
|
||||
import {firestoreReducer} from 'redux-firestore';
|
||||
// Typing
|
||||
import {Schema} from './firebase/config';
|
||||
import Dev from '../models/Dev';
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
firebase: firebaseReducer,
|
||||
firestore: firestoreReducer,
|
||||
},
|
||||
});
|
||||
|
||||
// State type
|
||||
export interface RootState {
|
||||
firebase: FirebaseReducer.Reducer<Dev, Schema>;
|
||||
}
|
||||
|
||||
export default store;
|
||||
13
src/types/Alert.ts
Normal file
13
src/types/Alert.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
interface IAlert {
|
||||
show: boolean;
|
||||
color: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export const formAlert: IAlert = {
|
||||
show: false,
|
||||
color: 'danger',
|
||||
text: 'Something went wrong',
|
||||
};
|
||||
|
||||
export default IAlert;
|
||||
9
src/types/Comment.ts
Normal file
9
src/types/Comment.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
interface Comment {
|
||||
// userID: string;
|
||||
text: string;
|
||||
name: string;
|
||||
picture: string;
|
||||
// date: Date;
|
||||
}
|
||||
|
||||
export default Comment;
|
||||
13
src/types/Education.ts
Normal file
13
src/types/Education.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import TimePeriod from '../types/TimePeriod';
|
||||
|
||||
interface Education {
|
||||
id: number;
|
||||
school: string;
|
||||
degree: string;
|
||||
from: TimePeriod;
|
||||
to: TimePeriod;
|
||||
field: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default Education;
|
||||
13
src/types/Experience.ts
Normal file
13
src/types/Experience.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import TimePeriod from '../types/TimePeriod';
|
||||
|
||||
interface Experience {
|
||||
id: number;
|
||||
company: string;
|
||||
from: TimePeriod;
|
||||
to: TimePeriod;
|
||||
position: string;
|
||||
description: string;
|
||||
location: string;
|
||||
}
|
||||
|
||||
export default Experience;
|
||||
11
src/types/Links.ts
Normal file
11
src/types/Links.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
interface Links {
|
||||
website: string;
|
||||
instagram: string;
|
||||
facebook: string;
|
||||
linkedin: string;
|
||||
twitter: string;
|
||||
github: string;
|
||||
youtube: string;
|
||||
}
|
||||
|
||||
export default Links;
|
||||
10
src/types/Repo.ts
Normal file
10
src/types/Repo.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
interface Repo {
|
||||
name: string;
|
||||
description: string;
|
||||
link: string;
|
||||
stars: number;
|
||||
watchers: number;
|
||||
forks: number;
|
||||
}
|
||||
|
||||
export default Repo;
|
||||
21
src/types/TimePeriod.ts
Normal file
21
src/types/TimePeriod.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import moment from 'moment';
|
||||
|
||||
type TimePeriod = string | Date | 'Current';
|
||||
|
||||
/** format exp date to be used */
|
||||
export const parseDate = (date: TimePeriod): string => {
|
||||
if (date === 'Current') {
|
||||
return date;
|
||||
}
|
||||
return moment(date).format('MMM. YYYY');
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats a time period assignment: experience or education.
|
||||
* @param from Start of the assignment. Must be a Date
|
||||
* @param to End of the assignment. Can be "Current"
|
||||
*/
|
||||
export const getTimePeriod = (from: TimePeriod, to: TimePeriod): string =>
|
||||
`${parseDate(from)} - ${parseDate(to)}`;
|
||||
|
||||
export default TimePeriod;
|
||||
63
tslint.json
Normal file
63
tslint.json
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"rules": {
|
||||
"adjacent-overload-signatures": true,
|
||||
"ban-comma-operator": true,
|
||||
"no-namespace": true,
|
||||
"no-parameter-reassignment": true,
|
||||
"no-reference": true,
|
||||
"no-unnecessary-type-assertion": true,
|
||||
"label-position": true,
|
||||
"no-conditional-assignment": true,
|
||||
"no-construct": true,
|
||||
"no-duplicate-super": true,
|
||||
"no-duplicate-switch-case": true,
|
||||
"no-duplicate-variable": [
|
||||
true,
|
||||
"check-parameters"
|
||||
],
|
||||
"no-shadowed-variable": true,
|
||||
"no-empty": [
|
||||
true,
|
||||
"allow-empty-catch"
|
||||
],
|
||||
"no-floating-promises": true,
|
||||
"no-implicit-dependencies": true,
|
||||
"no-invalid-this": true,
|
||||
"no-string-throw": true,
|
||||
"no-unsafe-finally": true,
|
||||
"no-void-expression": [
|
||||
true,
|
||||
"ignore-arrow-function-shorthand"
|
||||
],
|
||||
"no-duplicate-imports": true,
|
||||
// Warn when an empty interface is defined. These are generally not useful.
|
||||
"no-empty-interface": {
|
||||
"severity": "warning"
|
||||
},
|
||||
"no-import-side-effect": {
|
||||
"severity": "warning"
|
||||
},
|
||||
"no-var-keyword": {
|
||||
"severity": "warning"
|
||||
},
|
||||
"triple-equals": {
|
||||
"severity": "warning"
|
||||
},
|
||||
"deprecation": {
|
||||
"severity": "warning"
|
||||
},
|
||||
"prefer-for-of": {
|
||||
"severity": "warning"
|
||||
},
|
||||
"unified-signatures": {
|
||||
"severity": "warning"
|
||||
},
|
||||
"prefer-const": {
|
||||
"severity": "warning"
|
||||
},
|
||||
"trailing-comma": {
|
||||
"severity": "warning"
|
||||
}
|
||||
},
|
||||
"defaultSeverity": "error"
|
||||
}
|
||||
698
yarn.lock
698
yarn.lock
|
|
@ -956,7 +956,7 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.5.1", "@babel/runtime@^7.7.4":
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.4":
|
||||
version "7.9.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f"
|
||||
integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==
|
||||
|
|
@ -1082,6 +1082,289 @@
|
|||
debug "^3.1.0"
|
||||
lodash.once "^4.1.1"
|
||||
|
||||
"@firebase/analytics-types@0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.3.0.tgz#33c3f695313b561d48d18d663a20f20362d3ee7c"
|
||||
integrity sha512-0AJ6xn53Qn0D/YOVHHvlWFfnzzRSdd98Lr8Oqe1PJ2HPIN+o7qf03YmOG7fLpR1uplcWd+7vGKmxUrN3jKUBwg==
|
||||
|
||||
"@firebase/analytics@0.3.4":
|
||||
version "0.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.3.4.tgz#64146d8cb36c2239ba39330459022c3e8d861489"
|
||||
integrity sha512-x5Hxj3B9Zm4H6CEjMD/J86WjmiX9C6AhBBltaYzWMtqkqa/WvvWMicl4MpwZXjOdBbOd286oGgJPFqQMUYI/WQ==
|
||||
dependencies:
|
||||
"@firebase/analytics-types" "0.3.0"
|
||||
"@firebase/component" "0.1.11"
|
||||
"@firebase/installations" "0.4.9"
|
||||
"@firebase/logger" "0.2.3"
|
||||
"@firebase/util" "0.2.46"
|
||||
tslib "1.11.1"
|
||||
|
||||
"@firebase/app-types@0.6.0":
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.6.0.tgz#8dcc3e793c6983e9d54f7eb623a7618c05f2d94c"
|
||||
integrity sha512-ld6rzjXk/SUauHiQZJkeuSJpxIZ5wdnWuF5fWBFQNPaxsaJ9kyYg9GqEvwZ1z2e6JP5cU9gwRBlfW1WkGtGDYA==
|
||||
|
||||
"@firebase/app@0.6.3":
|
||||
version "0.6.3"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.6.3.tgz#dade13b381f3b02b9a3847c57a0eec584629d2df"
|
||||
integrity sha512-59Q/XNi+QyV1GOvxN+BusoKjqDKUjDupePDhlT6SqvFdvb03TjG03fSfurhXGXmTk6f500aOIyVJ8UlYpTYrsg==
|
||||
dependencies:
|
||||
"@firebase/app-types" "0.6.0"
|
||||
"@firebase/component" "0.1.11"
|
||||
"@firebase/logger" "0.2.3"
|
||||
"@firebase/util" "0.2.46"
|
||||
dom-storage "2.1.0"
|
||||
tslib "1.11.1"
|
||||
xmlhttprequest "1.8.0"
|
||||
|
||||
"@firebase/auth-interop-types@0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.1.4.tgz#e81589f58508630a5bffa604d7c949a0d01ea97b"
|
||||
integrity sha512-CLKNS84KGAv5lRnHTQZFWoR11Ti7gIPFirDDXWek/fSU+TdYdnxJFR5XSD4OuGyzUYQ3Dq7aVj5teiRdyBl9hA==
|
||||
|
||||
"@firebase/auth-types@0.10.0":
|
||||
version "0.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.10.0.tgz#9403633e723336055fad4bbf5e4c9fe3c55f8d3f"
|
||||
integrity sha512-VuW7c+RAk3AYPU0Hxmun3RzXn7fbJDdjQbxvvpRMnQ9zrhk8mH42cY466M0n4e/UGQ+0smlx5BqZII8aYQ5XPg==
|
||||
|
||||
"@firebase/auth@0.14.5":
|
||||
version "0.14.5"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-0.14.5.tgz#52a313579a711e70cefa735045242be1e29aefdc"
|
||||
integrity sha512-76ejEQrJ81s2ZI2RV/AoZnw3sDl7dZSpaJJtPlhqlahymtQ2sSeAZJAmECcTB27PF6EeCdRhB9qOIKGAEAhbJg==
|
||||
dependencies:
|
||||
"@firebase/auth-types" "0.10.0"
|
||||
|
||||
"@firebase/component@0.1.11":
|
||||
version "0.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.1.11.tgz#0341f2dd99eac32a28d2b674eef9be9b7c5c9ad9"
|
||||
integrity sha512-HZ0fwtv8/b3KV4NUOqlcIr03+CpBKW0F1Jo6/HJ39AutS6XXbM2jtpXOd1wMq9lbhBHgEwt1sMPNKoPR1bFflQ==
|
||||
dependencies:
|
||||
"@firebase/util" "0.2.46"
|
||||
tslib "1.11.1"
|
||||
|
||||
"@firebase/database-types@0.5.0":
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-0.5.0.tgz#603a0865c3180a9ffb6f5fa065d156387385a74d"
|
||||
integrity sha512-6/W3frFznYOALtw2nrWVPK2ytgdl89CzTqVBHCCGf22wT6uKU63iDBo+Nw+7olFGpD15O0zwYalFIcMZ27tkew==
|
||||
dependencies:
|
||||
"@firebase/app-types" "0.6.0"
|
||||
|
||||
"@firebase/database@0.6.2":
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/database/-/database-0.6.2.tgz#d8ef8134d2ce71d5af2a948f8d2fd1ae36a48a12"
|
||||
integrity sha512-0D0WOqYlNg3NMi0hJPx18tun6FMfr31d1dZB0Lai0K5jScBhPr2h4Fy7yp5lyOklwDSAoBYxmpX4nzHuDheL9Q==
|
||||
dependencies:
|
||||
"@firebase/auth-interop-types" "0.1.4"
|
||||
"@firebase/component" "0.1.11"
|
||||
"@firebase/database-types" "0.5.0"
|
||||
"@firebase/logger" "0.2.3"
|
||||
"@firebase/util" "0.2.46"
|
||||
faye-websocket "0.11.3"
|
||||
tslib "1.11.1"
|
||||
|
||||
"@firebase/firestore-types@1.10.1":
|
||||
version "1.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-1.10.1.tgz#bf018f9c495f470592de745389474dc1c2960d3f"
|
||||
integrity sha512-vyKdm+AYUFT8XeUX62IOqaqPFCs/mAMoSEsqIz9HnSVsqCw/IocNjtjSa+3M80kRw4V8fI7JI+Xz6Wg5VJXLqA==
|
||||
|
||||
"@firebase/firestore@1.14.3":
|
||||
version "1.14.3"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-1.14.3.tgz#675e0c26db056eee140e06bb17ecc91f21cc4390"
|
||||
integrity sha512-vmkXa5Msumutf0ZQjF8AQQwXr4mXI7D8TBbI44w+CMQEnKcD5MW7Dr1SmCTWy8+kNAAFwdA6lCiUtDY5Gx/Hlw==
|
||||
dependencies:
|
||||
"@firebase/component" "0.1.11"
|
||||
"@firebase/firestore-types" "1.10.1"
|
||||
"@firebase/logger" "0.2.3"
|
||||
"@firebase/util" "0.2.46"
|
||||
"@firebase/webchannel-wrapper" "0.2.40"
|
||||
"@grpc/grpc-js" "0.8.1"
|
||||
"@grpc/proto-loader" "^0.5.0"
|
||||
tslib "1.11.1"
|
||||
|
||||
"@firebase/functions-types@0.3.16":
|
||||
version "0.3.16"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.3.16.tgz#be0362d7f61648fdf36a7d95de239eddee88f931"
|
||||
integrity sha512-kHhBvSYiY2prY4vNQCALYs1+OruTdylvGemHG6G6Bs/rj3qw7ui3WysBsDU/rInJitHIcsZ35qrtanoJeQUIXQ==
|
||||
|
||||
"@firebase/functions@0.4.43":
|
||||
version "0.4.43"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.4.43.tgz#4307e56b4ae9b6a5c92e5227ae351896c7a0c14d"
|
||||
integrity sha512-9cBGRr5JPcshtdUPpWuzsRIVPcWWNncK97QWBoFakVymPjvFNS3r0ZxD3hSUr9i05VrZdrqJfdljTNm8eEmJiA==
|
||||
dependencies:
|
||||
"@firebase/component" "0.1.11"
|
||||
"@firebase/functions-types" "0.3.16"
|
||||
"@firebase/messaging-types" "0.4.4"
|
||||
isomorphic-fetch "2.2.1"
|
||||
tslib "1.11.1"
|
||||
|
||||
"@firebase/installations-types@0.3.3":
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.3.3.tgz#f2e49e73afaeb7b352250365d0d90dff0b792592"
|
||||
integrity sha512-XvWhPPAGeZlc+CfCA8jTt2pv19Jovi/nUV73u30QbjBbk5xci9bp5I29aBZukHsR6YNBjFCLSkLPbno4m/bLUg==
|
||||
|
||||
"@firebase/installations@0.4.9":
|
||||
version "0.4.9"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.4.9.tgz#b346d1392919b45216b11f938a689e9cf44aa70e"
|
||||
integrity sha512-5oY3iycidoK2MhNl4GiFYn/B9rbW69VLpH54EGEFl1UruGk464WyqC7RhJxYl8bUkFwZ4gg99MXMq/JhF0vcJA==
|
||||
dependencies:
|
||||
"@firebase/component" "0.1.11"
|
||||
"@firebase/installations-types" "0.3.3"
|
||||
"@firebase/util" "0.2.46"
|
||||
idb "3.0.2"
|
||||
tslib "1.11.1"
|
||||
|
||||
"@firebase/logger@0.2.3":
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.2.3.tgz#6a0eea0c2ce0a609f2965c82ce793ce5b7b32572"
|
||||
integrity sha512-PrYcr1bWF+QpVnFxvNSZYBAzgL1WJFWIOvoLAfvRoXiinwqh1jbePN6lXbX7c8THaNUelEYIUOzDPdJ4IZ5+Sw==
|
||||
|
||||
"@firebase/messaging-types@0.4.4":
|
||||
version "0.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/messaging-types/-/messaging-types-0.4.4.tgz#bef66157bdd3ddaafd6d48f1c5ee973fdc385f84"
|
||||
integrity sha512-JGtkr+1A1Dw7+yCqQigqBfGKtq0gTCruFScBD4MVjqZHiqGIYpnQisWnpGbkzPR6aOt6iQxgwxUhHG1ulUQGeg==
|
||||
|
||||
"@firebase/messaging@0.6.15":
|
||||
version "0.6.15"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.6.15.tgz#e0f693338e4d6f10a28a94127765c70f643915cd"
|
||||
integrity sha512-WswV3JtxAgqc0LPQtIBdMWJdMhVZu7gKF6MO5ETIpNaLZZ0QayYNu5+G9btoZz218HB/gvUp2NFX43OWAsqdZw==
|
||||
dependencies:
|
||||
"@firebase/component" "0.1.11"
|
||||
"@firebase/installations" "0.4.9"
|
||||
"@firebase/messaging-types" "0.4.4"
|
||||
"@firebase/util" "0.2.46"
|
||||
idb "3.0.2"
|
||||
tslib "1.11.1"
|
||||
|
||||
"@firebase/performance-types@0.0.12":
|
||||
version "0.0.12"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.0.12.tgz#15fa79e296b502e21054a66c9e7ded59398fd8a7"
|
||||
integrity sha512-eIDF7CHetOE5sc+hCaUebEn/2Aiaju7UkgZDTl7lNQHz5fK9wJ/11HaE8WdnDr//ngS3lQAGC2RB4lAZeEWraA==
|
||||
|
||||
"@firebase/performance@0.3.3":
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.3.3.tgz#5141c8e4452777238847515a53db309cbf446a58"
|
||||
integrity sha512-YcoMnJWnlSQwi+eL1BDLWK7/sMlFoT7+TSJjN/C5loOZ3HWLATziGzevQSZkpajyXZ8nOylVhEGHABLHM0qqNA==
|
||||
dependencies:
|
||||
"@firebase/component" "0.1.11"
|
||||
"@firebase/installations" "0.4.9"
|
||||
"@firebase/logger" "0.2.3"
|
||||
"@firebase/performance-types" "0.0.12"
|
||||
"@firebase/util" "0.2.46"
|
||||
tslib "1.11.1"
|
||||
|
||||
"@firebase/polyfill@0.3.35":
|
||||
version "0.3.35"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/polyfill/-/polyfill-0.3.35.tgz#b3acca690ab5906558494bac9025ea5f41ce18d0"
|
||||
integrity sha512-O04KLyrHFXnA8Xsx+zEBlHu6iHWWhXNtOIE9WhWZO+D9onVjNEY3l7KtXvwpH/b+R1PE0Uyxy0cSGK9f5el6HQ==
|
||||
dependencies:
|
||||
core-js "3.6.5"
|
||||
promise-polyfill "8.1.3"
|
||||
whatwg-fetch "2.0.4"
|
||||
|
||||
"@firebase/remote-config-types@0.1.8":
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.1.8.tgz#0c8d8a839621230053ba55704b5d1145bfe54daa"
|
||||
integrity sha512-K12IBHO7OD4gCW0FEqZL9zMqVAfS4+joC4YIn3bHezZfu3RL+Bw1wCb0cAD7RfDPcQxWJjxOHpce4YhuqSxPFA==
|
||||
|
||||
"@firebase/remote-config@0.1.20":
|
||||
version "0.1.20"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.1.20.tgz#1193be41db9f32a5714a0fdc5f092ef05342f32a"
|
||||
integrity sha512-7ib4YhhQ/yjEhMiFsYEt4lId+9mzv5CGhGccArmgCyTNSkeImS/BqAeqcOtveyFXHSv9RDHaA4/L6066LsudRQ==
|
||||
dependencies:
|
||||
"@firebase/component" "0.1.11"
|
||||
"@firebase/installations" "0.4.9"
|
||||
"@firebase/logger" "0.2.3"
|
||||
"@firebase/remote-config-types" "0.1.8"
|
||||
"@firebase/util" "0.2.46"
|
||||
tslib "1.11.1"
|
||||
|
||||
"@firebase/storage-types@0.3.11":
|
||||
version "0.3.11"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.3.11.tgz#98f6ced5460502ab12778ce71d4dc9bf0ab7f2ee"
|
||||
integrity sha512-EMOo5aeiJIa8eQ/VqjIa/DYlDcEJX1V84FOxmLfNWZIlmCSvcqx9E9mcNlOnoUB4iePqQjTMQRtKlIBvvEVhVg==
|
||||
|
||||
"@firebase/storage@0.3.33":
|
||||
version "0.3.33"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.3.33.tgz#d531aa01a4e41e0a34c80982bd62ca5a652e31db"
|
||||
integrity sha512-pFhsy+LglBjyVAYd6LlyUTeHTXR4yV24eL+fLZCYOE3W23Ago/3RpkX+MaEP5ZSpdFmnV/H6R6qDClSFx1EEYA==
|
||||
dependencies:
|
||||
"@firebase/component" "0.1.11"
|
||||
"@firebase/storage-types" "0.3.11"
|
||||
"@firebase/util" "0.2.46"
|
||||
tslib "1.11.1"
|
||||
|
||||
"@firebase/util@0.2.46":
|
||||
version "0.2.46"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/util/-/util-0.2.46.tgz#e14e5fda81df8ed22f028d4b4a0e7d646b384331"
|
||||
integrity sha512-rKzQRc7YAbve+MECliis5ac6lRB1AZgOyZdoAbXaEtmGWUwnlM99uNhCekA963CaBkzlHwQG2inLf3WGW7nLFA==
|
||||
dependencies:
|
||||
tslib "1.11.1"
|
||||
|
||||
"@firebase/webchannel-wrapper@0.2.40":
|
||||
version "0.2.40"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.2.40.tgz#5215554d2ce1d87329241e8b08ac96e4dd1994ea"
|
||||
integrity sha512-f0jc79nQvwcwhOGFAD9b5K55Cb/a0A7LKBdRyQgVFLBGm+MuSFF5Rm/5Ll8/u72hJhbdICQj+xYl2uIuCMdXFQ==
|
||||
|
||||
"@fortawesome/fontawesome-common-types@^0.2.28":
|
||||
version "0.2.28"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.28.tgz#1091bdfe63b3f139441e9cba27aa022bff97d8b2"
|
||||
integrity sha512-gtis2/5yLdfI6n0ia0jH7NJs5i/Z/8M/ZbQL6jXQhCthEOe5Cr5NcQPhgTvFxNOtURE03/ZqUcEskdn2M+QaBg==
|
||||
|
||||
"@fortawesome/fontawesome-free@^5.13.0":
|
||||
version "5.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.13.0.tgz#fcb113d1aca4b471b709e8c9c168674fbd6e06d9"
|
||||
integrity sha512-xKOeQEl5O47GPZYIMToj6uuA2syyFlq9EMSl2ui0uytjY9xbe8XS0pexNWmxrdcCyNGyDmLyYw5FtKsalBUeOg==
|
||||
|
||||
"@fortawesome/fontawesome-svg-core@^1.2.28":
|
||||
version "1.2.28"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.28.tgz#e5b8c8814ef375f01f5d7c132d3c3a2f83a3abf9"
|
||||
integrity sha512-4LeaNHWvrneoU0i8b5RTOJHKx7E+y7jYejplR7uSVB34+mp3Veg7cbKk7NBCLiI4TyoWS1wh9ZdoyLJR8wSAdg==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "^0.2.28"
|
||||
|
||||
"@fortawesome/free-brands-svg-icons@^5.13.0":
|
||||
version "5.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.13.0.tgz#e79de73ba6555055204828dca9c0691e7ce5242b"
|
||||
integrity sha512-/6xXiJFCMEQxqxXbL0FPJpwq5Cv6MRrjsbJEmH/t5vOvB4dILDpnY0f7zZSlA8+TG7jwlt12miF/yZpZkykucA==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "^0.2.28"
|
||||
|
||||
"@fortawesome/free-regular-svg-icons@^5.13.0":
|
||||
version "5.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.13.0.tgz#925a13d8bdda0678f71551828cac80ab47b8150c"
|
||||
integrity sha512-70FAyiS5j+ANYD4dh9NGowTorNDnyvQHHpCM7FpnF7GxtDjBUCKdrFqCPzesEIpNDFNd+La3vex+jDk4nnUfpA==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "^0.2.28"
|
||||
|
||||
"@fortawesome/free-solid-svg-icons@^5.13.0":
|
||||
version "5.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.13.0.tgz#44d9118668ad96b4fd5c9434a43efc5903525739"
|
||||
integrity sha512-IHUgDJdomv6YtG4p3zl1B5wWf9ffinHIvebqQOmV3U+3SLw4fC+LUCCgwfETkbTtjy5/Qws2VoVf6z/ETQpFpg==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "^0.2.28"
|
||||
|
||||
"@fortawesome/react-fontawesome@^0.1.9":
|
||||
version "0.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.9.tgz#c865b9286c707407effcec99958043711367cd02"
|
||||
integrity sha512-49V3WNysLZU5fZ3sqSuys4nGRytsrxJktbv3vuaXkEoxv22C6T7TEG0TW6+nqVjMnkfCQd5xOnmJoZHMF78tOw==
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
|
||||
"@grpc/grpc-js@0.8.1":
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-0.8.1.tgz#3003a422577da39e7113566f2fdd4872f31e6090"
|
||||
integrity sha512-e8gSjRZnOUefsR3obOgxG9RtYW2Mw83hh7ogE2ByCdgRhoX0mdnJwBcZOami3E0l643KCTZvORFwfSEi48KFIQ==
|
||||
dependencies:
|
||||
semver "^6.2.0"
|
||||
|
||||
"@grpc/proto-loader@^0.5.0":
|
||||
version "0.5.4"
|
||||
resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.5.4.tgz#038a3820540f621eeb1b05d81fbedfb045e14de0"
|
||||
integrity sha512-HTM4QpI9B2XFkPz7pjwMyMgZchJ93TVkL3kWPW8GDMDKYxsMnmf4w2TNMJK7+KNiYHS5cJrCEAFlF+AwtXWVPA==
|
||||
dependencies:
|
||||
lodash.camelcase "^4.3.0"
|
||||
protobufjs "^6.8.6"
|
||||
|
||||
"@hapi/address@2.x.x":
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
|
||||
|
|
@ -1285,6 +1568,69 @@
|
|||
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
|
||||
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
|
||||
|
||||
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
|
||||
integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78=
|
||||
|
||||
"@protobufjs/base64@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735"
|
||||
integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
|
||||
|
||||
"@protobufjs/codegen@^2.0.4":
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb"
|
||||
integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==
|
||||
|
||||
"@protobufjs/eventemitter@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
|
||||
integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A=
|
||||
|
||||
"@protobufjs/fetch@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
|
||||
integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=
|
||||
dependencies:
|
||||
"@protobufjs/aspromise" "^1.1.1"
|
||||
"@protobufjs/inquire" "^1.1.0"
|
||||
|
||||
"@protobufjs/float@^1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
|
||||
integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=
|
||||
|
||||
"@protobufjs/inquire@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
|
||||
integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=
|
||||
|
||||
"@protobufjs/path@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
|
||||
integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=
|
||||
|
||||
"@protobufjs/pool@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
|
||||
integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=
|
||||
|
||||
"@protobufjs/utf8@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
|
||||
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
|
||||
|
||||
"@reduxjs/toolkit@^1.3.6":
|
||||
version "1.3.6"
|
||||
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.3.6.tgz#306171fce2ab7423931736c34fa82190959dcd62"
|
||||
integrity sha512-eNYURfoJa6mRNU5YtBVbmE5+nDoc4lpjZ181PBwRC6nIFYZdNR3GcoQ4uomFt8eHpXAUAdpCdxBlDsmwyXOt9Q==
|
||||
dependencies:
|
||||
immer "^6.0.1"
|
||||
redux "^4.0.0"
|
||||
redux-thunk "^2.3.0"
|
||||
reselect "^4.0.0"
|
||||
|
||||
"@samverschueren/stream-to-observable@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
|
||||
|
|
@ -1527,6 +1873,19 @@
|
|||
"@types/minimatch" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/history@*":
|
||||
version "4.7.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.5.tgz#527d20ef68571a4af02ed74350164e7a67544860"
|
||||
integrity sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw==
|
||||
|
||||
"@types/hoist-non-react-statics@^3.3.0":
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
|
||||
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
|
||||
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
|
||||
|
|
@ -1578,6 +1937,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440"
|
||||
integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==
|
||||
|
||||
"@types/long@^4.0.1":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
|
||||
integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
|
||||
|
||||
"@types/minimatch@*", "@types/minimatch@3.0.3":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||
|
|
@ -1598,6 +1962,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.38.tgz#58841a382f231ad005dbb935c36d44aa1118a26b"
|
||||
integrity sha512-75eLjX0pFuTcUXnnWmALMzzkYorjND0ezNEycaKesbUBg9eGZp4GHPuDmkRc4mQQvIpe29zrzATNRA6hkYqwmA==
|
||||
|
||||
"@types/node@^13.7.0":
|
||||
version "13.13.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.6.tgz#caa6756b64d30547a2082235531fa0dd8cba1b6e"
|
||||
integrity sha512-zqRj8ugfROCjXCNbmPBe2mmQ0fJWP9lQaN519hwunOgpHgVykme4G6FW95++dyNFDvJUk4rtExkVkL0eciu5NA==
|
||||
|
||||
"@types/parse-json@^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||
|
|
@ -1620,6 +1989,33 @@
|
|||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-redux@^7.1.8":
|
||||
version "7.1.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.8.tgz#3631feb559f7858d6ad9eea1d6ef41fa64fe7205"
|
||||
integrity sha512-kpplH7Wg2SYU00sZVT98WBN0ou6QKrYcShRaW+5Vpe5l7bluKWJbWmAL+ieiso07OQzpcP5i1PeY3690640ZWg==
|
||||
dependencies:
|
||||
"@types/hoist-non-react-statics" "^3.3.0"
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
redux "^4.0.0"
|
||||
|
||||
"@types/react-router-dom@^5.1.5":
|
||||
version "5.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.5.tgz#7c334a2ea785dbad2b2dcdd83d2cf3d9973da090"
|
||||
integrity sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw==
|
||||
dependencies:
|
||||
"@types/history" "*"
|
||||
"@types/react" "*"
|
||||
"@types/react-router" "*"
|
||||
|
||||
"@types/react-router@*":
|
||||
version "5.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.7.tgz#e9d12ed7dcfc79187e4d36667745b69a5aa11556"
|
||||
integrity sha512-2ouP76VQafKjtuc0ShpwUebhHwJo0G6rhahW9Pb8au3tQTjYXd2jta4wv6U2tGLR/I42yuG00+UXjNYY0dTzbg==
|
||||
dependencies:
|
||||
"@types/history" "*"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@^16.9.0":
|
||||
version "16.9.35"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.35.tgz#a0830d172e8aadd9bd41709ba2281a3124bbd368"
|
||||
|
|
@ -3308,6 +3704,11 @@ core-js-pure@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a"
|
||||
integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw==
|
||||
|
||||
core-js@3.6.5:
|
||||
version "3.6.5"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a"
|
||||
integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==
|
||||
|
||||
core-js@^2.4.0:
|
||||
version "2.6.11"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c"
|
||||
|
|
@ -3944,6 +4345,11 @@ dom-serializer@0:
|
|||
domelementtype "^2.0.1"
|
||||
entities "^2.0.0"
|
||||
|
||||
dom-storage@2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/dom-storage/-/dom-storage-2.1.0.tgz#00fb868bc9201357ea243c7bcfd3304c1e34ea39"
|
||||
integrity sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q==
|
||||
|
||||
domain-browser@^1.1.1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
|
||||
|
|
@ -4090,6 +4496,13 @@ encodeurl@~1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
|
||||
|
||||
encoding@^0.1.11:
|
||||
version "0.1.12"
|
||||
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
|
||||
integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=
|
||||
dependencies:
|
||||
iconv-lite "~0.4.13"
|
||||
|
||||
end-of-stream@^1.0.0, end-of-stream@^1.1.0:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
|
||||
|
|
@ -4643,6 +5056,13 @@ fast-levenshtein@~2.0.6:
|
|||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
||||
|
||||
faye-websocket@0.11.3, faye-websocket@~0.11.1:
|
||||
version "0.11.3"
|
||||
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e"
|
||||
integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==
|
||||
dependencies:
|
||||
websocket-driver ">=0.5.1"
|
||||
|
||||
faye-websocket@^0.10.0:
|
||||
version "0.10.0"
|
||||
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4"
|
||||
|
|
@ -4650,13 +5070,6 @@ faye-websocket@^0.10.0:
|
|||
dependencies:
|
||||
websocket-driver ">=0.5.1"
|
||||
|
||||
faye-websocket@~0.11.1:
|
||||
version "0.11.3"
|
||||
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e"
|
||||
integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==
|
||||
dependencies:
|
||||
websocket-driver ">=0.5.1"
|
||||
|
||||
fb-watchman@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85"
|
||||
|
|
@ -4810,6 +5223,26 @@ find-up@^3.0.0:
|
|||
dependencies:
|
||||
locate-path "^3.0.0"
|
||||
|
||||
firebase@^7.14.3:
|
||||
version "7.14.3"
|
||||
resolved "https://registry.yarnpkg.com/firebase/-/firebase-7.14.3.tgz#dfe6fa3e5982a6d6d6d44bfc50dba23568a5a777"
|
||||
integrity sha512-qExwE/vhf/l6Mf8ES3IFX1SB6/DysKPtvrIWnIuswmRMeSA9eERwrqp4Pom4NHhzjBP1jOmlIPKeOplsNwMlOQ==
|
||||
dependencies:
|
||||
"@firebase/analytics" "0.3.4"
|
||||
"@firebase/app" "0.6.3"
|
||||
"@firebase/app-types" "0.6.0"
|
||||
"@firebase/auth" "0.14.5"
|
||||
"@firebase/database" "0.6.2"
|
||||
"@firebase/firestore" "1.14.3"
|
||||
"@firebase/functions" "0.4.43"
|
||||
"@firebase/installations" "0.4.9"
|
||||
"@firebase/messaging" "0.6.15"
|
||||
"@firebase/performance" "0.3.3"
|
||||
"@firebase/polyfill" "0.3.35"
|
||||
"@firebase/remote-config" "0.1.20"
|
||||
"@firebase/storage" "0.3.33"
|
||||
"@firebase/util" "0.2.46"
|
||||
|
||||
flat-cache@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"
|
||||
|
|
@ -5250,6 +5683,18 @@ hex-color-regex@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
||||
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
|
||||
|
||||
history@^4.9.0:
|
||||
version "4.10.1"
|
||||
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
|
||||
integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
loose-envify "^1.2.0"
|
||||
resolve-pathname "^3.0.0"
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
value-equal "^1.0.1"
|
||||
|
||||
hmac-drbg@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
||||
|
|
@ -5259,6 +5704,13 @@ hmac-drbg@^1.0.0:
|
|||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||
dependencies:
|
||||
react-is "^16.7.0"
|
||||
|
||||
hosted-git-info@^2.1.4:
|
||||
version "2.8.8"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
|
||||
|
|
@ -5418,7 +5870,7 @@ https-browserify@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
||||
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
|
||||
|
||||
iconv-lite@0.4.24, iconv-lite@^0.4.24:
|
||||
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||
|
|
@ -5432,6 +5884,11 @@ icss-utils@^4.0.0, icss-utils@^4.1.1:
|
|||
dependencies:
|
||||
postcss "^7.0.14"
|
||||
|
||||
idb@3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/idb/-/idb-3.0.2.tgz#c8e9122d5ddd40f13b60ae665e4862f8b13fa384"
|
||||
integrity sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw==
|
||||
|
||||
identity-obj-proxy@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14"
|
||||
|
|
@ -5464,6 +5921,16 @@ immer@1.10.0:
|
|||
resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d"
|
||||
integrity sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg==
|
||||
|
||||
immer@5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/immer/-/immer-5.0.0.tgz#07f462b7d95f7e86c214a861ecacd766d42b8c0a"
|
||||
integrity sha512-G7gRqKbi9NE025XVyqyTV98dxUOtdKvu/P1QRaVZfA55aEcXgjbxPdm+TlWdcSMNPKijlaHNz61DGPyelouRlA==
|
||||
|
||||
immer@^6.0.1:
|
||||
version "6.0.5"
|
||||
resolved "https://registry.yarnpkg.com/immer/-/immer-6.0.5.tgz#77187d13b71c6cee40dde3b8e87a50a7a636d630"
|
||||
integrity sha512-Q2wd90qrgFieIpLzAO2q9NLEdmyp/sr76Ml4Vm5peUKgyTa2CQa3ey8zuzwSKOlKH7grCeGBGUcLLVCVW1aguA==
|
||||
|
||||
import-cwd@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
|
||||
|
|
@ -5921,7 +6388,7 @@ is-root@2.1.0:
|
|||
resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c"
|
||||
integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==
|
||||
|
||||
is-stream@^1.1.0:
|
||||
is-stream@^1.0.1, is-stream@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
|
||||
|
|
@ -5965,6 +6432,11 @@ is-wsl@^2.1.1:
|
|||
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d"
|
||||
integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
|
||||
|
||||
isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
|
|
@ -5987,6 +6459,14 @@ isobject@^3.0.0, isobject@^3.0.1:
|
|||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
|
||||
|
||||
isomorphic-fetch@2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
|
||||
integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
|
||||
dependencies:
|
||||
node-fetch "^1.0.1"
|
||||
whatwg-fetch ">=0.10.0"
|
||||
|
||||
isstream@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
|
|
@ -6826,6 +7306,11 @@ lodash._reinterpolate@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
||||
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
|
||||
|
||||
lodash.camelcase@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||
integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
|
||||
|
||||
lodash.memoize@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
|
|
@ -6894,7 +7379,12 @@ loglevel@^1.6.6:
|
|||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.7.tgz#b3e034233188c68b889f5b862415306f565e2c56"
|
||||
integrity sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==
|
||||
|
||||
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||
long@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
|
||||
integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
|
||||
|
||||
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||
|
|
@ -7103,6 +7593,14 @@ min-indent@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.0.tgz#cfc45c37e9ec0d8f0a0ec3dd4ef7f7c3abe39256"
|
||||
integrity sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=
|
||||
|
||||
mini-create-react-context@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz#df60501c83151db69e28eac0ef08b4002efab040"
|
||||
integrity sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.5.5"
|
||||
tiny-warning "^1.0.3"
|
||||
|
||||
mini-css-extract-plugin@0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e"
|
||||
|
|
@ -7226,6 +7724,11 @@ moment@2.24.0:
|
|||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
|
||||
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
|
||||
|
||||
moment@^2.25.3:
|
||||
version "2.25.3"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.25.3.tgz#252ff41319cf41e47761a1a88cab30edfe9808c0"
|
||||
integrity sha512-PuYv0PHxZvzc15Sp8ybUCoQ+xpyPWvjOuK72a5ovzp2LI32rJXOiIfyoFoYvG3s6EwwrdkMyWuRiEHSZRLJNdg==
|
||||
|
||||
move-concurrently@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
|
||||
|
|
@ -7326,6 +7829,14 @@ no-case@^3.0.3:
|
|||
lower-case "^2.0.1"
|
||||
tslib "^1.10.0"
|
||||
|
||||
node-fetch@^1.0.1:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
|
||||
integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==
|
||||
dependencies:
|
||||
encoding "^0.1.11"
|
||||
is-stream "^1.0.1"
|
||||
|
||||
node-forge@0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579"
|
||||
|
|
@ -7916,6 +8427,13 @@ path-to-regexp@0.1.7:
|
|||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
|
||||
|
||||
path-to-regexp@^1.7.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
|
||||
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
|
||||
dependencies:
|
||||
isarray "0.0.1"
|
||||
|
||||
path-type@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
|
||||
|
|
@ -8785,6 +9303,11 @@ promise-inflight@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
|
||||
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
|
||||
|
||||
promise-polyfill@8.1.3:
|
||||
version "8.1.3"
|
||||
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.1.3.tgz#8c99b3cf53f3a91c68226ffde7bde81d7f904116"
|
||||
integrity sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==
|
||||
|
||||
promise@^8.0.3:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e"
|
||||
|
|
@ -8809,6 +9332,25 @@ prop-types@^15.6.2, prop-types@^15.7.2:
|
|||
object-assign "^4.1.1"
|
||||
react-is "^16.8.1"
|
||||
|
||||
protobufjs@^6.8.6:
|
||||
version "6.9.0"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.9.0.tgz#c08b2bf636682598e6fabbf0edb0b1256ff090bd"
|
||||
integrity sha512-LlGVfEWDXoI/STstRDdZZKb/qusoAWUnmLg9R8OLSO473mBLWHowx8clbX5/+mKDEI+v7GzjoK9tRPZMMcoTrg==
|
||||
dependencies:
|
||||
"@protobufjs/aspromise" "^1.1.2"
|
||||
"@protobufjs/base64" "^1.1.2"
|
||||
"@protobufjs/codegen" "^2.0.4"
|
||||
"@protobufjs/eventemitter" "^1.1.0"
|
||||
"@protobufjs/fetch" "^1.1.0"
|
||||
"@protobufjs/float" "^1.0.2"
|
||||
"@protobufjs/inquire" "^1.1.0"
|
||||
"@protobufjs/path" "^1.1.2"
|
||||
"@protobufjs/pool" "^1.1.0"
|
||||
"@protobufjs/utf8" "^1.1.0"
|
||||
"@types/long" "^4.0.1"
|
||||
"@types/node" "^13.7.0"
|
||||
long "^4.0.0"
|
||||
|
||||
proxy-addr@~2.0.5:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
|
||||
|
|
@ -9016,11 +9558,67 @@ react-error-overlay@^6.0.7:
|
|||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
|
||||
integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==
|
||||
|
||||
react-is@^16.12.0, react-is@^16.8.1, react-is@^16.8.4:
|
||||
react-google-button@^0.7.1:
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/react-google-button/-/react-google-button-0.7.1.tgz#e84b4ce270a66e345489dc86e47235e877fcc81a"
|
||||
integrity sha512-FN8/9Va6oAGKL561yddNzcjz0iGlt2GZFwiDPczEu0fDVnR21/nBrPs7Y5x97V1S4//nvtkvv6ohfwtxIHpbfg==
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.9.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-redux-firebase@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/react-redux-firebase/-/react-redux-firebase-3.4.0.tgz#acf317a3c3912ae2eb5a25aa252087ac5ca6abe2"
|
||||
integrity sha512-wT+39Qsb7D5CIVzaPHxbSq9ti4FkdDpXp79ZpESS1cq8NQOywkFnybi1PVUD3Owj9yz1SeiclxIXPejqSZ9SGw==
|
||||
dependencies:
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
lodash "^4.17.15"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-redux@^7.2.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d"
|
||||
integrity sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.5.5"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.7.2"
|
||||
react-is "^16.9.0"
|
||||
|
||||
react-router-dom@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662"
|
||||
integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
history "^4.9.0"
|
||||
loose-envify "^1.3.1"
|
||||
prop-types "^15.6.2"
|
||||
react-router "5.2.0"
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-router@5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293"
|
||||
integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
history "^4.9.0"
|
||||
hoist-non-react-statics "^3.1.0"
|
||||
loose-envify "^1.3.1"
|
||||
mini-create-react-context "^0.4.0"
|
||||
path-to-regexp "^1.7.0"
|
||||
prop-types "^15.6.2"
|
||||
react-is "^16.6.0"
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-scripts@3.4.1:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.4.1.tgz#f551298b5c71985cc491b9acf3c8e8c0ae3ada0a"
|
||||
|
|
@ -9184,6 +9782,33 @@ redent@^3.0.0:
|
|||
indent-string "^4.0.0"
|
||||
strip-indent "^3.0.0"
|
||||
|
||||
reduce-reducers@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/reduce-reducers/-/reduce-reducers-1.0.4.tgz#fb77e751a9eb0201760ac5a605ca8c9c2d0537f8"
|
||||
integrity sha512-Mb2WZ2bJF597exiqX7owBzrqJ74DHLK3yOQjCyPAaNifRncE8OD0wFIuoMhXxTnHK07+8zZ2SJEKy/qtiyR7vw==
|
||||
|
||||
redux-firestore@^0.13.0:
|
||||
version "0.13.0"
|
||||
resolved "https://registry.yarnpkg.com/redux-firestore/-/redux-firestore-0.13.0.tgz#154e705726740c6173e2f8a1d82214aeccc6954f"
|
||||
integrity sha512-0Vf/MXrH6gUqVwSmAQAz9Ic1J4uADCaF29DcpcNU2ecZoYG5aQSZO2iGXUqzjAOeatE1Ksz8SDEvKu8gBRvYXg==
|
||||
dependencies:
|
||||
immer "5.0.0"
|
||||
lodash "^4.17.15"
|
||||
reduce-reducers "^1.0.4"
|
||||
|
||||
redux-thunk@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
|
||||
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
|
||||
|
||||
redux@^4.0.0:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
|
||||
integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
|
||||
dependencies:
|
||||
loose-envify "^1.4.0"
|
||||
symbol-observable "^1.2.0"
|
||||
|
||||
regenerate-unicode-properties@^8.2.0:
|
||||
version "8.2.0"
|
||||
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
|
||||
|
|
@ -9369,6 +9994,11 @@ requires-port@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
||||
|
||||
reselect@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
|
||||
integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
|
||||
|
||||
resolve-cwd@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
|
||||
|
|
@ -9386,6 +10016,11 @@ resolve-from@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
||||
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
|
||||
|
||||
resolve-pathname@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd"
|
||||
integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==
|
||||
|
||||
resolve-url-loader@3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.1.tgz#28931895fa1eab9be0647d3b2958c100ae3c0bf0"
|
||||
|
|
@ -10324,7 +10959,7 @@ svgo@^1.0.0, svgo@^1.2.2:
|
|||
unquote "~1.1.1"
|
||||
util.promisify "~1.0.0"
|
||||
|
||||
symbol-observable@^1.1.0:
|
||||
symbol-observable@^1.1.0, symbol-observable@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
|
||||
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
|
||||
|
|
@ -10443,6 +11078,16 @@ timsort@^0.3.0:
|
|||
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
|
||||
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
|
||||
|
||||
tiny-invariant@^1.0.2:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
|
||||
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
|
||||
|
||||
tiny-warning@^1.0.0, tiny-warning@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
|
||||
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
|
||||
|
||||
tmp@0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877"
|
||||
|
|
@ -10529,7 +11174,7 @@ ts-pnp@1.1.6, ts-pnp@^1.1.6:
|
|||
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.6.tgz#389a24396d425a0d3162e96d2b4638900fdc289a"
|
||||
integrity sha512-CrG5GqAAzMT7144Cl+UIFP7mz/iIhiy+xQ6GGcnjTezhALT02uPMRw7tgDSESgB5MsfKt55+GPWw4ir1kVtMIQ==
|
||||
|
||||
tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0:
|
||||
tslib@1.11.1, tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0:
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
|
||||
integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==
|
||||
|
|
@ -10598,10 +11243,10 @@ typedarray@^0.0.6:
|
|||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
typescript@~3.7.2:
|
||||
version "3.7.5"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae"
|
||||
integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==
|
||||
typescript@^3.9.2:
|
||||
version "3.9.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.2.tgz#64e9c8e9be6ea583c54607677dd4680a1cf35db9"
|
||||
integrity sha512-q2ktq4n/uLuNNShyayit+DTobV2ApPEo/6so68JaD5ojvc/6GClBipedB9zNWYxRSAlZXAe405Rlijzl6qDiSw==
|
||||
|
||||
unicode-canonical-property-names-ecmascript@^1.0.4:
|
||||
version "1.0.4"
|
||||
|
|
@ -10800,6 +11445,11 @@ validate-npm-package-license@^3.0.1:
|
|||
spdx-correct "^3.0.0"
|
||||
spdx-expression-parse "^3.0.0"
|
||||
|
||||
value-equal@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"
|
||||
integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==
|
||||
|
||||
vary@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||
|
|
@ -10999,7 +11649,12 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
|
|||
dependencies:
|
||||
iconv-lite "0.4.24"
|
||||
|
||||
whatwg-fetch@^3.0.0:
|
||||
whatwg-fetch@2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
|
||||
integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==
|
||||
|
||||
whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
|
||||
integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==
|
||||
|
|
@ -11270,6 +11925,11 @@ xmlchars@^2.1.1:
|
|||
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
||||
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
|
||||
|
||||
xmlhttprequest@1.8.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"
|
||||
integrity sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=
|
||||
|
||||
xregexp@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50"
|
||||
|
|
|
|||
Loading…
Reference in a new issue