├── .gitignore ├── README.md ├── package.json ├── public ├── _redirects ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.js ├── App.test.js ├── assets │ └── png │ │ ├── avatar.png │ │ └── instaclone.png ├── components │ ├── Auth │ │ ├── LoginForm │ │ │ ├── LoginForm.js │ │ │ ├── LoginForm.scss │ │ │ └── index.js │ │ └── RegisterForm │ │ │ ├── RegisterForm.js │ │ │ ├── RegisterForm.scss │ │ │ └── index.js │ ├── Header │ │ ├── Header.js │ │ ├── Header.scss │ │ ├── RightHeader │ │ │ ├── RightHeader.js │ │ │ ├── RightHeader.scss │ │ │ └── index.js │ │ ├── Search │ │ │ ├── Search.js │ │ │ ├── Search.scss │ │ │ └── index.js │ │ └── index.js │ ├── Home │ │ ├── Feed │ │ │ ├── Feed.js │ │ │ ├── Feed.scss │ │ │ └── index.js │ │ └── UsersNotFolloweds │ │ │ ├── UsersNotFolloweds.js │ │ │ ├── UsersNotFolloweds.scss │ │ │ └── index.js │ ├── Modal │ │ ├── ModalBasic │ │ │ ├── ModalBasic.js │ │ │ ├── ModalBasic.scss │ │ │ └── index.js │ │ ├── ModalPublication │ │ │ ├── Actions │ │ │ │ ├── Actions.js │ │ │ │ ├── Actions.scss │ │ │ │ └── index.js │ │ │ ├── CommentForm │ │ │ │ ├── CommentForm.js │ │ │ │ ├── CommentForm.scss │ │ │ │ └── index.js │ │ │ ├── Comments │ │ │ │ ├── Comments.js │ │ │ │ ├── Comments.scss │ │ │ │ └── index.js │ │ │ ├── ModalPublication.js │ │ │ ├── ModalPublication.scss │ │ │ └── index.js │ │ └── ModalUpload │ │ │ ├── ModalUpload.js │ │ │ ├── ModalUpload.scss │ │ │ └── index.js │ ├── Publications │ │ ├── PreviewPublication │ │ │ ├── PreviewPublication.js │ │ │ ├── PreviewPublication.scss │ │ │ └── index.js │ │ ├── Publications.js │ │ ├── Publications.scss │ │ └── index.js │ ├── User │ │ ├── AvatarForm │ │ │ ├── AvatarForm.js │ │ │ ├── AvatarForm.scss │ │ │ └── index.js │ │ ├── DescriptionForm │ │ │ ├── DescriptionForm.js │ │ │ ├── DescriptionForm.scss │ │ │ └── index.js │ │ ├── EmailForm │ │ │ ├── EmailForm.js │ │ │ ├── EmailForm.scss │ │ │ └── index.js │ │ ├── ListUsers │ │ │ ├── ListUsers.js │ │ │ ├── ListUsers.scss │ │ │ └── index.js │ │ ├── PasswordForm │ │ │ ├── PasswordForm.js │ │ │ ├── PasswordForm.scss │ │ │ └── index.js │ │ ├── Profile │ │ │ ├── Followers │ │ │ │ ├── Followers.js │ │ │ │ ├── Followers.scss │ │ │ │ └── index.js │ │ │ ├── HeaderProfile │ │ │ │ ├── HeaderProfile.js │ │ │ │ ├── HeaderProfile.scss │ │ │ │ └── index.js │ │ │ ├── Profile.js │ │ │ ├── Profile.scss │ │ │ └── index.js │ │ ├── SettignsForm │ │ │ ├── SettignsForm.js │ │ │ ├── SettignsForm.scss │ │ │ └── index.js │ │ └── SiteWebForm │ │ │ ├── SiteWebForm.js │ │ │ ├── SiteWebForm.scss │ │ │ └── index.js │ └── UserNotFound │ │ ├── UserNotFound.js │ │ ├── UserNotFound.scss │ │ └── index.js ├── config │ └── apollo.js ├── context │ └── AuthContext.js ├── gql │ ├── comment.js │ ├── follow.js │ ├── like.js │ ├── publication.js │ └── user.js ├── hooks │ └── useAuth.js ├── index.js ├── index.scss ├── layouts │ └── LayoutBasic.js ├── pages │ ├── Auth │ │ ├── Auth.js │ │ ├── Auth.scss │ │ └── index.js │ ├── Error404 │ │ ├── Error404.js │ │ ├── Error404.scss │ │ └── index.js │ ├── Home │ │ ├── Home.js │ │ ├── Home.scss │ │ └── index.js │ └── User.js ├── routes │ ├── Navigation.js │ └── routes.js ├── scss │ ├── colors.scss │ └── index.scss ├── serviceWorker.js ├── setupTests.js └── utils │ ├── constants.js │ └── token.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crea un Instagram con React, GraphQL, Apollo, MongoDB y AWS 2 | 3 | _Curso en Udemy donde te explico paso a paso la creacion un Instagram._ 4 | 5 | **Curso:** https://courses.agustinnavarrogaldon.com/instaclone 6 | 7 | ## Comenzando 🚀 8 | 9 | _Gracias a este curso aprenderás desde los conceptos básicos de **React, GraphQL, MongoDB, AWS**, hasta lo más **avanzado** para crear una APP completa desde cero **como un Instagram**._ 10 | 11 | _Una vez terminado el curso **serás capaz de crear cualquier tipo de aplicación** conectada a una base de datos con GraphQL sin necesidad de ayuda._ 12 | 13 | _También veremos como implementar los **S3 de Amazon AWS** al proyecto y **RealTime**_ 14 | 15 | ### Que veremos en el curso 16 | 17 | - React JS en profundidad y el uso de Hooks. 18 | - Crear aplicaciones completas con React, Apollo y GraphQL. 19 | - Como usar de Apollo Client y Apollo Server. 20 | - GraphQL, Mutation, Squemas, Resolvers, Query, etc... 21 | - Aprenderemos a hacer RealTime con Apollo, React y GrphQL 22 | - Como conectar los S3 de Amazon al servidor y al cliente. 23 | - Creamos un S3 Bucket en AWS para guardar todas las imágenes del servidor. 24 | - Subiremos desde nuestra app todas las publicaciones a Amazon. 25 | - Usaremos MongoDB y Mongoose para guardar los datos. 26 | - Aprendemos a usar SASS en un proyecto de React JS. 27 | - Veremos como usar variables de entorno. 28 | - Aprendemos a usar la biblioteca Semantic UI React para crear nuestra web. 29 | - -Veremos como crear componentes reutlizables. 30 | - Crear aplicaciones FullStack desde el frontend hasta el backend con JavaScript. 31 | 32 | --- 33 | 34 | ⌨️ con ❤️ por [xAgustin93](https://github.com/xAgustin93) 😊 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "instaclone", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@apollo/client": "^3.0.0-rc.8", 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.3.2", 9 | "@testing-library/user-event": "^7.1.2", 10 | "apollo-link-context": "^1.0.20", 11 | "apollo-upload-client": "^13.0.0", 12 | "formik": "^2.1.4", 13 | "graphql": "^15.1.0", 14 | "jwt-decode": "^2.2.0", 15 | "lodash": "^4.17.15", 16 | "node-sass": "^4.14.1", 17 | "react": "^16.13.1", 18 | "react-dom": "^16.13.1", 19 | "react-dropzone": "^11.0.1", 20 | "react-router-dom": "^5.2.0", 21 | "react-scripts": "3.4.1", 22 | "react-toastify": "^6.0.6", 23 | "semantic-ui-css": "^2.4.1", 24 | "semantic-ui-react": "^0.88.2", 25 | "yup": "^0.29.1" 26 | }, 27 | "scripts": { 28 | "start": "react-scripts start", 29 | "build": "react-scripts build", 30 | "test": "react-scripts test", 31 | "eject": "react-scripts eject" 32 | }, 33 | "eslintConfig": { 34 | "extends": "react-app" 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.2%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xAgustin93/instaclone-react-graphql_client/4841fcea5697da30e750878f26116d9f8aae7009/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 24 | Instaclone 25 | 26 | 27 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xAgustin93/instaclone-react-graphql_client/4841fcea5697da30e750878f26116d9f8aae7009/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xAgustin93/instaclone-react-graphql_client/4841fcea5697da30e750878f26116d9f8aae7009/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useMemo } from "react"; 2 | import { ApolloProvider } from "@apollo/client"; 3 | import { ToastContainer } from "react-toastify"; 4 | import client from "./config/apollo"; 5 | import Auth from "./pages/Auth"; 6 | import { getToken, decodeToken, removeToken } from "./utils/token"; 7 | import AuthContext from "./context/AuthContext"; 8 | import Navigation from "./routes/Navigation"; 9 | 10 | export default function App() { 11 | const [auth, setAuth] = useState(undefined); 12 | 13 | useEffect(() => { 14 | const token = getToken(); 15 | if (!token) { 16 | setAuth(null); 17 | } else { 18 | setAuth(decodeToken(token)); 19 | } 20 | }, []); 21 | 22 | const logout = () => { 23 | removeToken(); 24 | setAuth(null); 25 | }; 26 | 27 | const setUser = (user) => { 28 | setAuth(user); 29 | }; 30 | 31 | const authData = useMemo( 32 | () => ({ 33 | auth, 34 | logout, 35 | setUser, 36 | }), 37 | [auth] 38 | ); 39 | 40 | if (auth === undefined) return null; 41 | 42 | return ( 43 | 44 | 45 | {!auth ? : } 46 | 57 | 58 | 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/assets/png/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xAgustin93/instaclone-react-graphql_client/4841fcea5697da30e750878f26116d9f8aae7009/src/assets/png/avatar.png -------------------------------------------------------------------------------- /src/assets/png/instaclone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xAgustin93/instaclone-react-graphql_client/4841fcea5697da30e750878f26116d9f8aae7009/src/assets/png/instaclone.png -------------------------------------------------------------------------------- /src/components/Auth/LoginForm/LoginForm.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Form, Button } from "semantic-ui-react"; 3 | import { useFormik } from "formik"; 4 | import * as Yup from "yup"; 5 | import { useMutation } from "@apollo/client"; 6 | import { LOGIN } from "../../../gql/user"; 7 | import { setToken, decodeToken } from "../../../utils/token"; 8 | import useAuth from "../../../hooks/useAuth"; 9 | import "./LoginForm.scss"; 10 | 11 | export default function LoginForm() { 12 | const [error, setError] = useState(""); 13 | const [login] = useMutation(LOGIN); 14 | const { setUser } = useAuth(); 15 | 16 | const formik = useFormik({ 17 | initialValues: initialValues(), 18 | validationSchema: Yup.object({ 19 | email: Yup.string() 20 | .email("El email no es valido") 21 | .required("El emial es obligatorio"), 22 | password: Yup.string().required("La contraseña es obligatorio"), 23 | }), 24 | onSubmit: async (formData) => { 25 | setError(""); 26 | try { 27 | const { data } = await login({ 28 | variables: { 29 | input: formData, 30 | }, 31 | }); 32 | const { token } = data.login; 33 | setToken(token); 34 | setUser(decodeToken(token)); 35 | } catch (error) { 36 | setError(error.message); 37 | } 38 | }, 39 | }); 40 | 41 | return ( 42 |
43 |

