diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b079b0d..d5bbf6d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -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 }} diff --git a/.gitignore b/.gitignore index 16901e6..f73c45d 100644 --- a/.gitignore +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/README.md b/README.md index 879fabb..3f8efd1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ # DevBook +![Deploy](https://github.com/rjNemo/devbook_ts/workflows/Deploy/badge.svg?branch=master) + 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 diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json deleted file mode 100644 index da18d93..0000000 --- a/cypress/fixtures/example.json +++ /dev/null @@ -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" -} \ No newline at end of file diff --git a/cypress/integration/landing.spec.js b/cypress/integration/landing.spec.js new file mode 100644 index 0000000..884a2b4 --- /dev/null +++ b/cypress/integration/landing.spec.js @@ -0,0 +1,6 @@ +describe('Landing page', () => { + it('contains app name', () => { + cy.visit('/'); + cy.get('h1').contains('DevBook'); + }); +}); diff --git a/cypress/integration/layout.spec.js b/cypress/integration/layout.spec.js new file mode 100644 index 0000000..28e6d62 --- /dev/null +++ b/cypress/integration/layout.spec.js @@ -0,0 +1,6 @@ +describe('App Layout', () => { + it('contains a navbar', () => { + cy.visit('/'); + cy.get('nav'); + }); +}); diff --git a/cypress/integration/smoke.spec.js b/cypress/integration/smoke.spec.js deleted file mode 100644 index ebf3e7d..0000000 --- a/cypress/integration/smoke.spec.js +++ /dev/null @@ -1,6 +0,0 @@ -describe('smoke', () => { - it('sees learn', () => { - cy.visit('/'); - cy.get('a').contains('Learn'); - }); -}); diff --git a/firestore.indexes.json b/firestore.indexes.json new file mode 100644 index 0000000..0b113bd --- /dev/null +++ b/firestore.indexes.json @@ -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": [] +} diff --git a/package.json b/package.json index 52c8b59..8b26dce 100644 --- a/package.json +++ b/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" ] } -} \ No newline at end of file +} diff --git a/public/index.html b/public/index.html index aa069f2..112696b 100644 --- a/public/index.html +++ b/public/index.html @@ -4,40 +4,18 @@ - + - - - React App + DevBook
- diff --git a/public/manifest.json b/public/manifest.json index 080d6c7..82dcda3 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -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" +} \ No newline at end of file diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 74b5e05..0000000 --- a/src/App.css +++ /dev/null @@ -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); - } -} diff --git a/src/App.test.tsx b/src/App.test.tsx index 4db7ebc..f7eac70 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -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(); - const linkElement = getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); +test('to pass ci', () => {}); diff --git a/src/App.tsx b/src/App.tsx index a53698a..1b03e2d 100644 --- a/src/App.tsx +++ b/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 ( -
-
- logo -

- Edit src/App.tsx and save to reload. -

