unknown %!s(int64=2) %!d(string=hai) anos
pai
achega
b5a8b0153b

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 1
.eslintcache


+ 79 - 8
package-lock.json

@@ -30,6 +30,7 @@
         "modern-normalize": "^1.0.0",
         "react": "^17.0.1",
         "react-dom": "^17.0.1",
+        "react-dropzone": "^12.0.1",
         "react-js-pagination": "^3.0.3",
         "react-loader-spinner": "^4.0.0",
         "react-query": "^3.24.3",
@@ -4285,6 +4286,14 @@
         "node": ">= 4.5.0"
       }
     },
+    "node_modules/attr-accept": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
+      "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/autoprefixer": {
       "version": "9.8.6",
       "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz",
@@ -8617,6 +8626,22 @@
         "node": ">= 10.13.0"
       }
     },
+    "node_modules/file-selector": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.4.0.tgz",
+      "integrity": "sha512-iACCiXeMYOvZqlF1kTiYINzgepRBymz1wwjiuup9u9nayhb6g4fSwiyJ/6adli+EPwrWtpgQAh2PoS7HukEGEg==",
+      "dependencies": {
+        "tslib": "^2.0.3"
+      },
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/file-selector/node_modules/tslib": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+      "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
+    },
     "node_modules/file-uri-to-path": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@@ -16696,13 +16721,13 @@
       }
     },
     "node_modules/prop-types": {
-      "version": "15.7.2",
-      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
-      "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+      "version": "15.8.1",
+      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+      "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
       "dependencies": {
         "loose-envify": "^1.4.0",
         "object-assign": "^4.1.1",
-        "react-is": "^16.8.1"
+        "react-is": "^16.13.1"
       }
     },
     "node_modules/property-expr": {
@@ -17042,6 +17067,22 @@
         "scheduler": "^0.20.1"
       }
     },
+    "node_modules/react-dropzone": {
+      "version": "12.0.1",
+      "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.0.1.tgz",
+      "integrity": "sha512-E//nFCJfb8eDZ0zI9bOZ/v+8DOwK+7IY76Fv8u8ogfqX/d/K+32EWkFQ2+O9kF8XfUAyGtSA4cKvCjW4o78Qgg==",
+      "dependencies": {
+        "attr-accept": "^2.2.2",
+        "file-selector": "^0.4.0",
+        "prop-types": "^15.8.1"
+      },
+      "engines": {
+        "node": ">= 10.13"
+      },
+      "peerDependencies": {
+        "react": ">= 16.8"
+      }
+    },
     "node_modules/react-error-overlay": {
       "version": "6.0.8",
       "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.8.tgz",
@@ -25993,6 +26034,11 @@
       "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
       "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": {
       "version": "9.8.6",
       "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz",
@@ -29622,6 +29668,21 @@
         }
       }
     },
+    "file-selector": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.4.0.tgz",
+      "integrity": "sha512-iACCiXeMYOvZqlF1kTiYINzgepRBymz1wwjiuup9u9nayhb6g4fSwiyJ/6adli+EPwrWtpgQAh2PoS7HukEGEg==",
+      "requires": {
+        "tslib": "^2.0.3"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "2.3.1",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+          "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
+        }
+      }
+    },
     "file-uri-to-path": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@@ -36161,13 +36222,13 @@
       }
     },
     "prop-types": {
-      "version": "15.7.2",
-      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
-      "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+      "version": "15.8.1",
+      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+      "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
       "requires": {
         "loose-envify": "^1.4.0",
         "object-assign": "^4.1.1",
-        "react-is": "^16.8.1"
+        "react-is": "^16.13.1"
       }
     },
     "property-expr": {
@@ -36448,6 +36509,16 @@
         "scheduler": "^0.20.1"
       }
     },
+    "react-dropzone": {
+      "version": "12.0.1",
+      "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.0.1.tgz",
+      "integrity": "sha512-E//nFCJfb8eDZ0zI9bOZ/v+8DOwK+7IY76Fv8u8ogfqX/d/K+32EWkFQ2+O9kF8XfUAyGtSA4cKvCjW4o78Qgg==",
+      "requires": {
+        "attr-accept": "^2.2.2",
+        "file-selector": "^0.4.0",
+        "prop-types": "^15.8.1"
+      }
+    },
     "react-error-overlay": {
       "version": "6.0.8",
       "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.8.tgz",

+ 1 - 0
package.json

@@ -26,6 +26,7 @@
     "modern-normalize": "^1.0.0",
     "react": "^17.0.1",
     "react-dom": "^17.0.1",
+    "react-dropzone": "^12.0.1",
     "react-js-pagination": "^3.0.3",
     "react-loader-spinner": "^4.0.0",
     "react-query": "^3.24.3",

+ 23 - 0
src/api-data/index.ts

@@ -53,10 +53,33 @@ const loginUser = async <T>(number:string,code:string):Promise<T | undefined> =>
   }
 };
 
