master
Kar 2022-09-07 00:13:55 +05:30
commit 0faf7e9d31
43 changed files with 7565 additions and 0 deletions

2
.eslintignore Normal file
View File

@ -0,0 +1,2 @@
node_modules
out

84
.eslintrc Normal file
View File

@ -0,0 +1,84 @@
{
// Configuration for JavaScript files
"extends": [
"airbnb-base",
"next/core-web-vitals", // Needed to avoid warning in next.js build: 'The Next.js plugin was not detected in your ESLint configuration'
"plugin:prettier/recommended"
],
"rules": {
"prettier/prettier": [
"error",
{
"singleQuote": true,
"endOfLine": "auto"
}
]
},
"overrides": [
// Configuration for TypeScript files
{
"files": ["**/*.ts", "**/*.tsx"],
"plugins": [
"@typescript-eslint",
"unused-imports",
"tailwindcss",
"simple-import-sort"
],
"extends": [
"plugin:tailwindcss/recommended",
"airbnb-typescript",
"next/core-web-vitals",
"plugin:prettier/recommended"
],
"parserOptions": {
"project": "./tsconfig.json"
},
"rules": {
"prettier/prettier": [
"error",
{
"singleQuote": true,
"endOfLine": "auto"
}
],
"react/destructuring-assignment": "off", // Vscode doesn't support automatically destructuring, it's a pain to add a new variable
"jsx-a11y/anchor-is-valid": "off", // Next.js use his own internal link system
"react/require-default-props": "off", // Allow non-defined react props as undefined
"react/jsx-props-no-spreading": "off", // _app.tsx uses spread operator and also, react-hook-form
"react-hooks/exhaustive-deps": "off", // Incorrectly report needed dependency with Next.js router
"@next/next/no-img-element": "off", // We currently not using next/image because it isn't supported with SSG mode
"@typescript-eslint/comma-dangle": "off", // Avoid conflict rule between Eslint and Prettier
"@typescript-eslint/consistent-type-imports": "error", // Ensure `import type` is used when it's necessary
"import/prefer-default-export": "off", // Named export is easier to refactor automatically
"simple-import-sort/imports": "error", // Import configuration for `eslint-plugin-simple-import-sort`
"simple-import-sort/exports": "error", // Export configuration for `eslint-plugin-simple-import-sort`
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"error",
{ "argsIgnorePattern": "^_" }
]
}
},
// Configuration for testing
{
"files": ["**/*.test.ts", "**/*.test.tsx"],
"plugins": ["jest", "jest-formatting", "testing-library", "jest-dom"],
"extends": [
"plugin:jest/recommended",
"plugin:jest-formatting/recommended",
"plugin:testing-library/react",
"plugin:jest-dom/recommended"
]
},
// Configuration for e2e testing (Cypress)
{
"files": ["**/*.cy.ts"],
"plugins": ["cypress"],
"extends": ["plugin:cypress/recommended"],
"parserOptions": {
"project": "./cypress/tsconfig.json"
}
}
]
}

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
/node_modules
/.pnp
.pnp.js
/coverage
/.next
/out
.swc/
/build
cypress/screenshots
cypress/videos
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
.env*.local
.env

4
.husky/commit-msg Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no -- commitlint --edit $1

5
.husky/pre-commit Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Disable concurent to run `check-types` after ESLint in lint-staged
npx lint-staged --concurrent false

12
LICENSE Normal file
View File

@ -0,0 +1,12 @@
MUKTI
license Mukti/Moksh - 'the liberation' is a basic realization that the result of distribution or giving away does not always come short, specially when things are non physical. MUKTI is not a license but a step towards a license free civilization.
Human race is better with 'some sense' rather a bunch of license. [have some sense, not licenses.]
till all the licenses are deprecated and decentralize [ specially for software, hardware, education, experience,even the network protocol (internet) ], lets use freedom centric and/or having "take if it helps you, just don't sue us - kind of mentality" licenses like GPL, MIT, BSD ...
this will not be registered / certified / legalise in any manner, as it's not about enforcement - it is about freedom to share.
example of 'some sense':
verbose incremental documentation with versioning, attaching contributor information.
try to make some OFFERING to the contributor or the contributing organization with or without a subscription / support.
maximize the use of freedom centric tools[hardware, software, os ...] enen if it costs and costs more, try not to be the product.

