25
.dockerignore
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
**/.classpath
|
||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/azds.yaml
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
README.md
|
||||||
|
.DS_Store
|
||||||
2
.env.template
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
REACT_APP_AUTH_SERVICE_URL = https://dev-auth.fiskerdps.com
|
||||||
|
REACT_APP_UPLOAD_SERVICE_URL = http://localhost:8080/api/upload
|
||||||
@@ -1 +0,0 @@
|
|||||||
[{"/Users/jwufiskerinc.com/Documents/GitHub/file-upload-webapp/src/index.js":"1","/Users/jwufiskerinc.com/Documents/GitHub/file-upload-webapp/src/reportWebVitals.js":"2","/Users/jwufiskerinc.com/Documents/GitHub/file-upload-webapp/src/components/ErrorBoundary.jsx":"3","/Users/jwufiskerinc.com/Documents/GitHub/file-upload-webapp/src/components/App/index.js":"4"},{"size":610,"mtime":1609866784202,"results":"5","hashOfConfig":"6"},{"size":362,"mtime":1609865788231,"results":"7","hashOfConfig":"6"},{"size":682,"mtime":1609866806365,"results":"8","hashOfConfig":"6"},{"size":406,"mtime":1609866869723,"results":"9","hashOfConfig":"6"},{"filePath":"10","messages":"11","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1qss98p",{"filePath":"12","messages":"13","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"14","messages":"15","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"16","messages":"17","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/jwufiskerinc.com/Documents/GitHub/file-upload-webapp/src/index.js",[],"/Users/jwufiskerinc.com/Documents/GitHub/file-upload-webapp/src/reportWebVitals.js",[],"/Users/jwufiskerinc.com/Documents/GitHub/file-upload-webapp/src/components/ErrorBoundary.jsx",[],"/Users/jwufiskerinc.com/Documents/GitHub/file-upload-webapp/src/components/App/index.js",[]]
|
|
||||||
24
.github/workflows/test.workflow.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
name: Node.js CI
|
||||||
|
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [12.x]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- run: npm install
|
||||||
|
- run: npm run build --if-present
|
||||||
|
- run: npm test
|
||||||
|
env:
|
||||||
|
CI: true
|
||||||
1
.gitignore
vendored
@@ -17,6 +17,7 @@
|
|||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
|
|||||||
11
Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
FROM node:12-alpine as builder
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY . .
|
||||||
|
COPY .env.template .env
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
COPY --from=builder build /usr/share/nginx/html
|
||||||
81
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
@Library('fisker') _
|
||||||
|
|
||||||
|
pipeline {
|
||||||
|
agent none
|
||||||
|
options {
|
||||||
|
ansiColor('xterm')
|
||||||
|
}
|
||||||
|
environment {
|
||||||
|
PROJECT = getProject()
|
||||||
|
ENV = getEnv()
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Build') {
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
allOf {
|
||||||
|
not {
|
||||||
|
changeRequest()
|
||||||
|
}
|
||||||
|
anyOf {
|
||||||
|
branch 'development'
|
||||||
|
branch 'main'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
agent {
|
||||||
|
kubernetes {
|
||||||
|
cloud 'dev'
|
||||||
|
inheritFrom 'fisker'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
slack("Build Started - ${env.JOB_NAME} (${env.BUILD_URL})", 'info', '#team-eng-compute-jenkins')
|
||||||
|
slack(getChanges(), 'info', '#team-eng-compute-jenkins')
|
||||||
|
container('awscli') {
|
||||||
|
ecr()
|
||||||
|
}
|
||||||
|
container('kaniko') {
|
||||||
|
buildImage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
failure {
|
||||||
|
slack("${env.JOB_NAME} build failed!", 'error', '#team-eng-compute-jenkins')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Deploy') {
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
allOf {
|
||||||
|
not {
|
||||||
|
changeRequest()
|
||||||
|
}
|
||||||
|
anyOf {
|
||||||
|
branch 'development'
|
||||||
|
branch 'main'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
agent {
|
||||||
|
kubernetes {
|
||||||
|
cloud getEnv()
|
||||||
|
inheritFrom 'fisker'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
slack("Deploying ${PROJECT} to ${ENV}... :partydeploy: ", 'info', '#team-eng-compute-jenkins')
|
||||||
|
container('helm') {
|
||||||
|
deploy(getEnv())
|
||||||
|
}
|
||||||
|
slack("Successfully deployed ${PROJECT} to ${ENV}! :tada: ", 'info', '#team-eng-compute-jenkins')
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
failure {
|
||||||
|
slack("${PROJECT} deploy to ${ENV} failed!", 'error', '#team-eng-compute-jenkins')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
README.md
@@ -1,6 +1,14 @@
|
|||||||
# Getting Started with Create React App
|
# Fisker OTA Admin Portal
|
||||||
|
|
||||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
Front-end web application for administarting OTA services
|
||||||
|
|
||||||
|
# Setup
|
||||||
|
|
||||||
|
1. Install Node 12
|
||||||
|
2. Run `npm install`
|
||||||
|
3. Setup environment variables listed in .env.template
|
||||||
|
4. Or copy .env.template to .env
|
||||||
|
5. Edit .env with the service urls for authentication and api services
|
||||||
|
|
||||||
## Available Scripts
|
## Available Scripts
|
||||||
|
|
||||||
@@ -39,24 +47,6 @@ Instead, it will copy all the configuration files and the transitive dependencie
|
|||||||
|
|
||||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
|
||||||
|
|
||||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
|
||||||
|
|
||||||
### Code Splitting
|
|
||||||
|
|
||||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
|
||||||
|
|
||||||
### Analyzing the Bundle Size
|
|
||||||
|
|
||||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
|
||||||
|
|
||||||
### Making a Progressive Web App
|
|
||||||
|
|
||||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
|
||||||
|
|
||||||
### Advanced Configuration
|
### Advanced Configuration
|
||||||
|
|
||||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
||||||
|
|||||||
2
k8s/Chart.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
name: ota-admin-portal
|
||||||
|
version: 1.0.0
|
||||||
48
k8s/templates/deployment.yaml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ .Chart.Name }}
|
||||||
|
labels:
|
||||||
|
app: {{ .Chart.Name }}
|
||||||
|
spec:
|
||||||
|
replicas: {{ .Values.replicas }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: {{ .Chart.Name }}
|
||||||
|
strategy:
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 25%
|
||||||
|
maxUnavailable: 25%
|
||||||
|
type: RollingUpdate
|
||||||
|
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: {{ .Chart.Name }}
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: {{ .Chart.Name }}
|
||||||
|
image: "{{ .Values.image.registry }}/{{ .Values.image.name }}:{{ .Values.image.tag}}"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: {{ .Values.resources.requests.cpu }}
|
||||||
|
memory: {{ .Values.resources.requests.memory }}
|
||||||
|
limits:
|
||||||
|
cpu: {{ .Values.resources.limits.cpu }}
|
||||||
|
memory: {{ .Values.resources.limits.memory }}
|
||||||
|
env:
|
||||||
|
# non-secret env vars
|
||||||
|
{{- range $name, $value := $.Values.env }}
|
||||||
|
{{- if not (empty $value) }}
|
||||||
|
- name: {{ $name | quote }}
|
||||||
|
value: {{ $value | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
# Params for env vars populated from k8s secrets
|
||||||
|
{{- range $.Values.secrets }}
|
||||||
|
- name: {{ . }}
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ $.Chart.Name }}
|
||||||
|
key: {{ . }}
|
||||||
|
{{- end }}
|
||||||
21
k8s/templates/ingress.yaml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1beta1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: nginx
|
||||||
|
labels:
|
||||||
|
app: {{ .Chart.Name }}
|
||||||
|
name: {{ .Chart.Name }}
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: {{ .Values.ingress.hostname }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
serviceName: {{ .Chart.Name }}
|
||||||
|
servicePort: 80
|
||||||
|
path: /
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- {{ .Values.ingress.hostname }}
|
||||||
|
secretName: fiskerdps-cert
|
||||||
12
k8s/templates/service.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ .Chart.Name }}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: {{ .Chart.Name }}
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 80
|
||||||
|
targetPort: 80
|
||||||
|
type: ClusterIP
|
||||||
12
k8s/values-dev.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
ingress:
|
||||||
|
hostname: dev-ota-admin.fiskerdps.com
|
||||||
|
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 250m
|
||||||
|
memory: 256Mi
|
||||||
|
|
||||||
|
replicas: 1
|
||||||
12
k8s/values-prd.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
ingress:
|
||||||
|
hostname: ota-admin.fiskerdps.com
|
||||||
|
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 250m
|
||||||
|
memory: 256Mi
|
||||||
|
|
||||||
|
replicas: 1
|
||||||
13
nginx.conf
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
events { worker_connections 1024; }
|
||||||
|
|
||||||
|
http {
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
323
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1764,6 +1764,14 @@
|
|||||||
"react-transition-group": "^4.4.0"
|
"react-transition-group": "^4.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@material-ui/icons": {
|
||||||
|
"version": "4.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.2.tgz",
|
||||||
|
"integrity": "sha512-fQNsKX2TxBmqIGJCSi3tGTO/gZ+eJgWmMJkgDiOfyNaunNaxcklJQFaFogYcFl0qFuaEz1qaXYXboa/bUXVSOQ==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.4.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@material-ui/styles": {
|
"@material-ui/styles": {
|
||||||
"version": "4.11.2",
|
"version": "4.11.2",
|
||||||
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.2.tgz",
|
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.2.tgz",
|
||||||
@@ -3067,6 +3075,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||||
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
|
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
|
||||||
},
|
},
|
||||||
|
"attr-accept": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg=="
|
||||||
|
},
|
||||||
"autoprefixer": {
|
"autoprefixer": {
|
||||||
"version": "9.8.6",
|
"version": "9.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz",
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz",
|
||||||
@@ -3096,6 +3109,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.1.1.tgz",
|
||||||
"integrity": "sha512-5Kgy8Cz6LPC9DJcNb3yjAXTu3XihQgEdnIg50c//zOC/MyLP0Clg+Y8Sh9ZjjnvBrDZU4DgXS9C3T9r4/scGZQ=="
|
"integrity": "sha512-5Kgy8Cz6LPC9DJcNb3yjAXTu3XihQgEdnIg50c//zOC/MyLP0Clg+Y8Sh9ZjjnvBrDZU4DgXS9C3T9r4/scGZQ=="
|
||||||
},
|
},
|
||||||
|
"axios": {
|
||||||
|
"version": "0.21.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
||||||
|
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
|
||||||
|
"requires": {
|
||||||
|
"follow-redirects": "^1.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"axobject-query": {
|
"axobject-query": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
||||||
@@ -6501,6 +6522,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"file-selector": {
|
||||||
|
"version": "0.1.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.1.19.tgz",
|
||||||
|
"integrity": "sha512-kCWw3+Aai8Uox+5tHCNgMFaUdgidxvMnLWO6fM5sZ0hA2wlHP5/DHGF0ECe84BiB95qdJbKNEJhWKVDvMN+JDQ==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^2.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"file-uri-to-path": {
|
"file-uri-to-path": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||||
@@ -7140,6 +7176,19 @@
|
|||||||
"resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
|
||||||
"integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
|
"integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
|
||||||
},
|
},
|
||||||
|
"history": {
|
||||||
|
"version": "4.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
|
||||||
|
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
|
||||||
|
"requires": {
|
||||||
|
"@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": {
|
"hmac-drbg": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
||||||
@@ -8078,81 +8127,6 @@
|
|||||||
"istanbul-lib-report": "^3.0.0"
|
"istanbul-lib-report": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jest": {
|
|
||||||
"version": "26.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/jest/-/jest-26.6.0.tgz",
|
|
||||||
"integrity": "sha512-jxTmrvuecVISvKFFhOkjsWRZV7sFqdSUAd1ajOKY+/QE/aLBVstsJ/dX8GczLzwiT6ZEwwmZqtCUHLHHQVzcfA==",
|
|
||||||
"requires": {
|
|
||||||
"@jest/core": "^26.6.0",
|
|
||||||
"import-local": "^3.0.2",
|
|
||||||
"jest-cli": "^26.6.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"ansi-styles": {
|
|
||||||
"version": "4.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
|
||||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
|
||||||
"requires": {
|
|
||||||
"color-convert": "^2.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"chalk": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
|
||||||
"requires": {
|
|
||||||
"ansi-styles": "^4.1.0",
|
|
||||||
"supports-color": "^7.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"color-convert": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
|
||||||
"requires": {
|
|
||||||
"color-name": "~1.1.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"color-name": {
|
|
||||||
"version": "1.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
|
||||||
},
|
|
||||||
"has-flag": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
|
|
||||||
},
|
|
||||||
"jest-cli": {
|
|
||||||
"version": "26.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz",
|
|
||||||
"integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==",
|
|
||||||
"requires": {
|
|
||||||
"@jest/core": "^26.6.3",
|
|
||||||
"@jest/test-result": "^26.6.2",
|
|
||||||
"@jest/types": "^26.6.2",
|
|
||||||
"chalk": "^4.0.0",
|
|
||||||
"exit": "^0.1.2",
|
|
||||||
"graceful-fs": "^4.2.4",
|
|
||||||
"import-local": "^3.0.2",
|
|
||||||
"is-ci": "^2.0.0",
|
|
||||||
"jest-config": "^26.6.3",
|
|
||||||
"jest-util": "^26.6.2",
|
|
||||||
"jest-validate": "^26.6.2",
|
|
||||||
"prompts": "^2.0.1",
|
|
||||||
"yargs": "^15.4.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"supports-color": {
|
|
||||||
"version": "7.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
|
||||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
|
||||||
"requires": {
|
|
||||||
"has-flag": "^4.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"jest-changed-files": {
|
"jest-changed-files": {
|
||||||
"version": "26.6.2",
|
"version": "26.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz",
|
||||||
@@ -9987,6 +9961,16 @@
|
|||||||
"object-visit": "^1.0.0"
|
"object-visit": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"material-ui-dropzone": {
|
||||||
|
"version": "3.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/material-ui-dropzone/-/material-ui-dropzone-3.5.0.tgz",
|
||||||
|
"integrity": "sha512-3BC6mz/4OEM4ZpbqMfuMN065JQyqfEbifT6/VzIua7Zj4b0DaR5YPCgpN+fL/e8yBgTs9MGBZJQY06p5pfKwvw==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.4.4",
|
||||||
|
"clsx": "^1.0.2",
|
||||||
|
"react-dropzone": "^10.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"md5.js": {
|
"md5.js": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||||
@@ -10118,6 +10102,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
||||||
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="
|
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="
|
||||||
},
|
},
|
||||||
|
"mini-create-react-context": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.12.1",
|
||||||
|
"tiny-warning": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"mini-css-extract-plugin": {
|
"mini-css-extract-plugin": {
|
||||||
"version": "0.11.3",
|
"version": "0.11.3",
|
||||||
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz",
|
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz",
|
||||||
@@ -12541,6 +12534,16 @@
|
|||||||
"scheduler": "^0.20.1"
|
"scheduler": "^0.20.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-dropzone": {
|
||||||
|
"version": "10.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-10.2.2.tgz",
|
||||||
|
"integrity": "sha512-U5EKckXVt6IrEyhMMsgmHQiWTGLudhajPPG77KFSvgsMqNEHSyGpqWvOMc5+DhEah/vH4E1n+J5weBNLd5VtyA==",
|
||||||
|
"requires": {
|
||||||
|
"attr-accept": "^2.0.0",
|
||||||
|
"file-selector": "^0.1.12",
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-error-overlay": {
|
"react-error-overlay": {
|
||||||
"version": "6.0.8",
|
"version": "6.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.8.tgz",
|
||||||
@@ -12556,6 +12559,52 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
|
||||||
"integrity": "sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg=="
|
"integrity": "sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg=="
|
||||||
},
|
},
|
||||||
|
"react-router": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==",
|
||||||
|
"requires": {
|
||||||
|
"@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"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"isarray": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||||
|
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
||||||
|
},
|
||||||
|
"path-to-regexp": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
|
||||||
|
"requires": {
|
||||||
|
"isarray": "0.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-router-dom": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==",
|
||||||
|
"requires": {
|
||||||
|
"@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-scripts": {
|
"react-scripts": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-4.0.1.tgz",
|
||||||
@@ -12620,6 +12669,113 @@
|
|||||||
"webpack-dev-server": "3.11.0",
|
"webpack-dev-server": "3.11.0",
|
||||||
"webpack-manifest-plugin": "2.2.0",
|
"webpack-manifest-plugin": "2.2.0",
|
||||||
"workbox-webpack-plugin": "5.1.4"
|
"workbox-webpack-plugin": "5.1.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"requires": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chalk": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||||
|
"requires": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"requires": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
|
},
|
||||||
|
"has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"version": "26.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jest/-/jest-26.6.0.tgz",
|
||||||
|
"integrity": "sha512-jxTmrvuecVISvKFFhOkjsWRZV7sFqdSUAd1ajOKY+/QE/aLBVstsJ/dX8GczLzwiT6ZEwwmZqtCUHLHHQVzcfA==",
|
||||||
|
"requires": {
|
||||||
|
"@jest/core": "^26.6.0",
|
||||||
|
"import-local": "^3.0.2",
|
||||||
|
"jest-cli": "^26.6.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"jest-cli": {
|
||||||
|
"version": "26.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz",
|
||||||
|
"integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==",
|
||||||
|
"requires": {
|
||||||
|
"@jest/core": "^26.6.3",
|
||||||
|
"@jest/test-result": "^26.6.2",
|
||||||
|
"@jest/types": "^26.6.2",
|
||||||
|
"chalk": "^4.0.0",
|
||||||
|
"exit": "^0.1.2",
|
||||||
|
"graceful-fs": "^4.2.4",
|
||||||
|
"import-local": "^3.0.2",
|
||||||
|
"is-ci": "^2.0.0",
|
||||||
|
"jest-config": "^26.6.3",
|
||||||
|
"jest-util": "^26.6.2",
|
||||||
|
"jest-validate": "^26.6.2",
|
||||||
|
"prompts": "^2.0.1",
|
||||||
|
"yargs": "^15.4.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"supports-color": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
|
"requires": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-shallow-renderer": {
|
||||||
|
"version": "16.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz",
|
||||||
|
"integrity": "sha512-rkIMcQi01/+kxiTE9D3fdS959U1g7gs+/rborw++42m1O9FAQiNI/UNRZExVUoAOprn4umcXf+pFRou8i4zuBg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"react-is": "^16.12.0 || ^17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-test-renderer": {
|
||||||
|
"version": "17.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.1.tgz",
|
||||||
|
"integrity": "sha512-/dRae3mj6aObwkjCcxZPlxDFh73XZLgvwhhyON2haZGUEhiaY5EjfAdw+d/rQmlcFwdTpMXCSGVk374QbCTlrA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"react-is": "^17.0.1",
|
||||||
|
"react-shallow-renderer": "^16.13.1",
|
||||||
|
"scheduler": "^0.20.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react-is": {
|
||||||
|
"version": "17.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz",
|
||||||
|
"integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-transition-group": {
|
"react-transition-group": {
|
||||||
@@ -13023,6 +13179,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||||
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
|
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
|
||||||
},
|
},
|
||||||
|
"resolve-pathname": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
|
||||||
|
},
|
||||||
"resolve-url": {
|
"resolve-url": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
|
||||||
@@ -14702,6 +14863,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
|
||||||
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
|
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
|
||||||
},
|
},
|
||||||
|
"tiny-invariant": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw=="
|
||||||
|
},
|
||||||
"tiny-warning": {
|
"tiny-warning": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||||
@@ -15177,6 +15343,11 @@
|
|||||||
"spdx-expression-parse": "^3.0.0"
|
"spdx-expression-parse": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"value-equal": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
|
||||||
|
},
|
||||||
"vary": {
|
"vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
|||||||
13
package.json
@@ -1,14 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material-ui/core": "^4.11.2",
|
"@material-ui/core": "^4.11.2",
|
||||||
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@testing-library/jest-dom": "^5.11.8",
|
"@testing-library/jest-dom": "^5.11.8",
|
||||||
"@testing-library/react": "^11.2.2",
|
"@testing-library/react": "^11.2.2",
|
||||||
"@testing-library/user-event": "^12.6.0",
|
"@testing-library/user-event": "^12.6.0",
|
||||||
|
"axios": "^0.21.1",
|
||||||
|
"material-ui-dropzone": "^3.5.0",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "4.0.1",
|
"react-scripts": "4.0.1",
|
||||||
"web-vitals": "^0.2.4"
|
"web-vitals": "^0.2.4"
|
||||||
},
|
},
|
||||||
@@ -16,6 +20,7 @@
|
|||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
|
"test:debug": "react-scripts --inspect-brk test --runInBand --no-cache",
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
@@ -35,5 +40,11 @@
|
|||||||
"last 1 firefox version",
|
"last 1 firefox version",
|
||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "12.20.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"react-test-renderer": "^17.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -10,34 +10,13 @@
|
|||||||
content="Web site created using create-react-app"
|
content="Web site created using create-react-app"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
<!--
|
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
|
||||||
-->
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
<!--
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
<title>File Upload App</title>
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
|
||||||
-->
|
|
||||||
<title>React App</title>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<!--
|
|
||||||
This HTML file is a template.
|
|
||||||
If you open it directly in the browser, you will see an empty page.
|
|
||||||
|
|
||||||
You can add webfonts, meta tags, or analytics to this file.
|
|
||||||
The build step will place the bundled scripts into the <body> tag.
|
|
||||||
|
|
||||||
To begin the development, run `npm start` or `yarn start`.
|
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
|
||||||
-->
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
BIN
public/logo-192.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
public/logo-512.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"short_name": "React App",
|
"short_name": "OTA Admin Portal",
|
||||||
"name": "Create React App Sample",
|
"name": "Fisker OTA Admin Portal",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "favicon.ico",
|
"src": "favicon.ico",
|
||||||
@@ -8,12 +8,12 @@
|
|||||||
"type": "image/x-icon"
|
"type": "image/x-icon"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "logo192.png",
|
"src": "logo-192.png",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"sizes": "192x192"
|
"sizes": "192x192"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "logo512.png",
|
"src": "logo-512.png",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"sizes": "512x512"
|
"sizes": "512x512"
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/components/404/index.jsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Typography } from "@material-ui/core";
|
||||||
|
import React from "react";
|
||||||
|
import useStyles from '../Styles';
|
||||||
|
|
||||||
|
const PageNotFound = () => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.paper}>
|
||||||
|
<Typography component="h1" variant="h2">
|
||||||
|
Page Not Found
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PageNotFound;
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
.App {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-logo {
|
|
||||||
height: 40vmin;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
|
||||||
.App-logo {
|
|
||||||
animation: App-logo-spin infinite 20s linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-header {
|
|
||||||
background-color: #282c34;
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: calc(10px + 2vmin);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-link {
|
|
||||||
color: #61dafb;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes App-logo-spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,79 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
jest.mock("../Contexts/UserContext");
|
||||||
import App from '.';
|
jest.mock("../Contexts/FileUploadContext");
|
||||||
|
|
||||||
test('renders learn react link', () => {
|
import { render, screen, cleanup, waitForElementToBeRemoved, waitFor } from "@testing-library/react"
|
||||||
render(<App />);
|
import { setToken } from "../Contexts/UserContext";
|
||||||
const linkElement = screen.getByText(/learn react/i);
|
import App from ".";
|
||||||
expect(linkElement).toBeInTheDocument();
|
|
||||||
|
const TEST_TOKEN = { accessToken: { jwtToken: "TEST" }};
|
||||||
|
const LOADING_STATUS = "Loading...";
|
||||||
|
|
||||||
|
const renderRoute = async (route) => {
|
||||||
|
window.history.pushState({}, "", route);
|
||||||
|
const { container } = render(<App />);
|
||||||
|
if (screen.queryByText(LOADING_STATUS)) {
|
||||||
|
await waitForElementToBeRemoved(() => screen.getByText(LOADING_STATUS));
|
||||||
|
}
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("App", () => {
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
setToken(null);
|
||||||
|
cleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Route / unauthenticated", async () => {
|
||||||
|
const container = await renderRoute("/");
|
||||||
|
expect(container.querySelector("h1").innerHTML).toEqual("Sign in");
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Route /signup unauthenticated", async () => {
|
||||||
|
const container = await renderRoute("/signup");
|
||||||
|
expect(container.querySelector("h1").innerHTML).toEqual("Sign up");
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Route /home unauthenticated", async () => {
|
||||||
|
const container = await renderRoute("/home");
|
||||||
|
expect(container.querySelector("h1").innerHTML).toEqual("Sign in");
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Route / authenticated", async () => {
|
||||||
|
setToken(TEST_TOKEN);
|
||||||
|
const container = await renderRoute("/");
|
||||||
|
expect(container.querySelector("h1").innerHTML).toEqual("Upload file");
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Route /signup authenticated", async () => {
|
||||||
|
setToken(TEST_TOKEN);
|
||||||
|
const container = await renderRoute("/signup");
|
||||||
|
expect(container.querySelector("h1").innerHTML).toEqual("Upload file");
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Route /home authenticated", async () => {
|
||||||
|
setToken(TEST_TOKEN);
|
||||||
|
const container = await renderRoute("/home");
|
||||||
|
expect(container.querySelector("h1").innerHTML).toEqual("Upload file");
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Route /page-not-found unauthenticated", async () => {
|
||||||
|
const container = await renderRoute("/page-not-found");
|
||||||
|
expect(container.querySelector("h1").innerHTML).toEqual("Page Not Found");
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Route /page-not-found authenticated", async () => {
|
||||||
|
setToken(TEST_TOKEN);
|
||||||
|
const container = await renderRoute("/page-not-found");
|
||||||
|
expect(container.querySelector("h1").innerHTML).toEqual("Page Not Found");
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
780
src/components/App/__snapshots__/App.test.js.snap
Normal file
@@ -0,0 +1,780 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`App Route / authenticated 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<main
|
||||||
|
class="MuiContainer-root MuiContainer-maxWidthXs"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-25"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="MuiTypography-root MuiTypography-h5"
|
||||||
|
>
|
||||||
|
Upload file
|
||||||
|
</h1>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-fileuploadprovider"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
class="makeStyles-form-27"
|
||||||
|
novalidate=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiDropzoneArea-root"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
accept=""
|
||||||
|
autocomplete="off"
|
||||||
|
multiple=""
|
||||||
|
style="display: none;"
|
||||||
|
tabindex="-1"
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="MuiDropzoneArea-textContainer"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="MuiTypography-root MuiDropzoneArea-text MuiTypography-h5"
|
||||||
|
>
|
||||||
|
Drag and drop a file here or click
|
||||||
|
</p>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiDropzoneArea-icon"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root MuiGrid-container"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root MuiGrid-item"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-text"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label"
|
||||||
|
>
|
||||||
|
Sign Out
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`App Route / unauthenticated 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<main
|
||||||
|
class="MuiContainer-root MuiContainer-maxWidthXs"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-1"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="MuiTypography-root MuiTypography-h5"
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</h1>
|
||||||
|
<form
|
||||||
|
action="{onSubmit}"
|
||||||
|
class="makeStyles-form-3"
|
||||||
|
novalidate=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-focused Mui-focused Mui-required Mui-required"
|
||||||
|
data-shrink="true"
|
||||||
|
for="email"
|
||||||
|
id="email-label"
|
||||||
|
>
|
||||||
|
Email Address
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth Mui-focused Mui-focused MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
autocomplete="email"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-7 PrivateNotchedOutline-legendNotched-8"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Email Address
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||||
|
data-shrink="false"
|
||||||
|
for="password"
|
||||||
|
id="password-label"
|
||||||
|
>
|
||||||
|
Password
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
autocomplete="current-password"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
required=""
|
||||||
|
type="password"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-7"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Password
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-4 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||||
|
tabindex="0"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label"
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root MuiGrid-container"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root MuiGrid-item"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-body2 MuiTypography-colorPrimary"
|
||||||
|
href="/signup"
|
||||||
|
>
|
||||||
|
Don't have an account? Sign Up
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`App Route /home authenticated 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<main
|
||||||
|
class="MuiContainer-root MuiContainer-maxWidthXs"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-33"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="MuiTypography-root MuiTypography-h5"
|
||||||
|
>
|
||||||
|
Upload file
|
||||||
|
</h1>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-fileuploadprovider"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
class="makeStyles-form-35"
|
||||||
|
novalidate=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiDropzoneArea-root"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
accept=""
|
||||||
|
autocomplete="off"
|
||||||
|
multiple=""
|
||||||
|
style="display: none;"
|
||||||
|
tabindex="-1"
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="MuiDropzoneArea-textContainer"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="MuiTypography-root MuiDropzoneArea-text MuiTypography-h5"
|
||||||
|
>
|
||||||
|
Drag and drop a file here or click
|
||||||
|
</p>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiDropzoneArea-icon"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root MuiGrid-container"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root MuiGrid-item"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-text"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label"
|
||||||
|
>
|
||||||
|
Sign Out
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`App Route /home unauthenticated 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<main
|
||||||
|
class="MuiContainer-root MuiContainer-maxWidthXs"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-17"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="MuiTypography-root MuiTypography-h5"
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</h1>
|
||||||
|
<form
|
||||||
|
action="{onSubmit}"
|
||||||
|
class="makeStyles-form-19"
|
||||||
|
novalidate=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-focused Mui-focused Mui-required Mui-required"
|
||||||
|
data-shrink="true"
|
||||||
|
for="email"
|
||||||
|
id="email-label"
|
||||||
|
>
|
||||||
|
Email Address
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth Mui-focused Mui-focused MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
autocomplete="email"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-21 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-23 PrivateNotchedOutline-legendNotched-24"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Email Address
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||||
|
data-shrink="false"
|
||||||
|
for="password"
|
||||||
|
id="password-label"
|
||||||
|
>
|
||||||
|
Password
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
autocomplete="current-password"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
required=""
|
||||||
|
type="password"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-21 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-23"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Password
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-20 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||||
|
tabindex="0"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label"
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root MuiGrid-container"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root MuiGrid-item"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-body2 MuiTypography-colorPrimary"
|
||||||
|
href="/signup"
|
||||||
|
>
|
||||||
|
Don't have an account? Sign Up
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`App Route /page-not-found authenticated 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-41"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="MuiTypography-root MuiTypography-h2"
|
||||||
|
>
|
||||||
|
Page Not Found
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`App Route /page-not-found unauthenticated 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-37"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="MuiTypography-root MuiTypography-h2"
|
||||||
|
>
|
||||||
|
Page Not Found
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`App Route /signup authenticated 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<main
|
||||||
|
class="MuiContainer-root MuiContainer-maxWidthXs"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-29"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="MuiTypography-root MuiTypography-h5"
|
||||||
|
>
|
||||||
|
Upload file
|
||||||
|
</h1>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-fileuploadprovider"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
class="makeStyles-form-31"
|
||||||
|
novalidate=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiDropzoneArea-root"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
accept=""
|
||||||
|
autocomplete="off"
|
||||||
|
multiple=""
|
||||||
|
style="display: none;"
|
||||||
|
tabindex="-1"
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="MuiDropzoneArea-textContainer"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="MuiTypography-root MuiDropzoneArea-text MuiTypography-h5"
|
||||||
|
>
|
||||||
|
Drag and drop a file here or click
|
||||||
|
</p>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiDropzoneArea-icon"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root MuiGrid-container"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root MuiGrid-item"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-text"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label"
|
||||||
|
>
|
||||||
|
Sign Out
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`App Route /signup unauthenticated 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<main
|
||||||
|
class="MuiContainer-root MuiContainer-maxWidthXs"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-9"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="MuiTypography-root MuiTypography-h5"
|
||||||
|
>
|
||||||
|
Sign up
|
||||||
|
</h1>
|
||||||
|
<form
|
||||||
|
class="makeStyles-form-11"
|
||||||
|
novalidate=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-focused Mui-focused Mui-required Mui-required"
|
||||||
|
data-shrink="true"
|
||||||
|
for="email"
|
||||||
|
id="email-label"
|
||||||
|
>
|
||||||
|
Email Address
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth Mui-focused Mui-focused MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
autocomplete="email"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-13 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-15 PrivateNotchedOutline-legendNotched-16"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Email Address
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||||
|
data-shrink="false"
|
||||||
|
for="password"
|
||||||
|
id="password-label"
|
||||||
|
>
|
||||||
|
Password
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
autocomplete="new-password"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
required=""
|
||||||
|
type="password"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-13 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-15"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Password
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||||
|
data-shrink="false"
|
||||||
|
for="passwordConfirm"
|
||||||
|
id="passwordConfirm-label"
|
||||||
|
>
|
||||||
|
Confirm Password
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="passwordConfirm"
|
||||||
|
name="password"
|
||||||
|
required=""
|
||||||
|
type="password"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-13 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-15"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Confirm Password
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-12 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||||
|
tabindex="0"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label"
|
||||||
|
>
|
||||||
|
Sign Up
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root MuiGrid-container"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root MuiGrid-item"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-body2 MuiTypography-colorPrimary"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
Already have an account? Sign In
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import './App.css';
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
return (
|
|
||||||
<div className="App">
|
|
||||||
<header className="App-header">
|
|
||||||
<p>
|
|
||||||
File upload demo
|
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
className="App-link"
|
|
||||||
href="https://reactjs.org"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Learn React
|
|
||||||
</a>
|
|
||||||
</header>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
13
src/components/App/index.jsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { UserProvider } from '../Contexts/UserContext';
|
||||||
|
import SiteRoutes from '../Routes/SiteRoutes';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<UserProvider>
|
||||||
|
<SiteRoutes />
|
||||||
|
</UserProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
62
src/components/Contexts/FileUploadContext.jsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import React, { useContext, useState } from "react";
|
||||||
|
import { uploadFile, getCancelToken } from "../../services/uploadFile";
|
||||||
|
|
||||||
|
const FileUploadContext = React.createContext();
|
||||||
|
|
||||||
|
export const FileUploadProvider = ({ children }) => {
|
||||||
|
const [uploading, setUploading] = useState(false);
|
||||||
|
const [progress, setProgress] = useState(0);
|
||||||
|
const [status, setStatus] = useState(null);
|
||||||
|
const [cancelUpload, setCancelUpload] = useState(null);
|
||||||
|
|
||||||
|
const done = () => {
|
||||||
|
setCancelUpload(null);
|
||||||
|
setUploading(false);
|
||||||
|
setProgress(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancel = async () => {
|
||||||
|
if (cancelUpload && progress < 100) {
|
||||||
|
cancelUpload.cancel();
|
||||||
|
setStatus("Upload cancelled");
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
|
||||||
|
const upload = async (files) => {
|
||||||
|
try {
|
||||||
|
if (!files || files.length === 0) throw new Error("No file provided");
|
||||||
|
|
||||||
|
const file = files[0].file;
|
||||||
|
const filename = file.name;
|
||||||
|
|
||||||
|
setUploading(true);
|
||||||
|
setProgress(0);
|
||||||
|
setStatus(`Uploading ${filename}`);
|
||||||
|
setCancelUpload(getCancelToken());
|
||||||
|
|
||||||
|
const result = await uploadFile(file, setProgress, cancelUpload);
|
||||||
|
const url = ((result && result.url) ? result.url : "No URL available");
|
||||||
|
setStatus(`Uploaded ${filename}\n${url}`);
|
||||||
|
setCancelUpload(null);
|
||||||
|
setProgress(100);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
setStatus(`Error occured: ${e.message}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FileUploadContext.Provider value={{
|
||||||
|
uploading,
|
||||||
|
progress,
|
||||||
|
status,
|
||||||
|
upload,
|
||||||
|
cancel,
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</FileUploadContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFileUploadContext = () => useContext(FileUploadContext);
|
||||||
61
src/components/Contexts/FileUploadContext.test.jsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
jest.mock("../../services/uploadFile");
|
||||||
|
|
||||||
|
import {uploadFile, getCancelToken, setUploadFileResponse, setUploadFileDelay, getIssuedCancelToken } from "../../services/uploadFile"
|
||||||
|
import { FileUploadProvider, useFileUploadContext } from "../Contexts/FileUploadContext";
|
||||||
|
import {render, cleanup, screen, fireEvent, waitFor} from "@testing-library/react"
|
||||||
|
|
||||||
|
describe("FileUploadContext", () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { progress, uploading, status, upload, cancel } = useFileUploadContext();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="uploading">{uploading.toString()}</div>
|
||||||
|
<div data-testid="progress">{progress.toString()}</div>
|
||||||
|
<div data-testid="status">{status}</div>
|
||||||
|
<button data-testid="uploadNoFile" onClick={() => upload()}/>
|
||||||
|
<button data-testid="upload" onClick={() => upload([{ file: { name: "test.jpg" }}])}/>
|
||||||
|
<button data-testid="cancel" onClick={() => cancel()}/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(<FileUploadProvider><TestComp /></FileUploadProvider>);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Initial state", async () => {
|
||||||
|
expect(screen.getByTestId("uploading").innerHTML).toEqual("false");
|
||||||
|
expect(screen.getByTestId("progress").innerHTML).toEqual("0");
|
||||||
|
expect(screen.getByTestId("status").innerHTML).toEqual("");
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Upload no file", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("uploadNoFile"));
|
||||||
|
expect(screen.getByTestId("uploading").innerHTML).toEqual("false");
|
||||||
|
expect(screen.getByTestId("progress").innerHTML).toEqual("0");
|
||||||
|
expect(screen.getByTestId("status").innerHTML).toEqual("Error occured: No file provided");
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Upload file", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("upload"));
|
||||||
|
await waitFor(() => expect(screen.getByTestId("progress").innerHTML).toEqual("100"));
|
||||||
|
expect(screen.getByTestId("uploading").innerHTML).toEqual("true");
|
||||||
|
expect(screen.getByTestId("status").innerHTML).toEqual("Uploaded test.jpg\nCLOUDFRONT_URL");
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Cancel upload", async () => {
|
||||||
|
setUploadFileDelay(true);
|
||||||
|
fireEvent.click(screen.getByTestId("upload"));
|
||||||
|
await waitFor(() => expect(screen.getByTestId("progress").innerHTML).toEqual("50"));
|
||||||
|
expect(screen.getByTestId("uploading").innerHTML).toEqual("true");
|
||||||
|
expect(screen.getByTestId("status").innerHTML).toEqual("Uploading test.jpg");
|
||||||
|
fireEvent.click(screen.getByTestId("cancel"));
|
||||||
|
await waitFor(() => expect(screen.getByTestId("progress").innerHTML).toEqual("0"));
|
||||||
|
expect(screen.getByTestId("uploading").innerHTML).toEqual("false");
|
||||||
|
expect(screen.getByTestId("status").innerHTML).toEqual("Upload cancelled");
|
||||||
|
})
|
||||||
|
})
|
||||||
96
src/components/Contexts/UserContext.jsx
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import auth from '../../services/auth';
|
||||||
|
|
||||||
|
const UserContext = React.createContext();
|
||||||
|
|
||||||
|
export const UserProvider = ({ children }) => {
|
||||||
|
const [fetching, setFetching] = useState(false);
|
||||||
|
const [token, setToken] = useState(null);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!localStorage) return;
|
||||||
|
const token = JSON.parse(localStorage.getItem("token"));
|
||||||
|
if (!token) return;
|
||||||
|
const { accessToken: { jwtToken }} = token;
|
||||||
|
const verifyToken = async (accessToken) => {
|
||||||
|
const result = await auth.verify(accessToken);
|
||||||
|
if (result.authenticated) {
|
||||||
|
setToken(token);
|
||||||
|
} else {
|
||||||
|
await signOut();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
verifyToken(jwtToken);
|
||||||
|
return () => {};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const signIn = async (username, password) => {
|
||||||
|
try {
|
||||||
|
if (!username) throw new Error('Email is required');
|
||||||
|
if (!password) throw new Error('Password is required');
|
||||||
|
|
||||||
|
setFetching(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
const result = await auth.signIn(username, password);
|
||||||
|
|
||||||
|
if (result.message) throw new Error(result.message);
|
||||||
|
signedIn(result);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
setError(error.message);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
setFetching(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const signUp = async (username, password, confirmPassword) => {
|
||||||
|
try {
|
||||||
|
if (!username) throw new Error('Email is required');
|
||||||
|
if (!password) throw new Error('Password is required');
|
||||||
|
if (password !== confirmPassword) throw new Error('Passwords do not match');
|
||||||
|
|
||||||
|
setFetching(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
const result = await auth.signUp(username, password);
|
||||||
|
if (result.message) throw new Error(result.message);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
setError(error.message);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
setFetching(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const signOut = async () => {
|
||||||
|
setToken(null);
|
||||||
|
if (!localStorage) return;
|
||||||
|
localStorage.removeItem("token");
|
||||||
|
};
|
||||||
|
|
||||||
|
const signedIn = (token) => {
|
||||||
|
setToken(token);
|
||||||
|
if (!localStorage || !token || !token.accessToken) return;
|
||||||
|
localStorage.setItem("token", JSON.stringify(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UserContext.Provider value={{
|
||||||
|
fetching,
|
||||||
|
token,
|
||||||
|
error,
|
||||||
|
setError,
|
||||||
|
signIn,
|
||||||
|
signUp,
|
||||||
|
signOut,
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</UserContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUserContext = () => useContext(UserContext);
|
||||||
169
src/components/Contexts/UserContext.test.jsx
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
jest.mock("../../services/auth");
|
||||||
|
|
||||||
|
import {render, cleanup, screen, fireEvent, waitFor} from "@testing-library/react"
|
||||||
|
import { UserProvider, useUserContext } from "../Contexts/UserContext";
|
||||||
|
import auth from "../../services/auth";
|
||||||
|
|
||||||
|
const TEST_TOKEN = { accessToken: { jwtToken: "TEST" }};
|
||||||
|
|
||||||
|
describe("UseContext", () => {
|
||||||
|
|
||||||
|
describe("Signup", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { signUp, error, fetching } = useUserContext();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{error}</div>
|
||||||
|
<div data-testid="fetching">{fetching.toString()}</div>
|
||||||
|
<button data-testid="signUpNoEmail" onClick={() => signUp("")}/>
|
||||||
|
<button data-testid="signUpNoPassword" onClick={() => signUp("test@test.com", "")}/>
|
||||||
|
<button data-testid="signUpBadConfirm" onClick={() => signUp("test@test.com", "password", "")}/>
|
||||||
|
<button data-testid="signUp" onClick={() => signUp("test@test.com", "password", "password")}/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(<UserProvider><TestComp /></UserProvider>);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Initial state", () => {
|
||||||
|
expect(screen.getByTestId("error").innerHTML).toEqual("");
|
||||||
|
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Error with no email address", () => {
|
||||||
|
fireEvent.click(screen.getByTestId("signUpNoEmail"));
|
||||||
|
expect(screen.getByTestId("error").innerHTML).toEqual("Email is required");
|
||||||
|
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Error with no password", () => {
|
||||||
|
fireEvent.click(screen.getByTestId("signUpNoPassword"));
|
||||||
|
expect(screen.getByTestId("error").innerHTML).toEqual("Password is required");
|
||||||
|
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Error with non-matching password", () => {
|
||||||
|
fireEvent.click(screen.getByTestId("signUpBadConfirm"));
|
||||||
|
expect(screen.getByTestId("error").innerHTML).toEqual("Passwords do not match");
|
||||||
|
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("No error sign up", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("signUp"));
|
||||||
|
await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false"));
|
||||||
|
expect(screen.getByTestId("error").innerHTML).toEqual("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Handle server error", async () => {
|
||||||
|
auth.setSignUpResponse({ message: "SERVER-ERROR", error: "ERR" });
|
||||||
|
fireEvent.click(screen.getByTestId("signUp"));
|
||||||
|
await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false"));
|
||||||
|
expect(screen.getByTestId("error").innerHTML).toEqual("SERVER-ERROR");
|
||||||
|
auth.setSignUpResponse({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Signin", () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { signIn, error, token, fetching } = useUserContext();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{error}</div>
|
||||||
|
<div data-testid="fetching">{fetching.toString()}</div>
|
||||||
|
<div data-testid="token">{JSON.stringify(token)}</div>
|
||||||
|
<button data-testid="signInNoEmail" onClick={() => signIn("")}/>
|
||||||
|
<button data-testid="signInNoPassword" onClick={() => signIn("test@test.com", "")}/>
|
||||||
|
<button data-testid="signIn" onClick={() => signIn("test@test.com", "password", "password")}/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(<UserProvider><TestComp /></UserProvider>);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Initial state", () => {
|
||||||
|
expect(screen.getByTestId("error").innerHTML).toEqual("");
|
||||||
|
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
|
||||||
|
expect(screen.getByTestId("token").innerHTML).toEqual("null");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Error with no email address", () => {
|
||||||
|
fireEvent.click(screen.getByTestId("signInNoEmail"));
|
||||||
|
expect(screen.getByTestId("error").innerHTML).toEqual("Email is required");
|
||||||
|
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
|
||||||
|
expect(screen.getByTestId("token").innerHTML).toEqual("null");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Error with no password", () => {
|
||||||
|
fireEvent.click(screen.getByTestId("signInNoPassword"));
|
||||||
|
expect(screen.getByTestId("error").innerHTML).toEqual("Password is required");
|
||||||
|
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
|
||||||
|
expect(screen.getByTestId("token").innerHTML).toEqual("null");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("No error sign in", async () => {
|
||||||
|
const TOKEN_STRING = JSON.stringify(TEST_TOKEN);
|
||||||
|
auth.setSignInResponse(TEST_TOKEN);
|
||||||
|
fireEvent.click(screen.getByTestId("signIn"));
|
||||||
|
await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false"));
|
||||||
|
expect(screen.getByTestId("error").innerHTML).toEqual("");
|
||||||
|
expect(screen.getByTestId("token").innerHTML).toEqual(TOKEN_STRING);
|
||||||
|
if (!localStorage) return;
|
||||||
|
expect(localStorage.getItem("token")).toEqual(TOKEN_STRING);
|
||||||
|
localStorage.removeItem("token");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Handle server error", async () => {
|
||||||
|
auth.setSignInResponse({ message: "SERVER-ERROR", error: "ERR" });
|
||||||
|
fireEvent.click(screen.getByTestId("signIn"));
|
||||||
|
await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false"));
|
||||||
|
expect(screen.getByTestId("error").innerHTML).toEqual("SERVER-ERROR");
|
||||||
|
auth.setSignUpResponse({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Signout", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { signIn, signOut, error, token, fetching } = useUserContext();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{error}</div>
|
||||||
|
<div data-testid="fetching">{fetching.toString()}</div>
|
||||||
|
<div data-testid="token">{JSON.stringify(token)}</div>
|
||||||
|
<button data-testid="signIn" onClick={() => signIn("test@test.com", "password", "password")}/>
|
||||||
|
<button data-testid="signOut" onClick={() => signOut()}/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(<UserProvider><TestComp /></UserProvider>);
|
||||||
|
auth.setSignInResponse(TEST_TOKEN);
|
||||||
|
fireEvent.click(screen.getByTestId("signIn"));
|
||||||
|
await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false"));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
auth.setSignInResponse({});
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Token cleared", () => {
|
||||||
|
fireEvent.click(screen.getByTestId("signOut"));
|
||||||
|
expect(screen.getByTestId("error").innerHTML).toEqual("");
|
||||||
|
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
|
||||||
|
expect(screen.getByTestId("token").innerHTML).toEqual("null");
|
||||||
|
if (!localStorage) return;
|
||||||
|
expect(localStorage.getItem('token')).toBeNull();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
21
src/components/Contexts/__mocks__/FileUploadContext.jsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
let uploading = false;
|
||||||
|
let progress = 0;
|
||||||
|
let status = null;
|
||||||
|
|
||||||
|
export const FileUploadProvider = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<div data-testid="mocked-fileuploadprovider">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFileUploadContext = () => ({
|
||||||
|
uploading,
|
||||||
|
progress,
|
||||||
|
status,
|
||||||
|
upload: jest.fn(),
|
||||||
|
cancel: jest.fn(),
|
||||||
|
});
|
||||||
35
src/components/Contexts/__mocks__/UserContext.jsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
let token = null;
|
||||||
|
let fetching = false;
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
export const UserProvider = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<div data-testid="mocked-userprovider">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUserContext = () => ({
|
||||||
|
token,
|
||||||
|
fetching,
|
||||||
|
error,
|
||||||
|
setError: jest.fn(),
|
||||||
|
signIn: jest.fn(),
|
||||||
|
signUp: jest.fn(),
|
||||||
|
signOut: jest.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const setToken = (val) => {
|
||||||
|
token = val;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setFetching = (val) => {
|
||||||
|
fetching = val;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setError = (val) => {
|
||||||
|
error = val;
|
||||||
|
};
|
||||||
@@ -11,8 +11,6 @@ export default class ErrorBoundary extends Component {
|
|||||||
return { hasError: true, error };
|
return { hasError: true, error };
|
||||||
}
|
}
|
||||||
componentDidCatch(error, errorInfo) {
|
componentDidCatch(error, errorInfo) {
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log({ error, errorInfo });
|
|
||||||
this.setState({ errorInfo });
|
this.setState({ errorInfo });
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
15
src/components/FileUploadForm/FileUploadForm.test.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
jest.mock("../Contexts/UserContext");
|
||||||
|
jest.mock("../Contexts/FileUploadContext");
|
||||||
|
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import { render, cleanup } from "@testing-library/react"
|
||||||
|
import FileUploadForm from './index';
|
||||||
|
|
||||||
|
describe("File Upload Form", () => {
|
||||||
|
|
||||||
|
it("Should render", () => {
|
||||||
|
const { container } = render(<BrowserRouter><FileUploadForm /></BrowserRouter>);
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
cleanup();
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`File Upload Form Should render 1`] = `
|
||||||
|
<div>
|
||||||
|
<main
|
||||||
|
class="MuiContainer-root MuiContainer-maxWidthXs"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-1"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="MuiTypography-root MuiTypography-h5"
|
||||||
|
>
|
||||||
|
Upload file
|
||||||
|
</h1>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-fileuploadprovider"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
class="makeStyles-form-3"
|
||||||
|
novalidate=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiDropzoneArea-root"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
accept=""
|
||||||
|
autocomplete="off"
|
||||||
|
multiple=""
|
||||||
|
style="display: none;"
|
||||||
|
tabindex="-1"
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="MuiDropzoneArea-textContainer"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="MuiTypography-root MuiDropzoneArea-text MuiTypography-h5"
|
||||||
|
>
|
||||||
|
Drag and drop a file here or click
|
||||||
|
</p>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiDropzoneArea-icon"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root MuiGrid-container"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root MuiGrid-item"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-text"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label"
|
||||||
|
>
|
||||||
|
Sign Out
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
46
src/components/FileUploadForm/index.jsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Button, Container, CssBaseline, Grid, Typography } from "@material-ui/core";
|
||||||
|
import { DropzoneAreaBase } from "material-ui-dropzone";
|
||||||
|
import { useUserContext } from "../Contexts/UserContext";
|
||||||
|
import { useFileUploadContext, FileUploadProvider } from "../Contexts/FileUploadContext";
|
||||||
|
import ModalProgressBar from "../ModalProgressBar";
|
||||||
|
import useStyles from "../Styles";
|
||||||
|
|
||||||
|
const FileUploadZone = ({ classes }) => {
|
||||||
|
const { uploading, progress, status, upload, cancel } = useFileUploadContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className={classes.form} noValidate>
|
||||||
|
<DropzoneAreaBase
|
||||||
|
maxFileSize={5e+7}
|
||||||
|
showAlerts={false}
|
||||||
|
onAdd={upload}
|
||||||
|
/>
|
||||||
|
<ModalProgressBar uploading={uploading} progress={progress} onCancel={cancel} status={status} />
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function FileUploadForm() {
|
||||||
|
const { signOut } = useUserContext();
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container component="main" maxWidth="xs">
|
||||||
|
<CssBaseline />
|
||||||
|
<div className={classes.paper}>
|
||||||
|
<Typography component="h1" variant="h5">
|
||||||
|
Upload file
|
||||||
|
</Typography>
|
||||||
|
<FileUploadProvider>
|
||||||
|
<FileUploadZone classes={classes} />
|
||||||
|
</FileUploadProvider>
|
||||||
|
<Grid container>
|
||||||
|
<Grid item >
|
||||||
|
<Button onClick={signOut}>Sign Out</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
15
src/components/MessageBar.jsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Snackbar } from "@material-ui/core";
|
||||||
|
import { useUserContext } from './Contexts/UserContext';
|
||||||
|
|
||||||
|
export const MessageBar = () => {
|
||||||
|
const { error, setError } = useUserContext();
|
||||||
|
const open = (error !== null);
|
||||||
|
|
||||||
|
return (<Snackbar
|
||||||
|
open={open}
|
||||||
|
message={error}
|
||||||
|
anchorOrigin={{ vertical: "top", horizontal: "center" }}
|
||||||
|
autoHideDuration={10000}
|
||||||
|
onClose={() => setError(null)}/>)
|
||||||
|
}
|
||||||
44
src/components/ModalProgressBar/index.jsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Modal from '@material-ui/core/Modal';
|
||||||
|
|
||||||
|
import { Button, LinearProgress } from "@material-ui/core";
|
||||||
|
|
||||||
|
const getModalStyle = () => {
|
||||||
|
const top = 30;
|
||||||
|
const left = 50;
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: `350px`,
|
||||||
|
top: `${top}%`,
|
||||||
|
left: `${left}%`,
|
||||||
|
transform: `translate(-${left}%, -${top}%)`,
|
||||||
|
backgroundColor: `white`,
|
||||||
|
border: `none`,
|
||||||
|
position: `absolute`,
|
||||||
|
margin: `1em`,
|
||||||
|
padding: `1em`,
|
||||||
|
textAlign: `center`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const ModalProgressBar = ({ onCancel, uploading, progress, status }) => {
|
||||||
|
const modalStyle = getModalStyle();
|
||||||
|
const onClickCancel = () => {
|
||||||
|
if (onCancel) onCancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={uploading}>
|
||||||
|
<div style={modalStyle}>
|
||||||
|
{status && <p>{status}</p>}
|
||||||
|
<LinearProgress variant="determinate" value={progress} />
|
||||||
|
<Button onClick={onClickCancel}>
|
||||||
|
{ progress < 100 ? "Cancel" : "Done" }
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalProgressBar;
|
||||||
18
src/components/Routes/AuthRoute.jsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Redirect, Route } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const TYPES = {
|
||||||
|
PUBLIC: 0,
|
||||||
|
GUEST: 1,
|
||||||
|
PROTECTED: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AuthRoute = ({ token, type, ...others }) => {
|
||||||
|
if (!token && type === TYPES.PROTECTED) {
|
||||||
|
return <Redirect to="/" />;
|
||||||
|
}
|
||||||
|
else if (token && type === TYPES.GUEST) {
|
||||||
|
return <Redirect to="/home" />;
|
||||||
|
}
|
||||||
|
return <Route render {...others} />;
|
||||||
|
}
|
||||||
13
src/components/Routes/ProtectedRoute.jsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Redirect, Route } from 'react-router-dom';
|
||||||
|
import { useUserContext } from '../Contexts/UserContext';
|
||||||
|
|
||||||
|
export const ProtectedRoute = ({ render, ...others }) => {
|
||||||
|
const context = useUserContext();
|
||||||
|
const { token, setError } = context;
|
||||||
|
if (!token) {
|
||||||
|
setError('Please sign in to access');
|
||||||
|
return <Redirect to="/" />;
|
||||||
|
}
|
||||||
|
return <Route render {...others} />;
|
||||||
|
}
|
||||||
34
src/components/Routes/SiteRoutes.jsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import React, { Suspense } from 'react';
|
||||||
|
import {
|
||||||
|
BrowserRouter,
|
||||||
|
Switch,
|
||||||
|
} from 'react-router-dom';
|
||||||
|
|
||||||
|
import { AuthRoute, TYPES } from '../Routes/AuthRoute'
|
||||||
|
import { MessageBar } from '../MessageBar';
|
||||||
|
import { useUserContext } from '../Contexts/UserContext';
|
||||||
|
|
||||||
|
const SignInForm = React.lazy(() => import('../SignInForm'));
|
||||||
|
const SignUpForm = React.lazy(() => import('../SignUpForm'));
|
||||||
|
const FileUploadForm = React.lazy(() => import('../FileUploadForm'));
|
||||||
|
const PageNotFound = React.lazy(() => import('../404'));
|
||||||
|
|
||||||
|
const SiteRoutes = () => {
|
||||||
|
const { token } = useUserContext();
|
||||||
|
return (
|
||||||
|
<Suspense fallback={"Loading..."}>
|
||||||
|
<MessageBar />
|
||||||
|
<BrowserRouter>
|
||||||
|
<Switch>
|
||||||
|
<AuthRoute path="/" exact render={() => <SignInForm />} type={TYPES.GUEST} token={token} />
|
||||||
|
<AuthRoute path="/signup" exact render={() => <SignUpForm />} type={TYPES.GUEST} token={token} />
|
||||||
|
<AuthRoute path="/home" render={() => <FileUploadForm />} type={TYPES.PROTECTED} token={token} />
|
||||||
|
<PageNotFound />
|
||||||
|
</Switch>
|
||||||
|
</BrowserRouter>
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default SiteRoutes;
|
||||||
15
src/components/SignInForm/SignInForm.test.jsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
jest.mock("../Contexts/UserContext");
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import { render, cleanup } from "@testing-library/react"
|
||||||
|
import SignInForm from './index';
|
||||||
|
|
||||||
|
describe("Sign In Form", () => {
|
||||||
|
|
||||||
|
it("Should render", () => {
|
||||||
|
const { container } = render(<BrowserRouter><SignInForm /></BrowserRouter>);
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
cleanup();
|
||||||
|
})
|
||||||
|
})
|
||||||
145
src/components/SignInForm/__snapshots__/SignInForm.test.jsx.snap
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Sign In Form Should render 1`] = `
|
||||||
|
<div>
|
||||||
|
<main
|
||||||
|
class="MuiContainer-root MuiContainer-maxWidthXs"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-1"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="MuiTypography-root MuiTypography-h5"
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</h1>
|
||||||
|
<form
|
||||||
|
action="{onSubmit}"
|
||||||
|
class="makeStyles-form-3"
|
||||||
|
novalidate=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-focused Mui-focused Mui-required Mui-required"
|
||||||
|
data-shrink="true"
|
||||||
|
for="email"
|
||||||
|
id="email-label"
|
||||||
|
>
|
||||||
|
Email Address
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth Mui-focused Mui-focused MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
autocomplete="email"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-7 PrivateNotchedOutline-legendNotched-8"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Email Address
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||||
|
data-shrink="false"
|
||||||
|
for="password"
|
||||||
|
id="password-label"
|
||||||
|
>
|
||||||
|
Password
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
autocomplete="current-password"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
required=""
|
||||||
|
type="password"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-7"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Password
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-4 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||||
|
tabindex="0"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label"
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root MuiGrid-container"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root MuiGrid-item"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-body2 MuiTypography-colorPrimary"
|
||||||
|
href="/signup"
|
||||||
|
>
|
||||||
|
Don't have an account? Sign Up
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import React, { useRef } from 'react';
|
||||||
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
|
import { Button, Container, CssBaseline, Grid, Link, TextField, Typography } from '@material-ui/core';
|
||||||
|
import { useUserContext } from '../Contexts/UserContext';
|
||||||
|
import useStyles from '../Styles';
|
||||||
|
|
||||||
|
export default function SignInForm() {
|
||||||
|
const classes = useStyles();
|
||||||
|
const emailEl = useRef(null);
|
||||||
|
const passwordEl = useRef(null);
|
||||||
|
const { fetching, signIn, setError } = useUserContext();
|
||||||
|
const onSubmit = async (event) => {
|
||||||
|
try {
|
||||||
|
event.preventDefault();
|
||||||
|
const username = emailEl.current.value;
|
||||||
|
const password = passwordEl.current.value;
|
||||||
|
await signIn(username, password);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
setError(e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container component="main" maxWidth="xs">
|
||||||
|
<CssBaseline />
|
||||||
|
<div className={classes.paper}>
|
||||||
|
<Typography component="h1" variant="h5">
|
||||||
|
Sign in
|
||||||
|
</Typography>
|
||||||
|
<form className={classes.form} noValidate action="{onSubmit}">
|
||||||
|
<TextField
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
id="email"
|
||||||
|
label="Email Address"
|
||||||
|
name="email"
|
||||||
|
autoComplete="email"
|
||||||
|
autoFocus
|
||||||
|
inputRef={emailEl}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
name="password"
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
autoComplete="current-password"
|
||||||
|
inputRef={passwordEl}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={fetching}
|
||||||
|
fullWidth
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
className={classes.submit}
|
||||||
|
onClick={onSubmit}
|
||||||
|
>
|
||||||
|
{ fetching ? "Signing In..." : "Sign In" }
|
||||||
|
</Button>
|
||||||
|
<Grid container>
|
||||||
|
<Grid item>
|
||||||
|
<Link component={RouterLink} to="/signup" variant="body2">
|
||||||
|
{"Don't have an account? Sign Up"}
|
||||||
|
</Link>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
14
src/components/SignUpForm/SignUpForm.test.jsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
jest.mock("../Contexts/UserContext");
|
||||||
|
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import { render, cleanup } from "@testing-library/react"
|
||||||
|
import SignUpForm from './index';
|
||||||
|
|
||||||
|
describe("Sign Up Form", () => {
|
||||||
|
|
||||||
|
it("Should render", () => {
|
||||||
|
const { container } = render(<BrowserRouter><SignUpForm /></BrowserRouter>);
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
cleanup();
|
||||||
|
})
|
||||||
|
})
|
||||||
189
src/components/SignUpForm/__snapshots__/SignUpForm.test.jsx.snap
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Sign Up Form Should render 1`] = `
|
||||||
|
<div>
|
||||||
|
<main
|
||||||
|
class="MuiContainer-root MuiContainer-maxWidthXs"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-1"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="MuiTypography-root MuiTypography-h5"
|
||||||
|
>
|
||||||
|
Sign up
|
||||||
|
</h1>
|
||||||
|
<form
|
||||||
|
class="makeStyles-form-3"
|
||||||
|
novalidate=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-focused Mui-focused Mui-required Mui-required"
|
||||||
|
data-shrink="true"
|
||||||
|
for="email"
|
||||||
|
id="email-label"
|
||||||
|
>
|
||||||
|
Email Address
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth Mui-focused Mui-focused MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
autocomplete="email"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-7 PrivateNotchedOutline-legendNotched-8"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Email Address
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||||
|
data-shrink="false"
|
||||||
|
for="password"
|
||||||
|
id="password-label"
|
||||||
|
>
|
||||||
|
Password
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
autocomplete="new-password"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
required=""
|
||||||
|
type="password"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-7"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Password
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||||
|
data-shrink="false"
|
||||||
|
for="passwordConfirm"
|
||||||
|
id="passwordConfirm-label"
|
||||||
|
>
|
||||||
|
Confirm Password
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="passwordConfirm"
|
||||||
|
name="password"
|
||||||
|
required=""
|
||||||
|
type="password"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-7"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Confirm Password
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-4 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||||
|
tabindex="0"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label"
|
||||||
|
>
|
||||||
|
Sign Up
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root MuiGrid-container"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root MuiGrid-item"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-body2 MuiTypography-colorPrimary"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
Already have an account? Sign In
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
91
src/components/SignUpForm/index.jsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import React, { useRef } from 'react';
|
||||||
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
|
import { Button, Container, CssBaseline, Grid, Link, TextField, Typography } from '@material-ui/core';
|
||||||
|
import useStyles from '../Styles';
|
||||||
|
import { useUserContext } from '../Contexts/UserContext';
|
||||||
|
|
||||||
|
export default function SignInForm() {
|
||||||
|
const { signUp, signIn, fetching, setError } = useUserContext();
|
||||||
|
const classes = useStyles();
|
||||||
|
const emailEl = useRef(null);
|
||||||
|
const passwordEl = useRef(null);
|
||||||
|
const confirmEl = useRef(null);
|
||||||
|
const onSubmit = async (event) => {
|
||||||
|
try {
|
||||||
|
event.preventDefault();
|
||||||
|
const email = emailEl.current.value;
|
||||||
|
const password = passwordEl.current.value;
|
||||||
|
const confirm = confirmEl.current.value;
|
||||||
|
await signUp(email, password, confirm);
|
||||||
|
await signIn(email, password);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
setError(e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container component="main" maxWidth="xs">
|
||||||
|
<CssBaseline />
|
||||||
|
<div className={classes.paper}>
|
||||||
|
<Typography component="h1" variant="h5">
|
||||||
|
Sign up
|
||||||
|
</Typography>
|
||||||
|
<form className={classes.form} noValidate onSubmit={onSubmit}>
|
||||||
|
<TextField
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
id="email"
|
||||||
|
label="Email Address"
|
||||||
|
name="email"
|
||||||
|
autoComplete="email"
|
||||||
|
autoFocus
|
||||||
|
inputRef={emailEl}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
name="password"
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
autoComplete="new-password"
|
||||||
|
inputRef={passwordEl}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
name="password"
|
||||||
|
label="Confirm Password"
|
||||||
|
type="password"
|
||||||
|
id="passwordConfirm"
|
||||||
|
inputRef={confirmEl}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
fullWidth
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
className={classes.submit}
|
||||||
|
disabled={fetching}
|
||||||
|
>
|
||||||
|
{ fetching ? "Signing Up..." : "Sign Up" }
|
||||||
|
</Button>
|
||||||
|
<Grid container>
|
||||||
|
<Grid item>
|
||||||
|
<Link component={RouterLink} to="/" variant="body2">
|
||||||
|
{"Already have an account? Sign In"}
|
||||||
|
</Link>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
23
src/components/Styles.jsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
paper: {
|
||||||
|
marginTop: theme.spacing(8),
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
width: '100%', // Fix IE 11 issue.
|
||||||
|
marginTop: theme.spacing(1),
|
||||||
|
},
|
||||||
|
submit: {
|
||||||
|
margin: theme.spacing(3, 0, 2),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default useStyles;
|
||||||
@@ -2,14 +2,12 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import App from './components/App';
|
import App from './components/App';
|
||||||
import ErrorBoundary from './components/ErrorBoundary';
|
// import ErrorBoundary from './components/ErrorBoundary';
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from './reportWebVitals';
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ErrorBoundary>
|
|
||||||
<App />
|
<App />
|
||||||
</ErrorBoundary>
|
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.6 KiB |
16
src/services/__mocks__/auth.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
let signInResponse = {};
|
||||||
|
let signUpResponse = {};
|
||||||
|
let verifyResponse = {};
|
||||||
|
|
||||||
|
const logResponse = (response) => {
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
signIn: async (username, password) => logResponse(signInResponse),
|
||||||
|
signUp: async (username, password) => logResponse(signUpResponse),
|
||||||
|
verify: async (accessToken) => logResponse(verifyResponse),
|
||||||
|
setSignInResponse: (value) => { signInResponse = value; },
|
||||||
|
setSignUpResponse: (value) => { signUpResponse = value; },
|
||||||
|
setVerifyResponse: (value) => { verifyResponse = value; },
|
||||||
|
}
|
||||||
29
src/services/__mocks__/uploadFile.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import delay from "../../utils/delay";
|
||||||
|
|
||||||
|
let uploadFileResponse = { url: "CLOUDFRONT_URL" };
|
||||||
|
let uploadFileDelay = false;
|
||||||
|
let issuedCancelToken = null;
|
||||||
|
|
||||||
|
export const getCancelToken = () => {
|
||||||
|
issuedCancelToken = {
|
||||||
|
cancel: jest.fn()
|
||||||
|
}
|
||||||
|
return issuedCancelToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const uploadFile = async (file, onProgress, cancelToken) => {
|
||||||
|
if (!uploadFileDelay) return uploadFileResponse;
|
||||||
|
onProgress(50);
|
||||||
|
await delay(10000);
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setUploadFileResponse = (value) => {
|
||||||
|
uploadFileResponse = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setUploadFileDelay = (value) => {
|
||||||
|
uploadFileDelay = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getIssuedCancelToken = () => issuedCancelToken;
|
||||||
35
src/services/auth.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
const AUTH_URL = process.env.REACT_APP_AUTH_SERVICE_URL;
|
||||||
|
|
||||||
|
const auth = {
|
||||||
|
signIn: (username, password) => fetch(`${AUTH_URL}/auth/login`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
})
|
||||||
|
}).then((response) => response.json()),
|
||||||
|
|
||||||
|
signUp: (username, password) => fetch(`${AUTH_URL}/auth/register`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
})
|
||||||
|
}).then((response) => response.json()),
|
||||||
|
|
||||||
|
verify: (accessToken) => fetch(`${AUTH_URL}/auth/verify`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ token: accessToken })
|
||||||
|
}).then((response) => response.json()),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default auth;
|
||||||
29
src/services/uploadFile.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const UPLOAD_ENDPOINT = process.env.REACT_APP_UPLOAD_SERVICE_URL;
|
||||||
|
|
||||||
|
export const getCancelToken = () => {
|
||||||
|
const token = axios.CancelToken;
|
||||||
|
return token.source();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const uploadFile = (file, onProgress, cancelToken) => {
|
||||||
|
const form = new FormData();
|
||||||
|
let options = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
cancelToken,
|
||||||
|
};
|
||||||
|
if (onProgress) {
|
||||||
|
options = {
|
||||||
|
...options,
|
||||||
|
onUploadProgress: (event) => {
|
||||||
|
onProgress(Math.min(99, Math.floor((event.loaded / event.total) * 100)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
form.append('file', file);
|
||||||
|
return axios.post(UPLOAD_ENDPOINT, form, options);
|
||||||
|
};
|
||||||
8
src/utils/delay.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const delay = (duration) => new Promise((resolve) => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve();
|
||||||
|
}, duration);
|
||||||
|
})
|
||||||
|
|
||||||
|
export default delay;
|
||||||