├── .babelrc
├── .env
├── .eslintrc.json
├── .gitignore
├── Imagens
├── WebAccessibility.png
├── autor1.jpg
├── autor2.jpg
├── codigo.jpg
├── logo.svg
└── react.jpg
├── README.md
├── jsconfig.json
├── next.config.js
├── package-lock.json
├── package.json
├── pages
├── 404.jsx
├── _app.jsx
├── _document.jsx
├── index.jsx
└── post
│ └── [slug].jsx
├── public
├── favicon.ico
├── logo.svg
└── vercel.svg
├── schemas
├── author.js
├── blockContent.js
├── index.js
└── post.js
├── src
├── Components
│ ├── Bar
│ │ ├── Bar.jsx
│ │ └── Bar.module.scss
│ ├── Cardpost
│ │ ├── Cardpost.jsx
│ │ └── Cardpost.module.scss
│ ├── Footer
│ │ ├── Footer.jsx
│ │ └── Footer.module.scss
│ └── Heading
│ │ ├── Heading.jsx
│ │ └── Heading.module.scss
├── Layout
│ └── MainLayout.jsx
├── UI
│ ├── Author
│ │ ├── Author.jsx
│ │ └── Author.module.scss
│ ├── Error
│ │ ├── Error.jsx
│ │ └── Error.module.scss
│ └── Logo
│ │ └── Logo.jsx
└── sanity.js
└── styles
├── Custom404.module.scss
├── Home.module.scss
├── Post.module.scss
├── globals.scss
├── mixins.scss
└── variables.scss
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"],
3 | "plugins": []
4 | }
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_PROJECT_ID='3tmy3ohm'
2 | NEXT_PUBLIC_DATASET='production'
3 | NEXT_PUBLIC_API_VERSION='2022-12-14'
4 | NEXT_PUBLIC_USE_CDN=false
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next/babel","next/core-web-vitals"]
3 | }
--------------------------------------------------------------------------------
/.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 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/Imagens/WebAccessibility.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canaldofront/minimal-blog/bbed3b3044975ea1b5e4b34b23e40f9756d2cbc4/Imagens/WebAccessibility.png
--------------------------------------------------------------------------------
/Imagens/autor1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canaldofront/minimal-blog/bbed3b3044975ea1b5e4b34b23e40f9756d2cbc4/Imagens/autor1.jpg
--------------------------------------------------------------------------------
/Imagens/autor2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canaldofront/minimal-blog/bbed3b3044975ea1b5e4b34b23e40f9756d2cbc4/Imagens/autor2.jpg
--------------------------------------------------------------------------------
/Imagens/codigo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canaldofront/minimal-blog/bbed3b3044975ea1b5e4b34b23e40f9756d2cbc4/Imagens/codigo.jpg
--------------------------------------------------------------------------------
/Imagens/logo.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/Imagens/react.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canaldofront/minimal-blog/bbed3b3044975ea1b5e4b34b23e40f9756d2cbc4/Imagens/react.jpg
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | ```
12 |
13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
14 |
15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
16 |
17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
18 |
19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
20 |
21 | ## Learn More
22 |
23 | To learn more about Next.js, take a look at the following resources:
24 |
25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
27 |
28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
29 |
30 | ## Deploy on Vercel
31 |
32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
33 |
34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
35 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "src/*": ["src/*"],
6 | "styles/*": ["styles/*"],
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | images: {
5 | domains: ['cdn.sanity.io'],
6 | },
7 | };
8 |
9 | module.exports = nextConfig;
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "minimal-blog",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@portabletext/react": "^2.0.0",
13 | "eslint": "8.29.0",
14 | "eslint-config-next": "13.0.6",
15 | "next": "13.0.6",
16 | "next-sanity": "^3.1.3",
17 | "next-sanity-image": "^5.0.0",
18 | "react": "18.2.0",
19 | "react-dom": "18.2.0",
20 | "react-icons": "^4.7.1"
21 | },
22 | "devDependencies": {
23 | "sass": "^1.56.1"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/pages/404.jsx:
--------------------------------------------------------------------------------
1 | import Error from 'src/UI/Error/Error';
2 | import styles from 'styles/Custom404.module.scss';
3 |
4 | const Custom404 = () => {
5 | return (
6 |
7 | Oops!! Error 404
8 |
9 |
10 | );
11 | };
12 |
13 | export default Custom404;
14 |
--------------------------------------------------------------------------------
/pages/_app.jsx:
--------------------------------------------------------------------------------
1 | import MainLayout from 'src/Layout/MainLayout';
2 | import '../styles/globals.scss';
3 |
4 | function MyApp({ Component, pageProps }) {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
12 | export default MyApp;
13 |
--------------------------------------------------------------------------------
/pages/_document.jsx:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from 'next/document';
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/pages/index.jsx:
--------------------------------------------------------------------------------
1 | import Cardpost from 'src/Components/Cardpost/Cardpost';
2 | import styles from 'styles/Home.module.scss';
3 | import client from 'src/sanity';
4 | import { useRouter } from 'next/router';
5 | import Error from 'src/UI/Error/Error';
6 |
7 | export default function Home({ posts, author }) {
8 | const router = useRouter();
9 |
10 | // Filter Posts
11 | const searchFor = router.query.search || null;
12 | const foundPosts =
13 | posts?.filter((post) => post.title.toLowerCase().includes(searchFor)) || [];
14 |
15 | // Pagination
16 | const numberOfPosts = searchFor === null ? posts.length : foundPosts.length;
17 |
18 | const postsPerPage = 6;
19 | const lastPage = Math.ceil(numberOfPosts / postsPerPage);
20 |
21 | let currentPage = +router.query.page || 1;
22 |
23 | if (currentPage < 1) currentPage = 1;
24 | if (currentPage > lastPage) currentPage = lastPage;
25 |
26 | const inicialIndex = postsPerPage * (currentPage - 1);
27 | const finalIndex = postsPerPage * currentPage;
28 |
29 | const disableLeftButton = currentPage <= 1;
30 | const disableRightButton = currentPage >= lastPage;
31 | const disablePagination = numberOfPosts <= postsPerPage;
32 |
33 | // Render Posts
34 | const currentPostList = searchFor === null ? posts : foundPosts;
35 |
36 | const renderPosts = currentPostList
37 | .sort((a, b) => new Date(b.publishedAt) - new Date(a.publishedAt))
38 | .slice(inicialIndex, finalIndex)
39 | .map((post) => );
40 |
41 | return (
42 |
43 | Últimos artigos do Minimal Blog
44 | {renderPosts}
45 | {searchFor !== null && foundPosts.length === 0 && (
46 |
47 | )}
48 | {!disablePagination && (
49 |
50 |
56 |
57 | {currentPage} / {lastPage}
58 |
59 |
65 |
66 | )}
67 |
68 | );
69 | }
70 |
71 | export const getStaticProps = async () => {
72 | const posts = await client.fetch(`*[_type == "post"]`);
73 | const author = await client.fetch(`*[_type == "author"]`);
74 |
75 | return {
76 | props: {
77 | posts,
78 | author,
79 | },
80 | };
81 | };
82 |
--------------------------------------------------------------------------------
/pages/post/[slug].jsx:
--------------------------------------------------------------------------------
1 | import client from 'src/sanity';
2 | import Author from 'src/UI/Author/Author';
3 | import styles from 'styles/Post.module.scss';
4 | import Image from 'next/image';
5 | import { useNextSanityImage } from 'next-sanity-image';
6 | import { PortableText } from '@portabletext/react';
7 |
8 | const Post = ({ post, author }) => {
9 | const {
10 | title,
11 | subtitle,
12 | mainImage,
13 | body,
14 | author: authorRef,
15 | publishedAt,
16 | } = post || {};
17 |
18 | const getAuthor = author?.find((author) => author._id === authorRef._ref);
19 | const imageProps = useNextSanityImage(client, mainImage);
20 |
21 | const ptComponents = {
22 | types: {
23 | image: ({ value }) => {
24 | if (!value.asset._ref) return null;
25 | return (
26 |
31 | );
32 | },
33 | },
34 | };
35 |
36 | return (
37 |
38 |
39 |
{title}
40 |
{subtitle}
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | );
51 | };
52 |
53 | export default Post;
54 |
55 | export const getStaticPaths = async () => {
56 | const query = `*[_type == "post" && defined(slug.current)[]].slug.current`;
57 | const paths = await client.fetch(query);
58 |
59 | return {
60 | paths: paths.map((slug) => ({ params: { slug } })),
61 | fallback: true,
62 | };
63 | };
64 |
65 | export const getStaticProps = async (context) => {
66 | const { slug = '' } = context.params;
67 |
68 | const query = `*[_type == "post" && slug.current == "${slug}"][0]`;
69 | const post = await client.fetch(query);
70 |
71 | const author = await client.fetch(`*[_type == "author"]`);
72 |
73 | return {
74 | props: {
75 | post,
76 | author,
77 | },
78 | };
79 | };
80 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canaldofront/minimal-blog/bbed3b3044975ea1b5e4b34b23e40f9756d2cbc4/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/schemas/author.js:
--------------------------------------------------------------------------------
1 | import {defineField, defineType} from 'sanity'
2 |
3 | export default defineType({
4 | name: 'author',
5 | title: 'Author',
6 | type: 'document',
7 | fields: [
8 | defineField({
9 | name: 'name',
10 | title: 'Name',
11 | type: 'string',
12 | }),
13 | defineField({
14 | name: 'image',
15 | title: 'Image',
16 | type: 'image',
17 | options: {
18 | hotspot: true,
19 | },
20 | }),
21 | ],
22 | })
23 |
--------------------------------------------------------------------------------
/schemas/blockContent.js:
--------------------------------------------------------------------------------
1 | import {defineType, defineArrayMember} from 'sanity'
2 |
3 | /**
4 | * This is the schema definition for the rich text fields used for
5 | * for this blog studio. When you import it in schemas.js it can be
6 | * reused in other parts of the studio with:
7 | * {
8 | * name: 'someName',
9 | * title: 'Some title',
10 | * type: 'blockContent'
11 | * }
12 | */
13 | export default defineType({
14 | title: 'Block Content',
15 | name: 'blockContent',
16 | type: 'array',
17 | of: [
18 | defineArrayMember({
19 | title: 'Block',
20 | type: 'block',
21 | // Styles let you set what your user can mark up blocks with. These
22 | // correspond with HTML tags, but you can set any title or value
23 | // you want and decide how you want to deal with it where you want to
24 | // use your content.
25 | styles: [
26 | {title: 'Normal', value: 'normal'},
27 | {title: 'H1', value: 'h1'},
28 | {title: 'H2', value: 'h2'},
29 | {title: 'H3', value: 'h3'},
30 | {title: 'H4', value: 'h4'},
31 | {title: 'Quote', value: 'blockquote'},
32 | ],
33 | lists: [{title: 'Bullet', value: 'bullet'}],
34 | // Marks let you mark up inline text in the block editor.
35 | marks: {
36 | // Decorators usually describe a single property – e.g. a typographic
37 | // preference or highlighting by editors.
38 | decorators: [
39 | {title: 'Strong', value: 'strong'},
40 | {title: 'Emphasis', value: 'em'},
41 | ],
42 | // Annotations can be any object structure – e.g. a link or a footnote.
43 | annotations: [
44 | {
45 | title: 'URL',
46 | name: 'link',
47 | type: 'object',
48 | fields: [
49 | {
50 | title: 'URL',
51 | name: 'href',
52 | type: 'url',
53 | },
54 | ],
55 | },
56 | ],
57 | },
58 | }),
59 | // You can add additional types here. Note that you can't use
60 | // primitive types such as 'string' and 'number' in the same array
61 | // as a block type.
62 | defineArrayMember({
63 | type: 'image',
64 | options: {hotspot: true},
65 | }),
66 | ],
67 | })
68 |
--------------------------------------------------------------------------------
/schemas/index.js:
--------------------------------------------------------------------------------
1 | import blockContent from './blockContent'
2 | import post from './post'
3 | import author from './author'
4 |
5 | export const schemaTypes = [post, author, blockContent]
6 |
--------------------------------------------------------------------------------
/schemas/post.js:
--------------------------------------------------------------------------------
1 | import {defineField, defineType} from 'sanity'
2 |
3 | export default defineType({
4 | name: 'post',
5 | title: 'Post',
6 | type: 'document',
7 | fields: [
8 | defineField({
9 | name: 'title',
10 | title: 'Title',
11 | type: 'string',
12 | }),
13 | defineField({
14 | name: 'subtitle',
15 | title: 'Subtitle',
16 | type: 'string',
17 | }),
18 | defineField({
19 | name: 'short_text',
20 | title: 'Short Text',
21 | type: 'text',
22 | }),
23 | defineField({
24 | name: 'slug',
25 | title: 'Slug',
26 | type: 'slug',
27 | options: {
28 | source: 'title',
29 | maxLength: 96,
30 | },
31 | }),
32 | defineField({
33 | name: 'author',
34 | title: 'Author',
35 | type: 'reference',
36 | to: {type: 'author'},
37 | }),
38 | defineField({
39 | name: 'mainImage',
40 | title: 'Main image',
41 | type: 'image',
42 | options: {
43 | hotspot: true,
44 | },
45 | }),
46 | defineField({
47 | name: 'publishedAt',
48 | title: 'Published at',
49 | type: 'datetime',
50 | }),
51 | defineField({
52 | name: 'body',
53 | title: 'Body',
54 | type: 'blockContent',
55 | }),
56 | ],
57 |
58 | preview: {
59 | select: {
60 | title: 'title',
61 | author: 'author.name',
62 | media: 'mainImage',
63 | },
64 | prepare(selection) {
65 | const {author} = selection
66 | return {...selection, subtitle: author && `by ${author}`}
67 | },
68 | },
69 | })
70 |
--------------------------------------------------------------------------------
/src/Components/Bar/Bar.jsx:
--------------------------------------------------------------------------------
1 | import styles from './Bar.module.scss';
2 |
3 | const Bar = () => {
4 | return (
5 |
6 | Novos artigos todas as terças!
7 |
8 | );
9 | };
10 |
11 | export default Bar;
12 |
--------------------------------------------------------------------------------
/src/Components/Bar/Bar.module.scss:
--------------------------------------------------------------------------------
1 | @import 'styles/variables.scss';
2 |
3 | .bar {
4 | background-color: $primary;
5 | padding: 1.5rem 0;
6 | text-align: center;
7 | margin-bottom: 3rem;
8 |
9 | span {
10 | font-size: 1.4rem;
11 | color: $white;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Components/Cardpost/Cardpost.jsx:
--------------------------------------------------------------------------------
1 | import Author from 'src/UI/Author/Author';
2 | import styles from './Cardpost.module.scss';
3 | import Link from 'next/link';
4 | import client from 'src/sanity';
5 | import { useNextSanityImage } from 'next-sanity-image';
6 | import Image from 'next/image';
7 |
8 | const Cardpost = ({ post, author }) => {
9 | const {
10 | author: authorRef,
11 | title,
12 | mainImage,
13 | publishedAt,
14 | short_text,
15 | slug,
16 | } = post || {};
17 |
18 | const imageProps = useNextSanityImage(client, mainImage);
19 | const getAuthor = author?.find((author) => author._id === authorRef._ref);
20 |
21 | return (
22 |
23 |
24 |
25 |
26 |
27 |
{title}
28 |
{short_text}
29 |
30 |
31 |
32 | Continuar lendo →
33 |
34 |
35 |
36 |
37 | );
38 | };
39 |
40 | export default Cardpost;
41 |
--------------------------------------------------------------------------------
/src/Components/Cardpost/Cardpost.module.scss:
--------------------------------------------------------------------------------
1 | @import 'styles/variables.scss';
2 | @import 'styles/mixins.scss';
3 |
4 | .post {
5 | display: flex;
6 | align-items: center;
7 | gap: 2rem;
8 |
9 | @include device(tablet) {
10 | flex-direction: column;
11 | max-width: 50rem;
12 | }
13 |
14 | &:not(:last-of-type) {
15 | margin-bottom: 4rem;
16 |
17 | @include device(tablet) {
18 | margin-bottom: 6rem;
19 | }
20 | }
21 |
22 | &:hover {
23 | cursor: pointer;
24 | }
25 |
26 | .image {
27 | img {
28 | max-width: 20rem;
29 | height: 20rem;
30 |
31 | @include device(tablet) {
32 | max-width: 100%;
33 | height: auto;
34 | }
35 | }
36 | }
37 |
38 | .content {
39 | h2 {
40 | font-family: $titleFontFamily;
41 | font-weight: 400;
42 | margin-bottom: 1rem;
43 | }
44 |
45 | p {
46 | margin-bottom: 2rem;
47 | }
48 | }
49 |
50 | .footer {
51 | display: flex;
52 | justify-content: space-between;
53 | align-items: center;
54 |
55 | a {
56 | text-decoration: none;
57 | color: $black;
58 |
59 | &:hover {
60 | font-weight: 600;
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Components/Footer/Footer.jsx:
--------------------------------------------------------------------------------
1 | import Logo from 'src/UI/Logo/Logo';
2 | import styles from './Footer.module.scss';
3 | import { AiFillInstagram } from 'react-icons/ai';
4 | import { IoLogoWhatsapp } from 'react-icons/io';
5 | import { MdEmail } from 'react-icons/md';
6 | import { BsTelegram } from 'react-icons/bs';
7 |
8 | const social = [
9 | { component: , url: '#' },
10 | { component: , url: '#' },
11 | { component: , url: '#' },
12 | { component: , url: '#' },
13 | ];
14 |
15 | const Footer = () => {
16 | const renderSocial = social.map((el, i) => (
17 |
18 | {el.component}
19 |
20 | ));
21 |
22 | return (
23 |
35 | );
36 | };
37 |
38 | export default Footer;
39 |
--------------------------------------------------------------------------------
/src/Components/Footer/Footer.module.scss:
--------------------------------------------------------------------------------
1 | @import 'styles/variables.scss';
2 | @import 'styles/mixins.scss';
3 |
4 | .footer-wrapper {
5 | padding-top: 8rem;
6 | padding-bottom: 2rem;
7 | background-color: $secondary;
8 |
9 | .footer {
10 | @include max-width(large);
11 | display: flex;
12 | flex-direction: column;
13 |
14 | .content {
15 | margin-bottom: 6rem;
16 | max-width: 25rem;
17 |
18 | p {
19 | margin: 1rem 0;
20 | }
21 |
22 | .social {
23 | display: flex;
24 | gap: 1rem;
25 |
26 | a {
27 | text-decoration: none;
28 | }
29 |
30 | svg {
31 | width: 2rem;
32 | height: 2rem;
33 | color: $primary;
34 | }
35 | }
36 | }
37 |
38 | .copy {
39 | align-self: center;
40 | font-size: 1.4rem;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Components/Heading/Heading.jsx:
--------------------------------------------------------------------------------
1 | import styles from './Heading.module.scss';
2 | import { BiSearchAlt } from 'react-icons/bi';
3 | import Logo from 'src/UI/Logo/Logo';
4 |
5 | const Heading = () => {
6 | return (
7 |
24 | );
25 | };
26 |
27 | export default Heading;
28 |
--------------------------------------------------------------------------------
/src/Components/Heading/Heading.module.scss:
--------------------------------------------------------------------------------
1 | @import 'styles/variables.scss';
2 | @import 'styles/mixins.scss';
3 |
4 | .heading {
5 | @include max-width(large);
6 | display: flex;
7 | justify-content: space-between;
8 | align-items: center;
9 | margin-bottom: $marginBottom;
10 |
11 | @include device(tablet) {
12 | flex-direction: column;
13 | gap: 2rem;
14 | }
15 |
16 | .search {
17 | width: 100%;
18 | max-width: 50rem;
19 | background-color: $secondary;
20 | border: 1rem;
21 | position: relative;
22 |
23 | label {
24 | display: none;
25 | opacity: 0;
26 | width: 0;
27 | height: 0;
28 | position: absolute;
29 | top: 0;
30 | left: 0;
31 | }
32 |
33 | input {
34 | border: none;
35 | outline: none;
36 | background-color: transparent;
37 | width: 100%;
38 | padding: 1.5rem 5.5rem 1.5rem 2rem;
39 |
40 | &::placeholder {
41 | font-family: $fontFamily;
42 | color: $gray;
43 | }
44 | }
45 |
46 | .button {
47 | border: none;
48 | outline: none;
49 | background-color: transparent;
50 | padding: 0.5rem;
51 | position: absolute;
52 | top: 50%;
53 | right: 2rem;
54 | transform: translateY(-40%);
55 |
56 | svg {
57 | width: 2rem;
58 | height: 2rem;
59 | color: $gray;
60 | }
61 |
62 | &:hover {
63 | cursor: pointer;
64 | }
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Layout/MainLayout.jsx:
--------------------------------------------------------------------------------
1 | import Footer from 'src/Components/Footer/Footer';
2 | import Heading from 'src/Components/Heading/Heading';
3 | import Bar from '../Components/Bar/Bar';
4 |
5 | const MainLayout = ({ children }) => {
6 | return (
7 | <>
8 |
12 | {children}
13 |
14 | >
15 | );
16 | };
17 |
18 | export default MainLayout;
19 |
--------------------------------------------------------------------------------
/src/UI/Author/Author.jsx:
--------------------------------------------------------------------------------
1 | import styles from './Author.module.scss';
2 | import Image from 'next/image';
3 | import client from 'src/sanity';
4 | import { useNextSanityImage } from 'next-sanity-image';
5 |
6 | const Author = ({ date = new Date(), author }) => {
7 | const { name, image } = author || {};
8 |
9 | const newDate = new Date(date);
10 | const formattedDate = new Intl.DateTimeFormat('pt-BR', {
11 | day: 'numeric',
12 | month: 'long',
13 | }).format(newDate);
14 |
15 | const imageProps = useNextSanityImage(client, image);
16 |
17 | return (
18 |
19 |
20 |
21 |
22 |
23 | {name}
24 | {formattedDate}
25 |
26 |
27 | );
28 | };
29 |
30 | export default Author;
31 |
--------------------------------------------------------------------------------
/src/UI/Author/Author.module.scss:
--------------------------------------------------------------------------------
1 | @import 'styles/variables.scss';
2 | @import 'styles/mixins.scss';
3 |
4 | .author {
5 | display: flex;
6 | align-items: center;
7 | gap: 1.5rem;
8 |
9 | .image {
10 | @include flex-center;
11 | border-radius: 50%;
12 | overflow: hidden;
13 | }
14 |
15 | .content {
16 | span {
17 | display: block;
18 | }
19 |
20 | .name {
21 | font-size: 1.5rem;
22 | }
23 |
24 | .date {
25 | font-size: 1.2rem;
26 | color: $gray;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/UI/Error/Error.jsx:
--------------------------------------------------------------------------------
1 | import styles from './Error.module.scss';
2 | import Link from 'next/link';
3 |
4 | const Error = ({ text }) => {
5 | return (
6 |
7 |
{text}
8 |
9 | Voltar para artigos
10 |
11 |
12 | );
13 | };
14 |
15 | export default Error;
16 |
--------------------------------------------------------------------------------
/src/UI/Error/Error.module.scss:
--------------------------------------------------------------------------------
1 | .error {
2 | text-align: center;
3 |
4 | h2 {
5 | margin-bottom: 4rem;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/UI/Logo/Logo.jsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import Image from 'next/image';
3 |
4 | const Logo = () => {
5 | return (
6 |
7 |
8 |
9 | );
10 | };
11 |
12 | export default Logo;
13 |
--------------------------------------------------------------------------------
/src/sanity.js:
--------------------------------------------------------------------------------
1 | import { createClient } from 'next-sanity';
2 |
3 | const client = createClient({
4 | projectId: process.env.NEXT_PUBLIC_PROJECT_ID,
5 | dataset: process.env.NEXT_PUBLIC_DATASET,
6 | apiVersion: process.env.NEXT_PUBLIC_API_VERSION,
7 | useCdn: process.env.NEXT_PUBLIC_USE_CDN,
8 | });
9 |
10 | export default client;
11 |
--------------------------------------------------------------------------------
/styles/Custom404.module.scss:
--------------------------------------------------------------------------------
1 | @import 'styles/mixins.scss';
2 |
3 | .section {
4 | @include max-width(small);
5 | @include flex-center;
6 | flex-direction: column;
7 | margin: 20rem auto;
8 |
9 | h1 {
10 | font-size: 7rem;
11 | margin-bottom: 4rem;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/styles/Home.module.scss:
--------------------------------------------------------------------------------
1 | @import 'styles/variables.scss';
2 | @import 'styles/mixins.scss';
3 |
4 | .page {
5 | @include max-width(small);
6 | margin-bottom: $marginBottom;
7 | display: flex;
8 | flex-direction: column;
9 | align-items: center;
10 |
11 | h1 {
12 | max-width: 35rem;
13 | margin-bottom: 6rem;
14 | text-align: center;
15 | }
16 |
17 | .pagination {
18 | margin-top: 6rem;
19 |
20 | button {
21 | background-color: $primary;
22 | color: $white;
23 | padding: 1rem;
24 | border: none;
25 |
26 | &:hover {
27 | cursor: pointer;
28 | }
29 |
30 | &:disabled {
31 | background-color: $gray;
32 | cursor: default;
33 | }
34 | }
35 |
36 | span {
37 | margin: 0 1rem;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/styles/Post.module.scss:
--------------------------------------------------------------------------------
1 | @import 'styles/variables.scss';
2 | @import 'styles/mixins.scss';
3 |
4 | .post {
5 | @include max-width(small);
6 | margin-bottom: $marginBottom;
7 | display: flex;
8 | flex-direction: column;
9 |
10 | .heading {
11 | max-width: 60rem;
12 | align-self: center;
13 | text-align: center;
14 | display: flex;
15 | flex-direction: column;
16 | align-items: center;
17 | margin-bottom: 4rem;
18 |
19 | h1 {
20 | margin-bottom: 1rem;
21 | }
22 |
23 | p {
24 | margin-bottom: 2.5rem;
25 | }
26 | }
27 |
28 | .content {
29 | .image {
30 | margin-bottom: 4rem;
31 |
32 | img {
33 | max-width: 100%;
34 | height: auto;
35 | }
36 | }
37 |
38 | :is(h1, h2, h3, h4, h5, h6) {
39 | margin: 2rem 0 1rem 0;
40 | }
41 |
42 | ul {
43 | margin: 2rem 0 2rem 4rem;
44 | }
45 |
46 | img {
47 | max-width: 100%;
48 | height: auto;
49 | display: block;
50 | text-align: center;
51 | margin: 2rem auto;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/styles/globals.scss:
--------------------------------------------------------------------------------
1 | @import './variables.scss';
2 | @import './mixins.scss';
3 |
4 | * {
5 | margin: 0;
6 | padding: 0;
7 | box-sizing: border-box;
8 | }
9 |
10 | html {
11 | font-size: 62.5%;
12 | scroll-behavior: smooth;
13 |
14 | @include device(notebook) {
15 | font-size: 60%;
16 | }
17 |
18 | @include device(mobile) {
19 | font-size: 58%;
20 | }
21 | }
22 |
23 | body {
24 | font-size: 1.6rem;
25 | font-family: $fontFamily;
26 | }
27 |
28 | h1,
29 | .h1 {
30 | font-weight: 500;
31 | font-size: 3.2rem;
32 | line-height: 4.5rem;
33 | color: $primary;
34 | font-family: $titleFontFamily;
35 | }
36 |
37 | h2,
38 | .h2 {
39 | font-size: 2.4rem;
40 | font-weight: 600;
41 | }
42 |
43 | h3,
44 | .h3 {
45 | font-size: 2.1rem;
46 | font-weight: 500;
47 | }
48 |
49 | h4,
50 | .h4 {
51 | font-size: 1.8rem;
52 | font-weight: 500;
53 | }
54 |
55 | h5,
56 | .h5 {
57 | font-size: 1.6rem;
58 | font-weight: 500;
59 | }
60 |
61 | p,
62 | .p {
63 | font-weight: 400;
64 | font-size: 1.6rem;
65 | line-height: 2.5rem;
66 |
67 | &:not(:last-of-type) {
68 | margin-bottom: 1.5rem;
69 | }
70 | }
71 |
72 | .btn {
73 | text-decoration: none;
74 | border: none;
75 | outline: none;
76 | padding: 1.5rem 3rem;
77 | display: inline-block;
78 | font-weight: 500;
79 |
80 | &.btn-primary {
81 | background-color: $primary;
82 | color: $white;
83 |
84 | &:hover {
85 | cursor: pointer;
86 | background-color: lighten($primary, 5);
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/styles/mixins.scss:
--------------------------------------------------------------------------------
1 | @mixin max-width($size) {
2 | width: 100%;
3 | margin: 0 auto;
4 | padding: 0 2rem;
5 |
6 | @if $size == large {
7 | max-width: 116rem;
8 | }
9 |
10 | @if $size == small {
11 | max-width: 96rem;
12 | }
13 | }
14 |
15 | @mixin flex-center {
16 | display: flex;
17 | justify-content: center;
18 | align-items: center;
19 | }
20 |
21 | @mixin device($device) {
22 | @if $device == desktop {
23 | @media only screen and (max-width: 1440px) {
24 | @content;
25 | }
26 | }
27 |
28 | @if $device == notebook {
29 | @media only screen and (max-width: 1024px) {
30 | @content;
31 | }
32 | }
33 |
34 | @if $device == tablet {
35 | @media only screen and (max-width: 768px) {
36 | @content;
37 | }
38 | }
39 |
40 | @if $device == mobile {
41 | @media only screen and (max-width: 425px) {
42 | @content;
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/styles/variables.scss:
--------------------------------------------------------------------------------
1 | $primary: #111727;
2 | $secondary: #f9fbfc;
3 | $white: #ffffff;
4 | $gray: #808080;
5 | $black: #000000;
6 |
7 | $titleFontFamily: 'Syne', sans-serif;
8 | $fontFamily: 'Manrope', sans-serif;
9 |
10 | $marginBottom: 8rem;
11 |
--------------------------------------------------------------------------------