Entra para ver fotos y vídeos de tus amigos.

44 | 52 | 60 | 63 | {error &&

{error}

} 64 | 65 | ); 66 | } 67 | 68 | function initialValues() { 69 | return { 70 | email: "", 71 | password: "", 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /src/components/Auth/LoginForm/LoginForm.scss: -------------------------------------------------------------------------------- 1 | @import "../../../scss/index.scss"; 2 | 3 | .login-form { 4 | h2 { 5 | font-size: 17px !important; 6 | text-align: center; 7 | margin-bottom: 20px; 8 | } 9 | input { 10 | padding: 15px !important; 11 | background-color: #f9f9f9 !important; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Auth/LoginForm/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./LoginForm"; 2 | -------------------------------------------------------------------------------- /src/components/Auth/RegisterForm/RegisterForm.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Form, Button } from "semantic-ui-react"; 3 | import { useFormik } from "formik"; 4 | import * as Yup from "yup"; 5 | import { toast } from "react-toastify"; 6 | import { useMutation } from "@apollo/client"; 7 | import { REGISTER } from "../../../gql/user"; 8 | import "./RegisterForm.scss"; 9 | 10 | export default function RegisterForm(props) { 11 | const { setShowLogin } = props; 12 | const [register] = useMutation(REGISTER); 13 | 14 | const formik = useFormik({ 15 | initialValues: initialValues(), 16 | validationSchema: Yup.object({ 17 | name: Yup.string().required("Tu nombre es obligatorio"), 18 | username: Yup.string() 19 | .matches( 20 | /^[a-zA-Z0-9-]*$/, 21 | "El nombre del usuario no puede tener espacio" 22 | ) 23 | .required("El nombre de usuario es obligatorio"), 24 | email: Yup.string() 25 | .email("El email no es valido") 26 | .required("El email es obligatorio"), 27 | password: Yup.string() 28 | .required("La contraseña es obligatoria") 29 | .oneOf([Yup.ref("repeatPassword")], "Las contraseñas no son iguales"), 30 | repeatPassword: Yup.string() 31 | .required("La contraseña es obligatoria") 32 | .oneOf([Yup.ref("password")], "Las contraseñas no son iguales"), 33 | }), 34 | onSubmit: async (formData) => { 35 | try { 36 | const newUser = formData; 37 | delete newUser.repeatPassword; 38 | 39 | await register({ 40 | variables: { 41 | input: newUser, 42 | }, 43 | }); 44 | toast.success("Usuario registrado correctamente"); 45 | setShowLogin(true); 46 | } catch (error) { 47 | toast.error(error.message); 48 | console.log(error); 49 | } 50 | }, 51 | }); 52 | 53 | return ( 54 | <> 55 |

56 | Regístrate para ver fotos y vídeos de tus amigos. 57 |

58 |
59 | 67 | 75 | 83 | 91 | 99 | 102 | 103 | 104 | ); 105 | } 106 | 107 | function initialValues() { 108 | return { 109 | name: "", 110 | username: "", 111 | email: "", 112 | password: "", 113 | repeatPassword: "", 114 | }; 115 | } 116 | -------------------------------------------------------------------------------- /src/components/Auth/RegisterForm/RegisterForm.scss: -------------------------------------------------------------------------------- 1 | @import "../../../scss/index.scss"; 2 | 3 | .register-form-title { 4 | font-size: 17px !important; 5 | text-align: center; 6 | margin-bottom: 20px; 7 | } 8 | 9 | .register-form { 10 | input { 11 | padding: 13px 15px !important; 12 | background-color: #f9f9f9 !important; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Auth/RegisterForm/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./RegisterForm"; 2 | -------------------------------------------------------------------------------- /src/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Container, Grid, Image } from "semantic-ui-react"; 3 | import { Link } from "react-router-dom"; 4 | import Logo from "../../assets/png/instaclone.png"; 5 | import RightHeader from "./RightHeader"; 6 | import Search from "./Search"; 7 | import "./Header.scss"; 8 | 9 | export default function Header() { 10 | return ( 11 |
12 | 13 | 14 | 15 | 16 | Instaclone 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/Header/Header.scss: -------------------------------------------------------------------------------- 1 | @import "../../scss/index.scss"; 2 | 3 | .header { 4 | position: relative; 5 | border-bottom: 1px solid #dbdbdb; 6 | padding: 12px 0; 7 | background-color: $background-light; 8 | z-index: 1; 9 | 10 | &__logo { 11 | display: flex !important; 12 | align-items: center; 13 | 14 | img { 15 | height: 25px; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Header/RightHeader/RightHeader.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Icon, Image } from "semantic-ui-react"; 3 | import { Link } from "react-router-dom"; 4 | import { useQuery } from "@apollo/client"; 5 | import { GET_USER } from "../../../gql/user"; 6 | import useAuth from "../../../hooks/useAuth"; 7 | import ModalUpload from "../../Modal/ModalUpload"; 8 | import ImageNoFound from "../../../assets/png/avatar.png"; 9 | import "./RightHeader.scss"; 10 | 11 | export default function RightHeader() { 12 | const [showModal, setShowModal] = useState(false); 13 | const { auth } = useAuth(); 14 | const { data, loading, error } = useQuery(GET_USER, { 15 | variables: { username: auth.username }, 16 | }); 17 | 18 | if (loading || error) return null; 19 | const { getUser } = data; 20 | 21 | return ( 22 | <> 23 |
24 | 25 | 26 | 27 | setShowModal(true)} /> 28 | 29 | 30 | 31 |
32 | 33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/components/Header/RightHeader/RightHeader.scss: -------------------------------------------------------------------------------- 1 | @import "../../../scss/index.scss"; 2 | 3 | .right-header { 4 | display: flex; 5 | align-items: center; 6 | justify-content: flex-end; 7 | height: 100%; 8 | 9 | i.icon { 10 | height: 100%; 11 | display: flex; 12 | align-items: center; 13 | font-size: 19px; 14 | color: $font-dark; 15 | } 16 | 17 | .ui.image.avatar { 18 | width: 25px; 19 | height: 25px; 20 | border: 1px solid $border-dark; 21 | padding: 2px; 22 | } 23 | 24 | i.icon, 25 | .ui.image.avatar { 26 | margin: 0 0 0 19px; 27 | &:hover { 28 | cursor: pointer; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/Header/RightHeader/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./RightHeader"; 2 | -------------------------------------------------------------------------------- /src/components/Header/Search/Search.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Search as SearchSU, Image } from "semantic-ui-react"; 3 | import { Link } from "react-router-dom"; 4 | import { size } from "lodash"; 5 | import { useQuery } from "@apollo/client"; 6 | import { SEARCH } from "../../../gql/user"; 7 | import ImageNoFound from "../../../assets/png/avatar.png"; 8 | import "./Search.scss"; 9 | 10 | export default function Search() { 11 | const [search, setSearch] = useState(null); 12 | const [results, setResults] = useState([]); 13 | const { data, loading } = useQuery(SEARCH, { 14 | variables: { search }, 15 | }); 16 | 17 | useEffect(() => { 18 | if (size(data?.search) > 0) { 19 | const users = []; 20 | data.search.forEach((user, index) => { 21 | users.push({ 22 | key: index, 23 | title: user.name, 24 | username: user.username, 25 | avatar: user.avatar, 26 | }); 27 | }); 28 | setResults(users); 29 | } else { 30 | setResults([]); 31 | } 32 | }, [data]); 33 | 34 | const onChange = (e) => { 35 | if (e.target.value) setSearch(e.target.value); 36 | else setSearch(null); 37 | }; 38 | 39 | const handleResultSelect = () => { 40 | setSearch(null); 41 | setResults([]); 42 | }; 43 | 44 | return ( 45 | } 55 | /> 56 | ); 57 | } 58 | 59 | function ResultSearch(props) { 60 | const { data } = props; 61 | 62 | return ( 63 | 64 | 65 |
66 |

{data.title}

67 |

{data.username}

68 |
69 | 70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /src/components/Header/Search/Search.scss: -------------------------------------------------------------------------------- 1 | @import "../../../scss/index.scss"; 2 | 3 | .search-users { 4 | .ui.left.icon.input { 5 | width: 100%; 6 | } 7 | 8 | .results { 9 | max-height: 60vh; 10 | overflow-y: scroll; 11 | } 12 | 13 | &__item { 14 | display: flex; 15 | align-items: center; 16 | 17 | .ui.image { 18 | width: 35px !important; 19 | height: 35px !important; 20 | border-radius: 50% !important; 21 | margin-right: 20px; 22 | } 23 | 24 | p { 25 | margin: 0; 26 | color: $font-dark; 27 | 28 | &:first-of-type { 29 | font-weight: bold; 30 | } 31 | &:last-of-type { 32 | color: $font-grey; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/components/Header/Search/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./Search"; 2 | -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./Header"; 2 | -------------------------------------------------------------------------------- /src/components/Home/Feed/Feed.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Image } from "semantic-ui-react"; 3 | import { map } from "lodash"; 4 | import { Link } from "react-router-dom"; 5 | import { useQuery } from "@apollo/client"; 6 | import { GET_PUBLICATIONS_FOLLOWEDS } from "../../../gql/publication"; 7 | import Action from "../../Modal/ModalPublication/Actions"; 8 | import CommentForm from "../../Modal/ModalPublication/CommentForm"; 9 | import ImageNotFound from "../../../assets/png/avatar.png"; 10 | import ModalPublication from "../../Modal/ModalPublication"; 11 | import "./Feed.scss"; 12 | 13 | export default function Feed() { 14 | const [showModal, setShowModal] = useState(false); 15 | const [publicationSelect, setPublicationSelect] = useState(null); 16 | const { data, loading, startPolling, stopPolling } = useQuery( 17 | GET_PUBLICATIONS_FOLLOWEDS 18 | ); 19 | 20 | useEffect(() => { 21 | startPolling(1000); 22 | return () => { 23 | stopPolling(); 24 | }; 25 | }, [startPolling, stopPolling]); 26 | 27 | if (loading) return null; 28 | const { getPublicationsFolloweds } = data; 29 | 30 | const openPublication = (publication) => { 31 | setPublicationSelect(publication); 32 | setShowModal(true); 33 | }; 34 | 35 | return ( 36 | <> 37 |
38 | {map(getPublicationsFolloweds, (publication, index) => ( 39 |
40 | 41 |
42 | 46 | {publication.idUser.name} 47 |
48 | 49 |
openPublication(publication)} 53 | /> 54 |
55 | 56 |
57 |
58 | 59 |
60 |
61 | ))} 62 |
63 | {showModal && ( 64 | 69 | )} 70 | 71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /src/components/Home/Feed/Feed.scss: -------------------------------------------------------------------------------- 1 | @import "../../../scss/index.scss"; 2 | 3 | .feed { 4 | padding-top: 50px; 5 | height: calc(100vh - 48px); 6 | overflow-y: scroll; 7 | &::-webkit-scrollbar { 8 | display: none; 9 | } 10 | 11 | &__box { 12 | margin-bottom: 50px; 13 | border: 1px solid $border-grey; 14 | 15 | &-user { 16 | background-color: $background-light; 17 | padding: 15px !important; 18 | color: $font-dark; 19 | 20 | img { 21 | margin-right: 15px !important; 22 | } 23 | 24 | span { 25 | font-weight: bold; 26 | } 27 | } 28 | 29 | &-photo { 30 | height: 600px; 31 | background-position: center; 32 | background-repeat: no-repeat; 33 | background-size: cover; 34 | 35 | &:hover { 36 | cursor: pointer; 37 | opacity: 0.95; 38 | } 39 | } 40 | 41 | &-actions { 42 | padding: 5px 5px; 43 | 44 | i { 45 | margin-right: 10px !important; 46 | } 47 | } 48 | 49 | &-form { 50 | > form { 51 | position: relative !important; 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/Home/Feed/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./Feed"; 2 | -------------------------------------------------------------------------------- /src/components/Home/UsersNotFolloweds/UsersNotFolloweds.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Image } from "semantic-ui-react"; 3 | import { Link } from "react-router-dom"; 4 | import { map } from "lodash"; 5 | import { useQuery } from "@apollo/client"; 6 | import { GET_NOT_FOLLOWEDS } from "../../../gql/follow"; 7 | import ImageNotFound from "../../../assets/png/avatar.png"; 8 | import "./UsersNotFolloweds.scss"; 9 | 10 | export default function UsersNotFolloweds() { 11 | const { data, loading } = useQuery(GET_NOT_FOLLOWEDS); 12 | 13 | if (loading) return null; 14 | const { getNotFolloweds } = data; 15 | 16 | return ( 17 |
18 |

Usuarios que no sigues

19 | {map(getNotFolloweds, (user, index) => ( 20 | 25 | 26 | {user.name} 27 | 28 | ))} 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/components/Home/UsersNotFolloweds/UsersNotFolloweds.scss: -------------------------------------------------------------------------------- 1 | @import "../../../scss/index.scss"; 2 | 3 | .users-not-followeds { 4 | display: flex; 5 | flex-direction: column; 6 | margin-top: 35px; 7 | 8 | &__user { 9 | margin-top: 20px; 10 | color: $font-dark; 11 | 12 | img { 13 | margin-right: 15px !important; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Home/UsersNotFolloweds/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./UsersNotFolloweds"; 2 | -------------------------------------------------------------------------------- /src/components/Modal/ModalBasic/ModalBasic.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Modal } from "semantic-ui-react"; 3 | import "./ModalBasic.scss"; 4 | 5 | export default function ModalBasic(props) { 6 | const { show, setShow, title, children } = props; 7 | 8 | const onClose = () => { 9 | setShow(false); 10 | }; 11 | 12 | return ( 13 | 14 | {title && {title}} 15 | {children} 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Modal/ModalBasic/ModalBasic.scss: -------------------------------------------------------------------------------- 1 | @import "../../../scss/index.scss"; 2 | 3 | .modal-basic { 4 | border-radius: 15px !important; 5 | overflow: hidden; 6 | 7 | .header { 8 | text-align: center; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/Modal/ModalBasic/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./ModalBasic"; 2 | -------------------------------------------------------------------------------- /src/components/Modal/ModalPublication/Actions/Actions.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Icon } from "semantic-ui-react"; 3 | import { useMutation, useQuery } from "@apollo/client"; 4 | import { 5 | ADD_LIKE, 6 | IS_LIKE, 7 | DELETE_LIKE, 8 | COUNT_LIKE, 9 | } from "../../../../gql/like"; 10 | import "./Actions.scss"; 11 | 12 | export default function Actions(props) { 13 | const { publication } = props; 14 | const [loadingAction, setLoadingAction] = useState(false); 15 | const [addLike] = useMutation(ADD_LIKE); 16 | const [deleteLike] = useMutation(DELETE_LIKE); 17 | const { data, loading, refetch } = useQuery(IS_LIKE, { 18 | variables: { idPublication: publication.id }, 19 | }); 20 | const { 21 | data: dataCount, 22 | loading: loadingCount, 23 | refetch: refetchCount, 24 | } = useQuery(COUNT_LIKE, { 25 | variables: { idPublication: publication.id }, 26 | }); 27 | 28 | const onAddLike = async () => { 29 | setLoadingAction(true); 30 | try { 31 | await addLike({ 32 | variables: { 33 | idPublication: publication.id, 34 | }, 35 | }); 36 | refetch(); 37 | refetchCount(); 38 | } catch (error) { 39 | console.log(error); 40 | } 41 | setLoadingAction(false); 42 | }; 43 | 44 | const onDeleteLike = async () => { 45 | setLoadingAction(true); 46 | try { 47 | await deleteLike({ 48 | variables: { 49 | idPublication: publication.id, 50 | }, 51 | }); 52 | refetch(); 53 | refetchCount(); 54 | } catch (error) { 55 | console.log(error); 56 | } 57 | setLoadingAction(false); 58 | }; 59 | 60 | const onAction = () => { 61 | if (!loadingAction) { 62 | if (isLike) { 63 | onDeleteLike(); 64 | } else { 65 | onAddLike(); 66 | } 67 | } 68 | }; 69 | 70 | if (loading || loadingCount) return null; 71 | const { isLike } = data; 72 | const { countLikes } = dataCount; 73 | 74 | return ( 75 |
76 | 81 | {countLikes} {countLikes === 1 ? "Like" : "Likes"} 82 |
83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /src/components/Modal/ModalPublication/Actions/Actions.scss: -------------------------------------------------------------------------------- 1 | @import "../../../../scss/index.scss"; 2 | 3 | .actions { 4 | height: 35px; 5 | display: flex; 6 | align-items: center; 7 | padding: 0 10px; 8 | 9 | i { 10 | font-size: 16px !important; 11 | 12 | &:hover { 13 | cursor: pointer; 14 | } 15 | } 16 | 17 | .like.active { 18 | color: $danger; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/Modal/ModalPublication/Actions/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./Actions"; 2 | -------------------------------------------------------------------------------- /src/components/Modal/ModalPublication/CommentForm/CommentForm.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Form, Button } from "semantic-ui-react"; 3 | import { useFormik } from "formik"; 4 | import * as Yup from "yup"; 5 | import { useMutation } from "@apollo/client"; 6 | import { ADD_COMMENT } from "../../../../gql/comment"; 7 | import "./CommentForm.scss"; 8 | 9 | export default function CommentForm(props) { 10 | const { publication } = props; 11 | const [addComment] = useMutation(ADD_COMMENT); 12 | 13 | const formik = useFormik({ 14 | initialValues: { 15 | comment: "", 16 | }, 17 | validationSchema: Yup.object({ 18 | comment: Yup.string().required(), 19 | }), 20 | onSubmit: async (formData) => { 21 | try { 22 | await addComment({ 23 | variables: { 24 | input: { 25 | idPublication: publication.id, 26 | comment: formData.comment, 27 | }, 28 | }, 29 | }); 30 | formik.handleReset(); 31 | } catch (error) { 32 | console.log(error); 33 | } 34 | }, 35 | }); 36 | 37 | return ( 38 |
39 | 46 | 47 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /src/components/Modal/ModalPublication/CommentForm/CommentForm.scss: -------------------------------------------------------------------------------- 1 | @import "../../../../scss/index.scss"; 2 | 3 | .comment-form { 4 | position: absolute !important; 5 | bottom: 0; 6 | display: flex; 7 | width: 100%; 8 | 9 | .field { 10 | margin: 0 !important; 11 | width: 100%; 12 | 13 | .ui.input { 14 | height: 60px; 15 | input { 16 | border: 0; 17 | border-top: 1px solid $border-grey; 18 | border-radius: 0; 19 | padding-right: 80px; 20 | } 21 | } 22 | } 23 | 24 | .ui.button { 25 | background-color: transparent; 26 | margin: 0; 27 | position: absolute; 28 | right: 0; 29 | height: 100%; 30 | 31 | &:hover { 32 | background-color: transparent; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/Modal/ModalPublication/CommentForm/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./CommentForm"; 2 | -------------------------------------------------------------------------------- /src/components/Modal/ModalPublication/Comments/Comments.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { Image } from "semantic-ui-react"; 3 | import { map } from "lodash"; 4 | import { Link } from "react-router-dom"; 5 | import { useQuery } from "@apollo/client"; 6 | import { GET_COMMENTS } from "../../../../gql/comment"; 7 | import ImageNoFound from "../../../../assets/png/avatar.png"; 8 | import "./Comments.scss"; 9 | 10 | export default function Comments(props) { 11 | const { publication } = props; 12 | const { data, loading, startPolling, stopPolling } = useQuery(GET_COMMENTS, { 13 | variables: { 14 | idPublication: publication.id, 15 | }, 16 | }); 17 | 18 | useEffect(() => { 19 | startPolling(1000); 20 | return () => { 21 | stopPolling(); 22 | }; 23 | }, [startPolling, stopPolling]); 24 | 25 | if (loading) return null; 26 | const { getComments } = data; 27 | 28 | return ( 29 |
30 | {map(getComments, (comment, index) => ( 31 | 36 | 37 |
38 |

{comment.idUser.username}

39 |

{comment.comment}

40 |
41 | 42 | ))} 43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/components/Modal/ModalPublication/Comments/Comments.scss: -------------------------------------------------------------------------------- 1 | @import "../../../../scss/index.scss"; 2 | 3 | .comments { 4 | height: calc(100% - 95px); 5 | overflow-y: scroll; 6 | 7 | .comment { 8 | display: flex; 9 | align-items: center; 10 | padding: 10px; 11 | border-bottom: 1px solid $border-grey; 12 | color: $font-dark; 13 | 14 | img { 15 | margin-right: 15px !important; 16 | } 17 | 18 | p { 19 | margin: 0; 20 | 21 | &:first-of-type { 22 | font-weight: bold; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Modal/ModalPublication/Comments/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./Comments"; 2 | -------------------------------------------------------------------------------- /src/components/Modal/ModalPublication/ModalPublication.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Modal, Grid } from "semantic-ui-react"; 3 | import Comments from "./Comments"; 4 | import Actions from "./Actions"; 5 | import CommentForm from "./CommentForm"; 6 | import "./ModalPublication.scss"; 7 | 8 | export default function ModalPublication(props) { 9 | const { show, setShow, publication } = props; 10 | 11 | const onClose = () => setShow(false); 12 | 13 | return ( 14 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/Modal/ModalPublication/ModalPublication.scss: -------------------------------------------------------------------------------- 1 | @import "../../../scss/index.scss"; 2 | 3 | .modal-publication { 4 | height: 520px !important; 5 | 6 | .ui.grid { 7 | margin: 0; 8 | height: 100%; 9 | } 10 | 11 | &__left { 12 | background-position: center; 13 | background-size: cover; 14 | background-repeat: no-repeat; 15 | } 16 | 17 | &__right { 18 | padding: 0 !important; 19 | height: 100%; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/Modal/ModalPublication/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./ModalPublication"; 2 | -------------------------------------------------------------------------------- /src/components/Modal/ModalUpload/ModalUpload.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback } from "react"; 2 | import { Modal, Icon, Button, Dimmer, Loader } from "semantic-ui-react"; 3 | import { toast } from "react-toastify"; 4 | import { useDropzone } from "react-dropzone"; 5 | import { useMutation } from "@apollo/client"; 6 | import { PUBLISH } from "../../../gql/publication"; 7 | import "./ModalUpload.scss"; 8 | 9 | export default function ModalUpload(props) { 10 | const { show, setShow } = props; 11 | const [fileUpload, setFileUpload] = useState(null); 12 | const [isLoading, setIsLoading] = useState(false); 13 | const [publish] = useMutation(PUBLISH); 14 | 15 | const onDrop = useCallback((acceptedFile) => { 16 | const file = acceptedFile[0]; 17 | setFileUpload({ 18 | type: "image", 19 | file, 20 | preview: URL.createObjectURL(file), 21 | }); 22 | }); 23 | 24 | const { getRootProps, getInputProps } = useDropzone({ 25 | accept: "image/jpeg, image/png", 26 | noKeyboard: true, 27 | multiple: false, 28 | onDrop, 29 | }); 30 | 31 | const onClose = () => { 32 | setIsLoading(false); 33 | setFileUpload(null); 34 | setShow(false); 35 | }; 36 | 37 | const onPublish = async () => { 38 | try { 39 | setIsLoading(true); 40 | const result = await publish({ 41 | variables: { 42 | file: fileUpload.file, 43 | }, 44 | }); 45 | const { data } = result; 46 | 47 | if (!data.publish.status) { 48 | toast.warning("Error en la publicación"); 49 | isLoading(false); 50 | } else { 51 | onClose(); 52 | } 53 | } catch (error) { 54 | console.log(error); 55 | } 56 | }; 57 | 58 | return ( 59 | 60 |
65 | {!fileUpload && ( 66 | <> 67 | 68 |

Arrastra tu foto que quieras publicar

69 | 70 | )} 71 | 72 |
73 | 74 | {fileUpload?.type === "image" && ( 75 |
79 | )} 80 | 81 | {fileUpload && ( 82 | 85 | )} 86 | 87 | {isLoading && ( 88 | 89 | 90 |

Publicando...

91 |
92 | )} 93 | 94 | ); 95 | } 96 | -------------------------------------------------------------------------------- /src/components/Modal/ModalUpload/ModalUpload.scss: -------------------------------------------------------------------------------- 1 | @import "../../../scss/index.scss"; 2 | 3 | .modal-upload { 4 | border-radius: 15px !important; 5 | overflow: hidden; 6 | padding: 8px; 7 | width: 650px; 8 | height: 650px; 9 | 10 | .dropzone { 11 | height: 100%; 12 | width: 100%; 13 | display: flex; 14 | align-items: center; 15 | justify-content: center; 16 | flex-direction: column; 17 | border: 2px dashed $border-dark; 18 | border-radius: 15px !important; 19 | position: relative; 20 | z-index: 1; 21 | 22 | i { 23 | font-size: 50px; 24 | color: $font-grey; 25 | } 26 | p { 27 | margin-block-start: 20px; 28 | } 29 | } 30 | 31 | .image { 32 | position: absolute; 33 | top: 0; 34 | left: 0; 35 | width: 100%; 36 | height: 100%; 37 | background-repeat: no-repeat; 38 | background-position: center; 39 | background-size: cover; 40 | } 41 | 42 | .btn-upload { 43 | position: absolute; 44 | bottom: 0; 45 | left: 0; 46 | width: 100%; 47 | height: 45px; 48 | border: 0 !important; 49 | border-radius: 0 !important; 50 | margin: 0 !important; 51 | z-index: 2; 52 | } 53 | 54 | .publishing { 55 | p { 56 | margin-top: 80px; 57 | font-size: 18px; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/Modal/ModalUpload/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./ModalUpload"; 2 | -------------------------------------------------------------------------------- /src/components/Publications/PreviewPublication/PreviewPublication.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Image } from "semantic-ui-react"; 3 | import ModalPublication from "../../Modal/ModalPublication"; 4 | import "./PreviewPublication.scss"; 5 | 6 | export default function PreviewPublication(props) { 7 | const { publication } = props; 8 | const [showModal, setShowModal] = useState(false); 9 | 10 | return ( 11 | <> 12 |
setShowModal(true)}> 13 | 14 |
15 | 16 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/components/Publications/PreviewPublication/PreviewPublication.scss: -------------------------------------------------------------------------------- 1 | @import "../../../scss/index.scss"; 2 | 3 | .preview-publication { 4 | height: 100%; 5 | 6 | &:hover { 7 | cursor: pointer; 8 | opacity: 0.9; 9 | } 10 | 11 | &__image { 12 | width: 100%; 13 | height: 100%; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Publications/PreviewPublication/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./PreviewPublication"; 2 | -------------------------------------------------------------------------------- /src/components/Publications/Publications.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Grid } from "semantic-ui-react"; 3 | import { map } from "lodash"; 4 | import PreviewPublication from "./PreviewPublication"; 5 | import "./Publications.scss"; 6 | 7 | export default function Publications(props) { 8 | const { getPublications } = props; 9 | 10 | return ( 11 |
12 |

Publicaciones

13 | 14 | {map(getPublications, (publication, index) => ( 15 | 16 | 17 | 18 | ))} 19 | 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Publications/Publications.scss: -------------------------------------------------------------------------------- 1 | @import "../../scss/index.scss"; 2 | 3 | .publications { 4 | margin-top: 50px; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Publications/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./Publications"; 2 | -------------------------------------------------------------------------------- /src/components/User/AvatarForm/AvatarForm.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback } from "react"; 2 | import { Button } from "semantic-ui-react"; 3 | import { useDropzone } from "react-dropzone"; 4 | import { toast } from "react-toastify"; 5 | import { useMutation } from "@apollo/client"; 6 | import { UPDATE_AVATAR, GET_USER, DELETE_AVATAR } from "../../../gql/user"; 7 | import "./AvatarForm.scss"; 8 | import AuthContext from "../../../context/AuthContext"; 9 | 10 | export default function AvatarForm(props) { 11 | const { setShowModal, auth } = props; 12 | const [loading, setLoading] = useState(false); 13 | 14 | const [updateAvatar] = useMutation(UPDATE_AVATAR, { 15 | update(cache, { data: { updateAvatar } }) { 16 | const { getUser } = cache.readQuery({ 17 | query: GET_USER, 18 | variables: { username: auth.username }, 19 | }); 20 | 21 | cache.writeQuery({ 22 | query: GET_USER, 23 | variables: { username: auth.username }, 24 | data: { 25 | getUser: { ...getUser, avatar: updateAvatar.urlAvatar }, 26 | }, 27 | }); 28 | }, 29 | }); 30 | 31 | const [deleteAvatar] = useMutation(DELETE_AVATAR, { 32 | update(cache) { 33 | const { getUser } = cache.readQuery({ 34 | query: GET_USER, 35 | variables: { username: auth.username }, 36 | }); 37 | 38 | cache.writeQuery({ 39 | query: GET_USER, 40 | variables: { username: auth.username }, 41 | data: { 42 | getUser: { ...getUser, avatar: "" }, 43 | }, 44 | }); 45 | }, 46 | }); 47 | 48 | const onDrop = useCallback(async (acceptedFile) => { 49 | const file = acceptedFile[0]; 50 | 51 | try { 52 | setLoading(true); 53 | const result = await updateAvatar({ variables: { file } }); 54 | const { data } = result; 55 | 56 | if (!data.updateAvatar.status) { 57 | toast.warning("Error al actualizar el avatar"); 58 | setLoading(false); 59 | } else { 60 | setLoading(false); 61 | setShowModal(false); 62 | } 63 | } catch (error) { 64 | console.log(error); 65 | } 66 | }, []); 67 | 68 | const { getRootProps, getInputProps } = useDropzone({ 69 | accept: "image/jpeg, image/png", 70 | noKeyboard: true, 71 | multiple: false, 72 | onDrop, 73 | }); 74 | 75 | const onDeleteAvatar = async () => { 76 | try { 77 | const result = await deleteAvatar(); 78 | const { data } = result; 79 | 80 | if (!data.deleteAvatar) { 81 | toast.warning("Error al borrar el avatar"); 82 | } else { 83 | setShowModal(false); 84 | } 85 | } catch (error) { 86 | console.log(error); 87 | } 88 | }; 89 | 90 | return ( 91 |
92 | 95 | 96 | 97 | 98 |
99 | ); 100 | } 101 | -------------------------------------------------------------------------------- /src/components/User/AvatarForm/AvatarForm.scss: -------------------------------------------------------------------------------- 1 | @import "../../../scss/index.scss"; 2 | 3 | .avatar-form { 4 | display: flex; 5 | flex-direction: column; 6 | 7 | .ui.button { 8 | background-color: $background-light; 9 | border-bottom: 1px solid $border-grey; 10 | padding: 18px 0; 11 | margin: 0; 12 | border-radius: 0; 13 | 14 | &:nth-child(1) { 15 | color: $action; 16 | } 17 | &:nth-child(2) { 18 | color: $danger; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/User/AvatarForm/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./AvatarForm"; 2 | -------------------------------------------------------------------------------- /src/components/User/DescriptionForm/DescriptionForm.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Form, TextArea, Button } from "semantic-ui-react"; 3 | import { toast } from "react-toastify"; 4 | import { useFormik } from "formik"; 5 | import * as Yup from "yup"; 6 | import { useMutation } from "@apollo/client"; 7 | import { UPDATE_USER } from "../../../gql/user"; 8 | import "./DescriptionForm.scss"; 9 | 10 | export default function DescriptionForm(props) { 11 | const { setShowModal, currentDescription, refetch } = props; 12 | const [updateUser] = useMutation(UPDATE_USER); 13 | 14 | const formik = useFormik({ 15 | initialValues: { 16 | description: currentDescription || "", 17 | }, 18 | validationSchema: Yup.object({ 19 | description: Yup.string().required(), 20 | }), 21 | onSubmit: async (formData) => { 22 | try { 23 | await updateUser({ 24 | variables: { 25 | input: formData, 26 | }, 27 | }); 28 | refetch(); 29 | setShowModal(false); 30 | } catch (error) { 31 | toast.error("Error al actualziar tu biografía"); 32 | } 33 | }, 34 | }); 35 | 36 | return ( 37 |
38 |