├── .env.example ├── .eslintrc ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── License.md ├── README.md ├── docker-compose.yaml ├── docs ├── create-post.png ├── list.png ├── request-password.png ├── sign-in.png ├── sign-up.png └── single-post.png ├── next-env.d.ts ├── next.config.js ├── package.json ├── postcss.config.js ├── prisma ├── migrations │ ├── 20211019164222_init │ │ └── migration.sql │ ├── 20220307124425_non_unique_timestamps │ │ └── migration.sql │ ├── 20220430110520_add_next_auth │ │ └── migration.sql │ ├── 20220501133427_add_reset_token │ │ └── migration.sql │ └── migration_lock.toml ├── schema.prisma └── seed.ts ├── public ├── favicon.ico └── vercel.svg ├── render.yaml ├── sandbox.config.json ├── src ├── components │ ├── Button.tsx │ ├── DefaultLayout.tsx │ ├── Feedback.tsx │ ├── Form.tsx │ └── header.tsx ├── pages │ ├── _app.tsx │ ├── _document.tsx │ ├── api │ │ ├── auth │ │ │ ├── [...nextauth].ts │ │ │ ├── reset-password.ts │ │ │ └── signup.ts │ │ └── trpc │ │ │ └── [trpc].ts │ ├── auth │ │ ├── forgot-password.tsx │ │ ├── reset-password.tsx │ │ ├── signin.tsx │ │ └── signup.tsx │ ├── create-post.tsx │ ├── index.tsx │ └── post │ │ └── [id].tsx ├── server │ ├── context.ts │ ├── env.js │ ├── prisma.ts │ ├── routers │ │ ├── _app.ts │ │ └── post.ts │ └── trpc.ts ├── styles │ └── global.css └── utils │ ├── absolute-url.ts │ ├── constants │ ├── errors.ts │ └── signin-states.ts │ ├── hooks │ ├── usePosts.ts │ └── useRegistration.ts │ ├── password.ts │ ├── publicRuntimeConfig.ts │ ├── session.ts │ ├── trpc.ts │ └── types.ts ├── tailwind.config.js ├── tsconfig.json └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | # Make sure to override these in deployment 2 | DATABASE_URL=postgresql://postgres:@localhost:5832/trpcdb 3 | NEXTAUTH_URL=http://localhost:3000 4 | NEXTAUTH_SECRET="# Linux: `openssl rand -hex 32` or go to https://generate-secret.now.sh/32" 5 | 6 | EMAIL_SERVER=smtp://username:password@smtp.example.com:587 7 | EMAIL_FROM="NextAuth " -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "plugin:@typescript-eslint/recommended", 5 | "plugin:react/recommended", 6 | "plugin:react-hooks/recommended", 7 | "plugin:prettier/recommended", 8 | "plugin:@next/next/recommended" 9 | ], 10 | "parserOptions": { 11 | "project": "tsconfig.json", 12 | "ecmaVersion": 2022, 13 | "sourceType": "module" 14 | }, 15 | "rules": { 16 | "@typescript-eslint/explicit-function-return-type": "off", 17 | "@typescript-eslint/explicit-module-boundary-types": "off", 18 | "react/react-in-jsx-scope": "off", 19 | "react/prop-types": "off", 20 | "@typescript-eslint/ban-ts-comment": "off", 21 | "@typescript-eslint/no-explicit-any": "off" 22 | }, 23 | "settings": { 24 | "react": { 25 | "version": "detect" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main workflow 2 | on: [push] 3 | jobs: 4 | all: 5 | env: 6 | DATABASE_URL: postgresql://postgres:@localhost:5432/trpcdb 7 | NODE_ENV: test 8 | NEXTAUTH_SECRET: supersecret 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | node-version: ['16.x'] 13 | os: [ubuntu-latest] 14 | services: 15 | postgres: 16 | image: postgres:12.1 17 | env: 18 | POSTGRES_USER: postgres 19 | POSTGRES_DB: trpcdb 20 | ports: 21 | - 5432:5432 22 | steps: 23 | - name: Checkout repo 24 | uses: actions/checkout@v2 25 | 26 | - name: Use Node.js ${{ matrix.node-version }} 27 | uses: actions/setup-node@v2 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | - run: yarn install 31 | 32 | - name: Next.js cache 33 | uses: actions/cache@v2 34 | with: 35 | path: ${{ github.workspace }}/.next/cache 36 | key: ${{ runner.os }}-${{ runner.node }}-${{ hashFiles('**/yarn.lock') }}-nextjs 37 | 38 | - run: yarn lint 39 | - run: yarn build 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | 7 | # next.js 8 | /.next 9 | /out 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | *.pem 17 | 18 | # debug 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # local env files 24 | .env 25 | .env.local 26 | .env.development.local 27 | .env.test.local 28 | .env.production.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | *.db 34 | *.db-journal 35 | prisma/_sqlite/migrations 36 | 37 | 38 | # cache 39 | .eslintcache 40 | 41 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | 3 | Copyright 2022 Sara Vieira, contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NextJS FullStack Starter 2 | 3 | A starter for NextJS fullstack projects with all you need to start your side project or your dream. Do your thing. 4 | 5 | ## Features 6 | 7 | - 📡 API using [tRPC](https://trpc.io) 8 | - 📦 Database with [Prisma](https://www.prisma.io/) & Postgres 9 | - 👾 Very lenient [Typescript](https://www.typescriptlang.org/) configuration (this is by design) 10 | - 🔒 Auth with [NextAuth](https://next-auth.js.org/) 11 | - Credentials setup for Email and Password 12 | - Forgot Password email and template 13 | - ☀️ [Tailwind](https://tailwindcss.com/) 14 | - 🐻 [Zustand](https://github.com/pmndrs/zustand) for any extra state management needs 15 | - 📰 Blog example 16 | - Create post 17 | - Show all posts 18 | - Protected routes 19 | - Protected API routes 20 | - 🎨 ESLint + Prettier + [Lint Staged](https://github.com/okonet/lint-staged) 21 | - 💚 CI setup using GitHub Actions: 22 | - Build 23 | - Linting 24 | 25 | ## Pages 26 | 27 | - Post list 28 | - Single post 29 | - Create a post 30 | - Sign in 31 | - Sign up 32 | - Request a new password 33 | - Change Password 34 | 35 | ### Screenshots 36 | 37 |
38 | Post list 39 | post list 40 |
41 |
42 | Single post 43 | single post 44 |
45 |
46 | Create post 47 | create post 48 |
49 |
50 | Signin 51 | signin 52 |
53 |
54 | Signup 55 | signup 56 |
57 |
58 | Request password 59 | request password 60 |
61 | 62 | ## Development 63 | 64 | ### Requirements 65 | 66 | - Node >= 14 67 | - Docker (for running Postgres) 68 | 69 | ### Start project 70 | 71 | ```bash 72 | git clone https://github.com/SaraVieira/next-fullstack-starter 73 | cd trpc-prisma-starter 74 | yarn 75 | cp .env.example .env 76 | yarn dx 77 | 78 | ``` 79 | 80 | ### Commands 81 | 82 | ```bash 83 | yarn build # runs `prisma generate` + `prisma migrate` + `next build` 84 | yarn db-nuke # resets local db 85 | yarn dev # starts next.js 86 | yarn dx # starts postgres db + runs migrations + seeds + starts next.js 87 | yarn lint # runs eslint on all files 88 | ``` 89 | 90 | ## Deployment 91 | 92 | ### Using [Render](https://render.com/) 93 | 94 | The project contains a [`render.yaml`](./render.yaml) [_"Blueprint"_](https://render.com/docs/blueprint-spec) which makes the project easily deployable on [Render](https://render.com/). 95 | 96 | Go to [dashboard.render.com/blueprints](https://dashboard.render.com/blueprints) and connect to this Blueprint and see how the app and database automatically gets deployed. 97 | 98 | ## License 99 | 100 | MIT 101 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.6' 2 | services: 3 | postgres: 4 | image: postgres:13 5 | ports: 6 | - '5832:5432' # expose pg on port 5832 to not collide with pg from elswhere 7 | restart: always 8 | volumes: 9 | - db_data:/var/lib/postgresql/data 10 | environment: 11 | POSTGRES_PASSWORD: ${PGPASSWORD} 12 | POSTGRES_HOST_AUTH_METHOD: trust 13 | volumes: 14 | db_data: 15 | -------------------------------------------------------------------------------- /docs/create-post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaraVieira/next-fullstack-starter/6e0f913ad1ec7d53d0fd57998083d563f35d23de/docs/create-post.png -------------------------------------------------------------------------------- /docs/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaraVieira/next-fullstack-starter/6e0f913ad1ec7d53d0fd57998083d563f35d23de/docs/list.png -------------------------------------------------------------------------------- /docs/request-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaraVieira/next-fullstack-starter/6e0f913ad1ec7d53d0fd57998083d563f35d23de/docs/request-password.png -------------------------------------------------------------------------------- /docs/sign-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaraVieira/next-fullstack-starter/6e0f913ad1ec7d53d0fd57998083d563f35d23de/docs/sign-in.png -------------------------------------------------------------------------------- /docs/sign-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaraVieira/next-fullstack-starter/6e0f913ad1ec7d53d0fd57998083d563f35d23de/docs/sign-up.png -------------------------------------------------------------------------------- /docs/single-post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaraVieira/next-fullstack-starter/6e0f913ad1ec7d53d0fd57998083d563f35d23de/docs/single-post.png -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const { env } = require('./src/server/env'); 3 | 4 | function getConfig(config) { 5 | return config; 6 | } 7 | 8 | /** 9 | * @link https://nextjs.org/docs/api-reference/next.config.js/introduction 10 | */ 11 | module.exports = getConfig({ 12 | /** 13 | * Dynamic configuration available for the browser and server. 14 | * Note: requires `ssr: true` or a `getInitialProps` in `_app.tsx` 15 | * @link https://nextjs.org/docs/api-reference/next.config.js/runtime-configuration 16 | */ 17 | publicRuntimeConfig: { 18 | NODE_ENV: env.NODE_ENV, 19 | }, 20 | typescript: { 21 | // !! WARN !! 22 | // Dangerously allow production builds to successfully complete even if 23 | // your project has type errors. 24 | // !! WARN !! 25 | ignoreBuildErrors: true, 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-fullstack-starter", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build:1-migrate": "prisma migrate deploy", 7 | "build:2-next": "next build", 8 | "build": "run-s build:*", 9 | "db-up": "docker-compose up -d", 10 | "db-seed": "prisma db seed", 11 | "db-migrate-dev": "yarn prisma migrate dev", 12 | "db-nuke": "docker-compose down --volumes --remove-orphans", 13 | "dev": "next dev", 14 | "dx": "run-s db-up db-migrate-dev db-seed dev", 15 | "start": "next start", 16 | "lint": "eslint src", 17 | "lint-fix": "yarn lint --fix", 18 | "ts-node": "ts-node --compiler-options \"{\\\"module\\\":\\\"commonjs\\\"}\"", 19 | "postinstall": "prisma generate", 20 | "prepare": "husky install" 21 | }, 22 | "prisma": { 23 | "seed": "yarn ts-node prisma/seed.ts" 24 | }, 25 | "prettier": { 26 | "printWidth": 80, 27 | "trailingComma": "all", 28 | "singleQuote": true 29 | }, 30 | "dependencies": { 31 | "@heroicons/react": "^1.0.6", 32 | "@next-auth/prisma-adapter": "^1.0.3", 33 | "@prisma/client": "^4.11.0", 34 | "@tailwindcss/forms": "^0.5.0", 35 | "@tanstack/react-query": "^4.24.10", 36 | "@trpc/client": "^10.14.0", 37 | "@trpc/next": "^10.14.0", 38 | "@trpc/react": "^9.23.2", 39 | "@trpc/react-query": "^10.14.0", 40 | "@trpc/server": "^10.14.0", 41 | "@types/bcryptjs": "^2.4.2", 42 | "autoprefixer": "^10.4.5", 43 | "bcrypt": "^5.0.1", 44 | "bcryptjs": "^2.4.3", 45 | "classnames": "^2.3.1", 46 | "clsx": "^1.1.1", 47 | "lodash-es": "^4.17.21", 48 | "next": "^12.1.6-canary.16", 49 | "next-auth": "^4.3.4", 50 | "nodemailer": "^6.7.4", 51 | "postcss": "^8.4.13", 52 | "react": "^18.1.0", 53 | "react-dom": "^18.1.0", 54 | "react-query": "^3.38.0", 55 | "superjson": "^1.12.2", 56 | "tailwindcss": "^3.0.24", 57 | "zod": "^3.21.0", 58 | "zustand": "^4.0.0-rc.1" 59 | }, 60 | "devDependencies": { 61 | "@next/eslint-plugin-next": "^12.1.5", 62 | "@types/node": "^17.0.30", 63 | "@types/react": "^18.0.8", 64 | "@typescript-eslint/eslint-plugin": "^5.21.0", 65 | "@typescript-eslint/parser": "^5.21.0", 66 | "eslint": "^8.14.0", 67 | "eslint-config-next": "^12.1.5", 68 | "eslint-config-prettier": "^8.5.0", 69 | "eslint-plugin-prettier": "^4.0.0", 70 | "eslint-plugin-react": "^7.29.4", 71 | "eslint-plugin-react-hooks": "^4.5.0", 72 | "husky": ">=6", 73 | "lint-staged": ">=10", 74 | "npm-run-all": "^4.1.5", 75 | "prettier": "^2.6.2", 76 | "prisma": "^3.13.0", 77 | "ts-node": "^10.7.0", 78 | "typescript": "4.6.4" 79 | }, 80 | "publishConfig": { 81 | "access": "restricted" 82 | }, 83 | "lint-staged": { 84 | "*.{js,ts,jsx,tsx}": "eslint --cache --fix" 85 | }, 86 | "license": "MIT" 87 | } 88 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /prisma/migrations/20211019164222_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Post" ( 3 | "id" TEXT NOT NULL, 4 | "title" TEXT NOT NULL, 5 | "text" TEXT NOT NULL, 6 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 7 | "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 8 | 9 | CONSTRAINT "Post_pkey" PRIMARY KEY ("id") 10 | ); 11 | 12 | -- CreateIndex 13 | CREATE UNIQUE INDEX "Post_createdAt_key" ON "Post"("createdAt"); 14 | 15 | -- CreateIndex 16 | CREATE UNIQUE INDEX "Post_updatedAt_key" ON "Post"("updatedAt"); 17 | -------------------------------------------------------------------------------- /prisma/migrations/20220307124425_non_unique_timestamps/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX "Post_createdAt_key"; 3 | 4 | -- DropIndex 5 | DROP INDEX "Post_updatedAt_key"; 6 | -------------------------------------------------------------------------------- /prisma/migrations/20220430110520_add_next_auth/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Post" ADD COLUMN "userId" TEXT; 3 | 4 | -- CreateTable 5 | CREATE TABLE "Account" ( 6 | "id" TEXT NOT NULL, 7 | "userId" TEXT NOT NULL, 8 | "type" TEXT NOT NULL, 9 | "provider" TEXT NOT NULL, 10 | "providerAccountId" TEXT NOT NULL, 11 | "refresh_token" TEXT, 12 | "access_token" TEXT, 13 | "expires_at" INTEGER, 14 | "token_type" TEXT, 15 | "scope" TEXT, 16 | "id_token" TEXT, 17 | "session_state" TEXT, 18 | "oauth_token_secret" TEXT, 19 | "oauth_token" TEXT, 20 | 21 | CONSTRAINT "Account_pkey" PRIMARY KEY ("id") 22 | ); 23 | 24 | -- CreateTable 25 | CREATE TABLE "Session" ( 26 | "id" TEXT NOT NULL, 27 | "sessionToken" TEXT NOT NULL, 28 | "userId" TEXT NOT NULL, 29 | "expires" TIMESTAMP(3) NOT NULL, 30 | 31 | CONSTRAINT "Session_pkey" PRIMARY KEY ("id") 32 | ); 33 | 34 | -- CreateTable 35 | CREATE TABLE "User" ( 36 | "id" TEXT NOT NULL, 37 | "name" TEXT, 38 | "email" TEXT, 39 | "password" TEXT NOT NULL, 40 | "emailVerified" TIMESTAMP(3), 41 | "image" TEXT, 42 | 43 | CONSTRAINT "User_pkey" PRIMARY KEY ("id") 44 | ); 45 | 46 | -- CreateIndex 47 | CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId"); 48 | 49 | -- CreateIndex 50 | CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken"); 51 | 52 | -- CreateIndex 53 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); 54 | 55 | -- AddForeignKey 56 | ALTER TABLE "Post" ADD CONSTRAINT "Post_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; 57 | 58 | -- AddForeignKey 59 | ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; 60 | 61 | -- AddForeignKey 62 | ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; 63 | -------------------------------------------------------------------------------- /prisma/migrations/20220501133427_add_reset_token/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `resetToken` to the `User` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "User" ADD COLUMN "resetToken" TEXT NOT NULL; 9 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | datasource db { 5 | provider = "postgres" 6 | url = env("DATABASE_URL") 7 | } 8 | 9 | generator client { 10 | provider = "prisma-client-js" 11 | } 12 | 13 | model Post { 14 | id String @id @default(uuid()) 15 | title String 16 | text String 17 | createdAt DateTime @default(now()) 18 | updatedAt DateTime @default(now()) @updatedAt 19 | User User? @relation(fields: [userId], references: [id]) 20 | userId String? 21 | } 22 | 23 | model Account { 24 | id String @id @default(cuid()) 25 | userId String 26 | type String 27 | provider String 28 | providerAccountId String 29 | refresh_token String? @db.Text 30 | access_token String? @db.Text 31 | expires_at Int? 32 | token_type String? 33 | scope String? 34 | id_token String? @db.Text 35 | session_state String? 36 | oauth_token_secret String? 37 | oauth_token String? 38 | 39 | user User @relation(fields: [userId], references: [id], onDelete: Cascade) 40 | 41 | @@unique([provider, providerAccountId]) 42 | } 43 | 44 | model Session { 45 | id String @id @default(cuid()) 46 | sessionToken String @unique 47 | userId String 48 | expires DateTime 49 | user User @relation(fields: [userId], references: [id], onDelete: Cascade) 50 | } 51 | 52 | model User { 53 | id String @id @default(cuid()) 54 | name String? 55 | email String? @unique 56 | password String 57 | emailVerified DateTime? 58 | resetToken String 59 | image String? 60 | accounts Account[] 61 | sessions Session[] 62 | posts Post[] 63 | } 64 | -------------------------------------------------------------------------------- /prisma/seed.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Adds seed data to your db 3 | * 4 | * @link https://www.prisma.io/docs/guides/database/seed-database 5 | */ 6 | import { PrismaClient } from '@prisma/client'; 7 | 8 | const prisma = new PrismaClient(); 9 | 10 | async function main() { 11 | const firstPostId = '5c03994c-fc16-47e0-bd02-d218a370a078'; 12 | await prisma.post.upsert({ 13 | where: { 14 | id: firstPostId, 15 | }, 16 | create: { 17 | id: firstPostId, 18 | title: 'First Post', 19 | text: 'This is an example post generated from `prisma/seed.ts`', 20 | }, 21 | update: {}, 22 | }); 23 | } 24 | 25 | main() 26 | .catch((e) => { 27 | console.error(e); 28 | process.exit(1); 29 | }) 30 | .finally(async () => { 31 | await prisma.$disconnect(); 32 | }); 33 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaraVieira/next-fullstack-starter/6e0f913ad1ec7d53d0fd57998083d563f35d23de/public/favicon.ico -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /render.yaml: -------------------------------------------------------------------------------- 1 | #### Render Blueprint specification: https://dashboard.render.com/blueprints #### 2 | ## 👇 Preview environments: https://render.com/docs/preview-environments ### 3 | # previewsEnabled: true 4 | ## 👇 Automatically nuke the environment after X days of inactivity to reduce billing: 5 | # previewsExpireAfterDays: 2 6 | services: 7 | - type: web 8 | name: next-fullstack-starter 9 | env: node 10 | plan: free 11 | ## 👇 Specify the plan for the PR deployment: 12 | # previewPlan: starter 13 | ## 👇 Preview Environment Initialization script: 14 | # initialDeployHook: yarn db-seed 15 | buildCommand: yarn --prod=false && 16 | yarn build 17 | startCommand: yarn start 18 | healthCheckPath: /api/trpc/healthz 19 | envVars: 20 | - key: DATABASE_URL 21 | fromDatabase: 22 | name: next-fullstack-starter-db 23 | property: connectionString 24 | 25 | databases: 26 | - name: next-fullstack-starter-db 27 | plan: free 28 | -------------------------------------------------------------------------------- /sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "next" 3 | } 4 | -------------------------------------------------------------------------------- /src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import Link from 'next/link'; 3 | import React, { ButtonHTMLAttributes } from 'react'; 4 | 5 | type Props = { 6 | loading?: boolean; 7 | variant?: 'primary' | 'secondary'; 8 | children: React.ReactNode; 9 | href?: string; 10 | } & ButtonHTMLAttributes; 11 | 12 | const Loading = () => ( 13 | 20 | 24 | 28 | 29 | ); 30 | 31 | export const Button = ({ 32 | loading, 33 | children, 34 | variant = 'primary', 35 | className = '', 36 | href, 37 | ...props 38 | }: Props) => { 39 | const disabled = props.disabled || loading; 40 | const classes = { 41 | primary: 'bg-red-600 rounded-md h-[48px] disabled:opacity-50', 42 | secondary: 'text-red-600 rounded-md h-[48px] disabled:opacity-50', 43 | }; 44 | 45 | if (href) { 46 | return ( 47 | 48 | 56 | {loading && } 57 | {children} 58 | 59 | 60 | ); 61 | } 62 | return ( 63 | 71 | ); 72 | }; 73 | -------------------------------------------------------------------------------- /src/components/DefaultLayout.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { Header } from './header'; 3 | 4 | type DefaultLayoutProps = { children: ReactNode }; 5 | 6 | export const DefaultLayout = ({ children }: DefaultLayoutProps) => { 7 | return ( 8 | <> 9 |
10 |
11 |
{children}
12 |
13 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/Feedback.tsx: -------------------------------------------------------------------------------- 1 | import { XCircleIcon } from '@heroicons/react/outline'; 2 | import { CheckCircleIcon, ExclamationIcon } from '@heroicons/react/solid'; 3 | import classNames from 'classnames'; 4 | 5 | type Props = { 6 | message: string; 7 | variant: 'success' | 'warning' | 'error'; 8 | }; 9 | 10 | export const Feedback = ({ message, variant = 'success' }: Props) => { 11 | const isSuccess = variant === 'success'; 12 | const isError = variant === 'error'; 13 | const isWarning = variant === 'warning'; 14 | return ( 15 |
23 |
24 |
25 | {variant === 'error' && ( 26 |
41 |
42 |

50 | {message} 51 |

52 |
53 |
54 |
55 | ); 56 | }; 57 | -------------------------------------------------------------------------------- /src/components/Form.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef, ReactElement } from 'react'; 2 | 3 | export const Label = ({ children, ...props }) => ( 4 | 7 | ); 8 | 9 | // eslint-disable-next-line react/display-name 10 | export const Input = forwardRef( 11 | (props: any, ref): ReactElement => ( 12 | <> 13 | 14 | 19 | 20 | ), 21 | ); 22 | 23 | // eslint-disable-next-line react/display-name 24 | export const Textarea = forwardRef( 25 | (props: any, ref): ReactElement => ( 26 | <> 27 | 28 |