Browse Source

15.03.2023 00:30

Volddemar4ik 1 year ago
parent
commit
a4d33dfce8

File diff suppressed because it is too large
+ 3284 - 30
js/Project/project/package-lock.json


+ 7 - 0
js/Project/project/package.json

@@ -3,6 +3,9 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "@dnd-kit/core": "^6.0.8",
+    "@dnd-kit/sortable": "^7.0.2",
+    "@dnd-kit/utilities": "^3.2.1",
     "@emotion/react": "^11.10.6",
     "@emotion/styled": "^11.10.6",
     "@fontsource/roboto": "^4.5.8",
@@ -11,6 +14,7 @@
     "@testing-library/jest-dom": "^5.16.5",
     "@testing-library/react": "^13.4.0",
     "@testing-library/user-event": "^13.5.0",
+    "array-move": "^4.0.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
     "react-dropzone": "^14.2.3",
@@ -46,5 +50,8 @@
       "last 1 firefox version",
       "last 1 safari version"
     ]
+  },
+  "devDependencies": {
+    "node-sass": "^7.0.3"
   }
 }

BIN
js/Project/project/public/images/noPhoto.png


BIN
js/Project/project/public/images/noimage.png


+ 8 - 6
js/Project/project/src/App.js

@@ -11,6 +11,7 @@ import { CUser } from './components/user';
 import { ReduxFeed } from './components/feed';
 import { CComments } from './components/post';
 import { CreatePost } from './components/create_post';
+import Authorization from './components/auth_reg';
 import Header from './components/structure/header';
 import Footer from './components/structure/footer';
 
@@ -20,10 +21,10 @@ export const url = 'http://hipstagram.node.ed.asmer.org.ua/graphql'
 
 const history = createHistory()
 
-const Autorization = () =>
-    <div>
-        Авторизация
-    </div>
+// const Autorization = () =>
+//     <div>
+//         Авторизация
+//     </div>
 
 const Registration = () =>
     <div>
