diff --git a/.changeset/config.json b/.changeset/config.json index beeeaee0..0b3ad9d4 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -7,5 +7,19 @@ "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", - "ignore": [] -} \ No newline at end of file + "ignore": [ + "@examples/with-code-share", + "@examples/with-code-share-shared", + "@examples/with-code-share-server", + "@examples/with-code-share-client", + "@examples/deel-hometask-challenge", + "@examples/next-client-app", + "@examples/server-palmares", + "@examples/server-express-only", + "@examples/next-monorepo", + "@example/basic", + "@examples/bench", + "@example/tests", + "@examples/schemas" + ] +} diff --git a/.changeset/cool-kiwis-count.md b/.changeset/cool-kiwis-count.md new file mode 100644 index 00000000..9e5444df --- /dev/null +++ b/.changeset/cool-kiwis-count.md @@ -0,0 +1,23 @@ +--- +'@palmares/eventemitter2-emitter': patch +'@palmares/server-vercel': patch +'@palmares/sequelize-engine': patch +'@palmares/console-logging': patch +'@palmares/express-adapter': patch +'@palmares/drizzle-engine': patch +'@palmares/vercel-adapter': patch +'@palmares/redis-emitter': patch +'@palmares/databases': patch +'@palmares/logging': patch +'@palmares/schemas': patch +'@palmares/jest-tests': patch +'@palmares/zod-schema': patch +'@palmares/client': patch +'@palmares/events': patch +'@palmares/server': patch +'@palmares/tests': patch +'@palmares/node-std': patch +'@palmares/core': patch +--- + +- Added ESModules support, you can have deeply nested packages and it wont affect the function of your app. For example, if module A has a dependency in @palmares/schema, and palmares schema depends on @palmares/core, it will work normally diff --git a/examples/basic/CHANGELOG.md b/examples/basic/CHANGELOG.md index 4f185ea2..08ef781f 100644 --- a/examples/basic/CHANGELOG.md +++ b/examples/basic/CHANGELOG.md @@ -1,5 +1,18 @@ # @example/basic +## 0.0.19 + +### Patch Changes + +- Everything now has peer dependencies instead of dependency +- Updated dependencies +- Updated dependencies + - @palmares/databases@0.1.16 + - @palmares/server@0.1.13 + - @palmares/core@0.1.13 + - @palmares/express-adapter@0.1.14 + - @palmares/sequelize-engine@0.1.16 + ## 0.0.18 ### Patch Changes diff --git a/examples/basic/package.json b/examples/basic/package.json index c41c38ac..3e0db16a 100644 --- a/examples/basic/package.json +++ b/examples/basic/package.json @@ -1,6 +1,6 @@ { "name": "@example/basic", - "version": "0.0.18", + "version": "0.0.19", "private": true, "description": "This is a basic example application, just to test if everything works as expected", "main": "manage.ts", diff --git a/examples/bench/CHANGELOG.md b/examples/bench/CHANGELOG.md index 48318cf5..376c619e 100644 --- a/examples/bench/CHANGELOG.md +++ b/examples/bench/CHANGELOG.md @@ -1,5 +1,21 @@ # @examples/bench +## 0.0.19 + +### Patch Changes + +- Everything now has peer dependencies instead of dependency +- Updated dependencies +- Updated dependencies + - @palmares/console-logging@0.1.7 + - @palmares/databases@0.1.16 + - @palmares/server@0.1.13 + - @palmares/core@0.1.13 + - @palmares/express-adapter@0.1.14 + - @palmares/node-std@0.1.13 + - @palmares/sequelize-engine@0.1.16 + - @palmares/logging@0.1.13 + ## 0.0.18 ### Patch Changes diff --git a/examples/bench/package.json b/examples/bench/package.json index 905ab2dc..a10d95b3 100644 --- a/examples/bench/package.json +++ b/examples/bench/package.json @@ -1,6 +1,6 @@ { "name": "@examples/bench", - "version": "0.0.18", + "version": "0.0.19", "description": "asdasdasd", "main": "manage.ts", "scripts": { diff --git a/examples/deel-hometask-challenge/CHANGELOG.md b/examples/deel-hometask-challenge/CHANGELOG.md index dabbbab5..5167814d 100644 --- a/examples/deel-hometask-challenge/CHANGELOG.md +++ b/examples/deel-hometask-challenge/CHANGELOG.md @@ -1,5 +1,23 @@ # @examples/deel-hometask-challenge +## 0.0.25 + +### Patch Changes + +- Everything now has peer dependencies instead of dependency +- Updated dependencies +- Updated dependencies + - @palmares/console-logging@0.1.7 + - @palmares/databases@0.1.16 + - @palmares/server@0.1.13 + - @palmares/core@0.1.13 + - @palmares/express-adapter@0.1.14 + - @palmares/node-std@0.1.13 + - @palmares/sequelize-engine@0.1.16 + - @palmares/zod-schema@0.1.22 + - @palmares/logging@0.1.13 + - @palmares/schemas@0.1.20 + ## 0.0.24 ### Patch Changes diff --git a/examples/deel-hometask-challenge/package.json b/examples/deel-hometask-challenge/package.json index aaab76b5..a3956ecf 100644 --- a/examples/deel-hometask-challenge/package.json +++ b/examples/deel-hometask-challenge/package.json @@ -1,6 +1,6 @@ { "name": "@examples/deel-hometask-challenge", - "version": "0.0.24", + "version": "0.0.25", "description": "This is the home task challenge solution for deel", "main": "manage.ts", "scripts": { diff --git a/examples/next-monorepo/CHANGELOG.md b/examples/next-monorepo/CHANGELOG.md index fa9e866f..c45feadb 100644 --- a/examples/next-monorepo/CHANGELOG.md +++ b/examples/next-monorepo/CHANGELOG.md @@ -1,5 +1,11 @@ # @examples/next-monorepo +## 0.0.6 + +### Patch Changes + +- Everything now has peer dependencies instead of dependency + ## 0.0.5 ### Patch Changes diff --git a/examples/next-monorepo/next-client-app/CHANGELOG.md b/examples/next-monorepo/next-client-app/CHANGELOG.md new file mode 100644 index 00000000..9bbfa26e --- /dev/null +++ b/examples/next-monorepo/next-client-app/CHANGELOG.md @@ -0,0 +1,14 @@ +# @examples/next-client-app + +## 0.1.1 + +### Patch Changes + +- Updated dependencies +- Updated dependencies + - @palmares/console-logging@0.1.7 + - @palmares/server@0.1.13 + - @palmares/core@0.1.13 + - @palmares/node-std@0.1.13 + - @palmares/vercel-adapter@0.0.17 + - @palmares/logging@0.1.13 diff --git a/examples/next-monorepo/next-client-app/package.json b/examples/next-monorepo/next-client-app/package.json index a7e3f41f..d861c776 100644 --- a/examples/next-monorepo/next-client-app/package.json +++ b/examples/next-monorepo/next-client-app/package.json @@ -1,6 +1,6 @@ { "name": "@examples/next-client-app", - "version": "0.1.0", + "version": "0.1.1", "private": true, "scripts": { "dev": "next dev", diff --git a/examples/next-monorepo/package.json b/examples/next-monorepo/package.json index 76473d67..64a6d807 100644 --- a/examples/next-monorepo/package.json +++ b/examples/next-monorepo/package.json @@ -1,6 +1,6 @@ { "name": "@examples/next-monorepo", - "version": "0.0.5", + "version": "0.0.6", "description": "testing the serverless vercel on a next app", "keywords": [], "author": "", diff --git a/examples/next-monorepo/server-palmares/CHANGELOG.md b/examples/next-monorepo/server-palmares/CHANGELOG.md new file mode 100644 index 00000000..05e5952a --- /dev/null +++ b/examples/next-monorepo/server-palmares/CHANGELOG.md @@ -0,0 +1,15 @@ +# @examples/server-palmares + +## 0.0.2 + +### Patch Changes + +- Updated dependencies +- Updated dependencies + - @palmares/console-logging@0.1.7 + - @palmares/server@0.1.13 + - @palmares/core@0.1.13 + - @palmares/express-adapter@0.1.14 + - @palmares/node-std@0.1.13 + - @palmares/vercel-adapter@0.0.17 + - @palmares/logging@0.1.13 diff --git a/examples/next-monorepo/server-palmares/package.json b/examples/next-monorepo/server-palmares/package.json index df1efad6..1c76821f 100644 --- a/examples/next-monorepo/server-palmares/package.json +++ b/examples/next-monorepo/server-palmares/package.json @@ -1,6 +1,6 @@ { "name": "@examples/server-palmares", - "version": "0.0.1", + "version": "0.0.2", "description": "testing the serverless vercel", "main": "manage.ts", "scripts": { diff --git a/examples/schemas/CHANGELOG.md b/examples/schemas/CHANGELOG.md index a3abeaea..804b981e 100644 --- a/examples/schemas/CHANGELOG.md +++ b/examples/schemas/CHANGELOG.md @@ -1,5 +1,15 @@ # @examples/schemas +## 0.0.25 + +### Patch Changes + +- Everything now has peer dependencies instead of dependency +- Updated dependencies +- Updated dependencies + - @palmares/zod-schema@0.1.22 + - @palmares/schemas@0.1.20 + ## 0.0.24 ### Patch Changes diff --git a/examples/schemas/package.json b/examples/schemas/package.json index 4bee3d80..1f1471d1 100644 --- a/examples/schemas/package.json +++ b/examples/schemas/package.json @@ -1,6 +1,6 @@ { "name": "@examples/schemas", - "version": "0.0.24", + "version": "0.0.25", "description": "asdasdasd", "main": "./src/index.ts", "scripts": { diff --git a/examples/server-express-only/CHANGELOG.md b/examples/server-express-only/CHANGELOG.md index 038eaf5b..569501f5 100644 --- a/examples/server-express-only/CHANGELOG.md +++ b/examples/server-express-only/CHANGELOG.md @@ -1,5 +1,19 @@ # @examples/server-express-only +## 0.0.17 + +### Patch Changes + +- Everything now has peer dependencies instead of dependency +- Updated dependencies +- Updated dependencies + - @palmares/console-logging@0.1.7 + - @palmares/server@0.1.13 + - @palmares/core@0.1.13 + - @palmares/express-adapter@0.1.14 + - @palmares/node-std@0.1.13 + - @palmares/logging@0.1.13 + ## 0.0.16 ### Patch Changes diff --git a/examples/server-express-only/package.json b/examples/server-express-only/package.json index 3dea4be1..73f879e2 100644 --- a/examples/server-express-only/package.json +++ b/examples/server-express-only/package.json @@ -1,6 +1,6 @@ { "name": "@examples/server-express-only", - "version": "0.0.16", + "version": "0.0.17", "description": "This is an example just to see if the server works as expected", "main": "manage.ts", "scripts": { diff --git a/examples/server-vercel/CHANGELOG.md b/examples/server-vercel/CHANGELOG.md index 305ee794..f3fe6170 100644 --- a/examples/server-vercel/CHANGELOG.md +++ b/examples/server-vercel/CHANGELOG.md @@ -1,5 +1,19 @@ # @palmares/server-vercel +## 0.0.17 + +### Patch Changes + +- Everything now has peer dependencies instead of dependency +- Updated dependencies +- Updated dependencies + - @palmares/console-logging@0.1.7 + - @palmares/server@0.1.13 + - @palmares/core@0.1.13 + - @palmares/node-std@0.1.13 + - @palmares/vercel-adapter@0.0.17 + - @palmares/logging@0.1.13 + ## 0.0.16 ### Patch Changes diff --git a/examples/server-vercel/package.json b/examples/server-vercel/package.json index 734a252b..53e1d348 100644 --- a/examples/server-vercel/package.json +++ b/examples/server-vercel/package.json @@ -1,6 +1,6 @@ { "name": "@palmares/server-vercel", - "version": "0.0.16", + "version": "0.0.17", "description": "testing the serverless vercel", "main": "manage.ts", "scripts": { diff --git a/examples/tests/CHANGELOG.md b/examples/tests/CHANGELOG.md index 2443caa6..c1bbb6d8 100644 --- a/examples/tests/CHANGELOG.md +++ b/examples/tests/CHANGELOG.md @@ -1,5 +1,19 @@ # @example/tests +## 0.0.16 + +### Patch Changes + +- Everything now has peer dependencies instead of dependency +- Updated dependencies +- Updated dependencies + - @palmares/console-logging@0.1.7 + - @palmares/core@0.1.13 + - @palmares/jest-tests@0.1.13 + - @palmares/node-std@0.1.13 + - @palmares/logging@0.1.13 + - @palmares/tests@0.1.13 + ## 0.0.15 ### Patch Changes diff --git a/examples/tests/package.json b/examples/tests/package.json index 9eba1a76..f4867e12 100644 --- a/examples/tests/package.json +++ b/examples/tests/package.json @@ -1,6 +1,6 @@ { "name": "@example/tests", - "version": "0.0.15", + "version": "0.0.16", "description": "testing the tests", "main": "manage.ts", "scripts": { diff --git a/examples/with-code-share/apps/client/.gitignore b/examples/with-code-share/apps/client/.gitignore new file mode 100644 index 00000000..bc6d3062 --- /dev/null +++ b/examples/with-code-share/apps/client/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*storybook.log \ No newline at end of file diff --git a/examples/with-code-share/apps/client/.storybook/main.ts b/examples/with-code-share/apps/client/.storybook/main.ts new file mode 100644 index 00000000..07987742 --- /dev/null +++ b/examples/with-code-share/apps/client/.storybook/main.ts @@ -0,0 +1,27 @@ +import type { StorybookConfig } from "@storybook/react-vite"; + +import { join, dirname } from "path"; + +/** + * This function is used to resolve the absolute path of a package. + * It is needed in projects that use Yarn PnP or are set up within a monorepo. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function getAbsolutePath(value: string): any { + return dirname(require.resolve(join(value, "package.json"))); +} +const config: StorybookConfig = { + stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], + addons: [ + getAbsolutePath("@storybook/addon-onboarding"), + getAbsolutePath("@storybook/addon-links"), + getAbsolutePath("@storybook/addon-essentials"), + getAbsolutePath("@chromatic-com/storybook"), + getAbsolutePath("@storybook/addon-interactions"), + ], + framework: { + name: getAbsolutePath("@storybook/react-vite"), + options: {}, + }, +}; +export default config; diff --git a/examples/with-code-share/apps/client/.storybook/preview.ts b/examples/with-code-share/apps/client/.storybook/preview.ts new file mode 100644 index 00000000..bb486805 --- /dev/null +++ b/examples/with-code-share/apps/client/.storybook/preview.ts @@ -0,0 +1,19 @@ +import '../src/index.css' +import type { Preview } from "@storybook/react"; + +import { setDefaultAdapter, ZodSchemaAdapter } from 'shared' + +setDefaultAdapter(new ZodSchemaAdapter()); + +const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, +}; + +export default preview; diff --git a/examples/with-code-share/apps/client/CHANGELOG.md b/examples/with-code-share/apps/client/CHANGELOG.md new file mode 100644 index 00000000..1a3233dc --- /dev/null +++ b/examples/with-code-share/apps/client/CHANGELOG.md @@ -0,0 +1,10 @@ +# client + +## 0.0.1 + +### Patch Changes + +- Updated dependencies +- Updated dependencies + - @palmares/client@0.1.13 + - shared@1.0.1 diff --git a/examples/with-code-share/apps/client/README.md b/examples/with-code-share/apps/client/README.md new file mode 100644 index 00000000..74872fd4 --- /dev/null +++ b/examples/with-code-share/apps/client/README.md @@ -0,0 +1,50 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default tseslint.config({ + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) +``` + +- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` +- Optionally add `...tseslint.configs.stylisticTypeChecked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: + +```js +// eslint.config.js +import react from 'eslint-plugin-react' + +export default tseslint.config({ + // Set the react version + settings: { react: { version: '18.3' } }, + plugins: { + // Add the react plugin + react, + }, + rules: { + // other rules... + // Enable its recommended rules + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + }, +}) +``` diff --git a/examples/with-code-share/apps/client/eslint.config.js b/examples/with-code-share/apps/client/eslint.config.js new file mode 100644 index 00000000..092408a9 --- /dev/null +++ b/examples/with-code-share/apps/client/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/examples/with-code-share/apps/client/index.html b/examples/with-code-share/apps/client/index.html new file mode 100644 index 00000000..6b57d8a2 --- /dev/null +++ b/examples/with-code-share/apps/client/index.html @@ -0,0 +1,13 @@ + + + + + + + Velozient Computers + + +
+ + + diff --git a/examples/with-code-share/apps/client/package.json b/examples/with-code-share/apps/client/package.json new file mode 100644 index 00000000..90938df8 --- /dev/null +++ b/examples/with-code-share/apps/client/package.json @@ -0,0 +1,58 @@ +{ + "name": "@examples/with-code-share-client", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite --clearScreen false", + "build:app": "vite build", + "lint": "eslint .", + "preview": "vite preview", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" + }, + "packageManager": "pnpm@9.9.0", + "dependencies": { + "@faker-js/faker": "^8.4.1", + "@palmares/client": "workspace:*", + "@tanstack/react-query": "^5.55.0", + "@tanstack/react-virtual": "^3.10.7", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "@examples/with-code-share-shared": "workspace:*" + }, + "devDependencies": { + "@chromatic-com/storybook": "^1.9.0", + "@eslint/js": "^9.10.0", + "@palmares/core": "workspace:*", + "@palmares/server": "workspace:*", + "@storybook/addon-essentials": "^8.2.9", + "@storybook/addon-interactions": "^8.2.9", + "@storybook/addon-links": "^8.2.9", + "@storybook/addon-onboarding": "^8.2.9", + "@storybook/blocks": "^8.2.9", + "@storybook/react": "^8.2.9", + "@storybook/react-vite": "^8.2.9", + "@storybook/test": "^8.2.9", + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.20", + "eslint": "^9.10.0", + "eslint-plugin-react-hooks": "5.1.0-rc-fb9a90fa48-20240614", + "eslint-plugin-react-refresh": "^0.4.11", + "eslint-plugin-storybook": "^0.8.0", + "globals": "^15.9.0", + "postcss": "^8.4.45", + "storybook": "^8.2.9", + "tailwindcss": "^3.4.10", + "typescript": "^5.5.4", + "typescript-eslint": "^8.4.0", + "vite": "^5.4.3" + }, + "eslintConfig": { + "extends": [ + "plugin:storybook/recommended" + ] + } +} diff --git a/examples/with-code-share/apps/client/postcss.config.js b/examples/with-code-share/apps/client/postcss.config.js new file mode 100644 index 00000000..2e7af2b7 --- /dev/null +++ b/examples/with-code-share/apps/client/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/examples/with-code-share/apps/client/public/vite.svg b/examples/with-code-share/apps/client/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/examples/with-code-share/apps/client/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/with-code-share/apps/client/src/App.tsx b/examples/with-code-share/apps/client/src/App.tsx new file mode 100644 index 00000000..6331180c --- /dev/null +++ b/examples/with-code-share/apps/client/src/App.tsx @@ -0,0 +1,15 @@ +import { QueryClientProvider } from "@tanstack/react-query"; +import Main from "./components/main"; +import { queryClient } from "./utils"; + +function App() { + return ( + <> + +
+ + + ) +} + +export default App diff --git a/examples/with-code-share/apps/client/src/assets/react.svg b/examples/with-code-share/apps/client/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/examples/with-code-share/apps/client/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/with-code-share/apps/client/src/components/AssignForm/AssignForm.component.tsx b/examples/with-code-share/apps/client/src/components/AssignForm/AssignForm.component.tsx new file mode 100644 index 00000000..768eb2fd --- /dev/null +++ b/examples/with-code-share/apps/client/src/components/AssignForm/AssignForm.component.tsx @@ -0,0 +1,53 @@ +import useUsers from "../../hooks/users/useUsers"; +import { useEditItem } from "../../hooks"; +import { InventoryInput } from "shared"; +import AssignFormLayout from "./AssignForm.layout"; + +export default function AssignForm(props: { + open: boolean, + onOpen: (isOpen: boolean) => void; + item: InventoryInput, +}) { + const editItem = useEditItem() + const { + data, + error, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + status, + onSearch, + } = useUsers() + + function onSelectUser(user: NonNullable['pages'][number]['data'][number]) { + editItem.mutateAsync({ + ...props.item, + assignmentDate: new Date().toISOString(), + status: 'use', + userId: user.id, + user: { + firstName: user.firstName, + lastName: user.lastName, + email: user.email + } + }).then(() => { + props.onOpen(false) + }) + } + + return ( + + ) +} \ No newline at end of file diff --git a/examples/with-code-share/apps/client/src/components/AssignForm/AssignForm.layout.tsx b/examples/with-code-share/apps/client/src/components/AssignForm/AssignForm.layout.tsx new file mode 100644 index 00000000..52607efe --- /dev/null +++ b/examples/with-code-share/apps/client/src/components/AssignForm/AssignForm.layout.tsx @@ -0,0 +1,96 @@ +import Modal from "../Modal/Modal.component"; +import { useVirtualList } from "../../hooks"; +import { AbstractUser, InventoryInput, ModelFields } from "shared"; +import { InfiniteData } from "@tanstack/react-query"; + +export default function AssignFormLayout['allRows']>(props: { + open: boolean, + onOpen: (isOpen: boolean) => void; + data?: InfiniteData<{ + data: ModelFields[]; + nextCursor: number | null; + }, unknown>; + fetchNextPage: () => void; + item: InventoryInput; + error?: Error | null; + status: string; + onSearch: (search: string) => void; + onSelectUser: (user: TUsers[number]) => void; + hasNextPage: boolean; + isFetchingNextPage: boolean; +}) { + const { + parentRef, + rowVirtualizer, + allRows: allUsers + } = useVirtualList({ + data: props.data, + fetchNextPage: props.fetchNextPage, + hasNextPage: props.hasNextPage, + isFetchingNextPage: props.isFetchingNextPage, + estimatedSize: 40, + }) + + return ( + +
+ props.onSearch(e.target.value)} + /> +
+ {props.status === 'pending' ? ( +

Loading...

+ ) : props.status === 'error' ? ( + Error: {props.error?.message} + ) : ( +
+ {rowVirtualizer.getVirtualItems().map((virtualRow) => { + const isLoaderRow = virtualRow.index > allUsers.length - 1 + const user = allUsers[virtualRow.index]; + return ( +
+ {user === undefined ? ( + isLoaderRow + ? props.hasNextPage + ? 'Loading more...' + : null + : 'No items found' + ) : ( + + )} +
+ ) + })} +
+ )} +
+
+
+ ) +} \ No newline at end of file diff --git a/examples/with-code-share/apps/client/src/components/AssignForm/AssignForm.mock.tsx b/examples/with-code-share/apps/client/src/components/AssignForm/AssignForm.mock.tsx new file mode 100644 index 00000000..557304cc --- /dev/null +++ b/examples/with-code-share/apps/client/src/components/AssignForm/AssignForm.mock.tsx @@ -0,0 +1,58 @@ +import { InventoryInput, mockUsers } from 'shared'; + +import useGetMockedDataLikeReactInfiniteQuery from "../../hooks/usegetMockedDataLikeReactInfiniteQuery"; +import AssignFormLayout from "./AssignForm.layout"; + +export const Wrapper = (props: { + open?: boolean; + onOpen?: (isOpen: boolean) => void; + item: InventoryInput; + users?: ReturnType['rows']; +}) => { + const { + data, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + status + } = useGetMockedDataLikeReactInfiniteQuery((nextCursor) => { + if (props.users) { + if (nextCursor) { + const offset = props.users.findIndex((user) => user.id === nextCursor); + return { + nextCursor: offset > -1 ? props.users[offset + 1]?.id : null, + rows: props.users.slice(offset + 1, offset + 21), + } + } + return { + nextCursor: props.users[20]?.id, + rows: props.users.slice(0, 20), + } + } + + const mockedData = mockUsers(20, { offset: nextCursor as number | undefined }); + return { + nextCursor: mockedData.nextOffset, + rows: mockedData.rows, + } + }, 60) + + return ( + { + if (props.onOpen) props.onOpen(isOpen); + }} + item={props.item} + data={data} + status={status} + onSearch={() => { + console.log('Searching') + }} + onSelectUser={() => {}} + hasNextPage={hasNextPage} + fetchNextPage={fetchNextPage} + isFetchingNextPage={isFetchingNextPage} + /> + ); +}; \ No newline at end of file diff --git a/examples/with-code-share/apps/client/src/components/AssignForm/AssignForm.stories.tsx b/examples/with-code-share/apps/client/src/components/AssignForm/AssignForm.stories.tsx new file mode 100644 index 00000000..e4d2ed2b --- /dev/null +++ b/examples/with-code-share/apps/client/src/components/AssignForm/AssignForm.stories.tsx @@ -0,0 +1,20 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import { mockInventory } from 'shared'; + +import { Wrapper } from "./AssignForm.mock"; + +const meta = { + title: "Components/AssignForm", + component: Wrapper, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Basic: Story = { + args: { + item: mockInventory(1).rows[0], + }, +}; + diff --git a/examples/with-code-share/apps/client/src/components/Item/Item.component.tsx b/examples/with-code-share/apps/client/src/components/Item/Item.component.tsx new file mode 100644 index 00000000..1f90c26c --- /dev/null +++ b/examples/with-code-share/apps/client/src/components/Item/Item.component.tsx @@ -0,0 +1,46 @@ +import { useState } from 'react'; +import { VirtualItem } from '@tanstack/react-virtual'; + +import ItemsForm from '../ItemsForm/ItemsForm.component'; +import AssignForm from '../AssignForm/AssignForm.component'; +import { useDeleteItem } from '../../hooks'; + +import type { ArrayInventoryOutput } from 'shared'; +import ItemLayout from './Item.layout'; + +export default function Item(props: { + isLoaderRow: boolean, + virtualRow: VirtualItem, + hasMore: boolean, + item?: ArrayInventoryOutput[number], +}) { + const removeItem = useDeleteItem(); + const [editOpen, setEditOpen] = useState(undefined); + const [assignOpen, setAssignOpen] = useState(false); + + return ( + setAssignOpen(true)} + onOpenEdit={() => setEditOpen(props.item)} + onRemove={() => { + if (props.item) removeItem.mutateAsync(props.item) + }} + isLoaderRow={props.isLoaderRow} + virtualRow={props.virtualRow} + hasMore={props.hasMore} + item={props.item} + > + setEditOpen(undefined)} + /> + {props.item !== undefined ? ( + + ) : null} + + ) +} \ No newline at end of file diff --git a/examples/with-code-share/apps/client/src/components/Item/Item.layout.tsx b/examples/with-code-share/apps/client/src/components/Item/Item.layout.tsx new file mode 100644 index 00000000..69d5d3fb --- /dev/null +++ b/examples/with-code-share/apps/client/src/components/Item/Item.layout.tsx @@ -0,0 +1,136 @@ +import { Fragment, PropsWithChildren } from 'react'; +import { VirtualItem } from '@tanstack/react-virtual'; + +import type { ArrayInventoryOutput } from 'shared'; + + +export default function ItemLayout(props: PropsWithChildren<{ + isLoaderRow: boolean, + virtualRow: VirtualItem, + hasMore: boolean, + item?: ArrayInventoryOutput[number], + onRemove: () => void; + onOpenEdit: () => void; + onOpenAssign: () => void; +}>) { + const textByStatus = { + available: 'Available', + use: 'In Use', + maintenance: 'In Maintenance', + }; + const warrantyExpiryDate = new Date(props.item?.warrantyExpiryDate as string); + const today = new Date(); + const isExpired = warrantyExpiryDate < today; + const isExpiringSoon = warrantyExpiryDate < new Date(today.setDate(today.getDate() + 30)); + + return ( + +
+ {props.item === undefined ? ( + props.isLoaderRow + ? props.hasMore + ? ( +
+

Loading...

+
+ ) + : null + : 'No items found' + ) : ( +
+
+
+
+ {`The +
+
+

Serial Number: {props.item.serial}

+

Manufacturer: {props.item.manufacturer}

+

+ Status: + + {textByStatus[props.item.status]} + +

+

Specification: {props.item.specifications}

+
+
+

Purchased on: + {Intl.DateTimeFormat('en-US').format(new Date(props.item.purchaseDate as string))} +

+

Warranty until: + + {Intl.DateTimeFormat('en-US').format(new Date(props.item.warrantyExpiryDate as string))} + +

+

Assigned on: + {props.item?.user ? `${props.item?.user?.firstName} ${props.item?.user?.lastName}` : ( + No person assigned + )} +

+

Assigned to: + {props.item?.user ? `${props.item?.user?.firstName} ${props.item?.user?.lastName}` : ( + No person assigned + )} +

+
+
+
+ + + +
+
+
+ )} +
+ {props.children} +
+ ); +} \ No newline at end of file diff --git a/examples/with-code-share/apps/client/src/components/Items/Items.component.tsx b/examples/with-code-share/apps/client/src/components/Items/Items.component.tsx new file mode 100644 index 00000000..d43967db --- /dev/null +++ b/examples/with-code-share/apps/client/src/components/Items/Items.component.tsx @@ -0,0 +1,31 @@ +import Item from "../Item/Item.component"; +import { useItems } from "../../hooks"; +import ItemsLayout from "./Items.layout"; + +export default function Items() { + const { + data, + error, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + status, + } = useItems() + + return ( + ( + + )} + /> + ); +} \ No newline at end of file diff --git a/examples/with-code-share/apps/client/src/components/Items/Items.layout.tsx b/examples/with-code-share/apps/client/src/components/Items/Items.layout.tsx new file mode 100644 index 00000000..74d0b03a --- /dev/null +++ b/examples/with-code-share/apps/client/src/components/Items/Items.layout.tsx @@ -0,0 +1,59 @@ +import Item from "../Item/Item.component"; +import { useVirtualList } from "../../hooks"; +import { InfiniteData } from "@tanstack/react-query"; +import { ArrayInventoryOutput } from "shared"; +import { ComponentProps } from "react"; + +export default function ItemsLayout(props: { + data?: InfiniteData<{ + data: ArrayInventoryOutput, + nextCursor: number | null + }>; + fetchNextPage: () => void; + hasNextPage: boolean; + isFetchingNextPage: boolean; + error?: Error | null; + status: string + onRenderItem: (key: number, props: ComponentProps) => JSX.Element; +}) { + const { + parentRef, + rowVirtualizer, + allRows + } = useVirtualList({ + data: props.data, + fetchNextPage: props.fetchNextPage, + hasNextPage: props.hasNextPage, + isFetchingNextPage: props.isFetchingNextPage, + estimatedSize: document.body.clientWidth > 767 ? 160 : 370 + }) + + return ( +
+ {props.status === 'pending' ? ( +

Loading...

+ ) : props.status === 'error' ? ( + Error: {props.error?.message} + ) : ( +
+ {rowVirtualizer.getVirtualItems().map((virtualRow) => { + const isLoaderRow = virtualRow.index > allRows.length - 1 + const item = allRows[virtualRow.index]; + return props.onRenderItem(virtualRow.index,{ + item, + isLoaderRow, + virtualRow, + hasMore: props.hasNextPage + }) + })} +
+ )} +
+ ); +} \ No newline at end of file diff --git a/examples/with-code-share/apps/client/src/components/Items/Items.stories.tsx b/examples/with-code-share/apps/client/src/components/Items/Items.stories.tsx new file mode 100644 index 00000000..34a32b71 --- /dev/null +++ b/examples/with-code-share/apps/client/src/components/Items/Items.stories.tsx @@ -0,0 +1,113 @@ +import { ComponentProps, useMemo, useState } from "react"; +import type { Meta, StoryObj } from "@storybook/react"; + +import { ArrayInventoryOutput, mockInventory, mockUsers } from 'shared'; + +import ItemsLayout from "./Items.layout"; +import useGetMockedDataLikeReactInfiniteQuery from "../../hooks/usegetMockedDataLikeReactInfiniteQuery"; +import ItemLayout from "../Item/Item.layout"; +import ItemsFormLayout from "../ItemsForm/ItemsForm.layout"; +import { MutationErrors } from "../../utils"; +import { Wrapper as WrapperForAssignForm } from "../AssignForm/AssignForm.mock"; + +const WrapperForItem = (props: { + key: number | string; +} & ComponentProps & { + users: ReturnType['rows']; +}) => { + const [editOpen, setEditOpen] = useState(undefined); + const [assignOpen, setAssignOpen] = useState(false); + + return ( + setAssignOpen(true)} + onOpenEdit={() => { + setEditOpen(props.item) + props.onOpenEdit() + }} + onRemove={() => { + props.onRemove() + }} + hasMore={props.hasMore} + > + setEditOpen(undefined)} + onAddOrUpdateData={async (item) => { + const shouldFail = Math.random() > 0.5; + if (shouldFail) { + throw new MutationErrors([{ + code: "serial", + message: "Serial number already exists", + path: ["serial"], + }]) + } + return Promise.resolve(item); + }} + /> + {props.item && ( + + )} + + ) +} + +const Wrapper = () => { + const users = useMemo(() => mockUsers(60).rows, []); + const { + data, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + status + } = useGetMockedDataLikeReactInfiniteQuery((nextCursor) => { + const mockedData = mockInventory(20, { offset: nextCursor as number | undefined, users }); + return { + nextCursor: mockedData.nextOffset, + rows: mockedData.rows, + } + }, 60) + + return ( +
+ ( + {}} + onOpenEdit={() => {}} + onRemove={() => { + alert('Removing item') + }} + /> + )} + /> +
+ ); +}; + + + +const meta = { + title: "Components/Items", + component: Wrapper, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Basic: Story = { + args: {}, +}; + diff --git a/examples/with-code-share/apps/client/src/components/ItemsForm/ItemsForm.component.tsx b/examples/with-code-share/apps/client/src/components/ItemsForm/ItemsForm.component.tsx new file mode 100644 index 00000000..0937d892 --- /dev/null +++ b/examples/with-code-share/apps/client/src/components/ItemsForm/ItemsForm.component.tsx @@ -0,0 +1,25 @@ +import { InventoryInput } from "shared"; + +import { useAddItem, useEditItem } from "../../hooks"; +import ItemsFormLayout from "./ItemsForm.layout"; + +export default function ItemsForm(props: { + newItem?: boolean; + item?: InventoryInput, + onClose?: () => void, +}) { + const updateItem = useEditItem(); + const addItem = useAddItem(); + + return ( + { + const addItemOrUpdateItem = item.id ? updateItem : addItem; + return addItemOrUpdateItem.mutateAsync(item); + }} + /> + ) +} \ No newline at end of file diff --git a/examples/with-code-share/apps/client/src/components/ItemsForm/ItemsForm.layout.tsx b/examples/with-code-share/apps/client/src/components/ItemsForm/ItemsForm.layout.tsx new file mode 100644 index 00000000..b11251a0 --- /dev/null +++ b/examples/with-code-share/apps/client/src/components/ItemsForm/ItemsForm.layout.tsx @@ -0,0 +1,201 @@ +import { useEffect, useState } from "react"; + +import { InventoryInput, InventoryOutput, getInventorySchemaWithSave } from "shared"; + +import Modal from "../Modal/Modal.component"; +import { formatErrors, MutationErrors, uuid } from "../../utils"; + +export default function ItemsFormLayout(props: { + newItem?: boolean; + item?: InventoryInput, + onClose?: () => void, + onAddOrUpdateData: (item: InventoryInput) => Promise, +}) { + const [isOpen, setIsOpen] = useState(props.item ? true : typeof props.newItem === 'boolean' ? props.newItem : false); + const [item, setItem] = useState(props.item ? props.item : props.newItem ? getNewUser() : undefined); + const [errors, setErrors] = useState>({}); + + function getNewUser() { + return { + uuid: uuid(), + manufacturer: "Apple", + serial: "", + purchaseDate: new Date().toISOString(), + warrantyExpiryDate: new Date().toISOString(), + specifications: "", + imageUrl: "", + status: "available", + userId: null, + } satisfies InventoryInput + } + + function onSubmit() { + if (item) { + getInventorySchemaWithSave(async (item) => { + props.onAddOrUpdateData(item).then(() => { + setErrors({}); + setIsOpen(false); + setItem(undefined); + if (props.onClose) props.onClose(); + }).catch((error) => { + if (error instanceof MutationErrors) setErrors(formatErrors(error.errors)); + }); + return item; + }) + .validate({ ...item }, {}) + .then((validateData) => { + console.log(validateData); + if (validateData.isValid === false) return setErrors(formatErrors(validateData.errors)); + else validateData.save(); + }) + } + } + + useEffect(() => { + if (typeof props.item === 'object') { + if (props.item) setIsOpen(true); + else setIsOpen(false); + setItem(props.item); + } + }, [props.item]); + + useEffect(() => { + if (typeof props.newItem === 'boolean') { + if (props.newItem) setIsOpen(true); + else setIsOpen(false); + setItem(getNewUser()); + } + }, [props.newItem]); + + return ( + { + if (isOpen === false) { + setItem(undefined); + props.onClose?.(); + } + setIsOpen(isOpen); + }}> +
{ + e.preventDefault(); + onSubmit() + }} + > +
+
+ + +
+
+ + { + if (item) setItem({ ...item, serial: e.target.value }); + }} /> + {errors['serial']?.message} +
+
+ + { + if (item) setItem({ ...item, purchaseDate: new Date(e.target.value).toISOString() }); + }} + /> + {errors['purchaseDate']?.message} +
+
+ + { + if (item) setItem({ ...item, warrantyExpiryDate: new Date(e.target.value).toISOString() }); + }} + /> + {errors['warrantyExpiryDate']?.message} +
+
+ +