6
README.md Normal file
View File

@ -0,0 +1,6 @@
```shell
yarn
yarn dev
```

6
__mocks__/next/router.ts Normal file
View File

@ -0,0 +1,6 @@
// The easiest solution to mock `next/router`: https://github.com/vercel/next.js/issues/7479
export const useRouter = () => {
return {
basePath: '.',
};
};

1
commitlint.config.js Normal file
View File

@ -0,0 +1 @@
module.exports = { extends: ['@commitlint/config-conventional'] };

8
cypress.config.js Normal file
View File

@ -0,0 +1,8 @@
/* eslint-disable import/no-extraneous-dependencies */
const { defineConfig } = require('cypress');
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
},
});

View File

@ -0,0 +1,27 @@
describe('Seo metadata', () => {
describe('Verify SEO Metadata', () => {
it('should render SEO metadata on Index page', () => {
cy.visit('/');
// The Index page should have a page title
cy.title().should('not.be.empty');
// The Index page should also contain a meta description for SEO
cy.get('head meta[name="description"]')
.invoke('attr', 'content')
.should('not.be.empty');
});
it('should render SEO metadata on About page', () => {
cy.visit('/about');
// The About page should have a page title
cy.title().should('not.be.empty');
// The About page should also contain a meta description for SEO
cy.get('head meta[name="description"]')
.invoke('attr', 'content')
.should('not.be.empty');
});
});
});

View File

@ -0,0 +1,25 @@
describe('Navigation', () => {
describe('Static pages', () => {
it('should navigate to the about page', () => {
// Start from the index page
cy.visit('/');
// The index page should contain an h1
cy.findByRole('heading', {
name: 'Boilerplate code for your Nextjs project with Tailwind CSS',
});
// Find a link containing "About" text and click it
cy.findByRole('link', { name: 'About' }).click();
// The new url should include "/about"
cy.url().should('include', '/about');
// The new page should contain two "lorem ipsum" paragraphs
cy.findAllByText('Lorem ipsum dolor sit amet', { exact: false }).should(
'have.length',
2
);
});
});
});

View File

@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

21
cypress/support/e2e.ts Normal file
View File

@ -0,0 +1,21 @@
/* eslint-disable import/no-extraneous-dependencies */
// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import '@testing-library/cypress/add-commands';
// Alternatively you can use CommonJS syntax:
// require('./commands')

12
cypress/tsconfig.json Normal file
View File

@ -0,0 +1,12 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"types": ["node", "cypress", "@testing-library/cypress"],
"isolatedModules": false
},
"include": ["**/*.cy.ts"],
"exclude": []
}

35
jest.config.js Normal file
View File

@ -0,0 +1,35 @@
const nextJest = require('next/jest');
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './',
});
const customJestConfig = {
moduleNameMapper: {
// Handle module aliases (this will be automatically configured for you soon)
'^@/(.*)$': '<rootDir>/src/$1',
'^@/public/(.*)$': '<rootDir>/public/$1',
},
setupFilesAfterEnv: ['./jest.setup.js'],
clearMocks: true,
collectCoverage: true,
collectCoverageFrom: [
'./src/**/*.{js,jsx,ts,tsx}',
'!./src/**/_*.{js,jsx,ts,tsx}',
'!**/*.d.ts',
'!**/node_modules/**',
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
testEnvironment: 'jest-environment-jsdom',
};
module.exports = createJestConfig(customJestConfig);

3
jest.setup.js Normal file
View File

@ -0,0 +1,3 @@
// Optional: configure or set up a testing framework before each test.
// If you delete this file, remove `setupFilesAfterEnv` from `jest.config.js`
import '@testing-library/jest-dom/extend-expect';

5
lint-staged.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
'*.{js,jsx,ts,tsx}': ['eslint --fix', 'eslint'],
'**/*.ts?(x)': () => 'npm run check-types',
'*.json': ['prettier --write'],
};

