├── .babelrc ├── .commitlintrc ├── .editorconfig ├── .env.example ├── .eslintrc ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .lintstagedrc ├── .prettierignore ├── .prettierrc ├── LICENSE ├── next-env.d.ts ├── next.config.js ├── package.json ├── public └── favicon.ico ├── src ├── assets │ └── hero.svg ├── components │ ├── Footer │ │ ├── index.tsx │ │ └── styles.ts │ ├── Header │ │ ├── index.tsx │ │ └── styles.ts │ ├── HomeContent │ │ ├── index.tsx │ │ └── styles.ts │ ├── Layout │ │ └── index.tsx │ ├── PostItem │ │ ├── index.tsx │ │ └── styles.ts │ ├── PostList │ │ ├── index.tsx │ │ └── styles.ts │ ├── SignInButton │ │ ├── index.tsx │ │ └── styles.ts │ └── SubscribeButton │ │ ├── index.tsx │ │ └── styles.ts ├── lib │ ├── formatDate.ts │ ├── getPosts.ts │ └── manageSubscription.ts ├── pages │ ├── _app.tsx │ ├── _document.tsx │ ├── api │ │ ├── auth │ │ │ └── [...nextauth].ts │ │ ├── subscribe.ts │ │ └── webhooks.ts │ ├── index.tsx │ └── posts │ │ ├── [slug].tsx │ │ ├── index.tsx │ │ └── preview │ │ └── [slug].tsx ├── services │ ├── api.ts │ ├── fauna.ts │ ├── prismic.ts │ ├── stripe.ts │ └── stripeJs.ts └── styles │ ├── globals.tsx │ └── twin.d.ts ├── tailwind.config.js ├── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["next/babel", { "preset-react": { "runtime": "automatic" } }]], 3 | "plugins": ["babel-plugin-macros", ["styled-components", { "ssr": true }]] 4 | } 5 | -------------------------------------------------------------------------------- /.commitlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@commitlint/config-conventional" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Prismic 2 | PRISMIC_ENDPOINT= 3 | PRISMIC_ACCESS_TOKEN= 4 | 5 | # Fauna 6 | FAUNADB_KEY= 7 | 8 | # Next-auth 9 | GITHUB_ID= 10 | GITHUB_SECRET= 11 | NEXTAUTH_URL= 12 | 13 | # Stripe 14 | NEXT_PUBLIC_STRIPE_PUBLIC_KEY= 15 | STRIPE_API_KEY= 16 | STRIPE_SUCCESS_URL= 17 | STRIPE_CANCEL_URL= 18 | STRIPE_WEBHOOK_SECRET= 19 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next", "next/core-web-vitals", "plugin:prettier/recommended"], 3 | "plugins": ["prettier", "simple-import-sort"], 4 | "rules": { 5 | "prettier/prettier": "error", 6 | "simple-import-sort/imports": "error", 7 | "simple-import-sort/exports": "error", 8 | "@next/next/no-page-custom-font": "off" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | .pnp 4 | .pnp.js 5 | 6 | # next.js 7 | .next 8 | 9 | # fauna 10 | .faunarc 11 | 12 | # jest 13 | coverage 14 | 15 | # misc 16 | .DS_Store 17 | *.pem 18 | 19 | # debug 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # local env files 24 | .env 25 | 26 | # vercel 27 | .vercel 28 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | yarn pretty-quick --staged 6 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,ts,tsx}": [ 3 | "eslint --fix" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # next.js 2 | .next 3 | public 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "tabWidth": 2, 4 | "semi": false, 5 | "singleQuote": true, 6 | "arrowParens": "avoid", 7 | "printWidth": 100 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Daniel Soares 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-news", 3 | "author": "Daniel Soares ", 4 | "version": "1.0.0", 5 | "private": true, 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "next dev", 9 | "build": "next build", 10 | "start": "next start", 11 | "lint": "next lint", 12 | "prepare": "husky install", 13 | "clean": "rm -rf node_modules .next", 14 | "format": "prettier --write \"./**/*.{js,jsx,ts,tsx,json,md,mdx}\"", 15 | "postversion": "git push --tags && git push" 16 | }, 17 | "dependencies": { 18 | "@prismicio/client": "^5.1.0", 19 | "@stripe/stripe-js": "^1.16.0", 20 | "axios": "^0.24.0", 21 | "faunadb": "^4.3.0", 22 | "next": "11.1.3", 23 | "next-auth": "^3.27.3", 24 | "prismic-dom": "^2.2.5", 25 | "react": "17.0.2", 26 | "react-dom": "17.0.2", 27 | "react-icons": "^4.2.0", 28 | "stripe": "^8.164.0", 29 | "styled-components": "^5.3.0" 30 | }, 31 | "devDependencies": { 32 | "@babel/core": "^7.14.8", 33 | "@commitlint/cli": "^15.0.0", 34 | "@commitlint/config-conventional": "^15.0.0", 35 | "@tailwindcss/typography": "^0.4.1", 36 | "@types/node": "^16.4.0", 37 | "@types/prismic-dom": "^2.1.1", 38 | "@types/react": "17.0.38", 39 | "@types/styled-components": "^5.1.11", 40 | "@types/tailwindcss": "^2.2.1", 41 | "autoprefixer": "^10.3.1", 42 | "babel-loader": "^8.2.2", 43 | "babel-plugin-macros": "^3.1.0", 44 | "babel-plugin-styled-components": "^2.0.0", 45 | "eslint": "8.5.0", 46 | "eslint-config-next": "11.1.3", 47 | "eslint-config-prettier": "^8.3.0", 48 | "eslint-plugin-prettier": "^4.0.0", 49 | "eslint-plugin-simple-import-sort": "^7.0.0", 50 | "husky": "^7.0.1", 51 | "lint-staged": "^12.0.0", 52 | "postcss": "^8.3.5", 53 | "prettier": "^2.3.2", 54 | "pretty-quick": "^3.1.1", 55 | "react-is": "^17.0.2", 56 | "tailwindcss": "^2.2.4", 57 | "twin.macro": "^2.6.2", 58 | "typescript": "4.3.5", 59 | "webpack": "^5.45.1" 60 | }, 61 | "babelMacros": { 62 | "twin": { 63 | "preset": "styled-components" 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoaress/react-news/59746a29ad079fea7f5be09ac90fa4b7191e97fe/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/hero.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/components/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | 3 | import { Content, Wrapper } from './styles' 4 | 5 | export function Footer() { 6 | return ( 7 | 8 | 9 |

10 | {new Date().getFullYear()} © React News 11 |
12 | by{' '} 13 | 14 | Daniel Soares 15 | 16 | . 17 |

18 |
19 |
20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /src/components/Footer/styles.ts: -------------------------------------------------------------------------------- 1 | import tw, { styled } from 'twin.macro' 2 | 3 | export const Wrapper = styled.footer` 4 | ${tw`h-20`} 5 | ` 6 | 7 | export const Content = styled.div` 8 | ${tw`flex items-center h-20 px-8 mx-auto`} 9 | 10 | p { 11 | ${tw`w-full text-sm text-center`} 12 | 13 | a { 14 | ${tw`font-bold transition-colors duration-200 text-primary-600 hover:text-primary-400`} 15 | } 16 | } 17 | ` 18 | -------------------------------------------------------------------------------- /src/components/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import { useRouter } from 'next/router' 3 | 4 | import { SignInButton } from '@/components/SignInButton' 5 | 6 | import { Content, SignInButtonWrapper, Wrapper } from './styles' 7 | 8 | export function Header() { 9 | const { asPath } = useRouter() 10 | 11 | const links = [ 12 | { 13 | label: 'Home', 14 | url: '/' 15 | }, 16 | { 17 | label: 'Posts', 18 | url: '/posts' 19 | } 20 | ] 21 | 22 | return ( 23 | 24 | 25 |
26 | ReactNews 27 |
28 | 29 | 36 | 37 | 38 | 39 | 40 |
41 |
42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Header/styles.ts: -------------------------------------------------------------------------------- 1 | import tw, { styled } from 'twin.macro' 2 | 3 | export const Wrapper = styled.header` 4 | ${tw`h-20 shadow-md`} 5 | ` 6 | 7 | export const Content = styled.div` 8 | ${tw`flex items-center h-20 max-w-screen-xl px-8 mx-auto`} 9 | 10 | .logo { 11 | ${tw`text-base font-black md:text-3xl`} 12 | 13 | span { 14 | ${tw`text-primary-600`} 15 | } 16 | } 17 | 18 | nav { 19 | ${tw`h-20 ml-6 space-x-3 text-sm md:text-base md:ml-20 md:space-x-8`} 20 | 21 | a { 22 | ${tw`relative inline-block h-20 px-2 transition-colors duration-200 text-neutral-600 line-height[5rem] hover:text-neutral-900`} 23 | 24 | &.active { 25 | ${tw`text-neutral-900 font-bold after:(content h-1 rounded-t w-full absolute bottom-0 left-0 bg-primary-600)`} 26 | } 27 | } 28 | } 29 | ` 30 | 31 | export const SignInButtonWrapper = styled.div` 32 | ${tw`hidden ml-auto md:block`} 33 | ` 34 | -------------------------------------------------------------------------------- /src/components/HomeContent/index.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | 3 | import heroImg from '@/assets/hero.svg' 4 | import { SubscribeButton } from '@/components/SubscribeButton' 5 | 6 | import { Content, ImageWrapper, Wrapper } from './styles' 7 | 8 | export function HomeContent() { 9 | return ( 10 | 11 | 12 | 👋 Hey, welcome 13 | 14 |

15 | News about the React world. 16 |

17 | 18 |

19 | Get access to all the publication
20 | for $9.90 month 21 |

22 | 23 | 24 |
25 | 26 | Girl coding 27 | 28 |
29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /src/components/HomeContent/styles.ts: -------------------------------------------------------------------------------- 1 | import tw, { styled } from 'twin.macro' 2 | 3 | export const Wrapper = styled.main` 4 | height: calc(100vh - 10rem); 5 | ${tw`grid items-center justify-between max-w-screen-xl grid-cols-3 gap-12 px-8 mx-auto`} 6 | ` 7 | 8 | export const Content = styled.section` 9 | ${tw`col-span-3 space-y-8 text-center md:text-left lg:col-span-2`} 10 | 11 | > span { 12 | ${tw`text-lg font-bold md:text-2xl`} 13 | } 14 | 15 | h1 { 16 | ${tw`text-3xl font-black leading-none md:text-7xl`} 17 | 18 | span { 19 | ${tw`text-primary-600`} 20 | } 21 | } 22 | 23 | p { 24 | ${tw`text-lg md:leading-9 md:text-2xl`} 25 | 26 | span { 27 | ${tw`font-bold text-primary-600`} 28 | } 29 | } 30 | 31 | button { 32 | ${tw`mx-auto md:m-0`} 33 | } 34 | ` 35 | 36 | export const ImageWrapper = styled.div` 37 | ${tw`hidden lg:block`} 38 | ` 39 | -------------------------------------------------------------------------------- /src/components/Layout/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | 3 | import { Footer } from '@/components/Footer' 4 | import { Header } from '@/components/Header' 5 | 6 | type LayoutProps = { 7 | children: ReactNode 8 | } 9 | 10 | export function Layout({ children }: LayoutProps) { 11 | return ( 12 | <> 13 |
14 | {children} 15 |