- - Learn React - -
-
+ + + + + + + ); -} +}; + +/** + * Display a loading screen while fetching authentication state + */ +const AuthApp: FC = () => { + const auth = useSelector(selectAuthState); + if (!isLoaded(auth)) { + // TODO: insert Splash Screen here + return
Loading...
; + } + return ( + <> + + + + ); +}; export default App; diff --git a/src/components/Alert.tsx b/src/components/Alert.tsx new file mode 100644 index 0000000..e00238c --- /dev/null +++ b/src/components/Alert.tsx @@ -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 = ({text, color = 'danger'}) => ( +
+ {text} +
+); + +export default Alert; diff --git a/src/components/DevProfile.tsx b/src/components/DevProfile.tsx new file mode 100644 index 0000000..44c65d6 --- /dev/null +++ b/src/components/DevProfile.tsx @@ -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 = ({ + id, + displayName, + picture, + description, + location, + skills, +}) => ( +
+ {displayName} +
+

{displayName}

+

{description}

+

{location}

+ + View Profile + +
+
    + {skills.map((s, i) => ( +
  • + {s} +
  • + ))} +
+
+); + +export default DevProfile; diff --git a/src/components/FormHeader.tsx b/src/components/FormHeader.tsx new file mode 100644 index 0000000..2a671e8 --- /dev/null +++ b/src/components/FormHeader.tsx @@ -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 = props => ( + <> +
+ * marks required fields + +); + +export default FormHeader; diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..04400fb --- /dev/null +++ b/src/components/Header.tsx @@ -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 = ({title, lead, icon = 'faUser'}) => { + const RenderIcon = (icon: string) => { + if (icon === 'faUser') { + return ; + } + if (icon === 'connectdevelop') { + return ; + } + if (icon === 'code-branch') { + return ; + } + if (icon === 'code') { + return null; + } + if (icon === 'graduation-cap') { + return ; + } + if (icon === 'not-found') { + return ; + } + }; + + return ( + <> +

{title}

+

+ {RenderIcon(icon)} {lead} +

+ + ); +}; + +export default Header; diff --git a/src/components/NavBar.tsx b/src/components/NavBar.tsx new file mode 100644 index 0000000..e2eddc5 --- /dev/null +++ b/src/components/NavBar.tsx @@ -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 { + isEmpty: boolean; + isLoaded: boolean; +} + +/** + * Main Navbar serves navigation Routes. + */ +const NavBar: FC = ({firebase, isEmpty, isLoaded, isActive}) => { + const publicLinks = ( +
    +
  • + + Developers + +
  • +
  • + + Register + +
  • +
  • + + Login + +
  • +
+ ); + + const privateLinks = ( +
    +
  • + + Developers + +
  • +
  • + + Posts + +
  • +
  • + + + Dashboard + +
  • +
  • + firebase.logout()} + > + + Log out + +
  • +
+ ); + + /** Display appropriated links after loading given authenticated prop */ + const RenderLinks = + isLoaded && !isEmpty && isActive ? privateLinks : publicLinks; + + return ( + + ); +}; + +/** connect HOC subscribes to the store */ +export default enhance(NavBar); diff --git a/src/constants/routes.ts b/src/constants/routes.ts new file mode 100644 index 0000000..1908343 --- /dev/null +++ b/src/constants/routes.ts @@ -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; diff --git a/src/constants/statuses.ts b/src/constants/statuses.ts new file mode 100644 index 0000000..0e57d3a --- /dev/null +++ b/src/constants/statuses.ts @@ -0,0 +1,12 @@ +const Statuses: string[] = [ + 'Developer', + 'Junior Developer', + 'Senior Developer', + 'Manager', + 'Student or Learning', + 'Instructor or Teacher', + 'Intern', + 'Other', +]; + +export default Statuses; diff --git a/src/hooks/index.ts b/src/hooks/index.ts new file mode 100644 index 0000000..f7cd256 --- /dev/null +++ b/src/hooks/index.ts @@ -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 = (initFormData: T) => { + const [formData, setFormData] = useState(initFormData); + + /** update each input state value onChange */ + const handleChange = ( + e: ChangeEvent, + ): 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): void => + setFormData({...formData, [e.target.name]: e.target.checked}); + + return {formData, handleChange, handleCheckboxesChange, resetForm}; +}; + +export default useForm; diff --git a/src/index.css b/src/index.css deleted file mode 100644 index ec2585e..0000000 --- a/src/index.css +++ /dev/null @@ -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; -} diff --git a/src/index.tsx b/src/index.tsx index f5185c1..2907924 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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( , - 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(); diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 6b60c10..0000000 --- a/src/logo.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/models/Dev.ts b/src/models/Dev.ts new file mode 100644 index 0000000..759112c --- /dev/null +++ b/src/models/Dev.ts @@ -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; diff --git a/src/models/Post.ts b/src/models/Post.ts new file mode 100644 index 0000000..ffa7d71 --- /dev/null +++ b/src/models/Post.ts @@ -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; diff --git a/src/models/User.ts b/src/models/User.ts new file mode 100644 index 0000000..58ddd81 --- /dev/null +++ b/src/models/User.ts @@ -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; diff --git a/src/pages/AddEducation.tsx b/src/pages/AddEducation.tsx new file mode 100644 index 0000000..bdfb2c8 --- /dev/null +++ b/src/pages/AddEducation.tsx @@ -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 {} +/** + * Form to add an Education step to Profile + */ +const AddEducation: FC = ({firebase, educations}) => { + const [alert, setAlert] = useState(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): 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 ( +
+ + +
+
+ +
+
+ +
+
+ +
+
+

From Date

+ +
+
+

To Date

+ +
+
+

+ {' '} + Current School +

+
+
+ +
+ {alert.show && } + + + Go Back + + +
+ ); +}; + +export default enhance(AddEducation); diff --git a/src/pages/AddExperience.tsx b/src/pages/AddExperience.tsx new file mode 100644 index 0000000..7382cab --- /dev/null +++ b/src/pages/AddExperience.tsx @@ -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 {} + +/** + * Form to add an Experience step to Profile + */ +const AddExperience: FC = ({firebase, experiences}) => { + const [alert, setAlert] = useState(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): 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 ( +
+ + +
+
+ +
+
+ +
+
+ +
+
+

From Date

+ +
+
+

To Date

+ +
+
+

+ {' '} + Current Job +

+
+
+ +
+ {alert.show && } + + + Go Back + + +
+ ); +}; + +export default enhance(AddExperience); diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx new file mode 100644 index 0000000..ddbf17b --- /dev/null +++ b/src/pages/Dashboard.tsx @@ -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 {} +/** + * Main page from which a Dev can peek and edit its own profile. + */ +const Dashboard: FC = ({ + 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, + ) => { + 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, + ) => { + firebase.updateProfile({ + experiences: entries.filter((e: Experience) => e.id !== id), + }); + }; + + return ( +
+
+
+ + Edit Profile + + + Add Experience + + + Add Education + +
+ +

Experience Credentials

+ + + + + + + + + + + {experiences?.map((exp: Experience) => ( + + + + + + + ))} + +
CompanyTitleYears
{exp.company}{exp.position}{getTimePeriod(exp.from, exp.to)} + +
+ +

Education Credentials

+ + + + + + + + + + + {educations?.map((edu: Education, i: number) => ( + + + + + + + ))} + +
SchoolDegreeYears
{edu.school}{edu.degree}{getTimePeriod(edu.from, edu.to)} + +
+
+ +
+
+ ); +}; + +export default enhance(Dashboard); diff --git a/src/pages/Developers.tsx b/src/pages/Developers.tsx new file mode 100644 index 0000000..154ecf3 --- /dev/null +++ b/src/pages/Developers.tsx @@ -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 = (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 ( +
+
+
+ {developers.map(dev => ( + // use spread operator to pass props + + ))} +
+
+ ); +}; + +export default Developers; diff --git a/src/pages/EditProfile.tsx b/src/pages/EditProfile.tsx new file mode 100644 index 0000000..a8e5142 --- /dev/null +++ b/src/pages/EditProfile.tsx @@ -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 {} + +/** + * Form to update dev's personal information. + */ +const EditProfile: FC = ({ + firebase, + status, + skills, + company, + links, + location, + bio, +}) => { + const [showLinks, setShowLinks] = useState(false); + const [alert, setAlert] = useState(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(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): 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 ( +
+ + +
+
+ + + Give us an idea of where you are at in your career + +
+
+ + + Could be your own company or one you work for + +
+
+ + + Could be your own or a company website + +
+
+ + + City & state suggested (eg. Boston, MA) + +
+
+ + + Please use comma separated values (eg. HTML,CSS,JavaScript,PHP) + +
+
+ + + If you want your latest repos and a Github link, include your + username + +
+
+ + Tell us a little about yourself +
+ +
+ + Optional +
+ + {showLinks && ( + <> +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + )} + {alert.show && } + + + Go Back + + +
+ ); +}; + +export default enhance(EditProfile); diff --git a/src/pages/Landing.tsx b/src/pages/Landing.tsx new file mode 100644 index 0000000..e02a2e4 --- /dev/null +++ b/src/pages/Landing.tsx @@ -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 = () => ( +
+
+
+
+
+ + Sign up + + + Login + +
+
+
+
+); + +export default Landing; diff --git a/src/pages/NotFound.tsx b/src/pages/NotFound.tsx new file mode 100644 index 0000000..c4383bb --- /dev/null +++ b/src/pages/NotFound.tsx @@ -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 = () => ( +
+
+
+
+
+ + Sign up + + + Login + +
+
+
+
+); + +export default NotFound; diff --git a/src/pages/Post.tsx b/src/pages/Post.tsx new file mode 100644 index 0000000..8ddf23b --- /dev/null +++ b/src/pages/Post.tsx @@ -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 = () => ( +
+ + Back To Posts + + +
+ +
+

{post.text}

+
+
+ +
+
+

Leave A Comment

+
+
+ + +
+
+ +
+ {post.comments.map((c: Comment, i: number) => ( +
+ +
+

{c.text}

+
+
+ ))} +
+
+); + +export default PostPage; diff --git a/src/pages/Posts.tsx b/src/pages/Posts.tsx new file mode 100644 index 0000000..e26d47f --- /dev/null +++ b/src/pages/Posts.tsx @@ -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 ( +
+
+
+
+

Say Something

+
+ +
+ + +
+ {posts.map((post: Post) => ( +
+ +
+

{post.text}

+ + + + Discussion + +
+
+ ))} +
+
+
+
+ ); +}; + +export default Posts; diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx new file mode 100644 index 0000000..d9093f0 --- /dev/null +++ b/src/pages/Profile.tsx @@ -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 = () => { + /** 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 ( +
+ + Back to profiles + + +
+
+ Some guy +

{dev.displayName}

+

{dev.description}

+

{dev.location}

+
+ {Object.entries(dev.links).map(([icon, webAddress], i: number) => ( + + + + ))} +
+
+ +
+

{`${dev.displayName}'s Bio`}

+

{dev.bio}

+
+

Skill Set

+
+ {dev.skills.map((s: string, i: number) => ( +
+ {s} +
+ ))} +
+
+ +
+

Experiences

+ {dev.experiences.map((exp: Experience, i: number) => ( +
+

{exp.company}

+

{getTimePeriod(exp.from, exp.to)}

+

+ Position: + {exp.position} +

+

+ Description: + {exp.description} +

+
+ ))} +
+ +
+

Education

+ {dev.educations.map((edu: Education, i: number) => ( +
+

{edu.school}

+

{getTimePeriod(edu.from, edu.to)}

+

+ Degree: + {edu.degree} +

+

+ Field: + {edu.field} +

+

+ Description: + {edu.description} +

+
+ ))} +
+ +
+

+ GitHub Repos +

+ + {dev.repos.map((r: Repo, i: number) => ( +
+
+

+ {r.name} +

+

{r.description}

+
+
+
    +
  • + Stars: 42 +
  • +
  • + Watchers: 2 +
  • +
  • + Forks: 4 +
  • +
+
+
+ ))} +
+
+
+ ); +}; + +export default Profile; diff --git a/src/pages/SignIn.tsx b/src/pages/SignIn.tsx new file mode 100644 index 0000000..02bf606 --- /dev/null +++ b/src/pages/SignIn.tsx @@ -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 { + isEmpty: boolean; + isLoaded: boolean; +} + +/** + * Sign in form + */ +const SignIn: FC = ({firebase, isEmpty, isLoaded, isActive}) => { + const [error, setError] = useState(null); + + // handle form data + const initFormData: InitFormData = { + email: '', + password: '', + }; + const {formData, handleChange, resetForm} = useForm( + initFormData, + ); + const {email, password} = formData; + + // prevent submitting invalid forms + const isDisabled: boolean = email === '' || password === ''; + + /** create user with password */ + const handleSubmit = (e: React.FormEvent) => { + 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 ; + } + + return ( +
+ {error && } +
+ +
+
+ +
+
+ +
+ + +
+

+ Don't have an account? Sign up +

+
+ ); +}; + +/** subscribe to store and firebase */ +export default enhance(SignIn); diff --git a/src/pages/SignUp.tsx b/src/pages/SignUp.tsx new file mode 100644 index 0000000..c5d6ea3 --- /dev/null +++ b/src/pages/SignUp.tsx @@ -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 { + 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 = ({firebase, isEmpty, isLoaded, isActive}) => { + const [error, setError] = useState(null); + + // handle form data + const initFormData: InitFormData = { + name: '', + email: '', + password: '', + password2: '', + }; + + const {formData, handleChange, resetForm} = useForm( + 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) => { + 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 ; + } + + return ( +
+ {error && } +
+ +
+
+ +
+ +
+ + + This site uses Gravatar, so use a Gravatar email. + +
+ +
+ +
+ +
+ +
+ + +
+

+ Already have an account? Sign in +

+
+ ); +}; + +/** subscribe to store and firebase */ +export default enhance(SignUp); diff --git a/src/pages/__tests__/SignUp.spec.tsx b/src/pages/__tests__/SignUp.spec.tsx new file mode 100644 index 0000000..62ebaea --- /dev/null +++ b/src/pages/__tests__/SignUp.spec.tsx @@ -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', () => {}); +}); diff --git a/src/router/PrivateRoute.tsx b/src/router/PrivateRoute.tsx new file mode 100644 index 0000000..546fa06 --- /dev/null +++ b/src/router/PrivateRoute.tsx @@ -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; +} +/** + * Redirects to the login screen if you're not authenticated yet or + * if auth is not loaded yet + */ +const PrivateRoute: FC = ({ + 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 ( + + isLoaded(auth) && !isEmpty(auth) && isActive ? ( + + ) : ( + + ) + } + /> + ); +}; + +/** subscribe to store and firebase */ +export default PrivateRoute; diff --git a/src/router/Router.tsx b/src/router/Router.tsx new file mode 100644 index 0000000..2d65705 --- /dev/null +++ b/src/router/Router.tsx @@ -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 = () => ( + + + + + + + + + + + + + + +); + +export default Router; diff --git a/src/services/firebase/index.ts b/src/services/firebase/index.ts new file mode 100644 index 0000000..24fec7c --- /dev/null +++ b/src/services/firebase/index.ts @@ -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; diff --git a/src/static/css/style.min.css b/src/static/css/style.min.css new file mode 100644 index 0000000..9847217 --- /dev/null +++ b/src/static/css/style.min.css @@ -0,0 +1 @@ +.m-1{margin:1rem}.my-1{margin:1rem 0}.p-1{padding:1rem}.py-1{padding:1rem 0}.m-2{margin:2rem}.my-2{margin:2rem 0}.p-2{padding:2rem}.py-2{padding:2rem 0}.m-3{margin:3rem}.my-3{margin:3rem 0}.p-3{padding:3rem}.py-3{padding:3rem 0}.m-4{margin:4rem}.my-4{margin:4rem 0}.p-4{padding:4rem}.py-4{padding:4rem 0}.m-5{margin:5rem}.my-5{margin:5rem 0}.p-5{padding:5rem}.py-5{padding:5rem 0}.m-1{margin:1rem}.my-1{margin:1rem 0}.p-1{padding:1rem}.py-1{padding:1rem 0}.m-2{margin:2rem}.my-2{margin:2rem 0}.p-2{padding:2rem}.py-2{padding:2rem 0}.m-3{margin:3rem}.my-3{margin:3rem 0}.p-3{padding:3rem}.py-3{padding:3rem 0}.m-4{margin:4rem}.my-4{margin:4rem 0}.p-4{padding:4rem}.py-4{padding:4rem 0}.m-5{margin:5rem}.my-5{margin:5rem 0}.p-5{padding:5rem}.py-5{padding:5rem 0}.bg-primary{background-color:#17a2b8;color:#fff}.bg-light{background-color:#f4f4f4;color:#333;border:#ccc 1px solid}.bg-dark{background-color:#343a40;color:#fff}.bg-danger{background-color:#dc3545;color:#fff}.bg-success{background-color:#28a745;color:#fff}.bg-white{background-color:#fff;color:#333;border:#ccc 1px solid}.dark-overlay{height:100%;width:100%;position:absolute;top:0;left:0;background-color:rgba(0,0,0,0.7)}.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:#17a2b8}.btn{display:inline-block;background-color:#f4f4f4;color:#333;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:disabled{background-color:#333}.btn.btn-primary{background-color:#17a2b8;color:#fff}.btn.btn-primary:hover{background:#1ab6cf}.btn.btn-dark{background-color:#343a40;color:#fff}.btn.btn-dark:hover{background:#3f474e}.btn.btn-light{background-color:#f4f4f4;color:#333}.btn.btn-light:hover{background:#626d78;color:white}.btn.btn-danger{background-color:#dc3545;color:#fff}.btn.btn-danger:hover{background:#e04b59}.btn.btn-success{background-color:#28a745;color:#fff}.btn.btn-success:hover{background:#2dbc4e}.container{max-width:1100px;margin:auto;overflow:hidden;padding:0 2rem;margin-top:5rem;margin-bottom:3rem}.alert{padding:0.8rem;margin:1rem;opacity:0.9;background-color:#f4f4f4;color:#333}.alert.alert-primary{background-color:#17a2b8;color:#fff}.alert.alert-dark{background-color:#343a40;color:#fff}.alert.alert-success{background-color:#28a745;color:#fff}.alert.alert-danger{background-color:#dc3545;color:#fff}.round-img{border-radius:50%}.line{height:1px;background:#ccc;margin:1.5rem 0}.badge{font-size:0.8rem;padding:0.1rem;text-align:center;margin:0.3rem;background:#f4f4f4;color:#333;border-radius:1rem}.badge.badge-primary{background-color:#17a2b8;color:#fff}.badge.badge-dark{background-color:#343a40;color:#fff}.badge.badge-success{background-color:#28a745;color:#fff}.badge.badge-danger{background-color:#dc3545;color:#fff}.table th,.table td{padding:1rem;text-align:left}.table th{background:#f4f4f4}.form-group{margin:1.2rem 0}.form-text{display:block;margin-top:0.3rem;color:#888}.form input[type='text'],.form input[type='email'],.form input[type='password'],.form input[type='date'],.form select,.form textarea{display:block;width:100%;padding:0.4rem;font-size:1.2rem;border:1px solid #ccc;border-radius:0.3rem}.form input[type='submit']{font:inherit}.form .social-input{display:flex}.form .social-input svg{padding:0.5rem;width:3rem}.form .social-input svg.fa-twitter{color:#38a1f3}.form .social-input svg.fa-facebook{color:#3b5998}.form .social-input svg.fa-instagram{color:#3f729b}.form .social-input svg.fa-youtube{color:#c4302b}.form .social-input svg.fa-linkedin{color:#0077b5}@media (max-width: 700px){.hide-sm{display:none}.container{margin-top:8rem}.x-large{font-size:3rem}.large{font-size:2rem}.lead{font-size:1rem}.navbar{flex-direction:column;text-align:center}.navbar ul{text-align:center;justify-content:center}.navbar h1{margin-bottom:1rem}.dash-buttons a{display:block;width:100%;margin-bottom:0.2rem}.profile{grid-template-columns:1fr;text-align:center}.profile ul{display:none}.profile-grid{grid-template-areas:"top" "about" "exp" "edu" "github"}.profile-about .skills{flex-direction:column}.post{grid-template-columns:1fr}.post a,.post button{padding:0.3rem 0.4rem}}*{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:#333}a{text-decoration:none;color:#17a2b8}ul{list-style:none}img{width:100%}.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 #17a2b8;opacity:0.9}.navbar ul{display:flex}.navbar a{color:white;padding:0.45rem;margin:0 0.25rem}.navbar a:hover{color:#17a2b8}.landing{position:relative;background:url("../img/showcase.jpg") no-repeat center center/cover;height:100vh}.landing-inner{color:white;height:100%;display:flex;flex-direction:column;width:80%;margin:auto;align-items:center;justify-content:center;text-align:center}.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-grid{display:grid;grid-template-areas:'top top' 'about about' 'exp edu' 'github github';grid-gap:1rem}.profile-grid .profile-top{grid-area:top;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center}.profile-grid .profile-top img{width:250px}.profile-grid .profile-top .icons a{color:white;margin:0 0.3rem}.profile-grid .profile-top .icons a:hover{color:#343a40}.profile-grid .profile-about{grid-area:about;text-align:center}.profile-grid .profile-about .skills{display:flex;justify-content:center;align-items:center;text-align:center}.profile-grid .profile-exp{grid-area:exp}.profile-grid .profile-edu{grid-area:edu}.profile-grid .profile-exp>div,.profile-grid .profile-edu>div{margin-bottom:1rem;padding-bottom:1rem;border-bottom:#ccc 1px dotted}.profile-grid .profile-exp>div:last-child,.profile-grid .profile-edu>div:last-child{border:none}.profile-grid .profile-exp>div p,.profile-grid .profile-edu>div p{margin:0.5rem 0}.profile-grid .profile-github{grid-area:github}.profile-grid .profile-github .repo{display:flex}.profile-grid .profile-github .repo>div:first-child{flex:7;flex-basis:70%}.profile-grid .profile-github .repo>div:last-child{flex:3;flex-basis:30%}.post-form-header{padding:0.5rem}.post{display:grid;grid-template-columns:1fr 4fr;grid-gap:2rem;align-items:center}.post>div:first-child{text-align:center}.post img{width:150px}.not-found{position:relative;background:url("../img/404.jpg") no-repeat center center/cover;height:100vh} diff --git a/src/static/img/404.jpg b/src/static/img/404.jpg new file mode 100644 index 0000000..597e6b2 Binary files /dev/null and b/src/static/img/404.jpg differ diff --git a/src/static/img/showcase.jpg b/src/static/img/showcase.jpg new file mode 100644 index 0000000..7317d9b Binary files /dev/null and b/src/static/img/showcase.jpg differ diff --git a/src/static/scss/_config.scss b/src/static/scss/_config.scss new file mode 100644 index 0000000..9625dc4 --- /dev/null +++ b/src/static/scss/_config.scss @@ -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; + } +} diff --git a/src/static/scss/_form.scss b/src/static/scss/_form.scss new file mode 100644 index 0000000..9b0237e --- /dev/null +++ b/src/static/scss/_form.scss @@ -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; + } + } + } +} diff --git a/src/static/scss/_mobile.scss b/src/static/scss/_mobile.scss new file mode 100644 index 0000000..df99cb7 --- /dev/null +++ b/src/static/scss/_mobile.scss @@ -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; + } + } +} diff --git a/src/static/scss/_utils.scss b/src/static/scss/_utils.scss new file mode 100644 index 0000000..821a924 --- /dev/null +++ b/src/static/scss/_utils.scss @@ -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; + } +} diff --git a/src/static/scss/style.scss b/src/static/scss/style.scss new file mode 100644 index 0000000..6ffa043 --- /dev/null +++ b/src/static/scss/style.scss @@ -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; +} diff --git a/src/store/auth/index.ts b/src/store/auth/index.ts new file mode 100644 index 0000000..fac40f3 --- /dev/null +++ b/src/store/auth/index.ts @@ -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; diff --git a/src/store/firebase/config.ts b/src/store/firebase/config.ts new file mode 100644 index 0000000..712f17a --- /dev/null +++ b/src/store/firebase/config.ts @@ -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; diff --git a/src/store/firebase/index.ts b/src/store/firebase/index.ts new file mode 100644 index 0000000..ca0a975 --- /dev/null +++ b/src/store/firebase/index.ts @@ -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(connect(selectProfile), withFirebase); diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..56d75b7 --- /dev/null +++ b/src/store/index.ts @@ -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; +} + +export default store; diff --git a/src/types/Alert.ts b/src/types/Alert.ts new file mode 100644 index 0000000..b637bb6 --- /dev/null +++ b/src/types/Alert.ts @@ -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; diff --git a/src/types/Comment.ts b/src/types/Comment.ts new file mode 100644 index 0000000..e627d7b --- /dev/null +++ b/src/types/Comment.ts @@ -0,0 +1,9 @@ +interface Comment { + // userID: string; + text: string; + name: string; + picture: string; + // date: Date; +} + +export default Comment; diff --git a/src/types/Education.ts b/src/types/Education.ts new file mode 100644 index 0000000..b696b38 --- /dev/null +++ b/src/types/Education.ts @@ -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; diff --git a/src/types/Experience.ts b/src/types/Experience.ts new file mode 100644 index 0000000..3eda35c --- /dev/null +++ b/src/types/Experience.ts @@ -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; diff --git a/src/types/Links.ts b/src/types/Links.ts new file mode 100644 index 0000000..0b8fc07 --- /dev/null +++ b/src/types/Links.ts @@ -0,0 +1,11 @@ +interface Links { + website: string; + instagram: string; + facebook: string; + linkedin: string; + twitter: string; + github: string; + youtube: string; +} + +export default Links; diff --git a/src/types/Repo.ts b/src/types/Repo.ts new file mode 100644 index 0000000..c423b13 --- /dev/null +++ b/src/types/Repo.ts @@ -0,0 +1,10 @@ +interface Repo { + name: string; + description: string; + link: string; + stars: number; + watchers: number; + forks: number; +} + +export default Repo; diff --git a/src/types/TimePeriod.ts b/src/types/TimePeriod.ts new file mode 100644 index 0000000..4007fdc --- /dev/null +++ b/src/types/TimePeriod.ts @@ -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; diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..6af008b --- /dev/null +++ b/tslint.json @@ -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" +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 39da000..669364c 100644 --- a/yarn.lock +++ b/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"