瀏覽代碼

create profile page

Alex 2 年之前
父節點
當前提交
79c7cb4987

+ 14 - 5
src/actions/ActionLogin.js

@@ -1,6 +1,9 @@
+import {actionUserCreate} from "../reducers/UserReducer";
+import {actionFullUserFindOne} from "./ActionUserFind";
+
 const {actionAuthLogin} = require("../reducers/AuthReducer");
 const {actionPromise} = require("../reducers/PromiseReducer");
-const {gql} = require("./ActionGQL");
+const {gql} = require("./PathDB");
 
 
 const actionLogin = (login, password) => {
@@ -13,11 +16,14 @@ export const actionFullLogin = (login, password) =>
     async dispatch => {
         let token = await dispatch(actionLogin(login, password))
         if (token){
-            dispatch(actionAuthLogin(token))
+            let user = await dispatch(actionAuthLogin(token))
+            if (user) {
+                localStorage?.userId &&
+                dispatch(actionFullUserFindOne(localStorage?.userId))
+            }
         }
     }
 
-
 export const actionRegister = (login, password) => {
     return actionPromise('register', gql(`mutation register($login:String, $password: String){
       UserUpsert(user:{
@@ -35,8 +41,11 @@ export const actionFullRegister = (login, password) =>
         if (allow) {
             let token = await dispatch(actionLogin(login, password))
             if (token) {
-                console.log('good')
-                dispatch(actionAuthLogin(token))
+                let user = await dispatch(actionAuthLogin(token))
+                if (user) {
+                    localStorage?.userId &&
+                    dispatch(actionFullUserFindOne(localStorage?.userId))
+                }
             }
         }
     }

+ 21 - 0
src/actions/ActionUserFind.js

@@ -0,0 +1,21 @@
+import {actionUserCreate} from "../reducers/UserReducer";
+const {actionPromise} = require("../reducers/PromiseReducer");
+const {gql} = require("./PathDB");
+
+export const actionUserFindOne = (_id) => {
+    return actionPromise('userfindone', gql(`query userfindone($q: String){
+      UserFindOne(query: $q){
+        _id createdAt login nick acl avatar{
+            _id text url originalFileName
+          }
+        }
+      }`,  {q: JSON.stringify([{_id}])}))
+}
+
+export const actionFullUserFindOne = (_id) =>
+    async dispatch => {
+        let value = await dispatch(actionUserFindOne(_id))
+        if (value){
+            dispatch(actionUserCreate(value))
+        }
+    }

src/actions/ActionGQL.js → src/actions/PathDB.js


+ 12 - 6
src/components/Header.jsx

@@ -9,12 +9,18 @@ import {useState} from "react";
 import '../scss/Header.scss';
 import {actionAuthLogout} from "../reducers/AuthReducer";
 import {connect} from "react-redux";
+import {actionUserRemove} from "../reducers/UserReducer";
+import {actionFullUserFindOne} from "../actions/ActionUserFind";
 
 const pages = ['catalog', 'about us', 'our team', 'faq', 'contact']
 const settingsDefaultUserAuth = ['Profile', 'Logout']
 // const settingsAdmin = ['Profile', 'Dashboard', 'Logout']
 
-const Header = () => {
+const Header = ({user={}, createUser}) => {
+    if (localStorage.authToken && localStorage.userId && Object.keys(user).length === 0){
+        createUser(localStorage.userId)
+    }
+
     const [anchorElNav, setAnchorElNav] = useState(null);
     const [anchorElUser, setAnchorElUser] = useState(null);
 
@@ -148,17 +154,17 @@ const Header = () => {
     }
     const CUserIcon = connect(state => ({auth: state.auth}))(UserIcon)
 
-    const ButtonLogOut = ({actionLogOut}) => {
+    const ButtonLogOut = ({actionLogOut, actionUserRemove}) => {
         return (
             <Button
-                onClick={() => {actionLogOut(); handleCloseNavMenu()}}
+                onClick={() => {actionLogOut(); actionUserRemove(); handleCloseNavMenu()}}
                 style={{textDecoration: 'none', color: '#fff', textAlign: "center", width: '100%'}}
             >
                 Log out
             </Button>
         )
     }
-    const CButtonLogOut = connect(null, {actionLogOut: actionAuthLogout})(ButtonLogOut)
+    const CButtonLogOut = connect(null, {actionLogOut: actionAuthLogout, actionUserRemove: actionUserRemove})(ButtonLogOut)
 
     return (
         <AppBar sx={{padding: '10px 0', backgroundColor: 'rgb(131,179,175)'}} className='Header' position="fixed" enableColorOnDark={true}>
@@ -239,5 +245,5 @@ const Header = () => {
         </AppBar>
     );
 };
-
-export default Header;
+const CHeader = connect(state => ({user: state.user}), {createUser: actionFullUserFindOne})(Header)
+export default CHeader;

+ 3 - 5
src/pages/MyAccountPage.jsx

@@ -5,8 +5,6 @@ import {
     Box,
     Button,
     Container,
-    FormControl,
-    OutlinedInput,
     TextField,
     Typography,
     useMediaQuery
@@ -19,7 +17,7 @@ import {connect} from 'react-redux';
 import {actionFullLogin, actionFullRegister} from "../actions/ActionLogin";
 import Redirect from "react-router-dom/es/Redirect";
 
-const MyAccountPage = ({auth, promise, onLogin, onRegister}) => {
+const MyAccountPage = ({auth, promise, user, onLogin, onRegister}) => {
     const matches = useMediaQuery('(max-width:899px)')
     const [status, setStatus] = useState('login')
 
@@ -106,7 +104,7 @@ const MyAccountPage = ({auth, promise, onLogin, onRegister}) => {
             <Breadcrumb links={['my account']}/>
             <main style={{backgroundColor: "#f3f3f3", padding: matches ? "20px 0" : "50px 0"}}>
                 <Container maxWidth="sm">
-                    {auth?.payload ? <Redirect to={'/profile'}/> :
+                    {(auth?.payload && Object.keys(user).length !== 0) ? <Redirect to={'/profile'}/>:
                         status === 'login' ?
                             <Form title={'LOGIN'} target={'register'} onClickEvent={onLogin} children={
                                 <>
@@ -151,6 +149,6 @@ const MyAccountPage = ({auth, promise, onLogin, onRegister}) => {
         </>
     )
 }
-const CLoginForm = connect(state => ({auth: state.auth, promise: state.promise}), {onLogin: actionFullLogin, onRegister: actionFullRegister})(MyAccountPage)
+const CLoginForm = connect(state => ({auth: state.auth, promise: state.promise, user: state.user}), {onLogin: actionFullLogin, onRegister: actionFullRegister})(MyAccountPage)
 
 export default CLoginForm

+ 207 - 7
src/pages/ProfilePage.jsx

@@ -1,15 +1,215 @@
 import Header from "../components/Header";
 import Footer from "../components/Footer";
 import Breadcrumb from "../components/Breadcrumbs";
+import {connect} from "react-redux";
+import {Box, Button, Container, Grid, TextField, Typography, useMediaQuery} from "@mui/material";
+import Redirect from "react-router-dom/es/Redirect";
+import Tabs from "@mui/material/Tabs";
+import Tab from "@mui/material/Tab";
+import {useRef, useState} from "react";
+import PropTypes from "prop-types";
+import ManageAccountsIcon from '@mui/icons-material/ManageAccounts';
+import CancelIcon from '@mui/icons-material/Cancel';
+import SendAndArchiveIcon from '@mui/icons-material/SendAndArchive';
+import {actionAuthLogout} from "../reducers/AuthReducer";
+import {actionUserRemove} from "../reducers/UserReducer";
+
+function TabPanel(props) {
+    const { children, value, index, ...other } = props;
 
-const ProfilePage = () => {
     return (
-        <>
-            <Header/>
-            <Breadcrumb links={['Profile']}/>
+        <div
+            role="tabpanel"
+            hidden={value !== index}
+            id={`vertical-tabpanel-${index}`}
+            aria-labelledby={`vertical-tab-${index}`}
+            style={{width: '100%'}}
+            {...other}
+        >
+            {value === index && (
+                <Box sx={{ p: 3}}>
+                    <Typography>{children}</Typography>
+                </Box>
+            )}
+        </div>
+    );
+}
+TabPanel.propTypes = {
+    children: PropTypes.node,
+    index: PropTypes.number.isRequired,
+    value: PropTypes.number.isRequired,
+};
+function a11yProps(index) {
+    return {
+        id: `vertical-tab-${index}`,
+        'aria-controls': `vertical-tabpanel-${index}`,
+    };
+}
 
-            <Footer/>
-        </>
+const ItemTabsAccountDefault = ({title, content}) => {
+    const matches = useMediaQuery('(max-width:899px)')
+
+    return(
+        <Grid item xs={6} sm={4} marginBottom='20px'>
+            <Typography
+                color='#616161'
+                fontWeight='300'
+                marginBottom='5px'
+                fontSize={matches ? '13px' : '16px'}
+            >
+                {title}
+            </Typography>
+            <Typography
+                color='#000'
+                fontWeight='400'
+                fontSize={matches ? '16px' : '22px'}
+            >
+                {content}
+            </Typography>
+        </Grid>
     )
 }
-export default ProfilePage
+const FormUpload = ({user, time, setStatus}) => {
+    return (
+        <form action="/upload" method="post" encType="multipart/form-data" id='formProfile'>
+            <Grid container spacing={2} justifyContent={'space-between'} alignItems={'center'} textAlign={'center'}>
+                <TextField sx={{color: '#000'}} label={'Login'} variant="outlined" value={user?.login}/>
+                <TextField sx={{color: '#000'}} label={'Nick'} variant="outlined" value={user?.nick} />
+                <Button variant="contained" component="Avatar">Choose a new avatar<input type="file" hidden/></Button>
+                <ItemTabsAccountDefault title={'Status account'} content={user?.acl[1]}/>
+                <ItemTabsAccountDefault title={'Account creation date'} content={time}/>
+                <Grid item xs={12} md={4} display='flex' justifyContent={'center'} alignItems={'center'}>
+                    <Button
+                        style={{ color: '#1976d2'}}
+                        fullWidth
+                        type='submit'
+                        marginRight='20px'
+                    >
+                        <SendAndArchiveIcon style={{marginRight: '5px'}}/>
+                        Save
+                    </Button>
+                    <Button
+                        style={{ color: '#1976d2'}}
+                        fullWidth
+                        onClick={() => setStatus(false)}
+                        marginRight='20px'
+                    >
+                        <CancelIcon style={{marginRight: '5px'}}/>
+                        Cancel
+                    </Button>
+                </Grid>
+            </Grid>
+        </form>
+    )
+}
+
+const AccountDetails = ({user, time}) => {
+    const [status, setStatus] = useState(false)
+
+    return (
+        !status ?
+            <Grid container spacing={2} justifyContent={'space-between'} alignItems={'center'} textAlign={'center'}>
+                <ItemTabsAccountDefault title={'Login'} content={user?.login}/>
+                <ItemTabsAccountDefault title={'Nick'} content={user?.nick}/>
+                <ItemTabsAccountDefault title={'Status account'} content={user?.acl[1]}/>
+                <ItemTabsAccountDefault title={'Account creation date'} content={time}/>
+                <ItemTabsAccountDefault title={'Avatar'}
+                                 content={
+                                     (user?.avatar && <img src={user?.avatar} alt={'User avatar'}/>) || 'Not installed'
+                                 }
+                />
+                <Grid item xs={12} md={4}>
+                    <Typography
+                        sx={{cursor: 'pointer'}}
+                        color={'#1976d2'}
+                        display='flex'
+                        justifyContent='center'
+                        alignItems='center'
+                        variant='h6'
+                        onClick={() => setStatus(true)}
+                    >
+                        <ManageAccountsIcon style={{marginRight: '10px'}}/>
+                        Edit data
+                    </Typography>
+                </Grid>
+            </Grid> :
+            <FormUpload user={user} time={time} setStatus={value => setStatus(value)}/>
+    )
+}
+
+
+const ProfilePage = ({user = {}, authLogOut, userLogOut}) => {
+    const matches = useMediaQuery('(max-width:899px)')
+    const matches2 = useMediaQuery('(max-width:768px)')
+    const [value, setValue] = useState(0)
+
+    const handleChange = (event, newValue) => {
+        setValue(newValue);
+    };
+
+    let formattedTime = 0;
+    if (Object.keys(user).length !== 0) {
+        let date = new Date(+user.createdAt);
+        let year = date.getFullYear();
+        let month = "0" + date.getMonth();
+        let day = "0" + date.getDate();
+        let hours = "0" + date.getHours();
+        let minutes = "0" + date.getMinutes();
+        let seconds = "0" + date.getSeconds();
+        formattedTime = day.substr(-2) + '.' + month.substr(-2) + '.' + year +
+            ' ' + hours.substr(-2) + ':' + minutes.substr(-2) + ':' + seconds.substr(-2);
+    }
+
+    return (
+        Object.keys(user).length === 0 ? <Redirect to={'/my-account'}/> :
+            <>
+                <Header/>
+                <Breadcrumb links={['Profile']}/>
+                <main style={{backgroundColor: "#f3f3f3", padding: matches ? "20px 0" : "50px 0"}}>
+                    <Container maxWidth="lg">
+                        <Box>
+                            <Typography
+                                variant='h5'
+                                textAlign='center'
+                                fontFamily='sarif'
+                                marginBottom={matches ? '20px':'40px'}
+                            >
+                                LOGGED IN AS <strong>{user.login.toUpperCase()}</strong>
+                            </Typography>
+                        </Box>
+                        <Box
+                            sx={{ flexGrow: 1, bgcolor: '#fff', display: 'flex', height: '100%', alignItems: 'center'}}
+                            flexDirection={matches2 ? 'column': "row"}
+                        >
+                            <Tabs
+                                orientation={matches2 ? 'horizontal': "vertical"}
+                                variant="scrollable"
+                                value={value}
+                                onChange={handleChange}
+                                aria-label="Profile settings"
+                                sx={{ borderRight: 1, borderColor: 'divider', padding: '50px 0', height: '100%'}}
+                            >
+                                <Tab sx={{padding: '0 50px', textAlign: 'center'}} label={'ACCOUNT DETAILS'} {...a11yProps(0)} />
+                                <Tab sx={{padding: '0 50px', textAlign: 'center'}} label={'my orders'} {...a11yProps(1)} />
+                                <Tab sx={{padding: '0 50px', textAlign: 'center'}} label={'wish list'} {...a11yProps(2)} />
+                                <Button onClick={() => {authLogOut(); userLogOut()}}>Logout</Button>
+                            </Tabs>
+                            <TabPanel value={value} index={0}>
+                                <AccountDetails user={user} time={formattedTime}/>
+                            </TabPanel>
+                            <TabPanel value={value} index={1}>
+                                my orders
+                            </TabPanel>
+                            <TabPanel value={value} index={2}>
+                                wish list
+                            </TabPanel>
+                        </Box>
+                    </Container>
+                </main>
+                <Footer/>
+            </>
+    )
+}
+const CProfilePage = connect(state => ({user: state.user}), {authLogOut: actionAuthLogout, userLogOut: actionUserRemove})(ProfilePage)
+
+export default CProfilePage

+ 2 - 0
src/reducers/AuthReducer.js

@@ -20,6 +20,7 @@ export const AuthReducer = (state, { type, token }) => {
         localStorage.setItem('authToken', token)
         let payload = jwtDecode(token)
         if (typeof payload === 'object') {
+            localStorage.setItem('userId', payload?.sub?.id)
             return {
                 ...state,
                 token,
@@ -29,6 +30,7 @@ export const AuthReducer = (state, { type, token }) => {
     }
     if (type === 'AUTH_LOGOUT') {
         localStorage.removeItem('authToken')
+        localStorage.removeItem('userId')
         return {}
     }
     return state

+ 3 - 1
src/reducers/CombineReducers.js

@@ -2,9 +2,11 @@ import {combineReducers} from "redux";
 import {AuthReducer} from "./AuthReducer";
 import {PromiseReducer} from "./PromiseReducer";
 import {CartReducer} from "./CartReducer";
+import {UserReducer} from "./UserReducer";
 
 export const rootReducer = combineReducers({
     auth: AuthReducer,
     promise: PromiseReducer,
-    cart: CartReducer
+    cart: CartReducer,
+    user: UserReducer
 })

+ 39 - 0
src/reducers/UserReducer.js

@@ -0,0 +1,39 @@
+export const UserReducer = (state={}, { type, user={} }) => {
+    if (type === 'USER_CREATE') {
+        if (Object.entries(user).length !== 0) {
+            return {
+                ...state,
+                ...user
+            }
+        }
+        else
+            return state
+    }
+    if (type === 'USER_CHANGE') {
+        if(Object.entries(user).length !== 0) {
+            let changeUser = {}
+            for (let key in state) {
+                if (state[key] === user[key]) {
+                    changeUser[key] = state[key]
+                }
+                else {
+                    changeUser[key] = user[key]
+                }
+            }
+            return {
+                ...changeUser
+            }
+        }
+        else {
+            return state
+        }
+    }
+    if (type === 'USER_REMOVE') {
+        return {}
+    }
+    return state
+}
+
+export const actionUserCreate = user => ({ type: 'USER_CREATE', user })
+export const actionUserChange = user => ({ type: 'USER_CHANGE', user })
+export const actionUserRemove = () => ({ type: 'USER_REMOVE' })