diff --git a/next.config.js b/next.config.js index d1e3377..24846a3 100644 --- a/next.config.js +++ b/next.config.js @@ -9,7 +9,8 @@ const nextConfig = { 'media-exp1.licdn.com', 'lh3.googleusercontent.com' ] - } + }, + pageExtensions: ['page.tsx', 'ts'], } module.exports = nextConfig diff --git a/package.json b/package.json index 8b70bbc..f4bd543 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,11 @@ "postbuild": "next-sitemap" }, "dependencies": { - "@hookform/resolvers": "^2.9.7", + "@hookform/resolvers": "^3.3.2", "@prismicio/client": "^6.7.0", "@prismicio/helpers": "^2.3.3", - "@prismicio/next": "^0.1.3", - "@prismicio/react": "^2.5.0", + "@prismicio/next": "^1.5.0", + "@prismicio/react": "^2.7.3", "@prismicio/slice-simulator-react": "^0.2.2", "@radix-ui/react-accordion": "^1.0.1", "@radix-ui/react-dialog": "^1.0.2", @@ -23,26 +23,28 @@ "@radix-ui/react-toast": "^1.0.0", "@radix-ui/react-tooltip": "^1.0.0", "chroma-js": "^2.4.2", - "date-fns": "^2.29.1", + "classnames": "^2.3.2", + "date-fns": "^2.30.0", "mongodb": "^4.8.1", "mongoose": "^6.5.0", - "next": "12.2.3", - "next-auth": "^4.10.3", + "next": "13.5.6", + "next-auth": "^4.24.7", + "next-share": "^0.27.0", "node-html-parser": "^5.4.2-0", - "react": "18.2.0", - "react-dom": "18.2.0", - "react-hook-form": "^7.34.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-hook-form": "^7.48.2", "react-icons": "^4.4.0", - "sass": "^1.54.0", - "zod": "^3.17.10" + "sass": "^1.69.5", + "zod": "^3.22.4" }, "devDependencies": { "@types/chroma-js": "^2.1.4", "@types/date-fns": "^2.6.0", "@types/mongoose": "^5.11.97", - "@types/node": "18.6.3", - "@types/react": "18.0.15", - "@types/react-dom": "18.0.6", + "@types/node": "^20.10.0", + "@types/react": "^18.2.38", + "@types/react-dom": "^18.2.17", "@typescript-eslint/eslint-plugin": "^5.31.0", "@typescript-eslint/parser": "^5.31.0", "eslint": "^8.0.1", @@ -58,6 +60,6 @@ "next-sitemap": "^3.1.32", "prettier": "^2.7.1", "slice-machine-ui": "^0.4.2", - "typescript": "4.7.4" + "typescript": "^5.3.2" } } diff --git a/public/background-author-card.png b/public/background-author-card.png deleted file mode 100644 index f613af0..0000000 Binary files a/public/background-author-card.png and /dev/null differ diff --git a/public/grid.svg b/public/grid.svg deleted file mode 100644 index 8cab75a..0000000 --- a/public/grid.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/public/sitemap.xml b/public/sitemap.xml index 4427ce9..39fed00 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -1,4 +1,3 @@ -https://www.brchallenges.com/sitemap-0.xml \ No newline at end of file diff --git a/src/components/Accordion/styles.module.scss b/src/components/Accordion/styles.module.scss index a501c55..5ef8b5a 100644 --- a/src/components/Accordion/styles.module.scss +++ b/src/components/Accordion/styles.module.scss @@ -6,9 +6,10 @@ } .accordion__item { - background: var(--mauve2); + background: var(--background-secondary); border-radius: 4px; overflow: hidden; + box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px; } .accordion__trigger { @@ -19,9 +20,12 @@ justify-content: space-between; font-weight: 500; + letter-spacing: -0.3px; + font-size: 1.125rem; + font-family: var(--family-bold); padding: 24px; - background: var(--mauve3); + background: var(--background-quartenary); svg { transform: rotate(90deg); @@ -37,7 +41,7 @@ padding: 30px 48px; border-top: 1px #000; - font-size: 1rem; + font-size: 1.125rem; line-height: 2rem; font-weight: 300; diff --git a/src/components/Challenge/Card/index.tsx b/src/components/Challenge/Card/index.tsx deleted file mode 100644 index 972b0ba..0000000 --- a/src/components/Challenge/Card/index.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import Link from 'next/link' -import Image from 'next/image' -import { useSession } from 'next-auth/react' -import { PrismicNextImage } from '@prismicio/next' - -import { Challenge } from 'types' -import { SHORT_DATE, FULL_DATE } from 'utils/constants' -import { - getDaysRemaining, - getFullDate, - getStatusChallenge, - isChallengeClosed -} from 'utils/format' -import { IconPerson, IconCalendar, IconClock } from 'components/SVG' - -import styles from './styles.module.scss' - -export const ChallengeCard = (challenge: Challenge) => { - const { data } = useSession() - - const status = getStatusChallenge({ - ...challenge, - userChallenges: data?.user.challenges || [] - }) - - const isClosed = isChallengeClosed(status.type) - - return ( -
- - -
- -
-
- - -
- - -

{challenge.name}

