├── src ├── ui │ ├── button │ │ ├── index.ts │ │ └── button.tsx │ ├── dialog │ │ ├── index.ts │ │ └── dialog.tsx │ └── loading-indicator │ │ ├── index.ts │ │ ├── linear-loading-indicator.tsx │ │ └── circular-loading-indicator.tsx ├── main.scss ├── routes │ ├── index.ts │ └── app-route.tsx ├── core │ ├── splash-screen │ │ ├── index.ts │ │ ├── splash-screen.tsx │ │ └── splash-screen.scss │ └── error │ │ ├── error-boundary │ │ ├── index.ts │ │ └── error-boundary.tsx │ │ ├── index.ts │ │ └── not-found-page │ │ └── not-found-page.tsx ├── features │ ├── auth │ │ ├── routes │ │ │ ├── index.ts │ │ │ └── auth-routes.tsx │ │ └── views │ │ │ ├── sign-in-page │ │ │ └── sign-in-page.tsx │ │ │ └── sign-up-page │ │ │ └── sign-up-page.tsx │ ├── home │ │ ├── routes │ │ │ ├── index.ts │ │ │ └── home-routes.tsx │ │ ├── components │ │ │ ├── Feature │ │ │ │ └── Feature.tsx │ │ │ ├── Header │ │ │ │ └── Header.tsx │ │ │ ├── Navbar │ │ │ │ └── Navbar.tsx │ │ │ └── Footer │ │ │ │ └── Footer.tsx │ │ └── views │ │ │ └── home │ │ │ └── home.tsx │ ├── users │ │ ├── routes │ │ │ ├── index.ts │ │ │ └── users-routes.tsx │ │ └── views │ │ │ ├── users-page │ │ │ ├── users-page.tsx │ │ │ └── users-page.unit.tsx │ │ │ ├── user-detail-page │ │ │ └── user-detail-page.tsx │ │ │ └── user-creation-page │ │ │ └── user-creation-page.tsx │ └── dashboard │ │ ├── routes │ │ ├── index.ts │ │ └── dashboard-routes.tsx │ │ └── views │ │ └── dashboard-page │ │ └── dashboard-page.tsx ├── assets │ ├── styles │ │ └── tailwind.css │ └── logo.png ├── layout │ ├── portal-layout │ │ ├── components │ │ │ └── topbar │ │ │ │ └── topbar.tsx │ │ ├── portal-layout.tsx │ │ └── stacked-layout │ │ │ └── stacked-layout.tsx │ └── minimal-layout │ │ └── minimal-layout.tsx ├── main.tsx └── app.tsx ├── .browserslistrc ├── cypress.json ├── .husky ├── pre-commit └── commit-msg ├── commitlint.config.js ├── docs └── assets │ ├── logo.png │ └── react-stater-template.png ├── public ├── images │ ├── logo.png │ ├── react-stater-template.png │ ├── services-shape-1.svg │ ├── services-shape.svg │ ├── about-shape-1.svg │ ├── about-shape-2.svg │ ├── banner-bg.svg │ └── about1.svg └── favicon.svg ├── .editorconfig ├── postcss.config.js ├── cypress ├── tsconfig.json ├── fixtures │ └── example.json ├── integration │ └── example.spec.ts ├── .eslintrc.json ├── support │ ├── index.ts │ └── commands.ts └── plugins │ └── index.ts ├── .eslintignore ├── tailwind.config.js ├── jest.setup.ts ├── CHANGELOG.md ├── .vscode ├── extensions.json └── settings.json ├── .prettierrc.json ├── tsconfig.json ├── vite.config.ts ├── LICENSE ├── .github ├── workflows │ └── pull-request-checks.yml └── pull_request_template.md ├── .eslintrc.json ├── index.html ├── package.json ├── .gitignore ├── README.md └── jest.config.ts /src/ui/button/index.ts: -------------------------------------------------------------------------------- 1 | export * from './button'; 2 | -------------------------------------------------------------------------------- /src/ui/dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dialog'; 2 | -------------------------------------------------------------------------------- /src/main.scss: -------------------------------------------------------------------------------- 1 | @import './assets/styles/tailwind.css'; 2 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults 2 | not IE 11 3 | not op_mini all 4 | -------------------------------------------------------------------------------- /src/routes/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './app-route'; 2 | -------------------------------------------------------------------------------- /src/core/splash-screen/index.ts: -------------------------------------------------------------------------------- 1 | export * from './splash-screen'; 2 | -------------------------------------------------------------------------------- /src/core/error/error-boundary/index.ts: -------------------------------------------------------------------------------- 1 | export * from './error-boundary'; 2 | -------------------------------------------------------------------------------- /src/features/auth/routes/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './auth-routes'; 2 | -------------------------------------------------------------------------------- /src/features/home/routes/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './home-routes'; 2 | -------------------------------------------------------------------------------- /src/features/users/routes/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './users-routes'; 2 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3000", 3 | "video": false 4 | } 5 | -------------------------------------------------------------------------------- /src/features/dashboard/routes/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './dashboard-routes'; 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /src/ui/dialog/dialog.tsx: -------------------------------------------------------------------------------- 1 | export const Dialog = () => { 2 | return
Dialog
; 3 | }; 4 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | }; 4 | -------------------------------------------------------------------------------- /src/assets/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/ui/button/button.tsx: -------------------------------------------------------------------------------- 1 | export const Button = () => { 2 | return ; 3 | }; 4 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /docs/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truonghungit/react-starter-template/HEAD/docs/assets/logo.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truonghungit/react-starter-template/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truonghungit/react-starter-template/HEAD/public/images/logo.png -------------------------------------------------------------------------------- /src/core/error/index.ts: -------------------------------------------------------------------------------- 1 | export * from './error-boundary'; 2 | export * from './not-found-page/not-found-page'; 3 | -------------------------------------------------------------------------------- /src/layout/portal-layout/components/topbar/topbar.tsx: -------------------------------------------------------------------------------- 1 | export default function Topbar() { 2 | return
Topbar
; 3 | } 4 | -------------------------------------------------------------------------------- /src/ui/loading-indicator/index.ts: -------------------------------------------------------------------------------- 1 | export * from './circular-loading-indicator'; 2 | export * from './linear-loading-indicator'; 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | -------------------------------------------------------------------------------- /src/features/users/views/users-page/users-page.tsx: -------------------------------------------------------------------------------- 1 | export default function UsersPage() { 2 | return
Users Page work
; 3 | } 4 | -------------------------------------------------------------------------------- /src/ui/loading-indicator/linear-loading-indicator.tsx: -------------------------------------------------------------------------------- 1 | export function LinearLoadingIndicator() { 2 | return
Loading
; 3 | } 4 | -------------------------------------------------------------------------------- /docs/assets/react-stater-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truonghungit/react-starter-template/HEAD/docs/assets/react-stater-template.png -------------------------------------------------------------------------------- /public/images/react-stater-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truonghungit/react-starter-template/HEAD/public/images/react-stater-template.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'tailwindcss': {}, 4 | 'postcss-import': {}, 5 | 'autoprefixer': {}, 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /src/features/users/views/user-detail-page/user-detail-page.tsx: -------------------------------------------------------------------------------- 1 | export default function UserDetailPage() { 2 | return
Users Page work
; 3 | } 4 | -------------------------------------------------------------------------------- /src/features/dashboard/views/dashboard-page/dashboard-page.tsx: -------------------------------------------------------------------------------- 1 | export default function DashboardPage() { 2 | return
Dashboard page work
; 3 | } 4 | -------------------------------------------------------------------------------- /src/features/users/views/user-creation-page/user-creation-page.tsx: -------------------------------------------------------------------------------- 1 | export default function UserCreationPage() { 2 | return
User Creation Page work
; 3 | } 4 | -------------------------------------------------------------------------------- /cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["es5", "dom"], 5 | "types": ["node", "cypress"] 6 | }, 7 | "include": ["./**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /src/layout/portal-layout/portal-layout.tsx: -------------------------------------------------------------------------------- 1 | import StackedLayout from './stacked-layout/stacked-layout'; 2 | 3 | export default function PortalLayout() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | # Ignore artifacts: 4 | coverage/ 5 | dist/ 6 | public/ 7 | __mocks__/ 8 | 9 | commitlint.config.js 10 | jest.config.js 11 | postcss.config.js 12 | *.d.ts 13 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ['./src/**/*.html', './src/**/*.{js,jsx,ts,tsx}'], 3 | theme: { 4 | extend: {}, 5 | }, 6 | plugins: [require('@tailwindcss/forms')], 7 | }; 8 | -------------------------------------------------------------------------------- /src/layout/minimal-layout/minimal-layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router-dom'; 2 | 3 | export default function MinimalLayout() { 4 | return ( 5 | <> 6 | 7 | 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /cypress/integration/example.spec.ts: -------------------------------------------------------------------------------- 1 | describe('My First Test', () => { 2 | it('visits the app root url', () => { 3 | cy.visit('/'); 4 | cy.get("[data-testid='title']").should('contain.text', 'Hello React + TypeScript + Vite'); 5 | }); 6 | }); 7 | 8 | export {}; 9 | -------------------------------------------------------------------------------- /jest.setup.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | import '@testing-library/jest-dom/extend-expect'; 7 | -------------------------------------------------------------------------------- /src/features/dashboard/routes/dashboard-routes.tsx: -------------------------------------------------------------------------------- 1 | import { RouteObject } from 'react-router-dom'; 2 | 3 | import DashboardPage from '../views/dashboard-page/dashboard-page'; 4 | 5 | const dashboardRoutes: Array = [ 6 | { 7 | path: 'dashboard', 8 | element: , 9 | }, 10 | ]; 11 | 12 | export default dashboardRoutes; 13 | -------------------------------------------------------------------------------- /src/features/home/routes/home-routes.tsx: -------------------------------------------------------------------------------- 1 | import { lazy } from 'react'; 2 | import { RouteObject } from 'react-router-dom'; 3 | 4 | const Home = lazy(() => import('../views/home/home')); 5 | 6 | const homeRoutes: Array = [ 7 | { 8 | path: '', 9 | index: true, 10 | element: , 11 | }, 12 | ]; 13 | 14 | export default homeRoutes; 15 | -------------------------------------------------------------------------------- /src/features/users/views/users-page/users-page.unit.tsx: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | 3 | import { render } from '@testing-library/react'; 4 | 5 | import UsersPage from './users-page'; 6 | 7 | describe('UsersPage', () => { 8 | it('should', () => { 9 | const { getByText } = render(); 10 | getByText('Users Page work'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/core/splash-screen/splash-screen.tsx: -------------------------------------------------------------------------------- 1 | import './splash-screen.scss'; 2 | 3 | export function SplashScreen() { 4 | return ( 5 |
6 | Coffee and Code Logo 7 |
8 |
9 |
10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.0 (2022-05-12) 2 | 3 | ### Features 4 | 5 | - Add ReactJS 17 6 | - Add TypeScript 7 | - Add Vite 8 | - Add React Router 6 9 | - Add ESLint & Prettier 10 | - Add Commitlint & Commitizen 11 | - Add Jest & Testing Library 12 | - Add Cypress 13 | - Add PULL_REQUEST_TEMPLATE 14 | - Add Tailwind CSS 15 | - Add Home page 16 | - Add Sign In & Sign Up pages 17 | - Add Stacked layout 18 | -------------------------------------------------------------------------------- /cypress/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:cypress/recommended"], 3 | "plugins": ["cypress"], 4 | "rules": { 5 | "cypress/no-force": "warn", 6 | "cypress/assertion-before-screenshot": "warn", 7 | "cypress/require-data-selectors": "warn", 8 | "cypress/no-pause": "error" 9 | }, 10 | "env": { 11 | "cypress/globals": true // enable Cypress global variables. 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "csstools.postcss", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode", 6 | "orta.vscode-jest", 7 | "csstools.postcss", 8 | "bradlc.vscode-tailwindcss", 9 | "editorconfig.editorconfig", 10 | "eamodio.gitlens", 11 | "mgmcdermott.vscode-language-babel", 12 | "streetsidesoftware.code-spell-checker" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import './main.scss'; 2 | 3 | import { StrictMode } from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import { BrowserRouter } from 'react-router-dom'; 6 | 7 | import App from './app'; 8 | 9 | const rootElement = document.getElementById('root'); 10 | 11 | ReactDOM.render( 12 | 13 | 14 | 15 | 16 | , 17 | rootElement, 18 | ); 19 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "quoteProps": "as-needed", 8 | "jsxSingleQuote": true, 9 | "trailingComma": "all", 10 | "bracketSpacing": true, 11 | "bracketSameLine": false, 12 | "arrowParens": "avoid", 13 | "htmlWhitespaceSensitivity": "css", 14 | "vueIndentScriptAndStyle": false, 15 | "endOfLine": "auto" 16 | } 17 | -------------------------------------------------------------------------------- /src/app.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from 'react'; 2 | import { useRoutes } from 'react-router-dom'; 3 | 4 | import { ErrorBoundary, ErrorFallback } from '@/core/error'; 5 | import { SplashScreen } from '@/core/splash-screen'; 6 | 7 | import appRoutes from './routes'; 8 | 9 | export default function App() { 10 | const routes = useRoutes(appRoutes); 11 | 12 | return ( 13 | 14 | }>{routes} 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/features/auth/routes/auth-routes.tsx: -------------------------------------------------------------------------------- 1 | import { lazy } from 'react'; 2 | import { RouteObject } from 'react-router-dom'; 3 | 4 | const SignInPage = lazy(() => import('../views/sign-in-page/sign-in-page')); 5 | const SignUpPage = lazy(() => import('../views/sign-up-page/sign-up-page')); 6 | 7 | const authRoutes: Array = [ 8 | { 9 | path: 'sign-in', 10 | element: , 11 | }, 12 | { 13 | path: 'sign-up', 14 | element: , 15 | }, 16 | ]; 17 | 18 | export default authRoutes; 19 | -------------------------------------------------------------------------------- /public/images/services-shape-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/images/services-shape.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/features/users/routes/users-routes.tsx: -------------------------------------------------------------------------------- 1 | import { lazy } from 'react'; 2 | import { RouteObject } from 'react-router-dom'; 3 | 4 | const UsersPage = lazy(() => import('../views/users-page/users-page')); 5 | const UserDetailPage = lazy(() => import('../views/users-page/users-page')); 6 | const UserCreationPage = lazy(() => import('../views/user-creation-page/user-creation-page')); 7 | 8 | const usersRoutes: Array = [ 9 | { 10 | path: 'users', 11 | element: , 12 | }, 13 | { 14 | path: 'users/create', 15 | element: , 16 | }, 17 | { 18 | path: 'users/:id', 19 | element: , 20 | }, 21 | ]; 22 | 23 | export default usersRoutes; 24 | -------------------------------------------------------------------------------- /cypress/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.ts is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.ts using ES2015 syntax: 17 | import './commands'; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "lib": ["dom", "dom.iterable", "esnext", "scripthost"], 6 | "allowJs": false, 7 | "allowSyntheticDefaultImports": true, 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "isolatedModules": true, 11 | "jsx": "react-jsx", 12 | "moduleResolution": "Node", 13 | "noEmit": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "resolveJsonModule": true, 16 | "skipLibCheck": true, 17 | "strict": true, 18 | "sourceMap": true, 19 | "types": ["vite/client", "node", "jest", "@testing-library/jest-dom"], 20 | "baseUrl": ".", 21 | "paths": { 22 | "@/*": ["src/*"] 23 | } 24 | }, 25 | "include": ["vite.config.*", "jest.setup.ts", "src/**/*", "src/**/*.tsx"] 26 | } 27 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import legacy from '@vitejs/plugin-legacy'; 2 | import reactRefresh from '@vitejs/plugin-react-refresh'; 3 | import { fileURLToPath } from 'url'; 4 | import { defineConfig } from 'vite'; 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [legacy(), reactRefresh()], 9 | esbuild: { 10 | jsxInject: `import React from 'react'`, // automatically import React in jsx files 11 | }, 12 | resolve: { 13 | alias: { 14 | // for TypeScript path alias import like : @/x/y/z 15 | '@': fileURLToPath(new URL('./src', import.meta.url)), 16 | }, 17 | }, 18 | server: { 19 | proxy: { 20 | '/api': { 21 | target: 'http://localhost:8080', 22 | secure: false, 23 | rewrite: path => path.replace(/^\/api/, ''), 24 | }, 25 | }, 26 | open: true, 27 | }, 28 | }); 29 | -------------------------------------------------------------------------------- /src/routes/app-route.tsx: -------------------------------------------------------------------------------- 1 | import { RouteObject } from 'react-router-dom'; 2 | 3 | import { NotFoundPage } from '@/core/error'; 4 | 5 | import authRoutes from '../features/auth/routes'; 6 | import dashboardRoutes from '../features/dashboard/routes'; 7 | import homeRoutes from '../features/home/routes'; 8 | import usersRoutes from '../features/users/routes'; 9 | import MinimalLayout from '../layout/minimal-layout/minimal-layout'; 10 | import PortalLayout from '../layout/portal-layout/portal-layout'; 11 | 12 | const appRoutes: Array = [ 13 | ...homeRoutes, 14 | { 15 | path: '/', 16 | element: , 17 | children: [...authRoutes], 18 | }, 19 | { 20 | path: '/', 21 | element: , 22 | children: [...dashboardRoutes, ...usersRoutes], 23 | }, 24 | { 25 | path: '*', 26 | element: , 27 | }, 28 | ]; 29 | 30 | export default appRoutes; 31 | -------------------------------------------------------------------------------- /cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Hung Pham 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 | -------------------------------------------------------------------------------- /public/images/about-shape-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/images/about-shape-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /cypress/plugins/index.ts: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | // eslint-disable-next-line @typescript-eslint/no-var-requires 16 | const path = require('path'); 17 | 18 | // eslint-disable-next-line @typescript-eslint/no-var-requires 19 | const { startDevServer } = require('@cypress/vite-dev-server'); 20 | 21 | /** 22 | * @type {Cypress.PluginConfig} 23 | */ 24 | // eslint-disable-next-line no-unused-vars 25 | module.exports = (on, config) => { 26 | // `on` is used to hook into various events Cypress emits 27 | // `config` is the resolved Cypress config 28 | on('dev-server:start', options => { 29 | return startDevServer({ 30 | options, 31 | viteConfig: { 32 | configFile: path.resolve(__dirname, '..', '..', 'vite.config.ts'), 33 | }, 34 | }); 35 | }); 36 | 37 | return config; 38 | }; 39 | -------------------------------------------------------------------------------- /.github/workflows/pull-request-checks.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Pull Request Checks 5 | 6 | on: 7 | pull_request: 8 | branches: [master] 9 | 10 | jobs: 11 | build_test: 12 | name: 'Build and Test' 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Get Yarn cache path 17 | id: yarn-cache 18 | run: echo "::set-output name=dir::$(yarn cache dir)" 19 | 20 | - name: Checkout 21 | uses: actions/checkout@v3 22 | 23 | - name: Setup Node 24 | uses: actions/setup-node@master 25 | with: 26 | node-version: 14.x 27 | 28 | - name: Load Yarn cache 29 | uses: actions/cache@v2 30 | with: 31 | path: ${{ steps.yarn-cache.outputs.dir }} 32 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 33 | restore-keys: | 34 | ${{ runner.os }}-yarn- 35 | 36 | - name: Install dependencies 37 | run: yarn install 38 | 39 | - name: Run unit tests with coverage 40 | run: yarn test:unit:coverage 41 | 42 | - name: Build app 43 | run: yarn build 44 | -------------------------------------------------------------------------------- /src/features/home/components/Feature/Feature.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | 3 | export type FeatureProps = { 4 | title: string; 5 | description: string; 6 | className?: string; 7 | }; 8 | 9 | export default function Feature({ title, description, className }: FeatureProps) { 10 | const featureClass = classNames( 11 | 'relative flex flex-col items-center justify-between col-span-4 px-8 py-8 space-y-4 overflow-hidden bg-gray-100 sm:rounded-xl', 12 | className, 13 | ); 14 | 15 | return ( 16 |
17 |
18 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 |

{title}

36 |

{description}

37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/features/home/components/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | export default function Header() { 2 | return ( 3 |
4 |

5 | Start Crafting Your{' '} 6 | 7 | Next Great Idea 8 | 9 |

10 |
11 | Simplifying the creation of landing pages, application pages, dashboard pages and so much more! 12 |
13 |
14 | 15 | 20 | Get Started 21 | 22 | 23 |
24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/ui/loading-indicator/circular-loading-indicator.tsx: -------------------------------------------------------------------------------- 1 | export type CircularLoadingIndicatorProps = { 2 | color?: string; 3 | size?: string; 4 | }; 5 | 6 | export function CircularLoadingIndicator() { 7 | // throw new Error('Hello'); 8 | 9 | return ( 10 | 17 | 21 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "workbench.colorCustomizations": { 4 | // Activity Bar 5 | // The Activity Bar is displayed either on the far left or right of the workbench and allows fast switching between views of the Side Bar. 6 | "activityBar.background": "#171520", 7 | "activityBar.dropBackground": "#34294f66", 8 | "activityBar.foreground": "#ffffffCC", 9 | "activityBarBadge.background": "#5b21b6", 10 | "activityBarBadge.foreground": "#e5e7eb", 11 | // Status Bar Colors 12 | // The Status Bar is shown in the bottom of the workbench. 13 | "statusBar.background": "#171520", 14 | "statusBar.foreground": "#5b21b6", 15 | "statusBarItem.prominentBackground": "#2a2139", 16 | "statusBarItem.prominentHoverBackground": "#34294f" 17 | }, 18 | "editor.tabSize": 2, 19 | "editor.defaultFormatter": "esbenp.prettier-vscode", // Allows prettier to format non JS/TS related files such as JSON, HTML,LESS etc 20 | "editor.formatOnSave": true, 21 | "eslint.format.enable": true, 22 | "eslint.alwaysShowStatus": true, 23 | // Use eslint to format JS/TS files 24 | "[javascript]": { 25 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 26 | }, 27 | "[javascriptreact]": { 28 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 29 | }, 30 | "[typescript]": { 31 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 32 | }, 33 | "[typescriptreact]": { 34 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 35 | }, 36 | // Show linting errors in VSCode editor 37 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], 38 | "editor.codeActionsOnSave": { 39 | "source.fixAll.eslint": true 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /public/images/banner-bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/core/error/not-found-page/not-found-page.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | 3 | export function NotFoundPage() { 4 | return ( 5 | <> 6 |
7 |
8 |
9 | 10 | Coffee and Code Logo 11 | Coffee and Code Logo 12 | 13 |
14 |
15 |
16 |

Page not found.

17 |

Sorry, we couldn’t find the page you’re looking for.

18 |
19 | 20 | Go back home 21 | 22 |
23 |
24 |
25 |
26 | 41 |
42 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "es6": true, // enable ES2015 features. 5 | "browser": true, // enable use of global browser variables like `windows`. 6 | "node": true // enable use of global node variables like `process`. 7 | }, 8 | "parser": "@typescript-eslint/parser", // Allows Eslint to understand TypeScript syntax. 9 | "plugins": [ 10 | "react", 11 | "react-hooks", 12 | "jsx-a11y", 13 | "@typescript-eslint", 14 | "import", 15 | "simple-import-sort" // Plugin for sorting imports in file. 16 | ], 17 | "extends": [ 18 | "eslint:recommended", // Eslint recommended configuration by eslint. 19 | "plugin:@typescript-eslint/recommended", // Turns on rules from TypeScript-specific plugin. 20 | "plugin:import/recommended", // Linting of ES2015+ import/export syntax. 21 | "plugin:react/recommended", // Recommended react linting configs. 22 | "plugin:react-hooks/recommended", // Recommended react hooks linting configs. 23 | "plugin:jsx-a11y/recommended", // Turns on a11y rules for JSX. 24 | "plugin:prettier/recommended" // Turns off all rules that are unnecessary or might conflict with Prettier. 25 | ], 26 | "settings": { 27 | "react": { 28 | "version": "detect" // auto-detect React version from package.json. 29 | }, 30 | "import/parsers": { 31 | "@typescript-eslint/parser": [".ts", ".tsx"] // use typescript-eslint parser for .ts|tsx files. 32 | }, 33 | "import/resolver": { 34 | "typescript": { 35 | "alwaysTryTypes": true // always try to resolve types under `@types` directory even it doesn't contain any source code, like `@types/unist`. 36 | } 37 | } 38 | }, 39 | "rules": { 40 | "import/first": "warn", 41 | "import/newline-after-import": "warn", 42 | "import/no-duplicates": "error", 43 | "import/no-named-as-default-member": "off", 44 | "import/default": "off", 45 | "simple-import-sort/imports": "warn", 46 | "simple-import-sort/exports": "warn", 47 | "react/prop-types": "off", 48 | "react/react-in-jsx-scope": "off", 49 | "react/jsx-sort-props": [ 50 | "warn", 51 | { 52 | "callbacksLast": true, 53 | "shorthandFirst": true, 54 | "ignoreCase": true, 55 | "reservedFirst": true, 56 | "noSortAlphabetically": true 57 | } 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/core/splash-screen/splash-screen.scss: -------------------------------------------------------------------------------- 1 | $color: #9333ea; 2 | 3 | .splash-screen { 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: center; 8 | position: fixed; 9 | top: 0; 10 | left: 0; 11 | right: 0; 12 | bottom: 0; 13 | background-color: #111827; 14 | color: #f9fafb; 15 | z-index: 999999; 16 | pointer-events: none; 17 | opacity: 1; 18 | visibility: visible; 19 | transition: opacity 400ms cubic-bezier(0.4, 0, 0.2, 1); 20 | } 21 | 22 | .dot-typing { 23 | position: relative; 24 | left: -9999px; 25 | width: 10px; 26 | height: 10px; 27 | border-radius: 5px; 28 | background-color: $color; 29 | color: $color; 30 | box-shadow: 9984px 0 0 0 $color, 9999px 0 0 0 $color, 10014px 0 0 0 $color; 31 | -webkit-animation: dot-typing 1.5s infinite linear; 32 | animation: dot-typing 1.5s infinite linear; 33 | } 34 | 35 | @-webkit-keyframes dot-typing { 36 | 0% { 37 | box-shadow: 9984px 0 0 0 $color, 9999px 0 0 0 $color, 10014px 0 0 0 $color; 38 | } 39 | 16.667% { 40 | box-shadow: 9984px -10px 0 0 $color, 9999px 0 0 0 $color, 10014px 0 0 0 $color; 41 | } 42 | 33.333% { 43 | box-shadow: 9984px 0 0 0 $color, 9999px 0 0 0 $color, 10014px 0 0 0 $color; 44 | } 45 | 50% { 46 | box-shadow: 9984px 0 0 0 $color, 9999px -10px 0 0 $color, 10014px 0 0 0 $color; 47 | } 48 | 66.667% { 49 | box-shadow: 9984px 0 0 0 $color, 9999px 0 0 0 $color, 10014px 0 0 0 $color; 50 | } 51 | 83.333% { 52 | box-shadow: 9984px 0 0 0 $color, 9999px 0 0 0 $color, 10014px -10px 0 0 $color; 53 | } 54 | 100% { 55 | box-shadow: 9984px 0 0 0 $color, 9999px 0 0 0 $color, 10014px 0 0 0 $color; 56 | } 57 | } 58 | 59 | @keyframes dot-typing { 60 | 0% { 61 | box-shadow: 9984px 0 0 0 $color, 9999px 0 0 0 $color, 10014px 0 0 0 $color; 62 | } 63 | 16.667% { 64 | box-shadow: 9984px -10px 0 0 $color, 9999px 0 0 0 $color, 10014px 0 0 0 $color; 65 | } 66 | 33.333% { 67 | box-shadow: 9984px 0 0 0 $color, 9999px 0 0 0 $color, 10014px 0 0 0 $color; 68 | } 69 | 50% { 70 | box-shadow: 9984px 0 0 0 $color, 9999px -10px 0 0 $color, 10014px 0 0 0 $color; 71 | } 72 | 66.667% { 73 | box-shadow: 9984px 0 0 0 $color, 9999px 0 0 0 $color, 10014px 0 0 0 $color; 74 | } 75 | 83.333% { 76 | box-shadow: 9984px 0 0 0 $color, 9999px 0 0 0 $color, 10014px -10px 0 0 $color; 77 | } 78 | 100% { 79 | box-shadow: 9984px 0 0 0 $color, 9999px 0 0 0 $color, 10014px 0 0 0 $color; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React Starter Template 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 40 | 41 | 42 | 43 | 44 | 45 |
46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # What 👋 2 | 3 | A _quick_ description outlining the context of this _pull request_. 4 | 5 | ### _Example:_ 6 | 7 | > An update to the _client-side_ validation system for the _login widget_. 8 | 9 | ## Where 🔍 10 | 11 | An overview of _"avenues"_ that are influenced/affected by this work. 12 | 13 | ### _Example:_ 14 | 15 | > - This resolves issue [`#123`](#) and [`#456`](#). 16 | > - The _login widget_ can be found on the core [`/login`](#) screen. 17 | > - Leverages the [`redux-form`](https://redux-form.com) implementation from pull request [`#789`](#). 18 | > - This validation enhancement will hide behind feature flag `[LOGIN.VALIDATION]`. 19 | 20 | ## Why 🤔 21 | 22 | Dispel any ambiguity around why this bug/feature/enhancement was required. 23 | 24 | ### _Example:_ 25 | 26 | > Although the current validation system worked from a technical perspective, there were concerns round _user_ accessibility _(specifically message location and content)_ which would impact our **WCAG 2 AA** compliance rating. 27 | 28 | ## How 💡 29 | 30 | Background on the changes/choices made to fulfill this _pull request_. 31 | 32 | ### _Example:_ 33 | 34 | > - `redux-form` has a [built in validation system](https://redux-form.com/8.1.0/examples/syncvalidation/) that fits our needs. 35 | > - The integration requires that our form `` elements conform to the [``](https://redux-form.com/8.1.0/docs/api/field.md/) abstraction _(which I created a simple HOC to achieve)_. 36 | > - `redux-form` creates its own entry _(and format)_ in the `redux` _"store"_ so there were several references in the application that needed to be updated to the new state schema. 37 | 38 | ## Note 📋 39 | 40 | Any information that does not fit into the above categories giving extra context to the _pull request_. 41 | 42 | ### _Example:_ 43 | 44 | > We endeavor to move this validation pattern into our [stand alone component architecture](#) next sprint. The _login widget_ is our initial test pilot _(to validate our validation enhancements)_. 45 | 46 | ## Demo 📺 47 | 48 | Bring clarity to the code with visual aids: 49 | 50 | - **Screenshots:** `cmd` + `shift` + `4` _(MacOS)_ | `shift` + `windows` + `s` _(Windows 11)_. 51 | - **Gifs:** [GIPHY Capture](https://giphy.com/apps/giphycapture) _(free/MacOS)_. 52 | - **Code Snippets:** [Carbon](https://carbon.now.sh/) _(free)_. 53 | - **Video:** [Loom](https://www.loom.com) 54 | 55 | If applicable, a **before** and **after** representation of your work is preferred. 56 | 57 | ### _Example:_ 58 | 59 | > ### Before 👎 🙁 60 | > 61 | > Global _invalidation_ message at the bottom of the form is visually discrete and uninformative. 62 | > 63 | > ![form-before](https://user-images.githubusercontent.com/15273233/52890596-749c0100-31ea-11e9-94d4-588b914a4fde.gif) 64 | > 65 | > ### After 👍 🙂 66 | > 67 | > Individual _invalid_ messages on a per/input basis. Message _plus_ the `` itself has an error aesthetic. 68 | > 69 | > ![form-after](https://user-images.githubusercontent.com/15273233/52890599-7796f180-31ea-11e9-9b7b-af84a1107391.gif) 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-starter-template", 3 | "version": "1.0.0", 4 | "author": { 5 | "name": "Hung Pham", 6 | "email": "truonghungit@gmail.com", 7 | "url": "https://github.com/truonghungit" 8 | }, 9 | "scripts": { 10 | "build": "npm run typecheck && vite build", 11 | "commit": "git-cz", 12 | "eslint": "eslint . --ext .ts,.tsx", 13 | "format": "prettier . --write --ignore-path .gitignore && git update-index --again", 14 | "prepare": "husky install", 15 | "serve": "npm run build && vite preview --port 3000", 16 | "start": "vite", 17 | "test:e2e": "start-server-and-test serve http://127.0.0.1:3000/ 'cypress open'", 18 | "test:e2e:ci": "start-server-and-test serve http://127.0.0.1:3000/ 'cypress run'", 19 | "test:unit": "jest --watch", 20 | "test:unit:coverage": "jest --silent --watchAll=false --coverage", 21 | "typecheck": "tsc" 22 | }, 23 | "lint-staged": { 24 | "*.{css,md,json}": [ 25 | "prettier --write" 26 | ], 27 | "*.ts?(x)": [ 28 | "eslint", 29 | "prettier --write" 30 | ], 31 | "package.json": "npx sort-package-json" 32 | }, 33 | "config": { 34 | "commitizen": { 35 | "path": "@commitlint/cz-commitlint" 36 | } 37 | }, 38 | "dependencies": { 39 | "@headlessui/react": "^1.6.1", 40 | "@heroicons/react": "^1.0.6", 41 | "classnames": "^2.3.1", 42 | "react": "^17.0.2", 43 | "react-dom": "^17.0.2", 44 | "react-router-dom": "6" 45 | }, 46 | "devDependencies": { 47 | "@commitlint/cli": "^16.2.1", 48 | "@commitlint/config-conventional": "^16.2.1", 49 | "@commitlint/cz-commitlint": "^15.0.0", 50 | "@cypress/vite-dev-server": "^2.2.2", 51 | "@tailwindcss/forms": "^0.5.1", 52 | "@testing-library/jest-dom": "^5.16.1", 53 | "@testing-library/react": "^12.1.2", 54 | "@testing-library/react-hooks": "^7.0.2", 55 | "@testing-library/user-event": "^13.5.0", 56 | "@types/jest": "^27.0.3", 57 | "@types/node": "^17.0.2", 58 | "@types/react": "^17.0.37", 59 | "@types/react-dom": "^17.0.11", 60 | "@typescript-eslint/eslint-plugin": "^5.8.0", 61 | "@typescript-eslint/parser": "^5.8.0", 62 | "@vitejs/plugin-legacy": "^1.6.4", 63 | "@vitejs/plugin-react-refresh": "^1.3.6", 64 | "autoprefixer": "^10.4.2", 65 | "commitizen": "^4.2.4", 66 | "cypress": "^9.1.1", 67 | "cz-conventional-changelog": "3.3.0", 68 | "eslint": "^8.5.0", 69 | "eslint-config-prettier": "^8.3.0", 70 | "eslint-import-resolver-typescript": "^2.5.0", 71 | "eslint-plugin-cypress": "^2.12.1", 72 | "eslint-plugin-import": "^2.25.3", 73 | "eslint-plugin-jest": "^25.3.0", 74 | "eslint-plugin-jest-dom": "^3.9.2", 75 | "eslint-plugin-jsx-a11y": "^6.5.1", 76 | "eslint-plugin-prettier": "^4.0.0", 77 | "eslint-plugin-react": "^7.27.1", 78 | "eslint-plugin-react-hooks": "^4.3.0", 79 | "eslint-plugin-simple-import-sort": "^7.0.0", 80 | "eslint-plugin-testing-library": "^5.0.1", 81 | "husky": "^7.0.4", 82 | "identity-obj-proxy": "^3.0.0", 83 | "jest": "^27.4.5", 84 | "lint-staged": "^12.1.3", 85 | "postcss": "^8.4.6", 86 | "postcss-import": "^14.0.2", 87 | "prettier": "^2.5.1", 88 | "sass": "^1.49.7", 89 | "sort-package-json": "^1.53.1", 90 | "start-server-and-test": "^1.14.0", 91 | "tailwindcss": "^3.0.18", 92 | "ts-jest": "^27.1.2", 93 | "typescript": "^4.5.4", 94 | "vite": "^2.7.4" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /public/images/about1.svg: -------------------------------------------------------------------------------- 1 | abstract -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/jetbrains,macos,node 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=jetbrains,macos,node 3 | 4 | # Local .env files 5 | *.local 6 | 7 | # Vite 8 | dist/ 9 | 10 | ### JetBrains ### 11 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 12 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 13 | 14 | # User-specific stuff 15 | .idea 16 | .idea/**/workspace.xml 17 | .idea/**/tasks.xml 18 | .idea/**/usage.statistics.xml 19 | .idea/**/dictionaries 20 | .idea/**/shelf 21 | 22 | # Generated files 23 | .idea/**/contentModel.xml 24 | 25 | # Sensitive or high-churn files 26 | .idea/**/dataSources/ 27 | .idea/**/dataSources.ids 28 | .idea/**/dataSources.local.xml 29 | .idea/**/sqlDataSources.xml 30 | .idea/**/dynamic.xml 31 | .idea/**/uiDesigner.xml 32 | .idea/**/dbnavigator.xml 33 | 34 | # File-based project format 35 | *.iws 36 | 37 | # IntelliJ 38 | out/ 39 | 40 | # mpeltonen/sbt-idea plugin 41 | .idea_modules/ 42 | 43 | # JIRA plugin 44 | atlassian-ide-plugin.xml 45 | 46 | # Cursive Clojure plugin 47 | .idea/replstate.xml 48 | 49 | # Editor-based Rest Client 50 | .idea/httpRequests 51 | 52 | ### JetBrains Patch ### 53 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 54 | 55 | # *.iml 56 | # modules.xml 57 | # .idea/misc.xml 58 | # *.ipr 59 | 60 | # Sonarlint plugin 61 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 62 | .idea/**/sonarlint/ 63 | 64 | # SonarQube Plugin 65 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 66 | .idea/**/sonarIssues.xml 67 | 68 | # Markdown Navigator plugin 69 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 70 | .idea/**/markdown-navigator.xml 71 | .idea/**/markdown-navigator-enh.xml 72 | .idea/**/markdown-navigator/ 73 | 74 | # Cache file creation bug 75 | # See https://youtrack.jetbrains.com/issue/JBR-2257 76 | .idea/$CACHE_FILE$ 77 | 78 | # CodeStream plugin 79 | # https://plugins.jetbrains.com/plugin/12206-codestream 80 | .idea/codestream.xml 81 | 82 | ### macOS ### 83 | # General 84 | .DS_Store 85 | .AppleDouble 86 | .LSOverride 87 | 88 | # Thumbnails 89 | ._* 90 | 91 | # Files that might appear in the root of a volume 92 | .DocumentRevisions-V100 93 | .fseventsd 94 | .Spotlight-V100 95 | .TemporaryItems 96 | .Trashes 97 | .VolumeIcon.icns 98 | .com.apple.timemachine.donotpresent 99 | 100 | # Directories potentially created on remote AFP share 101 | .AppleDB 102 | .AppleDesktop 103 | Network Trash Folder 104 | Temporary Items 105 | .apdisk 106 | 107 | ### Node ### 108 | # Logs 109 | logs 110 | *.log 111 | npm-debug.log* 112 | yarn-debug.log* 113 | yarn-error.log* 114 | lerna-debug.log* 115 | .pnpm-debug.log* 116 | 117 | # Diagnostic reports (https://nodejs.org/api/report.html) 118 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 119 | 120 | # Directory for instrumented libs generated by jscoverage/JSCover 121 | lib-cov 122 | 123 | # Coverage directory used by tools like istanbul / jest 124 | coverage 125 | *.lcov 126 | 127 | # Dependency directories 128 | node_modules/ 129 | 130 | # TypeScript cache 131 | *.tsbuildinfo 132 | 133 | # Optional npm cache directory 134 | .npm 135 | 136 | # Optional eslint cache 137 | .eslintcache 138 | 139 | # Microbundle cache 140 | .rpt2_cache/ 141 | .rts2_cache_cjs/ 142 | .rts2_cache_es/ 143 | .rts2_cache_umd/ 144 | 145 | # Optional REPL history 146 | .node_repl_history 147 | 148 | # Output of 'npm pack' 149 | *.tgz 150 | 151 | # dotenv environment variables file 152 | .env 153 | .env.test 154 | .env.production 155 | 156 | # Next.js build output 157 | .next 158 | out 159 | 160 | # Gatsby files 161 | .cache/ 162 | # Comment in the public line in if your project uses Gatsby and not Next.js 163 | # https://nextjs.org/blog/next-9-1#public-directory-support 164 | # public 165 | 166 | # Stores VSCode versions used for testing VSCode extensions 167 | .vscode-test 168 | 169 | # Yarn Integrity file 170 | .yarn-integrity 171 | 172 | # yarn v2 173 | .yarn/cache 174 | .yarn/unplugged 175 | .yarn/build-state.yml 176 | .yarn/install-state.gz 177 | .pnp.* 178 | 179 | # End of https://www.toptal.com/developers/gitignore/api/jetbrains,macos,node 180 | -------------------------------------------------------------------------------- /src/core/error/error-boundary/error-boundary.tsx: -------------------------------------------------------------------------------- 1 | import { Component, isValidElement } from 'react'; 2 | 3 | const changedArray = (a: Array = [], b: Array = []) => 4 | a.length !== b.length || a.some((item, index) => !Object.is(item, b[index])); 5 | 6 | export interface FallbackProps { 7 | error: Error; 8 | resetErrorBoundary: (...args: Array) => void; 9 | } 10 | 11 | interface ErrorBoundaryPropsWithComponent { 12 | onResetKeysChange?: (prevResetKeys: Array | undefined, resetKeys: Array | undefined) => void; 13 | onReset?: (...args: Array) => void; 14 | onError?: (error: Error, info: { componentStack: string }) => void; 15 | resetKeys?: Array; 16 | fallback?: never; 17 | FallbackComponent: React.ComponentType; 18 | fallbackRender?: never; 19 | } 20 | 21 | declare function FallbackRender( 22 | props: FallbackProps, 23 | ): React.ReactElement | null; 24 | 25 | interface ErrorBoundaryPropsWithRender { 26 | onResetKeysChange?: (prevResetKeys: Array | undefined, resetKeys: Array | undefined) => void; 27 | onReset?: (...args: Array) => void; 28 | onError?: (error: Error, info: { componentStack: string }) => void; 29 | resetKeys?: Array; 30 | fallback?: never; 31 | FallbackComponent?: never; 32 | fallbackRender: typeof FallbackRender; 33 | } 34 | 35 | interface ErrorBoundaryPropsWithFallback { 36 | onResetKeysChange?: (prevResetKeys: Array | undefined, resetKeys: Array | undefined) => void; 37 | onReset?: (...args: Array) => void; 38 | onError?: (error: Error, info: { componentStack: string }) => void; 39 | resetKeys?: Array; 40 | fallback: React.ReactElement | null; 41 | FallbackComponent?: never; 42 | fallbackRender?: never; 43 | } 44 | 45 | type ErrorBoundaryProps = 46 | | ErrorBoundaryPropsWithFallback 47 | | ErrorBoundaryPropsWithComponent 48 | | ErrorBoundaryPropsWithRender; 49 | 50 | type ErrorBoundaryState = { error: Error | null }; 51 | 52 | const initialState: ErrorBoundaryState = { error: null }; 53 | 54 | export class ErrorBoundary extends Component< 55 | React.PropsWithRef>, 56 | ErrorBoundaryState 57 | > { 58 | static getDerivedStateFromError(error: Error) { 59 | return { error }; 60 | } 61 | 62 | state = initialState; 63 | resetErrorBoundary = (...args: Array) => { 64 | this.props.onReset?.(...args); 65 | this.reset(); 66 | }; 67 | 68 | reset() { 69 | this.setState(initialState); 70 | } 71 | 72 | componentDidCatch(error: Error, info: React.ErrorInfo) { 73 | this.props.onError?.(error, info); 74 | } 75 | 76 | componentDidUpdate(prevProps: ErrorBoundaryProps, prevState: ErrorBoundaryState) { 77 | const { error } = this.state; 78 | const { resetKeys } = this.props; 79 | 80 | // There's an edge case where if the thing that triggered the error 81 | // happens to *also* be in the resetKeys array, we'd end up resetting 82 | // the error boundary immediately. This would likely trigger a second 83 | // error to be thrown. 84 | // So we make sure that we don't check the resetKeys on the first call 85 | // of cDU after the error is set 86 | 87 | if (error !== null && prevState.error !== null && changedArray(prevProps.resetKeys, resetKeys)) { 88 | this.props.onResetKeysChange?.(prevProps.resetKeys, resetKeys); 89 | this.reset(); 90 | } 91 | } 92 | 93 | render() { 94 | const { error } = this.state; 95 | 96 | const { fallbackRender, FallbackComponent, fallback } = this.props; 97 | 98 | if (error !== null) { 99 | const props = { 100 | error, 101 | resetErrorBoundary: this.resetErrorBoundary, 102 | }; 103 | if (isValidElement(fallback)) { 104 | return fallback; 105 | } else if (typeof fallbackRender === 'function') { 106 | return fallbackRender(props); 107 | } else if (FallbackComponent) { 108 | return ; 109 | } else { 110 | throw new Error('react-error-boundary requires either a fallback, fallbackRender, or FallbackComponent prop'); 111 | } 112 | } 113 | 114 | return this.props.children; 115 | } 116 | } 117 | 118 | export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { 119 | return ( 120 |
121 |

Something went wrong:

122 |
{error.message}
123 | 124 |
125 | ); 126 | } 127 | -------------------------------------------------------------------------------- /src/features/home/components/Navbar/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | 3 | export default function Navbar() { 4 | return ( 5 | 109 | ); 110 | } 111 | -------------------------------------------------------------------------------- /src/features/home/components/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | export default function Footer() { 2 | return ( 3 | 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # drawing React Starter Template 2 | 3 | [![Pull Request Checks](https://github.com/truonghungit/react-starter-template/actions/workflows/pull-request-checks.yml/badge.svg)](https://github.com/truonghungit/react-starter-template/actions/workflows/pull-request-checks.yml) 4 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=truonghungit_react-starter-template&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=truonghungit_react-starter-template) 5 | ![react](https://img.shields.io/badge/React-20232A?style=flat&logo=react&logoColor=61DAFB) 6 | ![typescript](https://img.shields.io/badge/TypeScript-007ACC?style=flat&logo=typescript&logoColor=white) 7 | ![stars](https://img.shields.io/github/stars/truonghungit/react-starter-template.svg) 8 | [![license](https://img.shields.io/github/license/truonghungit/react-starter-template?style=flat)](https://github.com/truonghungit/react-starter-template/blob/master/LICENSE) 9 | [![code style: prettier](https://img.shields.io/badge/code_style-Prettier-ff69b4.svg?style=flat)](https://github.com/prettier/prettier) 10 | 11 | ![React stater template](./docs/assets/react-stater-template.png) 12 | 13 | This is a [React](https://reactjs.org) + [TypeScript](https://www.typescriptlang.org/) starter template built with [Vite](https://vitejs.dev). We provide everything you'll need to get started building React Apps. 14 | 15 | 16 | #### [Live Demo](https://react-starter-template.vercel.app) 17 | #### [Changelog](https://github.com/truonghungit/react-starter-template/blob/master/CHANGELOG.md) 18 | 19 | ## 🎯 Goals 20 | 21 | ### Get started in seconds 22 | 23 | This starter allows you to focus on development rather than learn and configure build tools. 24 | We've prepared the fantastic below for you. 25 | 26 | 27 | - [Vite](https://vitejs.dev) Next Generation Frontend Tooling 28 | - [ReactJS](https://reactjs.org) with [TypeScript](https://www.typescriptlang.org) Always update to the latest version 29 | 30 | #### Testing 31 | - [Jest](https://jestjs.io/) Test runner 32 | - [Testing Library](https://testing-library.com/) Test UI from a users perspective (for React and Cypress) 33 | - [Cypress](https://www.cypress.io) 34 | 35 | #### Format and lint 36 | - [ESLint](https://eslint.org/) Enforce code standards & [Prettier](https://prettier.io/) Code formatting 37 | - [Browserslist](https://github.com/browserslist/browserslist) Supported browsers 38 | - [Commitizen](https://github.com/commitizen/cz-cli) 39 | - [Commitlint](https://commitlint.js.org) 40 | - [Lint Staged](https://github.com/okonet/lint-staged) 🚫💩 slip into your code base! 41 | 42 | #### Workflow 43 | - Github action for pull request checks 44 | - [Pull Request template](https://github.com/truonghungit/react-starter-template/blob/master/.github/pull_request_template.md) Consistent and helpful colabouration. 45 | 46 | #### Library 47 | - [React Router](https://reactrouter.com/) Single Page App **(SPA)** navigation 48 | - TailwindCSS Integrated 49 | - Headless ui & heroicons 50 | 51 | #### Example pages 52 | - Home page as landing page 53 | - Sign In, Sign Up pages, 54 | - Dashboard page 55 | 56 | ### Recent best practices 57 | 58 | This repository provide an up to date example of React application following all recent best practices in various areas 59 | 60 | ## 💻 Getting started 61 | 62 | #### Create project 63 | 64 | Clone the latest version 65 | 66 | ```bash 67 | git clone https://github.com/truonghungit/react-starter-template.git my-app 68 | cd my-app 69 | ``` 70 | 71 | [Previous versions](https://github.com/truonghungit/react-starter-template/tags) are also available for download. 72 | 73 | You can also click on **_Use this template_** on GitHub 74 | 75 | ![image](https://user-images.githubusercontent.com/9523581/167988431-84445e1c-9f64-4fbe-b93b-f0fa9d84f624.png) 76 | 77 | #### Install dependencies. 78 | 79 | ```bash 80 | yarn install 81 | ``` 82 | 83 | #### Start 84 | 85 | Start your application in local development server with hot reload at http://localhost:3000. 86 | 87 | ```bash 88 | yarn start 89 | ``` 90 | 91 | ## 🤖 Testing 92 | 93 | ### Unit tests 94 | 95 | Execute all unit tests 96 | 97 | ```bash 98 | yarn test:unit 99 | ``` 100 | 101 | Execute all unit tests and collect coverage 102 | 103 | ```bash 104 | yarn test:unit:coverage 105 | ``` 106 | 107 | ### End to end testing 108 | 109 | Run e2e tests 110 | 111 | ```bash 112 | yarn test:e2e 113 | ``` 114 | 115 | ## 😎 Make It Your Own 116 | 117 | When using this starter project to build your own application you might consider some of the following steps: 118 | 119 | - rename project in `package.json` `name` property and set appropriate version (eg `0.0.1` or `1.0.0`) 120 | - delete pre-existing `CHANGELOG.md` (you will generate your own with future releases of your features) 121 | - update the `README.md` content with your context 122 | - edit the title and Open Graph metadata properties in `index.html` 123 | - replace logo in `/public/images` folder 124 | - update github workflow if you need in `.github/workflows` 125 | 126 | 127 | ## 🌲 Branching 128 | 129 | We use [Trunk Based Development](https://trunkbaseddevelopment.com/) to accommodate short-lived branches and a _**"trunk"**_ _(our `master` branch)_ as a source of truth. 130 | 131 | - **Feature: `feature/*`** 132 | 133 | Example: `feature/JIRA-123-my-new-feature` 134 | 135 | - **Bug: `bugfix/*`** 136 | 137 | Example: `bugfix/JIRA-123-fix-an-issue` 138 | 139 | - **Release: `release/*`** 140 | 141 | Example: `release/JIRA-123-brand-new-product` 142 | 143 | ## 🏆 Making Commits 144 | 145 | We format our commit messages using [Commitizen](https://github.com/commitizen/cz-cli). This provides the project a consistent, easy structure that allows for automation opportunities. 146 | 147 | - It is **important** that you use Commitizen when making commits 148 | 149 | - In your **terminal** run `yarn commit` when making a commit to enter the interactive GUI. 150 | 151 | ## 💾 Pull Requests 152 | 153 | This project has a template that sets the pull request structure that we expect from contributors. 154 | 155 | - It is **important** to _**give**_ as much context to _**get**_ the best review from your peers. 156 | 157 | _(write the pull request that you would love to encounter yourself)_ 158 | 159 | - You do not have to fill out each section if it is not applicable. 160 | 161 | ## 🗜️ Merging 162 | 163 | We always _**Squash**_ our _Pull Requests_. This makes a `cherry-pick` from `master` to a `release/*` branch when addressing a Bug Fix easy. 164 | 165 | Make sure to _**ALWAYS**_ rebase _(not merge)_ `master` into your local branch when developing. We strive for a flat Git commit history when possible. 166 | 167 | ## 🧰 Recommended VS Code extensions 168 | 169 | - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) 170 | - [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) 171 | - [Gitlens](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens) 172 | - [Tailwind CSS](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) 173 | - [VS Code Jest](https://marketplace.visualstudio.com/items?itemName=orta.vscode-jest) 174 | 175 | ## 📝 License 176 | 177 | Copyright © 2022 truonghungit. This source code is licensed under the MIT license found in the 178 | [LICENSE](https://github.com/truonghungit/react-starter-template/blob/master/LICENSE) file. 179 | 180 | --- 181 | 182 | Made with ♥ by truonghungit. 183 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | // All imported modules in your tests should be mocked automatically 8 | // automock: false, 9 | 10 | // Stop running tests after `n` failures 11 | // bail: 0, 12 | 13 | // The directory where Jest should store its cached dependency information 14 | // cacheDirectory: "C:\\Users\\Admin\\AppData\\Local\\Temp\\jest", 15 | 16 | // Automatically clear mock calls, instances and results before every test 17 | clearMocks: true, 18 | 19 | // Indicates whether the coverage information should be collected while executing the test 20 | collectCoverage: false, 21 | 22 | // An array of glob patterns indicating a set of files for which coverage information should be collected 23 | // collectCoverageFrom: undefined, 24 | 25 | // The directory where Jest should output its coverage files 26 | coverageDirectory: 'coverage', 27 | 28 | // An array of regexp pattern strings used to skip coverage collection 29 | // coveragePathIgnorePatterns: [ 30 | // "\\\\node_modules\\\\" 31 | // ], 32 | 33 | // Indicates which provider should be used to instrument code for coverage 34 | // coverageProvider: "babel", 35 | 36 | // A list of reporter names that Jest uses when writing coverage reports 37 | // coverageReporters: [ 38 | // "json", 39 | // "text", 40 | // "lcov", 41 | // "clover" 42 | // ], 43 | 44 | // An object that configures minimum threshold enforcement for coverage results 45 | // coverageThreshold: undefined, 46 | 47 | // A path to a custom dependency extractor 48 | // dependencyExtractor: undefined, 49 | 50 | // Make calling deprecated APIs throw helpful error messages 51 | // errorOnDeprecated: false, 52 | 53 | // Force coverage collection from ignored files using an array of glob patterns 54 | // forceCoverageMatch: [], 55 | 56 | // A path to a module which exports an async function that is triggered once before all test suites 57 | // globalSetup: undefined, 58 | 59 | // A path to a module which exports an async function that is triggered once after all test suites 60 | // globalTeardown: undefined, 61 | 62 | // A set of global variables that need to be available in all test environments 63 | // globals: {}, 64 | 65 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 66 | // maxWorkers: "50%", 67 | 68 | // An array of directory names to be searched recursively up from the requiring module's location 69 | // moduleDirectories: [ 70 | // "node_modules" 71 | // ], 72 | 73 | // An array of file extensions your modules use 74 | // moduleFileExtensions: [ 75 | // "js", 76 | // "jsx", 77 | // "ts", 78 | // "tsx", 79 | // "json", 80 | // "node" 81 | // ], 82 | 83 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 84 | // moduleNameMapper: {}, 85 | 86 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 87 | // modulePathIgnorePatterns: [], 88 | 89 | // Activates notifications for test results 90 | // notify: false, 91 | 92 | // An enum that specifies notification mode. Requires { notify: true } 93 | // notifyMode: "failure-change", 94 | 95 | // A preset that is used as a base for Jest's configuration 96 | // preset: undefined, 97 | 98 | // Run tests from one or more projects 99 | // projects: undefined, 100 | 101 | // Use this configuration option to add custom reporters to Jest 102 | // reporters: undefined, 103 | 104 | // Automatically reset mock state before every test 105 | // resetMocks: false, 106 | 107 | // Reset the module registry before running each individual test 108 | // resetModules: false, 109 | 110 | // A path to a custom resolver 111 | // resolver: undefined, 112 | 113 | // Automatically restore mock state and implementation before every test 114 | // restoreMocks: false, 115 | 116 | // The root directory that Jest should scan for tests and modules within 117 | // rootDir: undefined, 118 | 119 | // A list of paths to directories that Jest should use to search for files in 120 | // roots: [ 121 | // "" 122 | // ], 123 | roots: ['/src/'], 124 | 125 | // Allows you to use a custom runner instead of Jest's default test runner 126 | // runner: "jest-runner", 127 | 128 | // The paths to modules that run some code to configure or set up the testing environment before each test 129 | // setupFiles: [], 130 | 131 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 132 | // setupFilesAfterEnv: [], 133 | setupFilesAfterEnv: ['/jest.setup.ts'], 134 | 135 | // The number of seconds after which a test is considered as slow and reported as such in the results. 136 | // slowTestThreshold: 5, 137 | 138 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 139 | // snapshotSerializers: [], 140 | 141 | // The test environment that will be used for testing 142 | testEnvironment: 'jest-environment-jsdom', 143 | 144 | // Options that will be passed to the testEnvironment 145 | // testEnvironmentOptions: {}, 146 | 147 | // Adds a location field to test results 148 | // testLocationInResults: false, 149 | 150 | // The glob patterns Jest uses to detect test files 151 | // testMatch: [ 152 | // "**/__tests__/**/*.[jt]s?(x)", 153 | // "**/?(*.)+(spec|test).[tj]s?(x)" 154 | // ], 155 | 156 | // Matches parent folder `__tests__` and filename should contain `test`. 157 | testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(unit).[tj]s?(x)'], 158 | 159 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 160 | // testPathIgnorePatterns: [ 161 | // "\\\\node_modules\\\\" 162 | // ], 163 | 164 | // The regexp pattern or array of patterns that Jest uses to detect test files 165 | // testRegex: [], 166 | 167 | // This option allows the use of a custom results processor 168 | // testResultsProcessor: undefined, 169 | 170 | // This option allows use of a custom test runner 171 | // testRunner: "jest-circus/runner", 172 | 173 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 174 | // testURL: "http://localhost", 175 | 176 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 177 | // timers: "real", 178 | 179 | // A map from regular expressions to paths to transformers 180 | // transform: undefined, 181 | transform: { 182 | '^.+\\.tsx?$': 'ts-jest', // Transform TypeScript files using ts-jest 183 | }, 184 | 185 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 186 | // transformIgnorePatterns: [ 187 | // "\\\\node_modules\\\\", 188 | // "\\.pnp\\.[^\\\\]+$" 189 | // ], 190 | 191 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 192 | // unmockedModulePathPatterns: undefined, 193 | 194 | // Indicates whether each individual test should be reported during the run 195 | // verbose: undefined, 196 | 197 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 198 | // watchPathIgnorePatterns: [], 199 | 200 | // Whether to use watchman for file crawling 201 | // watchman: true, 202 | }; 203 | -------------------------------------------------------------------------------- /src/features/auth/views/sign-in-page/sign-in-page.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | 3 | export default function SignInPage() { 4 | return ( 5 | <> 6 |
7 |
8 | 9 | Coffee and Code Logo 10 | 11 |

Sign in to your account

12 |

13 | Or{' '} 14 | 15 | sign up to start your 14-day free trial 16 | 17 |

18 |
19 | 20 |
21 |
22 |
23 |
24 | 27 |
28 | 36 |
37 |
38 | 39 |
40 | 43 |
44 | 52 |
53 |
54 | 55 |
56 |
57 | 63 | 66 |
67 | 68 | 73 |
74 | 75 |
76 | 82 |
83 |
84 | 85 |
86 |
87 |
88 |
89 |
90 |
91 | Or continue with 92 |
93 |
94 | 95 | 140 |
141 |
142 |
143 |
144 | 145 | ); 146 | } 147 | -------------------------------------------------------------------------------- /src/features/auth/views/sign-up-page/sign-up-page.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | 3 | export default function SignUpPage() { 4 | return ( 5 | <> 6 |
7 |
8 |
9 |
10 | 11 | Coffee and Code Logo 12 | 13 |

Sign up for your account

14 |

15 | Already have an account? 16 | 17 | Sign in 18 | 19 |

20 |
21 | 22 |
23 |
24 |
25 | 28 |
29 | 36 |
37 |
38 | 39 |
40 | 43 |
44 | 52 |
53 |
54 | 55 |
56 | 59 |
60 | 68 |
69 |
70 | 71 |
72 |
73 | 79 | 89 |
90 |
91 | 92 |
93 | 99 |
100 |
101 | 102 |
103 |
104 | 111 | 112 | 159 |
160 |
161 |
162 |
163 |
164 | 169 |
170 |
171 | 172 | ); 173 | } 174 | -------------------------------------------------------------------------------- /src/layout/portal-layout/stacked-layout/stacked-layout.tsx: -------------------------------------------------------------------------------- 1 | import { Disclosure, Menu, Transition } from '@headlessui/react'; 2 | import { BellIcon, MenuIcon, XIcon } from '@heroicons/react/outline'; 3 | import { Fragment } from 'react'; 4 | import { Link, LinkProps, Outlet, useMatch, useResolvedPath } from 'react-router-dom'; 5 | 6 | const user = { 7 | name: 'Tom Cook', 8 | email: 'tom@example.com', 9 | imageUrl: 10 | 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80', 11 | }; 12 | const navigation = [ 13 | { name: 'Dashboard', to: '/dashboard', current: true }, 14 | { name: 'Users', to: '/users', current: false }, 15 | { name: 'Projects', to: '/projects', current: false }, 16 | { name: 'Calendar', to: '/calendar', current: false }, 17 | { name: 'Reports', to: '/reports', current: false }, 18 | ]; 19 | const userNavigation = [ 20 | { name: 'Your Profile', href: '#' }, 21 | { name: 'Settings', href: '#' }, 22 | { name: 'Sign out', href: '#' }, 23 | ]; 24 | 25 | function classNames(...classes: Array) { 26 | return classes.filter(Boolean).join(' '); 27 | } 28 | 29 | function StyledLink({ children, to, ...props }: LinkProps) { 30 | const resolved = useResolvedPath(to); 31 | const match = useMatch({ path: resolved.pathname, end: true }); 32 | 33 | return ( 34 | 42 | {children} 43 | 44 | ); 45 | } 46 | 47 | export default function StackedLayout() { 48 | return ( 49 | <> 50 |
51 | 52 | {({ open }) => ( 53 | <> 54 |
55 |
56 |
57 |
58 | 59 | Coffee and Code Logo 60 | 61 |
62 |
63 |
64 | {navigation.map(item => ( 65 | 66 | {item.name} 67 | 68 | ))} 69 |
70 |
71 |
72 |
73 |
74 | 81 | 82 | {/* Profile dropdown */} 83 | 84 |
85 | 86 | Open user menu 87 | 88 | 89 |
90 | 99 | 100 | {userNavigation.map(item => ( 101 | 102 | {({ active }) => ( 103 | 110 | {item.name} 111 | 112 | )} 113 | 114 | ))} 115 | 116 | 117 |
118 |
119 |
120 |
121 | {/* Mobile menu button */} 122 | 123 | Open main menu 124 | {open ? ( 125 | 130 |
131 |
132 |
133 | 134 | 135 |
136 | {navigation.map(item => ( 137 | 149 | {item.name} 150 | 151 | ))} 152 |
153 |
154 |
155 |
156 | 157 |
158 |
159 |
{user.name}
160 |
{user.email}
161 |
162 | 169 |
170 |
171 | {userNavigation.map(item => ( 172 | 178 | {item.name} 179 | 180 | ))} 181 |
182 |
183 |
184 | 185 | )} 186 |
187 | 188 |
189 |
190 |

Dashboard

191 |
192 |
193 |
194 |
195 | {/* Replace with your content */} 196 | 197 | {/*
198 |
199 |
*/} 200 | {/* /End replace */} 201 |
202 |
203 |
204 | 205 | ); 206 | } 207 | -------------------------------------------------------------------------------- /src/features/home/views/home/home.tsx: -------------------------------------------------------------------------------- 1 | import { Popover, Transition } from '@headlessui/react'; 2 | import { 3 | AnnotationIcon, 4 | ChatAlt2Icon, 5 | ChatAltIcon, 6 | DocumentReportIcon, 7 | HeartIcon, 8 | InboxIcon, 9 | MenuIcon, 10 | PencilAltIcon, 11 | QuestionMarkCircleIcon, 12 | ReplyIcon, 13 | SparklesIcon, 14 | TrashIcon, 15 | UsersIcon, 16 | XIcon, 17 | } from '@heroicons/react/outline'; 18 | import { ChevronDownIcon } from '@heroicons/react/solid'; 19 | import { Fragment } from 'react'; 20 | import { Link } from 'react-router-dom'; 21 | 22 | const solutions = [ 23 | { 24 | name: 'Inbox', 25 | description: 'Get a better understanding of where your traffic is coming from.', 26 | href: '#', 27 | icon: InboxIcon, 28 | }, 29 | { 30 | name: 'Messaging', 31 | description: 'Speak directly to your customers in a more meaningful way.', 32 | href: '#', 33 | icon: AnnotationIcon, 34 | }, 35 | { name: 'Live Chat', description: "Your customers' data will be safe and secure.", href: '#', icon: ChatAlt2Icon }, 36 | { 37 | name: 'Knowledge Base', 38 | description: "Connect with third-party tools that you're already using.", 39 | href: '#', 40 | icon: QuestionMarkCircleIcon, 41 | }, 42 | ]; 43 | const features = [ 44 | { 45 | name: 'Unlimited Inboxes', 46 | description: 'Ac tincidunt sapien vehicula erat auctor pellentesque rhoncus. Et magna sit morbi lobortis.', 47 | icon: InboxIcon, 48 | }, 49 | { 50 | name: 'Manage Team Members', 51 | description: 'Ac tincidunt sapien vehicula erat auctor pellentesque rhoncus. Et magna sit morbi lobortis.', 52 | icon: UsersIcon, 53 | }, 54 | { 55 | name: 'Spam Report', 56 | description: 'Ac tincidunt sapien vehicula erat auctor pellentesque rhoncus. Et magna sit morbi lobortis.', 57 | icon: TrashIcon, 58 | }, 59 | { 60 | name: 'Compose in Markdown', 61 | description: 'Ac tincidunt sapien vehicula erat auctor pellentesque rhoncus. Et magna sit morbi lobortis.', 62 | icon: PencilAltIcon, 63 | }, 64 | { 65 | name: 'Team Reporting', 66 | description: 'Ac tincidunt sapien vehicula erat auctor pellentesque rhoncus. Et magna sit morbi lobortis.', 67 | icon: DocumentReportIcon, 68 | }, 69 | { 70 | name: 'Saved Replies', 71 | description: 'Ac tincidunt sapien vehicula erat auctor pellentesque rhoncus. Et magna sit morbi lobortis.', 72 | icon: ReplyIcon, 73 | }, 74 | { 75 | name: 'Email Commenting', 76 | description: 'Ac tincidunt sapien vehicula erat auctor pellentesque rhoncus. Et magna sit morbi lobortis.', 77 | icon: ChatAltIcon, 78 | }, 79 | { 80 | name: 'Connect with Customers', 81 | description: 'Ac tincidunt sapien vehicula erat auctor pellentesque rhoncus. Et magna sit morbi lobortis.', 82 | icon: HeartIcon, 83 | }, 84 | ]; 85 | const metrics = [ 86 | { id: 1, stat: '8K+', emphasis: 'Companies', rest: 'use laoreet amet lacus nibh integer quis.' }, 87 | { id: 2, stat: '25K+', emphasis: 'Countries around the globe', rest: 'lacus nibh integer quis.' }, 88 | { id: 3, stat: '98%', emphasis: 'Customer satisfaction', rest: 'laoreet amet lacus nibh integer quis.' }, 89 | { id: 4, stat: '12M+', emphasis: 'Issues resolved', rest: 'lacus nibh integer quis.' }, 90 | ]; 91 | const footerNavigation = { 92 | solutions: [ 93 | { name: 'Marketing', href: '#' }, 94 | { name: 'Analytics', href: '#' }, 95 | { name: 'Commerce', href: '#' }, 96 | { name: 'Insights', href: '#' }, 97 | ], 98 | support: [ 99 | { name: 'Pricing', href: '#' }, 100 | { name: 'Documentation', href: '#' }, 101 | { name: 'Guides', href: '#' }, 102 | { name: 'API Status', href: '#' }, 103 | ], 104 | company: [ 105 | { name: 'About', href: '#' }, 106 | { name: 'Blog', href: '#' }, 107 | { name: 'Jobs', href: '#' }, 108 | { name: 'Press', href: '#' }, 109 | { name: 'Partners', href: '#' }, 110 | ], 111 | legal: [ 112 | { name: 'Claim', href: '#' }, 113 | { name: 'Privacy', href: '#' }, 114 | { name: 'Terms', href: '#' }, 115 | ], 116 | social: [ 117 | { 118 | name: 'Facebook', 119 | href: '#', 120 | icon: (props: React.SVGProps) => ( 121 | 122 | 127 | 128 | ), 129 | }, 130 | { 131 | name: 'Instagram', 132 | href: '#', 133 | icon: (props: React.SVGProps) => ( 134 | 135 | 140 | 141 | ), 142 | }, 143 | { 144 | name: 'Twitter', 145 | href: '#', 146 | icon: (props: React.SVGProps) => ( 147 | 148 | 149 | 150 | ), 151 | }, 152 | { 153 | name: 'GitHub', 154 | href: '#', 155 | icon: (props: React.SVGProps) => ( 156 | 157 | 162 | 163 | ), 164 | }, 165 | { 166 | name: 'Dribbble', 167 | href: '#', 168 | icon: (props: React.SVGProps) => ( 169 | 170 | 175 | 176 | ), 177 | }, 178 | ], 179 | }; 180 | 181 | function classNames(...classes: Array) { 182 | return classes.filter(Boolean).join(' '); 183 | } 184 | 185 | export default function Home() { 186 | return ( 187 |
188 |
189 | 190 |
191 |
192 | 193 | Coffee and Code Logo 194 | Coffee and Code Logo 195 | 196 |
197 |
198 | 199 | Open menu 200 | 202 |
203 | 204 | 205 | {({ open }) => ( 206 | <> 207 | 213 | Solutions 214 | 222 | 223 | 232 | 233 |
234 | 251 |
252 |
253 |
254 | 255 | )} 256 |
257 | 258 | 259 | Pricing 260 | 261 | 262 | Partners 263 | 264 | 265 | Dashboard 266 | 267 |
268 |
269 | 270 | Sign in 271 | 272 | 276 | Sign up 277 | 278 |
279 |
280 | 281 | 290 | 294 |
295 |
296 |
297 |
298 | Workflow 303 |
304 |
305 | 306 | Close menu 307 | 309 |
310 |
311 |
312 | 326 |
327 |
328 |
329 | 340 |
341 | 345 | Sign up 346 | 347 |

348 | Existing customer? 349 | 350 | Sign in 351 | 352 |

353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 | 361 |
362 | {/* Hero section */} 363 |
364 |
365 |
366 |
367 |
368 | People working on laptops 373 |
374 |
375 |
376 |

377 | Take control of your 378 | customer support 379 |

380 |

381 | Anim aute id magna aliqua ad ad non deserunt sunt. Qui irure qui lorem cupidatat commodo. Elit sunt 382 | amet fugiat veniam occaecat fugiat aliqua. 383 |

384 | 400 |
401 |
402 |
403 |
404 | 405 | {/* Logo Cloud */} 406 |
407 |
408 |

409 | Trusted by over 5 very average small businesses 410 |

411 |
412 |
413 | Tuple 414 |
415 |
416 | Mirage 417 |
418 |
419 | StaticKit 424 |
425 |
426 | Transistor 431 |
432 |
433 | Workcation 438 |
439 |
440 |
441 |
442 | 443 | {/* Alternating Feature Sections */} 444 |
445 | 551 | 552 | {/* Gradient Feature Section */} 553 |
554 |
555 |

Inbox support built for efficiency

556 |

557 | Ac tincidunt sapien vehicula erat auctor pellentesque rhoncus. Et magna sit morbi lobortis. Blandit 558 | aliquam sit nisl euismod mattis in. 559 |

560 |
561 | {features.map(feature => ( 562 |
563 |
564 | 565 | 567 |
568 |
569 |

{feature.name}

570 |

{feature.description}

571 |
572 |
573 | ))} 574 |
575 |
576 |
577 | 578 | {/* Stats section */} 579 |
580 |
581 |
582 |
583 | People working on laptops 588 | 593 |
594 |
595 |
596 |
597 |

598 | 599 | Valuable Metrics 600 | 601 |

602 |

603 | Get actionable data that will help grow your business 604 |

605 |

606 | Rhoncus sagittis risus arcu erat lectus bibendum. Ut in adipiscing quis in viverra tristique sem. Ornare 607 | feugiat viverra eleifend fusce orci in quis amet. Sit in et vitae tortor, massa. Dapibus laoreet amet 608 | lacus nibh integer quis. Eu vulputate diam sit tellus quis at. 609 |

610 |
611 | {metrics.map(item => ( 612 |

613 | {item.stat} 614 | 615 | {item.emphasis} {item.rest} 616 | 617 |

618 | ))} 619 |
620 |
621 |
622 |
623 | 624 | {/* CTA Section */} 625 |
626 |
627 |

628 | Ready to get started? 629 | 630 | Get in touch or create an account. 631 | 632 |

633 | 647 |
648 |
649 |
650 | 651 | 757 |
758 | ); 759 | } 760 | --------------------------------------------------------------------------------