├── .env.example ├── .gitignore ├── README.md ├── components ├── Table │ ├── BasicTable.js │ └── Table.js └── layout │ ├── ActiveLink.js │ ├── Layout.js │ └── Navbar.js ├── context ├── Actions.js ├── GlobalState.js └── Reducers.js ├── jsconfig.json ├── lib └── gtag.js ├── models ├── School.js └── User.js ├── package-lock.json ├── package.json ├── pages ├── _app.js ├── _document.js ├── api │ ├── auth │ │ └── [...nextauth].js │ ├── hello.js │ └── schools │ │ └── index.js ├── dashboard.js ├── index.js └── profile.js ├── postcss.config.js ├── public ├── favicon.ico └── vercel.svg ├── styles └── globals.css ├── tailwind.config.js └── utils ├── dbConnect.js ├── scrollRestoration.js ├── usePaginatedSchools.js └── useSchools.js /.env.example: -------------------------------------------------------------------------------- 1 | MONGODB_URI= 2 | -------------------------------------------------------------------------------- /.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 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Next.js Starter | MongoDB | Tailwind | NextAuth 2 | 3 | This is a custom NextJS Starter with these technologies set up: 4 | 5 | - MongoDB 6 | - Mongoose 7 | - TailwindCSS 8 | - NextAuth with Google Sign In 9 | 10 | ## How to use 11 | 12 | Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: 13 | 14 | ```bash 15 | npx create-next-app myapp -e https://github.com/uguremirmustafa/with-tailwind-mongo-next-auth 16 | # or 17 | yarn create next-app myapp -e https://github.com/uguremirmustafa/with-tailwind-mongo-next-auth 18 | ``` 19 | 20 | ## Small Details 21 | 22 | - This project includes small script to restore scroll position on route change for nextjs. 23 | - Includes a modular navbar with indicator of the active page. 24 | - Example `env` file. 25 | - Smart importing with `jsconfig.json` file. 26 | 27 | ## Todo 28 | 29 | - Write down small docs to starter 30 | - Add a loading spinner. 31 | - Create data fetching functions 32 | - Add api routes 33 | - Add a header component to show title of the page 34 | - Add favicon 35 | - Remove console.logs from custom scrolling function 36 | -------------------------------------------------------------------------------- /components/Table/BasicTable.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTable } from 'react-table'; 3 | 4 | export default function BasicTable({ data, columns }) { 5 | const { getTableProps, getTableBodyProps, headerGroups, prepareRow, rows } = useTable({ 6 | columns, 7 | data, 8 | }); 9 | return ( 10 | <> 11 | 12 | 13 | {headerGroups.map((headerGroup) => ( 14 | 15 | {headerGroup.headers.map((column) => ( 16 | 20 | ))} 21 | 22 | ))} 23 | 24 | 25 | {rows.map((row, i) => { 26 | prepareRow(row); 27 | return ( 28 | 29 | {row.cells.map((cell) => { 30 | return ; 31 | })} 32 | 33 | ); 34 | })} 35 | 36 |
17 | {column.render('Header')} 18 | {/* {column.isSorted ? (column.isSortedDesc ? 'as 🔽' : ' 🔼') : ''} */} 19 |
{cell.render('Cell')}
37 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /components/Table/Table.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTable, usePagination } from 'react-table'; 3 | 4 | function Table({ setPerPage, setPage, columns, data, currentpage, perPage, totalPage }) { 5 | const { 6 | getTableProps, 7 | getTableBodyProps, 8 | headerGroups, 9 | prepareRow, 10 | page, 11 | // canPreviousPage, 12 | // canNextPage, 13 | pageOptions, 14 | // pageCount, 15 | // gotoPage, 16 | // nextPage, 17 | // previousPage, 18 | // setPageSize, 19 | // Get the state from the instance 20 | state: { pageIndex, pageSize }, 21 | } = useTable( 22 | { 23 | columns, 24 | data, 25 | useControlledState: (state) => { 26 | return React.useMemo( 27 | () => ({ 28 | ...state, 29 | pageIndex: currentpage, 30 | }), 31 | [state, currentpage] 32 | ); 33 | }, 34 | initialState: { pageIndex: currentpage }, // Pass our hoisted table state 35 | manualPagination: true, // Tell the usePagination 36 | // hook that we'll handle our own data fetching 37 | // This means we'll also have to provide our own 38 | // pageCount. 39 | pageCount: totalPage, 40 | }, 41 | usePagination 42 | ); 43 | 44 | return ( 45 | <> 46 | 47 | 48 | {headerGroups.map((headerGroup) => ( 49 | 50 | {headerGroup.headers.slice(0, 1).map((column) => ( 51 | 57 | ))} 58 | {headerGroup.headers.slice(1).map((column) => ( 59 | 65 | ))} 66 | 67 | ))} 68 | 69 | 70 | {page.map((row, i) => { 71 | prepareRow(row); 72 | return ( 73 | 74 | {row.cells.map((cell) => { 75 | return ( 76 | 79 | ); 80 | })} 81 | 82 | ); 83 | })} 84 | 85 |
55 | {column.render('Header')} 56 | 63 | {column.render('Header')} 64 |
77 | {cell.render('Cell')} 78 |
86 | 87 |
88 | {' '} 96 | {' '} 104 | {' '} 112 | {' '} 120 | 121 | Page{' '} 122 | 123 | {pageIndex} of {pageOptions.length} 124 | {' '} 125 | 126 | 127 | | Go to page:{' '} 128 | { 134 | const page = e.target.value ? Number(e.target.value) : 1; 135 | setPage(page); 136 | }} 137 | className="w-20 border-2 rounded px-2" 138 | /> 139 | {' '} 140 | 153 |
154 | 155 | ); 156 | } 157 | 158 | export default Table; 159 | -------------------------------------------------------------------------------- /components/layout/ActiveLink.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from 'next/link'; 3 | import { useRouter } from 'next/router'; 4 | 5 | export const ActiveLink = ({ href, children }) => { 6 | const router = useRouter(); 7 | 8 | let className = children.props.className || ''; 9 | if (router.pathname === href) { 10 | className = `${className} selected`; 11 | } 12 | 13 | return ( 14 | 15 | {React.cloneElement(children, { className })} 16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /components/layout/Layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Navbar from './Navbar'; 3 | 4 | function Layout({ children }) { 5 | return ( 6 |
7 | 8 |
{children}
9 |
10 | ); 11 | } 12 | 13 | export default Layout; 14 | -------------------------------------------------------------------------------- /components/layout/Navbar.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import React from 'react'; 3 | import { signIn, signOut, useSession } from 'next-auth/client'; 4 | import { ActiveLink } from './ActiveLink'; 5 | 6 | function Navbar() { 7 | const [session, loading] = useSession(); 8 | 9 | const protectedRoutes = [ 10 | { route: '/profile', label: 'Profil' }, 11 | { route: '/dashboard', label: 'dashboard' }, 12 | ]; 13 | const normalRoutes = [ 14 | { route: '/', label: 'Home' }, 15 | { route: '/dashboard', label: 'Dashboard' }, 16 | ]; 17 | 18 | const protectedLinks = protectedRoutes.map((i) => ( 19 | 20 | {i.label} 21 | 22 | )); 23 | 24 | const normalLinks = normalRoutes.map((i) => ( 25 | 26 | {i.label} 27 | 28 | )); 29 | return ( 30 | 57 | ); 58 | } 59 | 60 | export default Navbar; 61 | -------------------------------------------------------------------------------- /context/Actions.js: -------------------------------------------------------------------------------- 1 | const ACTIONS = { 2 | TOGGLE_MODAL: 'TOGGLE_MODAL', 3 | }; 4 | export default ACTIONS; 5 | -------------------------------------------------------------------------------- /context/GlobalState.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useReducer } from 'react'; 2 | import reducers from './Reducers'; 3 | export const AppContext = createContext(); 4 | 5 | export const DataProvider = ({ children }) => { 6 | const initialState = { 7 | modalOpen: false, 8 | }; 9 | const [state, dispatch] = useReducer(reducers, initialState); 10 | 11 | return {children}; 12 | }; 13 | -------------------------------------------------------------------------------- /context/Reducers.js: -------------------------------------------------------------------------------- 1 | import ACTIONS from './Actions'; 2 | 3 | const reducers = (state, action) => { 4 | switch (action.type) { 5 | case ACTIONS.OPEN_MODAL: 6 | return { 7 | ...state, 8 | modalOpen: action.payload, 9 | }; 10 | 11 | default: 12 | break; 13 | } 14 | }; 15 | export default reducers; 16 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@components/*": ["components/*"], 6 | "@styles/*": ["styles/*"], 7 | "@utils/*": ["utils/*"], 8 | "@models/*": ["models/*"], 9 | "@context/*": ["context/*"] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/gtag.js: -------------------------------------------------------------------------------- 1 | export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GOOGLE_TRACKER_ID; 2 | 3 | // https://developers.google.com/analytics/devguides/collection/gtagjs/pages 4 | export const pageview = (url) => { 5 | window.gtag('config', GA_TRACKING_ID, { 6 | page_path: url, 7 | }); 8 | }; 9 | 10 | // https://developers.google.com/analytics/devguides/collection/gtagjs/events 11 | export const event = ({ action, category, label, value }) => { 12 | window.gtag('event', action, { 13 | event_category: category, 14 | event_label: label, 15 | value: value, 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /models/School.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import mongoosePaginate from 'mongoose-paginate-v2'; 3 | const SchoolSchema = new mongoose.Schema( 4 | { 5 | name: { 6 | type: 'String', 7 | lowercase: true, 8 | }, 9 | il: 'String', 10 | ilce: 'String', 11 | kont: 'Number', 12 | tercihEdenler: [ 13 | { 14 | type: mongoose.Types.ObjectId, 15 | ref: 'user', 16 | }, 17 | ], 18 | yorumlar: [ 19 | { 20 | kullanici: 'String', 21 | yorum: 'String', 22 | }, 23 | { timestamps: true }, 24 | ], 25 | }, 26 | { 27 | timestamps: true, 28 | } 29 | ); 30 | SchoolSchema.plugin(mongoosePaginate); 31 | export default mongoose.models.School || mongoose.model('School', SchoolSchema); 32 | -------------------------------------------------------------------------------- /models/User.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | const userSchema = new mongoose.Schema( 4 | { 5 | name: String, 6 | email: String, 7 | image: String, 8 | }, 9 | { 10 | timestamps: true, 11 | } 12 | ); 13 | 14 | export default mongoose.models.user || mongoose.model('user', userSchema); 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-tailwindcss", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "autoprefixer": "^10.0.4", 12 | "axios": "^0.21.1", 13 | "mongodb": "^3.6.4", 14 | "mongoose": "^5.11.18", 15 | "mongoose-paginate-v2": "^1.3.16", 16 | "next": "latest", 17 | "next-auth": "^3.6.0", 18 | "postcss": "^8.1.10", 19 | "react": "^17.0.1", 20 | "react-dom": "^17.0.1", 21 | "react-query": "^3.12.0", 22 | "react-table": "^7.6.3", 23 | "tailwindcss": "^2.0.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import { Provider } from 'next-auth/client'; 2 | import '../styles/globals.css'; 3 | import { DataProvider } from '@context/GlobalState'; 4 | import Layout from '@components/layout/Layout'; 5 | import { initRouterListeners } from '@utils/scrollRestoration'; 6 | import { QueryClient, QueryClientProvider } from 'react-query'; 7 | 8 | initRouterListeners(); 9 | 10 | function MyApp({ Component, pageProps }) { 11 | const queryClient = new QueryClient(); 12 | 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | } 25 | 26 | export default MyApp; 27 | -------------------------------------------------------------------------------- /pages/_document.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import Document, { Html, Head, Main, NextScript } from 'next/document'; 4 | 5 | import { GA_TRACKING_ID } from '../lib/gtag'; 6 | 7 | class MyDocument extends Document { 8 | render() { 9 | return ( 10 | 11 | 12 |