6
netlify.toml Normal file
View File

@ -0,0 +1,6 @@
[build]
publish = "out"
command = "npm run build-prod"
[build.environment]
NETLIFY_NEXT_PLUGIN_SKIP = "true"

5
next-env.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

17
next.config.js Normal file
View File

@ -0,0 +1,17 @@
/* eslint-disable import/no-extraneous-dependencies */
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({
eslint: {
dirs: ['.'],
},
poweredByHeader: false,
trailingSlash: true,
basePath: '',
// The starter code load resources from `public` folder with `router.basePath` in React components.
// So, the source code is "basePath-ready".
// You can remove `basePath` if you don't need it.
reactStrictMode: true,
});

73
package.json Normal file
View File

@ -0,0 +1,73 @@
{
"name": "boilerplate-next-tailwind",
"version": "1.0.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"build-stats": "cross-env ANALYZE=true npm run build",
"export": "next export",
"build-prod": "run-s clean build export",
"clean": "rimraf .next out",
"lint": "next lint",
"check-types": "tsc --noEmit --pretty",
"test": "jest",
"cypress": "cypress open",
"cypress:headless": "cypress run",
"e2e": "start-server-and-test dev http://localhost:3000 cypress",
"e2e:headless": "start-server-and-test dev http://localhost:3000 cypress:headless",
"prepare": "husky install"
},
"dependencies": {
"next": "^12.2.5",
"next-seo": "^5.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@commitlint/cli": "^17.1.2",
"@commitlint/config-conventional": "^17.1.0",
"@next/bundle-analyzer": "^12.2.5",
"@testing-library/cypress": "^8.0.3",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.3.0",
"@types/jest": "^29.0.0",
"@types/node": "^18.7.14",
"@types/react": "^18.0.18",
"@typescript-eslint/eslint-plugin": "^5.36.1",
"@typescript-eslint/parser": "^5.36.1",
"autoprefixer": "^10.4.8",
"cross-env": "^7.0.3",
"cssnano": "^5.1.13",
"cypress": "^10.7.0",
"eslint": "^8.23.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-next": "^12.2.5",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jest": "^27.0.1",
"eslint-plugin-jest-dom": "^4.0.2",
"eslint-plugin-jest-formatting": "^3.1.0",
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^7.0.0",
"eslint-plugin-tailwindcss": "^3.6.1",
"eslint-plugin-testing-library": "^5.6.0",
"eslint-plugin-unused-imports": "^2.0.0",
"husky": "^8.0.1",
"jest": "^29.0.1",
"jest-environment-jsdom": "^29.0.1",
"lint-staged": "^13.0.3",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.16",
"prettier": "^2.7.1",
"rimraf": "^3.0.2",
"start-server-and-test": "^1.14.0",
"tailwindcss": "^3.1.8",
"typescript": "^4.8.2"
}
}

10
postcss.config.js Normal file
View File

@ -0,0 +1,10 @@
// Please do not use the array form (like ['tailwindcss', 'postcss-preset-env'])
// it will create an unexpected error: Invalid PostCSS Plugin found: [0]
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {}),
},
};

BIN
public/apple-touch-icon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

BIN
public/favicon-16x16.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

BIN
public/favicon-32x32.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1001 B

BIN
public/favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

28
src/layouts/Meta.test.tsx Normal file
View File

@ -0,0 +1,28 @@
import { render, waitFor } from '@testing-library/react';
import type { ReactNode } from 'react';
import { Meta } from './Meta';
// Mock `next/head`: https://bradgarropy.com/blog/mocking-nextjs
jest.mock(
'next/head',
() =>
function Head(props: { children: ReactNode }) {
// eslint-disable-next-line testing-library/no-node-access
return <>{props.children}</>;
}
);
describe('Meta component', () => {
describe('Render method', () => {
it('should a page title', async () => {
const title = 'Random title';
render(<Meta title={title} description="Random description" />);
await waitFor(() => {
expect(document.title).toEqual(title);
});
});
});
});

66
src/layouts/Meta.tsx Normal file
View File

