Bladeren bron

added page create post

makstravm 2 jaren geleden
bovenliggende
commit
96f402a63b

+ 1 - 0
package.json

@@ -8,6 +8,7 @@
         "@testing-library/react": "^12.1.2",
         "@testing-library/user-event": "^13.5.0",
         "antd": "^4.18.2",
+        "antd-img-crop": "^4.1.0",
         "array-move": "^4.0.0",
         "react": "^17.0.2",
         "react-dom": "^17.0.2",

+ 3 - 1
src/App.js

@@ -13,6 +13,7 @@ import { CMainPostsFeed } from './pages/MainPostsFeed';
 import { CRRoute } from './helpers';
 import { CPostPage } from './pages/PostPage';
 import { CAllPosts } from './pages/AllPosts';
+import { CEntityEditorPost } from './pages/EntityEditorPost';
 
 export const history = createHistory()
 
@@ -37,7 +38,8 @@ const AppContent = ({ isToken }) =>
                         <Route path='/' component={CMainPostsFeed} exact />
                         <Route path='/profile/:_id' component={CProfilePage} />
                         <Route path='/message' component={Aside} />
-                        <Route path='/add' component={CAdd} />
+                        {/* <Route path='/add' component={CAdd} /> */}
+                        <Route path='/add' component={CEntityEditorPost} />
                         <CRRoute path='/all' component={CAllPosts} />
                     </Container>
                     <CRRoute path='/post/:id' component={CPostPage} />

+ 87 - 1
src/App.scss

@@ -444,7 +444,7 @@ body {
         display: flex;
         flex-direction: column;
         justify-content: space-between;
-height: 100%;
+        height: 100%;
         max-height: 565px;
     }
     &__description-top {
@@ -461,3 +461,89 @@ height: 100%;
     font-size: 1.2em;
     font-weight: 500;
 }
+
+.ContainErditorPost {
+    width: 100%;
+    background-color: $defaultColorW;
+    padding: 15px 25px;
+    box-shadow: 0 0 6px 2px rgba($defaultColorB, 0.5);
+    border-radius: 5px;
+    .title {
+        width: 100%;
+        text-align: center;
+        display: inline-block;
+    }
+    .description {
+        white-space: pre;
+    }
+}
+.EditPhotos {
+    &__box {
+        margin-bottom: 15px;
+    }
+    .ant-upload-drag-icon {
+        margin-bottom: 5px;
+    }
+}
+.SortableList {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
+    grid-gap: 10px;
+}
+
+.SortableItemMask {
+    position: absolute;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    top: 0px;
+    left: 0px;
+    right: 0px;
+    border-radius: 4px;
+    background-color: rgba($defaultColorB, 0.5);
+    opacity: 0;
+    transition: 0.3s;
+    button {
+        svg {
+            width: 15px;
+            height: 15px;
+            fill: $defaultColorW;
+        }
+    }
+    .hiden-item,
+    .ant-image-mask {
+        position: absolute;
+        top: 0;
+        left: 0;
+        clip: rect(0 0 0);
+        pointer-events: none;
+        padding: 10px;
+    }
+}
+.SortableItem {
+    .Handle {
+        position: relative;
+        max-width: 130px;
+        border-radius: 4px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        box-shadow: 0 0 6px 2px rgba($defaultColorB, 0.3);
+        img {
+            width: 100%;
+            object-fit: cover;
+            max-height: 150px;
+        }
+        &:hover .SortableItemMask {
+            opacity: 1;
+        }
+    }
+}
+.ant-image-preview-img-wrapper {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    img {
+        max-height: 60vh;
+    }
+}

+ 20 - 5
src/actions/index.js

@@ -130,7 +130,7 @@ export const actionGetAllPosts = (skip) =>
                     _id   images{url _id}
                 }
             }`, {
-        id: JSON.stringify([{},{
+        id: JSON.stringify([{}, {
             sort: [{ _id: -1 }],
             skip: [skip || 0],
             limit: [36]
@@ -281,12 +281,18 @@ export const actionAddComment = (postId, text) =>
         CommentUpsert(comment:$comment){
             _id text
         }
-    }`, { comment: { _id: postId }, text }))
+    }`, { comment: { post: { _id: postId }, text } }))
 
 export const actionFindComment = (findId) =>
     actionPromise('findCommentPost', gql(`query commentFindPost ($id:String!){
         PostFindOne(query:$id){
-            comments{_id text owner{_id nick login avatar{_id url}} likes{_id}}
+            comments {
+                _id text owner{
+                    _id nick login
+                     avatar{_id url}
+                    } 
+                    likes{_id}
+                }
         }
     }`, { id: JSON.stringify([{ _id: findId }]) }))
 
@@ -305,7 +311,7 @@ export const actionFindSubComment = (findId) =>
     }`, { id: JSON.stringify([{ _id: findId }]) }))
 
 export const actionSubAddComment = (commentId, text, _id) =>
