init
commit
0faf7e9d31
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
out
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx --no -- commitlint --edit $1
|
|
@ -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
|
|
@ -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.
|
|
@ -0,0 +1,6 @@
|
|||
// The easiest solution to mock `next/router`: https://github.com/vercel/next.js/issues/7479
|
||||
export const useRouter = () => {
|
||||
return {
|
||||
basePath: '.',
|
||||
};
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
module.exports = { extends: ['@commitlint/config-conventional'] };
|
|
@ -0,0 +1,8 @@
|
|||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
const { defineConfig } = require('cypress');
|
||||
|
||||
module.exports = defineConfig({
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost:3000',
|
||||
},
|
||||
});
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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"
|
||||
}
|
|
@ -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')
|
|
@ -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": []
|
||||
}
|
|
@ -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);
|
|
@ -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';
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
'*.{js,jsx,ts,tsx}': ['eslint --fix', 'eslint'],
|
||||
'**/*.ts?(x)': () => 'npm run check-types',
|
||||
'*.json': ['prettier --write'],
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
[build]
|
||||
publish = "out"
|
||||
command = "npm run build-prod"
|
||||
|
||||
[build.environment]
|
||||
NETLIFY_NEXT_PLUGIN_SKIP = "true"
|
|
@ -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.
|
|
@ -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,
|
||||
});
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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: {} } : {}),
|
||||
},
|
||||
};
|
Binary file not shown.
After Width: | Height: | Size: 8.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 254 KiB |
Binary file not shown.
After Width: | Height: | Size: 445 B |
Binary file not shown.
After Width: | Height: | Size: 1001 B |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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 };
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
import '../styles/global.css';
|
||||
|
||||
import type { AppProps } from 'next/app';
|
||||
|
||||
const MyApp = ({ Component, pageProps }: AppProps) => (
|
||||
<Component {...pageProps} />
|
||||
);
|
||||
|
||||
export default MyApp;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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 };
|
|
@ -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',
|
||||
};
|
|
@ -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: [],
|
||||
};
|
|
@ -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"]
|
||||
}
|
Loading…
Reference in New Issue