From 27eddfd484ab134292b68c93c36d63e8c54e044d Mon Sep 17 00:00:00 2001 From: Gopikrishnan200 Date: Fri, 12 Dec 2025 19:57:29 +0530 Subject: [PATCH 1/5] feat(basic/ts): add typescript basic react chef --- recipes/_ts/basic/config.js | 72 +++++++++++++ recipes/_ts/basic/snippets/.babelrc | 14 +++ recipes/_ts/basic/snippets/.eslintrc.json | 3 + recipes/_ts/basic/snippets/.prettierrc.json | 20 ++++ recipes/_ts/basic/snippets/index.js | 37 +++++++ recipes/_ts/basic/snippets/sources/App.js | 32 ++++++ .../_ts/basic/snippets/sources/Dashboard.js | 57 ++++++++++ .../_ts/basic/snippets/sources/PageLoader.js | 16 +++ recipes/_ts/basic/snippets/sources/Routes.js | 42 ++++++++ recipes/_ts/basic/snippets/sources/Sidebar.js | 51 +++++++++ recipes/_ts/basic/snippets/sources/SignIn.js | 74 +++++++++++++ recipes/_ts/basic/snippets/sources/TopBar.js | 31 ++++++ .../blocks/ErrorHandler/ErrorHandler.tsx | 35 ++++++ .../components/blocks/ErrorHandler/index.ts | 1 + .../blocks/Loader/PageLoader/index.ts | 1 + .../sources/components/blocks/Loader/index.ts | 1 + .../blocks/Region/Footer/Footer.tsx | 25 +++++ .../components/blocks/Region/Footer/index.ts | 1 + .../Region/Sidebar/SidebarNav/SidebarNav.tsx | 23 ++++ .../blocks/Region/Sidebar/SidebarNav/index.ts | 1 + .../components/blocks/Region/Sidebar/index.ts | 1 + .../components/blocks/Region/TopBar/index.ts | 1 + .../sources/components/blocks/Region/index.ts | 3 + .../sources/components/blocks/index.ts | 3 + .../Fields/InputTextField/InputTextField.tsx | 27 +++++ .../ui/Fields/InputTextField/index.ts | 1 + .../sources/components/ui/Fields/index.ts | 1 + .../ui/Loader/BlockLoader/BlockLoader.tsx | 7 ++ .../components/ui/Loader/BlockLoader/index.ts | 1 + .../components/ui/Loader/Spinner/Spinner.tsx | 5 + .../components/ui/Loader/Spinner/index.ts | 1 + .../sources/components/ui/Loader/index.ts | 2 + .../snippets/sources/components/ui/index.ts | 2 + .../basic/snippets/sources/constants/index.ts | 1 + .../snippets/sources/constants/route-paths.ts | 5 + .../_ts/basic/snippets/sources/env/dev.env | 0 .../_ts/basic/snippets/sources/env/prod.env | 0 recipes/_ts/basic/snippets/sources/env/qa.env | 0 .../basic/snippets/sources/hooks/useFetch.tsx | 23 ++++ .../sources/hooks/useOnlineStatus.tsx | 21 ++++ .../basic/snippets/sources/hooks/usePost.tsx | 24 +++++ .../_ts/basic/snippets/sources/i18n/index.ts | 6 ++ recipes/_ts/basic/snippets/sources/index.js | 15 +++ .../sources/modules/Dashboard/index.ts | 1 + .../sources/modules/NotFound/NotFound.tsx | 21 ++++ .../sources/modules/NotFound/index.ts | 1 + .../snippets/sources/modules/SignIn/index.ts | 1 + .../basic/snippets/sources/modules/index.ts | 3 + .../sources/static/images/Settings.svg | 1 + .../basic/snippets/sources/static/index.html | 29 +++++ .../sources/static/translations/en.json | 6 ++ .../_ts/basic/snippets/sources/utils/index.ts | 1 + .../snippets/sources/utils/route-paths.tsx | 5 + .../basic/snippets/sources/utils/storage.tsx | 18 ++++ .../_ts/basic/snippets/sources/withI18n.js | 40 +++++++ recipes/_ts/basic/snippets/tsconfig.json | 15 +++ recipes/_ts/basic/snippets/webpack.config.js | 102 ++++++++++++++++++ recipes/install.js | 65 ++++++++--- recipes/moduleMatrix.js | 1 + 59 files changed, 980 insertions(+), 17 deletions(-) create mode 100644 recipes/_ts/basic/config.js create mode 100644 recipes/_ts/basic/snippets/.babelrc create mode 100644 recipes/_ts/basic/snippets/.eslintrc.json create mode 100644 recipes/_ts/basic/snippets/.prettierrc.json create mode 100644 recipes/_ts/basic/snippets/index.js create mode 100644 recipes/_ts/basic/snippets/sources/App.js create mode 100644 recipes/_ts/basic/snippets/sources/Dashboard.js create mode 100644 recipes/_ts/basic/snippets/sources/PageLoader.js create mode 100644 recipes/_ts/basic/snippets/sources/Routes.js create mode 100644 recipes/_ts/basic/snippets/sources/Sidebar.js create mode 100644 recipes/_ts/basic/snippets/sources/SignIn.js create mode 100644 recipes/_ts/basic/snippets/sources/TopBar.js create mode 100644 recipes/_ts/basic/snippets/sources/components/blocks/ErrorHandler/ErrorHandler.tsx create mode 100644 recipes/_ts/basic/snippets/sources/components/blocks/ErrorHandler/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/components/blocks/Loader/PageLoader/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/components/blocks/Loader/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/components/blocks/Region/Footer/Footer.tsx create mode 100644 recipes/_ts/basic/snippets/sources/components/blocks/Region/Footer/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/components/blocks/Region/Sidebar/SidebarNav/SidebarNav.tsx create mode 100644 recipes/_ts/basic/snippets/sources/components/blocks/Region/Sidebar/SidebarNav/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/components/blocks/Region/Sidebar/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/components/blocks/Region/TopBar/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/components/blocks/Region/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/components/blocks/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/components/ui/Fields/InputTextField/InputTextField.tsx create mode 100644 recipes/_ts/basic/snippets/sources/components/ui/Fields/InputTextField/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/components/ui/Fields/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/components/ui/Loader/BlockLoader/BlockLoader.tsx create mode 100644 recipes/_ts/basic/snippets/sources/components/ui/Loader/BlockLoader/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/components/ui/Loader/Spinner/Spinner.tsx create mode 100644 recipes/_ts/basic/snippets/sources/components/ui/Loader/Spinner/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/components/ui/Loader/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/components/ui/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/constants/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/constants/route-paths.ts create mode 100644 recipes/_ts/basic/snippets/sources/env/dev.env create mode 100644 recipes/_ts/basic/snippets/sources/env/prod.env create mode 100644 recipes/_ts/basic/snippets/sources/env/qa.env create mode 100644 recipes/_ts/basic/snippets/sources/hooks/useFetch.tsx create mode 100644 recipes/_ts/basic/snippets/sources/hooks/useOnlineStatus.tsx create mode 100644 recipes/_ts/basic/snippets/sources/hooks/usePost.tsx create mode 100644 recipes/_ts/basic/snippets/sources/i18n/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/index.js create mode 100644 recipes/_ts/basic/snippets/sources/modules/Dashboard/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/modules/NotFound/NotFound.tsx create mode 100644 recipes/_ts/basic/snippets/sources/modules/NotFound/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/modules/SignIn/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/modules/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/static/images/Settings.svg create mode 100644 recipes/_ts/basic/snippets/sources/static/index.html create mode 100644 recipes/_ts/basic/snippets/sources/static/translations/en.json create mode 100644 recipes/_ts/basic/snippets/sources/utils/index.ts create mode 100644 recipes/_ts/basic/snippets/sources/utils/route-paths.tsx create mode 100644 recipes/_ts/basic/snippets/sources/utils/storage.tsx create mode 100644 recipes/_ts/basic/snippets/sources/withI18n.js create mode 100644 recipes/_ts/basic/snippets/tsconfig.json create mode 100644 recipes/_ts/basic/snippets/webpack.config.js diff --git a/recipes/_ts/basic/config.js b/recipes/_ts/basic/config.js new file mode 100644 index 0000000..e4433b1 --- /dev/null +++ b/recipes/_ts/basic/config.js @@ -0,0 +1,72 @@ +const getConfig = () => { + return { + bundler: "webpack", + ui: "material-ui", + state: "none", + buildDir: "public", + sourceDir: { + main: "src", + static: "static", + assets: "assets", + images: "images", + containers: "modules", + i18n: "i18n", + components: "components", + businessLogic: "blocks", + userInterface: "ui", + utility: "utils", + hooks: "hooks", + theme: "theme", + store: "store", + services: "services", + locales: "translations", + }, + modules: { + signIn: "SignIn", + dashboard: "Dashboard", + notFound: "NotFound", + }, + canAdd: { + gitIgnore: true, + eslint: true, + environment: true, + prettier: true, + husky: true, + hooks: true, + hookForm: true, + routes: true, + utils: true, + static: true, + i18n: true, + modules: true, + componentsCopy: false, + fullComponents: true, + }, + }; +}; + +const getModulesList = () => { + return [ + "reactWithI18n", + "router", + "services", + "utils" + ]; +}; + +const getDevModulesList = () => { + return [ + "webpack", + "webpackPlugins", + "webpackLoaders", + "babel", + "basicTypescriptDev" + + ]; +}; + +module.exports = { + getConfig, + getModulesList, + getDevModulesList, +}; diff --git a/recipes/_ts/basic/snippets/.babelrc b/recipes/_ts/basic/snippets/.babelrc new file mode 100644 index 0000000..a3feffc --- /dev/null +++ b/recipes/_ts/basic/snippets/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "node": "current" + } + } + ], + "@babel/preset-react", + "@babel/preset-typescript" + ] +} \ No newline at end of file diff --git a/recipes/_ts/basic/snippets/.eslintrc.json b/recipes/_ts/basic/snippets/.eslintrc.json new file mode 100644 index 0000000..c4f6a88 --- /dev/null +++ b/recipes/_ts/basic/snippets/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["airbnb-base", "prettier"] +} \ No newline at end of file diff --git a/recipes/_ts/basic/snippets/.prettierrc.json b/recipes/_ts/basic/snippets/.prettierrc.json new file mode 100644 index 0000000..03207f6 --- /dev/null +++ b/recipes/_ts/basic/snippets/.prettierrc.json @@ -0,0 +1,20 @@ +{ + "arrowParens": "always", + "bracketSpacing": true, + "embeddedLanguageFormatting": "auto", + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "jsxBracketSameLine": false, + "jsxSingleQuote": false, + "printWidth": 100, + "proseWrap": "preserve", + "quoteProps": "as-needed", + "requirePragma": false, + "semi": true, + "singleQuote": false, + "tabWidth": 2, + "trailingComma": "all", + "useTabs": false, + "vueIndentScriptAndStyle": false, + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/recipes/_ts/basic/snippets/index.js b/recipes/_ts/basic/snippets/index.js new file mode 100644 index 0000000..b10a447 --- /dev/null +++ b/recipes/_ts/basic/snippets/index.js @@ -0,0 +1,37 @@ +const shell = require('shelljs') +const { getWebPackConfig } = require('./webpack.config') +const rootIndex = require('./sources/index') +const rootApp = require('./sources/App') +const rootRoutes = require('./sources/Routes') +const withI18n = require('./sources/withI18n') +const signInModule = require('./sources/SignIn') +const dashboardModule = require('./sources/Dashboard') +const pageLoaderBlock = require('./sources/PageLoader') +const sidebarBlock = require('./sources/Sidebar') +const topBarBlock = require('./sources/TopBar') + +const sourceCodes = { + 'index.js': rootIndex, + 'App.js': rootApp, + 'Routes.js': rootRoutes, + 'withI18n.js': withI18n, + 'SignIn.js': signInModule, + 'Dashboard.js': dashboardModule, + 'PageLoader.js': pageLoaderBlock, + 'Sidebar.js': sidebarBlock, + 'TopBar.js': topBarBlock, +} + +const getFileContent = (fileName) => { + return shell.cat(`${__dirname}/${fileName}`) +} + +const getDynamicSourceCode = (fileName, appName, baseConfig) => { + const { getSourceCode } = sourceCodes[fileName] + return getSourceCode(appName, baseConfig) +} +module.exports = { + getFileContent, + getWebPackConfig, + getDynamicSourceCode, +} diff --git a/recipes/_ts/basic/snippets/sources/App.js b/recipes/_ts/basic/snippets/sources/App.js new file mode 100644 index 0000000..e88fc5c --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/App.js @@ -0,0 +1,32 @@ +function getSourceCode(appName, { sourceDir }) { +return `import React from 'react' +import { BrowserRouter as Router } from 'react-router-dom' +import { createBrowserHistory } from 'history' + +import Routes from './Routes' + +// Block Components. +import { ErrorHandler, PageLoader } from './components/blocks' + +import { withTranslation } from '@/${sourceDir.i18n}' + +const browserHistory = createBrowserHistory() + +function App() { + return ( + + + + + + + ) +} + +export default withTranslation(App) +` +} + +module.exports = { + getSourceCode, +}; \ No newline at end of file diff --git a/recipes/_ts/basic/snippets/sources/Dashboard.js b/recipes/_ts/basic/snippets/sources/Dashboard.js new file mode 100644 index 0000000..10df319 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/Dashboard.js @@ -0,0 +1,57 @@ +function getSourceCode(appName, { sourceDir }) { +return `import React from 'react' +import { useNavigate } from 'react-router-dom' +import PropTypes from 'prop-types' + +import { I18nMsg } from '@/${sourceDir.i18n}' + +// Utils. +import { RoutePaths } from '@/${sourceDir.utility}' + +// Service Hooks +import useFetch from '@/${sourceDir.hooks}/useFetch' + +const SAMPLE_GET_API_URL = 'https://jsonplaceholder.typicode.com/users' + +const Dashboard = (props) => { + const navigate = useNavigate() + + const { loading, error, response = [] } = useFetch(SAMPLE_GET_API_URL) + + if (loading) return 'Loading..' + if (error) return error.message + + return ( + <> +
+
+

+ goes here +

+ {response.map((user) => { + return
  • {user.name}
  • + })} + +
    +
    + + ) +} + +Dashboard.propTypes = { + title: PropTypes.string, +} + +export default Dashboard +` +} + +module.exports = { + getSourceCode, +}; \ No newline at end of file diff --git a/recipes/_ts/basic/snippets/sources/PageLoader.js b/recipes/_ts/basic/snippets/sources/PageLoader.js new file mode 100644 index 0000000..97ec839 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/PageLoader.js @@ -0,0 +1,16 @@ +function getSourceCode(appName, { sourceDir }) { +return `import React, { Fragment } from 'react' + +// UI Components. +import { Spinner } from '@/components/ui' + +export default function PageLoader({ loading }) { + // Add your business logic with store condition. + return {loading && } +} +` +} + +module.exports = { + getSourceCode, +}; \ No newline at end of file diff --git a/recipes/_ts/basic/snippets/sources/Routes.js b/recipes/_ts/basic/snippets/sources/Routes.js new file mode 100644 index 0000000..d82a4ec --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/Routes.js @@ -0,0 +1,42 @@ +function getSourceCode(appName, { modules }) { + return `import React, { Suspense } from 'react' +import { Route, Routes } from 'react-router-dom' + +// Block Components +import { PageLoader } from '@/components/blocks' + +// Utils +import { RoutePaths } from '@/utils' + +// Lazy-loaded modules +const SignInModule = React.lazy(() => + import(/* webpackChunkName: "${modules.signIn}" */ '@/modules/${modules.signIn}') +) + +const DashBoardModule = React.lazy(() => + import(/* webpackChunkName: "${modules.dashboard}" */ '@/modules/${modules.dashboard}') +) + +const NotFoundModule = React.lazy(() => + import(/* webpackChunkName: "${modules.notFound}" */ '@/modules/${modules.notFound}') +) + +const RoutesComponent = () => { + return ( + }> + + } /> + } /> + } /> + + + ) +} + +export default RoutesComponent +` +} + +module.exports = { + getSourceCode, +} diff --git a/recipes/_ts/basic/snippets/sources/Sidebar.js b/recipes/_ts/basic/snippets/sources/Sidebar.js new file mode 100644 index 0000000..350dce7 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/Sidebar.js @@ -0,0 +1,51 @@ +function getSourceCode(appName, { sourceDir }) { +return `import React from 'react' +import PropTypes from 'prop-types' +import { useI18n } from '@/${sourceDir.i18n}' + +// Domain Components. +import SidebarNav from './SidebarNav' + +// Utils. +import { RoutePaths } from '@/${sourceDir.utility}' + +const Sidebar = (props) => { + const { open, variant, onClose, className, ...rest } = props + const { formatMessage } = useI18n() + + const pages = [ + { + title: formatMessage({ id: 'dashboard' }), + href: RoutePaths.dashboard, + icon: '', + }, + { + title: formatMessage({ id: 'other_module' }), + href: RoutePaths.dashboard, + icon: '', + }, + ] + + return ( +
    +
    + +
    +
    + ) +} + +Sidebar.propTypes = { + className: PropTypes.string, + onClose: PropTypes.func, + open: PropTypes.bool.isRequired, + variant: PropTypes.string.isRequired, +} + +export default Sidebar +` +} + +module.exports = { + getSourceCode, +}; \ No newline at end of file diff --git a/recipes/_ts/basic/snippets/sources/SignIn.js b/recipes/_ts/basic/snippets/sources/SignIn.js new file mode 100644 index 0000000..da489f1 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/SignIn.js @@ -0,0 +1,74 @@ +function getSourceCode(appName, { sourceDir }) { +return `import React from 'react' +import { useNavigate } from 'react-router-dom' +import PropTypes from 'prop-types' + +// UI Components. +import { InputTextField } from '@/components/ui' + +// Custom Hooks. +import { useI18n } from '@/${sourceDir.i18n}' + +// Utils. +import { RoutePaths } from '@/${sourceDir.utility}' + +// Service Hooks +import usePost from '@/${sourceDir.hooks}/usePost' + +const SAMPLE_POST_API_URL = 'https://jsonplaceholder.typicode.com/posts' + +const SignIn = (props) => { + const navigate = useNavigate() + const { formatMessage } = useI18n() + + const { loading, error, response, sendPostData } = usePost( + SAMPLE_POST_API_URL, + 'sendPostData' + ) + + return ( + <> +
    +
    +

    Login Form

    +
    + {error && 'API is failed'} + {response && 'Submitted successfully'} +
    + Username: + + +
    +
    + + ) +} + +SignIn.propTypes = { + title: PropTypes.string, +} + +export default SignIn +` +} + +module.exports = { + getSourceCode, +}; diff --git a/recipes/_ts/basic/snippets/sources/TopBar.js b/recipes/_ts/basic/snippets/sources/TopBar.js new file mode 100644 index 0000000..66da682 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/TopBar.js @@ -0,0 +1,31 @@ +function getSourceCode(appName, { sourceDir }) { +return `import React from 'react' +import { Link as RouterLink } from 'react-router-dom' +import PropTypes from 'prop-types' + +import { I18nMsg } from '@/${sourceDir.i18n}' + +const TopBar = (props) => { + const { className, ...rest } = props + + return ( +
    + + + +
    + ) +} + +TopBar.propTypes = { + className: PropTypes.string, + onSidebarOpen: PropTypes.func, +} + +export default TopBar +` +} + +module.exports = { + getSourceCode, +}; \ No newline at end of file diff --git a/recipes/_ts/basic/snippets/sources/components/blocks/ErrorHandler/ErrorHandler.tsx b/recipes/_ts/basic/snippets/sources/components/blocks/ErrorHandler/ErrorHandler.tsx new file mode 100644 index 0000000..6592b24 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/blocks/ErrorHandler/ErrorHandler.tsx @@ -0,0 +1,35 @@ +import React, { Component } from 'react' + +export default class ErrorBoundary extends Component { + constructor(props) { + super(props) + this.state = { error: null, errorInfo: null } + } + + componentDidCatch(error, errorInfo) { + // Catch errors in any components below and re-render with error message + this.setState({ + error: error, + errorInfo: errorInfo, + }) + // You can also log error messages to an error reporting service here + } + + render() { + if (this.state.errorInfo) { + // Error path + return ( +
    +

    Something went wrong.

    +
    + {this.state.error && this.state.error.toString()} +
    + {this.state.errorInfo.componentStack} +
    +
    + ) + } + // Normally, just render children + return this.props.children + } +} diff --git a/recipes/_ts/basic/snippets/sources/components/blocks/ErrorHandler/index.ts b/recipes/_ts/basic/snippets/sources/components/blocks/ErrorHandler/index.ts new file mode 100644 index 0000000..55ed726 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/blocks/ErrorHandler/index.ts @@ -0,0 +1 @@ +export { default } from './ErrorHandler' diff --git a/recipes/_ts/basic/snippets/sources/components/blocks/Loader/PageLoader/index.ts b/recipes/_ts/basic/snippets/sources/components/blocks/Loader/PageLoader/index.ts new file mode 100644 index 0000000..681ba20 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/blocks/Loader/PageLoader/index.ts @@ -0,0 +1 @@ +export { default } from './PageLoader' diff --git a/recipes/_ts/basic/snippets/sources/components/blocks/Loader/index.ts b/recipes/_ts/basic/snippets/sources/components/blocks/Loader/index.ts new file mode 100644 index 0000000..715d2d1 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/blocks/Loader/index.ts @@ -0,0 +1 @@ +export { default as PageLoader } from './PageLoader' diff --git a/recipes/_ts/basic/snippets/sources/components/blocks/Region/Footer/Footer.tsx b/recipes/_ts/basic/snippets/sources/components/blocks/Region/Footer/Footer.tsx new file mode 100644 index 0000000..b71b26e --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/blocks/Region/Footer/Footer.tsx @@ -0,0 +1,25 @@ +import React from 'react' +import PropTypes from 'prop-types' + +const Footer = (props) => { + const { className, ...rest } = props + + return ( +
    +
    + ©{' '} + + React Chef + + . 2020 +
    +
    React Chef
    +
    + ) +} + +Footer.propTypes = { + className: PropTypes.string, +} + +export default Footer diff --git a/recipes/_ts/basic/snippets/sources/components/blocks/Region/Footer/index.ts b/recipes/_ts/basic/snippets/sources/components/blocks/Region/Footer/index.ts new file mode 100644 index 0000000..5d06e9b --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/blocks/Region/Footer/index.ts @@ -0,0 +1 @@ +export { default } from './Footer' diff --git a/recipes/_ts/basic/snippets/sources/components/blocks/Region/Sidebar/SidebarNav/SidebarNav.tsx b/recipes/_ts/basic/snippets/sources/components/blocks/Region/Sidebar/SidebarNav/SidebarNav.tsx new file mode 100644 index 0000000..0289185 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/blocks/Region/Sidebar/SidebarNav/SidebarNav.tsx @@ -0,0 +1,23 @@ +import React from 'react' +import PropTypes from 'prop-types' + +const SidebarNav = (props) => { + const { pages, className, ...rest } = props + + return ( + + ) +} + +SidebarNav.propTypes = { + className: PropTypes.string, + pages: PropTypes.array.isRequired, +} + +export default SidebarNav diff --git a/recipes/_ts/basic/snippets/sources/components/blocks/Region/Sidebar/SidebarNav/index.ts b/recipes/_ts/basic/snippets/sources/components/blocks/Region/Sidebar/SidebarNav/index.ts new file mode 100644 index 0000000..bd8db7e --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/blocks/Region/Sidebar/SidebarNav/index.ts @@ -0,0 +1 @@ +export { default } from './SidebarNav' diff --git a/recipes/_ts/basic/snippets/sources/components/blocks/Region/Sidebar/index.ts b/recipes/_ts/basic/snippets/sources/components/blocks/Region/Sidebar/index.ts new file mode 100644 index 0000000..877187c --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/blocks/Region/Sidebar/index.ts @@ -0,0 +1 @@ +export { default } from './Sidebar' diff --git a/recipes/_ts/basic/snippets/sources/components/blocks/Region/TopBar/index.ts b/recipes/_ts/basic/snippets/sources/components/blocks/Region/TopBar/index.ts new file mode 100644 index 0000000..4f7373f --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/blocks/Region/TopBar/index.ts @@ -0,0 +1 @@ +export { default } from './TopBar' diff --git a/recipes/_ts/basic/snippets/sources/components/blocks/Region/index.ts b/recipes/_ts/basic/snippets/sources/components/blocks/Region/index.ts new file mode 100644 index 0000000..0833b26 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/blocks/Region/index.ts @@ -0,0 +1,3 @@ +export { default as Footer } from './Footer' +export { default as Sidebar } from './Sidebar' +export { default as TopBar } from './TopBar' diff --git a/recipes/_ts/basic/snippets/sources/components/blocks/index.ts b/recipes/_ts/basic/snippets/sources/components/blocks/index.ts new file mode 100644 index 0000000..3a09345 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/blocks/index.ts @@ -0,0 +1,3 @@ +export { TopBar, Sidebar, Footer } from './Region' +export { PageLoader } from './Loader' +export { default as ErrorHandler } from './ErrorHandler' diff --git a/recipes/_ts/basic/snippets/sources/components/ui/Fields/InputTextField/InputTextField.tsx b/recipes/_ts/basic/snippets/sources/components/ui/Fields/InputTextField/InputTextField.tsx new file mode 100644 index 0000000..f776e41 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/ui/Fields/InputTextField/InputTextField.tsx @@ -0,0 +1,27 @@ +import React from 'react' + +export default function InputTextField({ + name, + initialValue = '', + value, + placeholder, + handleChange, + onBlur, +}) { + const onChange = (event) => { + handleChange(event) + } + + return ( +
    + +
    + ) +} diff --git a/recipes/_ts/basic/snippets/sources/components/ui/Fields/InputTextField/index.ts b/recipes/_ts/basic/snippets/sources/components/ui/Fields/InputTextField/index.ts new file mode 100644 index 0000000..1f467a1 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/ui/Fields/InputTextField/index.ts @@ -0,0 +1 @@ +export { default } from './InputTextField' diff --git a/recipes/_ts/basic/snippets/sources/components/ui/Fields/index.ts b/recipes/_ts/basic/snippets/sources/components/ui/Fields/index.ts new file mode 100644 index 0000000..de2e702 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/ui/Fields/index.ts @@ -0,0 +1 @@ +export { default as InputTextField } from './InputTextField' diff --git a/recipes/_ts/basic/snippets/sources/components/ui/Loader/BlockLoader/BlockLoader.tsx b/recipes/_ts/basic/snippets/sources/components/ui/Loader/BlockLoader/BlockLoader.tsx new file mode 100644 index 0000000..ded7efd --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/ui/Loader/BlockLoader/BlockLoader.tsx @@ -0,0 +1,7 @@ +import React from 'react' + +const BlockLoader = ({ props }) => { + return <>Loader goes here... +} + +export default BlockLoader diff --git a/recipes/_ts/basic/snippets/sources/components/ui/Loader/BlockLoader/index.ts b/recipes/_ts/basic/snippets/sources/components/ui/Loader/BlockLoader/index.ts new file mode 100644 index 0000000..2ebe7f4 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/ui/Loader/BlockLoader/index.ts @@ -0,0 +1 @@ +export { default } from './BlockLoader' diff --git a/recipes/_ts/basic/snippets/sources/components/ui/Loader/Spinner/Spinner.tsx b/recipes/_ts/basic/snippets/sources/components/ui/Loader/Spinner/Spinner.tsx new file mode 100644 index 0000000..59d3c0e --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/ui/Loader/Spinner/Spinner.tsx @@ -0,0 +1,5 @@ +import React from 'react' + +export default function Spinner(props) { + return
    Spinner goes here...
    +} diff --git a/recipes/_ts/basic/snippets/sources/components/ui/Loader/Spinner/index.ts b/recipes/_ts/basic/snippets/sources/components/ui/Loader/Spinner/index.ts new file mode 100644 index 0000000..0be0188 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/ui/Loader/Spinner/index.ts @@ -0,0 +1 @@ +export { default } from './Spinner' diff --git a/recipes/_ts/basic/snippets/sources/components/ui/Loader/index.ts b/recipes/_ts/basic/snippets/sources/components/ui/Loader/index.ts new file mode 100644 index 0000000..5d4f2e0 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/ui/Loader/index.ts @@ -0,0 +1,2 @@ +export { default as Spinner } from './Spinner' +export { default as BlockLoader } from './BlockLoader' diff --git a/recipes/_ts/basic/snippets/sources/components/ui/index.ts b/recipes/_ts/basic/snippets/sources/components/ui/index.ts new file mode 100644 index 0000000..8cb7e22 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/components/ui/index.ts @@ -0,0 +1,2 @@ +export { Spinner, BlockLoader } from './Loader' +export { InputTextField } from './Fields' diff --git a/recipes/_ts/basic/snippets/sources/constants/index.ts b/recipes/_ts/basic/snippets/sources/constants/index.ts new file mode 100644 index 0000000..40e15e0 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/constants/index.ts @@ -0,0 +1 @@ +export { default as RoutePaths } from './route-paths' diff --git a/recipes/_ts/basic/snippets/sources/constants/route-paths.ts b/recipes/_ts/basic/snippets/sources/constants/route-paths.ts new file mode 100644 index 0000000..6965dad --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/constants/route-paths.ts @@ -0,0 +1,5 @@ +export default { + SignIn: '/', + DashBoard: '/dashboard', + NotFound: '/not-found', +} diff --git a/recipes/_ts/basic/snippets/sources/env/dev.env b/recipes/_ts/basic/snippets/sources/env/dev.env new file mode 100644 index 0000000..e69de29 diff --git a/recipes/_ts/basic/snippets/sources/env/prod.env b/recipes/_ts/basic/snippets/sources/env/prod.env new file mode 100644 index 0000000..e69de29 diff --git a/recipes/_ts/basic/snippets/sources/env/qa.env b/recipes/_ts/basic/snippets/sources/env/qa.env new file mode 100644 index 0000000..e69de29 diff --git a/recipes/_ts/basic/snippets/sources/hooks/useFetch.tsx b/recipes/_ts/basic/snippets/sources/hooks/useFetch.tsx new file mode 100644 index 0000000..cc757ae --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/hooks/useFetch.tsx @@ -0,0 +1,23 @@ +import { useState, useEffect } from 'react' +import axios from 'axios' + +export default function useFetch(url) { + const [response, setResponse] = useState(undefined) + const [error, setError] = useState() + const [loading, setLoading] = useState(false) + + useEffect(async () => { + if (!url || url.trim() === '') return + try { + setLoading(true) + const result = await axios.get(url) + setResponse(result.data) + } catch (err) { + setError(err) + } finally { + setLoading(false) + } + }, [url]) + + return { response, error, loading } +} \ No newline at end of file diff --git a/recipes/_ts/basic/snippets/sources/hooks/useOnlineStatus.tsx b/recipes/_ts/basic/snippets/sources/hooks/useOnlineStatus.tsx new file mode 100644 index 0000000..2bb3668 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/hooks/useOnlineStatus.tsx @@ -0,0 +1,21 @@ +function useOnlineStatus() { + const [online, setOnline] = useState(window.navigator.onLine) + + useEffect(() => { + function handleOnline() { + setOnline(true) + } + function handleOffline() { + setOnline(false) + } + window.addEventListener('online', handleOnline) + window.addEventListener('offline', handleOffline) + return () => { + window.removeEventListener('online', handleOnline) + window.removeEventListener('offline', handleOffline) + } + }, []) + return online +} + +export default useOnlineStatus diff --git a/recipes/_ts/basic/snippets/sources/hooks/usePost.tsx b/recipes/_ts/basic/snippets/sources/hooks/usePost.tsx new file mode 100644 index 0000000..53fc9a1 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/hooks/usePost.tsx @@ -0,0 +1,24 @@ +import { useState } from 'react' +import axios from 'axios' + +export default function usePost(url, methodName = 'sendPostData'){ + + const [response, setResponse] = useState(undefined) + const [error, setError] = useState() + const [loading, setLoading] = useState(false) + + const postData = async (payload) => { + if (!url || url.trim() === '') return + try { + setLoading(true) + const result = await axios.post(url, payload) + setResponse(result.data) + } catch (err) { + setError(err) + } finally { + setLoading(false) + } + } + + return { response, error, loading, [methodName]:postData } +} \ No newline at end of file diff --git a/recipes/_ts/basic/snippets/sources/i18n/index.ts b/recipes/_ts/basic/snippets/sources/i18n/index.ts new file mode 100644 index 0000000..21e90ac --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/i18n/index.ts @@ -0,0 +1,6 @@ +import { useIntl, FormattedMessage } from 'react-intl' + +export { default as withTranslation } from './withI18n' + +export const useI18n = useIntl +export const I18nMsg = FormattedMessage diff --git a/recipes/_ts/basic/snippets/sources/index.js b/recipes/_ts/basic/snippets/sources/index.js new file mode 100644 index 0000000..c5bb6af --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/index.js @@ -0,0 +1,15 @@ +function getSourceCode(appName) { +return `import React from 'react' +import { createRoot } from 'react-dom/client' + +import App from './App' + +const container = document.getElementById('${appName}') +const root = createRoot(container) +root.render() +` +} + +module.exports = { + getSourceCode, +}; diff --git a/recipes/_ts/basic/snippets/sources/modules/Dashboard/index.ts b/recipes/_ts/basic/snippets/sources/modules/Dashboard/index.ts new file mode 100644 index 0000000..a7a1745 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/modules/Dashboard/index.ts @@ -0,0 +1 @@ +export { default } from './Dashboard' diff --git a/recipes/_ts/basic/snippets/sources/modules/NotFound/NotFound.tsx b/recipes/_ts/basic/snippets/sources/modules/NotFound/NotFound.tsx new file mode 100644 index 0000000..c25aae9 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/modules/NotFound/NotFound.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import { useNavigate } from 'react-router-dom' + +// Utils. +import { RoutePaths } from '@/utils' + +const NotFound = () => { + const navigate = useNavigate(); + + return ( +
    +
    + +

    404 - Page not found

    + +
    +
    + ) +} + +export default NotFound diff --git a/recipes/_ts/basic/snippets/sources/modules/NotFound/index.ts b/recipes/_ts/basic/snippets/sources/modules/NotFound/index.ts new file mode 100644 index 0000000..2893216 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/modules/NotFound/index.ts @@ -0,0 +1 @@ +export { default } from './NotFound' diff --git a/recipes/_ts/basic/snippets/sources/modules/SignIn/index.ts b/recipes/_ts/basic/snippets/sources/modules/SignIn/index.ts new file mode 100644 index 0000000..9c9eb75 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/modules/SignIn/index.ts @@ -0,0 +1 @@ +export { default } from './SignIn' diff --git a/recipes/_ts/basic/snippets/sources/modules/index.ts b/recipes/_ts/basic/snippets/sources/modules/index.ts new file mode 100644 index 0000000..65a2fb9 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/modules/index.ts @@ -0,0 +1,3 @@ +export { default as SignIn } from './SignIn' +export { default as Dashboard } from './Dashboard' +export { default as NotFound } from './NotFound' diff --git a/recipes/_ts/basic/snippets/sources/static/images/Settings.svg b/recipes/_ts/basic/snippets/sources/static/images/Settings.svg new file mode 100644 index 0000000..7685cb0 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/static/images/Settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/recipes/_ts/basic/snippets/sources/static/index.html b/recipes/_ts/basic/snippets/sources/static/index.html new file mode 100644 index 0000000..ec8236f --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/static/index.html @@ -0,0 +1,29 @@ + + + + + + + + + + + + + <%= htmlWebpackPlugin.options.APP_ROOT_ID %> + + + + +
    + + diff --git a/recipes/_ts/basic/snippets/sources/static/translations/en.json b/recipes/_ts/basic/snippets/sources/static/translations/en.json new file mode 100644 index 0000000..4aea446 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/static/translations/en.json @@ -0,0 +1,6 @@ +{ + "app_name": "Google Clone", + "user_name": "User name", + "dashboard": "Dashboard", + "other_module": "Other module name" +} diff --git a/recipes/_ts/basic/snippets/sources/utils/index.ts b/recipes/_ts/basic/snippets/sources/utils/index.ts new file mode 100644 index 0000000..40e15e0 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/utils/index.ts @@ -0,0 +1 @@ +export { default as RoutePaths } from './route-paths' diff --git a/recipes/_ts/basic/snippets/sources/utils/route-paths.tsx b/recipes/_ts/basic/snippets/sources/utils/route-paths.tsx new file mode 100644 index 0000000..6965dad --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/utils/route-paths.tsx @@ -0,0 +1,5 @@ +export default { + SignIn: '/', + DashBoard: '/dashboard', + NotFound: '/not-found', +} diff --git a/recipes/_ts/basic/snippets/sources/utils/storage.tsx b/recipes/_ts/basic/snippets/sources/utils/storage.tsx new file mode 100644 index 0000000..92f25a2 --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/utils/storage.tsx @@ -0,0 +1,18 @@ +const noop = () => '' + +export const storageAPI = (type = 'local') => { + if (typeof window !== 'undefined') { + if (type === 'local') { + return window.localStorage + } + + return window.sessionStorage + } + + return { + getItem: noop, + setItem: noop, + removeItem: noop, + clear: noop, + } +} \ No newline at end of file diff --git a/recipes/_ts/basic/snippets/sources/withI18n.js b/recipes/_ts/basic/snippets/sources/withI18n.js new file mode 100644 index 0000000..a6810dd --- /dev/null +++ b/recipes/_ts/basic/snippets/sources/withI18n.js @@ -0,0 +1,40 @@ +function getSourceCode(appName, { sourceDir }) { +const langUrl = `${sourceDir.locales}/` + '${lang}.json' + +return `import React, { useState, useEffect } from 'react' +import { IntlProvider } from 'react-intl' +import axios from 'axios' + +const fetchTranslations = async (lang) => { + return await axios.get(\`${langUrl}\`) +} + +const withI18n = (Component) => (props) => { + const [messages, setMessages] = useState({}) + const locale = 'en' + + useEffect(() => { + fetchTranslations(locale).then((response) => { + setMessages(response.data) + }) + }, []) + + return ( + {}} + > + + + ) +} + +export default withI18n +` +} + +module.exports = { + getSourceCode, +}; \ No newline at end of file diff --git a/recipes/_ts/basic/snippets/tsconfig.json b/recipes/_ts/basic/snippets/tsconfig.json new file mode 100644 index 0000000..8141f8e --- /dev/null +++ b/recipes/_ts/basic/snippets/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/ui/*": ["src/ui/*"], + "@/blocks/*": ["src/blocks/*"], + "@/constants/*": ["src/constants/*"], + "@/hooks/*": ["src/hooks/*"], + "@/i18n/*": ["src/i18n/*"], + "@/modules/*": ["src/modules/*"], + "@/utils/*": ["src/utils/*"] + } + }, + "include": ["src"] +} diff --git a/recipes/_ts/basic/snippets/webpack.config.js b/recipes/_ts/basic/snippets/webpack.config.js new file mode 100644 index 0000000..bdbec2e --- /dev/null +++ b/recipes/_ts/basic/snippets/webpack.config.js @@ -0,0 +1,102 @@ +function getWebPackConfig(appName, { sourceDir, buildDir, portNumber }) { + return ` +const webpack = require("webpack"); +const path = require("path"); +const PACKAGE = require("./package.json"); + +// WebPack Plugins +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const CopyPlugin = require("copy-webpack-plugin"); + +module.exports = { + entry: "./${sourceDir.main}/index.tsx", + + module: { + rules: [ + { + test: /\\.(ts|tsx)$/, + exclude: /node_modules/, + use: ["babel-loader"], + }, + { + test: /\\.js$/, + exclude: /node_modules/, + use: ["babel-loader"], + }, + { + test: /\\.svg$/, + use: ["@svgr/webpack", "file-loader"], + }, + { + test: /\\.(png|jpg|jpeg|gif)$/i, + use: [ + { + loader: "file-loader", + }, + ], + }, + ], + }, + + resolve: { + extensions: [".*", ".js", ".ts", ".tsx"], + alias: { + "@": path.resolve(__dirname, "src"), + }, + }, + + output: { + path: path.resolve(__dirname, "${buildDir}"), + filename: "${appName}.js", + chunkFilename: "[name].js", + }, + + plugins: [ + new webpack.HotModuleReplacementPlugin(), + new webpack.EnvironmentPlugin({ + VERSION: PACKAGE.version, + }), + new HtmlWebpackPlugin({ + inject: true, + template: path.resolve(__dirname, "${sourceDir.main}/${sourceDir.static}/index.html"), + APP_ROOT_ID: "${appName}", + APP_VERSION: PACKAGE.version, + }), + new CopyPlugin({ + patterns: [ + { + from: "./${sourceDir.main}/${sourceDir.static}/${sourceDir.images}", + to: "images", + }, + { + from: "./${sourceDir.main}/${sourceDir.static}/${sourceDir.locales}/en.json", + to: "${sourceDir.locales}/en.json", + }, + ], + }), + ], + + devServer: { + open: true, + historyApiFallback: true, + static: { + directory: path.resolve(__dirname, "${sourceDir.main}/${sourceDir.static}"), + }, + hot: true, + port: ${portNumber}, + proxy: [ + { + context: ["/api"], // list of paths to proxy + target: "http://YOUR_API_URL:9000", + changeOrigin: true, + secure: false, // optional: allows self-signed certificates + }, + ], + }, +}; +`; +} + +module.exports = { + getWebPackConfig, +}; diff --git a/recipes/install.js b/recipes/install.js index dd99256..87d6c66 100644 --- a/recipes/install.js +++ b/recipes/install.js @@ -12,6 +12,10 @@ const slimSnippet = require("./slim/snippets"); const slimTypeScriptConfig = require("./_ts/slim/config"); const slimTypeScriptSnippet = require("./_ts/slim/snippets"); +// basic Typescript Version +const basicTypeScriptConfig = require("./_ts/basic/config"); +const basicTypeScriptSnippet = require("./_ts/basic/snippets"); + // Basic Javascript const basicConfig = require(`./basic/config`); const basicSnippet = require(`./basic/snippets`); @@ -31,7 +35,7 @@ const { getTwixtUIScripts } = require("./utils"); -const install = function(directory, appName = '') { +const install = function (directory, appName = '') { CFonts.say("React Chef", { type: 5, font: "block", // define the font face @@ -60,15 +64,19 @@ const install = function(directory, appName = '') { const defaultProjectType = 'slim'; const twixtUIProjectType = 'twixtui'; const slimTypescriptProjectType = 'slim typescript'; + const basicTypescriptProjectType = 'basic typescript'; let projectType = defaultProjectType; const isSlimProject = (type) => type === defaultProjectType; const isTwixtUIProject = (type) => type === twixtUIProjectType; const isSlimTypeScriptProject = (type) => type === slimTypescriptProjectType; const isTypeScriptProject = (type) => isSlimTypeScriptProject(type); + const isBasicTypeScriptProject = (type) => type === basicTypescriptProjectType; + const is_BasicTypeScriptProject = (type) => isBasicTypeScriptProject(type); + tryAccess(baseDirPath) .then(() => undefined, function onPathExist() { - if(!directory){ + if (!directory) { error( `Choose different App name. ${appName} is already exist in ${process.cwd()}` ); @@ -81,7 +89,7 @@ const install = function(directory, appName = '') { type: "list", name: "projectType", message: "choose your project type", - choices: ["Slim", "Slim TypeScript", "Basic", "TwixtUI"], + choices: ["Slim", "Slim TypeScript", "Basic", "TwixtUI", "Basic TypeScript"], default: "Slim", }, ]); @@ -89,7 +97,7 @@ const install = function(directory, appName = '') { .then((mainAnswer) => { projectType = mainAnswer.projectType.toLowerCase(); log(`projectType: ${projectType}`); - if(isSlimTypeScriptProject(projectType)){ + if (isSlimTypeScriptProject(projectType)) { log(`Slim Typescript - projectType: ${projectType}`); getConfig = slimTypeScriptConfig.getConfig; getModulesList = slimTypeScriptConfig.getModulesList; @@ -98,7 +106,16 @@ const install = function(directory, appName = '') { getWebPackConfig = slimTypeScriptSnippet.getWebPackConfig; getDynamicSourceCode = slimTypeScriptSnippet.getDynamicSourceCode; baseConfig = getConfig(); - } else if(isTwixtUIProject(projectType)){ + } else if (is_BasicTypeScriptProject(projectType)) { + log(`Basic TypeScript - projectType: ${projectType}`); + getConfig = basicTypeScriptConfig.getConfig; + getModulesList = basicTypeScriptConfig.getModulesList; + getDevModulesList = basicTypeScriptConfig.getDevModulesList; + getFileContent = basicTypeScriptSnippet.getFileContent; + getWebPackConfig = basicTypeScriptSnippet.getWebPackConfig; + getDynamicSourceCode = basicTypeScriptSnippet.getDynamicSourceCode; + baseConfig = getConfig(); + } else if (isTwixtUIProject(projectType)) { log(`TwixtUI - projectType: ${projectType}`); getConfig = twixtUIConfig.getConfig; getModulesList = twixtUIConfig.getModulesList; @@ -132,7 +149,7 @@ const install = function(directory, appName = '') { }, ]; - if(baseConfig.canAdd.buildDir){ + if (baseConfig.canAdd.buildDir) { projectQuestions.push({ type: "input", name: "buildDir", @@ -181,7 +198,7 @@ const install = function(directory, appName = '') { return inquirer.prompt(projectQuestions); }) .then((answers) => { - if(!directory) { + if (!directory) { shell.mkdir(baseDirPath); shell.cd(appName); } else { @@ -193,8 +210,9 @@ const install = function(directory, appName = '') { }) .then((answers) => { const isTypeScriptProjectType = isTypeScriptProject(projectType); + const is_BasicTypeScriptProjectType = is_BasicTypeScriptProject(projectType); const fileExtension = isTypeScriptProjectType ? 'ts' : 'js'; - const componentExtension = isTypeScriptProjectType ? 'tsx' : 'js'; + const componentExtension = (isTypeScriptProjectType || is_BasicTypeScriptProjectType) ? 'tsx' : 'js'; if (baseConfig.canAdd.gitIgnore) { const gitIgnoreFileName = `git-ignore.txt`; createFile('.gitignore', getFileContent(gitIgnoreFileName)); @@ -203,6 +221,10 @@ const install = function(directory, appName = '') { const babelConfigFileName = `.babelrc`; createFile(babelConfigFileName, getFileContent(babelConfigFileName)); + if (isTypeScriptProjectType) { + const tsConfigFileName = `tsconfig.json`; + createFile(tsConfigFileName, getFileContent(tsConfigFileName)); + } if (isTypeScriptProjectType) { const tsConfigFileName = `tsconfig.json`; createFile(tsConfigFileName, getFileContent(tsConfigFileName)); @@ -233,16 +255,25 @@ const install = function(directory, appName = '') { shell.mkdir(baseConfig.sourceDir.main); shell.cd(baseConfig.sourceDir.main); - const sourceSubBase = isTypeScriptProjectType ? '_ts/' : ''; - const projectTypeName = isSlimTypeScriptProject(projectType) ? 'slim' : projectType; + + let projectTypeName; + if (is_BasicTypeScriptProjectType) { + projectTypeName = 'basic'; + } else if (isSlimTypeScriptProject(projectType)) { + projectTypeName = 'slim'; + } else { + projectTypeName = projectType; + } + + const sourceSubBase = (isTypeScriptProjectType || is_BasicTypeScriptProjectType) ? '_ts/' : ''; const sourceSnippetDir = `${__dirname}/${sourceSubBase}${projectTypeName}/snippets/sources`; const indexSourceFileName = `index.js`; - const indexFileNameToCreateWithPath = !isTwixtUIProject(projectType) ? `index.${componentExtension}`: getTwixtUIIndexPath(projectType); + const indexFileNameToCreateWithPath = !isTwixtUIProject(projectType) ? `index.${componentExtension}` : getTwixtUIIndexPath(projectType); createFile(indexFileNameToCreateWithPath, getDynamicSourceCode(indexSourceFileName, appName, baseConfig)); const appSourceFileName = `App.js`; - const appFileNameToCreateWithPath = !isTwixtUIProject(projectType)? `App.${componentExtension}` : getTwixtUIHomePath(projectType); + const appFileNameToCreateWithPath = !isTwixtUIProject(projectType) ? `App.${componentExtension}` : getTwixtUIHomePath(projectType); createFile(appFileNameToCreateWithPath, getDynamicSourceCode(appSourceFileName, appName, baseConfig)); if (baseConfig.canAdd.routes) { @@ -376,16 +407,16 @@ const install = function(directory, appName = '') { build: "webpack --mode production --progress", ...(answers.eslint ? { - lint: "eslint src --ext .js", - } + lint: "eslint src --ext .js", + } : {}), ...(answers.prettier ? { - prettier: "prettier --write src", - } + prettier: "prettier --write src", + } : {}), clean: "rm -rf node_modules", - ...(isTwixtUIProject(projectType) ? getTwixtUIScripts(): {}) + ...(isTwixtUIProject(projectType) ? getTwixtUIScripts() : {}) }; delete packageFileObject.main; shell.rm("package.json"); diff --git a/recipes/moduleMatrix.js b/recipes/moduleMatrix.js index 87473eb..6b7bcfe 100644 --- a/recipes/moduleMatrix.js +++ b/recipes/moduleMatrix.js @@ -24,6 +24,7 @@ module.exports = { babel, slimDev: [...webpack, ...webpackPlugins, ...webpackLoaders, ...babel], slimTypescriptDev: [...typeScriptTooling, ...webpack, ...webpackPlugins, ...webpackLoaders, ...babelWithTypeScript], + basicTypescriptDev: [...typeScriptTooling, ...webpack, ...webpackPlugins, ...webpackLoaders, ...babelWithTypeScript], twixtUIDev: [...webpack, ...webpackPlugins, ...webpackLoaders, ...wepPackStyleLoaders, ...babel], husky: 'npm i -D husky', eslint: 'npx install-peerdeps --dev eslint-config-airbnb', From 2dc06d5e07b37790fee0124aa9095d89915979c1 Mon Sep 17 00:00:00 2001 From: Gopikrishnan200 Date: Sat, 13 Dec 2025 16:04:26 +0530 Subject: [PATCH 2/5] fix(basic/ts): add types and convert basic JS code to TypeScript --- recipes/_ts/basic/snippets/index.js | 30 +++++++----- recipes/_ts/basic/snippets/sources/App.js | 21 ++++----- .../_ts/basic/snippets/sources/Dashboard.js | 18 ++++--- .../_ts/basic/snippets/sources/PageLoader.js | 11 +++-- recipes/_ts/basic/snippets/sources/Routes.js | 2 +- recipes/_ts/basic/snippets/sources/Sidebar.js | 28 ++++++----- recipes/_ts/basic/snippets/sources/SignIn.js | 28 ++++++++--- recipes/_ts/basic/snippets/sources/TopBar.js | 15 +++--- .../blocks/ErrorHandler/ErrorHandler.tsx | 20 ++++++-- .../blocks/Region/Footer/Footer.tsx | 13 +++-- .../Region/Sidebar/SidebarNav/SidebarNav.tsx | 21 +++++---- .../Fields/InputTextField/InputTextField.tsx | 15 ++++-- .../basic/snippets/sources/hooks/useFetch.tsx | 39 +++++++++------ .../sources/hooks/useOnlineStatus.tsx | 9 +++- .../basic/snippets/sources/hooks/usePost.tsx | 47 +++++++++++-------- recipes/_ts/basic/snippets/sources/index.js | 7 ++- .../sources/modules/NotFound/NotFound.tsx | 4 +- .../_ts/basic/snippets/sources/withI18n.js | 10 ++-- recipes/install.js | 28 +++++------ 19 files changed, 227 insertions(+), 139 deletions(-) diff --git a/recipes/_ts/basic/snippets/index.js b/recipes/_ts/basic/snippets/index.js index b10a447..9df9872 100644 --- a/recipes/_ts/basic/snippets/index.js +++ b/recipes/_ts/basic/snippets/index.js @@ -11,25 +11,33 @@ const sidebarBlock = require('./sources/Sidebar') const topBarBlock = require('./sources/TopBar') const sourceCodes = { - 'index.js': rootIndex, - 'App.js': rootApp, - 'Routes.js': rootRoutes, - 'withI18n.js': withI18n, - 'SignIn.js': signInModule, - 'Dashboard.js': dashboardModule, - 'PageLoader.js': pageLoaderBlock, - 'Sidebar.js': sidebarBlock, - 'TopBar.js': topBarBlock, + index: rootIndex, + App: rootApp, + Routes: rootRoutes, + withI18n: withI18n, + SignIn: signInModule, + Dashboard: dashboardModule, + PageLoader: pageLoaderBlock, + Sidebar: sidebarBlock, + TopBar: topBarBlock, } + const getFileContent = (fileName) => { return shell.cat(`${__dirname}/${fileName}`) } const getDynamicSourceCode = (fileName, appName, baseConfig) => { - const { getSourceCode } = sourceCodes[fileName] - return getSourceCode(appName, baseConfig) + const key = fileName.replace(/\.(js|ts|tsx)$/, '') + const source = sourceCodes[key] + + if (!source) { + throw new Error(`Source template not found: ${key}`) + } + + return source.getSourceCode(appName, baseConfig) } + module.exports = { getFileContent, getWebPackConfig, diff --git a/recipes/_ts/basic/snippets/sources/App.js b/recipes/_ts/basic/snippets/sources/App.js index e88fc5c..ee9c5e9 100644 --- a/recipes/_ts/basic/snippets/sources/App.js +++ b/recipes/_ts/basic/snippets/sources/App.js @@ -1,7 +1,6 @@ -function getSourceCode(appName, { sourceDir }) { -return `import React from 'react' +function getSourceCode(appName, { sourceDir }) {return `import React from 'react' import { BrowserRouter as Router } from 'react-router-dom' -import { createBrowserHistory } from 'history' +import { createBrowserHistory, History } from 'history' import Routes from './Routes' @@ -10,16 +9,16 @@ import { ErrorHandler, PageLoader } from './components/blocks' import { withTranslation } from '@/${sourceDir.i18n}' -const browserHistory = createBrowserHistory() +const browserHistory: History = createBrowserHistory() -function App() { +const App: React.FC = () => { return ( - - - - - - + + + + + + ) } diff --git a/recipes/_ts/basic/snippets/sources/Dashboard.js b/recipes/_ts/basic/snippets/sources/Dashboard.js index 10df319..e60992a 100644 --- a/recipes/_ts/basic/snippets/sources/Dashboard.js +++ b/recipes/_ts/basic/snippets/sources/Dashboard.js @@ -1,7 +1,6 @@ function getSourceCode(appName, { sourceDir }) { return `import React from 'react' import { useNavigate } from 'react-router-dom' -import PropTypes from 'prop-types' import { I18nMsg } from '@/${sourceDir.i18n}' @@ -13,10 +12,19 @@ import useFetch from '@/${sourceDir.hooks}/useFetch' const SAMPLE_GET_API_URL = 'https://jsonplaceholder.typicode.com/users' -const Dashboard = (props) => { +interface DashboardProps { + title?: string +} + +interface User { + id: number + name: string +} + +const Dashboard: React.FC = (props) => { const navigate = useNavigate() - const { loading, error, response = [] } = useFetch(SAMPLE_GET_API_URL) + const { loading, error, response = [] } = useFetch(SAMPLE_GET_API_URL) if (loading) return 'Loading..' if (error) return error.message @@ -44,10 +52,6 @@ const Dashboard = (props) => { ) } -Dashboard.propTypes = { - title: PropTypes.string, -} - export default Dashboard ` } diff --git a/recipes/_ts/basic/snippets/sources/PageLoader.js b/recipes/_ts/basic/snippets/sources/PageLoader.js index 97ec839..546771e 100644 --- a/recipes/_ts/basic/snippets/sources/PageLoader.js +++ b/recipes/_ts/basic/snippets/sources/PageLoader.js @@ -4,11 +4,16 @@ return `import React, { Fragment } from 'react' // UI Components. import { Spinner } from '@/components/ui' -export default function PageLoader({ loading }) { - // Add your business logic with store condition. - return {loading && } +interface PageLoaderProps { + loading: boolean } + +export default function PageLoader({ loading }: PageLoaderProps) { + return loading ? : null +} + ` + } module.exports = { diff --git a/recipes/_ts/basic/snippets/sources/Routes.js b/recipes/_ts/basic/snippets/sources/Routes.js index d82a4ec..99b97fc 100644 --- a/recipes/_ts/basic/snippets/sources/Routes.js +++ b/recipes/_ts/basic/snippets/sources/Routes.js @@ -21,7 +21,7 @@ const NotFoundModule = React.lazy(() => import(/* webpackChunkName: "${modules.notFound}" */ '@/modules/${modules.notFound}') ) -const RoutesComponent = () => { +const RoutesComponent: React.FC = () => { return ( }> diff --git a/recipes/_ts/basic/snippets/sources/Sidebar.js b/recipes/_ts/basic/snippets/sources/Sidebar.js index 350dce7..f0629da 100644 --- a/recipes/_ts/basic/snippets/sources/Sidebar.js +++ b/recipes/_ts/basic/snippets/sources/Sidebar.js @@ -1,6 +1,5 @@ function getSourceCode(appName, { sourceDir }) { -return `import React from 'react' -import PropTypes from 'prop-types' +return `import React, { HTMLAttributes } from 'react' import { useI18n } from '@/${sourceDir.i18n}' // Domain Components. @@ -9,11 +8,24 @@ import SidebarNav from './SidebarNav' // Utils. import { RoutePaths } from '@/${sourceDir.utility}' -const Sidebar = (props) => { +interface Page { + title: string + href: string + icon: string +} + +interface SidebarProps extends HTMLAttributes { + open: boolean + variant: string + onClose?: () => void + className?: string +} + +const Sidebar: React.FC = (props) => { const { open, variant, onClose, className, ...rest } = props const { formatMessage } = useI18n() - const pages = [ + const pages: Page[] = [ { title: formatMessage({ id: 'dashboard' }), href: RoutePaths.dashboard, @@ -35,15 +47,9 @@ const Sidebar = (props) => { ) } -Sidebar.propTypes = { - className: PropTypes.string, - onClose: PropTypes.func, - open: PropTypes.bool.isRequired, - variant: PropTypes.string.isRequired, -} - export default Sidebar ` + } module.exports = { diff --git a/recipes/_ts/basic/snippets/sources/SignIn.js b/recipes/_ts/basic/snippets/sources/SignIn.js index da489f1..c003b75 100644 --- a/recipes/_ts/basic/snippets/sources/SignIn.js +++ b/recipes/_ts/basic/snippets/sources/SignIn.js @@ -1,7 +1,6 @@ function getSourceCode(appName, { sourceDir }) { return `import React from 'react' import { useNavigate } from 'react-router-dom' -import PropTypes from 'prop-types' // UI Components. import { InputTextField } from '@/components/ui' @@ -15,13 +14,31 @@ import { RoutePaths } from '@/${sourceDir.utility}' // Service Hooks import usePost from '@/${sourceDir.hooks}/usePost' +interface SignInProps { + title?: string +} + +interface PostData { + id: number + title: string + body: string + userId: number +} + +interface PostResponse { + id: number + title: string + body: string + userId: number +} + const SAMPLE_POST_API_URL = 'https://jsonplaceholder.typicode.com/posts' -const SignIn = (props) => { +const SignIn: React.FC = (props) => { const navigate = useNavigate() const { formatMessage } = useI18n() - const { loading, error, response, sendPostData } = usePost( + const { loading, error, response, sendPostData } = usePost( SAMPLE_POST_API_URL, 'sendPostData' ) @@ -61,12 +78,9 @@ const SignIn = (props) => { ) } -SignIn.propTypes = { - title: PropTypes.string, -} - export default SignIn ` + } module.exports = { diff --git a/recipes/_ts/basic/snippets/sources/TopBar.js b/recipes/_ts/basic/snippets/sources/TopBar.js index 66da682..ad88e81 100644 --- a/recipes/_ts/basic/snippets/sources/TopBar.js +++ b/recipes/_ts/basic/snippets/sources/TopBar.js @@ -1,11 +1,15 @@ function getSourceCode(appName, { sourceDir }) { -return `import React from 'react' +return `import React, { HTMLAttributes } from 'react' import { Link as RouterLink } from 'react-router-dom' -import PropTypes from 'prop-types' import { I18nMsg } from '@/${sourceDir.i18n}' -const TopBar = (props) => { +interface TopBarProps extends HTMLAttributes { + className?: string + onSidebarOpen?: () => void +} + +const TopBar: React.FC = (props) => { const { className, ...rest } = props return ( @@ -17,11 +21,6 @@ const TopBar = (props) => { ) } -TopBar.propTypes = { - className: PropTypes.string, - onSidebarOpen: PropTypes.func, -} - export default TopBar ` } diff --git a/recipes/_ts/basic/snippets/sources/components/blocks/ErrorHandler/ErrorHandler.tsx b/recipes/_ts/basic/snippets/sources/components/blocks/ErrorHandler/ErrorHandler.tsx index 6592b24..2add128 100644 --- a/recipes/_ts/basic/snippets/sources/components/blocks/ErrorHandler/ErrorHandler.tsx +++ b/recipes/_ts/basic/snippets/sources/components/blocks/ErrorHandler/ErrorHandler.tsx @@ -1,12 +1,24 @@ -import React, { Component } from 'react' +import React, { Component, ReactNode, ErrorInfo } from 'react' -export default class ErrorBoundary extends Component { - constructor(props) { +interface ErrorBoundaryProps { + children: ReactNode +} + +interface ErrorBoundaryState { + error: Error | null + errorInfo: ErrorInfo | null +} + +export default class ErrorBoundary extends Component< + ErrorBoundaryProps, + ErrorBoundaryState +> { + constructor(props: ErrorBoundaryProps) { super(props) this.state = { error: null, errorInfo: null } } - componentDidCatch(error, errorInfo) { + componentDidCatch(error: Error, errorInfo: ErrorInfo) { // Catch errors in any components below and re-render with error message this.setState({ error: error, diff --git a/recipes/_ts/basic/snippets/sources/components/blocks/Region/Footer/Footer.tsx b/recipes/_ts/basic/snippets/sources/components/blocks/Region/Footer/Footer.tsx index b71b26e..6ecbd3d 100644 --- a/recipes/_ts/basic/snippets/sources/components/blocks/Region/Footer/Footer.tsx +++ b/recipes/_ts/basic/snippets/sources/components/blocks/Region/Footer/Footer.tsx @@ -1,7 +1,10 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, { HTMLAttributes } from 'react' -const Footer = (props) => { +interface FooterProps extends HTMLAttributes { + className?: string +} + +const Footer: React.FC = (props) => { const { className, ...rest } = props return ( @@ -18,8 +21,4 @@ const Footer = (props) => { ) } -Footer.propTypes = { - className: PropTypes.string, -} - export default Footer diff --git a/recipes/_ts/basic/snippets/sources/components/blocks/Region/Sidebar/SidebarNav/SidebarNav.tsx b/recipes/_ts/basic/snippets/sources/components/blocks/Region/Sidebar/SidebarNav/SidebarNav.tsx index 0289185..5179243 100644 --- a/recipes/_ts/basic/snippets/sources/components/blocks/Region/Sidebar/SidebarNav/SidebarNav.tsx +++ b/recipes/_ts/basic/snippets/sources/components/blocks/Region/Sidebar/SidebarNav/SidebarNav.tsx @@ -1,7 +1,17 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, { HTMLAttributes } from 'react' -const SidebarNav = (props) => { +interface Page { + title: string + href?: string + icon?: string +} + +interface SidebarNavProps extends HTMLAttributes { + className?: string + pages: Page[] +} + +const SidebarNav: React.FC = (props) => { const { pages, className, ...rest } = props return ( @@ -15,9 +25,4 @@ const SidebarNav = (props) => { ) } -SidebarNav.propTypes = { - className: PropTypes.string, - pages: PropTypes.array.isRequired, -} - export default SidebarNav diff --git a/recipes/_ts/basic/snippets/sources/components/ui/Fields/InputTextField/InputTextField.tsx b/recipes/_ts/basic/snippets/sources/components/ui/Fields/InputTextField/InputTextField.tsx index f776e41..0106239 100644 --- a/recipes/_ts/basic/snippets/sources/components/ui/Fields/InputTextField/InputTextField.tsx +++ b/recipes/_ts/basic/snippets/sources/components/ui/Fields/InputTextField/InputTextField.tsx @@ -1,4 +1,13 @@ -import React from 'react' +import React, { ChangeEvent, FocusEvent } from 'react' + +interface InputTextFieldProps { + name: string + initialValue?: string + value?: string + placeholder?: string + handleChange: (event: ChangeEvent) => void + onBlur?: (event: FocusEvent) => void +} export default function InputTextField({ name, @@ -7,8 +16,8 @@ export default function InputTextField({ placeholder, handleChange, onBlur, -}) { - const onChange = (event) => { +}: InputTextFieldProps) { + const onChange = (event: ChangeEvent) => { handleChange(event) } diff --git a/recipes/_ts/basic/snippets/sources/hooks/useFetch.tsx b/recipes/_ts/basic/snippets/sources/hooks/useFetch.tsx index cc757ae..7158f8c 100644 --- a/recipes/_ts/basic/snippets/sources/hooks/useFetch.tsx +++ b/recipes/_ts/basic/snippets/sources/hooks/useFetch.tsx @@ -1,23 +1,34 @@ import { useState, useEffect } from 'react' -import axios from 'axios' +import axios, { AxiosError } from 'axios' -export default function useFetch(url) { - const [response, setResponse] = useState(undefined) - const [error, setError] = useState() +interface UseFetchResult { + response: T | undefined + error: AxiosError | null + loading: boolean +} + +export default function useFetch(url: string): UseFetchResult { + const [response, setResponse] = useState(undefined) + const [error, setError] = useState(null) const [loading, setLoading] = useState(false) - useEffect(async () => { + useEffect(() => { if (!url || url.trim() === '') return - try { - setLoading(true) - const result = await axios.get(url) - setResponse(result.data) - } catch (err) { - setError(err) - } finally { - setLoading(false) + + const fetchData = async () => { + try { + setLoading(true) + const result = await axios.get(url) + setResponse(result.data) + } catch (err) { + setError(err as AxiosError) + } finally { + setLoading(false) + } } + + fetchData() }, [url]) return { response, error, loading } -} \ No newline at end of file +} diff --git a/recipes/_ts/basic/snippets/sources/hooks/useOnlineStatus.tsx b/recipes/_ts/basic/snippets/sources/hooks/useOnlineStatus.tsx index 2bb3668..e8aabe5 100644 --- a/recipes/_ts/basic/snippets/sources/hooks/useOnlineStatus.tsx +++ b/recipes/_ts/basic/snippets/sources/hooks/useOnlineStatus.tsx @@ -1,5 +1,7 @@ -function useOnlineStatus() { - const [online, setOnline] = useState(window.navigator.onLine) +import { useState, useEffect } from 'react' + +function useOnlineStatus(): boolean { + const [online, setOnline] = useState(window.navigator.onLine) useEffect(() => { function handleOnline() { @@ -8,13 +10,16 @@ function useOnlineStatus() { function handleOffline() { setOnline(false) } + window.addEventListener('online', handleOnline) window.addEventListener('offline', handleOffline) + return () => { window.removeEventListener('online', handleOnline) window.removeEventListener('offline', handleOffline) } }, []) + return online } diff --git a/recipes/_ts/basic/snippets/sources/hooks/usePost.tsx b/recipes/_ts/basic/snippets/sources/hooks/usePost.tsx index 53fc9a1..410f2df 100644 --- a/recipes/_ts/basic/snippets/sources/hooks/usePost.tsx +++ b/recipes/_ts/basic/snippets/sources/hooks/usePost.tsx @@ -1,24 +1,33 @@ import { useState } from 'react' -import axios from 'axios' +import axios, { AxiosError } from 'axios' -export default function usePost(url, methodName = 'sendPostData'){ - - const [response, setResponse] = useState(undefined) - const [error, setError] = useState() +interface UsePostResult { + response: T | undefined + error: AxiosError | null + loading: boolean + [key: string]: any +} + +export default function usePost( + url: string, + methodName: string = 'sendPostData' +): UsePostResult { + const [response, setResponse] = useState(undefined) + const [error, setError] = useState(null) const [loading, setLoading] = useState(false) - const postData = async (payload) => { - if (!url || url.trim() === '') return - try { - setLoading(true) - const result = await axios.post(url, payload) - setResponse(result.data) - } catch (err) { - setError(err) - } finally { - setLoading(false) - } + const postData = async (payload: P) => { + if (!url || url.trim() === '') return + try { + setLoading(true) + const result = await axios.post(url, payload) + setResponse(result.data) + } catch (err) { + setError(err as AxiosError) + } finally { + setLoading(false) + } } - - return { response, error, loading, [methodName]:postData } -} \ No newline at end of file + + return { response, error, loading, [methodName]: postData } +} diff --git a/recipes/_ts/basic/snippets/sources/index.js b/recipes/_ts/basic/snippets/sources/index.js index c5bb6af..b4b1c7a 100644 --- a/recipes/_ts/basic/snippets/sources/index.js +++ b/recipes/_ts/basic/snippets/sources/index.js @@ -1,10 +1,15 @@ function getSourceCode(appName) { -return `import React from 'react' + return `import React from 'react' import { createRoot } from 'react-dom/client' import App from './App' const container = document.getElementById('${appName}') + +if (!container) { + throw new Error('Root container not found') +} + const root = createRoot(container) root.render() ` diff --git a/recipes/_ts/basic/snippets/sources/modules/NotFound/NotFound.tsx b/recipes/_ts/basic/snippets/sources/modules/NotFound/NotFound.tsx index c25aae9..bcbebbf 100644 --- a/recipes/_ts/basic/snippets/sources/modules/NotFound/NotFound.tsx +++ b/recipes/_ts/basic/snippets/sources/modules/NotFound/NotFound.tsx @@ -4,8 +4,8 @@ import { useNavigate } from 'react-router-dom' // Utils. import { RoutePaths } from '@/utils' -const NotFound = () => { - const navigate = useNavigate(); +const NotFound: React.FC = () => { + const navigate = useNavigate() return (
    diff --git a/recipes/_ts/basic/snippets/sources/withI18n.js b/recipes/_ts/basic/snippets/sources/withI18n.js index a6810dd..6ef40ba 100644 --- a/recipes/_ts/basic/snippets/sources/withI18n.js +++ b/recipes/_ts/basic/snippets/sources/withI18n.js @@ -1,16 +1,16 @@ function getSourceCode(appName, { sourceDir }) { const langUrl = `${sourceDir.locales}/` + '${lang}.json' -return `import React, { useState, useEffect } from 'react' +return `import React, { useState, useEffect, ComponentType, FC } from 'react' import { IntlProvider } from 'react-intl' import axios from 'axios' -const fetchTranslations = async (lang) => { - return await axios.get(\`${langUrl}\`) +const fetchTranslations = async (lang: string) => { + return await axios.get>(\`${langUrl}\`) } -const withI18n = (Component) => (props) => { - const [messages, setMessages] = useState({}) +const withI18n =

    (Component: ComponentType

    ): FC

    => (props: P) => { + const [messages, setMessages] = useState>({}) const locale = 'en' useEffect(() => { diff --git a/recipes/install.js b/recipes/install.js index 87d6c66..b551991 100644 --- a/recipes/install.js +++ b/recipes/install.js @@ -69,10 +69,8 @@ const install = function (directory, appName = '') { const isSlimProject = (type) => type === defaultProjectType; const isTwixtUIProject = (type) => type === twixtUIProjectType; const isSlimTypeScriptProject = (type) => type === slimTypescriptProjectType; - const isTypeScriptProject = (type) => isSlimTypeScriptProject(type); const isBasicTypeScriptProject = (type) => type === basicTypescriptProjectType; - const is_BasicTypeScriptProject = (type) => isBasicTypeScriptProject(type); - + const isTypeScriptProject = (type) => isSlimTypeScriptProject(type) || isBasicTypeScriptProject(type); tryAccess(baseDirPath) .then(() => undefined, function onPathExist() { @@ -89,7 +87,7 @@ const install = function (directory, appName = '') { type: "list", name: "projectType", message: "choose your project type", - choices: ["Slim", "Slim TypeScript", "Basic", "TwixtUI", "Basic TypeScript"], + choices: ["Slim", "Slim TypeScript", "Basic", "Basic TypeScript", "TwixtUI",], default: "Slim", }, ]); @@ -106,7 +104,7 @@ const install = function (directory, appName = '') { getWebPackConfig = slimTypeScriptSnippet.getWebPackConfig; getDynamicSourceCode = slimTypeScriptSnippet.getDynamicSourceCode; baseConfig = getConfig(); - } else if (is_BasicTypeScriptProject(projectType)) { + } else if (isBasicTypeScriptProject(projectType)) { log(`Basic TypeScript - projectType: ${projectType}`); getConfig = basicTypeScriptConfig.getConfig; getModulesList = basicTypeScriptConfig.getModulesList; @@ -210,9 +208,9 @@ const install = function (directory, appName = '') { }) .then((answers) => { const isTypeScriptProjectType = isTypeScriptProject(projectType); - const is_BasicTypeScriptProjectType = is_BasicTypeScriptProject(projectType); + const is_BasicTypeScriptProjectType = isBasicTypeScriptProject(projectType); const fileExtension = isTypeScriptProjectType ? 'ts' : 'js'; - const componentExtension = (isTypeScriptProjectType || is_BasicTypeScriptProjectType) ? 'tsx' : 'js'; + const componentExtension = isTypeScriptProjectType ? 'tsx' : 'js'; if (baseConfig.canAdd.gitIgnore) { const gitIgnoreFileName = `git-ignore.txt`; createFile('.gitignore', getFileContent(gitIgnoreFileName)); @@ -265,7 +263,7 @@ const install = function (directory, appName = '') { projectTypeName = projectType; } - const sourceSubBase = (isTypeScriptProjectType || is_BasicTypeScriptProjectType) ? '_ts/' : ''; + const sourceSubBase = isTypeScriptProjectType ? '_ts/' : ''; const sourceSnippetDir = `${__dirname}/${sourceSubBase}${projectTypeName}/snippets/sources`; const indexSourceFileName = `index.js`; @@ -277,7 +275,7 @@ const install = function (directory, appName = '') { createFile(appFileNameToCreateWithPath, getDynamicSourceCode(appSourceFileName, appName, baseConfig)); if (baseConfig.canAdd.routes) { - const RoutesFile = "Routes.js"; + const RoutesFile = `Routes.${componentExtension}`; createFile( RoutesFile, getDynamicSourceCode(RoutesFile, appName, baseConfig) @@ -320,7 +318,7 @@ const install = function (directory, appName = '') { shell.cp("-Rf", `${sourceSnippetDir}/i18n`, "."); shell.cd(baseConfig.sourceDir.i18n); - const withI18n = `withI18n.js`; + const withI18n = `withI18n.${componentExtension}`; createFile(withI18n, getDynamicSourceCode(withI18n, appName, baseConfig)); shell.cd(".."); } @@ -332,14 +330,14 @@ const install = function (directory, appName = '') { shell.cd( `${baseConfig.sourceDir.containers}/${baseConfig.modules.signIn}` ); - const signInModule = "SignIn.js"; + const signInModule = `SignIn.${componentExtension}`; createFile( signInModule, getDynamicSourceCode(signInModule, appName, baseConfig) ); shell.cd(`../${baseConfig.modules.dashboard}`); - const dashboardModule = "Dashboard.js"; + const dashboardModule = `Dashboard.${componentExtension}`; createFile( dashboardModule, getDynamicSourceCode(dashboardModule, appName, baseConfig) @@ -357,7 +355,7 @@ const install = function (directory, appName = '') { shell.cd( `${baseConfig.sourceDir.components}/${baseConfig.sourceDir.businessLogic}/Loader/${pageLoader}` ); - const pageLoaderBlock = `${pageLoader}.js`; + const pageLoaderBlock = `${pageLoader}.${componentExtension}`; createFile( pageLoaderBlock, getDynamicSourceCode(pageLoaderBlock, appName, baseConfig) @@ -365,7 +363,7 @@ const install = function (directory, appName = '') { const sidebar = "Sidebar"; shell.cd(`../../Region/${sidebar}`); - const sidebarBlock = `${sidebar}.js`; + const sidebarBlock = `${sidebar}.${componentExtension}`; createFile( sidebarBlock, getDynamicSourceCode(sidebarBlock, appName, baseConfig) @@ -373,7 +371,7 @@ const install = function (directory, appName = '') { const topBar = "TopBar"; shell.cd(`../../Region/${topBar}`); - const topBarBlock = `${topBar}.js`; + const topBarBlock = `${topBar}.${componentExtension}`; createFile( topBarBlock, getDynamicSourceCode(topBarBlock, appName, baseConfig) From 5985b0f16e28b4c582826f9a62e0e93c769d83b2 Mon Sep 17 00:00:00 2001 From: Gopikrishnan200 Date: Sun, 14 Dec 2025 12:50:31 +0530 Subject: [PATCH 3/5] fix(ts/basic): resolve comments on 1 --- recipes/_ts/basic/config.js | 3 +-- recipes/_ts/basic/snippets/index.js | 2 +- recipes/_ts/basic/snippets/sources/App.js | 22 +++++++++++++--------- recipes/install.js | 10 +++------- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/recipes/_ts/basic/config.js b/recipes/_ts/basic/config.js index e4433b1..c80fad9 100644 --- a/recipes/_ts/basic/config.js +++ b/recipes/_ts/basic/config.js @@ -60,8 +60,7 @@ const getDevModulesList = () => { "webpackPlugins", "webpackLoaders", "babel", - "basicTypescriptDev" - + "basicTypescriptDev" ]; }; diff --git a/recipes/_ts/basic/snippets/index.js b/recipes/_ts/basic/snippets/index.js index 9df9872..2a1fad1 100644 --- a/recipes/_ts/basic/snippets/index.js +++ b/recipes/_ts/basic/snippets/index.js @@ -20,7 +20,7 @@ const sourceCodes = { PageLoader: pageLoaderBlock, Sidebar: sidebarBlock, TopBar: topBarBlock, -} + } const getFileContent = (fileName) => { diff --git a/recipes/_ts/basic/snippets/sources/App.js b/recipes/_ts/basic/snippets/sources/App.js index ee9c5e9..74b9ef3 100644 --- a/recipes/_ts/basic/snippets/sources/App.js +++ b/recipes/_ts/basic/snippets/sources/App.js @@ -1,28 +1,32 @@ -function getSourceCode(appName, { sourceDir }) {return `import React from 'react' -import { BrowserRouter as Router } from 'react-router-dom' -import { createBrowserHistory, History } from 'history' +function getSourceCode(appName, { sourceDir }) { + return `import React from 'react' +import { unstable_HistoryRouter as HistoryRouter } from 'react-router-dom' +import { createBrowserHistory } from 'history' +import { WithTranslation } from 'react-i18next' import Routes from './Routes' -// Block Components. +// Block Components import { ErrorHandler, PageLoader } from './components/blocks' import { withTranslation } from '@/${sourceDir.i18n}' -const browserHistory: History = createBrowserHistory() +const browserHistory = createBrowserHistory() -const App: React.FC = () => { +interface AppProps extends WithTranslation {} + +const App: React.FC = (props) => { return ( - + - + ) } -export default withTranslation(App) +export default withTranslation()(App) ` } diff --git a/recipes/install.js b/recipes/install.js index b551991..a5b15bc 100644 --- a/recipes/install.js +++ b/recipes/install.js @@ -208,7 +208,7 @@ const install = function (directory, appName = '') { }) .then((answers) => { const isTypeScriptProjectType = isTypeScriptProject(projectType); - const is_BasicTypeScriptProjectType = isBasicTypeScriptProject(projectType); + const isBasicTypeScriptProjectType = isBasicTypeScriptProject(projectType); const fileExtension = isTypeScriptProjectType ? 'ts' : 'js'; const componentExtension = isTypeScriptProjectType ? 'tsx' : 'js'; if (baseConfig.canAdd.gitIgnore) { @@ -223,11 +223,7 @@ const install = function (directory, appName = '') { const tsConfigFileName = `tsconfig.json`; createFile(tsConfigFileName, getFileContent(tsConfigFileName)); } - if (isTypeScriptProjectType) { - const tsConfigFileName = `tsconfig.json`; - createFile(tsConfigFileName, getFileContent(tsConfigFileName)); - } - + createFile( 'webpack.config.js', getWebPackConfig(appName, { @@ -255,7 +251,7 @@ const install = function (directory, appName = '') { let projectTypeName; - if (is_BasicTypeScriptProjectType) { + if (isBasicTypeScriptProjectType) { projectTypeName = 'basic'; } else if (isSlimTypeScriptProject(projectType)) { projectTypeName = 'slim'; From e6f4ce98f57d751924e62f72ea13f223141ba233 Mon Sep 17 00:00:00 2001 From: Gopikrishnan200 Date: Tue, 16 Dec 2025 16:41:16 +0530 Subject: [PATCH 4/5] fix(ts/basic): resolve comments on dec 14 --- recipes/_ts/basic/snippets/sources/App.js | 7 +++---- recipes/_ts/basic/snippets/sources/i18n/index.ts | 10 +++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/recipes/_ts/basic/snippets/sources/App.js b/recipes/_ts/basic/snippets/sources/App.js index 74b9ef3..b103845 100644 --- a/recipes/_ts/basic/snippets/sources/App.js +++ b/recipes/_ts/basic/snippets/sources/App.js @@ -1,8 +1,7 @@ function getSourceCode(appName, { sourceDir }) { - return `import React from 'react' + return `import React, { FC } from 'react' import { unstable_HistoryRouter as HistoryRouter } from 'react-router-dom' -import { createBrowserHistory } from 'history' -import { WithTranslation } from 'react-i18next' +import { createBrowserHistory, History } from 'history' import Routes from './Routes' @@ -26,7 +25,7 @@ const App: React.FC = (props) => { ) } -export default withTranslation()(App) +export default withTranslation(App) ` } diff --git a/recipes/_ts/basic/snippets/sources/i18n/index.ts b/recipes/_ts/basic/snippets/sources/i18n/index.ts index 21e90ac..7fba212 100644 --- a/recipes/_ts/basic/snippets/sources/i18n/index.ts +++ b/recipes/_ts/basic/snippets/sources/i18n/index.ts @@ -1,6 +1,10 @@ -import { useIntl, FormattedMessage } from 'react-intl' +import { useIntl, FormattedMessage, type IntlShape } from 'react-intl' +// Re-export HOC with proper name export { default as withTranslation } from './withI18n' -export const useI18n = useIntl -export const I18nMsg = FormattedMessage +// Typed hook alias +export const useI18n = (): IntlShape => useIntl() + +// Typed component alias +export const I18nMsg: typeof FormattedMessage = FormattedMessage From c146681ab43948a95884e7a8a0b95364abccce94 Mon Sep 17 00:00:00 2001 From: Gopikrishnan200 Date: Tue, 16 Dec 2025 17:39:17 +0530 Subject: [PATCH 5/5] fix(ts/basic): use latest syntax and tsconfig --- recipes/_ts/basic/snippets/sources/App.js | 5 +++-- recipes/_ts/basic/snippets/sources/Routes.js | 2 +- recipes/_ts/basic/snippets/tsconfig.json | 16 ++++++++-------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/recipes/_ts/basic/snippets/sources/App.js b/recipes/_ts/basic/snippets/sources/App.js index b103845..b0b7726 100644 --- a/recipes/_ts/basic/snippets/sources/App.js +++ b/recipes/_ts/basic/snippets/sources/App.js @@ -6,7 +6,7 @@ import { createBrowserHistory, History } from 'history' import Routes from './Routes' // Block Components -import { ErrorHandler, PageLoader } from './components/blocks' +import { ErrorHandler, PageLoader } from '@/components/blocks' import { withTranslation } from '@/${sourceDir.i18n}' @@ -14,7 +14,7 @@ const browserHistory = createBrowserHistory() interface AppProps extends WithTranslation {} -const App: React.FC = (props) => { +function App(props: AppProps) { return ( @@ -26,6 +26,7 @@ const App: React.FC = (props) => { } export default withTranslation(App) + ` } diff --git a/recipes/_ts/basic/snippets/sources/Routes.js b/recipes/_ts/basic/snippets/sources/Routes.js index 99b97fc..ad2ef2f 100644 --- a/recipes/_ts/basic/snippets/sources/Routes.js +++ b/recipes/_ts/basic/snippets/sources/Routes.js @@ -21,7 +21,7 @@ const NotFoundModule = React.lazy(() => import(/* webpackChunkName: "${modules.notFound}" */ '@/modules/${modules.notFound}') ) -const RoutesComponent: React.FC = () => { +function RoutesComponent() { return ( }> diff --git a/recipes/_ts/basic/snippets/tsconfig.json b/recipes/_ts/basic/snippets/tsconfig.json index 8141f8e..c74eb4b 100644 --- a/recipes/_ts/basic/snippets/tsconfig.json +++ b/recipes/_ts/basic/snippets/tsconfig.json @@ -1,14 +1,14 @@ { "compilerOptions": { - "baseUrl": ".", + "baseUrl": "./src", "paths": { - "@/ui/*": ["src/ui/*"], - "@/blocks/*": ["src/blocks/*"], - "@/constants/*": ["src/constants/*"], - "@/hooks/*": ["src/hooks/*"], - "@/i18n/*": ["src/i18n/*"], - "@/modules/*": ["src/modules/*"], - "@/utils/*": ["src/utils/*"] + "@/ui/*": ["ui/*"], + "@components/*": ["components/*"], + "@/constants/*": ["constants/*"], + "@/hooks/*": ["hooks/*"], + "@/i18n/*": ["i18n/*"], + "@/modules/*": ["modules/*"], + "@/utils/*": ["utils/*"] } }, "include": ["src"]