├── .env.example
├── .env
├── .prettierrc
├── public
├── _logo192.png
├── favicon.ico
├── logo192.png
├── logo512.png
├── robots.txt
├── routes.json
├── manifest.json
└── index.html
├── src
├── img
│ ├── icons
│ │ ├── menu.png
│ │ ├── close.png
│ │ └── plus-icon.png
│ ├── MarcoBrunoDev.png
│ ├── cursoFrontEnd.png
│ ├── live-28-04-2020.png
│ ├── marcos-castro.png
│ └── social-media
│ │ ├── discord.png
│ │ ├── github.png
│ │ ├── twitch.png
│ │ ├── twitter.png
│ │ ├── youtube.png
│ │ ├── facebook.png
│ │ ├── instagram.png
│ │ └── linkedin.png
├── libs
│ ├── validation
│ │ ├── required.js
│ │ ├── email.js
│ │ ├── maxLength.js
│ │ ├── minLength.js
│ │ ├── index.js
│ │ └── useValidation.js
│ ├── date
│ │ └── index.js
│ └── social
│ │ └── index.js
├── components
│ ├── PhotoLive
│ │ └── index.js
│ ├── PlusButton
│ │ ├── index.js
│ │ └── styles.js
│ ├── MsgError
│ │ └── index.js
│ ├── LogoDev
│ │ ├── index.js
│ │ └── styles.js
│ ├── ModalNes
│ │ └── index.js
│ ├── TextNes
│ │ └── index.js
│ ├── SocialMedia
│ │ ├── styles.js
│ │ └── index.js
│ ├── FormNes
│ │ ├── index.js
│ │ └── styles.js
│ ├── TextareaNes
│ │ ├── styles.js
│ │ └── index.js
│ ├── TitleNes
│ │ └── index.js
│ ├── TagNes
│ │ └── index.js
│ ├── ActionNes
│ │ └── index.js
│ ├── FieldNes
│ │ ├── styles.js
│ │ └── index.js
│ ├── ContainerNes
│ │ └── index.js
│ ├── ButtonShare
│ │ └── index.js
│ └── ButtonNes
│ │ └── index.js
├── App.js
├── services
│ ├── axios.js
│ └── lives.service.js
├── pages
│ ├── HomePage
│ │ └── index.js
│ ├── PostsDev
│ │ └── index.js
│ ├── ContactsDev
│ │ └── index.js
│ ├── ProjectsDev
│ │ └── index.js
│ ├── LiveNew
│ │ └── index.js
│ ├── Template
│ │ └── index.js
│ ├── LivesSchedule
│ │ └── index.js
│ └── LiveDetails
│ │ └── index.js
├── styles
│ ├── settings
│ │ ├── Gaps.js
│ │ ├── Size.js
│ │ ├── Colors.js
│ │ └── Cursor.js
│ ├── tools
│ │ └── _containers.js
│ ├── elements
│ │ └── Base.js
│ └── generic
│ │ └── Reset.js
├── containers
│ ├── CardDescription
│ │ └── index.js
│ ├── ContainerDev
│ │ └── index.js
│ ├── HeaderDev
│ │ ├── index.js
│ │ └── styles.js
│ ├── WrapperCard
│ │ └── index.js
│ ├── CardGuest
│ │ ├── styles.js
│ │ └── index.js
│ ├── FooterDev
│ │ ├── styles.js
│ │ └── index.js
│ ├── CardInfo
│ │ ├── index.js
│ │ └── styles.js
│ ├── ShareSocial
│ │ ├── styles.js
│ │ └── index.js
│ ├── CardDay
│ │ ├── index.js
│ │ └── styles.js
│ ├── FormLive
│ │ ├── validation.js
│ │ └── index.js
│ ├── MenuDev
│ │ ├── index.js
│ │ └── styles.js
│ └── InfosLive
│ │ ├── styles.js
│ │ └── index.js
├── index.js
└── Routes.js
├── .vscode
└── settings.json
├── cypress
├── fixtures
│ └── example.json
├── support
│ ├── index.js
│ └── commands.js
├── plugins
│ └── index.js
├── data-builders
│ └── lives.builder.js
└── integration
│ └── pages
│ └── LiveNew.test.js
├── cypress.json
├── .gitignore
├── .eslintrc
├── package.json
├── .github
└── workflows
│ └── azure-static-web-apps-lemon-hill-0129ffe10.yml
└── README.md
/.env.example:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | REACT_APP_API=http://192.168.0.4:3001/api
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "singleQuote": true
4 | }
5 |
--------------------------------------------------------------------------------
/public/_logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/public/_logo192.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/routes.json:
--------------------------------------------------------------------------------
1 | { "routes": [{ "route": "/*", "serve": "/index.html", "statusCode": 200 }] }
2 |
--------------------------------------------------------------------------------
/src/img/icons/menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/src/img/icons/menu.png
--------------------------------------------------------------------------------
/src/img/icons/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/src/img/icons/close.png
--------------------------------------------------------------------------------
/src/img/MarcoBrunoDev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/src/img/MarcoBrunoDev.png
--------------------------------------------------------------------------------
/src/img/cursoFrontEnd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/src/img/cursoFrontEnd.png
--------------------------------------------------------------------------------
/src/img/icons/plus-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/src/img/icons/plus-icon.png
--------------------------------------------------------------------------------
/src/img/live-28-04-2020.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/src/img/live-28-04-2020.png
--------------------------------------------------------------------------------
/src/img/marcos-castro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/src/img/marcos-castro.png
--------------------------------------------------------------------------------
/src/img/social-media/discord.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/src/img/social-media/discord.png
--------------------------------------------------------------------------------
/src/img/social-media/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/src/img/social-media/github.png
--------------------------------------------------------------------------------
/src/img/social-media/twitch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/src/img/social-media/twitch.png
--------------------------------------------------------------------------------
/src/img/social-media/twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/src/img/social-media/twitter.png
--------------------------------------------------------------------------------
/src/img/social-media/youtube.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/src/img/social-media/youtube.png
--------------------------------------------------------------------------------
/src/img/social-media/facebook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/src/img/social-media/facebook.png
--------------------------------------------------------------------------------
/src/img/social-media/instagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/src/img/social-media/instagram.png
--------------------------------------------------------------------------------
/src/img/social-media/linkedin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcobrunodev/portfolio/HEAD/src/img/social-media/linkedin.png
--------------------------------------------------------------------------------
/src/libs/validation/required.js:
--------------------------------------------------------------------------------
1 | function required(value, msgError) {
2 | return !value && msgError;
3 | }
4 |
5 | export default required;
6 |
--------------------------------------------------------------------------------
/src/components/PhotoLive/index.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const PhotoLive = styled.img``;
4 |
5 | export default PhotoLive;
6 |
--------------------------------------------------------------------------------
/src/libs/validation/email.js:
--------------------------------------------------------------------------------
1 | function isEmail(value, msgError) {
2 | return !/\S+@\S+\.\S+/.test(value) && msgError;
3 | }
4 |
5 | export default isEmail;
6 |
--------------------------------------------------------------------------------
/src/libs/validation/maxLength.js:
--------------------------------------------------------------------------------
1 | function maxLength(length, value, msgError) {
2 | return value.length > length && msgError;
3 | }
4 |
5 | export default maxLength;
6 |
--------------------------------------------------------------------------------
/src/libs/validation/minLength.js:
--------------------------------------------------------------------------------
1 | function minLength(length, value, msgError) {
2 | return value.length < length && msgError;
3 | }
4 |
5 | export default minLength;
6 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.tabSize": 2,
3 | "editor.formatOnSave": true,
4 | "[javascript]": {
5 | "editor.defaultFormatter": "esbenp.prettier-vscode"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-one-expression-per-line */
2 | import React from 'react';
3 | import Routes from './Routes';
4 |
5 | const App = () => ;
6 |
7 | export default App;
8 |
--------------------------------------------------------------------------------
/src/services/axios.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | const { REACT_APP_API } = process.env;
4 |
5 | const api = axios.create({
6 | baseURL: REACT_APP_API,
7 | timeout: 1000,
8 | });
9 |
10 | export default api;
11 |
--------------------------------------------------------------------------------
/src/libs/validation/index.js:
--------------------------------------------------------------------------------
1 | import required from './required';
2 | import isEmail from './email';
3 | import minLength from './minLength';
4 | import maxLength from './maxLength';
5 |
6 | export { required, isEmail, minLength, maxLength };
7 |
--------------------------------------------------------------------------------
/src/components/PlusButton/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Plus, Action } from './styles';
3 |
4 | const PlusButton = () => (
5 |
6 |
7 |
8 | );
9 |
10 | export default PlusButton;
11 |
--------------------------------------------------------------------------------
/src/components/MsgError/index.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const MsgError = styled.span`
4 | font-size: var(--size-small);
5 | padding: var(--gap-smaller);
6 | color: var(--color-primary-dark);
7 | `;
8 |
9 | export default MsgError;
10 |
--------------------------------------------------------------------------------
/src/components/LogoDev/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Wrapper, Logo, Title } from './styles';
3 |
4 | const LogoDev = () => (
5 |
6 |
7 | MarcoBrunoDev
8 |
9 | );
10 |
11 | export default LogoDev;
12 |
--------------------------------------------------------------------------------
/src/components/PlusButton/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Link } from 'react-router-dom';
3 | import plus from '../../img/icons/plus-icon.png';
4 |
5 | const Plus = styled.img.attrs({ src: plus })``;
6 | const Action = styled(Link)``;
7 |
8 | export { Plus, Action };
9 |
--------------------------------------------------------------------------------
/src/pages/HomePage/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TitleNes from '../../components/TitleNes';
3 | import Template from '../Template';
4 |
5 | const HomePage = () => (
6 |
7 | Home
8 |
9 | );
10 |
11 | export default HomePage;
12 |
--------------------------------------------------------------------------------
/src/pages/PostsDev/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TitleNes from '../../components/TitleNes';
3 | import Template from '../Template';
4 |
5 | const PostsDev = () => (
6 |
7 | Posts
8 |
9 | );
10 |
11 | export default PostsDev;
12 |
--------------------------------------------------------------------------------
/src/components/ModalNes/index.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const ModalNes = styled.div`
4 | position: fixed;
5 | top: 0;
6 | bottom: 0;
7 | left: 0;
8 | right: 0;
9 | background-color: rgba(0, 0, 0, 0.8);
10 | z-index: 10;
11 | `;
12 |
13 | export default ModalNes;
14 |
--------------------------------------------------------------------------------
/src/pages/ContactsDev/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Template from '../Template';
3 | import TitleNes from '../../components/TitleNes';
4 |
5 | const ContactsDev = () => (
6 |
7 | Contatos
8 |
9 | );
10 |
11 | export default ContactsDev;
12 |
--------------------------------------------------------------------------------
/src/pages/ProjectsDev/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TitleNes from '../../components/TitleNes';
3 | import Template from '../Template';
4 |
5 | const ProjectsDev = () => (
6 |
7 | Projetos
8 |
9 | );
10 |
11 | export default ProjectsDev;
12 |
--------------------------------------------------------------------------------
/src/styles/settings/Gaps.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 |
3 | const Gaps = createGlobalStyle`
4 | :root{
5 | --gap-smaller: 5rem;
6 | --gap-small: 10rem;
7 | --gap-medium: 20rem;
8 | --gap-big: 40rem;
9 | --gap-bigger: 80rem;
10 | }
11 | `;
12 |
13 | export default Gaps;
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "viewportWidth": 1920,
3 | "viewportHeight": 1080,
4 | "baseUrl": "http://localhost:3000",
5 | "video": false,
6 | "defaultCommandTimeout": 8000,
7 | "pageLoadTimeout": 90000,
8 | "numTestsKeptInMemory": 5,
9 | "chromeWebSecurity": false,
10 | "env": {
11 | "RETRIES": 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/TextNes/index.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const TextNes = styled.p`
4 | font-size: var(--size-small);
5 | line-height: 1.8em;
6 | color: var(--color-gray-lighter);
7 |
8 | @media (min-width: 700px) {
9 | font-size: var(--size-medium);
10 | }
11 | `;
12 |
13 | export default TextNes;
14 |
--------------------------------------------------------------------------------
/src/components/SocialMedia/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Icon = styled.img`
4 | width: 100%;
5 | `;
6 |
7 | const Action = styled.a`
8 | display: inline-block;
9 | max-width: 60px;
10 | margin-left: var(--gap-small);
11 | margin-right: var(--gap-small);
12 | `;
13 |
14 | export { Icon, Action };
15 |
--------------------------------------------------------------------------------
/src/containers/CardDescription/index.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import ContainerNes from '../../components/ContainerNes';
3 |
4 | const CardDescription = styled(ContainerNes)`
5 | font-family: 'Fira Code', cursive;
6 | line-height: 1.4;
7 | font-size: var(--size-big);
8 | `;
9 |
10 | export default CardDescription;
11 |
--------------------------------------------------------------------------------
/src/containers/ContainerDev/index.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import _containers from '../../styles/tools/_containers';
3 |
4 | const ContainerDev = styled.section`
5 | display: flex;
6 | flex-direction: column;
7 | align-items: center;
8 | padding-bottom: var(--gap-bigger);
9 | ${_containers};
10 | `;
11 |
12 | export default ContainerDev;
13 |
--------------------------------------------------------------------------------
/src/services/lives.service.js:
--------------------------------------------------------------------------------
1 | import api from './axios';
2 |
3 | function create(live) {
4 | return api.post('/lives/', live);
5 | }
6 |
7 | function findLiveByUuid(uuid) {
8 | return api.get(`/lives/${uuid}`);
9 | }
10 |
11 | function findLivesFuture() {
12 | return api.get('/lives/future');
13 | }
14 |
15 | export default { findLivesFuture, create, findLiveByUuid };
16 |
--------------------------------------------------------------------------------
/src/pages/LiveNew/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Template from '../Template';
3 | import TitleNes from '../../components/TitleNes';
4 | import FormLive from '../../containers/FormLive';
5 |
6 | const LiveNew = () => (
7 |
8 | Cadastro de nova Live
9 |
10 |
11 |
12 | );
13 |
14 | export default LiveNew;
15 |
--------------------------------------------------------------------------------
/src/styles/settings/Size.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 |
3 | const Size = createGlobalStyle`
4 | :root {
5 | --size-smaller: 8rem;
6 | --size-small: 12rem;
7 | --size-medium: 16rem;
8 | --size-big: 22rem;
9 | --size-bigger: 34rem;
10 |
11 | --size-border: 4rem;
12 | --size-header: 50rem;
13 | }
14 | `;
15 |
16 | export default Size;
17 |
--------------------------------------------------------------------------------
/src/containers/HeaderDev/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import LogoDev from '../../components/LogoDev';
3 | import { Header, Logo } from './styles';
4 | import MenuDev from '../MenuDev';
5 |
6 | const HeaderDev = () => (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | );
15 |
16 | export default HeaderDev;
17 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/styles/tools/_containers.js:
--------------------------------------------------------------------------------
1 | const internal = `
2 | padding-left: var(--gap-small);
3 | padding-right: var(--gap-small);
4 | `;
5 |
6 | const external = `
7 | width: calc(100% - var(--gap-small));
8 | margin-left: auto;
9 | margin-right: auto;
10 | `
11 |
12 | const _containers = (type = 'internal') => {
13 | return type === 'internal' ? internal : external;
14 | }
15 |
16 |
17 | export default _containers;
--------------------------------------------------------------------------------
/src/pages/Template/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import HeaderDev from '../../containers/HeaderDev';
4 | import FooterDev from '../../containers/FooterDev';
5 |
6 | const Template = ({ children }) => (
7 | <>
8 |
9 | {children}
10 |
11 | >
12 | );
13 |
14 | Template.propTypes = {
15 | children: PropTypes.node.isRequired,
16 | };
17 |
18 | export default Template;
19 |
--------------------------------------------------------------------------------
/src/containers/WrapperCard/index.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Card } from '../CardDay/styles';
3 |
4 | const WrapperCard = styled.section`
5 | display: flex;
6 | justify-content: space-around;
7 | flex-wrap: wrap;
8 | width: 100%;
9 |
10 | & > ${Card} {
11 | margin-right: var(--gap-small);
12 | margin-left: var(--gap-small);
13 | margin-bottom: var(--gap-bigger);
14 | }
15 | `;
16 |
17 | export default WrapperCard;
18 |
--------------------------------------------------------------------------------
/src/components/FormNes/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Form } from './styles';
4 |
5 | const FormNes = ({ children, onSubmit }) => (
6 |
9 | );
10 |
11 | // oneOf([element, arrayOf(element))
12 |
13 | FormNes.propTypes = {
14 | children: PropTypes.node.isRequired,
15 | onSubmit: PropTypes.func.isRequired,
16 | };
17 |
18 | export default FormNes;
19 |
--------------------------------------------------------------------------------
/src/components/LogoDev/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import logo from '../../img/MarcoBrunoDev.png';
3 |
4 | const Wrapper = styled.figure`
5 | height: 100%;
6 | display: flex;
7 | align-items: center;
8 | `;
9 | const Logo = styled.img.attrs({ src: logo })`
10 | height: 100%;
11 | margin-right: var(--gap-small);
12 | `;
13 | const Title = styled.figcaption`
14 | color: var(--color-gray-lighter);
15 | `;
16 |
17 | export { Wrapper, Logo, Title };
18 |
--------------------------------------------------------------------------------
/src/containers/CardGuest/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Card } from '../CardInfo/styles';
3 |
4 | const Photo = styled.img``;
5 | const Name = styled.p``;
6 | const Guest = styled(Card)`
7 | position: relative;
8 |
9 | & > ${Name} {
10 | position: absolute;
11 | bottom: 0;
12 | padding: var(--gap-smaller) var(--gap-small);
13 | background-color: var(--color-primary-medium);
14 | }
15 | `;
16 |
17 | export { Guest, Photo, Name };
18 |
--------------------------------------------------------------------------------
/src/components/FormNes/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import ContainerNes from '../ContainerNes';
3 | import { Label } from '../FieldNes/styles';
4 |
5 | const Form = styled(ContainerNes)`
6 | padding: var(--gap-big);
7 | `;
8 |
9 | const Half = styled.div`
10 | @media (min-width: 700px) {
11 | display: flex;
12 | justify-content: space-between;
13 |
14 | & > ${Label} {
15 | width: 49%;
16 | }
17 | }
18 | `;
19 |
20 | export { Form, Half };
21 |
--------------------------------------------------------------------------------
/src/components/TextareaNes/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Textarea = styled.textarea`
4 | border: var(--size-border) solid var(--color-gray-lighter);
5 | color: var(--color-gray-lighter);
6 | height: 100px;
7 | font: inherit;
8 | background-color: var(--color-gray-medium);
9 | padding: var(--gap-small);
10 | resize: vertical;
11 |
12 | &:focus {
13 | border-color: var(--color-secondary-medium);
14 | }
15 | `;
16 |
17 | export default Textarea;
18 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import Size from './styles/settings/Size';
5 | import Colors from './styles/settings/Colors';
6 | import Gaps from './styles/settings/Gaps';
7 | import Reset from './styles/generic/Reset';
8 | import Base from './styles/elements/Base';
9 |
10 | import App from './App';
11 |
12 | ReactDOM.render(
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | ,
22 | document.getElementById('root')
23 | );
24 |
--------------------------------------------------------------------------------
/src/containers/FooterDev/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import ContainerNes from '../../components/ContainerNes';
3 | import { Action } from '../../components/SocialMedia/styles';
4 |
5 | const Navigation = styled.nav`
6 | display: flex;
7 | justify-content: center;
8 |
9 | & > ${Action} {
10 | margin: 0 var(--gap-small) var(--gap-medium);
11 | width: 60px;
12 | }
13 | `;
14 |
15 | const Footer = styled(ContainerNes)`
16 | margin-top: var(--gap-bigger);
17 | padding: var(--gap-medium) 0;
18 | text-align: center;
19 | `;
20 |
21 | export { Footer, Navigation };
22 |
--------------------------------------------------------------------------------
/src/libs/date/index.js:
--------------------------------------------------------------------------------
1 | import { utcToZonedTime, format } from 'date-fns-tz';
2 |
3 | const { timeZone } = new Intl.DateTimeFormat().resolvedOptions();
4 |
5 | const formatDate = (date) => {
6 | const dateStartLocal = utcToZonedTime(new Date(date), timeZone);
7 | const formtDateStartLocal = format(dateStartLocal, 'dd/MM/yyyy');
8 |
9 | return formtDateStartLocal;
10 | };
11 |
12 | const getHour = (date) => {
13 | const dateStartLocal = utcToZonedTime(new Date(date), timeZone);
14 | const hour = format(dateStartLocal, 'hh:mm');
15 |
16 | return hour;
17 | };
18 |
19 | export { formatDate, getHour };
20 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "MarcoBrunoDev",
3 | "name": "Site do MarcoBrunoDev",
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 |
--------------------------------------------------------------------------------
/src/containers/CardGuest/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import TagNes from '../../components/TagNes';
4 | import { Guest, Photo, Name } from './styles';
5 |
6 | const CardGuest = (props) => {
7 | const { name, photo } = props;
8 |
9 | return (
10 |
11 | Convidado
12 |
13 |
14 | {name}
15 |
16 | );
17 | };
18 |
19 | CardGuest.propTypes = {
20 | name: PropTypes.string.isRequired,
21 | photo: PropTypes.string.isRequired,
22 | };
23 |
24 | export default CardGuest;
25 |
--------------------------------------------------------------------------------
/src/styles/elements/Base.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 | import { mickey, mickeyHover } from '../settings/Cursor';
3 |
4 | const Base = createGlobalStyle`
5 | html {
6 | font-size: 1px;
7 | cursor: url(${mickey}),auto;
8 | }
9 |
10 | a:hover, button:hover {
11 | cursor: url(${mickeyHover}) 14 0,pointer;
12 | }
13 |
14 | body {
15 | padding-top: calc(var(--size-header) + var(--gap-medium));
16 | font-size: var(--size-medium);
17 | font-family: 'Press Start 2P', cursive;
18 | min-width: 320px;
19 | background-color: var(--color-gray-light);
20 | }
21 | `;
22 |
23 | export default Base;
24 |
--------------------------------------------------------------------------------
/src/styles/settings/Colors.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 |
3 | const Colors = createGlobalStyle`
4 | :root {
5 | --color-primary-dark: #d04154;
6 | --color-primary-medium: #f25a6f;
7 |
8 | --color-secondary-dark: #63497d;
9 | --color-secondary-medium: #7c5e99;
10 |
11 | --color-tertiary-dark: #4e9859;
12 | --color-tertiary-medium: #62C370;
13 |
14 | --color-gray-dark: #212529;
15 | --color-gray-medium: #2f3439;
16 | --color-gray-light: #3f454c;
17 | --color-gray-lighter: #fff;
18 |
19 | --color-title: #F5D547;
20 | --color-link: #20A4F3;
21 | }
22 | `;
23 |
24 | export default Colors;
25 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true
5 | },
6 | "extends": ["plugin:react/recommended", "plugin:cypress/recommended", "airbnb", "prettier"],
7 | "globals": {
8 | "Atomics": "readonly",
9 | "SharedArrayBuffer": "readonly"
10 | },
11 | "parserOptions": {
12 | "ecmaFeatures": {
13 | "jsx": true
14 | },
15 | "ecmaVersion": 2018,
16 | "sourceType": "module"
17 | },
18 | "plugins": ["react"],
19 | "rules": {
20 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
21 | "react/jsx-one-expression-per-line": [0],
22 | "no-use-before-define": ["error", { "functions": false }]
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/TitleNes/index.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 |
3 | const TitleNes = styled.h1`
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | color: var(--color-title);
8 | font-size: var(--size-big);
9 | line-height: 1.4;
10 | text-align: center;
11 | padding: var(--gap-big) var(--gap-small) 0;
12 | margin-bottom: var(--gap-bigger);
13 |
14 | @media (min-width: 700px) {
15 | font-size: var(--size-bigger);
16 | padding-top: var(--gap-bigger);
17 | }
18 |
19 | ${({ shimmerEffect }) =>
20 | shimmerEffect &&
21 | css`
22 | filter: blur(2px);
23 | `}
24 | `;
25 |
26 | export default TitleNes;
27 |
--------------------------------------------------------------------------------
/src/containers/CardInfo/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Card, Item } from './styles';
4 | import TagNes from '../../components/TagNes';
5 |
6 | const CardInfo = ({ label, items = [], shimmerEffect }) => (
7 |
8 | {label}
9 | {items.map((item) => (
10 | - {item}
11 | ))}
12 |
13 | );
14 |
15 | CardInfo.defaultProps = {
16 | items: [],
17 | shimmerEffect: false,
18 | };
19 |
20 | CardInfo.propTypes = {
21 | label: PropTypes.string.isRequired,
22 | items: PropTypes.arrayOf(String),
23 | shimmerEffect: PropTypes.bool,
24 | };
25 |
26 | export default CardInfo;
27 |
--------------------------------------------------------------------------------
/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/src/components/TagNes/index.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const TagNes = styled.span`
4 | display: inline-flex;
5 | align-items: center;
6 | position: relative;
7 | background-color: var(--color-primary-medium);
8 | padding: var(--gap-small) var(--gap-medium);
9 |
10 | &::before,
11 | &::after {
12 | content: '';
13 | display: inline-block;
14 | position: absolute;
15 | z-index: 2;
16 | background-color: var(--color-primary-medium);
17 | height: 68%;
18 | width: calc(var(--size-border) * 1.4);
19 | }
20 | &::before {
21 | left: 0;
22 | transform: translateX(-100%);
23 | }
24 | &::after {
25 | right: 0;
26 | transform: translateX(100%);
27 | }
28 | `;
29 |
30 | export default TagNes;
31 |
--------------------------------------------------------------------------------
/src/containers/HeaderDev/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Link } from 'react-router-dom';
3 | import ContainerNes from '../../components/ContainerNes';
4 | import { Title } from '../../components/LogoDev/styles';
5 |
6 | const Logo = styled(Link)`
7 | height: 100%;
8 | text-decoration: none;
9 | `;
10 |
11 | const Header = styled(ContainerNes)`
12 | position: fixed;
13 | top: 0;
14 | left: 0;
15 | right: 0;
16 | z-index: 10;
17 | display: flex;
18 | justify-content: space-between;
19 | align-items: center;
20 | height: var(--size-header);
21 |
22 | & ${Title} {
23 | display: none;
24 |
25 | @media (min-width: 450px) {
26 | display: block;
27 | }
28 | }
29 | `;
30 |
31 | export { Header, Logo };
32 |
--------------------------------------------------------------------------------
/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | ///
2 | // ***********************************************************
3 | // This example plugins/index.js can be used to load plugins
4 | //
5 | // You can change the location of this file or turn off loading
6 | // the plugins file with the 'pluginsFile' configuration option.
7 | //
8 | // You can read more here:
9 | // https://on.cypress.io/plugins-guide
10 | // ***********************************************************
11 |
12 | // This function is called when a project is opened or re-opened (e.g. due to
13 | // the project's config changing)
14 |
15 | /**
16 | * @type {Cypress.PluginConfig}
17 | */
18 | module.exports = (on, config) => {
19 | // `on` is used to hook into various events Cypress emits
20 | // `config` is the resolved Cypress config
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/ActionNes/index.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const ActionNes = styled.a`
4 | display: inline-flex;
5 | align-items: center;
6 | justify-content: center;
7 | position: relative;
8 | color: var(--color-link);
9 | text-decoration: none;
10 | transition: color 100ms linear;
11 |
12 | &::after {
13 | content: '';
14 | position: absolute;
15 | bottom: 3px;
16 | background-color: var(--color-link);
17 | width: 100%;
18 | height: 80%;
19 | transform-origin: bottom center;
20 | transform: scaleY(0.08);
21 | z-index: -1;
22 | transition: transform 50ms linear;
23 | }
24 |
25 | &:hover {
26 | color: var(--color-gray-lighter);
27 | }
28 |
29 | &:hover::after {
30 | transform: scaleY(1);
31 | }
32 | `;
33 |
34 | export default ActionNes;
35 |
--------------------------------------------------------------------------------
/src/containers/FooterDev/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Footer, Navigation } from './styles';
3 | import SocialMedia from '../../components/SocialMedia';
4 |
5 | const medias = [
6 | {
7 | what: 'twitch',
8 | href: 'https://twitch.com/marcobrunodev',
9 | },
10 | {
11 | what: 'discord',
12 | href: 'http://bit.ly/discord-collabcode',
13 | },
14 | {
15 | what: 'twitter',
16 | href: 'https://twitter.com/marcobrunodev',
17 | },
18 | {
19 | what: 'github',
20 | href: 'https://github.com/marcobrunodev',
21 | },
22 | ];
23 |
24 | const FooterDev = () => (
25 |
34 | );
35 |
36 | export default FooterDev;
37 |
--------------------------------------------------------------------------------
/src/components/FieldNes/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import MsgError from '../MsgError';
3 |
4 | const Label = styled.label`
5 | position: relative;
6 | display: flex;
7 | flex-direction: column;
8 | margin-bottom: var(--gap-big);
9 |
10 | & > ${MsgError} {
11 | position: absolute;
12 | bottom: 0;
13 | transform: translateY(100%);
14 | }
15 | `;
16 |
17 | const Content = styled.span`
18 | margin-bottom: var(--gap-smaller);
19 | `;
20 |
21 | const Input = styled.input`
22 | border: var(--size-border) solid var(--color-gray-lighter);
23 | color: var(--color-gray-lighter);
24 | height: 30px;
25 | font: inherit;
26 | background-color: var(--color-gray-medium);
27 | padding: var(--gap-smaller) var(--gap-small);
28 |
29 | &:focus {
30 | border-color: var(--color-secondary-medium);
31 | }
32 | `;
33 |
34 | export { Label, Content, Input };
35 |
--------------------------------------------------------------------------------
/src/components/ContainerNes/index.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import _containers from '../../styles/tools/_containers';
3 |
4 | const ContainerNes = styled.section`
5 | ${_containers('internal')};
6 | position: relative;
7 | background-color: var(--color-gray-dark);
8 | color: var(--color-gray-lighter);
9 | font-size: var(--size-medium);
10 | padding: calc(var(--size-border) + var(--gap-smaller) * 2);
11 |
12 | &::after {
13 | content: '';
14 | position: absolute;
15 | top: 50%;
16 | left: 50%;
17 | box-sizing: border-box;
18 | transform: translate(-50%, -50%);
19 | width: calc(100% - var(--gap-small));
20 | height: calc(100% - var(--gap-small));
21 | border: var(--size-border) solid var(--color-gray-lighter);
22 | }
23 |
24 | & > * {
25 | position: relative;
26 | z-index: 1;
27 | }
28 | `;
29 |
30 | export default ContainerNes;
31 |
--------------------------------------------------------------------------------
/src/styles/settings/Cursor.js:
--------------------------------------------------------------------------------
1 | const mickey = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABFklEQVRYR9WXURLDIAhE6/0PbSdOtUpcd1Gnpv1KGpTHBpCE1/cXq+vrMph7dGvXZTtpfW10DCA5jrH1H0Jhs5E0hnZdCR+vb5S8Nn8mQCeS9BdSalYJqMBjAGzq59xAESN7VFVUgV8AZB/dZBR7QTFDCqGquvUBVVoEtgIwpQRzmANSFHgWQKExHdIrPeuMvQNDarXe6nC/AutgV3JW+6bgqQLeV8FekRtgV+ToDKEKnACYKsfZjjkam7a0ZpYTytwmgainpC3HvwBocgKOxqRjehoR9DFKNFYtOwCGYCszobeCbl26N6yyQ6g8X/Wex/rBPsNEV6qAMaJPMynIHQCoSqS9JSMmwef51LflTgCRszU7DvAGiV6mHWfsaVUAAAAASUVORK5CYII=`;
2 | const mickeyHover = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAzElEQVRYR+2X0Q6AIAhF5f8/2jYXZkwEjNSVvVUjDpcrGgT7FUkI2D9xRfQETwNIiWO85wfINfQUEyxBG2ArsLwC0jioGt5zFcwF4OYDPi/mBYKm4t0U8ATgRm3ThFoAqkhNgWkA0jJLvaOVSs7j3qMnSgXWBMiWPXe94QqMBMBc1VZIvaTu5u5pQewq0EqNZvIEMCmxAawK0DNkay9QmfFNAJUXfgGgUkLaE7j/h8fnASkxHTz0DGIBMCnBeeM7AArpUd3mz2x3C7wADglA8BcWMZhZAAAAAElFTkSuQmCC`;
3 |
4 | export { mickey, mickeyHover };
5 |
--------------------------------------------------------------------------------
/src/containers/ShareSocial/styles.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 | import ModalNes from '../../components/ModalNes';
3 | import TitleNes from '../../components/TitleNes';
4 | import ContainerNes from '../../components/ContainerNes';
5 | import { Icon } from '../../components/SocialMedia/styles';
6 |
7 | const Share = styled(ModalNes)`
8 | display: none;
9 | justify-content: center;
10 | align-items: center;
11 | text-align: center;
12 |
13 | & > ${ContainerNes} {
14 | box-sizing: border-box;
15 | width: calc(100% - var(--gap-small) * 2);
16 | min-height: 360px;
17 | max-width: 600px;
18 | }
19 |
20 | & ${TitleNes} {
21 | font-size: var(--size-big);
22 | }
23 |
24 | & ${Icon} {
25 | width: 60px;
26 | margin-left: var(--gap-small);
27 | margin-right: var(--gap-small);
28 | }
29 |
30 | ${({ active }) =>
31 | active &&
32 | css`
33 | display: flex;
34 | `}
35 | `;
36 |
37 | export default Share;
38 |
--------------------------------------------------------------------------------
/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 | import 'cypress-file-upload';
27 |
--------------------------------------------------------------------------------
/src/components/FieldNes/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Label, Content, Input } from './styles';
4 | import MsgError from '../MsgError';
5 |
6 | const FieldNes = ({ content, type, name, placeholder, onChange, onBlur, msgError }) => (
7 |
12 | );
13 |
14 | FieldNes.defaultProps = {
15 | type: 'text',
16 | placeholder: '',
17 | msgError: '',
18 | };
19 |
20 | FieldNes.propTypes = {
21 | content: PropTypes.string.isRequired,
22 | type: PropTypes.string,
23 | name: PropTypes.string.isRequired,
24 | placeholder: PropTypes.string,
25 | onChange: PropTypes.func.isRequired,
26 | onBlur: PropTypes.func.isRequired,
27 | msgError: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
28 | };
29 |
30 | export default FieldNes;
31 |
--------------------------------------------------------------------------------
/src/Routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
3 | import HomePage from './pages/HomePage';
4 | import LivesSchedule from './pages/LivesSchedule';
5 | import LiveDetail from './pages/LiveDetails';
6 | import LiveNew from './pages/LiveNew';
7 | import ProjectsDev from './pages/ProjectsDev';
8 | import ContactsDev from './pages/ContactsDev';
9 | import PostsDev from './pages/PostsDev';
10 |
11 | const Routes = () => (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | );
24 |
25 | export default Routes;
26 |
--------------------------------------------------------------------------------
/src/components/TextareaNes/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Label, Content } from '../FieldNes/styles';
4 | import Textarea from './styles';
5 | import MsgError from '../MsgError';
6 |
7 | const TextareaNes = ({ name, content, placeholder, msgError, value, onChange, onBlur }) => (
8 |
19 | );
20 |
21 | TextareaNes.defaultProps = {
22 | msgError: '',
23 | placeholder: '',
24 | };
25 |
26 | TextareaNes.propTypes = {
27 | name: PropTypes.string.isRequired,
28 | placeholder: PropTypes.string,
29 | content: PropTypes.string.isRequired,
30 | msgError: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
31 | onChange: PropTypes.func.isRequired,
32 | value: PropTypes.string.isRequired,
33 | };
34 |
35 | export default TextareaNes;
36 |
--------------------------------------------------------------------------------
/src/components/ButtonShare/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import ButtonNes from '../ButtonNes';
4 |
5 | const isSupported = () => (navigator && navigator.share && true) || false;
6 |
7 | const shareWithWebApi = (event) => {
8 | event.preventDefault();
9 |
10 | navigator
11 | .share({
12 | title: 'LiveCoding na Twitch',
13 | text: 'LiveCoding sobre JavaScript, HTML e CSS',
14 | url: 'https://twitch.tv/marcobrunodev',
15 | })
16 | .then(function () {
17 | console.log('Funcionou!!');
18 | })
19 | .catch(function (err) {
20 | console.error(err);
21 | });
22 | };
23 |
24 | const ButtonShare = ({ changeActiveShareModal }) => {
25 | return isSupported() ? (
26 |
27 | Compartilhar
28 |
29 | ) : (
30 |
31 | Compartilhar
32 |
33 | );
34 | };
35 |
36 | ButtonShare.propTypes = {
37 | changeActiveShareModal: PropTypes.func.isRequired,
38 | };
39 |
40 | export default ButtonShare;
41 |
--------------------------------------------------------------------------------
/src/containers/CardDay/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Card, Title, Description, Menu } from './styles';
4 | import TagNes from '../../components/TagNes';
5 | import ButtonNes from '../../components/ButtonNes';
6 |
7 | const CardDay = ({ uuid, startDate, shortTitle, shortDescription, shimmerEffect }) => (
8 |
9 | {startDate}
10 | {shortTitle}
11 | {shortDescription}
12 |
13 |
19 |
20 | );
21 |
22 | CardDay.defaultProps = {
23 | shimmerEffect: false,
24 | };
25 |
26 | CardDay.propTypes = {
27 | uuid: PropTypes.string.isRequired,
28 | startDate: PropTypes.string.isRequired,
29 | shortDescription: PropTypes.string.isRequired,
30 | shortTitle: PropTypes.string.isRequired,
31 | shimmerEffect: PropTypes.bool,
32 | };
33 |
34 | export default CardDay;
35 |
--------------------------------------------------------------------------------
/src/libs/social/index.js:
--------------------------------------------------------------------------------
1 | const $canonical = document.querySelector('link[rel=canonical]');
2 | const url = $canonical ? $canonical.getAttribute('href') : window.location.href;
3 |
4 | function facebook() {
5 | const width = 630;
6 | const height = 630;
7 |
8 | const left = window.screen.width / 2 - width / 2;
9 | const top = window.screen.height / 2 - height / 2;
10 |
11 | window.open(
12 | `http://www.facebook.com/sharer.php?u=${url}`,
13 | 'Compartilhar no facebook',
14 | `toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=yes, resizable=no, copyhistory=no, width=${width}, height=${height}, top=${top}, left=${left}`
15 | );
16 | }
17 |
18 | function share(event) {
19 | if (navigator && navigator.share) {
20 | navigator
21 | .share({
22 | title: '',
23 | text: 'Site com uma lista de conteúdos gratuitos e online sobre programação',
24 | url,
25 | })
26 | .then(function () {
27 | console.log('Funcionou!!');
28 | })
29 | .catch(function (err) {
30 | console.error(err);
31 | });
32 | } else {
33 | facebook();
34 | }
35 | event.preventDefault();
36 | }
37 |
38 | export default share;
39 |
--------------------------------------------------------------------------------
/src/components/SocialMedia/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import instagram from '../../img/social-media/instagram.png';
4 | import linkedin from '../../img/social-media/linkedin.png';
5 | import twitch from '../../img/social-media/twitch.png';
6 | import twitter from '../../img/social-media/twitter.png';
7 | import youtube from '../../img/social-media/youtube.png';
8 | import github from '../../img/social-media/github.png';
9 | import discord from '../../img/social-media/discord.png';
10 | import facebook from '../../img/social-media/facebook.png';
11 | import { Icon, Action } from './styles';
12 |
13 | const isAction = (src, href) => {
14 | return href ? (
15 |
16 |
17 |
18 | ) : (
19 |
20 | );
21 | };
22 |
23 | const SocialMedia = ({ what, href }) => {
24 | const socialMedias = {
25 | instagram,
26 | linkedin,
27 | twitch,
28 | twitter,
29 | youtube,
30 | github,
31 | discord,
32 | facebook,
33 | };
34 |
35 | return isAction(socialMedias[what], href);
36 | };
37 |
38 | SocialMedia.defaultProps = {
39 | href: '',
40 | };
41 |
42 | SocialMedia.propTypes = {
43 | what: PropTypes.string.isRequired,
44 | href: PropTypes.string,
45 | };
46 |
47 | export default SocialMedia;
48 |
--------------------------------------------------------------------------------
/cypress/data-builders/lives.builder.js:
--------------------------------------------------------------------------------
1 | import faker from 'faker';
2 |
3 | const twoNumber = (number) => `0${number}`.slice(-2);
4 |
5 | const generatorDate = (dayGenerator = 1) => {
6 | const dateString = faker.date.recent(dayGenerator);
7 | const startDate = new Date(dateString);
8 | const day = `0${startDate.getDate()}`.slice(-2);
9 | const month = twoNumber(startDate.getMonth());
10 | const year = startDate.getFullYear();
11 | const hours = twoNumber(startDate.getHours());
12 | const minutes = twoNumber(startDate.getMinutes());
13 |
14 | return {
15 | date: `${year}-${month}-${day}`,
16 | time: `${hours}:${minutes}`,
17 | };
18 | };
19 |
20 | const generatorGoals = () => {
21 | const goals = faker.lorem.words(faker.random.number(6));
22 |
23 | return `${goals.replace(/ /g, '; ')};`;
24 | };
25 |
26 | const randomLiveInfo = (options = {}) => {
27 | const blank = {};
28 |
29 | return Object.assign(
30 | blank,
31 | {
32 | shortTitle: faker.lorem.sentence(1),
33 | title: faker.lorem.sentence(3),
34 | photo: faker.image.city(200, 200, true),
35 | start: generatorDate(),
36 | finish: generatorDate(2),
37 | goals: generatorGoals(),
38 | shortDescription: faker.lorem.sentence(10),
39 | description: faker.lorem.sentence(20, 40),
40 | },
41 | { ...options }
42 | );
43 | };
44 |
45 | export { generatorDate, randomLiveInfo };
46 |
--------------------------------------------------------------------------------
/src/containers/FormLive/validation.js:
--------------------------------------------------------------------------------
1 | import { required, minLength, maxLength } from '../../libs/validation';
2 |
3 | function validation(values) {
4 | const {
5 | shortTitle,
6 | title,
7 | startDate,
8 | startTime,
9 | finishDate,
10 | finishTime,
11 | goals,
12 | shortDescription,
13 | description,
14 | } = values;
15 | const errors = {};
16 |
17 | errors.shortTitle =
18 | required(shortTitle, 'Título curto é obrigatório') ||
19 | minLength(2, shortTitle, 'Tem que ter 2 ou mais caracteres') ||
20 | maxLength(22, shortTitle, 'No máximo 22 caracteres');
21 |
22 | errors.title =
23 | required(title, 'Título é obrigatório') ||
24 | minLength(2, title, 'Tem que ter 2 ou mais caracteres') ||
25 | maxLength(35, title, 'No máximo 35 caracteres');
26 |
27 | errors.startDate = required(startDate, 'Data de início é obrigatória');
28 | errors.startTime = required(startTime, 'Hora de início é obrigatória');
29 |
30 | errors.finishDate = required(finishDate, 'Data de fim é obrigatória');
31 | errors.finishTime = required(finishTime, 'Hora de fim é obrigatória');
32 |
33 | errors.goals = required(goals, 'Objetivos são obrigatórios');
34 | errors.shortDescription = required(shortDescription, 'Descrição curta é obrigatória');
35 | errors.description = required(description, 'Descrição é obrigatória');
36 |
37 | return errors;
38 | }
39 |
40 | export default validation;
41 |
--------------------------------------------------------------------------------
/src/styles/generic/Reset.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 |
3 | const Reset = createGlobalStyle`
4 | /* http://meyerweb.com/eric/tools/css/reset/
5 | v2.0 | 20110126
6 | License: none (public domain)
7 | */
8 |
9 | html, body, div, span, applet, object, iframe,
10 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
11 | a, abbr, acronym, address, big, cite, code,
12 | del, dfn, em, img, ins, kbd, q, s, samp,
13 | small, strike, strong, sub, sup, tt, var,
14 | b, u, i, center,
15 | dl, dt, dd, ol, ul, li,
16 | fieldset, form, label, legend,
17 | table, caption, tbody, tfoot, thead, tr, th, td,
18 | article, aside, canvas, details, embed,
19 | figure, figcaption, footer, header, hgroup,
20 | menu, nav, output, ruby, section, summary,
21 | time, mark, audio, video {
22 | margin: 0;
23 | padding: 0;
24 | border: 0;
25 | font-size: 100%;
26 | font: inherit;
27 | vertical-align: baseline;
28 | }
29 | /* HTML5 display-role reset for older browsers */
30 | article, aside, details, figcaption, figure,
31 | footer, header, hgroup, menu, nav, section {
32 | display: block;
33 | }
34 | body {
35 | line-height: 1;
36 | }
37 | ol, ul {
38 | list-style: none;
39 | }
40 | blockquote, q {
41 | quotes: none;
42 | }
43 | blockquote:before, blockquote:after,
44 | q:before, q:after {
45 | content: '';
46 | content: none;
47 | }
48 | table {
49 | border-collapse: collapse;
50 | border-spacing: 0;
51 | }
52 | `;
53 |
54 | export default Reset;
--------------------------------------------------------------------------------
/src/containers/MenuDev/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Icon, Navigation, Close, Action, Wrapper, Buttons, Medias } from './styles';
3 | import SocialMedia from '../../components/SocialMedia';
4 |
5 | const medias = [
6 | {
7 | what: 'twitch',
8 | href: 'https://twitch.com/marcobrunodev',
9 | },
10 | {
11 | what: 'discord',
12 | href: 'http://bit.ly/discord-collabcode',
13 | },
14 | {
15 | what: 'twitter',
16 | href: 'https://twitter.com/marcobrunodev',
17 | },
18 | {
19 | what: 'github',
20 | href: 'https://github.com/marcobrunodev',
21 | },
22 | ];
23 |
24 | const MenuDev = () => {
25 | const [menuActive, setMenuActive] = useState(false);
26 |
27 | function changeMenu() {
28 | setMenuActive(!menuActive);
29 | }
30 |
31 | return (
32 |
33 |
34 |
35 |
36 |
37 | Agenda
38 | Posts
39 | Projetos
40 | Sobre
41 |
42 |
43 |
44 | {medias.map(({ href, what }) => (
45 |
46 | ))}
47 |
48 |
49 |
50 | );
51 | };
52 |
53 | export default MenuDev;
54 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "portfolio",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.10.0",
7 | "@testing-library/react": "^10.2.1",
8 | "@testing-library/user-event": "^11.4.2",
9 | "axios": "^0.19.2",
10 | "date-fns": "^2.14.0",
11 | "date-fns-tz": "^1.0.10",
12 | "eslint-plugin-cypress": "^2.10.3",
13 | "faker": "^4.1.0",
14 | "react": "^16.13.1",
15 | "react-dom": "^16.13.1",
16 | "react-router-dom": "^5.1.2",
17 | "react-scripts": "3.4.1",
18 | "react-share": "^4.2.0",
19 | "styled-components": "^5.1.0"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "cypress run",
25 | "eject": "react-scripts eject",
26 | "cy:open": "cypress open"
27 | },
28 | "eslintConfig": {
29 | "extends": "react-app"
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.2%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | },
43 | "devDependencies": {
44 | "cypress": "^4.5.0",
45 | "cypress-file-upload": "4.0.7",
46 | "eslint-config-airbnb": "^18.1.0",
47 | "eslint-config-prettier": "^6.11.0",
48 | "eslint-plugin-jsx-a11y": "^6.2.3",
49 | "eslint-plugin-prettier": "^3.1.3",
50 | "eslint-plugin-react": "^7.19.0",
51 | "eslint-plugin-react-hooks": "^4.0.4"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
24 | MarcoBrunoDev
25 |
26 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/containers/InfosLive/styles.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 | import PhotoLive from '../../components/PhotoLive';
3 | import { Card } from '../CardInfo/styles';
4 | import CardDescription from '../CardDescription';
5 | import ButtonNes from '../../components/ButtonNes';
6 | import { Guest } from '../CardGuest/styles';
7 |
8 | const Wrapper = styled.article`
9 | padding-left: var(--gap-small);
10 | padding-right: var(--gap-small);
11 |
12 | ${PhotoLive} {
13 | display: none;
14 | }
15 | `;
16 |
17 | const Navigation = styled.nav`
18 | display: flex;
19 | flex-direction: column;
20 | align-items: center;
21 | width: 100%;
22 |
23 | & > ${CardDescription} {
24 | max-width: 1200px;
25 | margin-bottom: var(--gap-medium);
26 | }
27 |
28 | & > ${ButtonNes} {
29 | max-width: 320px;
30 | margin-bottom: var(--gap-small);
31 | }
32 | `;
33 |
34 | const Infos = styled.div`
35 | display: flex;
36 | flex-direction: column;
37 | align-items: center;
38 |
39 | & > ${Card} {
40 | margin-bottom: var(--gap-medium);
41 | text-align: center;
42 | line-height: 1.4;
43 | }
44 |
45 | & > ${Guest} {
46 | order: -1;
47 | }
48 |
49 | @media (min-width: 660px) {
50 | flex-direction: row;
51 | flex-wrap: wrap;
52 | align-items: flex-start;
53 | justify-content: center;
54 |
55 | & > ${Card}:not(:last-of-type) {
56 | margin-right: var(--gap-medium);
57 | }
58 |
59 | & > ${Guest} {
60 | margin-right: var(--gap-medium);
61 | }
62 | }
63 |
64 | @media (min-width: 1000px) {
65 | & > ${Guest} {
66 | order: 0;
67 | }
68 | }
69 |
70 | ${({ shimmerEffect }) =>
71 | shimmerEffect &&
72 | css`
73 | filter: blur(2px);
74 | `}
75 | `;
76 |
77 | export { Wrapper, Infos, Navigation };
78 |
--------------------------------------------------------------------------------
/src/containers/CardInfo/styles.js:
--------------------------------------------------------------------------------
1 | import styled, { css, keyframes } from 'styled-components';
2 | import ContainerNes from '../../components/ContainerNes';
3 | import TagNes from '../../components/TagNes';
4 |
5 | const turnTurn = keyframes`
6 | 0% {
7 | transform: rotate(0deg) scale(1.2) translate(-50%, -50%);
8 | opacity: 1;
9 | }
10 | 20% {
11 | transform: rotate(72deg) translate(-50%, -50%);
12 | opacity: 0.8;
13 | }
14 | 50% {
15 | transform: rotate(144deg) scale(1.2) translate(-50%, -50%);
16 | opacity: 1;
17 | }
18 | 80% {
19 | transform: rotate(216deg) translate(-50%, -50%);
20 | opacity: 0.8;
21 | }
22 | 100% {
23 | transform: rotate(360deg) scale(1.2) translate(-50%, -50%);
24 | opacity: 1;
25 | }
26 | `;
27 | const Card = styled(ContainerNes)`
28 | position: relative;
29 | display: flex;
30 | flex-direction: column;
31 | align-items: center;
32 | justify-content: center;
33 | box-sizing: border-box;
34 | width: 100%;
35 | max-width: 320px;
36 | height: 320px;
37 |
38 | & > ${TagNes} {
39 | position: absolute;
40 | top: 0;
41 | transform: translateY(-30%);
42 | order: 0;
43 | z-index: 5;
44 | }
45 |
46 | ${({ shimmerEffect }) =>
47 | shimmerEffect &&
48 | css`
49 | &::before {
50 | content: '';
51 | position: absolute;
52 | width: 25px;
53 | height: 25px;
54 | background-color: var(--color-secondary-medium);
55 | top: 50%;
56 | left: 50%;
57 | transform: translate(-50%, -50%);
58 | transform-origin: left top;
59 | animation: 2.5s ${turnTurn} linear infinite;
60 | box-shadow: 0px 0px 2px 2px var(--color-gray-light);
61 | }
62 | `}
63 | `;
64 |
65 | const Item = styled.dd`
66 | &:not(:last-child) {
67 | margin-bottom: var(--gap-medium);
68 | }
69 | `;
70 |
71 | export { Card, Item };
72 |
--------------------------------------------------------------------------------
/src/containers/InfosLive/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import CardDescription from '../CardDescription';
4 | import CardInfo from '../CardInfo';
5 | import { Wrapper, Infos, Navigation } from './styles';
6 | import CardGuest from '../CardGuest';
7 | import ButtonNes from '../../components/ButtonNes';
8 | import ButtonShare from '../../components/ButtonShare';
9 |
10 | const InfosLive = (props) => {
11 | const { goals, description, day, hour, guest, shimmerEffect, changeActiveShareModal } = props;
12 |
13 | return (
14 |
15 |
16 |
17 | {guest && }
18 |
19 |
20 |
21 |
22 | {description}
23 |
24 |
25 |
26 |
27 | Assistir!
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | InfosLive.propTypes = {
36 | shimmerEffect: PropTypes.bool,
37 | goals: PropTypes.arrayOf(PropTypes.string).isRequired,
38 | description: PropTypes.string.isRequired,
39 | day: PropTypes.string.isRequired,
40 | hour: PropTypes.string.isRequired,
41 | guest: PropTypes.shape({
42 | name: PropTypes.string,
43 | photo: PropTypes.string,
44 | }),
45 | changeActiveShareModal: PropTypes.bool.isRequired,
46 | };
47 |
48 | InfosLive.defaultProps = {
49 | guest: false,
50 | shimmerEffect: true,
51 | };
52 |
53 | export default InfosLive;
54 |
--------------------------------------------------------------------------------
/src/libs/validation/useValidation.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 |
3 | function useValidation(validation = false, callback, ...names) {
4 | const [values, setValues] = useState(Object.assign(...names.map((name) => ({ [name]: '' }))));
5 | const [errors, setErrors] = useState({});
6 | const [isSubmitting, setIsSubmitting] = useState(false);
7 |
8 | function handleChange({ target }) {
9 | const { name, value, type, checked } = target;
10 |
11 | setValues((oldValue) => {
12 | const newValue = type === 'checkbox' ? { [name]: checked } : { [name]: value };
13 |
14 | return { ...oldValue, ...newValue };
15 | });
16 | }
17 |
18 | function handleSubmit(event) {
19 | event.preventDefault();
20 |
21 | validationForm();
22 | setIsSubmitting(true);
23 | }
24 |
25 | function validationForm() {
26 | setErrors(validation instanceof Object ? validation(values) : {});
27 | }
28 |
29 | function validationField({ target }) {
30 | const { name } = target;
31 |
32 | const errorField = validation(values)[name];
33 |
34 | setErrors((oldErrors) => ({ ...oldErrors, [name]: errorField }));
35 | }
36 |
37 | function sendFormIsValid() {
38 | console.log('Conteudo do errors', errors);
39 | console.log('Qtd length error', Object.values(errors).filter((error) => error).length);
40 | console.log('form enviado?', isSubmitting);
41 |
42 | console.log(
43 | 'Erro do if',
44 | Object.values(errors).filter((error) => error).length === 0 && isSubmitting
45 | );
46 | if (Object.values(errors).filter((error) => error).length === 0 && isSubmitting) {
47 | callback(values);
48 | }
49 | }
50 |
51 | useEffect(sendFormIsValid, [errors]);
52 |
53 | return {
54 | values,
55 | setValues,
56 | handleChange,
57 | handleSubmit,
58 | errors,
59 | setErrors,
60 | validationForm,
61 | validationField,
62 | };
63 | }
64 |
65 | export default useValidation;
66 |
--------------------------------------------------------------------------------
/src/containers/MenuDev/styles.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 | import { mickeyHover } from '../../styles/settings/Cursor';
3 | import menu from '../../img/icons/menu.png';
4 | import close from '../../img/icons/close.png';
5 | import ButtonNes from '../../components/ButtonNes';
6 |
7 | const Action = styled(ButtonNes)`
8 | color: var(--color-gray-lighter);
9 | text-decoration: none;
10 | margin-bottom: var(--gap-small);
11 |
12 | @media (min-width: 500px) {
13 | margin: 0 var(--gap-smaller) var(--gap-small);
14 | width: calc(50% - var(--gap-smaller) * 2);
15 | }
16 | `;
17 |
18 | const Close = styled.img.attrs({ src: close })`
19 | cursor: url(${mickeyHover}) 14 0, pointer;
20 | margin-bottom: var(--gap-big);
21 | width: 45px;
22 | align-self: flex-end;
23 |
24 | &:hover {
25 | transform: scale(1.1);
26 | }
27 | `;
28 |
29 | const Icon = styled.img.attrs({ src: menu })`
30 | height: 75%;
31 | width: 50px;
32 | cursor: url(${mickeyHover}) 14 0, pointer;
33 | `;
34 |
35 | const Buttons = styled.div`
36 | @media (min-width: 500px) {
37 | display: flex;
38 | flex-wrap: wrap;
39 | }
40 | `;
41 |
42 | const Medias = styled.div`
43 | display: flex;
44 | justify-content: center;
45 | position: absolute;
46 | bottom: var(--gap-medium);
47 | right: 0;
48 | left: 0;
49 | `;
50 |
51 | const Navigation = styled.nav`
52 | display: flex;
53 | flex-direction: column;
54 | box-sizing: border-box;
55 | top: 0;
56 | left: 0;
57 | right: 0;
58 | height: 100vh;
59 | background-color: var(--color-gray-dark);
60 | padding: var(--gap-medium);
61 | transition: transform 150ms linear;
62 |
63 | ${({ active }) =>
64 | !active &&
65 | css`
66 | transform: translateY(-100%);
67 | `};
68 | `;
69 |
70 | const Wrapper = styled.div`
71 | & > ${Navigation} {
72 | position: fixed;
73 | z-index: 10;
74 | }
75 | `;
76 |
77 | export { Action, Close, Icon, Navigation, Wrapper, Buttons, Medias };
78 |
--------------------------------------------------------------------------------
/.github/workflows/azure-static-web-apps-lemon-hill-0129ffe10.yml:
--------------------------------------------------------------------------------
1 | name: Azure Static Web Apps CI/CD
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | types: [opened, synchronize, reopened, closed]
9 | branches:
10 | - master
11 |
12 | jobs:
13 | build_and_deploy_job:
14 | if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
15 | runs-on: ubuntu-latest
16 | name: Build and Deploy Job
17 | steps:
18 | - uses: actions/checkout@v2
19 | with:
20 | submodules: true
21 | - name: Build And Deploy
22 | id: builddeploy
23 | uses: Azure/static-web-apps-deploy@v0.0.1-preview
24 | with:
25 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_LEMON_HILL_0129FFE10 }}
26 | repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
27 | action: "upload"
28 | ###### Repository/Build Configurations - These values can be configured to match you app requirements. ######
29 | # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
30 | app_location: "/" # App source code path
31 | api_location: "api" # Api source code path - optional
32 | app_artifact_location: "build" # Built app content directory - optional
33 | ###### End of Repository/Build Configurations ######
34 |
35 | close_pull_request_job:
36 | if: github.event_name == 'pull_request' && github.event.action == 'closed'
37 | runs-on: ubuntu-latest
38 | name: Close Pull Request Job
39 | steps:
40 | - name: Close Pull Request
41 | id: closepullrequest
42 | uses: Azure/static-web-apps-deploy@v0.0.1-preview
43 | with:
44 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_LEMON_HILL_0129FFE10 }}
45 | action: "close"
46 |
--------------------------------------------------------------------------------
/src/components/ButtonNes/index.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 | import { Link } from 'react-router-dom';
3 |
4 | const ButtonNes = styled(Link)`
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | box-sizing: border-box;
9 | position: relative;
10 | width: 100%;
11 | min-height: 45rem;
12 | font-size: var(--size-medium);
13 | color: var(--color-gray-lighter);
14 | background-color: var(--color-primary-medium);
15 | text-decoration: none;
16 | border: none;
17 | font-family: inherit;
18 |
19 | ${({ twitch }) =>
20 | twitch &&
21 | css`
22 | background-color: var(--color-secondary-medium);
23 | `}
24 |
25 | ${({ share }) =>
26 | share &&
27 | css`
28 | background-color: var(--color-tertiary-medium);
29 | `}
30 |
31 | &::selection {
32 | background-color: transparent;
33 | }
34 |
35 | &:hover {
36 | background-color: var(--color-primary-dark);
37 |
38 | ${({ twitch }) =>
39 | twitch &&
40 | css`
41 | background-color: var(--color-secondary-dark);
42 | `}
43 |
44 | ${({ share }) =>
45 | share &&
46 | css`
47 | background-color: var(--color-tertiary-dark);
48 | `}
49 | }
50 |
51 | &::before,
52 | &::after {
53 | content: '';
54 | position: absolute;
55 | background-color: var(--color-gray-medium);
56 | opacity: 0.5;
57 | }
58 |
59 | &::before {
60 | width: 100%;
61 | height: var(--size-border);
62 | bottom: 0;
63 | transform-origin: center bottom;
64 | }
65 |
66 | &::after {
67 | width: var(--size-border);
68 | height: calc(100% - var(--size-border));
69 | right: 0;
70 | top: 0;
71 | transform-origin: right top;
72 | }
73 |
74 | &:hover::before {
75 | transform: scaleY(1.6);
76 | }
77 |
78 | &:hover::after {
79 | transform: scale(1.6, 0.95);
80 | }
81 |
82 | &:active::before {
83 | top: 0;
84 | }
85 |
86 | &:active::after {
87 | top: auto;
88 | bottom: 0;
89 | left: 0;
90 | transform: scale(1);
91 | }
92 | `;
93 |
94 | export default ButtonNes;
95 |
--------------------------------------------------------------------------------
/src/pages/LivesSchedule/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import Template from '../Template';
3 | import ContainerDev from '../../containers/ContainerDev';
4 | import TitleNes from '../../components/TitleNes';
5 | import CardDay from '../../containers/CardDay';
6 | import WrapperCard from '../../containers/WrapperCard';
7 | import PlusButton from '../../components/PlusButton';
8 | import service from '../../services/lives.service';
9 | import { formatDate } from '../../libs/date';
10 |
11 | const LivesSchedule = () => {
12 | const [lives, setLives] = useState([
13 | {
14 | uuid: 'a0',
15 | startDate: '',
16 | shortTitle: 'loading...',
17 | shortDescription: 'loading também...',
18 | },
19 | {
20 | uuid: 'a1',
21 | startDate: '',
22 | shortTitle: 'loading...',
23 | shortDescription: 'loading também...',
24 | },
25 | {
26 | uuid: 'a2',
27 | startDate: '',
28 | shortTitle: 'loading...',
29 | shortDescription: 'loading também...',
30 | },
31 | ]);
32 | const [shimmerEffect, setShimmerEffect] = useState(true);
33 |
34 | useEffect(() => {
35 | service
36 | .findLivesFuture()
37 | .then(({ data }) => {
38 | const newLives = data.map((live) => ({ ...live, startDate: formatDate(live.startDate) }));
39 |
40 | setLives(newLives);
41 | setShimmerEffect(false);
42 | })
43 | .catch((error) => {
44 | console.log(error);
45 | });
46 | }, []);
47 |
48 | return (
49 |
50 |
51 | Agenda das Lives
52 |
53 |
54 | {lives.map(({ uuid, shortTitle, startDate, shortDescription }) => (
55 |
63 | ))}
64 |
65 |
66 |
67 |
68 |
69 | );
70 | };
71 |
72 | export default LivesSchedule;
73 |
--------------------------------------------------------------------------------
/src/containers/ShareSocial/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { TwitterShareButton, LinkedinShareButton, FacebookShareButton } from 'react-share';
3 | import PropTypes from 'prop-types';
4 | import ContainerNes from '../../components/ContainerNes';
5 | import TitleNes from '../../components/TitleNes';
6 | import Share from './styles';
7 | import SocialMedia from '../../components/SocialMedia';
8 |
9 | const buttonsSocial = {
10 | twitter: (what, { title, url, via, hashtags }) => (
11 |
12 |
13 |
14 | ),
15 | linkedin: (what, { title, url }) => (
16 |
17 |
18 |
19 | ),
20 | facebook: (what, { quote, url }) => (
21 |
22 |
23 |
24 | ),
25 | };
26 |
27 | const ShareSocial = ({ title, url, via, hashtags, active, changeActive }) => {
28 | const medias = [
29 | {
30 | what: 'twitter',
31 | propsButton: {
32 | title,
33 | url,
34 | hashtags,
35 | via,
36 | },
37 | },
38 | {
39 | what: 'linkedin',
40 | propsButton: { title, url },
41 | },
42 | {
43 | what: 'facebook',
44 | propsButton: {
45 | quote: title,
46 | url,
47 | },
48 | },
49 | ];
50 |
51 | return (
52 |
53 |
54 | Compartilhe com pessoas felizes!
55 |
56 | {medias.map(({ what, propsButton }) => buttonsSocial[what](what, propsButton))}
57 |
58 |
59 | );
60 | };
61 |
62 | ShareSocial.defaultProps = {
63 | via: 'marcobrunodev',
64 | hashtags: ['LiveCoding'],
65 | };
66 |
67 | ShareSocial.propTypes = {
68 | title: PropTypes.string.isRequired,
69 | url: PropTypes.string.isRequired,
70 | via: PropTypes.string,
71 | hashtags: PropTypes.arrayOf(String),
72 | active: PropTypes.bool.isRequired,
73 | changeActive: PropTypes.func.isRequired,
74 | };
75 |
76 | export default ShareSocial;
77 |
--------------------------------------------------------------------------------
/src/pages/LiveDetails/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { useParams } from 'react-router-dom';
3 | import TitleNes from '../../components/TitleNes';
4 | import InfosLive from '../../containers/InfosLive';
5 | import Template from '../Template';
6 | import service from '../../services/lives.service';
7 | import { formatDate, getHour } from '../../libs/date';
8 | import ShareSocial from '../../containers/ShareSocial';
9 |
10 | const LiveDetails = () => {
11 | const [live, setLive] = useState({
12 | title: 'Carregando título feliz...',
13 | goals: ['- ainda não sei', '- o que', '- vai acontecer'],
14 | day: '00/00/0001',
15 | hour: 'AA:BB até CC:KK',
16 | description: `Lorem da galera...`,
17 | });
18 | const [shimmerEffect, setShimmerEffect] = useState(true);
19 | const [activeShareModal, setActiveShareModal] = useState(false);
20 | const { title, goals, day, hour, description, guest } = live;
21 | const { id } = useParams();
22 |
23 | useEffect(() => {
24 | service
25 | .findLiveByUuid(id)
26 | .then(({ data }) => {
27 | const dataLive = {
28 | title: data.title,
29 | goals: data.goals.map((goal) => `- ${goal}`),
30 | day: formatDate(data.startDate),
31 | hour: `${getHour(data.startDate)} - ${getHour(data.finishDate)}`,
32 | description: data.description,
33 | };
34 |
35 | setLive(dataLive);
36 | setShimmerEffect(false);
37 | })
38 | .catch(() => {});
39 | }, [id]);
40 |
41 | const changeActiveShareModal = () => {
42 | setActiveShareModal(!activeShareModal);
43 | };
44 |
45 | const getUrl = () => {
46 | const { href } = window.location;
47 | const isLocalhost = /^http:\/\/localhost/;
48 |
49 | return isLocalhost.test(href) ? 'https://twitch.tv/marcobrunodev' : href;
50 | };
51 |
52 | return (
53 |
54 | {title}
55 |
56 |
65 |
66 |
72 |
73 | );
74 | };
75 |
76 | export default LiveDetails;
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `yarn start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `yarn test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `yarn build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `yarn eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `yarn build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/src/containers/CardDay/styles.js:
--------------------------------------------------------------------------------
1 | import styled, { css, keyframes } from 'styled-components';
2 | import ContainerNes from '../../components/ContainerNes';
3 | import TagNes from '../../components/TagNes';
4 | import ButtonNes from '../../components/ButtonNes';
5 |
6 | const turnTurn = keyframes`
7 | 0% {
8 | transform: rotate(0deg) scale(1.2) translate(-50%, -50%);
9 | opacity: 1;
10 | }
11 | 20% {
12 | transform: rotate(72deg) translate(-50%, -50%);
13 | opacity: 0.8;
14 | }
15 | 50% {
16 | transform: rotate(144deg) scale(1.2) translate(-50%, -50%);
17 | opacity: 1;
18 | }
19 | 80% {
20 | transform: rotate(216deg) translate(-50%, -50%);
21 | opacity: 0.8;
22 | }
23 | 100% {
24 | transform: rotate(360deg) scale(1.2) translate(-50%, -50%);
25 | opacity: 1;
26 | }
27 | `;
28 |
29 | const Title = styled.h2`
30 | display: flex;
31 | align-items: center;
32 | justify-content: center;
33 | box-sizing: border-box;
34 | width: 100%;
35 | padding: var(--gap-smaller) var(--gap-small);
36 | line-height: 1.2;
37 | text-align: center;
38 | background-color: var(--color-gray-lighter);
39 | color: var(--color-gray-dark);
40 | padding-top: var(--gap-medium);
41 | font-size: var(--size-big);
42 | `;
43 |
44 | const Description = styled.p`
45 | color: var(--color-gray-lighter);
46 | padding: var(--gap-medium) var(--gap-small);
47 | font-family: 'Fira Code', monospace;
48 | font-size: var(--size-big);
49 | line-height: 1.2;
50 | height: 165px;
51 | overflow: hidden;
52 | `;
53 |
54 | const Menu = styled.div`
55 | margin-top: auto;
56 | width: 100%;
57 | `;
58 |
59 | const Card = styled(ContainerNes)`
60 | display: flex;
61 | box-sizing: border-box;
62 | background-color: var(--color-gray-dark);
63 | flex-direction: column;
64 | align-items: center;
65 | position: relative;
66 | height: 60vh;
67 | width: 20vw;
68 | width: calc(320px - var(--gap-smaller) * 2);
69 | height: 440px;
70 |
71 | ${TagNes} {
72 | position: absolute;
73 | top: 0;
74 | z-index: 2;
75 | transform: translateY(-30%);
76 | }
77 |
78 | ${ButtonNes}:not(:last-child) {
79 | margin-bottom: var(--gap-small);
80 | }
81 |
82 | ${(props) =>
83 | props.shimmerEffect &&
84 | css`
85 | position: relative;
86 |
87 | ${TagNes} {
88 | width: 100px;
89 | height: 16px;
90 | }
91 |
92 | ${Title}, ${TagNes}, ${Description}, ${ButtonNes} {
93 | filter: blur(2px);
94 | }
95 |
96 | &::before {
97 | content: '';
98 | position: absolute;
99 | width: 25px;
100 | height: 25px;
101 | background-color: var(--color-secondary-medium);
102 | top: 50%;
103 | left: 50%;
104 | transform: translate(-50%, -50%);
105 | transform-origin: left top;
106 | animation: 2.5s ${turnTurn} linear infinite;
107 | box-shadow: 0px 0px 2px 2px var(--color-gray-light);
108 | }
109 | `}
110 | `;
111 |
112 | export { Card, Title, Description, Menu };
113 |
--------------------------------------------------------------------------------
/cypress/integration/pages/LiveNew.test.js:
--------------------------------------------------------------------------------
1 | import { randomLiveInfo } from '../../data-builders/lives.builder';
2 |
3 | describe('Page LiveNew', () => {
4 | before(() => {
5 | cy.visit('/lives/new');
6 | });
7 |
8 | it('Should all errors in fields', () => {
9 | cy.get('button').click();
10 |
11 | cy.get('input[name="shortTitle"] + span').contains('Título curto é obrigatório');
12 | cy.get('input[name="title"] + span').contains('Título é obrigatório');
13 | cy.get('input[name="startDate"] + span').contains('Data de início é obrigatória');
14 | cy.get('input[name="startTime"] + span').contains('Hora de início é obrigatória');
15 | cy.get('input[name="finishDate"] + span').contains('Data de fim é obrigatória');
16 | cy.get('input[name="finishTime"] + span').contains('Hora de fim é obrigatória');
17 | cy.get('textarea[name="goals"] + span').contains('Objetivos são obrigatórios');
18 | cy.get('textarea[name="shortDescription"] + span').contains('Descrição curta é obrigatória');
19 | cy.get('textarea[name="description"] + span').contains('Descrição é obrigatória');
20 | });
21 |
22 | it('Should show the error in field when the field is missing', () => {
23 | cy.visit('/lives/new');
24 | cy.get('input[name="shortTitle"]').focus();
25 | cy.get('input[name="title"]').focus();
26 | cy.get('input[name="shortTitle"] + span').contains('Título curto é obrigatório');
27 | cy.get('input[name="startDate"]').focus();
28 | cy.get('input[name="title"] + span').contains('Título é obrigatório');
29 | cy.get('input[name="startTime"]').focus();
30 | cy.get('input[name="startDate"] + span').contains('Data de início é obrigatória');
31 | cy.get('input[name="finishDate"]').focus();
32 | cy.get('input[name="startTime"] + span').contains('Hora de início é obrigatória');
33 | cy.get('input[name="finishTime"]').focus();
34 | cy.get('input[name="finishDate"] + span').contains('Data de fim é obrigatória');
35 | cy.get('textarea[name="goals"]').focus();
36 | cy.get('input[name="finishTime"] + span').contains('Hora de fim é obrigatória');
37 | cy.get('textarea[name="shortDescription"]').focus();
38 | cy.get('textarea[name="goals"] + span').contains('Objetivos são obrigatórios');
39 | cy.get('textarea[name="description"]').focus();
40 | cy.get('textarea[name="shortDescription"] + span').contains('Descrição curta é obrigatória');
41 | cy.get('button').contains('Enviar').focus();
42 | cy.get('textarea[name="description"] + span').contains('Descrição é obrigatória');
43 | });
44 |
45 | it('Should create the event new in /lives', () => {
46 | const {
47 | shortTitle,
48 | title,
49 | start,
50 | finish,
51 | goals,
52 | shortDescription,
53 | description,
54 | } = randomLiveInfo();
55 |
56 | cy.get('input[name="shortTitle"]').type(shortTitle);
57 | cy.get('input[name="title"]').type(title);
58 | cy.get('input[name="startDate"]').type(start.date);
59 | cy.get('input[name="startTime"]').type(start.time);
60 | cy.get('input[name="finishDate"]').type(finish.date);
61 | cy.get('input[name="finishTime"]').type(finish.time);
62 | cy.get('textarea[name="goals"]').type(goals);
63 | cy.get('textarea[name="shortDescription"]').type(shortDescription);
64 | cy.get('textarea[name="description"]').type(description);
65 |
66 | cy.get('button').click();
67 |
68 | cy.location('pathname').should('include', 'lives');
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/src/containers/FormLive/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useHistory } from 'react-router-dom';
3 | import FormNes from '../../components/FormNes';
4 | import { Half } from '../../components/FormNes/styles';
5 | import FieldNes from '../../components/FieldNes';
6 | import TextareaNes from '../../components/TextareaNes';
7 | import ButtonNes from '../../components/ButtonNes';
8 | import useValidation from '../../libs/validation/useValidation';
9 | import validation from './validation';
10 | import service from '../../services/lives.service';
11 |
12 | function FormLive() {
13 | const history = useHistory();
14 | const { values, handleChange, handleSubmit, errors, validationField } = useValidation(
15 | validation,
16 | newLive,
17 | 'shortTitle',
18 | 'title',
19 | 'startDate',
20 | 'startTime',
21 | 'finishDate',
22 | 'finishTime',
23 | 'goals',
24 | 'shortDescription',
25 | 'description'
26 | );
27 |
28 | function newLive(live) {
29 | const [startYear, startMonth, startDay] = live.startDate.split('-');
30 | const [startHour, startMinute] = live.startTime.split(':');
31 | const [finishYear, finishMonth, finishDay] = live.finishDate.split('-');
32 | const [finishHour, finishMinute] = live.finishTime.split(':');
33 | const startDate = new Date(startYear, startMonth, startDay, startHour, startMinute);
34 | const finishDate = new Date(finishYear, finishMonth, finishDay, finishHour, finishMinute);
35 | const newStream = { ...live, startDate, finishDate };
36 |
37 | service
38 | .create(newStream)
39 | .then(() => {
40 | console.log('EITA!!!!');
41 | history.push('/lives');
42 | })
43 | .catch((err) => {
44 | console.log('ERRORS', err);
45 | console.log('body', err.response.data);
46 | });
47 | }
48 |
49 | return (
50 |
51 |
59 |
60 |
68 |
69 |
70 |
79 |
88 |
89 |
90 |
91 |
100 |
109 |
110 |
111 |
120 |
121 |
129 |
130 |
138 |
139 |
140 | Enviar
141 |
142 |
143 | );
144 | }
145 |
146 | export default FormLive;
147 |
--------------------------------------------------------------------------------