@ -0,0 +1,66 @@
import Head from 'next/head';
import { useRouter } from 'next/router';
import { NextSeo } from 'next-seo';
import { AppConfig } from '@/utils/AppConfig';
type IMetaProps = {
title: string;
description: string;
canonical?: string;
};
const Meta = (props: IMetaProps) => {
const router = useRouter();
return (
<>
<Head>
<meta charSet="UTF-8" key="charset" />
<meta
name="viewport"
content="width=device-width,initial-scale=1"
key="viewport"
/>
<link
rel="apple-touch-icon"
href={`${router.basePath}/apple-touch-icon.png`}
key="apple"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href={`${router.basePath}/favicon-32x32.png`}
key="icon32"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href={`${router.basePath}/favicon-16x16.png`}
key="icon16"
/>
<link
rel="icon"
href={`${router.basePath}/favicon.ico`}
key="favicon"
/>
</Head>
<NextSeo
title={props.title}
description={props.description}
canonical={props.canonical}
openGraph={{
title: props.title,
description: props.description,
url: props.canonical,
locale: AppConfig.locale,
site_name: AppConfig.site_name,
}}
/>
</>
);
};
export { Meta };

View File

@ -0,0 +1,18 @@
import { render, screen } from '@testing-library/react';
import About from '@/pages/about';
// The easiest solution to mock `next/router`: https://github.com/vercel/next.js/issues/7479
// The mock has been moved to `__mocks__` folder to avoid duplication
describe('About page', () => {
describe('Render method', () => {
it('should have two paragraphs of `Lorem ipsum`', () => {
render(<About />);
const paragraph = screen.getAllByText(/Lorem ipsum/);
expect(paragraph).toHaveLength(2);
});
});
});

View File

@ -0,0 +1,20 @@
import { render, screen } from '@testing-library/react';
import Index from '@/pages/index';
// The easiest solution to mock `next/router`: https://github.com/vercel/next.js/issues/7479
// The mock has been moved to `__mocks__` folder to avoid duplication
describe('Index page', () => {
describe('Render method', () => {
it('should have h1 tag', () => {
render(<Index />);
const heading = screen.getByRole('heading', {
name: /Boilerplate code/,
});
expect(heading).toBeInTheDocument();
});
});
});

9
src/pages/_app.tsx Normal file
View File

@ -0,0 +1,9 @@
import '../styles/global.css';
import type { AppProps } from 'next/app';
const MyApp = ({ Component, pageProps }: AppProps) => (
<Component {...pageProps} />
);
export default MyApp;

21
src/pages/_document.tsx Normal file
View File

