├── .env.sample ├── .eslintrc.cjs ├── .gitignore ├── .prettierrc ├── .storybook ├── main.ts └── preview.ts ├── @types └── vite-env.d.ts ├── LICENSE ├── README.md ├── cypress.config.ts ├── cypress ├── fixtures │ └── example.json └── support │ ├── commands.ts │ ├── component-index.html │ └── component.ts ├── index.html ├── package.json ├── public └── vite.svg ├── src ├── app │ ├── core.tsx │ ├── index.tsx │ ├── layouts │ │ ├── base │ │ │ ├── index.ts │ │ │ └── ui.tsx │ │ └── index.ts │ ├── providers │ │ ├── index.ts │ │ ├── with-effector.tsx │ │ └── with-error-boundary.tsx │ ├── router │ │ └── index.tsx │ └── styles │ │ └── global.tsx ├── entities │ └── session │ │ └── model.ts ├── features │ └── feature-example │ │ ├── index.ts │ │ └── ui.tsx ├── pages │ ├── 404 │ │ ├── index.ts │ │ └── page.tsx │ ├── error │ │ ├── index.ts │ │ └── page.tsx │ ├── home │ │ ├── index.ts │ │ └── page.tsx │ └── post │ │ ├── list │ │ ├── index.ts │ │ └── page.tsx │ │ └── single │ │ ├── index.ts │ │ └── page.tsx ├── shared │ ├── api │ │ └── todo-api │ │ │ └── index.ts │ ├── config │ │ └── index.ts │ ├── entry-point.ts │ ├── lib │ │ ├── media-query │ │ │ └── index.ts │ │ ├── react │ │ │ ├── index.ts │ │ │ └── with-suspense.tsx │ │ └── sentry │ │ │ └── index.ts │ ├── routing │ │ ├── index.ts │ │ └── paths.ts │ └── ui │ │ ├── atoms │ │ ├── container │ │ │ ├── index.ts │ │ │ ├── styles.tsx │ │ │ └── ui.tsx │ │ ├── link │ │ │ ├── index.ts │ │ │ ├── stories.tsx │ │ │ ├── styles.tsx │ │ │ ├── tests.tsx │ │ │ └── ui.tsx │ │ └── slots │ │ │ ├── index.ts │ │ │ └── ui.tsx │ │ ├── molecules │ │ └── .gitkeep │ │ └── organisms │ │ └── .gitkeep └── widgets │ └── .gitkeep ├── tsconfig.json ├── tsconfig.node.json ├── vercel.json └── vite.config.ts /.env.sample: -------------------------------------------------------------------------------- 1 | VITE_SENTRY_KEY= -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | const { 2 | configure, 3 | presets 4 | } = require("eslint-kit"); 5 | 6 | module.exports = configure({ 7 | allowDebug: process.env.NODE_ENV !== "production", 8 | 9 | presets: [ 10 | presets.imports({ 11 | sort: { 12 | newline: true, 13 | 14 | groups: [ 15 | ['^\\u0000'], 16 | ['^(child_process|crypto|events|fs|http|https|os|path)(/.*)?$', '^@?\\w'], 17 | ['^~/app', '^~/pages', '^~/widgets', '^~/features', '^~/entities', '^~/shared', '^'], 18 | ['^\\.'], 19 | ] 20 | }, 21 | }), 22 | presets.node(), 23 | presets.prettier(), 24 | presets.typescript(), 25 | presets.react({ 26 | newJSXTransform: true, 27 | }), 28 | presets.effector() 29 | ], 30 | 31 | extend: { 32 | rules: { 33 | "react/no-unknown-property": "off" 34 | } 35 | } 36 | }); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | # Lock files 27 | bun.lockb 28 | *storybook.log 29 | 30 | # Envs 31 | .env 32 | .env.development 33 | .env.local 34 | .env.production 35 | 36 | # Sentry Config File 37 | .env.sentry-build-plugin 38 | 39 | # Build things 40 | .swc -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "quoteProps": "consistent" 6 | } -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from '@storybook/react-vite' 2 | 3 | const config: StorybookConfig = { 4 | stories: ['../src/**/*.mdx', '../src/**/stories.@(js|jsx|mjs|ts|tsx)'], 5 | addons: [ 6 | '@storybook/addon-onboarding', 7 | '@storybook/addon-links', 8 | '@storybook/addon-essentials', 9 | '@chromatic-com/storybook', 10 | '@storybook/addon-interactions', 11 | ], 12 | framework: { 13 | name: '@storybook/react-vite', 14 | options: {}, 15 | }, 16 | docs: { 17 | autodocs: 'tag', 18 | }, 19 | } 20 | export default config 21 | -------------------------------------------------------------------------------- /.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import type { Preview } from '@storybook/react' 2 | 3 | const preview: Preview = { 4 | parameters: { 5 | controls: { 6 | matchers: { 7 | color: /(background|color)$/i, 8 | date: /Date$/i, 9 | }, 10 | }, 11 | }, 12 | } 13 | 14 | export default preview 15 | -------------------------------------------------------------------------------- /@types/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mikita Pitunou 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![GitHub License](https://img.shields.io/github/license/jassix/react-boilerplate) 2 | ![GitHub Repo stars](https://img.shields.io/github/stars/jassix/react-boilerplate) 3 | 4 | # React.js Boilerplate 5 | 6 | This is a boilerplate/template project for building React.js applications, utilizing several modern technologies and libraries including Effector for state management, Farfetched for data fetching, Cypress for end-to-end testing, Bun as runtime, Vite for fast development environment, Storybook for component documentation and testing, react-testing-library and Jest for unit testing, and Emotion for styling. 7 | 8 | ## Technologies Used 9 | 10 | - **Effector**: State management library for React applications. 11 | - **Farfetched**: Lightweight and composable data fetching library. 12 | - **Cypress**: End-to-end testing framework. 13 | - **Bun**: Fast JS runtime. 14 | - **Vite**: Fast, opinionated web dev build tool that serves your code via native ES Module imports during development. 15 | - **Storybook**: UI component explorer for frontend developers. 16 | - **React Testing Library** and **Jest**: For unit testing React components. 17 | - **Emotion**: A performant and flexible CSS-in-JS library. 18 | 19 | ## Features 20 | 21 | - Effortless state management with Effector. 22 | - Simplified data fetching with Farfetched. 23 | - Comprehensive testing capabilities with Cypress, Jest, and React Testing Library. 24 | - Efficient styling using Emotion. 25 | - Rapid development setup with Vite. 26 | - Component documentation and testing facilitated by Storybook. 27 | 28 | ## Getting Started 29 | 30 | ### Prerequisites 31 | 32 | Make sure you have Bun installed on your machine. 33 | 34 | ### Installation 35 | 36 | 1. Clone the repository: 37 | 38 | ```bash 39 | git clone https://github.com/jassix/react-boilerplate.git 40 | ``` 41 | 42 | 2. Navigate to the project directory: 43 | 44 | ```bash 45 | cd react-boilerplate 46 | ``` 47 | 48 | 3. Install dependencies: 49 | 50 | ```bash 51 | bun install 52 | ``` 53 | 54 | ### Development 55 | 56 | To start the development server, run: 57 | 58 | ```bash 59 | bun dev 60 | ``` 61 | 62 | This will start the Vite development server. 63 | 64 | ### Building 65 | 66 | To build the project for production, run: 67 | 68 | ```bash 69 | bun run build 70 | ``` 71 | 72 | This will generate a production-ready build in the `dist` directory. 73 | 74 | ### Testing 75 | 76 | #### Unit Testing 77 | 78 | To run unit tests using Jest, execute: 79 | 80 | ```bash 81 | bun run test 82 | ``` 83 | 84 | #### End-to-End Testing 85 | 86 | Cypress is used for end-to-end testing. To run Cypress tests, use: 87 | 88 | ```bash 89 | bun run test:e2e 90 | ``` 91 | 92 | ### Storybook 93 | 94 | To start Storybook and view your components in isolation, run: 95 | 96 | ```bash 97 | bun storybook 98 | ``` 99 | 100 | ## Folder Structure 101 | 102 | ``` 103 | / 104 | ├── public/ 105 | ├── src/ 106 | │ ├── app/ 107 | │ │ ├── providers/ - Applications providers 108 | │ │ ├── layouts/ - Different layouts for your pages 109 | │ │ ├── router/ - Router-Component, collect your routes in one place 110 | │ │ ├── styles/ - Global styles for your app 111 | │ │ ├── core.tsx - Root-Component, your providers and routing must will connected there 112 | │ │ └── index.tsx - Application entry-point 113 | │ ├── pages/ - Pages 114 | │ ├── widgets/ - Widgets 115 | │ ├── features/ - Features 116 | │ ├── entities/ - Entities 117 | │ └── shared/ - Shared 118 | │ ├── api/ - All requests to API 119 | │ ├── lib/ - Additional local "libraries" 120 | │ │ ├── media-query 121 | │ │ └── i18n 122 | │ ├── routing/ - Basement for routing, e.g. paths, or something else 123 | │ ├── config/ - General config for your app 124 | │ └── ui/ - UI-Kit, with atomic design 125 | │ ├── atom 126 | │ ├── molecules 127 | │ └── organisms 128 | └── package.json 129 | ``` 130 | 131 | ## License 132 | 133 | This project is licensed under the [MIT License](LICENSE). 134 | 135 | ## Acknowledgements 136 | 137 | - [Effector](https://effector.dev/) 138 | - [Farfetched](https://farfetched.pages.dev/) 139 | - [Cypress](https://www.cypress.io/) 140 | - [Bun](https://bun.sh/) 141 | - [Vite](https://vitejs.dev/) 142 | - [Storybook](https://storybook.js.org/) 143 | - [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) 144 | - [Jest](https://jestjs.io/) 145 | - [Emotion](https://emotion.sh/) 146 | 147 | --- 148 | 149 | Feel free to contribute to this project and tailor it to your specific needs! If you encounter any issues or have suggestions for improvements, please open an issue or create a pull request. Happy coding! 🚀 150 | -------------------------------------------------------------------------------- /cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | 3 | export default defineConfig({ 4 | component: { 5 | devServer: { 6 | framework: "react", 7 | bundler: "vite", 8 | }, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************** 3 | // This example commands.ts shows you how to 4 | // create various custom commands and overwrite 5 | // existing commands. 6 | // 7 | // For more comprehensive examples of custom 8 | // commands please read more here: 9 | // https://on.cypress.io/custom-commands 10 | // *********************************************** 11 | // 12 | // 13 | // -- This is a parent command -- 14 | // Cypress.Commands.add('login', (email, password) => { ... }) 15 | // 16 | // 17 | // -- This is a child command -- 18 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 19 | // 20 | // 21 | // -- This is a dual command -- 22 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 23 | // 24 | // 25 | // -- This will overwrite an existing command -- 26 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 27 | // 28 | // declare global { 29 | // namespace Cypress { 30 | // interface Chainable { 31 | // login(email: string, password: string): Chainable 32 | // drag(subject: string, options?: Partial): Chainable 33 | // dismiss(subject: string, options?: Partial): Chainable 34 | // visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable 35 | // } 36 | // } 37 | // } -------------------------------------------------------------------------------- /cypress/support/component-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Components App 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /cypress/support/component.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/component.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.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | 22 | import { mount } from 'cypress/react18' 23 | 24 | // Augment the Cypress namespace to include type definitions for 25 | // your custom command. 26 | // Alternatively, can be defined in cypress/support/component.d.ts 27 | // with a at the top of your spec. 28 | declare global { 29 | namespace Cypress { 30 | interface Chainable { 31 | mount: typeof mount 32 | } 33 | } 34 | } 35 | 36 | Cypress.Commands.add('mount', mount) 37 | 38 | // Example use: 39 | // cy.mount() -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-boilerplate", 3 | "author": "Mikita Pitunou (https://github.com/jassix) ", 4 | "private": true, 5 | "version": "0.0.0", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "bunx --bun vite", 9 | "build": "tsc && vite build", 10 | "lint": "eslint \"src/**/*.{js,mjs,cjs,ts,mts,jsx,tsx}\"", 11 | "preview": "bunx --bun vite preview", 12 | "test": "jest", 13 | "test:e2e": "bunx cypress run", 14 | "lint:fix": "eslint \"src/**/*.{js,mjs,cjs,ts,mts,jsx,tsx}\" --fix", 15 | "storybook": "storybook dev -p 6006", 16 | "build-storybook": "storybook build" 17 | }, 18 | "dependencies": { 19 | "@emotion/css": "^11.11.2", 20 | "@emotion/react": "^11.11.4", 21 | "@sentry/react": "^7.109.0", 22 | "@sentry/vite-plugin": "^2.16.1", 23 | "@swan-io/chicane": "^2.0.0", 24 | "axios": "^1.6.8", 25 | "class-variance-authority": "^0.7.0", 26 | "clsx": "^2.1.0", 27 | "compose-function": "^3.0.3", 28 | "cypress": "^13.7.2", 29 | "effector": "^23.2.0", 30 | "effector-react": "^23.2.0", 31 | "normalize.css": "^8.0.1", 32 | "react": "^18.2.0", 33 | "react-dom": "^18.2.0", 34 | "react-error-boundary": "^4.0.13", 35 | "ts-pattern": "^5.1.1", 36 | "zod": "^3.22.4" 37 | }, 38 | "devDependencies": { 39 | "@chromatic-com/storybook": "^1.3.1", 40 | "@emotion/eslint-plugin": "^11.11.0", 41 | "@storybook/addon-essentials": "^8.0.6", 42 | "@storybook/addon-interactions": "^8.0.6", 43 | "@storybook/addon-links": "^8.0.6", 44 | "@storybook/addon-onboarding": "^8.0.6", 45 | "@storybook/blocks": "^8.0.6", 46 | "@storybook/react": "^8.0.6", 47 | "@storybook/react-vite": "^8.0.6", 48 | "@storybook/test": "^8.0.6", 49 | "@swc/plugin-emotion": "^2.5.124", 50 | "@testing-library/react": "^14.2.2", 51 | "@types/axios": "^0.14.0", 52 | "@types/compose-function": "^0.0.33", 53 | "@types/react": "^18.2.66", 54 | "@types/react-dom": "^18.2.22", 55 | "@vitejs/plugin-react-swc": "^3.5.0", 56 | "effector-swc-plugin": "^0.3.0", 57 | "eslint": "^8.57.0", 58 | "eslint-kit": "^10.26.0", 59 | "eslint-plugin-react-refresh": "^0.4.6", 60 | "eslint-plugin-storybook": "^0.8.0", 61 | "jest": "^29.7.0", 62 | "prettier": "^3.2.5", 63 | "storybook": "^8.0.6", 64 | "typescript": "^5.2.2", 65 | "vite": "^5.2.8" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/core.tsx: -------------------------------------------------------------------------------- 1 | import 'normalize.css' 2 | 3 | import { Fragment } from 'react' 4 | 5 | import { withProviders } from './providers' 6 | import { ApplicationRouter } from './router' 7 | import { GlobalStyles } from './styles/global' 8 | 9 | export const Core = withProviders(() => ( 10 | 11 | 12 | 13 | 14 | )) 15 | -------------------------------------------------------------------------------- /src/app/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | 4 | import { appStarted } from '~/shared/entry-point' 5 | import { sentryInit } from '~/shared/lib/sentry' 6 | 7 | import { Core } from './core' 8 | 9 | sentryInit() 10 | 11 | const root = document.querySelector('#app')! 12 | 13 | ReactDOM.createRoot(root).render( 14 | 15 | 16 | , 17 | ) 18 | 19 | appStarted() 20 | -------------------------------------------------------------------------------- /src/app/layouts/base/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ui' 2 | -------------------------------------------------------------------------------- /src/app/layouts/base/ui.tsx: -------------------------------------------------------------------------------- 1 | import { FC, Fragment, PropsWithChildren } from 'react' 2 | 3 | import { ContentSlot, FooterSlot, NavbarSlot } from '~/app/layouts' 4 | import { Container } from '~/shared/ui/atoms/container' 5 | 6 | export const BaseLayout: FC = ({ children }) => ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | -------------------------------------------------------------------------------- /src/app/layouts/index.ts: -------------------------------------------------------------------------------- 1 | import { createSlot } from '~/shared/ui/atoms/slots' 2 | 3 | export const NavbarSlot = createSlot() 4 | export const ContentSlot = createSlot() 5 | export const FooterSlot = createSlot() 6 | 7 | export * from './base' 8 | -------------------------------------------------------------------------------- /src/app/providers/index.ts: -------------------------------------------------------------------------------- 1 | import compose from 'compose-function' 2 | 3 | import { withEffector } from './with-effector' 4 | import { withErrorBoundary } from './with-error-boundary' 5 | 6 | export const withProviders = compose(withErrorBoundary, withEffector) 7 | -------------------------------------------------------------------------------- /src/app/providers/with-effector.tsx: -------------------------------------------------------------------------------- 1 | import { fork } from 'effector' 2 | import { Provider } from 'effector-react' 3 | import { ReactElement } from 'react' 4 | 5 | const scope = fork() 6 | 7 | export const withEffector = (component: () => ReactElement) => () => ( 8 | {component()} 9 | ) 10 | -------------------------------------------------------------------------------- /src/app/providers/with-error-boundary.tsx: -------------------------------------------------------------------------------- 1 | import { ErrorInfo, ReactElement } from 'react' 2 | import { ErrorBoundary } from 'react-error-boundary' 3 | 4 | import { ErrorPage } from '~/pages/error' 5 | 6 | export const withErrorBoundary = (component: () => ReactElement) => () => { 7 | const onError = (_error: Error, _info: ErrorInfo) => {} 8 | 9 | return ( 10 | 11 | {component()} 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /src/app/router/index.tsx: -------------------------------------------------------------------------------- 1 | import { match } from 'ts-pattern' 2 | 3 | import { BaseLayout, ContentSlot } from '~/app/layouts' 4 | import { NotFoundPage } from '~/pages/404' 5 | import { HomePage } from '~/pages/home' 6 | import { PostListPage } from '~/pages/post/list' 7 | import { PostSinglePage } from '~/pages/post/single' 8 | import { Router } from '~/shared/routing' 9 | 10 | export const ApplicationRouter = () => { 11 | const router = Router.useRoute(['Index', 'PostList', 'PostSingle']) 12 | 13 | return match(router) 14 | .with({ name: 'Index' }, () => ( 15 | 16 | 17 | 18 | 19 | 20 | )) 21 | .with({ name: 'PostList' }, () => ( 22 | 23 | 24 | 25 | 26 | 27 | )) 28 | .with({ name: 'PostSingle' }, ({ params }) => ( 29 | 30 | 31 | 32 | 33 | 34 | )) 35 | .otherwise(() => ) 36 | } 37 | -------------------------------------------------------------------------------- /src/app/styles/global.tsx: -------------------------------------------------------------------------------- 1 | import { Global } from '@emotion/react' 2 | 3 | export const GlobalStyles = () => 4 | -------------------------------------------------------------------------------- /src/entities/session/model.ts: -------------------------------------------------------------------------------- 1 | import { createEvent, createStore } from 'effector' 2 | 3 | export const $isAuth = createStore(false) 4 | export const authChanged = createEvent() 5 | 6 | $isAuth.on(authChanged, (_, payload) => payload) 7 | -------------------------------------------------------------------------------- /src/features/feature-example/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ui' 2 | -------------------------------------------------------------------------------- /src/features/feature-example/ui.tsx: -------------------------------------------------------------------------------- 1 | export const FeatureExample = () =>
make something
2 | -------------------------------------------------------------------------------- /src/pages/404/index.ts: -------------------------------------------------------------------------------- 1 | export * from './page' 2 | -------------------------------------------------------------------------------- /src/pages/404/page.tsx: -------------------------------------------------------------------------------- 1 | import { Router } from '~/shared/routing' 2 | import { Link } from '~/shared/ui/atoms/link' 3 | 4 | export const NotFoundPage = () => ( 5 |
6 | page not found go back 7 |
8 | ) 9 | -------------------------------------------------------------------------------- /src/pages/error/index.ts: -------------------------------------------------------------------------------- 1 | export * from './page' 2 | -------------------------------------------------------------------------------- /src/pages/error/page.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | 3 | interface PageProps { 4 | error: Error 5 | } 6 | 7 | export const ErrorPage: FC = ({ error }) => { 8 | return
something went wrong! {error.message}
9 | } 10 | -------------------------------------------------------------------------------- /src/pages/home/index.ts: -------------------------------------------------------------------------------- 1 | export * from './page' 2 | -------------------------------------------------------------------------------- /src/pages/home/page.tsx: -------------------------------------------------------------------------------- 1 | import { Router } from '~/shared/routing' 2 | import { Link } from '~/shared/ui/atoms/link' 3 | 4 | export const HomePage = () => { 5 | return ( 6 |
7 | page Hello 8 | 15 | ; 16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/pages/post/list/index.ts: -------------------------------------------------------------------------------- 1 | export * from './page' 2 | -------------------------------------------------------------------------------- /src/pages/post/list/page.tsx: -------------------------------------------------------------------------------- 1 | export const PostListPage = () => { 2 | return
page
3 | } 4 | -------------------------------------------------------------------------------- /src/pages/post/single/index.ts: -------------------------------------------------------------------------------- 1 | export * from './page' 2 | -------------------------------------------------------------------------------- /src/pages/post/single/page.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | 3 | interface PageProps { 4 | id?: string 5 | } 6 | 7 | export const PostSinglePage: FC = ({ id }) => { 8 | return
page #{id}
9 | } 10 | -------------------------------------------------------------------------------- /src/shared/api/todo-api/index.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export const todoApiClient = axios.create({ 4 | baseURL: '', 5 | }) 6 | -------------------------------------------------------------------------------- /src/shared/config/index.ts: -------------------------------------------------------------------------------- 1 | export const env = () => ({ 2 | sentryKey: import.meta.env.VITE_SENTRY_KEY, 3 | }) 4 | -------------------------------------------------------------------------------- /src/shared/entry-point.ts: -------------------------------------------------------------------------------- 1 | import { createEvent } from 'effector' 2 | 3 | export const appStarted = createEvent() 4 | -------------------------------------------------------------------------------- /src/shared/lib/media-query/index.ts: -------------------------------------------------------------------------------- 1 | export const breakpoints = { 2 | xs: 0, 3 | sm: 600, 4 | md: 900, 5 | lg: 1200, 6 | xl: 1536, 7 | } 8 | 9 | export interface MediaQuery { 10 | parameter: 'max-width' | 'min-width' | 'max-height' | 'min-height' 11 | value: string 12 | } 13 | 14 | export const media = ( 15 | size: keyof typeof breakpoints, 16 | ...queries: MediaQuery[] 17 | ) => { 18 | const formatedQueries = queries 19 | .map((query) => `(${query.parameter}: ${query.value})`) 20 | .join(',') 21 | 22 | return `@media (max-width: ${breakpoints[size]}px) ${formatedQueries.length > 0 ? `, ${formatedQueries}` : ''}` 23 | } 24 | -------------------------------------------------------------------------------- /src/shared/lib/react/index.ts: -------------------------------------------------------------------------------- 1 | export * from './with-suspense' 2 | -------------------------------------------------------------------------------- /src/shared/lib/react/with-suspense.tsx: -------------------------------------------------------------------------------- 1 | import { FC, Suspense, SuspenseProps } from 'react' 2 | 3 | export const withSuspense = ( 4 | WrappedComponent: FC, 5 | suspenseProps: SuspenseProps, 6 | ): FC => { 7 | const WrapperComponent = (props: WrappedProps) => { 8 | return ( 9 | 10 | 11 | 12 | ) 13 | } 14 | 15 | return WrapperComponent 16 | } 17 | -------------------------------------------------------------------------------- /src/shared/lib/sentry/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | browserTracingIntegration, 3 | init, 4 | replayIntegration, 5 | } from '@sentry/react' 6 | 7 | import { env } from '~/shared/config' 8 | 9 | export const sentryInit = () => { 10 | init({ 11 | dsn: `https://${env().sentryKey}.ingest.us.sentry.io/4507053306413056`, 12 | 13 | integrations: [ 14 | browserTracingIntegration(), 15 | replayIntegration({ 16 | maskAllText: false, 17 | blockAllMedia: false, 18 | }), 19 | ], 20 | 21 | tracesSampleRate: 1, 22 | tracePropagationTargets: ['localhost', /^https:\/\/yourserver\.io\/api/], 23 | 24 | replaysSessionSampleRate: 0.1, 25 | replaysOnErrorSampleRate: 1, 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /src/shared/routing/index.ts: -------------------------------------------------------------------------------- 1 | export * from './paths' 2 | -------------------------------------------------------------------------------- /src/shared/routing/paths.ts: -------------------------------------------------------------------------------- 1 | import { createGroup, createRouter } from '@swan-io/chicane' 2 | 3 | export const Router = createRouter({ 4 | Index: '/', 5 | 6 | ...createGroup('Post', '/post', { 7 | List: '/', 8 | Single: '/?:id', 9 | }), 10 | }) 11 | -------------------------------------------------------------------------------- /src/shared/ui/atoms/container/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ui' 2 | -------------------------------------------------------------------------------- /src/shared/ui/atoms/container/styles.tsx: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/react' 2 | 3 | import { media } from '~/shared/lib/media-query' 4 | 5 | export const container = css({ 6 | display: 'flex', 7 | flexDirection: 'column', 8 | padding: '0 48px', 9 | 10 | [media('md')]: { 11 | padding: '0 32px', 12 | }, 13 | }) 14 | -------------------------------------------------------------------------------- /src/shared/ui/atoms/container/ui.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from 'react' 2 | 3 | import { container } from './styles' 4 | 5 | export const Container: FC = ({ children }) => ( 6 |
{children}
7 | ) 8 | -------------------------------------------------------------------------------- /src/shared/ui/atoms/link/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ui' 2 | -------------------------------------------------------------------------------- /src/shared/ui/atoms/link/stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react' 2 | 3 | import { Link } from './ui' 4 | 5 | const meta: Meta = { 6 | component: Link, 7 | } 8 | 9 | type Story = StoryObj 10 | 11 | export const Primary: Story = { 12 | render: () => hello, 13 | } 14 | 15 | export default meta 16 | -------------------------------------------------------------------------------- /src/shared/ui/atoms/link/styles.tsx: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/react' 2 | 3 | export const link = { 4 | base: css({ 5 | color: 'var(--link)', 6 | }), 7 | 8 | active: { 9 | color: 'var(--activeLink)', 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /src/shared/ui/atoms/link/tests.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jassix/react-boilerplate/102d28e13451710fdc6c1f90ec5f0b355196c736/src/shared/ui/atoms/link/tests.tsx -------------------------------------------------------------------------------- /src/shared/ui/atoms/link/ui.tsx: -------------------------------------------------------------------------------- 1 | import { useLinkProps } from '@swan-io/chicane' 2 | import clsx from 'clsx' 3 | import { AnchorHTMLAttributes, forwardRef, PropsWithChildren } from 'react' 4 | 5 | import { link } from './styles' 6 | 7 | export interface LinkProps 8 | extends PropsWithChildren, 9 | AnchorHTMLAttributes { 10 | to: string 11 | replace?: boolean 12 | } 13 | 14 | export const Link = forwardRef( 15 | ({ to, replace, children, ...props }, ref) => { 16 | const { active, onClick } = useLinkProps({ href: to, replace }) 17 | 18 | return ( 19 | 26 | {children} 27 | 28 | ) 29 | }, 30 | ) 31 | -------------------------------------------------------------------------------- /src/shared/ui/atoms/slots/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ui' 2 | -------------------------------------------------------------------------------- /src/shared/ui/atoms/slots/ui.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Children, 3 | cloneElement, 4 | FC, 5 | isValidElement, 6 | ReactElement, 7 | ReactNode, 8 | } from 'react' 9 | 10 | interface RendererProps { 11 | childs: ReactNode 12 | children?: ReactNode 13 | } 14 | 15 | type RendererType

= FC 16 | 17 | interface SlotProps

{ 18 | showChildren?: boolean 19 | restProps?: P & { defaultChildren: ReactNode } 20 | } 21 | 22 | type NormalOrFunctionChildren

= 23 | | ReactNode 24 | | ((props: P & { defaultChildren: ReactNode }) => ReactNode) 25 | 26 | interface SlotType

{ 27 | ( 28 | props: SlotProps

& { children?: NormalOrFunctionChildren

}, 29 | ): ReactElement | null 30 | Renderer: RendererType

31 | } 32 | 33 | export function createSlot

(): SlotType

{ 34 | const Slot: SlotType

= (({ children, showChildren, restProps }) => { 35 | if (!showChildren) { 36 | return null 37 | } 38 | 39 | if (typeof children === 'function' && restProps) { 40 | return children(restProps) 41 | } 42 | 43 | return <>{children} 44 | }) as SlotType

45 | 46 | const Renderer: RendererType

= ({ childs, children, ...restProps }) => { 47 | const slotted = Children.toArray(childs).find((child) => { 48 | return isValidElement(child) && child.type === Slot 49 | }) 50 | 51 | if (!slotted || !isValidElement(slotted)) { 52 | return <>{children} 53 | } 54 | 55 | return cloneElement(slotted, { 56 | showChildren: true, 57 | restProps: { ...restProps, defaultChildren: children }, 58 | } as unknown as SlotProps

) 59 | } 60 | 61 | Slot.Renderer = Renderer 62 | 63 | return Slot 64 | } 65 | -------------------------------------------------------------------------------- /src/shared/ui/molecules/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jassix/react-boilerplate/102d28e13451710fdc6c1f90ec5f0b355196c736/src/shared/ui/molecules/.gitkeep -------------------------------------------------------------------------------- /src/shared/ui/organisms/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jassix/react-boilerplate/102d28e13451710fdc6c1f90ec5f0b355196c736/src/shared/ui/organisms/.gitkeep -------------------------------------------------------------------------------- /src/widgets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jassix/react-boilerplate/102d28e13451710fdc6c1f90ec5f0b355196c736/src/widgets/.gitkeep -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | "jsxImportSource": "@emotion/react", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true, 23 | 24 | "paths": { 25 | "~/*": ["./src/*"] 26 | } 27 | }, 28 | "include": ["src", "@types"], 29 | "references": [{ "path": "./tsconfig.node.json" }] 30 | } 31 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [ 3 | {"source": "/(.*)", "destination": "/"} 4 | ] 5 | } -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sentryVitePlugin } from "@sentry/vite-plugin"; 2 | import { defineConfig } from 'vite' 3 | import react from '@vitejs/plugin-react-swc' 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [ 8 | react({ 9 | jsxImportSource: "@emotion/react", 10 | plugins: [ 11 | ["@swc/plugin-emotion", {}], 12 | ["effector-swc-plugin", {}] 13 | ], 14 | }), 15 | 16 | sentryVitePlugin({ 17 | org: "boxlife-hq", 18 | project: "javascript-react" 19 | }) 20 | ], 21 | build: { 22 | sourcemap: true 23 | }, 24 | server: { 25 | port: 3000, 26 | }, 27 | resolve: { 28 | alias: { 29 | "~": '/src' 30 | } 31 | } 32 | }) --------------------------------------------------------------------------------