@@ -49,8 +50,9 @@ function App() {
                     <Header />
                     <main style={{ flexGrow: '1' }}>
                         <Switch>
-                            <Route path="/" component={ReduxFeed} exact />
-                            <Route path="/autorization" component={Autorization} />
+                            {/* <Route path="/" component={ReduxFeed} exact /> */}
+                            <Route path="/feed" component={ReduxFeed} />
+                            <Route path="/authorization" component={Authorization} />
                             <Route path="/registration" component={Registration} />
                             <Route path="/post/:postId" component={CComments} />
                             <Route path="/user/:userId" component={CUser} />

+ 149 - 0
js/Project/project/src/components/auth_reg/index.js

@@ -0,0 +1,149 @@
+import { store } from '../redux';
+import { useState } from "react";
+import { Link, Redirect, useHistory } from "react-router-dom";
+import { actionFullLogin } from "../redux/action";
+
+import { Paper, Box, Typography, Button, Stack, OutlinedInput, IconButton, InputLabel, InputAdornment, FormControl } from "@mui/material";
+
+import { Visibility, VisibilityOff } from '@mui/icons-material';
+
+// поля логин/пароль/кнопка входа
+const LoginForm = ({ onLogin }) => {
+    // отслеживаем введение данных в полях логин/пароль
+    const [login, setLogin] = useState('')
+    const [pass, setPass] = useState('')
+
+    // включаем,выклюаем отображение пароля
+    const [showPassword, setShowPassword] = useState(false);
+    const handleClickShowPassword = () => setShowPassword((show) => !show);
+    const handleMouseDownPassword = (event) => {
+        event.preventDefault();
+    };
+
+
+
+    return (
+        <Box sx={{ width: '100%' }}>
+            <Stack spacing={2}>
+                <FormControl variant="outlined">
+                    <InputLabel htmlFor="outlined-adornment-password">Имя пользователя</InputLabel>
+                    <OutlinedInput
+                        id="login"
+                        label="Имя пользователя"
+                        type="text"
+                        size="small"
+                        value={login}
+                        onChange={e => setLogin(e.target.value)}
+                    />
+                </FormControl>
+
+                <FormControl variant="outlined">
+                    <InputLabel htmlFor="outlined-adornment-password">Пароль</InputLabel>
+                    <OutlinedInput
+                        id="password"
+                        label="Пароль"
+                        size="small"
+                        type={showPassword ? 'text' : 'password'}
+                        value={pass}
+                        onChange={e => setPass(e.target.value)}
+
+                        endAdornment={
+                            <InputAdornment position="end">
+                                <IconButton
+                                    aria-label="toggle password visibility"
+                                    onClick={handleClickShowPassword}
+                                    onMouseDown={handleMouseDownPassword}
+                                    edge="end"
+                                >
+                                    {showPassword ? <VisibilityOff /> : <Visibility />}
+                                </IconButton>
+                            </InputAdornment>
+                        }
+                    />
+                </FormControl>
+
+                <Button
+                    variant="contained"
+                    disabled={((login == '') || (pass == '')) || false}
+                    onClick={() => onLogin(login, pass)}
+                >
+                    Войти
+                </Button>
+            </Stack>
+        </Box>
+    )
+}
+
+// Вся страница формы
+export default function Authorization() {
+    const history = useHistory();
+
+    return (
+        <Box sx={{
+            display: 'grid',
+            height: '80vh',
+            justifyContent: 'center',
+            alignItems: 'center'
+        }}>
+            <Paper elevation={3}
+                sx={{
+                    padding: '5px',
+                    width: '400px',
+                    height: '500px',
+                    display: 'grid',
+                    justifyContent: 'center',
+                    alignItems: 'center'
+                }}>
+                <Typography
+                    variant="h4"
+                    noWrap
+                    sx={{
+                        display: 'grid',
+                        fontFamily: 'monospace',
+                        fontWeight: 700,
+                        letterSpacing: '.3rem',
+                        color: 'inherit',
+                        textDecoration: 'none',
+                        flexGrow: '1',
+                        marginTop: '20px',
+                        justifyContent: 'center'
+                    }} >
+                    Hipstagram
+                </Typography>
+
+                <Box
+                    // className='testtt'
+                    sx={{
+                        display: 'grid',
+                        alignSelf: 'start'
+                    }}>
+                    <LoginForm
+                        onLogin={async (login, pass) => {
+                            await store.dispatch(actionFullLogin(login, pass))
+
+                            if ((Object.keys(store.getState().auth)).length) {
+                                history.push('/feed')
+                            }
+                        }}
+                    />
+                </Box>
+
+
+                <Typography
+                    variant="subtitle1"
+                    sx={{
+                        display: 'grid',
+                        alignSelf: 'end',
+                        justifyContent: 'center',
+                    }}>
+                    <Link
+                        color="inherit"
+                        to='/registration'
+                    >
+                        Зарегистрироваться
+                    </Link>
+                </Typography>
+            </Paper>
+        </Box >
+    )
+}

+ 120 - 0
js/Project/project/src/components/create_post/dnd.js

@@ -0,0 +1,120 @@
+import * as React from 'react';
+import { useState, useEffect } from 'react';
+
+import {
+    DndContext,
+    KeyboardSensor,
+    PointerSensor,
+    useSensor,
+    useSensors, useDroppable
+} from "@dnd-kit/core";
+import { sortableKeyboardCoordinates, rectSortingStrategy, SortableContext, useSortable, horizontalListSortingStrategy } from "@dnd-kit/sortable";
+import { CSS } from "@dnd-kit/utilities";
+import { arrayMoveImmutable } from 'array-move';
+
+
+const SortableItem = (props) => {
+    const {
+        attributes,
+        listeners,
+        setNodeRef,
+        transform,
+        transition
+    } = useSortable({ id: props.id });
+
+    const itemStyle = {
+        transform: CSS.Transform.toString(transform),
+        transition,
+        //width: 110,
+        //height: 30,
+        //display: "flex",
+        //alignItems: "center",
+        //paddingLeft: 5,
+        //border: "1px solid gray",
+        //borderRadius: 5,
+        //marginBottom: 5,
+        //userSelect: "none",
+        cursor: "grab",
+        //boxSizing: "border-box"
+    };
+
+    const Render = props.render
+
+    return (
+        <div style={itemStyle} ref={setNodeRef} {...attributes} {...listeners}>
+            <Render {...{ [props.itemProp]: props.item }} />
+        </div>
+    );
+};
+
+
+const Droppable = ({ id, items, itemProp, keyField, render }) => {
+
+    return (
+        <SortableContext id={id} items={items} strategy={rectSortingStrategy}>
+            {items.map((item) => (
+                <SortableItem render={render} key={item[keyField]} id={item}
+                    itemProp={itemProp} item={item} />
+            ))}
+        </SortableContext>
+    );
+};
+
+
+export function Dnd({ items: startItems, render, itemProp, keyField, onChange, horizontal }) {
+    const [items, setItems] = useState(
+        startItems
+    );
+    useEffect(() => setItems(startItems), [startItems])
+
+    useEffect(() => {
+        if (typeof onChange === 'function') {
+            onChange(items)
+        }
+    }, [items])
+
+    const sensors = useSensors(
+        useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
+        useSensor(KeyboardSensor, {
+            coordinateGetter: sortableKeyboardCoordinates
+        })
+    );
+
+    const handleDragEnd = ({ active, over }) => {
+        const activeIndex = active.data.current.sortable.index;
+        const overIndex = over.data.current?.sortable.index || 0;
+
+        setItems((items) => {
+            return arrayMoveImmutable(items, activeIndex, overIndex)
+        });
+    }
+
+    const containerStyle = {
+        display: horizontal ? "flex" : '',
+        flex: 1,
+        flexDirection: 'row',
+        alignItems: 'flex-start',
+        flexWrap: 'wrap',
+        alignContent: 'flex-start',
+        justifyContent: 'center',
+        padding: '5px',
+        backgroundColor: '#fafafa',
+        color: '#bdbdbd',
+        position: 'relative'
+    };
+
+    return (
+        <DndContext
+            sensors={sensors}
+            onDragEnd={handleDragEnd}
+        >
+            <div style={containerStyle}>
+                <Droppable id="aaa"
+                    items={items}
+                    itemProp={itemProp}
+                    keyField={keyField}
+                    render={render} />
+            </div>
+        </DndContext>
+    );
+}

+ 21 - 30
js/Project/project/src/components/create_post/dropzone.js

@@ -1,34 +1,13 @@
 import React, { useCallback, useMemo } from 'react'
 import { useDropzone } from 'react-dropzone'
-
-export function MyDropzone() {
-    const onDrop = useCallback(acceptedFiles => {
-        // Do something with the files
-    }, [])
-    const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop })
-
-    return (
-        <div {...getRootProps()}>
-            <input {...getInputProps()} />
-            {
-                isDragActive ?
-                    <p>Drop the files here ...</p> :
-                    <p>Drag 'n' drop some files here, or click to select files</p>
-            }
-        </div>
-    )
-}
-
-
-// import React, { useMemo } from 'react';
-// import { useDropzone } from 'react-dropzone';
+import { Box, Stack } from '@mui/system';
 
 const baseStyle = {
     flex: 1,
     display: 'flex',
     flexDirection: 'column',
     alignItems: 'center',
-    padding: '20px',
+    padding: '30px',
     borderWidth: 2,
     borderRadius: 2,
     borderColor: '#eeeeee',
@@ -51,14 +30,19 @@ const rejectStyle = {
     borderColor: '#ff1744'
 };
 
-export function StyledDropzone(props) {
+
+export function StyledDropzone({ onFiles }) {
+    const onDrop = useCallback(acceptedFiles => {
+        onFiles(acceptedFiles)
+    }, [])
+
     const {
         getRootProps,
         getInputProps,
         isFocused,
         isDragAccept,
         isDragReject
-    } = useDropzone({ accept: { 'image/*': [] } });
+    } = useDropzone({ onDrop, accept: { 'image/*': [] } });
 
     const style = useMemo(() => ({
         ...baseStyle,
@@ -72,11 +56,18 @@ export function StyledDropzone(props) {
     ]);
 
     return (
-        <div className="container">
-            <div {...getRootProps({ style })}>
-                <input {...getInputProps()} className='getphoto' />
-                <p>Drag 'n' drop some files here, or click to select files</p>
+        <Box sx={{
+            width: '100%',
+            marginTop: '20px',
+            marginBottom: '20px'
+        }}>
+            {/* <Stack spacing={2}> */}
+            <div className="container">
+                <div {...getRootProps({ style })}>
+                    <input {...getInputProps()} className='getphoto' />
+                    <p>Перетащите или добавьте, кликнув сюда, файлы</p>
+                </div>
             </div>
-        </div>
+        </Box>
     );
 }

+ 79 - 37
js/Project/project/src/components/create_post/index.js

@@ -1,5 +1,8 @@
 import * as React from 'react';
-import { useState } from 'react';
+import { useState, useEffect } from 'react';
+import { connect, useDispatch, useSelector } from 'react-redux';
+import { useHistory } from "react-router-dom";
+import { store } from '../redux';
 
 import Box from '@mui/material/Box';
 import TextField from '@mui/material/TextField';
@@ -8,20 +11,75 @@ import Button from '@mui/material/Button';
 import Container from '@mui/material/Container';
 import SendRoundedIcon from '@mui/icons-material/SendRounded';
 
-import { MyDropzone, Basic, StyledDropzone } from './dropzone';
+import { StyledDropzone } from './dropzone';
+import { actionFilesUpload, actionCreatePost, actionFullCreatePost } from '../redux/action';
+import HighlightOffRoundedIcon from '@mui/icons-material/HighlightOffRounded';
+import { Dnd } from './dnd';
+import { UploadedFiles } from './uploaded_files';
+
 
 export const CreatePost = () => {
+    const [post, setPost] = useState({ title: '', text: '', images: [] })
+    console.log(1, "postMain: ", post)
+
+    // создаем объект с данными из форм для отправки на бек
+    // const newPostData = ({ ...post, images: ((post.images).map(item => `{_id:${item._id}}`)) })
+    const newPostData = ({ ...post, images: ((post.images).map(item => ({ _id: item._id }))) })
+    console.log(33, newPostData)
+
+    // const imgForCreatePost = (post.images).map(item => item._id)
+    // console.log('images arr: ', imgForCreatePost)
+
+    const dispatch = useDispatch()
+
+
+    // вот эту часть нужно будет сделать через connect
+    const images = useSelector(state => state.promise?.FilesUpload?.payload)
+
+    useEffect((() => {
+        if (images) {
+            setPost({ ...post, images: [...post.images, ...images] })
+        }
+    }), [images])
+
+
+    const deleteImage = image => setPost({ ...post, images: post.images.filter(i => i !== image) })
+
+    const localPostImage = ({ image }) => <UploadedFiles image={image}
+        onDelete={imgToDelete => deleteImage(imgToDelete)} />
+
+
+    // создаем новый пост и переходим на его страницу
+    const history = useHistory()
+
+    const newPost = useSelector(state => state.promise?.CreatePost?.payload?._id)
+    console.log('newPost', newPost)
+
+    const onSend = async (data) => {
+        await store.dispatch(actionFullCreatePost(data));
+
+        if (newPost) {
+            console.log('щас пойдем на переход', newPost)
+            history.push(`/post/${newPost}`);
+        }
+    }
+
+
 
-    const [titleValue, setTitleValue] = useState('')
-    const [textValue, setTextValue] = useState('')
 
 
     return (
         <Container maxWidth="80%">
             <Container>
-                <StyledDropzone />
+                <StyledDropzone onFiles={files => dispatch(actionFilesUpload(files))} onPost={post} />
             </Container>
 
+
+            <Dnd items={post.images} render={localPostImage} itemProp="image" keyField="_id"
+                onChange={images => setPost({ ...post, images })}
+                horizontal
+            />
+
             <Box
                 component="form"
                 sx={{
@@ -32,7 +90,9 @@ export const CreatePost = () => {
                     id="title"
                     label="Название поста"
                     variant="standard"
-                    onChange={e => setTitleValue(e.target.value)}
+                    onChange={e => setPost({ ...post, title: e.target.value })}
+                    value={post.title}
+
                 />
             </Box>
 
@@ -48,43 +108,25 @@ export const CreatePost = () => {
                     multiline
                     rows={2}
                     variant="standard"
-                    onChange={e => setTextValue(e.target.value)}
+                    onChange={e => setPost({ ...post, text: e.target.value })}
+                    value={post.text}
                 />
             </Box>
 
-            {/* <Container>
-                {/* <MyDropzone /> */}
-            {/* <Basic />
-            тут загрузка файла
-        </Container> * /} */}
-
-            < Stack spacing={2} direction="row" >
-                <Button variant="outlined" startIcon={<SendRoundedIcon style={{ transform: 'translate(3px, -4px) rotate(-30deg)' }} />} onClick={e => { console.log('click') }}>
+            <Stack spacing={2} direction="row" >
+                <Button
+                    variant="outlined"
+                    startIcon={
+                        <SendRoundedIcon
+                            style={{ transform: 'translate(3px, -4px) rotate(-30deg)' }}
+                        />}
+                    // onClick={() => { dispatch(actionCreatePost(newPostData)) }}
+
+                    onClick={() => onSend(newPostData)}
+                >
                     Опубликовать пост
                 </Button>
             </Stack >
         </Container >
-
-
     )
-
-
 }
-
-
-
-
-
-// const formData = new FormData()
-// formData.append('photo', getPhoto.files[0])  // аналог одного поля input; 'photo' - это имя поля формы, с которой отправляем файлыж photo.files[0] - это массив, в котором находятся все файлы, которые мы хотим залить на сервак. в реакте это компонент Dropzone
-
-// fetch('/upload', {
-//     method: 'POST',
-//     headers: {
-//         Authorization: 'Bearer ' + localStorage.authToken
-//     },
-//     body: formData
-// }).then(res => res.json()).then(res2 => console.log(res2))
-
-
-// создается форма formData, в которой один инпут, в который мы запихиваем один файл и через фетч идет заливка этого файла

+ 106 - 0
js/Project/project/src/components/create_post/test.js

@@ -0,0 +1,106 @@
+import React, { useCallback, useMemo } from 'react'
+import { useDropzone } from 'react-dropzone'
+import { Box, Stack } from '@mui/system';
+
+const baseStyle = {
+    flex: 1,
+    display: 'flex',
+    flexDirection: 'column',
+    alignItems: 'center',
+    padding: '30px',
+    borderWidth: 2,
+    borderRadius: 2,
+    borderColor: '#eeeeee',
+    borderStyle: 'dashed',
+    backgroundColor: '#fafafa',
+    color: '#bdbdbd',
+    outline: 'none',
+    transition: 'border .24s ease-in-out'
+};
+
+const focusedStyle = {
+    borderColor: '#2196f3'
+};
+
+const acceptStyle = {
+    borderColor: '#00e676'
+};
+
+const rejectStyle = {
+    borderColor: '#ff1744'
+};
+
+const Item = (prop) =>
+    <Box className='imgBlock' sx={{
+        minWidth: '150px',
+        width: '15%',
+        margin: '5px',
+        textAlign: 'center'
+    }}>
+        <div>
+            тут текст
+        </div>
+    </Box>
+
+// =========================================================================
+// ========================================================================
+
+
+
+
+
+export function StyledDropzone({ onFiles }) {
+    const onDrop = useCallback(acceptedFiles => {
+        onFiles(acceptedFiles)
+    }, [])
+
+    const {
+        getRootProps,
+        getInputProps,
+        isFocused,
+        isDragAccept,
+        isDragReject
+    } = useDropzone({ onDrop, accept: { 'image/*': [] } });
+
+    const style = useMemo(() => ({
+        ...baseStyle,
+        ...(isFocused ? focusedStyle : {}),
+        ...(isDragAccept ? acceptStyle : {}),
+        ...(isDragReject ? rejectStyle : {})
+    }), [
+        isFocused,
+        isDragAccept,
+        isDragReject
+    ]);
+
+    return (
+        <Box sx={{
+            width: '100%',
+            marginTop: '20px',
+            marginBottom: '20px'
+        }}>
+            <Stack spacing={2}>
+                <div className="container">
+                    <div {...getRootProps({ style })}>
+                        <input {...getInputProps()} className='getphoto' />
+                        <p>Перетащите или добавьте, кликнув сюда, файлы</p>
+                    </div>
+                </div>
+                <Box sx={{
+                    display: 'flex',
+                    flex: 1,
+                    flexDirection: 'row',
+                    alignItems: 'flex-start',
+                    flexWrap: 'wrap',
+                    alignContent: 'flex-start',
+                    justifyContent: 'center',
+                    padding: '5px',
+                    backgroundColor: '#fafafa',
+                    color: '#bdbdbd'
+                }}>
+                    <Item />
+                </Box >
+            </Stack>
+        </Box>
+    );
+}

+ 25 - 0
js/Project/project/src/components/create_post/uploaded_files.js

@@ -0,0 +1,25 @@
+import { Box } from "@mui/system"
+import HighlightOffRoundedIcon from '@mui/icons-material/HighlightOffRounded';
+
+
+export function UploadedFiles({ image, onDelete }) {
+    return (
+        <Box className='imgBlock' sx={{
+            position: 'relative'
+        }}>
+            <img
+                style={{ maxWidth: '200px', margin: '5px' }}
+                src={`http://hipstagram.node.ed.asmer.org.ua/` + image.url} key={image._id}
+            />
+            <HighlightOffRoundedIcon style={{
+                position: 'absolute',
+                top: '7px',
+                right: '7px',
+                color: 'rgba(238, 238, 238, 0.8)',
+                fontSize: '25px',
+                cursor: 'default'
+            }}
+                onClick={() => onDelete(image)} />
+        </Box>
+    )
+} 

+ 1 - 1
js/Project/project/src/components/feed/aboutMe.js

@@ -22,7 +22,7 @@ function AboutMe({ me }) {
             />
             <Stack direction="row" spacing={1} padding={2}>
                 <Typography>
-                    {me?.followers?.length || '0'} публикаций
+                    NaN публикаций
                 </Typography>
                 <Typography>
                     {me?.followers?.length || '0'} подписчиков

+ 57 - 13
js/Project/project/src/components/feed/index.js

@@ -8,7 +8,7 @@ import { styled } from '@mui/material/styles';
 import { Container } from '@mui/system';
 import Grid2 from '@mui/material/Unstable_Grid2/Grid2';
 
-import { actionfindPosts, actionFindUserOne, actionFeedFindOne } from '../redux/action';
+import { actionfindPosts, actionFindUserOne, actionFeedFindOne, actionFeedFind } from '../redux/action';
 import { RecipeReviewCard as Card } from './card_feed';
 import AboutMe from './aboutMe';
 
@@ -19,10 +19,26 @@ const Item = styled(Paper)(() => ({
     boxShadow: 'none',
 }))
 
-const myId = ((JSON.parse(atob(localStorage.authToken.split('.')[1]))).sub.id)
-console.log('myId: ', typeof myId)
+// забираем свой id из localStorage
+// const myId = ((JSON.parse(atob(localStorage.authToken.split('.')[1])))?.sub?.id)
+let myId
+if (localStorage.authToken !== undefined) {
+    myId = (JSON.parse(atob(localStorage?.authToken?.split('.')[1]))).sub.id
+}
+
+// запрос на ленту постов моих подписчиков
+const arr = ["5d6fccfc5fce6722147978f2", "5d66e01dc6a7071408ac1e1c"]
+const sort = -1
+
+
+
+
 
-function Feed({ feed = [], me = {} }) {
+
+
+function Feed({ feed, me = {} }) {
+    console.log('feed: ', feed)
+    console.log('testFeed: ', store.getState().promise?.Feed?.status === 'FULFILLED')
 
     return (
         <Container sx={{
@@ -36,7 +52,7 @@ function Feed({ feed = [], me = {} }) {
                             width: 500
                         }}>
                             <Stack spacing={2}>
-                                {feed.map(post => <Item key={post._id}><Card postData={post} /></Item>)}
+                                {feed && feed.map(post => <Item key={post._id}><Card postData={post} /></Item>)}
                             </Stack>
                         </Item>
                     </Grid2>
@@ -53,20 +69,48 @@ function Feed({ feed = [], me = {} }) {
     )
 }
 
-store.dispatch(actionfindPosts())
-store.dispatch(actionFindUserOne(myId)) // скорее всего нужно будет переписать все через connect
+// ===========================
+// test
+store.dispatch(actionFeedFind(myId, sort))
 
 const Me = connect(state => ({ me: state?.promise?.UserFindOne?.payload }))(AboutMe)
+export const ReduxFeed = connect(state => ({ feed: state.promise?.Feed?.payload }))(Feed)
+
+
+
+
+
+
+
+
+// ==============================
+// загружаем список подписчиков
+// store.dispatch(actionFeedFindOne(arr, sort))
+// export const ReduxFeed = connect(state => ({ feed: state?.promise?.Feed?.payload }))(Feed)
+
+// загрузка данных залогиненого пользователя справа(скорее всего нужно будет переписать все через connect)
+// store.dispatch(actionFindUserOne(myId))
+// const Me = connect(state => ({ me: state?.promise?.UserFindOne?.payload }))(AboutMe)
+// ===============================
+
+
+
+
+
+
+
+
+
+
+
+
 
-export const ReduxFeed = connect(state => ({ feed: state?.promise?.PostsFind?.payload }))(Feed)
 
 
 
 
-// запрос на ленту постов моих подписчиков
-const arr = ["5d6fccfc5fce6722147978f2", "5d66e01dc6a7071408ac1e1c"]
-const sort = -1
 
-store.dispatch(actionFeedFindOne(arr, sort))
-// export const ReduxFeed2 = connect(state => ({ feed: state?.promise?.feed?.payload }))(Feed)
 
+// загрузка ленты всех постов из базы
+// store.dispatch(actionfindPosts())
+// export const ReduxFeed = connect(state => ({ feed: state?.promise?.PostsFind?.payload }))(Feed)

+ 107 - 0
js/Project/project/src/components/feed/test.js

@@ -0,0 +1,107 @@
+// тут предыдущая версия index.js
+
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { store } from '../redux';
+// import { url } from '../../App';
+
+import { Box, Stack, Paper } from '@mui/material';
+import { styled } from '@mui/material/styles';
+import { Container } from '@mui/system';
+import Grid2 from '@mui/material/Unstable_Grid2/Grid2';
+
+import { actionfindPosts, actionFindUserOne, actionFeedFindOne, actionFeedFind } from '../redux/action';
+import { RecipeReviewCard as Card } from './card_feed';
+import AboutMe from './aboutMe';
+
+// сам item для поста
+const Item = styled(Paper)(() => ({
+    padding: '0 10px',
+    borderRadius: 0,
+    boxShadow: 'none',
+}))
+
+// забираем свой id из localStorage
+const myId = ((JSON.parse(atob(localStorage.authToken.split('.')[1])))?.sub?.id)
+
+function Feed({ feed = [], me = {} }) {
+
+    console.log('feed: ', feed)
+
+    return (
+        <Container sx={{
+            width: '80%',
+            mt: 1
+        }}>
+            <Box sx={{ flexGrow: 1 }}>
+                <Grid2 container spacing={2}>
+                    <Grid2 xs={7}>
+                        <Item sx={{
+                            width: 500
+                        }}>
+                            <Stack spacing={2}>
+                                {feed.map(post => <Item key={post._id}><Card postData={post} /></Item>)}
+                            </Stack>
+                        </Item>
+                    </Grid2>
+                    <Grid2 xs={5}>
+                        <Item sx={{
+                            position: 'fixed'
+                        }}>
+                            <Me me={me} />
+                        </Item>
+                    </Grid2>
+                </Grid2>
+            </Box>
+        </Container>
+    )
+}
+
+
+
+// запрос на ленту постов моих подписчиков
+
+
+const arr = ["5d6fccfc5fce6722147978f2", "5d66e01dc6a7071408ac1e1c"]
+const sort = -1
+
+
+
+
+
+// ==============================
+// загружаем список подписчиков
+// store.dispatch(actionFeedFindOne(arr, sort))
+// export const ReduxFeed = connect(state => ({ feed: state?.promise?.Feed?.payload }))(Feed)
+
+// загрузка данных залогиненого пользователя справа(скорее всего нужно будет переписать все через connect)
+// store.dispatch(actionFindUserOne(myId))
+// const Me = connect(state => ({ me: state?.promise?.UserFindOne?.payload }))(AboutMe)
+// ===============================
+
+
+
+
+
+
+// ===========================
+// test
+store.dispatch(actionFeedFind(myId, sort))
+export const ReduxFeed = connect(state => ({ feed: state?.promise }))(Feed)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// загрузка ленты всех постов из базы
+// store.dispatch(actionfindPosts())
+// export const ReduxFeed = connect(state => ({ feed: state?.promise?.PostsFind?.payload }))(Feed)

+ 1 - 4
js/Project/project/src/components/post/index.js

@@ -20,7 +20,7 @@ const Item = styled(Paper)(() => ({
 
 
 function Comments({ post = {}, loadPost }) {
-    console.log('start')
+    // console.log('start')
     console.log('post: ', post)
 
     const { postId } = useParams()
@@ -59,6 +59,3 @@ function Comments({ post = {}, loadPost }) {
 
 export const CComments = connect(state => ({ post: state?.promise?.PostFindOne?.payload }), { loadPost: actionFindPostOne })(Comments)
 
-
-
-    // const post2 = useSelector(state => state?.CategoryGoodsAndSubCategoryGoods?.payload)

+ 106 - 7
js/Project/project/src/components/redux/action.js

@@ -1,4 +1,6 @@
-import { url } from "../../App"
+import { store } from "."
+
+const url = 'http://hipstagram.node.ed.asmer.org.ua/graphql'
 
 // функция getGql
 function getGql(endpoint) {
@@ -31,7 +33,7 @@ function getGql(endpoint) {
   }
 }
 
-const gql = getGql('http://hipstagram.node.ed.asmer.org.ua/graphql')
+const gql = getGql(url)
 
 // акшоны для promiseReducer
 export const actionPending = nameOfPromise => ({ nameOfPromise, type: 'PROMISE', status: 'PENDING' })
@@ -64,7 +66,7 @@ export const actionFindPostOne = _id => actionPromise('PostFindOne', gql(`query
           _id
         }
         likesCount owner {
-          _id login nick avatar {
+          _id login nick  avatar {
             _id url
           }
         }
@@ -84,7 +86,7 @@ export const actionFindPostOne = _id => actionPromise('PostFindOne', gql(`query
 
 
 // запрос на поиск конкретного юзера
-export const actionFindUserOne = _id => actionPromise('UserFindOne', gql(`query OneUserFind ($userOne: String) {
+export const actionFindUserOne = (_id, promiseName = 'UserFindOne') => actionPromise(promiseName, gql(`query OneUserFind ($userOne: String) {
     UserFindOne(query: $userOne) {
       _id createdAt login nick 
       avatar {
@@ -126,7 +128,7 @@ const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token })
 const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' })
 
 // Запрос на логин
-export const actionLogin = (login, password) => actionPromise('login', gql(`query Login($login: String!, $password: String!) {
+export const actionLogin = (login, password) => actionPromise('Login', gql(`query Login($login: String!, $password: String!) {
 login(login: $login, password: $password)
 }`, {
   login,
@@ -152,13 +154,16 @@ export const actionFullLogin = (login, password) =>
     }
   }
 
+// ========================
+
+
 
 
 
 
 
-// Запрос на поиск ленты
-export const actionFeedFindOne = (arr, sortOne) => actionPromise('feed', gql(`query FeedFindOne ($feedOne: String){
+// Запрос на поиск ленты постов
+export const actionFeedFindOne = (arr, sortOne) => actionPromise('Feed', gql(`query FeedFindOne ($feedOne: String){
 	PostFind(query: $feedOne){
             _id createdAt title text likesCount owner{
               _id login avatar{
@@ -176,3 +181,97 @@ export const actionFeedFindOne = (arr, sortOne) => actionPromise('feed', gql(`qu
   feedOne: JSON.stringify([{ ___owner: { $in: arr } }, { sort: [{ _id: sortOne }] }])
 }))
 
+
+
+
+
+// =============================
+// Запрос для создания страницы юзера
+export const actionFullUserFindOne = _id =>
+  async dispatch => {
+    await dispatch(actionFindUserOne(_id))
+    dispatch(actionFeedFindOne([_id], -1))
+  }
+// ==============================
+
+
+
+// запрос для главной страницы ленты
+export const actionFeedFind = (id, sort) =>
+  async (dispatch, getState) => {
+    const userData = await dispatch(actionFindUserOne(id))
+    console.log('userData: ', userData)
+
+    // собираем список подписчиков
+    let followingList = []
+
+    for (const item of userData.following)
+      for (const [key, value] of Object.entries(item)) {
+        if (key === '_id') {
+          followingList.push(value)
+        }
+      }
+    console.log('followingList: ', followingList)
+
+    // console.log(666, store.getState().promise?.Feed?.status !== 'FULFILLED')
+
+    // if (store.getState().promise?.Feed?.status !== 'FULFILLED') {
+    dispatch(actionFeedFindOne(followingList, sort))
+    // }
+  }
+
+
+
+
+// запрос на загрузку картинок на бек
+function fileUpload(file) {
+  const formData = new FormData()
+  formData.append('photo', file)
+
+
+  return (
+    fetch('http://hipstagram.node.ed.asmer.org.ua/upload', {
+      method: 'POST',
+      headers: {
+        Authorization: 'Bearer ' + localStorage.authToken
+      },
+      body: formData
+    }).then(res => res.json())
+  )
+}
+
+function filesUpload(files) {
+  return Promise.all(files.map(fileUpload))
+}
+
+export const actionFilesUpload = (files) => actionPromise('FilesUpload',
+  filesUpload(files)
+)
+
+
+
+// запрос на создание поста
+export const actionCreatePost = (params) => actionPromise('CreatePost', gql(
+  `mutation CreatePost($createNewPost: PostInput){
+  PostUpsert(post: $createNewPost){
+    _id
+  }
+}`, {
+  createNewPost: params
+}))
+
+// санк для создания поста и последующего перехода на его страницу
+export const actionFullCreatePost = (params) =>
+  async dispatch => {
+    const newPost = await dispatch(actionCreatePost(params))
+    console.log('Тут нужный резульата: ', newPost)
+
+    if (newPost) {
+      dispatch(actionFindPostOne(newPost._id))
+    }
+  }
+
+
+
+
+

+ 8 - 1
js/Project/project/src/components/redux/index.js

@@ -23,4 +23,11 @@ export const store = createStore(totalReducer, applyMiddleware(thunk))
 store.subscribe(() => console.log(store.getState()))
 
 // запрос на логинизацию
-store.dispatch(actionFullLogin('volddemar4ik', 'Qwerty1324'))
+// store.dispatch(actionFullLogin('volddemar4ik', 'Qwerty1324'))
+
+
+
+// вот это раскомментитьь
+// if (localStorage.authToken == undefined) {
+//     store.dispatch(actionFullLogin('volddemar4ik', 'Qwerty1324'))
+// }

+ 4 - 4
js/Project/project/src/components/structure/header.js

@@ -47,8 +47,7 @@ function ResponsiveAppBar() {
                 <Toolbar disableGutters>
                     <IconButton edge='start'>
                         <Instagram sx={{
-                            display: { xs: 'flex' },
-                            mr: 1,
+                            // display: { xs: 'flex' },
                             color: 'black'
                         }} />
                     </IconButton>
@@ -57,7 +56,7 @@ function ResponsiveAppBar() {
                         variant="h6"
                         noWrap
                         component="a"
-                        href="/"
+                        href="/feed"
                         sx={{
                             display: { xs: 'none', md: 'flex' },
                             fontFamily: 'monospace',
@@ -65,7 +64,8 @@ function ResponsiveAppBar() {
                             letterSpacing: '.3rem',
                             color: 'inherit',
                             textDecoration: 'none',
-                            flexGrow: '1'
+                            flexGrow: '1',
+                            ml: 1
                         }}
                     >
                         Hipstagram

+ 173 - 0
js/Project/project/src/components/user/gallery2.js

@@ -0,0 +1,173 @@
+import * as React from 'react';
+import { useEffect } from 'react';
+
+import ImageList from '@mui/material/ImageList';
+import ImageListItem from '@mui/material/ImageListItem';
+import { Box } from '@mui/system';
+
+import AutoAwesomeMotionRoundedIcon from '@mui/icons-material/AutoAwesomeMotionRounded';
+import { useParams } from 'react-router';
+import { connect } from 'react-redux';
+import { actionFullUserFindOne } from '../redux/action';
+
+function StandardImageList({ userGallery = [], loadUserGallery }) {
+    console.log('userGallery: ', userGallery)
+
+    const { userId } = useParams()
+    useEffect(() => { loadUserGallery(userId) }, [userId])
+
+
+    const clicker = (name) => {
+        console.log('click: ', name)
+    }
+
+
+
+    return (
+        <ImageList sx={{ width: "100%", minHeight: "100%" }} cols={3} rowHeight={'auto'} >
+            {
+                itemData.map((item) => (
+                    // <ImageListItem key={item.img}>
+                    <ImageListItem key={Math.random()} >
+
+                        <AutoAwesomeMotionRoundedIcon sx={{
+                            color: 'white',
+                            transform: 'scale(-1, 1)',
+                            position: 'absolute',
+                            right: '10px',
+                            top: '10px',
+                            fontSize: '32px'
+                        }} />
+
+                        <Box sx={{
+                            width: '100%',
+                            paddingTop: '100%',
+                            backgroundSize: 'cover',
+                            backgroundColor: 'black',
+                            backgroundRepeat: 'no-repeat'
+                        }}
+
+                            style={{ backgroundImage: `url(${item.img})` }}
+
+                            // тут поставить ссылку на пост, к которому привязаны картинки
+                            onClick={() => { clicker(item.title) }}
+
+                        >
+
+
+                            {/* <img
+                            src={`${item.img}?w=164&h=164&fit=crop&auto=format`}
+                            srcSet={`${item.img}?w=164&h=164&fit=crop&auto=format&dpr=2 2x`}
+                            alt={item.title}
+                            loading="lazy"
+                            style={{
+                                width: '100%'
+                            }}
+                        /> */}
+                        </Box>
+                    </ImageListItem>
+                ))
+            }
+        </ImageList >
+    );
+}
+
+
+
+export const CStandardImageList = connect(state => ({ userGallery: state?.promise?.Feed?.payload }), { loadUserGallery: actionFullUserFindOne })(StandardImageList)
+
+// массив с картинками
+const itemData = [
+    {
+        img: '/images/image4.jpeg',
+        title: 'Cat',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1551963831-b3b1ca40c98e',
+        title: 'Breakfast',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1551782450-a2132b4ba21d',
+        title: 'Burger',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1522770179533-24471fcdba45',
+        title: 'Camera',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1444418776041-9c7e33cc5a9c',
+        title: 'Coffee',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1533827432537-70133748f5c8',
+        title: 'Hats',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1516802273409-68526ee1bdd6',
+        title: 'Basketball',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1518756131217-31eb79b20e8f',
+        title: 'Fern',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1597645587822-e99fa5d45d25',
+        title: 'Mushrooms',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1567306301408-9b74779a11af',
+        title: 'Tomato basil',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1471357674240-e1a485acb3e1',
+        title: 'Sea star',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1589118949245-7d38baf380d6',
+        title: 'Bike',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1551963831-b3b1ca40c98e',
+        title: 'Breakfast',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1551782450-a2132b4ba21d',
+        title: 'Burger',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1522770179533-24471fcdba45',
+        title: 'Camera',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1444418776041-9c7e33cc5a9c',
+        title: 'Coffee',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1533827432537-70133748f5c8',
+        title: 'Hats',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1558642452-9d2a7deb7f62',
+        title: 'Honey',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1516802273409-68526ee1bdd6',
+        title: 'Basketball',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1518756131217-31eb79b20e8f',
+        title: 'Fern',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1597645587822-e99fa5d45d25',
+        title: 'Mushrooms',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1567306301408-9b74779a11af',
+        title: 'Tomato basil',
+    },
+    {
+        img: 'https://images.unsplash.com/photo-1471357674240-e1a485acb3e1',
+        title: 'Sea star',
+    }
+];

+ 5 - 1
js/Project/project/src/components/user/index.js

@@ -49,4 +49,8 @@ function User({ user = {}, loadUser }) {
     );
 }
 
-export const CUser = connect(state => ({ user: state?.promise?.UserFindOne?.payload }), { loadUser: actionFindUserOne })(User)
+export const CUser = connect(state => ({ user: state?.promise?.UserFindOne?.payload }), { loadUser: actionFindUserOne })(User)
+
+// export const CUser = connect(state => ({ user: state?.promise?.UserFindOne?.payload }), { loadUser: actionFullUserFindOne })(User)
+
+// export const CUser = connect(state => ({ user: state?.promise }), { loadUser: actionFullUserFindOne })(User)

+ 6 - 1
js/Project/project/src/components/user/userData.js

@@ -6,8 +6,13 @@ import ManageAccountsOutlinedIcon from '@mui/icons-material/ManageAccountsOutlin
 
 import { url } from "../../App"
 
+// const myId = (JSON.parse(atob(localStorage?.authToken?.split('.')[1]))).sub.id
 
-const myId = ((JSON.parse(atob(localStorage.authToken.split('.')[1]))).sub.id)
+
+let myId
+if (localStorage.authToken !== undefined) {
+    myId = (JSON.parse(atob(localStorage?.authToken?.split('.')[1]))).sub.id
+}
 
 // Функция отображения кнопки редактирования своего профиля на карточке юзера
 function UptadeProfile() {