@ -0,0 +1,21 @@
import Document, { Head, Html, Main, NextScript } from 'next/document';
import { AppConfig } from '@/utils/AppConfig';
// Need to create a custom _document because i18n support is not compatible with `next export`.
class MyDocument extends Document {
// eslint-disable-next-line class-methods-use-this
render() {
return (
<Html lang={AppConfig.locale}>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;

12
src/pages/about.tsx Normal file
View File

@ -0,0 +1,12 @@
import { Meta } from '@/layouts/Meta';
import { Main } from '@/templates/Main';
const About = () => (
<Main meta={<Meta title="sample" description="sample description" />}>
<p>
About descriptiondescriptiondescription
</p>
</Main>
);
export default About;

22
src/pages/index.tsx Normal file
View File

@ -0,0 +1,22 @@
import { useRouter } from 'next/router';
import { Meta } from '@/layouts/Meta';
import { Main } from '@/templates/Main';
const Index = () => {
const router = useRouter();
return (
<Main
meta={
<Meta
title="next boilerplate"
description="next boilerplate, next boilerplate. "
/>
}
>
</Main>
);
};
export default Index;

21
src/styles/global.css Normal file
View File

@ -0,0 +1,21 @@
@tailwind base;
a {
@apply text-blue-700;
}
a:hover {
@apply border-b-2 border-blue-700;
}
@tailwind components;
@tailwind utilities;
.content p {
@apply my-6;
}
.content ul {
@apply my-6;
}

View File

@ -0,0 +1,33 @@
import { render, screen, within } from '@testing-library/react';
import { Main } from './Main';
describe('Main template', () => {
describe('Render method', () => {
it('should have 3 menu items', () => {
render(<Main meta={null}>{null}</Main>);
const menuItemList = screen.getAllByRole('listitem');
expect(menuItemList).toHaveLength(3);
});
it('should have a link to support creativedesignsguru.com', () => {
render(<Main meta={null}>{null}</Main>);
const copyrightSection = screen.getByText(/© Copyright/);
const copyrightLink = within(copyrightSection).getByRole('link');
/*
* PLEASE READ THIS SECTION
* We'll really appreciate if you could have a link to our website
* The link doesn't need to appear on every pages, one link on one page is enough.
* Thank you for your support it'll mean a lot for us.
*/
expect(copyrightLink).toHaveAttribute(
'href',
'https://creativedesignsguru.com'
);
});
});
});

55
src/templates/Main.tsx Normal file
View File

@ -0,0 +1,55 @@
import Link from 'next/link';
import type { ReactNode } from 'react';
import { AppConfig } from '@/utils/AppConfig';
type IMainProps = {
meta: ReactNode;
children: ReactNode;
};
const Main = (props: IMainProps) => (
<div className="w-full px-1 text-gray-700 antialiased">
{props.meta}
<div className="mx-auto max-w-screen-md">
<div className="border-b border-gray-300">
<div className="pt-16 pb-8">
<div className="text-3xl font-bold text-gray-900">
{AppConfig.title}
</div>
<div className="text-xl">{AppConfig.description}</div>
</div>
<div>
<ul className="flex flex-wrap text-xl">
<li className="mr-6">
<Link href="/">
<a className="border-none text-gray-700 hover:text-gray-900">
Home
</a>
</Link>
</li>
<li className="mr-6">
<Link href="/about/">
<a className="border-none text-gray-700 hover:text-gray-900">
About
</a>
</Link>
</li>
<li className="mr-6">
asd
</li>
</ul>
</div>
</div>
<div className="content py-5 text-xl">{props.children}</div>
<div className="border-t border-gray-300 py-8 text-center text-sm">
fgfvgf
</div>
</div>
</div>
);
export { Main };

6
src/utils/AppConfig.ts Normal file
View File

@ -0,0 +1,6 @@
export const AppConfig = {
site_name: 'Starter',
title: 'Nextjs Starter',
description: 'Starter code for your Nextjs Boilerplate with Tailwind CSS',
locale: 'en',
};

45
tailwind.config.js Normal file
View File

@ -0,0 +1,45 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{js,ts,jsx,tsx}'],
theme: {
fontSize: {
xs: '0.75rem',
sm: '0.875rem',
base: '1rem',
lg: '1.125rem',
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '1.875rem',
'4xl': '2.25rem',
'5xl': '3rem',
'6xl': '4rem',
},
extend: {
colors: {
gray: {
100: '#f7fafc',
200: '#edf2f7',
300: '#e2e8f0',
400: '#cbd5e0',
500: '#a0aec0',
600: '#718096',
700: '#4a5568',
800: '#2d3748',
900: '#1a202c',
},
blue: {
100: '#ebf8ff',
200: '#bee3f8',
300: '#90cdf4',
400: '#63b3ed',
500: '#4299e1',
600: '#3182ce',
700: '#2b6cb0',
800: '#2c5282',
900: '#2a4365',
},
},
},
},
plugins: [],
};

46
tsconfig.json Normal file
View File

@ -0,0 +1,46 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"removeComments": true,
"preserveConstEnums": true,
"strict": true,
"alwaysStrict": true,
"strictNullChecks": true,
"noUncheckedIndexedAccess": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"allowUnreachableCode": false,
"noFallthroughCasesInSwitch": true,
"target": "es5",
"outDir": "out",
"declaration": true,
"sourceMap": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"allowJs": false,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"jsx": "preserve",
"noEmit": true,
"isolatedModules": true,
"incremental": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@/public/*": ["./public/*"]
}
},
"exclude": ["./out/**/*", "./node_modules/**/*", "**/*.cy.ts"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}

6767
yarn.lock Normal file

File diff suppressed because it is too large Load Diff