App.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. import './App.scss'
  2. import { Provider, connect } from 'react-redux'
  3. import { Router, Route, Link, Redirect, Switch } from 'react-router-dom'
  4. import createHistory from 'history/createBrowserHistory'
  5. import React, { useMemo, useState, useEffect } from 'react'
  6. import { store } from './reducers'
  7. import { sortableContainer, sortableElement } from 'react-sortable-hoc'
  8. import { arrayMove, arrayMoveImmutable, arrayMoveMutable } from 'array-move'
  9. import { useDropzone } from 'react-dropzone'
  10. import 'antd/dist/antd.css' // or 'antd/dist/antd.less'
  11. import {
  12. actionPromise,
  13. actionPending,
  14. actionFulfilled,
  15. actionFullLogin,
  16. actionRegister,
  17. actionFullRegister,
  18. gql,
  19. backendURL,
  20. } from './actions'
  21. import { Upload, Button } from 'antd'
  22. import { UploadOutlined } from '@ant-design/icons'
  23. import ImgCrop from 'antd-img-crop'
  24. import { Avatar, Image, Divider, Radio } from 'antd'
  25. import { UserOutlined } from '@ant-design/icons'
  26. import user from './materials/user.png'
  27. console.log(store.getState())
  28. store.subscribe(() => console.log(store.getState()))
  29. const PageMain = () => <div className="PageMain">ГЛАВНАЯ</div>
  30. const uploadFile = (file) => {
  31. const myForm = new FormData()
  32. myForm.append('photo', file)
  33. return fetch(backendURL + '/upload', {
  34. method: 'POST',
  35. headers: localStorage.authToken
  36. ? { Authorization: 'Bearer ' + localStorage.authToken }
  37. : {},
  38. body: myForm,
  39. }).then((result) => result.json())
  40. }
  41. const actionUploadFile = (file) => actionPromise('uploadFile', uploadFile(file))
  42. const actionAvatar = (imageId) => async (dispatch, getState) => {
  43. await dispatch(
  44. actionPromise(
  45. 'setAvatar',
  46. gql(
  47. `mutation setAvatar($imageId:ID, $userId:String){
  48. UserUpsert(user:{_id: $userId, avatar: {_id: $imageId}}){
  49. _id, avatar{
  50. _id
  51. }
  52. }
  53. }`,
  54. { imageId, userId: getState().auth?.payload?.sub?.id },
  55. ),
  56. ),
  57. )
  58. }
  59. const actionAboutMe = () => async (dispatch, getState) => {
  60. await dispatch(
  61. actionPromise(
  62. 'aboutMe',
  63. gql(
  64. `query AboutMe($userId:String){
  65. UserFindOne(query:$userId)
  66. {
  67. _id createdAt login nick avatar{_id url}
  68. likesCount followers{_id login nick} following{_id login nick}
  69. }
  70. }`,
  71. {
  72. userId: JSON.stringify([{ _id: getState().auth?.payload?.sub?.id }]),
  73. },
  74. ),
  75. ),
  76. )
  77. }
  78. const actionPostUpsert = (post) =>
  79. actionPromise(
  80. 'postUpsert',
  81. gql(
  82. `
  83. mutation PostUpsert($post:PostInput){
  84. PostUpsert(post:$post){
  85. _id title text
  86. }
  87. }`,
  88. {
  89. post: {
  90. ...post,
  91. images: post.images.map(({ _id }) => ({ _id })),
  92. // title: post.title,
  93. // text:post.text,
  94. // title:post.title
  95. },
  96. },
  97. ),
  98. )
  99. const actionSetAvatar = (file) => async (dispatch) => {
  100. let result = await dispatch(actionUploadFile(file))
  101. if (result) {
  102. await dispatch(actionAvatar(result._id))
  103. await dispatch(actionAboutMe())
  104. }
  105. }
  106. const Page2 = () => <div className="PageMain">юзер</div>
  107. const PageCreatePost = () => (
  108. <div style={{ maxWidth: '700px', maxHeight: '700px', background: '#FFFACD' }}>
  109. <h2>Edit Post</h2>
  110. <CBasic />
  111. </div>
  112. )
  113. const Main = () => (
  114. <main>
  115. <Switch>
  116. <Route path="/" exact component={PageMain} />
  117. <Route path="/profile/:_id" component={Page2} />
  118. <Route path="/edit/post" component={PageCreatePost} />
  119. <CBasic />
  120. </Switch>
  121. </main>
  122. )
  123. const Header = () => (
  124. <section className="Header">
  125. <AddPost />
  126. <Recommendations />
  127. <Likes />
  128. <CUser />
  129. </section>
  130. )
  131. const Likes = () => <button className="Likes"> Likes </button>
  132. const Recommendations = () => (
  133. <button className="Recomendations"> Recommendations </button>
  134. )
  135. const AddPost = ({ children }) => {
  136. const [state, setState] = useState(false)
  137. return (
  138. <>
  139. <Link to={`/edit/post`}>
  140. <button onClick={() => setState(!state)}> + </button>
  141. {!state && children}
  142. </Link>
  143. </>
  144. )
  145. }
  146. const CBasic = connect(null, { onLoad: actionSetAvatar })(Basic)
  147. // const CAddPost =connect(null,{actionPostUpsert})
  148. const User = ({ aboutMe: { _id, login, avatar } = {}, getData }) => (
  149. <Link className="User" to={`/profile/${_id}`} onClick={() => getData()}>
  150. <Avatar src={backendURL + '/' + avatar?.url || user} />
  151. </Link>
  152. )
  153. const CUser = connect(
  154. (state) => ({ aboutMe: state.promise.aboutMe?.payload }),
  155. { getData: actionAboutMe },
  156. )(User)
  157. function Basic({ onLoad }) {
  158. const { acceptedFiles, getRootProps, getInputProps } = useDropzone()
  159. const files = acceptedFiles.map((file) => (
  160. <li key={file.path}>
  161. {file.path} - {file.size} bytes
  162. </li>
  163. )) // console.log('FILES',acceptedFiles[0])
  164. useEffect(() => {
  165. acceptedFiles[0] && onLoad(acceptedFiles[0])
  166. }, [acceptedFiles])
  167. return (
  168. <section className="container">
  169. <div {...getRootProps({ className: 'Dropzone' })}>
  170. <input {...getInputProps()} />
  171. <Button icon={<UploadOutlined />}>
  172. Drag 'n' drop some files here, or click to select files
  173. </Button>
  174. </div>
  175. <aside>
  176. <h4 style={{ color: 'black' }}>Files</h4>
  177. <ul>{files}</ul>
  178. </aside>
  179. </section>
  180. )
  181. }
  182. const defaultPost = {
  183. _id: '620b83e3ad55d22f3e2fb319',
  184. title: 'Bmw',
  185. text: 'Bmw',
  186. images: [
  187. {
  188. _id: '620b8374ad55d22f3e2fb316',
  189. url: 'images/e125a428191726307968880977dac103',
  190. },
  191. {
  192. _id: '620b8399ad55d22f3e2fb317',
  193. url: 'images/4ae46578989c497582995ba8caeb5de5',
  194. },
  195. {
  196. _id: '620b83b0ad55d22f3e2fb318',
  197. url: 'images/ae839539f61249b15feda98cad7eb858',
  198. },
  199. ],
  200. }
  201. store.getState().auth?.token && store.dispatch(actionAboutMe())
  202. const SortableItem = sortableElement(({ url }) => (
  203. <li>
  204. <img
  205. src={backendURL + '/' + url}
  206. style={{ maxWidth: '200px', maxHeight: '200px' }}
  207. />
  208. </li>
  209. ))
  210. const SortableContainer = sortableContainer(({ children }) => {
  211. return (
  212. <>
  213. <ul>{children}</ul>
  214. {/* <input value={title}/> */}
  215. </>
  216. )
  217. })
  218. const Input = ({ state, onChangeText }) => (
  219. <input
  220. className="Input"
  221. value={state}
  222. placeholder={state || ''}
  223. onChange={onChangeText}
  224. />
  225. )
  226. const PostEditor = ({ post = defaultPost, onSave, onFileDrop, fileStatus }) => {
  227. const [state, setState] = useState(post)
  228. // const [newTitle, setTitle] = useState(post.title || '')
  229. const onSortEnd = ({ oldIndex, newIndex }) => {
  230. setState({
  231. ...state,
  232. images: arrayMoveImmutable(state.images, oldIndex, newIndex),
  233. })
  234. }
  235. const onChangeTitle=(event)=>
  236. setState({
  237. ...state,
  238. title: event.target.value})
  239. const onChangeText=(event)=>
  240. setState({
  241. ...state,
  242. text: event.target.value})
  243. return (
  244. <section className="Post">
  245. <SortableContainer onSortEnd={onSortEnd}>
  246. {(state.images || []).map(({ _id, url }, index) => (
  247. <SortableItem key={`item-${_id}`} index={index} url={url} />
  248. ))}
  249. </SortableContainer>
  250. <h1 className="Title"> Title </h1>
  251. <Input state={state.title} onChangeText={onChangeTitle}/>
  252. <h1 className="Title"> Text </h1>
  253. <Input state={state.text} onChangeText={onChangeText}/>
  254. <button onClick={() => onSave(state)}> Save </button>
  255. </section>
  256. )
  257. }
  258. const CPost = connect(null, { onSave: actionPostUpsert })(PostEditor)
  259. // fileStatus=actionUploadFile(entity)
  260. //по файлу в дропзоне:
  261. //дергать onFileDrop
  262. //fileStatus - информация о заливке файла из redux
  263. //через useEffect дождаться когда файл зальется
  264. //и сделать setState({...state, array: [...state.array, {объект файла с бэка с _id и url}]})
  265. //по react-sortable-hoc
  266. //делаете как в пример arrayMove для state.array
  267. //по кнопке сохранения делаем onSave(state)
  268. //где-то рядом остальные поля из state типа title name text
  269. //но это вы уже знаете
  270. const history = createHistory()
  271. function App() {
  272. return (
  273. <Router history={history}>
  274. <Provider store={store}>
  275. <div className="App">
  276. <Header />
  277. <Divider />
  278. <Main />
  279. {/* <PostEditor /> */}
  280. <CPost />
  281. {/* <MyImages Images={Images}/> */}
  282. </div>
  283. </Provider>
  284. </Router>
  285. )
  286. }
  287. export default App