+const updateCredentials = async <T>(name: string,lastName: string):Promise<T | undefined> => {
+  try {
+    const { data } = await axios.patch('/users/current', { name, lastName });
+    success(`CREDENTIALS UPDATED`);
+    console.log(data)
+    return data;
+  } catch (e) {
+    console.log(e)
+    error('bad request');
+  }
+};
+const updateUserAvatar = async <T>(avatarFile: object):Promise<T | undefined> => {
+  try {
+    const { data } = await axios.patch('/users/avatars', avatarFile);
+    success(`AVATAR UPDATED`);
+    return data;
+  } catch (e) {
+    error('bad request');
+  } 
+};
+
 
 
 export {
   setToken,
   authorizeUser,
   loginUser,
+  updateCredentials,
+  updateUserAvatar
 };

+ 21 - 15
src/components/AuthPage/Registration/index.tsx

@@ -1,5 +1,9 @@
 import { makeStyles, Button, TextField, Typography } from '@material-ui/core'
-import {useState} from 'react';
+import { useState } from 'react';
+import { useDispatch } from 'react-redux';
+
+import UploadAvatar from '../../../components/DropZone/UploadAvatar'
+import { asyncCreateUser } from '../../../redux/authorization/operations'
 
 const useStyles = makeStyles({
   container: {
@@ -13,7 +17,8 @@ const useStyles = makeStyles({
     paddingBottom: 24,
   },
   title: {
-    marginBottom:20
+    marginBottom: 20,
+    textAlign: 'center'
   },
   buttonNext: {
     marginTop: 20,
@@ -26,27 +31,23 @@ const useStyles = makeStyles({
   }
 })
 
-interface IRegistration {
-}
-
-
 const Registration = () => {
   const classes = useStyles()
   const [name, setName] = useState<string>('')
   const [lastName, setLastName] = useState<string>('')
-  
+  const [upload, setUpload] = useState<object | null>(null)
+  const dispatch = useDispatch()
   const format = (a: string) => a.split(' ').join('').trim()
 
   const isValidCredentials = () => {
-    
     const validName = name.length
     const validLastName = lastName.length
     if (validName < 3 || validName > 30) return false
     if (validLastName < 3 || validLastName > 30) return false
+    if(!upload) return false
     return true
   }
 
-
   const handleTextField = (e: React.ChangeEvent<HTMLInputElement>) => {
     const value = format(e.target.value)
     const name = e.target.name
@@ -62,18 +63,22 @@ const Registration = () => {
     }
   }
 
-  const handleUpdateUser = () => {
-      console.log(name,lastName)
+  const handleUpdateUser = async() => {
+    upload&&dispatch(asyncCreateUser(name,lastName,upload))
+      console.log(name,lastName,upload)
   }
-
+ 
   return (
     <div className={classes.container} >
       <Typography
         className={classes.title}
         variant="h5"
         color="initial">
-        Fill registration form
-      </Typography>
+        Please upload avatar and fill credentials*
+      </Typography>      
+      <UploadAvatar
+        setUpload={setUpload}
+      />
       <TextField
         id="name"
         name='name'
@@ -83,6 +88,7 @@ const Registration = () => {
         variant='outlined'
         onChange={handleTextField}
         className={classes.textField}
+        required
         />
       <TextField
         id="lastName"
@@ -93,8 +99,8 @@ const Registration = () => {
         variant='outlined'
         onChange={handleTextField}
         className={classes.textField}
+        required
         />
-
       {isValidCredentials() &&
         <Button
         onClick={handleUpdateUser}

+ 1 - 4
src/components/AuthPage/index.tsx

@@ -9,12 +9,9 @@ import { authorizeUser } from '../../api-data'
 import {asyncLogin} from '../../redux/authorization/operations'
 
 
-
-
-
 const AuthPage = () => {
   const [isQR, setIsQR] = useState<boolean>(false)
-  const [isCode, setIsCode] = useState<string>('12221')
+  const [isCode, setIsCode] = useState<string>('123')
   const [isChecked, setIsChecked] = useState<boolean>(true);
   const [isReg, setIsReg] = useState<boolean>(false);
   const [country, setCountry] = useState<string>('Country')

+ 77 - 0
src/components/DropZone/UploadAvatar/index.tsx

@@ -0,0 +1,77 @@
+import { makeStyles, Typography,InputLabel,ListItem,ListItemText ,ListItemIcon } from '@material-ui/core'
+import FolderIcon from '@mui/icons-material/Folder';
+import { useDropzone } from 'react-dropzone';
+import { useEffect } from 'react';
+
+const useStyles = makeStyles({
+  container: {
+    width: '100%',
+  },
+  dropZone: {
+    display: 'flex',
+    flexDirection: 'column',
+    alignItems: 'center',
+    alignContent: 'center',
+    justifyContent: 'center',
+    width: '100%',
+    padding: '10px',
+    borderRadius: 10,
+    cursor: 'pointer',
+    marginBottom: 20,
+    outline: '2px solid  #959696',
+    '&:hover': {
+      outline: 'dashed  #0040b8',
+    },
+  },
+})
+
+interface IUploadAvatar {
+  setUpload: React.Dispatch<React.SetStateAction<object>>
+}
+function UploadAvatar({setUpload}:IUploadAvatar) {
+  const {
+    acceptedFiles,
+    getRootProps,
+    getInputProps
+  } = useDropzone({
+    accept: 'image/jpeg,image/png'
+  });
+
+  const classes = useStyles()
+  const acceptedFileItems = acceptedFiles.map((file: any) => (
+    <ListItem key={file.path}>
+      <ListItemIcon>
+        <FolderIcon/>
+      </ListItemIcon>
+      <ListItemText
+        primary="Avatar uploaded"
+          secondary={`${file.path} -${file.size} bytes`}/>
+   </ListItem>
+  ));
+
+  useEffect(() => {
+    const file = acceptedFiles[0]
+    file&&setUpload(file)
+  }, [setUpload,acceptedFiles])
+
+  return (
+    <section className={classes.container} >
+      <div {...getRootProps({ className: classes.dropZone })}>
+        <InputLabel>Drag or drop avatar*</InputLabel>
+        <input {...getInputProps()} required/>
+        <img
+          alt='drop zone img'
+          src='https://imagga.com/static/images/upload.svg'
+          width={88}
+          height={72}
+        />
+        <Typography variant="h6" color="initial">*.jpeg,*.png</Typography>
+      </div>
+      <aside>
+        <ul>{acceptedFileItems}</ul>
+      </aside>
+    </section>
+  );
+}
+
+export default UploadAvatar

+ 3 - 3
src/components/Routes/PrivateRoute/PrivateRoute.tsx

@@ -2,13 +2,13 @@ import { Route, Redirect } from 'react-router-dom';
 import { useSelector } from 'react-redux';
 
 import { IPrivateProps} from '../../../typescript/components/Routes/interfaces';
-import { getToken } from '../../../redux/authorization/selector'
+import { getName } from '../../../redux/authorization/selector'
 
 function PrivateRoute({ children, ...routeProps }: IPrivateProps) {
-  const token = useSelector(getToken)
+  const name = useSelector(getName)
   return (
     <Route {...routeProps}>
-      {token ? children : <Redirect to="/z/" />}
+      {name ? children : <Redirect to="/z/" />}
     </Route>
   );
 }

+ 3 - 3
src/components/Routes/PublicRoute/PublicRoute.tsx

@@ -2,15 +2,15 @@ import { Route, Redirect } from 'react-router-dom';
 import { useSelector } from 'react-redux';
 
 import { IPublicProps } from '../../../typescript/components/Routes/interfaces';
-import { getToken } from '../../../redux/authorization/selector'
+import { getName } from '../../../redux/authorization/selector'
 
 function PublicRoute({
   children,
   restricted = false,
   ...routeProps
 }: IPublicProps) {
-  const token = useSelector(getToken)
-  const shouldRedirect = token && restricted;
+  const name = useSelector(getName)
+  const shouldRedirect = name && restricted;
 
   return (
     <Route {...routeProps}>

+ 18 - 4
src/redux/authorization/operations/index.ts

@@ -7,16 +7,29 @@ import {
   actionLogOutReject,
 } from '../action';
 
-import { setToken, loginUser } from '../../../api-data';
+import { setToken, loginUser,updateCredentials,updateUserAvatar } from '../../../api-data';
+
+const asyncCreateUser = (name:string, lastName: string,avatar:object) => async (dispatch:any) => {
+  try {
+    dispatch(actionIsLoading(true));
+    const data = await updateCredentials(name, lastName);
+    console.log(data)
+  } catch (e) {
+    console.log(encodeURI)
+  } finally {
+    dispatch(actionIsLoading(false));
+  }
+};
 
 
 const asyncLogin = (number:string, code: string,cb:() => void ) => async (dispatch:any) => {
   try {
     dispatch(actionIsLoading(true));
-    const data = await loginUser<{token:string}>(number, code);
+    const data = await loginUser<{token:string,registration: boolean}>(number, code);
     if(!data?.token) throw new Error('bad request')
       dispatch(actionLogInSuccess(data.token))
-      setToken.set(data.token)
+    setToken.set(data.token)
+    if(!data?.registration) cb()
   } catch (e) {
     dispatch(actionLogInReject());
     cb()
@@ -29,6 +42,7 @@ const asyncLogout = () => async (dispatch:any) => {
   try {
     dispatch(actionIsLoading(true));
     dispatch(actionLogOutSuccess());
+    localStorage.removeItem('isChecked')
     setToken.unset()
   } catch (e) {
     dispatch(actionLogOutReject());
@@ -39,4 +53,4 @@ const asyncLogout = () => async (dispatch:any) => {
 
 
 
-export { asyncLogin, asyncLogout };
+export { asyncCreateUser, asyncLogin, asyncLogout };