-
- - -
- - {challenge.authors[0].name} - - - -
- - {challenge?.users && ( -
- - - Partipantes - - -
- {challenge.users.map((image, index) => ( -
- -
- ))} - - {!!challenge.participants && ( - - + {challenge.participants} - - )} -
- - {challenge.users.length === 0 && ( -

Sem participantes no momento 🙁

- )} -
- )} -
- -
- - - {isClosed ? 'Detalhes' : 'Acessar desafio'} - - - - {isClosed && ( - - - Participantes - - - )} - - {status.type === 'submitted' && ( - - Minha solução - - )} -
-
- ) -} diff --git a/src/components/Challenge/Card/styles.module.scss b/src/components/Challenge/Card/styles.module.scss deleted file mode 100644 index 7b79ad3..0000000 --- a/src/components/Challenge/Card/styles.module.scss +++ /dev/null @@ -1,134 +0,0 @@ -.challenge { - display: flex; - flex-direction: column; - - overflow: hidden; - border-radius: 8px; - background: var(--mauve2); - border: 1px solid var(--mauve6); - - &:hover .challenge__image img { - transform: scale(1.1); - } -} - -.challenge__image { - position: relative; - - width: 100%; - height: 280px; - overflow: hidden; - - img { - transition: transform 0.4s; - } -} - -.challenge__content { - display: flex; - flex-direction: column; - row-gap: 20px; - - padding: 0 24px; - margin: 16px 0 24px; - - h2 { - font-size: 1.75rem; - } - - strong, time { - font-weight: 400; - color: var(--mauve11); - font-size: 1.125rem; - - display: flex; - align-items: center; - column-gap: 6px; - } - - svg { - width: 17px; - height: auto; - } - - .challenge__info { - display: flex; - align-items: center; - column-gap: 40px; - } - - a:hover { - text-decoration: underline; - } - - @media(max-width: 600px) { - h2 { - font-size: 1.375rem; - } - - strong, time { - font-size: 0.875rem; - } - } -} - -.challenge__participants { - display: flex; - flex-direction: column; - row-gap: 8px; -} - -.users { - display: flex; - align-items: center; - - .avatar, .participants__counter { - transform: translateX(calc(var(--position) * -17px)); - } -} - -.avatar { - width: 40px; - height: 40px; - border-radius: 100%; - overflow: hidden; - position: relative; - border: 3px solid var(--mauve2); -} - -.participants__counter { - display: flex; - align-items: center; - justify-content: center; - - font-size: 0.875rem; - letter-spacing: -1px; - font-weight: 600; - - width: 40px; - height: 40px; - border-radius: 100%; - - background: var(--mauve3); -} - -.challenge__footer { - display: flex; - column-gap: 12px; - - padding: 12px 24px 16px; - margin-top: auto; - border-top: 1px solid var(--mauve6); - - a { - font-size: 1rem; - } - - @media(max-width: 600px) { - padding: 12px; - - a { - font-size: 0.875rem; - } - } -} \ No newline at end of file diff --git a/src/components/Challenge/Header/index.tsx b/src/components/Challenge/Header/index.tsx deleted file mode 100644 index 137b084..0000000 --- a/src/components/Challenge/Header/index.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import Link from 'next/link' -import { useRouter } from 'next/router' -import { useEffect, useState } from 'react' -import { useSession } from 'next-auth/react' -import { PrismicNextImage } from '@prismicio/next' - -import api from 'service/api' -import { useChallenge } from 'hooks' -import { ModalSolutionForm } from 'components/Modal' -import { IconCheck, IconPerson, IconPlus } from 'components/SVG' - -import styles from './styles.module.scss' - -export const ChallengeHeader = () => { - const router = useRouter() - - const { status, data } = useSession() - const { image, id, status_prismic } = useChallenge() - - const [isParticipant, setIsParticipant] = useState(false) - - const isVisibleParticipants = - status_prismic !== 'active' || data?.user.role === 'admin' - - const toParticipateChallenge = async () => { - if (status !== 'authenticated') { - router.push('/login') - } - - if (!isParticipant) { - await api.post(`/challenge/${id}/participate`) - setIsParticipant(true) - } - } - - useEffect(() => { - if (data?.user && data.user.challenges.includes(id)) { - setIsParticipant(true) - } - }, [data?.user, router.pathname]) - - return ( -
-
-
- -
- -
- {isParticipant && } - - {isParticipant ? ( - - Participando - - ) : ( - - )} - - {isVisibleParticipants && ( - - - - Ver participantes - - - )} -
-
-
- ) -} diff --git a/src/components/Challenge/Header/styles.module.scss b/src/components/Challenge/Header/styles.module.scss deleted file mode 100644 index 560cdbd..0000000 --- a/src/components/Challenge/Header/styles.module.scss +++ /dev/null @@ -1,71 +0,0 @@ -.header { - display: flex; - justify-content: center; - width: 100%; - - padding: 40px 24px 0; - - > div { - display: flex; - flex-direction: column; - align-items: stretch; - row-gap: 24px; - - width: 100%; - max-width: var(--width-normal); - } - - @media(max-width: 1080px) { - padding: 48px 24px 0; - } -} - -.header__image { - width: 100%; - height: 360px; - position: relative; - border-radius: 8px; - overflow: hidden; - - &:after { - content: ''; - background: rgba(0, 0, 0, 0.2); - width: 100%; - height: 100%; - - position: absolute; - inset: 0; - } - - @media(max-width: 768px) { - height: 280px; - } - - @media(max-width: 600px) { - height: 200px; - border-radius: 8px; - } -} - -.header__actions { - display: flex; - align-items: center; - justify-content: flex-end; - column-gap: 12px; - - > button, > a, .button__participant { - width: auto; - padding: 12px 16px; - } - - .button__participant { - opacity: 0.9; - background: var(--violet7); - } - - @media(max-width: 600px) { - .button__participant { - display: none; - } - } -} \ No newline at end of file diff --git a/src/components/Challenge/HeaderSimple/index.tsx b/src/components/Challenge/HeaderSimple/index.tsx deleted file mode 100644 index d2e3522..0000000 --- a/src/components/Challenge/HeaderSimple/index.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import Link from 'next/link' -import { useChallenge } from 'hooks' -import { IconPerson } from 'components/SVG' -import { PrismicNextImage } from '@prismicio/next' - -import styles from './styles.module.scss' - -export const ChallengeHeaderSimple = () => { - const { image, name, authors, id } = useChallenge() - - return ( -
-
- - -
- desafio - - -

{name}

-
- - -

- {authors[0].name} -

-
-
-
- ) -} diff --git a/src/components/Challenge/HeaderSimple/styles.module.scss b/src/components/Challenge/HeaderSimple/styles.module.scss deleted file mode 100644 index 5ccbc7c..0000000 --- a/src/components/Challenge/HeaderSimple/styles.module.scss +++ /dev/null @@ -1,97 +0,0 @@ -.header { - display: flex; - justify-content: center; - width: 100%; - - padding: 40px 24px 0; -} - -.header__content { - display: flex; - align-items: center; - justify-content: center; - - width: 100%; - max-width: var(--width-normal); - height: 350px; - - overflow: hidden; - position: relative; - border-radius: 8px; - - &:after { - content: ''; - background: linear-gradient(347deg, rgba(10, 12, 16, 0.5) 10%, rgba(10, 12, 16, 0.65) 50%, rgba(10, 12, 16, 0.5) 90%); - width: 100%; - height: 100%; - - backdrop-filter: blur(2px); - position: absolute; - inset: 0; - } - - @media(max-width: 768px) { - height: 280px; - } - - @media(max-width: 600px) { - height: 200px; - border-radius: 8px; - } -} - -.header__info { - position: absolute; - z-index: 10; - - display: flex; - flex-direction: column; - align-items: center; - - span { - font-weight: 500; - font-size: 0.875rem; - - padding: 4px 14px; - border-radius: 6px; - background: var(--link); - - margin-bottom: -10px; - } - - h1 { - font-size: 64px; - font-weight: 700; - color: var(--mauve12); - } - - p { - display: flex; - align-items: center; - column-gap: 6px; - - font-size: 1.125rem; - - svg path { - fill: var(--mauve12) - } - } - - @media(max-width: 600px) { - span { - font-size: 0.75rem; - padding: 4px 18px; - margin-bottom: -10px; - } - - h1 { - font-size: 44px; - margin-bottom: 4px; - } - - p { - column-gap: 6px; - font-size: 0.875rem; - } - } -} \ No newline at end of file diff --git a/src/components/Challenge/Navigation/index.tsx b/src/components/Challenge/Navigation/index.tsx deleted file mode 100644 index 76def98..0000000 --- a/src/components/Challenge/Navigation/index.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { IconFigma, IconGitHub, IconInfo, IconYoutube } from 'components/SVG' - -import { useChallenge } from 'hooks' -import styles from './styles.module.scss' - -export const ChallengeNavigation = () => { - const { prototype_url } = useChallenge() - - return ( - - ) -} diff --git a/src/components/Challenge/Navigation/styles.module.scss b/src/components/Challenge/Navigation/styles.module.scss deleted file mode 100644 index cfb3245..0000000 --- a/src/components/Challenge/Navigation/styles.module.scss +++ /dev/null @@ -1,43 +0,0 @@ -.container { - width: 210px; - visibility: hidden; - - position: sticky; - top: 24px; - left: 0; - - @media(max-width: 768px) { - display: none; - } -} - -.content { - list-style: none; - - display: flex; - flex-direction: column; - row-gap: 12px; - - li a { - display: flex; - align-items: center; - column-gap: 8px; - - padding: 12px; - border-radius: 4px; - color: var(--mauve11); - background: rgba(#28282c, 0.25); - - transition: all 0.4s; - - svg { - width: 20px; - height: auto; - } - - &.active, &:hover { - color: var(--violet10); - background: rgba(#28282c, 0.5); - } - } -} \ No newline at end of file diff --git a/src/components/Challenge/index.tsx b/src/components/Challenge/index.tsx deleted file mode 100644 index cee6d3f..0000000 --- a/src/components/Challenge/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -export { AuthorCard } from './AuthorCard' -export { ChallengeCard } from './Card' -export { ChallengeHeader } from './Header' -export { ChallengeHeaderSimple } from './HeaderSimple' -export { ChallengeNavigation } from './Navigation' diff --git a/src/components/ChallengeCard/index.tsx b/src/components/ChallengeCard/index.tsx new file mode 100644 index 0000000..f3c4d50 --- /dev/null +++ b/src/components/ChallengeCard/index.tsx @@ -0,0 +1,91 @@ +import Link from 'next/link' +import Image from 'next/image' +import { PrismicNextImage } from '@prismicio/next' +import { RxCalendar, RxPencil1, RxPerson, RxReader } from 'react-icons/rx' + +import { formatDate } from 'utils/format' +import type { Challenge } from 'types/challenge' + +import styles from './styles.module.scss' + +export const ChallengeCard = (challenge: Challenge) => ( +
+ + + + +
+ +

{challenge.name}

+ + +
+
+ + + Design + +

{challenge.authors.map(a => a.name).join(',')}

+
+
+ + + Publicando em + + +
+
+ +
+ + + Partipantes + + + {challenge.users.length ? ( +
+ {challenge.users.map((image, index) => ( + + imagem de avatar + + ))} + + {!!challenge.participants && ( + + + {challenge.participants} + + )} +
+ ) : ( +

Sem participantes no momento 🙁

+ )} +
+ +
+ + + Mais detalhes + + + + Ver participantes + +
+
+
+) diff --git a/src/components/ChallengeCard/styles.module.scss b/src/components/ChallengeCard/styles.module.scss new file mode 100644 index 0000000..eb5b24d --- /dev/null +++ b/src/components/ChallengeCard/styles.module.scss @@ -0,0 +1,192 @@ +.challenge { + display: flex; + flex-direction: column; + + overflow: hidden; + border-radius: 8px; + background: var(--background-quartenary); + box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px; + + filter: grayscale(0.1); + transition: all 0.4s; + + &:hover { + filter: grayscale(0); + + .challenge__image img { + filter: grayscale(0); + transform: scale(1.1); + } + } +} + +.challenge__image { + width: 100%; + height: 250px; + overflow: hidden; + + img { + filter: grayscale(0.3); + + width: 100%; + height: 100%; + object-fit: cover; + object-position: top; + + transition: all 0.4s; + } + + @media(max-width: 650px) { + height: 200px; + } +} + +.challenge__info { + padding: 16px 24px 24px; + + display: flex; + flex-direction: column; + row-gap: 12px; + + @media(max-width: 650px) { + padding: 16px; + } + + h1 { + font-size: 1.75rem; + + &:hover { + text-decoration: underline; + } + } + + strong { + display: flex; + align-items: center; + column-gap: 4px; + + margin-bottom: 2px; + } + + @media(max-width: 650px) { + padding: 16px; + + h1 { + font-size: 1.375rem; + } + + strong { + font-size: 0.875rem; + margin-bottom: 0; + + svg { + width: 0.875rem; + height: auto; + } + } + } +} + +.challenge__line { + display: flex; + align-items: flex-start; + column-gap: 64px; + + time, p { + font-size: 1rem; + color: var(--text-secondary); + font-family: var(--family-bold); + } + + @media(max-width: 650px) { + column-gap: 32px; + + time, p { + font-size: 0.875rem; + } + } +} + +.challenge__participants { + display: flex; + flex-direction: column; + row-gap: 8px; + + margin-bottom: 12px; + + @media(max-width: 650px) { + margin-bottom: 8px; + } +} + +.users { + position: relative; + height: 40px; + + .user__image, .participants__counter { + position: absolute; + top: 0; + left: calc(var(--position) * 25px); + + width: 40px; + height: 40px; + border-radius: 100%; + } + + .user__image { + overflow: hidden; + border: 2px solid #333; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + } + + .participants__counter { + display: flex; + align-items: center; + justify-content: center; + + font-weight: 600; + font-size: 0.875rem; + letter-spacing: -2px; + + background: #333; + } + + @media(max-width: 650px) { + .user__image, .participants__counter { + width: 32px; + height: 32px; + left: calc(var(--position) * 20px); + } + + picture:nth-child(n+15) { + display:none; + } + + .participants__counter { + left: calc(13 * 20px); + } + } +} + +.links { + display: flex; + margin-top: auto; + column-gap: 24px; + + a { + padding: 12px 0; + } + + @media(max-width: 650px) { + column-gap: 16px; + + a { + font-size: 0.75rem; + } + } +} \ No newline at end of file diff --git a/src/components/Form/ErrorMessage/styles.module.scss b/src/components/Form/ErrorMessage/styles.module.scss index 1e8e100..0d82fa1 100644 --- a/src/components/Form/ErrorMessage/styles.module.scss +++ b/src/components/Form/ErrorMessage/styles.module.scss @@ -1,5 +1,5 @@ .error { font-size: 0.875rem; - line-height: 1rem; - color: var(--red11); + letter-spacing: 0.3px; + color: var(--red); } \ No newline at end of file diff --git a/src/components/Form/Filter/index.tsx b/src/components/Form/Filter/index.tsx deleted file mode 100644 index 24a5d9e..0000000 --- a/src/components/Form/Filter/index.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useState } from 'react' -import { FaChevronDown } from 'react-icons/fa' - -import styles from './styles.module.scss' - -interface OptionProps { - value: string - label: string -} - -interface FilterProps { - label: string - selecteds: string[] - options: OptionProps[] - onFilter: (optionId: string) => void -} - -export const Filter = ({ - label, - options, - onFilter, - selecteds -}: FilterProps) => { - const [active, setActive] = useState(false) - - const handleFilterOption = (optionId: string) => { - onFilter(optionId) - setActive(false) - } - - return ( -
- -
    - {options.map(option => ( -
  • handleFilterOption(option.value)} - className={selecteds.includes(option.value) ? styles.selected : ''} - > - {option.label} -
  • - ))} -
-
- ) -} diff --git a/src/components/Form/Filter/styles.module.scss b/src/components/Form/Filter/styles.module.scss deleted file mode 100644 index 1ea5e26..0000000 --- a/src/components/Form/Filter/styles.module.scss +++ /dev/null @@ -1,103 +0,0 @@ -.container { - display: flex; - flex-direction: column; - align-items: stretch; - - // width: 200px; - position: relative; - - label { - display: flex; - align-items: center; - column-gap: 24px; - - font-size: 0.875rem; - line-height: 1.75rem; - color: var(--mauve12); - - width: 100%; - height: 35px; - border-radius: 8px; - padding: 0 16px; - background: var(--mauve4); - - svg { - width: 12px; - height: auto; - - transition: transform 0.4s; - } - - &.active svg { - transform: rotate(-180deg); - } - - @media(max-width: 650px) { - font-size: 0.75rem; - line-height: 1.5rem; - padding: 0 14px; - - svg { - width: 10px; - } - } - } -} - -.options { - display: flex; - flex-direction: column; - align-items: stretch; - row-gap: 4px; - - position: absolute; - top: 45px; - left: 0; - z-index: 10; - - list-style: none; - - width: 100%; - padding: 0 6px 0; - background: var(--mauve4); - box-shadow: 0px 3px 14px #00000033; - - overflow: hidden; - max-height: 0px; - - transition: all 0.4s; - - li { - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--mauve11); - - padding: 6px 14px; - border-radius: 4px; - - transition: background 0.2s; - - &:first-child { - margin-top: 12px; - } - - &:hover, &.selected { - cursor: pointer; - color: var(--mauve12); - background: var(--mauve7); - } - - @media(max-width: 650px) { - font-size: 0.75rem; - line-height: 1.125rem; - - padding: 4px; - } - } - - &.active { - border-radius: 4px; - max-height: 250px; - padding: 0 6px 12px; - } -} \ No newline at end of file diff --git a/src/components/Form/Input/index.tsx b/src/components/Form/Input/index.tsx index d7bf558..bb3b390 100644 --- a/src/components/Form/Input/index.tsx +++ b/src/components/Form/Input/index.tsx @@ -1,33 +1,30 @@ -import { InputHTMLAttributes } from 'react' -import { useFormContext } from 'react-hook-form' +import { ComponentProps } from 'react' +import { useController, useFormContext } from 'react-hook-form' import { ErrorMessage } from 'components/Form' import styles from './styles.module.scss' -interface InputProps extends InputHTMLAttributes { +interface InputProps extends ComponentProps<'input'> { name: string label: string } export const Input = ({ name, label, ...rest }: InputProps) => { - const { - register, - formState: { errors } - } = useFormContext() + const { control } = useFormContext() + const { field, fieldState } = useController({ name, control }) - const error = errors[name]?.message + const error = fieldState.error?.message const isError = typeof error === 'string' return (
diff --git a/src/components/Form/Input/styles.module.scss b/src/components/Form/Input/styles.module.scss index 9954cd6..f492d2a 100644 --- a/src/components/Form/Input/styles.module.scss +++ b/src/components/Form/Input/styles.module.scss @@ -4,23 +4,17 @@ align-items: flex-start; row-gap: 6px; - label { - font-size: 1rem; - line-height: 1.25rem; - color: var(--mauve11); - } - input { outline: none; width: 100%; padding: 12px; border-radius: 6px; - background: var(--mauve3); - border: 1px solid var(--mauve3); + border: 1px solid var(--line); + background: var(--background-tertiary); &:focus, &:not(:placeholder-shown) { - border-color: var(--violet8); + border-color: var(--purple); } &[aria-invalid=true] { @@ -28,8 +22,8 @@ } &::placeholder { - color: var(--mauve8); font-weight: 400; + color: var(--text-secondary); } &:disabled { diff --git a/src/components/Form/Radio/index.tsx b/src/components/Form/Radio/index.tsx deleted file mode 100644 index 00e1847..0000000 --- a/src/components/Form/Radio/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useFormContext } from 'react-hook-form' - -import styles from './styles.module.scss' - -export interface RadioOption { - name: string - value: string - label: string - onClick: (value: string) => void - isChecked: boolean -} - -export const Radio = ({ - name, - value, - label, - onClick, - isChecked -}: RadioOption) => { - const { register } = useFormContext() - - return ( -
- onClick(value)} - /> - -
- ) -} diff --git a/src/components/Form/Radio/styles.module.scss b/src/components/Form/Radio/styles.module.scss deleted file mode 100644 index 9755df7..0000000 --- a/src/components/Form/Radio/styles.module.scss +++ /dev/null @@ -1,43 +0,0 @@ -.radio { - display: flex; - align-items: center; - column-gap: 8px; - - min-width: 150px; - padding: 12px 16px; - border-radius: 4px; - border: 1px solid var(--mauve3); - background: var(--mauve3); - - &[aria-checked="true"] { - border-color: var(--violet8); - background: rgba(#5842c3, 0.05); // --violet8 - } - - label { - flex: 1; - font-size: 0.875rem; - } - - input { - position: relative; - - &:after { - content: ''; - top: -1px; - left: -1px; - width: 15px; - height: 15px; - position: absolute; - border-radius: 100%; - border: 1px solid rgba(#5842c3, 0.6); // --violet8 - background-color: var(--mauve2); - transition: border 0.2s; - } - - &:checked:after { - border-width: 5px; - border-color: var(--link); - } - } -} \ No newline at end of file diff --git a/src/components/Form/RadioGroup/index.tsx b/src/components/Form/RadioGroup/index.tsx index 6bc887f..5461658 100644 --- a/src/components/Form/RadioGroup/index.tsx +++ b/src/components/Form/RadioGroup/index.tsx @@ -1,37 +1,41 @@ -import { useState } from 'react' -import { useFormContext } from 'react-hook-form' +import { useController, useFormContext } from 'react-hook-form' -import { Radio, RadioOption, ErrorMessage } from 'components/Form' +import { ErrorMessage } from 'components/Form' import styles from './styles.module.scss' interface RadioGroupProps { name: string label: string - options: Omit[] + options: { + label: string + value: string + }[] } export const RadioGroup = ({ name, label, options }: RadioGroupProps) => { - const { getValues } = useFormContext() - const [checked, setChecked] = useState(getValues(name) || '') - - const handleCheckedRadio = (value: string): void => { - setChecked(value) - } + const { control } = useFormContext() + const { field } = useController({ name, control }) return (
- +
{options.map(option => ( - + className={styles.radio} + aria-checked={field.value === option.value} + > + field.onChange(option.value)} + /> + +
))} diff --git a/src/components/Form/RadioGroup/styles.module.scss b/src/components/Form/RadioGroup/styles.module.scss index 2ac858d..aa1a2f2 100644 --- a/src/components/Form/RadioGroup/styles.module.scss +++ b/src/components/Form/RadioGroup/styles.module.scss @@ -10,8 +10,47 @@ column-gap: 24px; } -.label { - font-size: 1.125rem; - line-height: 1.5rem; - color: var(--mauve11); +.radio { + display: flex; + align-items: center; + column-gap: 8px; + + min-width: 150px; + padding: 10px 16px; + border-radius: 4px; + border: 1px solid var(--line); + background: var(--background-tertiary); + + &[aria-checked="true"] { + border-color: var(--purple); + background: rgba(#6e56cf, 0.05); + } + + label { + flex: 1; + font-size: 0.875rem; + padding-bottom: 4px; + } + + input { + position: relative; + + &:after { + content: ''; + top: -1px; + left: -1px; + width: 15px; + height: 15px; + position: absolute; + border-radius: 100%; + border: 1px solid rgba(#6e56cf, 0.6); + background-color: var(--background-secondary); + transition: border 0.2s; + } + + &:checked:after { + border-width: 5px; + border-color: var(--purple); + } + } } \ No newline at end of file diff --git a/src/components/Form/index.tsx b/src/components/Form/index.tsx index 0f8fc19..0a338fe 100644 --- a/src/components/Form/index.tsx +++ b/src/components/Form/index.tsx @@ -1,6 +1,3 @@ export { ErrorMessage } from './ErrorMessage' -export { Filter } from './Filter' export { Input } from './Input' -export { Radio } from './Radio' -export type { RadioOption } from './Radio' export { RadioGroup } from './RadioGroup' diff --git a/src/components/Layout/Footer/index.tsx b/src/components/Layout/Footer/index.tsx index 18be887..a641220 100644 --- a/src/components/Layout/Footer/index.tsx +++ b/src/components/Layout/Footer/index.tsx @@ -12,7 +12,7 @@ export const LayoutFooter = () => ( rel="noreferrer" href="https://www.leonardovargas.dev/" > - Leo Vargas + Leo Vargas ✌

diff --git a/src/components/Layout/Footer/styles.module.scss b/src/components/Layout/Footer/styles.module.scss index 66802f1..625b214 100644 --- a/src/components/Layout/Footer/styles.module.scss +++ b/src/components/Layout/Footer/styles.module.scss @@ -7,10 +7,7 @@ padding-top: 48px; padding-bottom: 32px; - background: rgba(28, 28, 31, 0.4); - border-top: 1px solid var(--mauve6); - - color: var(--mauve11); + border-top: 1px solid rgba(255, 255, 255, 0.2); div { width: 100%; @@ -19,6 +16,7 @@ display: flex; align-items: center; justify-content: space-between; + padding: 0 24px; span { font-size: 1rem; @@ -31,7 +29,24 @@ a { font-weight: 500; - color: var(--blue10) + color: var(--blue); + } + } + } + + @media(max-width: 650px) { + margin-top: 24px; + padding: 24px 0; + + div { + padding: 0 16px; + + span { + font-size: 0.75rem; + } + + p { + font-size: 0.875rem; } } } diff --git a/src/components/AvatarMenu/index.tsx b/src/components/Layout/Header/AvatarDropdownMenu/index.tsx similarity index 75% rename from src/components/AvatarMenu/index.tsx rename to src/components/Layout/Header/AvatarDropdownMenu/index.tsx index 33c3190..212a384 100644 --- a/src/components/AvatarMenu/index.tsx +++ b/src/components/Layout/Header/AvatarDropdownMenu/index.tsx @@ -5,22 +5,35 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu' import { FaSignOutAlt, FaUser, FaUserAstronaut } from 'react-icons/fa' import styles from './styles.module.scss' -import { User } from 'types' -export const AvatarMenu = () => { - const { data } = useSession() +export const AvatarDropdownMenu = () => { + const { data, status } = useSession() + const user = data?.user - if (!data?.user) { - return <> + console.log('avatar', user) + + if (status !== 'authenticated') { + return ( + + Acessar conta + + ) } - const user = data.user as User + if (!user) { + return <> + } return ( {user?.image ? ( - + {`Imagem ) : ( diff --git a/src/components/AvatarMenu/styles.module.scss b/src/components/Layout/Header/AvatarDropdownMenu/styles.module.scss similarity index 89% rename from src/components/AvatarMenu/styles.module.scss rename to src/components/Layout/Header/AvatarDropdownMenu/styles.module.scss index 67cb744..7d73ad5 100644 --- a/src/components/AvatarMenu/styles.module.scss +++ b/src/components/Layout/Header/AvatarDropdownMenu/styles.module.scss @@ -1,3 +1,11 @@ +.button__signIn { + max-width: 150px; + + @media(max-width: 768px) { + display: none!important; + } +} + .user__avatar { position: relative; @@ -23,7 +31,6 @@ } } - @media(max-width: 768px) { display: none; } @@ -71,7 +78,7 @@ &:hover, &:focus { cursor: pointer; - background: rgba(255, 255, 255, 0.1); + background: var(--line); } } diff --git a/src/components/Layout/Header/index.tsx b/src/components/Layout/Header/index.tsx index c115137..bd70cd0 100644 --- a/src/components/Layout/Header/index.tsx +++ b/src/components/Layout/Header/index.tsx @@ -1,84 +1,77 @@ import Link from 'next/link' import { useState } from 'react' +import classNames from 'classnames' import { useSession, signOut } from 'next-auth/react' import { Logo } from 'components/SVG' -import { AvatarMenu } from 'components/AvatarMenu' +import { AvatarDropdownMenu } from './AvatarDropdownMenu' import styles from './styles.module.scss' export const LayoutHeader = () => { const { status } = useSession() - const [isMobileMenu, setIsMobileMenu] = useState(false) + const [activeMenuMobile, setActiveMenuMobile] = useState(false) const handleSignOut = () => { signOut() - setIsMobileMenu(false) + setActiveMenuMobile(false) } return (
@@ -115,7 +100,6 @@ export const ModalSolutionForm = () => { label="Repositório" name="repository_url" placeholder="Link do repositório (ex: github, gitlab, bitbucket, etc...)" - disabled={disabledInputs} /> { type="url" label="Visualização" placeholder="Link para visualizar o projeto" - disabled={disabledInputs} /> { diff --git a/src/components/Modal/SolutionForm/styles.module.scss b/src/components/Modal/SolutionForm/styles.module.scss index 7707111..7dc3069 100644 --- a/src/components/Modal/SolutionForm/styles.module.scss +++ b/src/components/Modal/SolutionForm/styles.module.scss @@ -16,7 +16,7 @@ width: 100%; max-width: 632px; border-radius: 8px; - background: var(--mauve2); + background: var(--background-secondary); } .header { @@ -24,7 +24,7 @@ align-items: center; justify-content: space-between; - padding: 24px 24px 16px; + padding: 24px; h2 { font-size: 1.125rem; @@ -35,7 +35,7 @@ p { font-size: 1rem; line-height: 1.25rem; - color: var(--mauve11); + color: var(--text-secondary); margin-top: 6px; } @@ -46,13 +46,13 @@ } .main { - padding: 40px 40px 48px; - border-top: 1px solid var(--mauve6); - border-bottom: 1px solid var(--mauve6); + display: flex; + flex-direction: column; + row-gap: 24px; - fieldset + fieldset { - margin-top: 32px; - } + padding: 24px 40px 32px; + border-top: 1px solid var(--line); + border-bottom: 1px solid var(--line); } .footer { @@ -61,22 +61,10 @@ justify-content: flex-end; column-gap: 16px; - padding: 24px 20px; + padding: 18px 24px; button { width: auto; - padding: 12px 16px; - } -} - -.button__trigger { - margin-right: auto; -} - -.button__text { - display: block; - - @media(max-width: 600px) { - display: none; + padding: 10px 32px 12px; } } \ No newline at end of file diff --git a/src/components/SEO/index.tsx b/src/components/SEO/index.tsx index f911786..567b316 100644 --- a/src/components/SEO/index.tsx +++ b/src/components/SEO/index.tsx @@ -16,7 +16,7 @@ export const SEO = ({ }: SEOProps) => { const { asPath } = useRouter() - const titleTab = `BR Challenges | ${tabName || title}` + const titleTab = `BRChallenges • ${tabName || title}` const urlPage = 'https://www.brchallenges.com' + asPath return ( diff --git a/src/components/SolutionCard/components/Footer/index.tsx b/src/components/SolutionCard/components/Footer/index.tsx deleted file mode 100644 index c042494..0000000 --- a/src/components/SolutionCard/components/Footer/index.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { IconGitHub, IconLinkedin } from 'components/SVG' -import type { Solution } from 'types' - -import styles from './styles.module.scss' - -export const SolutionFooter = (solution: Solution) => ( - -) diff --git a/src/components/SolutionCard/components/Footer/styles.module.scss b/src/components/SolutionCard/components/Footer/styles.module.scss deleted file mode 100644 index 01e698e..0000000 --- a/src/components/SolutionCard/components/Footer/styles.module.scss +++ /dev/null @@ -1,62 +0,0 @@ -.solution__footer { - margin-top: auto; - - display: flex; - column-gap: 8px; -} - -.link { - display: flex; - align-items: center; - justify-content: center; - - font-size: 0.875rem; - line-height: 1rem; - font-weight: 500; - - padding: 10px; - border-radius: 6px; - - &.preview { - flex: 1; - background: var(--mauve3); - transition: background .4s; - - &:hover { - background: var(--mauve4); - } - } - - &.social { - background: transparent; - border: 1px solid var(--mauve5); - transition: border .4s; - - svg { - width: 20px; - height: auto; - - path { - fill: var(--mauve11); - transition: fill .4s; - } - } - - &:hover { - border-color: var(--mauve10); - - svg path { - fill: var(--mauve12); - } - } - } - - &[aria-disabled="true"] { - pointer-events: none; - opacity: 0.4; - - svg path { - fill: var(--mauve10); - } - } -} \ No newline at end of file diff --git a/src/components/SolutionCard/components/Header/index.tsx b/src/components/SolutionCard/components/Header/index.tsx deleted file mode 100644 index 7248489..0000000 --- a/src/components/SolutionCard/components/Header/index.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import Image from 'next/image' -import { IconHeart } from 'components/SVG' -import { useSession } from 'next-auth/react' - -import api from 'service/api' -import { useChallenge, useToast } from 'hooks' -import type { Solution, SolutionLevel } from 'types' -import { LEVELS } from 'utils/constants' - -import styles from './styles.module.scss' - -interface SolutionHeartProps extends Solution { - isLike: boolean - onLike: (solutionId: string, level: SolutionLevel) => void -} - -export const SolutionHeart = ({ - onLike, - isLike, - ...solution -}: SolutionHeartProps) => { - const toast = useToast() - const { status } = useSession() - const { status_prismic, id } = useChallenge() - - const isVoting = status_prismic === 'voting' - const isFeatured = solution.status === 'featured' - const enableLikesInSolution = isFeatured && isVoting - const showLikes = isFeatured && status_prismic === 'finished' - - const handleChangeLike = async () => { - if (status === 'authenticated' && isVoting) { - const solution_id = solution._id - - const data = { - solution_id, - level: solution.level, - challenge_id: id - } - - await api.post('/like', data) - onLike(isLike ? '' : solution_id, solution.level) - - return - } - - toast.error('Ops! Tivemos um problema', { - description: 'Você precisa estar autenticado!', - duration: 5000 - }) - } - - return ( -
- {solution.user?.image && ( -
- - - -
- )} - - {showLikes && solution.likes > 0 && ( - - -

- {solution.likes} Like{solution.likes > 1 && 's'} -

-
- )} - - {enableLikesInSolution && ( - - )} -
- ) -} diff --git a/src/components/SolutionCard/components/Header/styles.module.scss b/src/components/SolutionCard/components/Header/styles.module.scss deleted file mode 100644 index 506d9d4..0000000 --- a/src/components/SolutionCard/components/Header/styles.module.scss +++ /dev/null @@ -1,50 +0,0 @@ -.solution__header { - display: flex; - align-items: center; - justify-content: space-between; - - button, .likes { - display: flex; - align-items: center; - column-gap: 8px; - - font-size: 0.875rem; - font-weight: 500; - - padding: 10px 18px; - border-radius: 8px; - background: var(--mauve3); - - svg { - margin-top: 3px; - } - } -} - -.user__image { - display: flex; - - padding: 3px; - border-radius: 100%; - border: 1px solid transparent; - - &[data-type="easy"] { - border-color: var(--green); - } - - &[data-type="medium"] { - border-color: #1194e2; - } - - &[data-type="hard"] { - border-color: var(--red); - } - - picture { - position: relative; - width: 72px; - height: 72px; - border-radius: 100%; - overflow: hidden; - } -} \ No newline at end of file diff --git a/src/components/SolutionCard/index.tsx b/src/components/SolutionCard/index.tsx deleted file mode 100644 index 3fd8c81..0000000 --- a/src/components/SolutionCard/index.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { LEVELS } from 'utils/constants' -import { SolutionHeart } from './components/Header' -import { SolutionFooter } from './components/Footer' -import type { Solution, SolutionLevel } from 'types' - -import styles from './styles.module.scss' - -interface SolutionCardProps { - solution: Solution - isLike: boolean - onLike: (solutionId: string, level: SolutionLevel) => void -} - -export const SolutionCard = ({ - solution, - onLike, - isLike -}: SolutionCardProps) => { - return ( -
  • - - {LEVELS[solution.level]} - - - - -
    - {solution.user?.name} -

    {solution.user?.bio || '-'}

    -
    - - -
  • - ) -} diff --git a/src/components/SolutionCard/styles.module.scss b/src/components/SolutionCard/styles.module.scss deleted file mode 100644 index 45f26a0..0000000 --- a/src/components/SolutionCard/styles.module.scss +++ /dev/null @@ -1,60 +0,0 @@ - -.solution { - display: flex; - flex-direction: column; - align-items: stretch; - row-gap: 16px; - - padding: 40px 20px 20px; - border-radius: 4px 4px 12px 12px; - background: var(--mauve2); - - position: relative; - overflow: hidden; -} - -.user__info { - margin-bottom: 12px; - - strong { - font-size: 1rem; - font-weight: 500; - color: var(--mauve12); - } - - p { - margin-top: 2px; - - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--mauve11); - } -} - -.level { - position: absolute; - top: 0; - left: -10px; - - width: calc(100% + 20px); - - font-weight: 500; - text-align: center; - font-size: 0.75rem; - letter-spacing: 1px; - - padding: 4px 10px; - transform: skew(-5deg); - - &[data-type="easy"] { - background: rgba(44, 182, 125, 0.5); - } - - &[data-type="medium"] { - background: rgba(17, 149, 226, 0.5); - } - - &[data-type="hard"] { - background: rgba(255, 107, 107, 0.5); - } -} \ No newline at end of file diff --git a/src/components/Toast/icons/CheckmarkIcon/index.tsx b/src/components/Toast/icons/CheckmarkIcon/index.tsx deleted file mode 100644 index 9f269e6..0000000 --- a/src/components/Toast/icons/CheckmarkIcon/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import styles from './styles.module.scss' - -export const CheckmarkIcon = () =>
    diff --git a/src/components/Toast/icons/ErrorIcon/index.tsx b/src/components/Toast/icons/Error/index.tsx similarity index 100% rename from src/components/Toast/icons/ErrorIcon/index.tsx rename to src/components/Toast/icons/Error/index.tsx diff --git a/src/components/Toast/icons/ErrorIcon/styles.module.scss b/src/components/Toast/icons/Error/styles.module.scss similarity index 99% rename from src/components/Toast/icons/ErrorIcon/styles.module.scss rename to src/components/Toast/icons/Error/styles.module.scss index d7e26eb..1ee3a12 100644 --- a/src/components/Toast/icons/ErrorIcon/styles.module.scss +++ b/src/components/Toast/icons/Error/styles.module.scss @@ -39,6 +39,7 @@ background-color: var(--red); position: relative; transform: rotate(45deg); + &:before, &:after { content: ''; @@ -50,6 +51,7 @@ height: 2px; width: 12px; } + &:before { transform: rotate(90deg); } diff --git a/src/components/Toast/icons/Success/index.tsx b/src/components/Toast/icons/Success/index.tsx new file mode 100644 index 0000000..d56cf18 --- /dev/null +++ b/src/components/Toast/icons/Success/index.tsx @@ -0,0 +1,3 @@ +import styles from './styles.module.scss' + +export const SuccessIcon = () =>
    diff --git a/src/components/Toast/icons/CheckmarkIcon/styles.module.scss b/src/components/Toast/icons/Success/styles.module.scss similarity index 99% rename from src/components/Toast/icons/CheckmarkIcon/styles.module.scss rename to src/components/Toast/icons/Success/styles.module.scss index bbab1e0..40f9d3c 100644 --- a/src/components/Toast/icons/CheckmarkIcon/styles.module.scss +++ b/src/components/Toast/icons/Success/styles.module.scss @@ -34,6 +34,7 @@ background-color: var(--green); position: relative; transform: rotate(45deg); + &:after { content: ''; box-sizing: border-box; diff --git a/src/components/Toast/icons/index.tsx b/src/components/Toast/icons/index.tsx new file mode 100644 index 0000000..0953652 --- /dev/null +++ b/src/components/Toast/icons/index.tsx @@ -0,0 +1,7 @@ +import { ErrorIcon } from './Error' +import { SuccessIcon } from './Success' + +export default { + success: , + error: +} diff --git a/src/components/Toast/index.tsx b/src/components/Toast/index.tsx index deb0753..0af8ca1 100644 --- a/src/components/Toast/index.tsx +++ b/src/components/Toast/index.tsx @@ -1,13 +1,12 @@ -import React, { useImperativeHandle, useState } from 'react' import { FiX } from 'react-icons/fi' import * as ToastPrimitive from '@radix-ui/react-toast' +import React, { useImperativeHandle, useState } from 'react' -import { CheckmarkIcon } from './icons/CheckmarkIcon' -import { ErrorIcon } from './icons/ErrorIcon' +import toastIcons from './icons' import styles from './styles.module.scss' -type ToastStatus = 'success' | 'error' +type ToastStatus = keyof typeof toastIcons interface ToastOptions { id: string @@ -36,11 +35,6 @@ export const ToastViewport = () => ( ) -const toastIcons = { - success: , - error: -} - export const Toast = React.forwardRef((props, forwardedRef) => { const [toasts, setToasts] = useState>([]) diff --git a/src/components/Toast/styles.module.scss b/src/components/Toast/styles.module.scss index 619f041..8188d70 100644 --- a/src/components/Toast/styles.module.scss +++ b/src/components/Toast/styles.module.scss @@ -1,4 +1,6 @@ .toast__viewport { + --toast-viewport-padding: 25px; + position: fixed; top: 0; right: 0; @@ -82,13 +84,13 @@ .toast__title { grid-area: title; font-weight: 500; - color: var(--text-dark); + color: var(--background-secondary); font-size: 14px; } .toast__description { grid-area: description; - color: var(--text); + color: var(--text-secondary); font-size: 13px; line-height: 1.3; } @@ -104,7 +106,7 @@ width: 16px; height: 16px; border-radius: 9999px; - color: var(--text-dark); + color: var(--background-secondary); background-color: white; box-shadow: rgb(0 0 0 / 16%) 0 0 8px; font-size: 14px; diff --git a/src/components/Tooltip/index.tsx b/src/components/Tooltip/index.tsx deleted file mode 100644 index 976babd..0000000 --- a/src/components/Tooltip/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { - Provider, - Root, - Trigger, - Portal, - Content, - TooltipArrow -} from '@radix-ui/react-tooltip' - -import styles from './styles.module.scss' - -interface TooltipProps { - icon: React.ReactNode - children: React.ReactNode -} - -export const Tooltip = ({ icon, children }: TooltipProps) => ( - - - {icon} - - - - {children} - - - - - -) diff --git a/src/components/Tooltip/styles.module.scss b/src/components/Tooltip/styles.module.scss deleted file mode 100644 index dac1b27..0000000 --- a/src/components/Tooltip/styles.module.scss +++ /dev/null @@ -1,26 +0,0 @@ -.trigger__button { - display: flex; - - border: 0; - background: transparent; - - svg { - margin: auto; - width: 16px; - height: auto; - - color: var(--text); - } -} - -.content { - font-size: 0.75rem; - line-height: 1.125rem; - text-align: center; - - padding: 12px; - border-radius: 6px; - background: var(--text-placeholder); - - max-width: 300px; -} \ No newline at end of file diff --git a/src/components/index.tsx b/src/components/index.tsx new file mode 100644 index 0000000..baef7c0 --- /dev/null +++ b/src/components/index.tsx @@ -0,0 +1,6 @@ +export { Layout } from './Layout' +export * from './Modal' +export { Toast, ToastViewport } from './Toast' +export { SEO } from './SEO' +export { ChallengeCard } from './ChallengeCard' +export { Accordion } from './Accordion' diff --git a/src/utils/data/quetions.json b/src/data/questions.json similarity index 96% rename from src/utils/data/quetions.json rename to src/data/questions.json index e4a95c8..0c7c53a 100644 --- a/src/utils/data/quetions.json +++ b/src/data/questions.json @@ -3,7 +3,7 @@ "id": "question-01", "name": "Os desafios sempre estarão disponíveis?", "description": [ - "Sim, os desafios estarão sempre disponíveis para os usuários. Isso permite que os desenvolvedores possam acessar e completar os desafios a qualquer momento, conforme sua disponibilidade e ritmo de aprendizado" + "Sim, os desafios estarão sempre disponíveis para os usuários. Isso permite que os desenvolvedores possam acessar e completar os desafios a qualquer momento, conforme sua disponibilidade e ritmo de aprendizado." ] }, { @@ -17,7 +17,7 @@ "id": "question-03", "name": "Posso fazer uma publicação no linkedin sobre a solução do desafio?", "description": [ - "Sim, é permitido compartilhar a solução de um desafio em sua rede social, como o LinkedIn. Isso pode ser uma ótima maneira de mostrar suas habilidades para sua rede profissional e ajudá-lo a se destacar no mercado de trabalho", + "Sim, é permitido compartilhar a solução de um desafio em sua rede social, como o LinkedIn. Isso pode ser uma ótima maneira de mostrar suas habilidades para sua rede profissional e ajudá-lo a se destacar no mercado de trabalho.", "Só não se esqueça de colocar o link do autor do protótipo e também incluir o link da página do desafio, assim ajuda a divulgar a plataforma para mais pessoas :)" ] }, @@ -26,7 +26,7 @@ "name": "Escolhi uma dificuldade no envio da soluçao, posso mudar para outra dificuldade?", "description": [ "Sim, é possível mudar de nível de dificuldade mesmo após ter feito o envio na plataforma. Isso permite que você escolha desafios que sejam adequados para o seu nível de habilidade e progrida gradualmente.", - "Se você escolheu um desafio fácil e deseja desafiar-se mais, pode mudar para um nível de dificuldade médio ou difícil" + "Se você escolheu um desafio fácil e deseja desafiar-se mais, pode mudar para um nível de dificuldade médio ou difícil." ] }, { diff --git a/src/hooks/index.tsx b/src/hooks/index.tsx index e88df81..551281c 100644 --- a/src/hooks/index.tsx +++ b/src/hooks/index.tsx @@ -1,2 +1 @@ -export { ChallengeProvider, useChallenge } from './useChallenge' -export { ToastProvider, useToast } from './useToast' +export * from './useToast' diff --git a/src/hooks/useChallenge.tsx b/src/hooks/useChallenge.tsx deleted file mode 100644 index af0ee69..0000000 --- a/src/hooks/useChallenge.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { createContext, useContext } from 'react' - -import { Challenge } from 'types' - -type ChallengeContextData = Challenge - -const ChallengeContext = createContext({} as ChallengeContextData) - -interface ProviderProps { - challenge: Challenge - children: React.ReactNode -} - -export const ChallengeProvider = ({ challenge, children }: ProviderProps) => { - return ( - - {children} - - ) -} - -export const useChallenge = () => { - const context = useContext(ChallengeContext) - - return context -} diff --git a/src/mocks/challenge.json b/src/mocks/challenge.json new file mode 100644 index 0000000..c1448e5 --- /dev/null +++ b/src/mocks/challenge.json @@ -0,0 +1,355 @@ +{ + "id": "empire-burger", + "name": "Empire Burger", + "content": [ + { + "type": "paragraph", + "text": "Esse projeto é uma landing page de uma hamburgueria fictícia chamada Empire Burger, nela há seções sobre as ofertas especiais, horário de funcionamento, cardápio contendo os ingredientes e preços, cards com os feedbacks dos clientes e a localização do estabelecimento. O seu desafio é construir este layout e deixá-lo o mais próximo possível do design.", + "spans": [ + { + "start": 69, + "end": 82, + "type": "strong" + } + ] + }, + { + "type": "paragraph", + "text": "Você pode usar qualquer ferramenta que deseja para ajudá-lo a completar o desafio, não será avaliado o código, mas sim a fidelidade ao layout e a implementação das funcionalidades solicitadas. Então, se você tem algo que gostaria de praticar, sinta-se à vontade para tentar.", + "spans": [] + }, + { + "type": "paragraph", + "text": "Para deixar o desafio mais justo o layout e os critérios de aceite foram separados em três níveis de dificuldade(fácil, médio e difícil). Conforme a dificuldade aumente, você deverá considerar a implementação das dificuldades anteriores, ou seja, se optar pelo dificuldade média, deverá levar em conta todos os critérios da dificuldade fácil e caso prefira a dificuldade difícil, deverá levar em conta todos os critérios da dificuldade fácil e média.", + "spans": [] + }, + { + "type": "paragraph", + "text": "Agora vamos para os critérios de aceite(funcionalidades):", + "spans": [] + }, + { + "type": "heading3", + "text": "Nível Fácil", + "spans": [] + }, + { + "type": "o-list-item", + "text": "Criar as seguintes seções: Menu, Banner hero, Ofertas especiais, Onde fica o nosso castelo, Footer.", + "spans": [ + { + "start": 27, + "end": 98, + "type": "strong" + } + ] + }, + { + "type": "o-list-item", + "text": "Ao clicar em um item do menu, o usuário deverá ser levado para a seção correspondente. (material de apoio)", + "spans": [ + { + "start": 88, + "end": 105, + "type": "hyperlink", + "data": { + "link_type": "Web", + "url": "https://www.w3docs.com/snippets/html/how-to-create-an-anchor-link-to-jump-to-a-specific-part-of-a-page.html", + "target": "_blank" + } + } + ] + }, + { + "type": "o-list-item", + "text": "Na seção Ofertas especiais os elementos devem ser organizados com o uso da propriedade display:grid do css. (material de apoio)", + "spans": [ + { + "start": 9, + "end": 26, + "type": "strong" + }, + { + "start": 87, + "end": 99, + "type": "em" + }, + { + "start": 103, + "end": 108, + "type": "em" + }, + { + "start": 109, + "end": 126, + "type": "hyperlink", + "data": { + "link_type": "Web", + "url": "https://css-tricks.com/snippets/css/complete-guide-grid/", + "target": "_blank" + } + } + ] + }, + { + "type": "o-list-item", + "text": "As informações do card da oferta(nome do prato e gramagem) devem estar no html, a única imagem deve ser a foto do prato com o preço.", + "spans": [] + }, + { + "type": "o-list-item", + "text": "Na seção Onde fica o nosso castelo você deverá incorporar uma localização do google maps. ", + "spans": [ + { + "start": 9, + "end": 34, + "type": "strong" + } + ] + }, + { + "type": "heading3", + "text": "Nível Médio", + "spans": [] + }, + { + "type": "o-list-item", + "text": "Todos os requisitos do nivel fácil.", + "spans": [] + }, + { + "type": "o-list-item", + "text": "Criar as seguintes seções: Cardápio, Atendimento, Nossas entregas.", + "spans": [ + { + "start": 27, + "end": 65, + "type": "strong" + } + ] + }, + { + "type": "o-list-item", + "text": "Na seção Cardápio os preços devem ser formatados com o método Intl.NumberFormat. (material de apoio)", + "spans": [ + { + "start": 9, + "end": 17, + "type": "strong" + }, + { + "start": 62, + "end": 79, + "type": "em" + }, + { + "start": 82, + "end": 99, + "type": "hyperlink", + "data": { + "link_type": "Web", + "url": "https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat", + "target": "_blank" + } + } + ] + }, + { + "type": "o-list-item", + "text": "O card Horário de funcionamento deverá ter os estados aberto e fechado, o estado será alterado conforme o horário do navegador do usuário. (material de apoio)", + "spans": [ + { + "start": 7, + "end": 31, + "type": "strong" + }, + { + "start": 140, + "end": 157, + "type": "hyperlink", + "data": { + "link_type": "Web", + "url": "https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Date", + "target": "_blank" + } + } + ] + }, + { + "type": "heading3", + "text": "Nível Difícil", + "spans": [] + }, + { + "type": "o-list-item", + "text": "Todos os requisitos do nivel fácil e médio", + "spans": [] + }, + { + "type": "o-list-item", + "text": "Criar as seguintes seções: Nossa realeza, Publicações do instagram.", + "spans": [ + { + "start": 27, + "end": 66, + "type": "strong" + } + ] + }, + { + "type": "o-list-item", + "text": "Buscar a lista de itens do cardápio via api. (acessar endpoint)", + "spans": [ + { + "start": 9, + "end": 35, + "type": "strong" + }, + { + "start": 46, + "end": 62, + "type": "hyperlink", + "data": { + "link_type": "Web", + "url": "https://api.brchallenges.com/api/empire-burger/menu", + "target": "_blank" + } + } + ] + }, + { + "type": "o-list-item", + "text": "Buscar a lista de depoimentos via api. (acessar endpoint)", + "spans": [ + { + "start": 9, + "end": 29, + "type": "strong" + }, + { + "start": 40, + "end": 56, + "type": "hyperlink", + "data": { + "link_type": "Web", + "url": "https://api.brchallenges.com/api/empire-burger/testimonials", + "target": "_blank" + } + } + ] + }, + { + "type": "o-list-item", + "text": "Na seção Nossa realeza os depoimentos deverão estar em um carrousel funcional.", + "spans": [ + { + "start": 9, + "end": 22, + "type": "strong" + } + ] + }, + { + "type": "o-list-item", + "text": "O texto de cada depoimento deverá estar limitado em quatro linhas, você pode usar a propriedade clamp do css. (material de apoio)", + "spans": [ + { + "start": 96, + "end": 101, + "type": "em" + }, + { + "start": 105, + "end": 108, + "type": "em" + }, + { + "start": 111, + "end": 128, + "type": "hyperlink", + "data": { + "link_type": "Web", + "url": "https://css-tricks.com/almanac/properties/l/line-clamp/" + } + } + ] + }, + { + "type": "paragraph", + "text": "Foi criado um repositório template no github com os assets e um README de base, clique aqui para acessar.", + "spans": [ + { + "start": 80, + "end": 91, + "type": "hyperlink", + "data": { + "link_type": "Web", + "url": "https://github.com/leovargasdev/br-challenges-empire-burger" + } + } + ] + } + ], + "description": "Esse projeto é uma landing page de uma hamburgueria fictícia chamada Empire Burger, nela há seções sobre as ofertas especiais, horário de funcionamento, cardápio contendo os ingredientes e preços, cards com os feedbacks dos clientes e a localização do estabelecimento. O seu desafio é construir este layout e deixá-lo o mais próximo possível do design.", + "deadline": "2022-10-03", + "image": { + "dimensions": { + "width": 1798, + "height": 635 + }, + "alt": null, + "copyright": null, + "url": "https://images.prismic.io/brchallenges/289d6134-7e60-4c8f-827a-a9514cffe100_Captura+de+tela+de+2023-11-26+16-42-17.png?auto=compress,format" + }, + "prototype_url": "https://www.figma.com/file/ag4Az50adOF53pBrwI0wFg/Empire-Burger?node-id=0%3A1", + "authors": [ + { + "name": "Tiago Alves", + "description": "UI Designer Pleno | Diretor de Criação", + "image": { + "dimensions": { + "width": 150, + "height": 150 + }, + "alt": "Imagem de perfil do Tiago Alves", + "copyright": null, + "url": "https://images.prismic.io/brchallenges/f8c84d81-1260-4deb-a75a-715f18b4e66d_avatar-tiago-alves.png?auto=compress,format" + }, + "links": [ + { + "type": "linkedin", + "link": { + "link_type": "Web", + "url": "https://www.linkedin.com/in/tiago-alves-775992105", + "target": "_blank" + } + }, + { + "type": "behance", + "link": { + "link_type": "Web", + "url": "https://www.behance.net/tiagofenixe9d9", + "target": "_blank" + } + }, + { + "type": "instagram", + "link": { + "link_type": "Web", + "url": "https://www.instagram.com/designer_produtivo_/", + "target": "_blank" + } + }, + { + "type": "website", + "link": { + "link_type": "Web", + "url": "https://designerprodutivo.com.br/link-bio/", + "target": "_blank" + } + } + ] + } + ], + "status_prismic": "finished" +} \ No newline at end of file diff --git a/src/mocks/challenges.json b/src/mocks/challenges.json new file mode 100644 index 0000000..1929cb2 --- /dev/null +++ b/src/mocks/challenges.json @@ -0,0 +1,236 @@ +[ + { + "id": "empire-burger", + "name": "Empire Burger", + "deadline": "2023-10-03", + "image": { + "dimensions": { + "width": 1798, + "height": 635 + }, + "alt": null, + "copyright": null, + "url": "https://images.prismic.io/brchallenges/289d6134-7e60-4c8f-827a-a9514cffe100_Captura+de+tela+de+2023-11-26+16-42-17.png?auto=compress,format" + }, + "authors": [ + { + "name": "Tiago Alves", + "description": "UI Designer Pleno | Diretor de Criação", + "image": { + "dimensions": { + "width": 150, + "height": 150 + }, + "alt": "Imagem de perfil do Tiago Alves", + "copyright": null, + "url": "https://images.prismic.io/brchallenges/f8c84d81-1260-4deb-a75a-715f18b4e66d_avatar-tiago-alves.png?auto=compress,format" + }, + "links": [ + { + "type": "linkedin", + "link": { + "link_type": "Web", + "url": "https://www.linkedin.com/in/tiago-alves-775992105", + "target": "_blank" + } + }, + { + "type": "behance", + "link": { + "link_type": "Web", + "url": "https://www.behance.net/tiagofenixe9d9", + "target": "_blank" + } + }, + { + "type": "instagram", + "link": { + "link_type": "Web", + "url": "https://www.instagram.com/designer_produtivo_/", + "target": "_blank" + } + }, + { + "type": "website", + "link": { + "link_type": "Web", + "url": "https://designerprodutivo.com.br/link-bio/", + "target": "_blank" + } + } + ] + } + ], + "status_prismic": "finished", + "users": [ + "https://lh3.googleusercontent.com/a/ACg8ocL870PI_QW8iFnXmYyOXUR1bKplbUz-wkYO_CCuF-7Y4Fo=s96-c", + "https://lh3.googleusercontent.com/a/ACg8ocJy2DqqChfuQouQ68-HQsFoyPbWJ8oBmVhb07NCBrCecg=s96-c", + "https://avatars.githubusercontent.com/u/143128765?v=4", + "https://avatars.githubusercontent.com/u/39505479?v=4", + "https://avatars.githubusercontent.com/u/108540280?v=4", + "https://lh3.googleusercontent.com/a/ACg8ocJ2msLvoWIc4059zPJM6c5r51s0GvuuOVjCN_-kBx9rTA=s96-c", + "https://avatars.githubusercontent.com/u/70710358?v=4", + "https://avatars.githubusercontent.com/u/99447663?v=4", + "https://avatars.githubusercontent.com/u/125400024?v=4", + "https://avatars.githubusercontent.com/u/53182026?v=4", + "https://lh3.googleusercontent.com/a/ACg8ocJ36QDq6rFJ8oM7KjrTn5dTXS32N4nhd7gBN9ZXMlPuPL0=s96-c", + "https://avatars.githubusercontent.com/u/47865001?v=4", + "https://avatars.githubusercontent.com/u/127137438?v=4" + ], + "participants": 140 + }, + { + "id": "blizzard", + "name": "Blizzard", + "deadline": "2023-01-01", + "image": { + "dimensions": { + "width": 1517, + "height": 775 + }, + "alt": null, + "copyright": null, + "url": "https://images.prismic.io/brchallenges/4ec42e3a-d334-4f2a-9c37-29706d13ce88_banner-desafio-blizzard.png?auto=compress,format" + }, + "authors": [ + { + "name": "Gilberto Prado", + "description": "Fundador da Insany Design | Senior UI Designer | Criador do uiBoost", + "image": { + "dimensions": { + "width": 800, + "height": 800 + }, + "alt": "Foto de perfil do Gilberto Prado", + "copyright": null, + "url": "https://images.prismic.io/brchallenges/b66932af-7840-4565-bc4f-a65f625cd055_1668693154594.jpeg?auto=compress,format" + }, + "links": [ + { + "type": "linkedin", + "link": { + "link_type": "Web", + "url": "https://www.linkedin.com/in/gilberto-insanydesign/" + } + }, + { + "type": "behance", + "link": { + "link_type": "Web", + "url": "https://www.behance.net/insanydesign" + } + }, + { + "type": "dribbble", + "link": { + "link_type": "Web", + "url": "https://dribbble.com/insanyDesign" + } + }, + { + "type": "instagram", + "link": { + "link_type": "Web", + "url": "https://www.instagram.com/insanydesign/" + } + }, + { + "type": "website", + "link": { + "link_type": "Web", + "url": "https://insany.design/" + } + } + ] + } + ], + "status_prismic": "finished", + "users": [ + "https://lh3.googleusercontent.com/a/ACg8ocL870PI_QW8iFnXmYyOXUR1bKplbUz-wkYO_CCuF-7Y4Fo=s96-c", + "https://avatars.githubusercontent.com/u/70710358?v=4", + "https://lh3.googleusercontent.com/a/ACg8ocJ36QDq6rFJ8oM7KjrTn5dTXS32N4nhd7gBN9ZXMlPuPL0=s96-c", + "https://avatars.githubusercontent.com/u/47865001?v=4", + "https://avatars.githubusercontent.com/u/37486496?v=4", + "https://avatars.githubusercontent.com/u/119149285?v=4", + "https://avatars.githubusercontent.com/u/98669788?v=4", + "https://lh3.googleusercontent.com/a/ACg8ocKc08p-8s7KH5Z3bbABeaQSAZj8tm7IzogWfhzReyNapLg=s96-c" + ], + "participants": 126 + }, + { + "id": "paqueta-calcados", + "name": "Paquetá Calçados", + "deadline": "2023-07-02", + "image": { + "dimensions": { + "width": 1872, + "height": 961 + }, + "alt": null, + "copyright": null, + "url": "https://images.prismic.io/brchallenges/55b4b298-c3ee-4386-8cd0-5059aec46a8a_capa-desafio-paqueta-calcados.png?auto=compress,format" + }, + "authors": [ + { + "name": "Ana Maria Almeida", + "description": "UI Design", + "image": { + "dimensions": { + "width": 325, + "height": 332 + }, + "alt": null, + "copyright": null, + "url": "https://images.prismic.io/brchallenges/65e83002-4e95-4718-b586-55d40f3ec0b5_Image.png?auto=compress,format" + }, + "links": [ + { + "type": "linkedin", + "link": { + "link_type": "Web", + "url": "https://www.linkedin.com/in/anamariawca/", + "target": "_blank" + } + }, + { + "type": "behance", + "link": { + "link_type": "Web", + "url": "https://www.behance.net/anamariawca" + } + }, + { + "type": "instagram", + "link": { + "link_type": "Web", + "url": "https://www.instagram.com/anamariawca/", + "target": "_blank" + } + }, + { + "type": "dribbble", + "link": { + "link_type": "Web", + "url": "https://dribbble.com/anamariawca", + "target": "_blank" + } + } + ] + } + ], + "status_prismic": "finished", + "users": [ + "https://avatars.githubusercontent.com/u/149005855?v=4", + "https://avatars.githubusercontent.com/u/93059906?v=4", + "https://avatars.githubusercontent.com/u/39505479?v=4", + "https://lh3.googleusercontent.com/a/ACg8ocJ2msLvoWIc4059zPJM6c5r51s0GvuuOVjCN_-kBx9rTA=s96-c", + "https://avatars.githubusercontent.com/u/149213668?v=4", + "https://lh3.googleusercontent.com/a/ACg8ocJ36QDq6rFJ8oM7KjrTn5dTXS32N4nhd7gBN9ZXMlPuPL0=s96-c", + "https://avatars.githubusercontent.com/u/115591322?v=4", + "https://avatars.githubusercontent.com/u/83200247?v=4", + "https://avatars.githubusercontent.com/u/98669788?v=4", + "https://lh3.googleusercontent.com/a/ACg8ocL6EEZTnLiMmBYEwTHdgI3VSJep2ECbaK4RLakz73_ZXZo=s96-c" + ], + "participants": 44 + } +] \ No newline at end of file diff --git a/src/pages/_app.tsx b/src/pages/_app.page.tsx similarity index 92% rename from src/pages/_app.tsx rename to src/pages/_app.page.tsx index 06288b3..c4900c6 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.page.tsx @@ -2,8 +2,8 @@ import type { AppProps } from 'next/app' import { PrismicPreview } from '@prismicio/next' import { SessionProvider } from 'next-auth/react' +import { Layout } from 'components' import { ToastProvider } from 'hooks' -import { Layout } from 'components/Layout' import 'styles/globals.scss' diff --git a/src/pages/_document.tsx b/src/pages/_document.page.tsx similarity index 94% rename from src/pages/_document.tsx rename to src/pages/_document.page.tsx index fc1bd51..22f2153 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.page.tsx @@ -21,7 +21,7 @@ export default class MyDocument extends Document { /> diff --git a/src/pages/api/auth/[...nextauth].tsx b/src/pages/api/auth/[...nextauth].ts similarity index 100% rename from src/pages/api/auth/[...nextauth].tsx rename to src/pages/api/auth/[...nextauth].ts diff --git a/src/pages/api/challenge/[id]/index.ts b/src/pages/api/challenge/[id]/index.ts deleted file mode 100644 index 8f7d4fe..0000000 --- a/src/pages/api/challenge/[id]/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { NextApiResponse, NextApiRequest } from 'next' - -import { formattedChallenge } from 'utils/format' -import { createClientPrismic } from 'service/prismic' - -export default async (req: NextApiRequest, res: NextApiResponse) => { - const challenge_id = req.query.id as string - - try { - const prismic = createClientPrismic({ req }) - - const response = await prismic.getByUID('challenges', challenge_id) - const challenge = formattedChallenge(response) - - return res.status(200).json(challenge) - } catch (err) { - console.log(err) - } - - return res.status(200).json({ error: true }) -} diff --git a/src/pages/api/challenge/[id]/participants.ts b/src/pages/api/challenge/[id]/participants.ts deleted file mode 100644 index 102d9cd..0000000 --- a/src/pages/api/challenge/[id]/participants.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { getSession } from 'next-auth/react' -import type { NextApiResponse, NextApiRequest } from 'next' - -import type { Like, Solution } from 'types' -import { connectMongoose, LikeModel, SolutionModel } from 'service/mongoose' - -export default async (req: NextApiRequest, res: NextApiResponse) => { - if (req.method !== 'GET') { - res.setHeader('Allow', 'GET') - res.status(405).end('Method not allowed') - return - } - - try { - const session = await getSession({ req }) - const user_id = session?.user._id - const challenge_id = req.query.id - - await connectMongoose() - - const solutions: Solution[] = await SolutionModel.aggregate([ - { $match: { status: { $ne: 'unpublish' }, challenge_id } }, - { - $lookup: { - from: 'users', - localField: 'user_id', - foreignField: '_id', - as: 'user', - pipeline: [{ $project: { createdAt: 0, updatedAt: 0, _id: 0 } }] - } - }, - { $unwind: '$user' }, - { $project: { user_id: 0, createdAt: 0, updatedAt: 0 } }, - { $sort: { likes: -1 } } - ]) - - const userLikes = { easy: '', medium: '', hard: '' } - - if (user_id) { - const isUserLikes: Like[] = await LikeModel.find({ - user_id, - challenge_id - }) - - if (isUserLikes) { - isUserLikes.forEach(like => { - userLikes[like.level] = like.solution_id.toString() - }) - } - } - - return res.status(200).json({ - userLikes, - solutions: solutions.map(solution => ({ - ...solution, - _id: String(solution._id) - })) - }) - } catch (error) { - console.log(error) - } - - return res.status(500).send('Internal Server Error') -} diff --git a/src/pages/api/challenge/[id]/participate.ts b/src/pages/api/challenge/[id]/participate.ts deleted file mode 100644 index 92b87e5..0000000 --- a/src/pages/api/challenge/[id]/participate.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { ZodError } from 'zod' -import { getSession } from 'next-auth/react' -import type { NextApiResponse, NextApiRequest } from 'next' - -import { ChallengeModel, connectMongoose, UserModel } from 'service/mongoose' - -export default async (req: NextApiRequest, res: NextApiResponse) => { - if (req.method !== 'POST') { - res.setHeader('Allow', 'POST') - res.status(405).end('Method not allowed') - return - } - - try { - const session = await getSession({ req }) - - if (!session?.user) { - return res.status(401).send('Unauthorized') - } - - const { _id: user_id, challenges } = session.user - - await connectMongoose() - const challenge_id = req.query.id as string - - const isParticipate = challenges.includes(challenge_id) - - if (isParticipate) { - return res.status(204).json({ message: 'O usuário já está participando' }) - } - - await UserModel.updateOne( - { _id: user_id }, - { $push: { challenges: challenge_id } } - ) - - await ChallengeModel.updateOne( - { challenge_id }, - { $inc: { participants: 1 } } - ) - - return res.status(201).json({ updated: true }) - } catch (error) { - if (error instanceof ZodError) { - return res.status(422).json(error.flatten().fieldErrors) - } - - return res.status(500).send('Internal Server Error') - } -} diff --git a/src/pages/api/challenge/[id]/solution.ts b/src/pages/api/challenge/[id]/solution.ts index 0444d31..ce68ed2 100644 --- a/src/pages/api/challenge/[id]/solution.ts +++ b/src/pages/api/challenge/[id]/solution.ts @@ -3,7 +3,7 @@ import { getSession } from 'next-auth/react' import type { NextApiResponse, NextApiRequest } from 'next' import { zodSolutionSchema } from 'utils/zod' -import { connectMongoose, SolutionModel } from 'service/mongoose' +// import { connectMongoose, SolutionModel } from 'service/mongoose' export default async (req: NextApiRequest, res: NextApiResponse) => { const isRedirect = !['POST', 'GET'].includes(req.method || '') @@ -16,30 +16,32 @@ export default async (req: NextApiRequest, res: NextApiResponse) => { const session = await getSession({ req }) - if (!session?.user) { - return res.status(401).send('Unauthorized') - } + console.log('session', session) + + // if (!session?.user) { + // return res.status(401).send('Unauthorized') + // } - const user_id = session.user._id - const challenge_id = req.query.id as string + // const user_id = session.user._id + // const challenge_id = req.query.id as string - await connectMongoose() + // await connectMongoose() if (req.method === 'POST') { try { const solution = zodSolutionSchema.parse(req.body) + console.log(solution) + // const isSolution = await SolutionModel.findOneAndUpdate( + // { user_id, challenge_id }, + // solution + // ) - const isSolution = await SolutionModel.findOneAndUpdate( - { user_id, challenge_id }, - solution - ) + // // Primeiro envio + // if (!isSolution) { + // await SolutionModel.create({ user_id, challenge_id, ...solution }) + // } - // Primeiro envio - if (!isSolution) { - await SolutionModel.create({ user_id, challenge_id, ...solution }) - } - - return res.status(200).json({ type: isSolution ? 'update' : 'create' }) + return res.status(200).send('') } catch (error) { if (error instanceof ZodError) { return res.status(422).json(error.flatten().fieldErrors) @@ -48,10 +50,17 @@ export default async (req: NextApiRequest, res: NextApiResponse) => { return res.status(500).send('Internal Server Error') } } - await connectMongoose() - const queryMongo = { user_id, challenge_id } - const ignoreFields = { createdAt: 0, updatedAt: 0, _id: 0, user_id: 0 } - const solution = await SolutionModel.findOne(queryMongo, ignoreFields) - return res.status(200).json(solution) + // await connectMongoose() + // const queryMongo = { user_id, challenge_id } + // const ignoreFields = { createdAt: 0, updatedAt: 0, _id: 0, user_id: 0 } + // const solution = await SolutionModel.findOne(queryMongo, ignoreFields) + + // return res.status(200).json(solution) + return res.status(200).json({ + repository_url: 'http://localhost:3000/desafio/paqueta-calcados', + linkedin_url: '', + url: 'http://aaa.com.br', + level: 'medium' + }) } diff --git a/src/pages/api/like.ts b/src/pages/api/like.ts deleted file mode 100644 index cc5472a..0000000 --- a/src/pages/api/like.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { getSession } from 'next-auth/react' -import type { NextApiResponse, NextApiRequest } from 'next' - -import { connectMongoose, LikeModel, SolutionModel } from 'service/mongoose' - -export default async (req: NextApiRequest, res: NextApiResponse) => { - if (req.method !== 'POST') { - res.setHeader('Allow', 'POST') - res.status(405).end('Method not allowed') - return - } - - try { - const session = await getSession({ req }) - - if (!session?.user) { - return res.status(401).send('Unauthorized') - } - - const { solution_id, challenge_id, level } = req.body - const user_id = session.user._id - - await connectMongoose() - - const isLike = await LikeModel.findOneAndUpdate( - { user_id, challenge_id, level }, - { solution_id } - ) - - if (!isLike) { - // Add like do usuário desse desafio e level - await LikeModel.create({ user_id, challenge_id, solution_id, level }) - - // Add um novo like a solução - await SolutionModel.updateOne( - { _id: solution_id }, - { $inc: { likes: 1 } } - ) - } else { - const newLike = solution_id - const oldLike = isLike.solution_id.toString() - - // Remove like da solução - await SolutionModel.updateOne({ _id: oldLike }, { $inc: { likes: -1 } }) - - // Limpa o like do usuário desse desafio e level - if (newLike === oldLike) { - await LikeModel.remove(isLike._id) - } - - // Add um novo like a solução - if (newLike !== oldLike) { - await SolutionModel.updateOne({ _id: newLike }, { $inc: { likes: 1 } }) - } - } - - return res.status(200).json({ ok: true }) - } catch (err) { - console.log(err) - - return res.status(500).send('Internal Server Error') - } -} diff --git a/src/pages/api/user/update.ts b/src/pages/api/user/update.ts deleted file mode 100644 index 71759cb..0000000 --- a/src/pages/api/user/update.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ZodError } from 'zod' -import { getSession } from 'next-auth/react' -import type { NextApiResponse, NextApiRequest } from 'next' -import { connectMongoose, UserModel } from 'service/mongoose' - -export default async (req: NextApiRequest, res: NextApiResponse) => { - if (req.method !== 'POST') { - res.setHeader('Allow', 'POST') - res.status(405).end('Method not allowed') - return - } - - try { - const session = await getSession({ req }) - - if (!session?.user) { - return res.status(401).send('Unauthorized') - } - - await connectMongoose() - - await UserModel.updateOne({ _id: session.user._id }, { $set: req.body }) - - return res.status(201).json({ update: true }) - } catch (error) { - if (error instanceof ZodError) { - return res.status(422).json(error.flatten().fieldErrors) - } - - return res.status(500).send('Internal Server Error') - } -} diff --git a/src/pages/desafio/[slug]/_components/Aside/index.tsx b/src/pages/desafio/[slug]/_components/Aside/index.tsx new file mode 100644 index 0000000..42a1f15 --- /dev/null +++ b/src/pages/desafio/[slug]/_components/Aside/index.tsx @@ -0,0 +1,47 @@ +import Link from 'next/link' +import { RxPerson, RxPlus } from 'react-icons/rx' +import { IoTicketSharp } from 'react-icons/io5' + +import { SocialShare } from '../SocialShare' +import { ModalSolutionForm } from 'components' +import type { Challenge } from 'types/challenge' + +import styles from './styles.module.scss' + +export const Aside = (challenge: Challenge) => { + const isParticipant = true + + return ( + + ) +} diff --git a/src/pages/desafio/[slug]/_components/Aside/styles.module.scss b/src/pages/desafio/[slug]/_components/Aside/styles.module.scss new file mode 100644 index 0000000..a1bdf28 --- /dev/null +++ b/src/pages/desafio/[slug]/_components/Aside/styles.module.scss @@ -0,0 +1,71 @@ +.container { + display: flex; + flex-direction: column; + row-gap: 24px; + + > button, > a { + font-size: 1rem; + padding: 12px 0; + } + + @media(max-width: 768px) { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px 24px; + + margin-bottom: 12px; + } + + @media(max-width: 650px) { + gap: 12px; + + > button, > a { + font-size: 0.75rem; + padding: 10px 0; + } + } +} + +.participant { + display: flex; + flex-direction: column; + row-gap: 8px; +} + +.participant__button { + display: flex; + align-items: center; + justify-content: center; + column-gap: 12px; + + + border-radius: 6px; + overflow: hidden; + border: 1px solid var(--line); + + > span { + display: flex; + background: var(--blue); + + width: 38px; + height: 38px; + + svg { + margin: auto; + } + } + + strong { + flex: 1; + padding-bottom: 4px; + + font-size: 1rem; + font-weight: 700; + letter-spacing: 1px; + font-family: var(--family-bold); + } + + @media(max-width: 768px) { + display: none; + } +} \ No newline at end of file diff --git a/src/components/Challenge/AuthorCard/index.tsx b/src/pages/desafio/[slug]/_components/CardAuthor/index.tsx similarity index 87% rename from src/components/Challenge/AuthorCard/index.tsx rename to src/pages/desafio/[slug]/_components/CardAuthor/index.tsx index 37bba44..f3ed4a5 100644 --- a/src/components/Challenge/AuthorCard/index.tsx +++ b/src/pages/desafio/[slug]/_components/CardAuthor/index.tsx @@ -1,5 +1,5 @@ -import { PrismicLink } from '@prismicio/react' import { ImBehance2 } from 'react-icons/im' +import { PrismicLink } from '@prismicio/react' import { FaLinkedin, FaFigma, @@ -9,7 +9,8 @@ import { } from 'react-icons/fa' import { PrismicNextImage } from '@prismicio/next' -import { Author } from 'types/author' +import type { Author } from 'types/author' + import styles from './styles.module.scss' const socials = { @@ -21,10 +22,10 @@ const socials = { website: { icon: , label: 'Website' } } -export const AuthorCard = (author: Author) => ( +export const CardAuthor = (author: Author) => (
    - +
    diff --git a/src/components/Challenge/AuthorCard/styles.module.scss b/src/pages/desafio/[slug]/_components/CardAuthor/styles.module.scss similarity index 75% rename from src/components/Challenge/AuthorCard/styles.module.scss rename to src/pages/desafio/[slug]/_components/CardAuthor/styles.module.scss index c2602e0..77fa075 100644 --- a/src/components/Challenge/AuthorCard/styles.module.scss +++ b/src/pages/desafio/[slug]/_components/CardAuthor/styles.module.scss @@ -1,6 +1,4 @@ .author { - margin-top: 32px; - display: grid; align-items: center; justify-items: start; @@ -10,12 +8,7 @@ width: 100%; padding: 16px 24px; border-radius: 8px; - - background-size: cover; - background-position: center; - background-repeat: no-repeat; - background: url('/background-author-card.png'), var(--mauve2); - + background: var(--background-secondary); @media (max-width: 600px) { display: flex; @@ -33,6 +26,12 @@ position: relative; overflow: hidden; + img { + object-fit: cover; + width: 100%; + height: 100%; + } + @media (max-width: 640px) { width: 100px; height: 100px; @@ -52,14 +51,14 @@ p { font-size: 1rem; line-height: 1.25rem; - color: var(--mauve11); + color: var(--text-secondary); } hr { margin: 8px 0 16px; border: 0; - border-bottom: 1px solid var(--mauve6); + border-bottom: 1px solid var(--line); } } @@ -85,22 +84,24 @@ padding: 8px 18px; border-radius: 8px; - background: var(--mauve3); - border: 1px solid var(--mauve7); + background: transparent; + border: 1px solid var(--text-secondary); font-weight: 500; font-size: 0.875rem; text-transform: capitalize; - transition: background 0.4s; + opacity: 0.7; + + transition: all 0.4s; svg { flex-shrink: 0; } - } - &:hover a { - background: var(--mauve4); + &:hover { + opacity: 1; + } } } } diff --git a/src/pages/desafio/[slug]/_components/ChallengeInfo/index.tsx b/src/pages/desafio/[slug]/_components/ChallengeInfo/index.tsx new file mode 100644 index 0000000..94f8507 --- /dev/null +++ b/src/pages/desafio/[slug]/_components/ChallengeInfo/index.tsx @@ -0,0 +1,54 @@ +import { RxCalendar } from 'react-icons/rx' +import { PrismicRichText } from '@prismicio/react' + +import { formatDate } from 'utils/format' +import { CardAuthor } from '../CardAuthor' +import type { Challenge } from 'types/challenge' + +import styles from './styles.module.scss' + +export const ChallengeInfo = (challenge: Challenge) => ( +
    +
    +

    {challenge.name}

    +
    + +

    + Publicado em{' '} + +

    +
    +
    + +
    +

    Autor do layout

    + + {challenge.authors.map(author => author.name).join(',')} + +
    + +
    +

    Descrição

    +
    + +
    +
    + +
    +

    Preview do layout

    +