diff --git a/env.development.local b/env.development.local new file mode 100644 index 0000000..c5315f4 --- /dev/null +++ b/env.development.local @@ -0,0 +1 @@ +REACT_APP_API_URL=http://localhost:8080/forum \ No newline at end of file diff --git a/package.json b/package.json index 9f6b215..0dd5482 100644 --- a/package.json +++ b/package.json @@ -3,22 +3,32 @@ "version": "0.1.0", "private": true, "dependencies": { + "@fortawesome/fontawesome-svg-core": "^1.2.32", + "@fortawesome/free-brands-svg-icons": "^5.15.1", + "@fortawesome/free-regular-svg-icons": "^5.15.1", + "@fortawesome/free-solid-svg-icons": "^5.15.1", + "@fortawesome/react-fontawesome": "^0.1.11", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "axios": "^0.20.0", "bootstrap": "^4.5.3", + "dotenv-cli": "^4.0.0", + "jstz": "^2.1.1", "node-sass": "^4.14.1", "react": "^16.14.0", "react-bootstrap": "^1.3.0", + "react-datepicker": "^3.3.0", "react-dom": "^16.14.0", "react-facebook-login": "^4.1.1", + "react-google-recaptcha": "^2.1.0", "react-router-dom": "^5.2.0", "react-scripts": "3.4.3" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", + "build-dev-local": "dotenv -e .env.development.local react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, diff --git a/src/App.js b/src/App.js index 08ae65c..e225dcc 100644 --- a/src/App.js +++ b/src/App.js @@ -2,12 +2,16 @@ import React from 'react'; import logo from './logo.svg'; import './App.scss'; import ZfgcHeader from './components/zfgc-header/zfgc-header.component.js'; +import ZfgcFooter from './components/zfgc-header/zfgc-footer.component.js'; import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom"; import SignIn from './components/sign-in/sign-in.component.js'; +import NewUserRoute from './routing/new-user.route.js'; +import ViewMembers from './routing/members/view-members.route.js'; +import ProfileRoute from './routing/profile/profile.route.js'; function App() { document.body.classList.add('theme-midnight'); @@ -22,7 +26,24 @@ function App() { + + + + + //top nav + + + + + + //user profile + + + +
+ +
); } diff --git a/src/App.scss b/src/App.scss index ce0fb7b..775ab5e 100644 --- a/src/App.scss +++ b/src/App.scss @@ -1,7 +1,16 @@ @import 'assets/style/variables.scss'; -@import "~bootstrap/scss/bootstrap"; + +@import 'assets/style/collapsible.scss'; +@import 'assets/style/footer.scss'; +@import 'assets/style/forms.scss'; @import 'assets/style/header.scss'; +@import 'assets/style/paginator.scss'; +@import 'assets/style/profile.scss'; +@import 'assets/style/registration.scss'; @import 'assets/style/sign-in.scss'; +@import 'assets/style/users.scss'; + +@import "../node_modules/react-datepicker/dist/react-datepicker.css"; html { height: 100%; @@ -11,3 +20,5 @@ body { font-size: 1em; font-family: Verdana, Helvetica, sans-serif; } + +@import "~bootstrap/scss/bootstrap"; diff --git a/src/assets/images/title-lg.png b/src/assets/images/title-lg.png new file mode 100644 index 0000000..6a9111e Binary files /dev/null and b/src/assets/images/title-lg.png differ diff --git a/src/assets/images/title-xs.png b/src/assets/images/title-xs.png new file mode 100644 index 0000000..27f8485 Binary files /dev/null and b/src/assets/images/title-xs.png differ diff --git a/src/assets/style/collapsible.scss b/src/assets/style/collapsible.scss new file mode 100644 index 0000000..0c36f6e --- /dev/null +++ b/src/assets/style/collapsible.scss @@ -0,0 +1,20 @@ +.accordion { + background-color: $msecondary; + border: $border-stroke solid; + border-radius: $border-radius; + margin-top: 2em; + box-shadow: 0.3em 1em 1em black; + + &.zfgc-collapsible{ + padding: 0; + } + + .card { + background-color: $msecondary; + + .card-body { + padding: 0px; + background-color: $msecondary; + } + } +} diff --git a/src/assets/style/footer.scss b/src/assets/style/footer.scss new file mode 100644 index 0000000..43dd04e --- /dev/null +++ b/src/assets/style/footer.scss @@ -0,0 +1,19 @@ +.zfgc-bottom{ + bottom: 0; + position: absolute; + width: 100%; + + .zfgc-footer{ + border-top: .2em solid black; + box-shadow: 0 -.1em 0.3em black; + + + .nav-heading { + .nav-tab{ + .nav-icon{ + font-size: 2em; + } + } + } + } +} \ No newline at end of file diff --git a/src/assets/style/forms.scss b/src/assets/style/forms.scss new file mode 100644 index 0000000..1f4ec38 --- /dev/null +++ b/src/assets/style/forms.scss @@ -0,0 +1,78 @@ +.zfgc-form{ + label { + font-weight: 600; + } + + &.was-validated { + .form-control, .custom-select{ + border: .15em solid; + } + } + + .react-datepicker-wrapper{ + display: block; + } + + input { + border: .1em solid black; + } + + select { + border: .1em solid black; + } + + input[type="checkbox"], input[type="radio"]{ + appearance: none; + background-color: #ffffff; + padding: .445em; + display: inline-flex; + position: relative; + margin-right: .4em; + + &:checked:after{ + font-size: 1em; + font-weight: 600; + position: absolute; + } + } + + input[type="checkbox"]{ + border-radius: .2em; + &:checked:after{ + content: '\2714'; + top: -.3em; + left: -.05em; + } + } + + input[type="radio"]{ + border-radius: .5em; + &:checked:after{ + content: '\25CF'; + top: -.35em; + left: .14em; + } + } + + .btn { + &:disabled{ + background-color: transparent !important; + border: .1em solid black; + cursor: not-allowed; + } + + margin-right: 1em; + + &.btn-primary { + box-shadow: 0.2em 0.2em 0.4em 0 black; + border: .13em solid black; + font-weight: 600; + } + + &.btn-danger{ + box-shadow: 0.2em 0.2em 0.4em 0 black; + border: .13em solid black; + font-weight: 600; + } + } +} \ No newline at end of file diff --git a/src/assets/style/header.scss b/src/assets/style/header.scss index b847cb1..cab763b 100644 --- a/src/assets/style/header.scss +++ b/src/assets/style/header.scss @@ -1,8 +1,55 @@ .zfgc-header{ - border-bottom: .2em solid black; - padding: 1em; + border-bottom: .2em solid black; + padding: 1em; + display: flex; + justify-content: space-between; + box-shadow: 0 0.1em 0.3em black; .user-heading{ font-weight: bold; + + .logged-in-wrapper{ + display: flex; + + img { + width: 4.5em; + border: .1em solid; + margin-right: 1em; + } + + .logout-button{ + cursor: pointer; + } + } + } + + .nav-heading{ + .nav-tab{ + width: 5em; + border-left: 1px solid #122140; + border-right: 1px solid #122140; + border-top: 1px solid #122140; + border-top-left-radius: .5em; + border-top-right-radius: .5em; + background-color: #25334e; + position: relative; + top: 3em; + height: 3em; + display: flex; + box-shadow: 0 0 0.3em 0 black; + justify-content: center; + padding-top: .7em; + cursor: pointer; + + .nav-icon{ + font-size: 1.7em; + } + } + } + + .title-heading { + img { + height: 5em; + } } } \ No newline at end of file diff --git a/src/assets/style/paginator.scss b/src/assets/style/paginator.scss new file mode 100644 index 0000000..5ebb7b3 --- /dev/null +++ b/src/assets/style/paginator.scss @@ -0,0 +1,21 @@ +.paginator{ + .left{ + font-size: 2.5em; + padding-right: .1em; + cursor: pointer; + + &.disabled{ + cursor: not-allowed !important; + } + } + + .right{ + font-size: 2.5em; + padding-left: .1em; + cursor: pointer; + + &.disabled{ + cursor: not-allowed !important; + } + } +} \ No newline at end of file diff --git a/src/assets/style/profile.scss b/src/assets/style/profile.scss new file mode 100644 index 0000000..b6038f3 --- /dev/null +++ b/src/assets/style/profile.scss @@ -0,0 +1,50 @@ +.profile-wrapper{ + label{ + font-weight: bold; + } + + .basic-details{ + .avatar-wrapper{ + img{ + border-radius: .5em; + border: .1em solid; + width: 8em; + height: 8em; + } + } + + .personal-text-wrapper{ + width: 128px; + font-size: .8em; + } + } + + .utility-details{ + .quick-comm-wrapper{ + font-size: 1.5em; + } + } + + .nav-wrapper{ + .nav-item{ + border-left: .1em solid; + border-right: .1em solid; + padding: 0 .3em; + cursor: pointer; + + &.selected, &:hover { + box-shadow: 0 0 1em 0.5em black; + clip-path: inset(.1em -1em .1em -1em); + + } + } + + } + + .profile-summary{ + .signature-wrapper{ + white-space: nowrap; + overflow-x: auto; + } + } +} \ No newline at end of file diff --git a/src/assets/style/registration.scss b/src/assets/style/registration.scss new file mode 100644 index 0000000..450cc54 --- /dev/null +++ b/src/assets/style/registration.scss @@ -0,0 +1,13 @@ +.tos-container { + display: inline-block; + padding: .5em; + height: 10em; + margin: 1em 0; + max-width: 45em; + overflow-y: auto; + background-color: #132241; + border-top: .1em solid; + border-left: .1em solid; + border-bottom: .2em solid black; + box-shadow: 0 0.1em 0.2em black; +} \ No newline at end of file diff --git a/src/assets/style/sign-in.scss b/src/assets/style/sign-in.scss index 2abf74e..5705596 100644 --- a/src/assets/style/sign-in.scss +++ b/src/assets/style/sign-in.scss @@ -1,11 +1,6 @@ .sign-in-form{ form{ - background-color: $msecondary; padding: 2em; - border: $border-stroke solid; - border-radius: $border-radius; - margin-top: 2em; - box-shadow: 0.3em 1em 1em black; } } \ No newline at end of file diff --git a/src/assets/style/themes/midnight-colors.scss b/src/assets/style/themes/midnight-colors.scss index 1aba1d9..d8e5a9b 100644 --- a/src/assets/style/themes/midnight-colors.scss +++ b/src/assets/style/themes/midnight-colors.scss @@ -1,6 +1,20 @@ $mprimary: #204378; $msecondary: #25334E; -$msecondary2: #132241; +$msecondary2: #132241; + +$green1: #77ff3f; +$green2: #cdff07; + +$red1: red; $onPrimary: #d5dffb; $onPrimary2: #black; + +$btnPrimary: #46c334; +$btnSecondary: #909090; +$btnDanger: #c33434; + +$hoverColor: $msecondary2; + +$link: $green1; +$link-visited: $green2; diff --git a/src/assets/style/users.scss b/src/assets/style/users.scss new file mode 100644 index 0000000..fbb69ae --- /dev/null +++ b/src/assets/style/users.scss @@ -0,0 +1,13 @@ +.user-panels-wrapper{ + max-height: 25em; + overflow-y: auto; + + .user-panel{ + cursor: pointer; + + &:hover{ + border-bottom: .1em solid; + box-shadow: 0 .5em 1em black; + } + } +} \ No newline at end of file diff --git a/src/assets/style/variables.scss b/src/assets/style/variables.scss index 80a2924..3b9c65c 100644 --- a/src/assets/style/variables.scss +++ b/src/assets/style/variables.scss @@ -6,4 +6,89 @@ $border-stroke: .2em; .theme-midnight { background-color: $mprimary; color: $onPrimary; + + .zfgc-footer{ + background-color: $mprimary; + } + + .zfgc-form{ + .invalid-feedback{ + color: $btnDanger; + } + + &.was-validated{ + .form-control:valid, .custom-select:valid, .form-check-input:valid{ + border-color: $btnPrimary; + } + + .form-control:invalid, .custom-select:invalid, .form-check-input:invalid{ + border-color: $btnDanger; + } + } + + .btn{ + &.btn-primary{ + background-color: $btnPrimary; + } + + &.btn-secondary{ + background-color: $btnSecondary; + } + + &.btn-danger { + background-color: $btnDanger; + } + } + + input[type="radio"], input[type="checkbox"]{ + &:checked:after{ + color: $btnPrimary; + } + } + } + + a{ + color: $green1; + + &:visited{ + color: $green2; + } + } + + .accordion { + .card{ + .card-header{ + background-color: $msecondary2; + + .btn-link{ + color: $onPrimary; + } + } + } + } + + .user-panel{ + &:hover{ + background-color: $hoverColor; + } + } + + .profile-wrapper{ + .nav-item{ + &.selected{ + border-color: $green2 !important; + color: $green2; + } + + &:hover{ + border-color: $red1; + } + } + } + + label{ + &.green{ + color: $green1; + } + } } \ No newline at end of file diff --git a/src/components/collapsible/collapsible.component.js b/src/components/collapsible/collapsible.component.js new file mode 100644 index 0000000..c53bd59 --- /dev/null +++ b/src/components/collapsible/collapsible.component.js @@ -0,0 +1,53 @@ +import React from 'react'; +import Accordion from 'react-bootstrap/Accordion'; +import Card from 'react-bootstrap/Card'; +import Button from 'react-bootstrap/Button'; +import { faPlusSquare, faMinusSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' + +// to remove +import Form from 'react-bootstrap/Form'; + + +class Collapsible extends React.Component { + + constructor(){ + super(); + this.state = {opened : true}; + this.sizeClass = "col-11 col-md-8 col-lg-6"; + } + + componentDidMount(){ + if(this.props.sizeClass && this.props.sizeClass !== null && this.props.sizeClass !== ""){ + this.sizeClass = this.props.sizeClass; + } + } + + handleExpand() { + this.setState({opened : !this.state.opened}); + } + + render() { + + let headerClass = this.props.title == null || this.props.title.trim().length == 0 || this.props.noCollapse ? 'd-none' : ''; + let squareIcon = this.state.opened ? faMinusSquare : faPlusSquare; + return ( + + + + this.handleExpand()}> +

{this.props.title}

+
+
+ + + {this.props.children} + + +
+
+ ); + } +} + +export default Collapsible; \ No newline at end of file diff --git a/src/components/forms/ZfgcForm.component.js b/src/components/forms/ZfgcForm.component.js new file mode 100644 index 0000000..4ad7e14 --- /dev/null +++ b/src/components/forms/ZfgcForm.component.js @@ -0,0 +1,49 @@ +import React from 'react'; + +class ZfgcForm extends React.Component { + + getState(){ + return this.state; + } + + initForm(viewModel){ + this.setState({vm : viewModel}); + } + + changeField(control, field){ + this.changeFieldInternal(control.target.value, field); + } + + changeFieldBool(control, field){ + this.changeFieldInternal(control.target.checked, field); + } + + changeFieldInternal(value, field){ + let vm = this.state.vm; + let children = field.split(".") + let fieldToWrite = null; + let temp = vm; + + for(let i = 0; i < children.length - 1; i++){ + temp = temp[children[i]]; + } + + temp[children[children.length - 1]] = value; + this.setState(this.state); + } + + renderSelectOptions(lookups, lookupName){ + let items = []; + + if(lookups && lookups !== null){ + for(let lkup of lookups[lookupName]){ + items.push(); + } + } + + return items; + } + +} + +export default ZfgcForm; \ No newline at end of file diff --git a/src/components/members/member-list.component.js b/src/components/members/member-list.component.js new file mode 100644 index 0000000..739a902 --- /dev/null +++ b/src/components/members/member-list.component.js @@ -0,0 +1,152 @@ +import React from 'react'; +import Collapsible from './../collapsible/collapsible.component.js'; +import ZfgcForm from './../forms/ZfgcForm.component.js'; +import Form from 'react-bootstrap/Form'; +import { faEnvelopeSquare, faCaretLeft, faCaretRight } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import UserEndpoints from './../../utilities/axios/users.endpoints.js'; +import { Link } from "react-router-dom" + +class MemberList extends ZfgcForm { + + constructor(){ + super(); + this.lookups = {}; + this.lookups.sortBy = [{id : "DISPLAY_NAME", value: "Username"}, {id: "DATE_REGISTERED", value: "Member Since"}, {id: "GROUP_NAME", value: "Member Group"}]; + this.lookups.sortOrder = [{id : "ASC", value: "Asc"}, {id : "DESC", value: "Desc"}]; + + } + + componentDidMount() { + UserEndpoints.getMembersList({'pageNo' : 1, 'usersPerPage' : 10, 'sortBy' : 'DISPLAY_NAME', 'sortOrder' : 'ASC'}).then(data => { + data.data.pageNo = 1; + data.data.sortBy = 'DISPLAY_NAME'; + data.data.sortOrder = 'ASC'; + super.initForm(data.data); + }); + } + + callMemberList(){ + return UserEndpoints.getMembersList({'pageNo' : this.state.vm.pageNo, + 'usersPerPage': 10, + 'sortBy' : this.state.vm.sortBy, + 'sortOrder' : this.state.vm.sortOrder}); + } + + handleSubmit = (event) => { + event.preventDefault(); + + if(this.state.vm.pageNo < 1){ + this.state.vm.pageNo = 1; + } + else if(this.state.vm.pageNo > this.state.vm.totalPages){ + this.state.vm.pageNo = this.state.vm.totalPages; + } + + this.callMemberList().then(data => { + super.initForm(data.data); + }); + }; + + handleArrowClickLeft = (event) => { + if(this.state.vm.pageNo > 1){ + this.state.vm.pageNo -= 1; + + this.callMemberList().then(data => { + data.data.pageNo = this.state.vm.pageNo; + super.initForm(data.data); + }); + } + }; + + handleArrowClickRight = (event) => { + if(this.state.vm.pageNo < this.state.vm.totalPages){ + this.state.vm.pageNo += 1; + + this.callMemberList().then(data => { + data.data.pageNo = this.state.vm.pageNo; + super.initForm(data.data); + }); + } + }; + + changeSorting = (event) => { + super.changeField(event, 'sortBy'); + + this.callMemberList().then(data => { + super.initForm(data.data); + }); + }; + + changeSortingOrder = (event) => { + super.changeField(event, 'sortOrder'); + + this.callMemberList().then(data => { + super.initForm(data.data); + }); + } + + renderUserPanels(){ + let items = []; + + if(this.state && this.state !== null && this.state.vm && this.state.vm !== null){ + let members = this.state.vm.members ? this.state.vm.members.slice(0) : []; + for(const member of members){ + items.push( +
+
+
+

+ {member.displayName} + +

+
{member.groupName}
+
+
{member.dateRegisteredAsString}
+ {member.ipAddress !== null ?
{member.ipAddress}
: ''} +
+ +
+ ); + } + } + + return items; + } + + render () { + return ( +
+ +
+
+ + Order By: + this.changeSorting(c) }> + {super.renderSelectOptions(this.lookups, "sortBy")} + + this.changeSortingOrder(c) }> + {super.renderSelectOptions(this.lookups, "sortOrder")} + + + + + Page + + super.changeField(c, 'pageNo') }/> + + of {this.state ? this.state.vm.totalPages : '1'} + +
+
+
+ {this.renderUserPanels()} +
+
+
+ ) + } + +} + +export default MemberList; \ No newline at end of file diff --git a/src/components/nav-tab/nav-tab.component.js b/src/components/nav-tab/nav-tab.component.js new file mode 100644 index 0000000..8fceb82 --- /dev/null +++ b/src/components/nav-tab/nav-tab.component.js @@ -0,0 +1,23 @@ +import React from 'react'; +import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; +import Tooltip from 'react-bootstrap/Tooltip'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import {Link} from "react-router-dom"; + +class NavTab extends React.Component { + render() { + + + return ( + {this.props.tooltip}}> +
+ + + +
+
+ ); + } +} + +export default NavTab; \ No newline at end of file diff --git a/src/components/profile/avatar.component.js b/src/components/profile/avatar.component.js new file mode 100644 index 0000000..86dc9da --- /dev/null +++ b/src/components/profile/avatar.component.js @@ -0,0 +1,36 @@ +import React from 'react'; +import Image from 'react-bootstrap/Image'; + +class Avatar extends React.Component { + constructor(){ + super(); + } + + componentDidMount(){ + + } + + renderAvatar(){ + let imageUrl = process.env.REACT_APP_API_URL + "/contentstream/avatar/-1"; + if(this.props.avatar && this.props.avatar !== null){ + if(this.props.avatar.avatarTypeId === 2 || this.props.avatar.avatarTypeId === 4){ + imageUrl = process.env.REACT_APP_API_URL + "/contentstream/avatar/" + this.props.avatar.avatarId; + } + else if(this.props.avatar.avatarTypeId === 3){ + imageUrl = this.props.avatar.avatarUrl; + } + } + + return + } + + render() { + return ( +
+ {this.renderAvatar()} +
+ ) + } +} + +export default Avatar; \ No newline at end of file diff --git a/src/components/profile/profile-info/ProfileSummary.component.js b/src/components/profile/profile-info/ProfileSummary.component.js new file mode 100644 index 0000000..6fed89b --- /dev/null +++ b/src/components/profile/profile-info/ProfileSummary.component.js @@ -0,0 +1,98 @@ +import React from "react"; +import Collapsible from './../../collapsible/collapsible.component.js'; + +class ProfileSummary extends React.Component { + + constructor() { + super(); + } + + componentDidMount() { + + } + + render () { + let rowClassesBase = "flex-column flex-md-row justify-content-between ml-3 mb-3 mr-3"; + let rowClasses = "d-flex " + rowClassesBase; + let labelClasses = "mb-0 col-12 col-md-6"; + + return ( +
+ +
+
+ +
{this.props.user.loginName}
+
+ +
+ +
{this.props.user.dateRegisteredAsString}
+
+ +
+ +
{this.props.user.primaryIpAddress.ipAddress}
+
+ +
+ +
{this.props.user.primaryHostname.hostname}
+
+ +
+ +
{this.props.user.userLocalTimeAsString}
+
+ +
+ +
01/01/2020
+
+ +
+ +
+
+
+
+ + +
+
+ +
{this.props.user.userContactInfo.email.emailAddress}
+
+ +
+ + +
+ +
+ +
{this.props.user.userContactInfo.gtalk}
+
+ +
+ +
{this.props.user.userContactInfo.nnid}
+
+ +
+ + +
+
+
+
+ ) + } + +} + +export default ProfileSummary; \ No newline at end of file diff --git a/src/components/profile/profile.component.js b/src/components/profile/profile.component.js new file mode 100644 index 0000000..0c2a841 --- /dev/null +++ b/src/components/profile/profile.component.js @@ -0,0 +1,96 @@ +import React from "react"; +import Collapsible from './../collapsible/collapsible.component.js'; +import UserEndpoints from './../../utilities/axios/users.endpoints.js'; +import ZfgcForm from './../forms/ZfgcForm.component.js'; +import { Link, useLocation, Switch, Route} from "react-router-dom" +import Avatar from './../profile/avatar.component.js'; +import { faEnvelopeSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +import ProfileSummary from './profile-info/ProfileSummary.component.js'; + +class Profile extends ZfgcForm { + + constructor(props) { + super(); + } + + componentDidMount() { + let query = new URLSearchParams(this.props.location.search); + this.userId = query.get("userId"); + UserEndpoints.getUserProfile({userId : this.userId}).then(data => { + super.initForm(data.data); + + UserEndpoints.getUserNavigation({userId : this.userId}).then(data => { + let tempState = super.getState(); + tempState.nav = data.data; + super.setState(tempState); + }); + }); + } + + handleNavClick(tab){ + for(const nav of this.getState().nav){ + nav.isSelected = false; + } + + tab.isSelected = !tab.isSelected; + + super.setState(this.getState()); + } + + renderNavigation(){ + let tabArray = []; + if(this.getState().nav){ + let nav = this.getState().nav; + + for(const tab of nav){ + tabArray.push( + this.handleNavClick(tab)}> + {tab.title} + + ); + } + } + + return tabArray; + } + + render () { + return ( +
+ {super.getState() !== null ? + +
+
+
+
{super.getState().vm.displayName}
+
{super.getState().vm.primaryMemberGroup.groupName}
+ +
{super.getState().vm.personalInfo.personalText}
+
+
+
+ +
+
+
+
+
+ {this.renderNavigation()} +
+
+ + }/> + +
+
+
+
: '' + } +
+ ) + } +} + +export default Profile; \ No newline at end of file diff --git a/src/components/registration/post-registration.component.js b/src/components/registration/post-registration.component.js new file mode 100644 index 0000000..c057526 --- /dev/null +++ b/src/components/registration/post-registration.component.js @@ -0,0 +1,36 @@ +import React from 'react'; +import ZfgcForm from './../forms/ZfgcForm.component.js'; +import Collapsible from './../collapsible/collapsible.component.js'; +import Button from 'react-bootstrap/Button'; +import Form from 'react-bootstrap/Form'; +import {Link} from "react-router-dom"; +import { faCheckCircle } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' + +class PostRegistration extends React.Component { + componentDidMount(){ + + } + + render () { + return ( +
+ +
+
+
+ + + + + +
+
+
+
+
+ ) + } +} + +export default PostRegistration; \ No newline at end of file diff --git a/src/components/registration/registration.component.js b/src/components/registration/registration.component.js new file mode 100644 index 0000000..dd3a654 --- /dev/null +++ b/src/components/registration/registration.component.js @@ -0,0 +1,139 @@ +import React from 'react'; +import Form from 'react-bootstrap/Form'; +import Button from 'react-bootstrap/Button'; +import ReCAPTCHA from "react-google-recaptcha"; +import UserEndpoints from './../../utilities/axios/users.endpoints.js'; +import Collapsible from './../collapsible/collapsible.component.js'; +import LookupEndpoints from './../../utilities/axios/lookups.endpoints.js'; +import jstz from 'jstz'; +import ZfgcForm from './../forms/ZfgcForm.component.js'; +import DatePicker from "react-datepicker"; +import { Redirect } from "react-router-dom"; + +class Registration extends ZfgcForm { + + componentDidMount(){ + + Promise.all([UserEndpoints.getNewUserTemplate(), LookupEndpoints.getLookupsList("TIMEZONE")]).then(([user, lkups]) => { + this.lookups = lkups.data; + let tz = jstz.determine(); + + for(let lkup of this.lookups.TIMEZONE){ + if(tz.name() == lkup.value){ + user.data.timeOffset = lkup.id; + break; + } + } + + super.initForm(user.data); + }); + } + + handleSubmit = (event) => { + const form = event.currentTarget; + event.preventDefault(); + event.stopPropagation(); + if (form.checkValidity() === false) { + form.className += " was-validated"; + } + else{ + UserEndpoints.registerNewUser(super.getState().vm).then((data) => { + let state = super.getState(); + state.redirect = true; + super.setState(state); + }); + } + }; + + render () { + + return ( +
+ {super.getState() && super.getState().redirect ? + : + "" + } + + +
+
+ + Username + super.changeField(c, 'loginName')} required> + + Please choose a username. + + + + + Display Name + super.changeField(c, 'displayName')} required> + + Please choose a display name. + + + + + Password + super.changeField(c, 'userSecurityInfo.newPassword')} required> + + Please enter a password. + + + + + Confirm Password + super.changeField(c, 'userSecurityInfo.confirmNewPassword')} required> + + Please confirm your password. + + + + + Email Address + super.changeField(c, 'userContactInfo.email.emailAddress')} required> + + Please enter an email address. + + + + + Date of Birth + super.changeField(c, 'personalInfo.birthDateAsString')}> + + Please enter a date of birth. + + + + + Timezone + super.changeField(c, 'timeOffset') } value={super.getState() && super.getState().vm ? super.getState().vm.timeOffset : '5'} custom> + {super.renderSelectOptions(this.lookups, "TIMEZONE")} + + + Please select a timezone. + + + + + super.changeFieldBool(c, 'agreeToTermsFlag')}required/> + + + + super.changeFieldInternal(c, 'gResponseToken')}/> + + + + + + + +
+
+
+
+ ) + } +} + +export default Registration; \ No newline at end of file diff --git a/src/components/registration/tos.component.js b/src/components/registration/tos.component.js new file mode 100644 index 0000000..0043614 --- /dev/null +++ b/src/components/registration/tos.component.js @@ -0,0 +1,81 @@ +import React from 'react'; +import ZfgcForm from './../forms/ZfgcForm.component.js'; +import Collapsible from './../collapsible/collapsible.component.js'; +import Button from 'react-bootstrap/Button'; +import Form from 'react-bootstrap/Form'; +import {Link} from "react-router-dom"; + +class TosForm extends React.Component { + componentDidMount(){ + + } + + + handleSubmit = (event) => { + event.preventDefault(); + event.stopPropagation(); + }; + + render () { + + return ( +
+ +
+
+
+ + +
+ You agree, through your use of this forum, that you will not post any material which is false, defamatory, inaccurate, abusive, vulgar, + hateful, harassing, obscene, profane, sexually oriented, threatening, invasive of a person's privacy, adult material, or otherwise in + violation of any International or United States Federal law. You also agree not to post any copyrighted material unless you own the + copyright or you have written consent from the owner of the copyrighted material. Spam, flooding, advertisements, chain letters, pyramid + schemes, and solicitations are also forbidden on this forum.

+ + Note that it is impossible for the staff or the owners of this forum to confirm the validity of posts. Please remember that we do not actively + monitor the posted messages, and as such, are not responsible for the content contained within. We do not warrant the accuracy, completeness, + or usefulness of any information presented. The posted messages express the views of the author, and not necessarily the views of this forum, + its staff, its subsidiaries, or this forum's owner. Anyone who feels that a posted message is objectionable is encouraged to notify an + administrator or moderator of this forum immediately. The staff and the owner of this forum reserve the right to remove objectionable + content, within a reasonable time frame, if they determine that removal is necessary. This is a manual process, however, please realize that + they may not be able to remove or edit particular messages immediately. This policy applies to member profile information as well.

+ + You remain solely responsible for the content of your posted messages. Furthermore, you agree to indemnify and hold harmless the owners of this + forum, any related websites to this forum, its staff, and its subsidiaries. The owners of this forum also reserve the right to reveal your + identity (or any other related information collected on this service) in the event of a formal complaint or legal action arising from any situation + used by your use of this forum.

+ + You have the ability, as you register, to choose your username. We advise that you keep the name appropriate. With this user account you are about + to register, you agree to never give your password out to another person except an administrator, for your protection and for validity reasons. You + also agree to NEVER use another person's account for any reason. We also HIGHLY recommend you use a complex and unique password for your account, + to prevent account theft.

+ + After you register and login to this forum, you will be able to fill out a detailed profile. It is your responsibility to present clean and accurate + information. Any information the forum owner or staff determines to be inaccurate or vulgar in nature will be removed, with or without prior notice. + Appropriate sanctions may be applicable.

+ + Please note that with each post, your IP address is recorded, in the event that you need to be banned from this forum or your ISP contacted. This will + nly happen in the event of a major violation of this agreement.

+ + Also note that the software places a cookie, a text file containing bits of information (such as your username and password), in your browser's cache. + This is ONLY used to keep you logged in/out. The software does not collect or send any other form of information to your computer. +
+ + + + + + + +
+
+
+ +
+
+ ) + } +} + +export default TosForm; \ No newline at end of file diff --git a/src/components/sign-in/sign-in.component.js b/src/components/sign-in/sign-in.component.js index 05b56b1..52e2dca 100644 --- a/src/components/sign-in/sign-in.component.js +++ b/src/components/sign-in/sign-in.component.js @@ -3,50 +3,66 @@ import Form from 'react-bootstrap/Form'; import Button from 'react-bootstrap/Button'; import UserEndpoints from './../../utilities/axios/users.endpoints.js'; import AuthStore from './../../utilities/common/AuthStore.common.js'; +import Collapsible from './../../components/collapsible/collapsible.component.js'; +import ZfgcForm from './../../components/forms/ZfgcForm.component.js'; -function SignIn() { +class SignIn extends ZfgcForm { - /*function handleAuthenticate(event){ - event.preventDefault(); - }*/ - let signInForm = { - "username": null, - "password": null - }; - const handleSubmit = (event) => { + componentDidMount(){ + let signInForm = { + "username": null, + "password": null, + "signedIn": false + }; + + super.initForm(signInForm); + } + + + + handleSubmit = (event) => { event.preventDefault(); - signInForm.username = event.target[0].value; - signInForm.password = event.target[1].value; - let auth = UserEndpoints.getClausiusLogin(signInForm); - auth.then(data => { - AuthStore.getInstance().setJwtToken(data.data); + let auth = UserEndpoints.getClausiusLogin(super.getState().vm); + auth.then(data => { + AuthStore.getInstance().setJwtToken(data.data.access_token, data.data.expires_in, super.getState().vm.signedIn); let user = UserEndpoints.getLoggedInUser(); + AuthStore.getInstance().getRefresh()(); }); - + }; - }; + render () { return ( -
-
- - Username - - - - - Password - - - - - - -
+
+ +
+
+ + Username + super.changeField(c, 'username') }> + + + + Password + super.changeField(c, 'password') }> + + + + super.changeFieldBool(c, 'signedIn') }> + + + + + + +
+
+
- ); + ) + } } diff --git a/src/components/zfgc-header/user-details.component.js b/src/components/zfgc-header/user-details.component.js new file mode 100644 index 0000000..6a787b6 --- /dev/null +++ b/src/components/zfgc-header/user-details.component.js @@ -0,0 +1,84 @@ +import React from 'react'; +import UserEndpoints from './../../utilities/axios/users.endpoints.js'; +import AuthStore from './../../utilities/common/AuthStore.common.js'; +import {Link} from "react-router-dom"; +import Image from 'react-bootstrap/Image'; +import { faDoorOpen, faEnvelope } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' + +class UserDetails extends React.Component { + + constructor(){ + super(); + + this.refresh = this.refresh.bind(this); + } + + componentDidMount(){ + AuthStore.getInstance().setUserDetailsRefresh(this.refresh); + this.refresh(); + } + + refresh() { + //check for access token in auth store first + //if we dont find it there, go to local storage + let token = AuthStore.getInstance().getJwtToken(); + let refresh = AuthStore.getInstance().getRefreshToken(); + + if(token === null){ + token = localStorage.getItem('zfgc-jwt'); + refresh = localStorage.getItem('zfgc-refresh'); + } + + if(token !== undefined && token !== null){ + AuthStore.getInstance().setJwtToken(token, refresh); + let auth = UserEndpoints.getLoggedInUser(); + + auth.then(data => { + if(data.data.usersId !== null){ + AuthStore.getInstance().setLoggedInUser(data.data); + } + + this.setState({}); + AuthStore.getInstance().getHeaderRefresh()(); + AuthStore.getInstance().getFooterRefresh()(); + }); + } + } + + handleLogout = (event) => { + AuthStore.getInstance().clearJwt(); + this.setState({}); + AuthStore.getInstance().getHeaderRefresh()(); + AuthStore.getInstance().getFooterRefresh()(); + }; + + render(){ + + let userRender =
+ Welcome, friend!
+ Sign in or Register +
; + if(AuthStore.getInstance().getLoggedInUser() !== null){ + userRender =
+ + + {AuthStore.getInstance().getLoggedInUser().displayName} + + + + + +
; + } + + return ( +
+ {userRender} +
+ ); + } + +} + +export default UserDetails; \ No newline at end of file diff --git a/src/components/zfgc-header/zfgc-footer.component.js b/src/components/zfgc-header/zfgc-footer.component.js new file mode 100644 index 0000000..50f1da9 --- /dev/null +++ b/src/components/zfgc-header/zfgc-footer.component.js @@ -0,0 +1,40 @@ +import React from 'react'; +import NavTab from '../nav-tab/nav-tab.component.js'; +import { faHome, faUsers, faAddressBook, faSearch } from "@fortawesome/free-solid-svg-icons"; +import AuthStore from './../../utilities/common/AuthStore.common.js'; + +class ZfgcFooter extends React.Component { + constructor(){ + super(); + + this.refresh = this.refresh.bind(this); + AuthStore.getInstance().setFooterRefresh(this.refresh); + } + + refresh() { + this.setState({}); + } + + renderMemberLink(){ + if(AuthStore.getInstance().getLoggedInUser() && AuthStore.getInstance().getLoggedInUser() !== null && AuthStore.getInstance().getLoggedInUser().member){ + return + } + + return ''; + } + + render () { + return ( +
+
+ + + {this.renderMemberLink()} + +
+
+ ) + } +} + +export default ZfgcFooter; \ No newline at end of file diff --git a/src/components/zfgc-header/zfgc-header.component.js b/src/components/zfgc-header/zfgc-header.component.js index 499e58a..b923e7a 100644 --- a/src/components/zfgc-header/zfgc-header.component.js +++ b/src/components/zfgc-header/zfgc-header.component.js @@ -1,13 +1,51 @@ import React from 'react'; -import {Link} from "react-router-dom"; +import { faHome, faUsers, faAddressBook, faSearch } from "@fortawesome/free-solid-svg-icons"; +import NavTab from '../nav-tab/nav-tab.component.js'; +import Image from 'react-bootstrap/Image'; +import ZfgcLogo from './../../assets/images/title-lg.png'; +import ZfgcLogoXs from './../../assets/images/title-xs.png'; +import UserDetails from './user-details.component.js'; +import AuthStore from './../../utilities/common/AuthStore.common.js'; class ZfgcHeader extends React.Component { + constructor() { + super(); + this.refresh = this.refresh.bind(this); + + AuthStore.getInstance().setHeaderRefresh(this.refresh); + } + + componentDidMount(){ + + } + + refresh() { + this.setState({}); + } + + renderMemberLink(){ + if(AuthStore.getInstance().getLoggedInUser() && AuthStore.getInstance().getLoggedInUser() !== null && AuthStore.getInstance().getLoggedInUser().member){ + return + } + + return ''; + } + render () { return (
+
+ + +
+
+ + + {this.renderMemberLink()} + +
- Welcome, friend!
- Sign in or Register +
); diff --git a/src/routing/members/view-members.route.js b/src/routing/members/view-members.route.js new file mode 100644 index 0000000..06274da --- /dev/null +++ b/src/routing/members/view-members.route.js @@ -0,0 +1,14 @@ +import React from 'react'; +import MemberList from './../../components/members/member-list.component.js'; + +class ViewMembers extends React.Component { + render (){ + return ( +
+ +
+ ); + } +} + +export default ViewMembers; \ No newline at end of file diff --git a/src/routing/new-user.route.js b/src/routing/new-user.route.js new file mode 100644 index 0000000..d5cdd7a --- /dev/null +++ b/src/routing/new-user.route.js @@ -0,0 +1,33 @@ +import React from 'react'; +import TosForm from './../components/registration/tos.component.js'; +import Registration from './../components/registration/registration.component.js'; +import PostRegistration from './../components/registration/post-registration.component.js'; +import { + BrowserRouter as Router, + Switch, + Route, Link, useRouteMatch +} from "react-router-dom"; + +function NewUserRoute() { + let { path, url } = useRouteMatch(); + + return ( +
+ + + + + + + + + + + + + +
+ ); +} + +export default NewUserRoute; \ No newline at end of file diff --git a/src/routing/profile/profile.route.js b/src/routing/profile/profile.route.js new file mode 100644 index 0000000..6568243 --- /dev/null +++ b/src/routing/profile/profile.route.js @@ -0,0 +1,16 @@ +import React from 'react'; +import Profile from './../../components/profile/profile.component.js'; + + +class ProfileRoute extends React.Component { + render () { + return ( +
+ +
+ ) + } + +} + +export default ProfileRoute; \ No newline at end of file diff --git a/src/utilities/axios/lookups.endpoints.js b/src/utilities/axios/lookups.endpoints.js new file mode 100644 index 0000000..39d130d --- /dev/null +++ b/src/utilities/axios/lookups.endpoints.js @@ -0,0 +1,12 @@ +import React from 'react'; +import ZfgcApi from './zfgc-api.endpoints.js'; + +class LookupEndpoints extends React.Component { + + static async getLookupsList(body){ + return ZfgcApi.getInstance().post('lookups/list', body, {headers : {'Content-Type' : 'text/plain'}}); + } + +} + +export default LookupEndpoints; \ No newline at end of file diff --git a/src/utilities/axios/users.endpoints.js b/src/utilities/axios/users.endpoints.js index ce08354..98cdf64 100644 --- a/src/utilities/axios/users.endpoints.js +++ b/src/utilities/axios/users.endpoints.js @@ -1,18 +1,39 @@ -import React from 'react'; -import ZfgcApi from './zfgc-api.endpoints'; -import AuthStore from './../common/AuthStore.common.js'; +import React from 'react' +import ZfgcApi from './zfgc-api.endpoints.js' +import AuthStore from './../common/AuthStore.common.js' class UserEndpoints extends React.Component { static async getClausiusLogin(body){ - return ZfgcApi.post('users/auth/login', body); + return ZfgcApi.getInstance().post('users/auth/login', body); + } + + static async getClausiusRefresh(body){ + return ZfgcApi.getInstance().post('users/auth/refresh', body); } static async getLoggedInUser(){ - let accessToken = AuthStore.getInstance().getJwtToken(); - ZfgcApi.get('users/loggedInUser', {headers : { - 'Authorization' : 'bearer ' + accessToken.access_token - }}); + return ZfgcApi.getInstance().get('users/loggedInUser'); + } + + static async getNewUserTemplate(){ + return ZfgcApi.getInstance().get('users/newuser/template'); + } + + static async registerNewUser(body){ + return ZfgcApi.getInstance().post('users/newuser', body); + } + + static async getMembersList(params){ + return ZfgcApi.getInstance().get('users/member-list?pageNo=' + params.pageNo + "&usersPerPage=" + params.usersPerPage + "&sortBy=" + params.sortBy + "&sortOrder=" + params.sortOrder); + } + + static getUserProfile(params){ + return ZfgcApi.getInstance().get('users/profile/' + params.userId); + } + + static getUserNavigation(params){ + return ZfgcApi.getInstance().get('users/profile/' + params.userId + '/navigation'); } } diff --git a/src/utilities/axios/zfgc-api.endpoints.js b/src/utilities/axios/zfgc-api.endpoints.js index 025064d..e300ad7 100644 --- a/src/utilities/axios/zfgc-api.endpoints.js +++ b/src/utilities/axios/zfgc-api.endpoints.js @@ -1,5 +1,57 @@ -import axios from 'axios'; +import AuthStore from './../common/AuthStore.common.js' +import axios from 'axios' -export default axios.create({ - baseURL: "http://localhost:8080/forum/" -}) \ No newline at end of file + +export default class ZfgcApi { + static myInstance = null; + ax = null; + + static getInstance() { + if(ZfgcApi.myInstance == null){ + ZfgcApi.myInstance = new ZfgcApi(); + } + + return this.myInstance; + } + + + + constructor(){ + this.ax = axios.create({ + baseURL: process.env.REACT_APP_API_URL + }); + + this.ax.interceptors.request.use(req => { + if(AuthStore.getInstance().getJwtToken() !== null){ + req.headers.authorization = "bearer " + AuthStore.getInstance().getJwtToken(); + } + + return req; + }, + err => { + let responseCode = err.response.status; + if(responseCode === 401){ + //check for a refresh token + //if it exists, attempt to get a new token + let refresh = localStorage.getItem('zfgc-refresh'); + if(refresh === null){ + //boot back to the login page + } + + } + else if(responseCode === 422){ + + } + }); + } + + + + get(url){ + return this.ax.get(url); + } + + post(url, body, headers){ + return this.ax.post(url, body, headers); + } +}; \ No newline at end of file diff --git a/src/utilities/common/AuthStore.common.js b/src/utilities/common/AuthStore.common.js index 1ce7194..2f4a58e 100644 --- a/src/utilities/common/AuthStore.common.js +++ b/src/utilities/common/AuthStore.common.js @@ -4,6 +4,10 @@ export default class AuthStore { static myInstance = null; jwtToken = null; + loggedInUser = null; + refresh = null; + headerRefresh = null; + footerRefresh = null; static getInstance() { if(AuthStore.myInstance == null){ @@ -13,6 +17,50 @@ export default class AuthStore { return this.myInstance; } + + getRefresh(){return this.refresh;} + setUserDetailsRefresh(refresh){ + this.refresh = refresh; + } + + getHeaderRefresh(){return this.headerRefresh;} + setHeaderRefresh(refresh){ + this.headerRefresh = refresh + } + + getFooterRefresh(){return this.footerRefresh;} + setFooterRefresh(refresh){ + this.footerRefresh = refresh + } + + getRefreshToken () { return this.refreshToken }; getJwtToken (){ return this.jwtToken}; - setJwtToken (jwt) { this.jwtToken = jwt; console.log("stored JWT")}; + setJwtToken (jwt, refresh, localStore) { + this.jwtToken = jwt; + this.freshToken = refresh; + + if(localStore === true){ + localStorage.setItem('zfgc-jwt', jwt); + localStorage.setItem('zfgc-refresh', refresh); + } + + console.log("stored JWT"); + }; + + clearJwt(){ + this.jwtToken = null; + this.freshToken = null; + this.loggedInUser = null; + + localStorage.removeItem('zfgc-jwt'); + localStorage.removeItem('zfgc-refresh'); + + console.log("cleared JWT"); + }; + + getLoggedInUser () { return this.loggedInUser; }; + setLoggedInUser (user) { + this.loggedInUser = user; + console.log("stored logged in user"); + }; } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 66a830a..ad28418 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1094,6 +1094,46 @@ resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18" integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== +"@fortawesome/fontawesome-common-types@^0.2.32": + version "0.2.32" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.32.tgz#3436795d5684f22742989bfa08f46f50f516f259" + integrity sha512-ux2EDjKMpcdHBVLi/eWZynnPxs0BtFVXJkgHIxXRl+9ZFaHPvYamAfCzeeQFqHRjuJtX90wVnMRaMQAAlctz3w== + +"@fortawesome/fontawesome-svg-core@^1.2.32": + version "1.2.32" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.32.tgz#da092bfc7266aa274be8604de610d7115f9ba6cf" + integrity sha512-XjqyeLCsR/c/usUpdWcOdVtWFVjPbDFBTQkn2fQRrWhhUoxriQohO2RWDxLyUM8XpD+Zzg5xwJ8gqTYGDLeGaQ== + dependencies: + "@fortawesome/fontawesome-common-types" "^0.2.32" + +"@fortawesome/free-brands-svg-icons@^5.15.1": + version "5.15.1" + resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.1.tgz#1dc0563f4036639e53d24b8e532ea78a53ca2250" + integrity sha512-pkTZIWn7iuliCCgV+huDfZmZb2UjslalXGDA2PcqOVUYJmYL11y6ooFiMJkJvUZu+xgAc1gZgQe+Px12mZF0CA== + dependencies: + "@fortawesome/fontawesome-common-types" "^0.2.32" + +"@fortawesome/free-regular-svg-icons@^5.15.1": + version "5.15.1" + resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.1.tgz#a8897d0ce325352dbba0e943101323e0175ee2b2" + integrity sha512-eD9NWFy89e7SVVtrLedJUxIpCBGhd4x7s7dhesokjyo1Tw62daqN5UcuAGu1NrepLLq1IeAYUVfWwnOjZ/j3HA== + dependencies: + "@fortawesome/fontawesome-common-types" "^0.2.32" + +"@fortawesome/free-solid-svg-icons@^5.15.1": + version "5.15.1" + resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.1.tgz#e1432676ddd43108b41197fee9f86d910ad458ef" + integrity sha512-EFMuKtzRMNbvjab/SvJBaOOpaqJfdSap/Nl6hst7CgrJxwfORR1drdTV6q1Ib/JVzq4xObdTDcT6sqTaXMqfdg== + dependencies: + "@fortawesome/fontawesome-common-types" "^0.2.32" + +"@fortawesome/react-fontawesome@^0.1.11": + version "0.1.11" + resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.11.tgz#c1a95a2bdb6a18fa97b355a563832e248bf6ef4a" + integrity sha512-sClfojasRifQKI0OPqTy8Ln8iIhnxR/Pv/hukBhWnBz9kQRmqi6JSH3nghlhAY7SUeIIM7B5/D2G8WjX0iepVg== + dependencies: + prop-types "^15.7.2" + "@hapi/address@2.x.x": version "2.1.4" resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" @@ -3387,6 +3427,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-react-context@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.3.0.tgz#546dede9dc422def0d3fc2fe03afe0bc0f4f7d8c" + integrity sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw== + dependencies: + gud "^1.0.0" + warning "^4.0.3" + cross-spawn@7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14" @@ -3415,6 +3463,15 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -3691,6 +3748,11 @@ data-urls@^1.0.0, data-urls@^1.1.0: whatwg-mimetype "^2.2.0" whatwg-url "^7.0.0" +date-fns@^2.0.1: + version "2.16.1" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b" + integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ== + debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -3722,7 +3784,7 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= -deep-equal@^1.0.1: +deep-equal@^1.0.1, deep-equal@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== @@ -3992,12 +4054,22 @@ dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" -dotenv-expand@5.1.0: +dotenv-cli@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/dotenv-cli/-/dotenv-cli-4.0.0.tgz#3cdd68b87ccd63c78dbfa72aab2f639bbeba5f4b" + integrity sha512-ByKEec+ashePEXthZaA1fif9XDtcaRnkN7eGdBDx3HHRjwZ/rA1go83Cbs4yRrx3JshsCf96FjAyIA2M672+CQ== + dependencies: + cross-spawn "^7.0.1" + dotenv "^8.1.0" + dotenv-expand "^5.1.0" + minimist "^1.1.3" + +dotenv-expand@5.1.0, dotenv-expand@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== -dotenv@8.2.0: +dotenv@8.2.0, dotenv@^8.1.0: version "8.2.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== @@ -5098,6 +5170,11 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= +gud@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" + integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== + gzip-size@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" @@ -5241,7 +5318,7 @@ 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.1.0, hoist-non-react-statics@^3.3.0: 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== @@ -6583,6 +6660,11 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jstz@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/jstz/-/jstz-2.1.1.tgz#fff3373a518fa7cce69299930466f5a2b980389d" + integrity sha512-8hfl5RD6P7rEeIbzStBz3h4f+BQHfq/ABtoU6gXKQv5OcZhnmrIpG7e1pYaZ8hS9e0mp+bxUj08fnDUbKctYyA== + jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz#8a9364e402448a3ce7f14d357738310d9248054f" @@ -8050,6 +8132,11 @@ pnp-webpack-plugin@1.6.4: dependencies: ts-pnp "^1.1.6" +popper.js@^1.14.4: + version "1.16.1" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" + integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== + portfinder@^1.0.26: version "1.0.28" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" @@ -8826,7 +8913,7 @@ prop-types-extra@^1.1.0: react-is "^16.3.2" warning "^4.0.0" -prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -8997,6 +9084,14 @@ react-app-polyfill@^1.0.6: regenerator-runtime "^0.13.3" whatwg-fetch "^3.0.0" +react-async-script@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/react-async-script/-/react-async-script-1.2.0.tgz#ab9412a26f0b83f5e2e00de1d2befc9400834b21" + integrity sha512-bCpkbm9JiAuMGhkqoAiC0lLkb40DJ0HOEJIku+9JDjxX3Rcs+ztEOG13wbrOskt3n2DTrjshhaQ/iay+SnGg5Q== + dependencies: + hoist-non-react-statics "^3.3.0" + prop-types "^15.5.0" + react-bootstrap@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/react-bootstrap/-/react-bootstrap-1.3.0.tgz#d9dde4ad554e9cd21d1465e8b5e5ef6679cae6a1" @@ -9021,6 +9116,17 @@ react-bootstrap@^1.3.0: uncontrollable "^7.0.0" warning "^4.0.3" +react-datepicker@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-3.3.0.tgz#38dec531fd7c8e0de6860a55dfbc3a8ff803d43c" + integrity sha512-QnIlBxDSWEGBi2X5P1BqWzvfnPFRKhtrsgAcujUVwyWeID/VatFaAOEjEjfD1bXR9FuSYVLlLR3j/vbG19hWOA== + dependencies: + classnames "^2.2.6" + date-fns "^2.0.1" + prop-types "^15.7.2" + react-onclickoutside "^6.9.0" + react-popper "^1.3.4" + react-dev-utils@^10.2.1: version "10.2.1" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-10.2.1.tgz#f6de325ae25fa4d546d09df4bb1befdc6dd19c19" @@ -9071,6 +9177,14 @@ react-facebook-login@^4.1.1: resolved "https://registry.yarnpkg.com/react-facebook-login/-/react-facebook-login-4.1.1.tgz#005121236a6ac0dee02099976fb1a3265f9d633e" integrity sha512-COnHEHlYGTKipz4963safFAK9PaNTcCiXfPXMS/yxo8El+/AJL5ye8kMJf23lKSSGGPgqFQuInskIHVqGqTvSw== +react-google-recaptcha@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/react-google-recaptcha/-/react-google-recaptcha-2.1.0.tgz#9f6f4954ce49c1dedabc2c532347321d892d0a16" + integrity sha512-K9jr7e0CWFigi8KxC3WPvNqZZ47df2RrMAta6KmRoE4RUi7Ys6NmNjytpXpg4HI/svmQJLKR+PncEPaNJ98DqQ== + dependencies: + prop-types "^15.5.0" + react-async-script "^1.1.1" + react-is@^16.12.0, react-is@^16.3.2, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -9081,6 +9195,11 @@ react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +react-onclickoutside@^6.9.0: + version "6.9.0" + resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.9.0.tgz#a54bc317ae8cf6131a5d78acea55a11067f37a1f" + integrity sha512-8ltIY3bC7oGhj2nPAvWOGi+xGFybPNhJM0V1H8hY/whNcXgmDeaeoCMPPd8VatrpTsUWjb/vGzrmu6SrXVty3A== + react-overlays@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-4.1.0.tgz#755a890519b02e3904845172d5223ff2dfb1bb29" @@ -9095,6 +9214,19 @@ react-overlays@^4.1.0: uncontrollable "^7.0.0" warning "^4.0.3" +react-popper@^1.3.4: + version "1.3.7" + resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.7.tgz#f6a3471362ef1f0d10a4963673789de1baca2324" + integrity sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww== + dependencies: + "@babel/runtime" "^7.1.2" + create-react-context "^0.3.0" + deep-equal "^1.1.1" + popper.js "^1.14.4" + prop-types "^15.6.1" + typed-styles "^0.0.7" + warning "^4.0.2" + 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" @@ -10794,6 +10926,11 @@ type@^2.0.0: resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3" integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow== +typed-styles@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/typed-styles/-/typed-styles-0.0.7.tgz#93392a008794c4595119ff62dde6809dbc40a3d9" + integrity sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q== + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -11058,7 +11195,7 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.x" -warning@^4.0.0, warning@^4.0.3: +warning@^4.0.0, warning@^4.0.2, warning@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==