-    actionPromise('addcomment', gql(`mutation addcomment($comment: CommentInput ){
+    actionPromise('addSubcomment', gql(`mutation addSubcomment($comment: CommentInput ){
         CommentUpsert(comment:$comment){
             _id text
         }
@@ -369,4 +375,13 @@ export const actionGetFindLiked = (_id) =>
             } `, { id: JSON.stringify([{ "post._id": _id }]) }))
 
 
-//****************---_____________ ---*************************//
+//****************---Create Post ---*************************/
+
+export const actionsentPost = (_id = '', photos, text, title) => ({ type: 'CREATE_POST', photos, text, title })
+
+export const actionSentPost = (photos, title, text, id = "undefined") =>
+    actionPromise('sentPost', gql(`mutation sentPost($post: PostInput){
+              PostUpsert(post: $post){
+                    _id images{_id url}
+                }
+            }`, { post: { text, title, images: { _id: photos._id } } }))

+ 3 - 3
src/components/main/Add.js

@@ -4,7 +4,7 @@ import { connect } from 'react-redux';
 import { Upload, message } from 'antd';
 import { backURL, gql } from '../../helpers';
 import { actionUpdateAvatar } from '../../actions';
-import { Apps } from './new';
+import { Apps } from '../uploadPhoto';
 
 const Add = ({ imageUrl, onUploadFile }) => {
     const [loading, setLoading] = useState(false)
@@ -22,7 +22,7 @@ const Add = ({ imageUrl, onUploadFile }) => {
         }
         if (file.status === 'done') {
             message.success(`${file.name} file uploaded successfully`);
-            // await onUploadFile(file.response)
+            await onUploadFile(file.response)
             console.log(file);
             setImageLoad(true)
             setLoading(false)
@@ -49,7 +49,7 @@ const Add = ({ imageUrl, onUploadFile }) => {
             <hr />
             <hr />
             <hr />
-            <Apps />
+            {/* <Apps /> */}
         </>
 
     )

+ 0 - 133
src/components/main/new/index.js

@@ -1,133 +0,0 @@
-import { PlusOutlined } from "@ant-design/icons";
-import { Button, message,Icon,  Upload } from "antd";
-import React, { useState } from "react";
-import ReactDOM from "react-dom";
-import {
-    arrayMove,
-    SortableContainer,
-    SortableElement,
-    SortableHandle
-} from "react-sortable-hoc";
-import { backURL } from "../../../helpers";
-
-import style from "./styles.scss";
-
-
-function getBase64(file) {
-    return new Promise((resolve, reject) => {
-        const reader = new FileReader();
-        reader.readAsDataURL(file);
-        reader.onload = () => resolve(reader.result);
-        reader.onerror = error => reject(error);
-    });
-}
-
-
-
-const Handle = SortableHandle(({ props, tabIndex }) => {
-    return (
-        < div className={style.handle} tabIndex={tabIndex} >
-            box
-            <img src={`${backURL + '/' + props.response.url}`} alt="avatar" style={{ width: '100%' }} />
-
-        </ div>
-    )
-})
-
-
-
-const SortableItem = SortableElement(props => {
-    const { value } = props;
-    console.log(props);
-    return (
-        <div className="qq">
-            <div className="content">
-                {props.shouldUseDragHandle && <Handle props={value} />}
-            </div>
-            {/* <Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
-                <img alt="example" style={{ width: '100%' }} src={previewImage} />
-            </Modal> */}
-        </div>
-
-    );
-});
-
-   const props = {
-        name: 'photo',
-        action: `${backURL}/upload`,
-        headers: localStorage.authToken || sessionStorage.authToken ? { Authorization: 'Bearer ' + (localStorage.authToken || sessionStorage.authToken) } : {}
-    }
-
-const SortableList = SortableContainer(({ items, handlerChange, ...restProps }) => {
- 
-    return (
-        <div className='ww'>
-            {items.map((item, index) => (
-                <SortableItem
-                    key={`item-${item.uid}`}
-                    index={index}
-                    value={item}
-                    {...restProps}
-                />
-
-            ))}
-        
-        </div>
-    );
-});
-
-
-export function Apps() {
-    const [photos, setPhotos] = useState([]);
-    const [loading, setLoading] = useState(false)
-    const [imageLoad, setImageLoad] = useState(false)
-
-
-    const handlerChange = async ({ file, fileList }) => {
-        if (file.status === 'uploading') {
-            // setLoading(true)
-        }
-        if (file.status === 'done') {
-            console.log(file, fileList);
-            setPhotos([...photos, file])
-            message.success(`${file.name} file uploaded successfully`);
-            // // await onUploadFile(file.response)
-            // console.log(file);
-            // setImageLoad(true)
-            // setLoading(false)
-        } else if (file.status === 'error') {
-            // message.error(`${file.name} file upload failed.`);
-        }
-    }
-console.log(photos);
-    const onSortEnd = ({ oldIndex, newIndex }) => {
-        setPhotos(arrayMove(photos, oldIndex, newIndex));
-    };
-
-    return (
-        <div className="rrr" >
-            <SortableList
-                shouldUseDragHandle={true}
-                useDragHandle
-                axis="xy"
-                items={photos}
-                loading={loading}
-                handlerChange={handlerChange}
-                onSortEnd={onSortEnd}
-            />
-            <Upload {...props}
-                multiple={true}
-                listType="picture-card"
-                showUploadList={false}
-                onChange={handlerChange}>
-                {/* {items.length >= 8 ? null : <div>
-                    <PlusOutlined />
-                
-                </div>} */} 
-                <div className="ant-upload-text">Upload</div>
-            </Upload>   
-        </div>
-    );
-}
-
-

+ 42 - 42
src/components/main/new/styles.scss

@@ -1,45 +1,45 @@
-.App {
-    font-family: sans-serif;
-    text-align: center;
-}
+// .App {
+//     font-family: sans-serif;
+//     text-align: center;
+// }
 
-.handle {
-    display: block;
-    width: 18px;
-    height: 18px;
-    margin-right: 20px;
-    overflow: hidden;
+// .handle {
+//     display: block;
+//     width: 18px;
+//     height: 18px;
+//     margin-right: 20px;
+//     overflow: hidden;
 
-    > svg {
-        opacity: 0.3;
-    }
+//     > svg {
+//         opacity: 0.3;
+//     }
 
-    cursor: grab;
-}
-.qq {
-    float: left;
-    padding-left: 8px;
-    padding-right: 8px;
-    width: calc(25% - 16px);
-    .content {
-        padding: 8px 12px;
-        background-color: #ddd;
-        height: 150px;
-        background-color: blue;
-    }
-}
-.ww {
-    background-color: #ddd;
-    margin-left: -8px;
-    margin-right: -8px;
-    white-space: nowrap;
-    &:after {
-        content: "";
-        clear: both;
-        display: table;
-    }
-}
-.rrr {
-    width: 600px;
-    margin: 0 auto;
-}
+//     cursor: grab;
+// }
+// .qq {
+//     float: left;
+//     padding-left: 8px;
+//     padding-right: 8px;
+//     width: calc(25% - 16px);
+//     .content {
+//         padding: 8px 12px;
+//         background-color: #ddd;
+//         height: 150px;
+//         background-color: blue;
+//     }
+// }
+// .ww {
+//     background-color: #ddd;
+//     margin-left: -8px;
+//     margin-right: -8px;
+//     white-space: nowrap;
+//     &:after {
+//         content: "";
+//         clear: both;
+//         display: table;
+//     }
+// }
+// .rrr {
+//     width: 600px;
+//     margin: 0 auto;
+// }

+ 124 - 0
src/components/uploadPhoto/index.js

@@ -0,0 +1,124 @@
+import { DeleteOutlined, EyeOutlined, InboxOutlined, LoadingOutlined, PlusOutlined } from "@ant-design/icons";
+import { Button, message, Icon, Upload, Image } from "antd";
+import Dragger from "antd/lib/upload/Dragger";
+import React, { useState } from "react";
+import ReactDOM from "react-dom";
+import ImgCrop from 'antd-img-crop';
+import {
+    arrayMove,
+    SortableContainer,
+    SortableElement,
+    SortableHandle
+} from "react-sortable-hoc";
+import { backURL } from "../../helpers";
+
+
+const SortableItemMask = ({ removePhotosItem, setVisible, id }) =>
+    <div className="SortableItemMask">
+        <Button type="link" onClick={() => setVisible(true)}>
+            <EyeOutlined />
+        </Button>
+        <Button type="link" onClick={() => removePhotosItem(id)}>
+            <DeleteOutlined />
+        </Button>
+    </div>
+
+
+const Handle = SortableHandle(({ tabIndex, value, removePhotosItem }) => {
+    const [visible, setVisible] = useState(false);
+    return (
+        <div className="Handle" tabIndex={tabIndex} >
+            <SortableItemMask id={value._id} setVisible={setVisible} removePhotosItem={removePhotosItem} />
+            <img src={`${backURL + '/' + value.url}`} alt="avatar" style={{ width: '100%' }} />
+            <Image className="hidden-item"
+                width={200}
+                style={{ display: 'none' }}
+                preview={{
+                    visible,
+                    src: backURL + '/' + value.url,
+                    onVisibleChange: value => {
+                        setVisible(value);
+                    },
+                }}
+            />
+        </ div>
+    )
+})
+
+
+const SortableItem = SortableElement(props => {
+    const { value, removePhotosItem } = props
+    return (
+        <div className="SortableItem">
+            <Handle value={value} removePhotosItem={removePhotosItem} />
+        </div >
+    );
+});
+
+const props = {
+    name: 'photo',
+    action: `${backURL}/upload`,
+    headers: localStorage.authToken || sessionStorage.authToken ? { Authorization: 'Bearer ' + (localStorage.authToken || sessionStorage.authToken) } : {},
+}
+
+const SortableList = SortableContainer(({ items,...restProps }) => {
+    return (
+        <div className='SortableList'>
+            {items.map((item, index) => (
+                <SortableItem
+                    key={`item-${item._id}`}
+                    index={index}
+                    value={item}
+                    {...restProps}
+                />
+            ))}
+        </div>
+    );
+});
+
+
+export function EditPhotos({ photos, setPhotos }) {
+
+    const handlerChange = async ({ file }) => {
+        if (file.status === 'done') {
+
+            setPhotos([...photos, file.response])
+            message.success(`${file.name} file uploaded successfully`);
+        } else if (file.status === 'error') {
+            message.error(`${file.name} file upload failed.`);
+        }
+    }
+    const removePhotosItem = (id) => setPhotos(photos.filter(p => p._id !== id))
+
+    const onSortEnd = ({ oldIndex, newIndex }) => {
+        setPhotos(arrayMove(photos, oldIndex, newIndex));
+    };
+
+    return (
+        <div className="EditPhotos" >
+            {photos.length >= 8 ? null
+                : <Dragger {...props} className="EditPhotos__box"
+                    multiple={true}
+                    listType="picture-card"
+                    showUploadList={false}
+                    onChange={handlerChange}>
+                    <p className="ant-upload-drag-icon">
+                        <InboxOutlined />
+                    </p>
+                    <p className="ant-upload-text">Click or drag file to this area to upload</p>
+                </Dragger>
+            }
+            <></>
+            <SortableList
+                shouldUseDragHandle={true}
+                useDragHandle
+                axis="xy"
+                removePhotosItem={removePhotosItem}
+                items={photos}
+                onSortEnd={onSortEnd}
+            />
+        </div>
+    );
+}
+
+

+ 3 - 3
src/index.js

@@ -5,9 +5,9 @@ import App from './App';
 import reportWebVitals from './reportWebVitals';
 
 ReactDOM.render(
-  <React.StrictMode>
-    <App />
-  </React.StrictMode>,
+
+    <App />,
+
   document.getElementById('root')
 );
 

+ 0 - 4
src/pages/AllPosts.jsx

@@ -3,10 +3,6 @@ import { connect } from 'react-redux';
 import { actionAllPosts, actionRemovePostsFeedAC } from '../actions';
 import { CPosts } from '../components/Posts';
 
-
-
-
-
 const AllPosts = ({ posts, onAllPosts, postsRemove }) => {
     const [checkScroll, setCheckScroll] = useState(true)
 

+ 166 - 0
src/pages/EntityEditorPost.jsx

@@ -0,0 +1,166 @@
+import { EditOutlined } from "@ant-design/icons"
+import { Button, Divider, Input } from "antd"
+import TextArea from "antd/lib/input/TextArea"
+import Paragraph from "antd/lib/typography/Paragraph"
+import Text from "antd/lib/typography/Text"
+import Title from "antd/lib/typography/Title"
+import { useEffect, useState } from "react"
+import { connect } from "react-redux"
+import { actionSentPost, actionsentPostAC } from "../actions"
+import { EditPhotos } from "../components/uploadPhoto"
+
+
+const ContainEditorPost = ({ children }) =>
+    <div className='ContainErditorPost'>{children}</div>
+
+
+const EditTitlePost = ({ titleSend, setTitleSend }) => {
+    const [title, setTitle] = useState(titleSend || 'Enter title')
+    const [error, setError] = useState(false)
+    const [editMode, setEditMode] = useState(false)
+
+    const addTaskHandler = () => {
+        if (title.trim() !== '') {
+            setTitleSend(title.trim())
+            setEditMode(false)
+        } else {
+            setError(true)
+            setTitleSend('')
+        }
+    }
+    const titleInputHandler = () => {
+        setEditMode(true)
+    }
+
+    const titleInputHandlerClose = () => {
+        addTaskHandler()
+    }
+
+    const onChangeTask = (e) => {
+        setTitle(e.currentTarget.value)
+        setError(false)
+    }
+
+    const onKeyPressAddTask = (e) => {
+        if (e.charCode === 13) {
+            addTaskHandler()
+        }
+    }
+    return (
+        <>
+            <Divider orientation="left" orientationMargin="0">
+                <Title level={3}>Title
+                    <Button type="link" onClick={titleInputHandler}><EditOutlined /></Button></Title>
+            </Divider>
+            {error && <Text type="danger">Field must not be empty</Text>}
+            {editMode
+                ? <Input
+                    value={title}
+                    placeholder="title"
+                    onChange={onChangeTask}
+                    // className={s.input + ' ' + (error && s.error)}
+                    autoFocus onBlur={titleInputHandlerClose}
+                    onKeyPress={onKeyPressAddTask}
+                />
+                : <Title level={5} onDoubleClick={titleInputHandler}>
+                    {title}
+                </Title>
+            }
+        </>)
+}
+
+
+const EditDescriptionPost = ({ description, setDescription }) => {
+    const [text, setText] = useState(description || 'Enter descriptin');
+    const [error, setError] = useState(false)
+    const [editMode, setEditMode] = useState(false)
+
+
+    const addTaskHandler = () => {
+        if (text.trim() !== '') {
+            setDescription(text)
+            setEditMode(false)
+        } else {
+            setError(true)
+            setDescription('')
+        }
+    }
+    const textInputHandler = () => {
+        setEditMode(true)
+    }
+
+    const textInputHandlerClose = () => {
+        addTaskHandler()
+    }
+
+    const onChangeTask = (e) => {
+        setText(e.currentTarget.value)
+        setError(false)
+    }
+
+    const onKeyPressAddTask = (e) => {
+        if (e.shiftKey && e.charCode === 13) {
+            setText(text += `'\n'`)
+        } else if (e.charCode === 13) {
+            addTaskHandler()
+        }
+    }
+    return (
+        <>
+            <Divider orientation="left" orientationMargin="0">
+                <Title level={3}>Description
+                    <Button type="link" onClick={textInputHandler}><EditOutlined /></Button></Title>
+            </Divider>
+            {error && <Text type="danger">Field must not be empty</Text>}
+            {editMode
+                ? <TextArea
+                    placeholder="Description"
+                    autoSize={{ minRows: 3, maxRows: 5 }}
+                    value={text}
+                    onChange={onChangeTask}
+                    autoFocus onBlur={textInputHandlerClose}
+                    onKeyPress={onKeyPressAddTask}
+                />
+                : <Paragraph className="description" onDoubleClick={textInputHandler}>
+                    {text}
+                </Paragraph>
+            }
+        </>)
+}
+
+const EntityEditorPost = ({ entity = { array: [] }, onSave }) => {
+
+    const [photos, setPhotos] = useState([]);
+    const [titleSend, setTitleSend] = useState('')
+    const [description, setDescription] = useState('');
+    //  photos.length && titleSend && description ? : true
+    const disabledBtn = false
+    const sentPost = () => {
+        const [newphotos ]= photos.map(ph => {
+            return { _id: ph._id }
+        })
+
+
+        onSave(newphotos, titleSend, description);
+    }
+    // const sentPost = () => {
+    //     const newphotos = photos.map(ph => {
+    //         return { _id: ph._id }
+    //     })
+
+    //     console.log(result)
+
+    // }
+    return (
+        <ContainEditorPost>
+            <h1 className="title" level={1}>Create / edit Post</h1>
+            <Divider orientation="left" orientationMargin="0"><Title level={3}>Photos</Title></Divider>
+            <EditPhotos photos={photos} setPhotos={setPhotos} />
+            <EditTitlePost titleSend={titleSend} setTitleSend={setTitleSend} />
+            <EditDescriptionPost description={description} setDescription={setDescription} />
+            <Divider orientation="right">   <Button disabled={disabledBtn} type="primary" onClick={sentPost}>Send a Post</Button></Divider>
+        </ContainEditorPost>
+    )
+}
+
+export const CEntityEditorPost = connect(null, { onSave: actionSentPost })(EntityEditorPost)

+ 11 - 1
src/redux/saga/index.js

@@ -260,6 +260,7 @@ function* subscribeWatcher() {
 
 function* addCommentWorker({ text }) {
     const { postsFeed: { posts: { _id } } } = yield select()
+    console.log(text);
     yield call(promiseWorker, actionAddComment(_id, text))
     const { comments } = yield call(promiseWorker, actionFindComment(_id))
     console.log(comments, _id);
@@ -331,9 +332,17 @@ function* findFollowWatcher() {
 }
 
 
-//*************** ******************//
+//*************** Create Post ******************//
 
 
+function* sentPostWorker({ photos, text, title }) {
+    yield call(promiseWorker, actionGetFindLiked(photos, text, title))
+}
+
+function* sentPostWatcher() {
+    yield takeEvery('CREATE_POST', sentPostWorker)
+}
+
 
 
 
@@ -351,6 +360,7 @@ export function* rootSaga() {
         addCommentWatcher(),
         updateAvatarWatcher(),
         findFollowWatcher(),
+        sentPostWatcher(),
 
     ])
 }

+ 27 - 1
yarn.lock

@@ -1049,7 +1049,7 @@
     core-js-pure "^3.19.0"
     regenerator-runtime "^0.13.4"
 
-"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.2.0", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
   version "7.16.7"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa"
   integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==
@@ -2314,6 +2314,14 @@ ansi-styles@^5.0.0:
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
   integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
 
+antd-img-crop@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/antd-img-crop/-/antd-img-crop-4.1.0.tgz#679872675c26122b8c86d0c5cf0fa8d43d9af6ae"
+  integrity sha512-41wH5kvn00fdWF1doN0MAXpTWjawUWbiOyDVuQGM8NIffs6YQ2ihgoVhaBMnbs9VCswrCAAHdo3nYL8B8Rg0tA==
+  dependencies:
+    "@babel/runtime" "^7.16.7"
+    react-easy-crop "^4.0.1"
+
 antd@^4.18.2:
   version "4.18.2"
   resolved "https://registry.yarnpkg.com/antd/-/antd-4.18.2.tgz#e4d03b2229b1f4b3b204e5e5539693aa653d3b7e"
@@ -6126,6 +6134,11 @@ normalize-url@^6.0.1:
   resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
   integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
 
+normalize-wheel@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/normalize-wheel/-/normalize-wheel-1.0.1.tgz#aec886affdb045070d856447df62ecf86146ec45"
+  integrity sha1-rsiGr/2wRQcNhWRH32Ls+GFG7EU=
+
 npm-run-path@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
@@ -7568,6 +7581,14 @@ react-dom@^17.0.2:
     object-assign "^4.1.1"
     scheduler "^0.20.2"
 
+react-easy-crop@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/react-easy-crop/-/react-easy-crop-4.0.1.tgz#0f540086e20055c51920e443dca5f8f79bdf609c"
+  integrity sha512-cREis2557y/ZkvgiNaLlFrzjduUSUvEYYxbglwggpo2gnxCjBQZeRgAPoedvXX0e0BgyGAI0zD3motVucJGhzA==
+  dependencies:
+    normalize-wheel "^1.0.1"
+    tslib "2.0.1"
+
 react-error-overlay@^6.0.10:
   version "6.0.10"
   resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6"
@@ -8743,6 +8764,11 @@ tsconfig-paths@^3.11.0:
     minimist "^1.2.0"
     strip-bom "^3.0.0"
 
+tslib@2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e"
+  integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==
+
 tslib@^1.8.1:
   version "1.14.1"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"