From d78948ae4fdbb2dd636debe6318fe02ce53b7adf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Emir=20=C5=9Een?= Date: Thu, 25 Jul 2024 17:04:38 +0300 Subject: [PATCH 01/23] docs(tutorial): fix layout preview in material ui tutorial (#6187) --- .../authentication/auth-pages/mui.tsx | 2 +- .../forms/server-side-validation-mui.tsx | 2 +- .../forms/use-select-material-ui.tsx | 2 +- .../general-concepts/auth-pages/mui.tsx | 2 +- .../guides-concepts/general-concepts/layout/mui.tsx | 2 +- .../notifications/notifications-mui.tsx | 2 +- .../guides-concepts/tables/example/material-ui.tsx | 2 +- .../tables/example/search-material-ui.tsx | 2 +- .../material-ui/introduction/previews/auth-page.tsx | 2 +- .../introduction/previews/basic-views.tsx | 2 +- .../material-ui/introduction/previews/example.tsx | 2 +- .../introduction/previews/layout-next-js.tsx | 2 +- .../previews/layout-react-router-dom.tsx | 2 +- .../introduction/previews/layout-remix.tsx | 2 +- .../material-ui/introduction/previews/theming.tsx | 2 +- .../introduction/previews/usage-next-js.tsx | 2 +- .../previews/usage-react-router-dom.tsx | 2 +- .../introduction/previews/usage-remix.tsx | 2 +- .../intro/material-ui/react-router/sandpack.tsx | 13 ++++++------- 19 files changed, 24 insertions(+), 25 deletions(-) diff --git a/documentation/docs/guides-concepts/authentication/auth-pages/mui.tsx b/documentation/docs/guides-concepts/authentication/auth-pages/mui.tsx index 7c9874119a50..392da927d82e 100644 --- a/documentation/docs/guides-concepts/authentication/auth-pages/mui.tsx +++ b/documentation/docs/guides-concepts/authentication/auth-pages/mui.tsx @@ -11,7 +11,7 @@ export function MaterialUIAuth() { "@refinedev/core": "latest", "@refinedev/simple-rest": "latest", "@refinedev/react-router-v6": "latest", - "@refinedev/mui": "latest", + "@refinedev/mui": "5.0.0", "react-router-dom": "latest", "react-router": "latest", "@emotion/react": "^11.8.2", diff --git a/documentation/docs/guides-concepts/forms/server-side-validation-mui.tsx b/documentation/docs/guides-concepts/forms/server-side-validation-mui.tsx index 8579037ea7f8..9205cea5b139 100644 --- a/documentation/docs/guides-concepts/forms/server-side-validation-mui.tsx +++ b/documentation/docs/guides-concepts/forms/server-side-validation-mui.tsx @@ -7,7 +7,7 @@ export default function ServerSideValidationMui() { height={460} showOpenInCodeSandbox={false} dependencies={{ - "@refinedev/mui": "latest", + "@refinedev/mui": "5.0.0", "@refinedev/core": "latest", "@refinedev/simple-rest": "latest", "@refinedev/react-router-v6": "latest", diff --git a/documentation/docs/guides-concepts/forms/use-select-material-ui.tsx b/documentation/docs/guides-concepts/forms/use-select-material-ui.tsx index aa93e733226c..256750f91a25 100644 --- a/documentation/docs/guides-concepts/forms/use-select-material-ui.tsx +++ b/documentation/docs/guides-concepts/forms/use-select-material-ui.tsx @@ -9,7 +9,7 @@ export default function UseSelectMaterialUI() { "@refinedev/core": "latest", "@refinedev/simple-rest": "latest", "@refinedev/react-hook-form": "latest", - "@refinedev/mui": "latest", + "@refinedev/mui": "5.0.0", "@mui/material": "latest", }} startRoute="/" diff --git a/documentation/docs/guides-concepts/general-concepts/auth-pages/mui.tsx b/documentation/docs/guides-concepts/general-concepts/auth-pages/mui.tsx index 7c9874119a50..392da927d82e 100644 --- a/documentation/docs/guides-concepts/general-concepts/auth-pages/mui.tsx +++ b/documentation/docs/guides-concepts/general-concepts/auth-pages/mui.tsx @@ -11,7 +11,7 @@ export function MaterialUIAuth() { "@refinedev/core": "latest", "@refinedev/simple-rest": "latest", "@refinedev/react-router-v6": "latest", - "@refinedev/mui": "latest", + "@refinedev/mui": "5.0.0", "react-router-dom": "latest", "react-router": "latest", "@emotion/react": "^11.8.2", diff --git a/documentation/docs/guides-concepts/general-concepts/layout/mui.tsx b/documentation/docs/guides-concepts/general-concepts/layout/mui.tsx index af369678799f..64de482e3be5 100644 --- a/documentation/docs/guides-concepts/general-concepts/layout/mui.tsx +++ b/documentation/docs/guides-concepts/general-concepts/layout/mui.tsx @@ -12,7 +12,7 @@ export function MaterialUILayout() { "@refinedev/simple-rest": "latest", "@refinedev/react-router-v6": "latest", "@refinedev/inferencer": "latest", - "@refinedev/mui": "latest", + "@refinedev/mui": "5.0.0", "react-router-dom": "latest", "react-router": "latest", "@emotion/react": "^11.8.2", diff --git a/documentation/docs/guides-concepts/notifications/notifications-mui.tsx b/documentation/docs/guides-concepts/notifications/notifications-mui.tsx index 8205c407f419..def764acd9db 100644 --- a/documentation/docs/guides-concepts/notifications/notifications-mui.tsx +++ b/documentation/docs/guides-concepts/notifications/notifications-mui.tsx @@ -14,7 +14,7 @@ export default function NotificationMui() { "@mui/lab": "^5.0.0-alpha.85", "@mui/material": "^5.14.2", "@mui/system": "latest", - "@refinedev/mui": "latest", + "@refinedev/mui": "5.0.0", }} startRoute="/" files={{ diff --git a/documentation/docs/guides-concepts/tables/example/material-ui.tsx b/documentation/docs/guides-concepts/tables/example/material-ui.tsx index 3daeeed0f82c..8c7427fff7a6 100644 --- a/documentation/docs/guides-concepts/tables/example/material-ui.tsx +++ b/documentation/docs/guides-concepts/tables/example/material-ui.tsx @@ -7,7 +7,7 @@ export default function BaseMaterialUI() { dependencies={{ "@refinedev/core": "latest", "@refinedev/simple-rest": "latest", - "@refinedev/mui": "latest", + "@refinedev/mui": "5.0.0", "@mui/x-data-grid": "latest", "@mui/material": "latest", "@mui/system": "latest", diff --git a/documentation/docs/guides-concepts/tables/example/search-material-ui.tsx b/documentation/docs/guides-concepts/tables/example/search-material-ui.tsx index db0da3c20ae3..017f46a064b0 100644 --- a/documentation/docs/guides-concepts/tables/example/search-material-ui.tsx +++ b/documentation/docs/guides-concepts/tables/example/search-material-ui.tsx @@ -7,7 +7,7 @@ export default function BaseCoreTable() { dependencies={{ "@refinedev/core": "latest", "@refinedev/simple-rest": "latest", - "@refinedev/mui": "latest", + "@refinedev/mui": "5.0.0", "@mui/x-data-grid": "latest", "@mui/material": "latest", "@mui/system": "latest", diff --git a/documentation/docs/ui-integrations/material-ui/introduction/previews/auth-page.tsx b/documentation/docs/ui-integrations/material-ui/introduction/previews/auth-page.tsx index 8ac9d1da5e63..ea4fc18f0b25 100644 --- a/documentation/docs/ui-integrations/material-ui/introduction/previews/auth-page.tsx +++ b/documentation/docs/ui-integrations/material-ui/introduction/previews/auth-page.tsx @@ -8,7 +8,7 @@ export default function AuthPage() { // showFiles initialPercentage={40} dependencies={{ - "@refinedev/mui": "latest", + "@refinedev/mui": "5.0.0", "@refinedev/core": "latest", "@refinedev/simple-rest": "latest", "@refinedev/react-router-v6": "latest", diff --git a/documentation/docs/ui-integrations/material-ui/introduction/previews/basic-views.tsx b/documentation/docs/ui-integrations/material-ui/introduction/previews/basic-views.tsx index 46ffec8f3799..dc0e455f9a57 100644 --- a/documentation/docs/ui-integrations/material-ui/introduction/previews/basic-views.tsx +++ b/documentation/docs/ui-integrations/material-ui/introduction/previews/basic-views.tsx @@ -7,7 +7,7 @@ export default function BasicViews() { showNavigator initialPercentage={40} dependencies={{ - "@refinedev/mui": "latest", + "@refinedev/mui": "5.0.0", "@refinedev/core": "latest", "@refinedev/simple-rest": "latest", "@refinedev/react-router-v6": "latest", diff --git a/documentation/docs/ui-integrations/material-ui/introduction/previews/example.tsx b/documentation/docs/ui-integrations/material-ui/introduction/previews/example.tsx index 38908c5e65d4..b5867ccd9d84 100644 --- a/documentation/docs/ui-integrations/material-ui/introduction/previews/example.tsx +++ b/documentation/docs/ui-integrations/material-ui/introduction/previews/example.tsx @@ -7,7 +7,7 @@ export default function Example() { showNavigator previewOnly dependencies={{ - "@refinedev/mui": "latest", + "@refinedev/mui": "5.0.0", "@refinedev/core": "latest", "@refinedev/simple-rest": "latest", "@refinedev/react-router-v6": "latest", diff --git a/documentation/docs/ui-integrations/material-ui/introduction/previews/layout-next-js.tsx b/documentation/docs/ui-integrations/material-ui/introduction/previews/layout-next-js.tsx index bc783a70c55d..39fe06e1c21a 100644 --- a/documentation/docs/ui-integrations/material-ui/introduction/previews/layout-next-js.tsx +++ b/documentation/docs/ui-integrations/material-ui/introduction/previews/layout-next-js.tsx @@ -7,7 +7,7 @@ export default function LayoutNextjs() { showNavigator hidePreview dependencies={{ - "@refinedev/mui": "latest", + "@refinedev/mui": "5.0.0", "@refinedev/core": "latest", "@refinedev/simple-rest": "latest", "@refinedev/react-router-v6": "latest", diff --git a/documentation/docs/ui-integrations/material-ui/introduction/previews/layout-react-router-dom.tsx b/documentation/docs/ui-integrations/material-ui/introduction/previews/layout-react-router-dom.tsx index 6a09a3850216..fe6e848d05fe 100644 --- a/documentation/docs/ui-integrations/material-ui/introduction/previews/layout-react-router-dom.tsx +++ b/documentation/docs/ui-integrations/material-ui/introduction/previews/layout-react-router-dom.tsx @@ -9,7 +9,7 @@ export default function LayoutReactRouterDom() { // showFiles initialPercentage={35} dependencies={{ - "@refinedev/mui": "latest", + "@refinedev/mui": "5.0.0", "@refinedev/core": "latest", "@refinedev/simple-rest": "latest", "@refinedev/react-router-v6": "latest", diff --git a/documentation/docs/ui-integrations/material-ui/introduction/previews/layout-remix.tsx b/documentation/docs/ui-integrations/material-ui/introduction/previews/layout-remix.tsx index a9d1abfc083a..04591fb0f9ec 100644 --- a/documentation/docs/ui-integrations/material-ui/introduction/previews/layout-remix.tsx +++ b/documentation/docs/ui-integrations/material-ui/introduction/previews/layout-remix.tsx @@ -7,7 +7,7 @@ export default function LayoutRemix() { showNavigator hidePreview dependencies={{ - "@refinedev/mui": "latest", + "@refinedev/mui": "5.0.0", "@refinedev/core": "latest", "@refinedev/simple-rest": "latest", "@refinedev/react-router-v6": "latest", diff --git a/documentation/docs/ui-integrations/material-ui/introduction/previews/theming.tsx b/documentation/docs/ui-integrations/material-ui/introduction/previews/theming.tsx index d4b5eca54c2e..f457a7dfad7c 100644 --- a/documentation/docs/ui-integrations/material-ui/introduction/previews/theming.tsx +++ b/documentation/docs/ui-integrations/material-ui/introduction/previews/theming.tsx @@ -9,7 +9,7 @@ export default function Usage() { height={320} showOpenInCodeSandbox={false} dependencies={{ - "@refinedev/mui": "latest", + "@refinedev/mui": "5.0.0", "@refinedev/core": "latest", "@refinedev/simple-rest": "latest", "@refinedev/react-router-v6": "latest", diff --git a/documentation/docs/ui-integrations/material-ui/introduction/previews/usage-next-js.tsx b/documentation/docs/ui-integrations/material-ui/introduction/previews/usage-next-js.tsx index d5ea114345d6..6ca275593c3d 100644 --- a/documentation/docs/ui-integrations/material-ui/introduction/previews/usage-next-js.tsx +++ b/documentation/docs/ui-integrations/material-ui/introduction/previews/usage-next-js.tsx @@ -8,7 +8,7 @@ export default function UsageNextjs() { hidePreview showFiles dependencies={{ - "@refinedev/mui": "latest", + "@refinedev/mui": "5.0.0", "@refinedev/core": "latest", "@refinedev/simple-rest": "latest", "@refinedev/react-hook-form": "^4.8.12", diff --git a/documentation/docs/ui-integrations/material-ui/introduction/previews/usage-react-router-dom.tsx b/documentation/docs/ui-integrations/material-ui/introduction/previews/usage-react-router-dom.tsx index 71bcc17123ce..3aaafdf9a5c9 100644 --- a/documentation/docs/ui-integrations/material-ui/introduction/previews/usage-react-router-dom.tsx +++ b/documentation/docs/ui-integrations/material-ui/introduction/previews/usage-react-router-dom.tsx @@ -8,7 +8,7 @@ export default function UsageReactRouterDom() { hidePreview showFiles dependencies={{ - "@refinedev/mui": "latest", + "@refinedev/mui": "5.0.0", "@refinedev/core": "latest", "@refinedev/simple-rest": "latest", "@refinedev/react-hook-form": "^4.8.12", diff --git a/documentation/docs/ui-integrations/material-ui/introduction/previews/usage-remix.tsx b/documentation/docs/ui-integrations/material-ui/introduction/previews/usage-remix.tsx index 6dc9fcb4885a..81ef67857b6a 100644 --- a/documentation/docs/ui-integrations/material-ui/introduction/previews/usage-remix.tsx +++ b/documentation/docs/ui-integrations/material-ui/introduction/previews/usage-remix.tsx @@ -8,7 +8,7 @@ export default function UsageRemix() { hidePreview showFiles dependencies={{ - "@refinedev/mui": "latest", + "@refinedev/mui": "5.0.0", "@refinedev/core": "latest", "@refinedev/simple-rest": "latest", "@refinedev/react-hook-form": "^4.8.12", diff --git a/documentation/tutorial/ui-libraries/intro/material-ui/react-router/sandpack.tsx b/documentation/tutorial/ui-libraries/intro/material-ui/react-router/sandpack.tsx index 13b7b4fc1c8f..31fa7581332e 100644 --- a/documentation/tutorial/ui-libraries/intro/material-ui/react-router/sandpack.tsx +++ b/documentation/tutorial/ui-libraries/intro/material-ui/react-router/sandpack.tsx @@ -234,13 +234,12 @@ export const finalFiles = { export const dependencies = { ...initialDependencies, - "@emotion/react": "latest", - "@emotion/styled": "latest", - "@mui/lab": "latest", - "@mui/material": "latest", - "@mui/x-data-grid": "latest", - "@mui/system": "latest", - "@refinedev/mui": "latest", + "@emotion/react": "11.11.4", + "@emotion/styled": "11.11.5", + "@mui/lab": "5.0.0-alpha.173", + "@mui/material": "5.16.5", + "@mui/x-data-grid": "6.19.11", + "@refinedev/mui": "5.0.0", "@refinedev/react-hook-form": "latest", "react-hook-form": "latest", }; From ea8d7ee8d3578062031dff6eecbf0159e4d1f658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Emir=20=C5=9Een?= Date: Thu, 25 Jul 2024 17:50:39 +0300 Subject: [PATCH 02/23] docs(blog): update blog post detail image aspect ratio --- documentation/src/components/blog/blog-post-page/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/components/blog/blog-post-page/index.js b/documentation/src/components/blog/blog-post-page/index.js index 2d4c373250a8..742af01fc730 100644 --- a/documentation/src/components/blog/blog-post-page/index.js +++ b/documentation/src/components/blog/blog-post-page/index.js @@ -107,7 +107,7 @@ export const BlogPostPageView = ({ children }) => {
Date: Fri, 26 Jul 2024 09:15:47 +0300 Subject: [PATCH 03/23] docs(ant-design): explain the differences between `onFinish` and `formProps.onFinish` (#6188) --- .../ant-design/hooks/use-drawer-form/index.md | 10 ++++++++++ .../ant-design/hooks/use-modal-form/index.md | 16 ++++++++++++++-- .../src/refine-theme/common-admonition.tsx | 1 + documentation/src/refine-theme/css/custom.css | 4 ++++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/documentation/docs/ui-integrations/ant-design/hooks/use-drawer-form/index.md b/documentation/docs/ui-integrations/ant-design/hooks/use-drawer-form/index.md index 88fd5f5f696f..4d230e48b80c 100644 --- a/documentation/docs/ui-integrations/ant-design/hooks/use-drawer-form/index.md +++ b/documentation/docs/ui-integrations/ant-design/hooks/use-drawer-form/index.md @@ -442,6 +442,8 @@ const { defaultFormValuesLoading } = useForm({ ## Return values +`useDrawerForm` returns the same values from [`useForm`](/docs/ui-integrations/ant-design/hooks/use-form#return-values) and additional values to work with [``](https://ant.design/components/drawer/) components. + ### show A function that opens the ``. It takes an optional `id` parameter. If `id` is provided, it will fetch the record data and fill the `
` with it. @@ -464,6 +466,14 @@ It's required to manage `` state and actions. Under the hood the `formProp It contains the props to manage the [Antd ``](https://ant.design/components/form#api) component such as [_`onValuesChange`, `initialValues`, `onFieldsChange`, `onFinish` etc._](/docs/ui-integrations/ant-design/hooks/use-form#return-values) +:::note Difference between `onFinish` and `formProps.onFinish` + +`onFinish` method returned directly from `useDrawerForm` is same with the `useForm`'s `onFinish`. When working with drawers, closing the drawer after submission and resetting the fields are necessary and to handle these, `formProps.onFinish` extends the `onFinish` method and handles the closing of the drawer and clearing the fields under the hood. + +If you're customizing the data before submitting it to your data provider, it's recommended to use `formProps.onFinish` and let it handle the operations after the submission. + +::: + ### drawerProps It's required to manage [``](https://ant.design/components/drawer/#API) state and actions. diff --git a/documentation/docs/ui-integrations/ant-design/hooks/use-modal-form/index.md b/documentation/docs/ui-integrations/ant-design/hooks/use-modal-form/index.md index 59883a1d6208..78b7a6c4d0d1 100644 --- a/documentation/docs/ui-integrations/ant-design/hooks/use-modal-form/index.md +++ b/documentation/docs/ui-integrations/ant-design/hooks/use-modal-form/index.md @@ -601,12 +601,22 @@ useModalForm({ ## Return Values +`useModalForm` returns the same values from [`useForm`](/docs/ui-integrations/ant-design/hooks/use-form#return-values) and additional values to work with [``][antd-modal] components. + ### formProps It's required to manage `` state and actions. Under the hood the `formProps` came from [`useForm`][antd-use-form]. It contains the props to manage the [Antd ``](https://ant.design/components/form#api) components such as [`onValuesChange`, `initialValues`, `onFieldsChange`, `onFinish` etc.](/docs/ui-integrations/ant-design/hooks/use-form#return-values) +:::note Difference between `onFinish` and `formProps.onFinish` + +`onFinish` method returned directly from `useModalForm` is same with the `useForm`'s `onFinish`. When working with modals, closing the modal after submission and resetting the fields are necessary and to handle these, `formProps.onFinish` extends the `onFinish` method and handles the closing of the modal and clearing the fields under the hood. + +If you're customizing the data before submitting it to your data provider, it's recommended to use `formProps.onFinish` and let it handle the operations after the submission. + +::: + ### modalProps The props needed by the [``][antd-modal] component. @@ -660,8 +670,10 @@ A function that can close the ``. It's useful when you want to close the ```tsx const { close, modalProps, formProps, onFinish } = useModalForm(); -const onFinishHandler = (values) => { - onFinish(values); +const onFinishHandler = async (values) => { + // Awaiting `onFinish` is important for features like unsaved changes notifier, invalidation, redirection etc. + // If you're using the `onFinish` from `formProps`, it will call the `close` internally. + await onFinish(values); close(); }; diff --git a/documentation/src/refine-theme/common-admonition.tsx b/documentation/src/refine-theme/common-admonition.tsx index 9848ba260aa1..bd9849b114e8 100644 --- a/documentation/src/refine-theme/common-admonition.tsx +++ b/documentation/src/refine-theme/common-admonition.tsx @@ -122,6 +122,7 @@ export const Admonition = ({ type, title, children }: Props) => { "gap-2", "text-xs sm:text-base 2xl:text-base 2xl:leading-7", "font-semibold", + "admonition-header", clsText, )} > diff --git a/documentation/src/refine-theme/css/custom.css b/documentation/src/refine-theme/css/custom.css index f5e5c607356c..f3462b473e56 100644 --- a/documentation/src/refine-theme/css/custom.css +++ b/documentation/src/refine-theme/css/custom.css @@ -478,6 +478,10 @@ button.sp-button.sp-icon-standalone[title="Open in CodeSandbox"]:has(svg + span) @apply text-gray-700 dark:text-gray-100; } +.admonition .admonition-header code { + text-transform: none; +} + h4 > del:has(code:only-child) { @apply no-underline; } From d44de34f2399992ca2ee7fd4cc4003f1467d3199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Emir=20=C5=9Een?= Date: Fri, 26 Jul 2024 09:25:03 +0300 Subject: [PATCH 04/23] chore(appwrite): update appwrite examples with cloud (#6168) --- .../src/App.tsx | 56 ++++++-- .../src/interfaces/index.d.ts | 15 ++ .../src/pages/categories/create.tsx | 31 +++++ .../src/pages/categories/edit.tsx | 31 +++++ .../src/pages/categories/index.tsx | 4 + .../src/pages/categories/list.tsx | 22 +++ .../src/pages/categories/show.tsx | 26 ++++ .../src/pages/posts/create.tsx | 89 +++++++++++- .../src/pages/posts/edit.tsx | 108 +++++++++++++-- .../src/pages/posts/list.tsx | 20 ++- .../src/pages/posts/show.tsx | 29 +++- .../src/utility/appwriteClient.ts | 8 +- .../{authProvider.ts => auth-provider.ts} | 22 +-- .../src/utility/index.ts | 3 +- .../src/utility/status.ts | 14 ++ examples/data-provider-appwrite/src/App.tsx | 128 ++++-------------- .../src/interfaces/index.d.ts | 10 +- .../src/pages/categories/create.tsx | 31 +++++ .../src/pages/categories/edit.tsx | 31 +++++ .../src/pages/categories/index.tsx | 4 + .../src/pages/categories/list.tsx | 22 +++ .../src/pages/categories/show.tsx | 26 ++++ .../src/pages/posts/create.tsx | 25 ++-- .../src/pages/posts/edit.tsx | 33 +++-- .../src/pages/posts/list.tsx | 48 +++---- .../src/pages/posts/show.tsx | 23 +--- .../src/utility/appwriteClient.ts | 4 +- .../src/utility/auth-provider.ts | 87 ++++++++++++ .../src/utility/index.ts | 2 + .../src/utility/status.ts | 14 ++ 30 files changed, 726 insertions(+), 240 deletions(-) create mode 100644 examples/data-provider-appwrite-tutorial-docs/src/pages/categories/create.tsx create mode 100644 examples/data-provider-appwrite-tutorial-docs/src/pages/categories/edit.tsx create mode 100644 examples/data-provider-appwrite-tutorial-docs/src/pages/categories/index.tsx create mode 100644 examples/data-provider-appwrite-tutorial-docs/src/pages/categories/list.tsx create mode 100644 examples/data-provider-appwrite-tutorial-docs/src/pages/categories/show.tsx rename examples/data-provider-appwrite-tutorial-docs/src/utility/{authProvider.ts => auth-provider.ts} (77%) create mode 100644 examples/data-provider-appwrite-tutorial-docs/src/utility/status.ts create mode 100644 examples/data-provider-appwrite/src/pages/categories/create.tsx create mode 100644 examples/data-provider-appwrite/src/pages/categories/edit.tsx create mode 100644 examples/data-provider-appwrite/src/pages/categories/index.tsx create mode 100644 examples/data-provider-appwrite/src/pages/categories/list.tsx create mode 100644 examples/data-provider-appwrite/src/pages/categories/show.tsx create mode 100644 examples/data-provider-appwrite/src/utility/auth-provider.ts create mode 100644 examples/data-provider-appwrite/src/utility/status.ts diff --git a/examples/data-provider-appwrite-tutorial-docs/src/App.tsx b/examples/data-provider-appwrite-tutorial-docs/src/App.tsx index 1a084aa7c1b7..3bba39d82e96 100644 --- a/examples/data-provider-appwrite-tutorial-docs/src/App.tsx +++ b/examples/data-provider-appwrite-tutorial-docs/src/App.tsx @@ -13,12 +13,20 @@ import routerProvider, { NavigateToResource, UnsavedChangesNotifier, } from "@refinedev/react-router-v6"; +import { BrowserRouter, Outlet, Route, Routes } from "react-router-dom"; + import "@refinedev/antd/dist/reset.css"; import { App as AntdApp, ConfigProvider } from "antd"; -import { BrowserRouter, Outlet, Route, Routes } from "react-router-dom"; import { appwriteClient, authProvider } from "./utility"; + import { PostCreate, PostEdit, PostList, PostShow } from "./pages/posts"; +import { + CategoryCreate, + CategoryList, + CategoryShow, + CategoryEdit, +} from "./pages/categories"; const App: React.FC = () => { return ( @@ -28,22 +36,32 @@ const App: React.FC = () => { { > - } + element={} /> @@ -80,22 +96,34 @@ const App: React.FC = () => { } /> } /> + + } /> + } /> + } /> + } /> + }> - + } > } - /> - } + element={ + + } /> diff --git a/examples/data-provider-appwrite-tutorial-docs/src/interfaces/index.d.ts b/examples/data-provider-appwrite-tutorial-docs/src/interfaces/index.d.ts index f28a07097431..8d20355b71d0 100644 --- a/examples/data-provider-appwrite-tutorial-docs/src/interfaces/index.d.ts +++ b/examples/data-provider-appwrite-tutorial-docs/src/interfaces/index.d.ts @@ -1,3 +1,10 @@ +export interface ICategory { + id: string; + title: string; +} + +export const IStatus = "draft" | "published" | "rejected"; + export interface IFile { name: string; percent: number; @@ -12,10 +19,18 @@ export interface IPost { id: string; title: string; content: string; + status: IStatus; + category: { + $id: string; + title: string; + }; + images: string; } export interface IPostVariables { id: string; title: string; content: string; + category: string; + images: string; } diff --git a/examples/data-provider-appwrite-tutorial-docs/src/pages/categories/create.tsx b/examples/data-provider-appwrite-tutorial-docs/src/pages/categories/create.tsx new file mode 100644 index 000000000000..eb1e0d43dbea --- /dev/null +++ b/examples/data-provider-appwrite-tutorial-docs/src/pages/categories/create.tsx @@ -0,0 +1,31 @@ +import { Create, useForm } from "@refinedev/antd"; +import { Form, Input } from "antd"; + +import type { HttpError } from "@refinedev/core"; +import type { ICategory } from "../../interfaces"; + +export const CategoryCreate = () => { + const { formProps, saveButtonProps } = useForm< + ICategory, + HttpError, + ICategory + >(); + + return ( + + + + + + + + ); +}; diff --git a/examples/data-provider-appwrite-tutorial-docs/src/pages/categories/edit.tsx b/examples/data-provider-appwrite-tutorial-docs/src/pages/categories/edit.tsx new file mode 100644 index 000000000000..b3ea84614aba --- /dev/null +++ b/examples/data-provider-appwrite-tutorial-docs/src/pages/categories/edit.tsx @@ -0,0 +1,31 @@ +import { Edit, useForm } from "@refinedev/antd"; +import { Form, Input } from "antd"; + +import type { HttpError } from "@refinedev/core"; +import type { ICategory } from "../../interfaces"; + +export const CategoryEdit = () => { + const { formProps, saveButtonProps } = useForm< + ICategory, + HttpError, + ICategory + >({}); + + return ( + +
+ + + +
+
+ ); +}; diff --git a/examples/data-provider-appwrite-tutorial-docs/src/pages/categories/index.tsx b/examples/data-provider-appwrite-tutorial-docs/src/pages/categories/index.tsx new file mode 100644 index 000000000000..817d7f6b8a12 --- /dev/null +++ b/examples/data-provider-appwrite-tutorial-docs/src/pages/categories/index.tsx @@ -0,0 +1,4 @@ +export * from "./list"; +export * from "./show"; +export * from "./create"; +export * from "./edit"; diff --git a/examples/data-provider-appwrite-tutorial-docs/src/pages/categories/list.tsx b/examples/data-provider-appwrite-tutorial-docs/src/pages/categories/list.tsx new file mode 100644 index 000000000000..ef608988e2d3 --- /dev/null +++ b/examples/data-provider-appwrite-tutorial-docs/src/pages/categories/list.tsx @@ -0,0 +1,22 @@ +import { List, useTable, getDefaultSortOrder } from "@refinedev/antd"; +import { Table } from "antd"; + +import type { ICategory } from "../../interfaces"; + +export const CategoryList = () => { + const { tableProps, sorters } = useTable(); + + return ( + + + + dataIndex="id" + title="ID" + sorter + defaultSortOrder={getDefaultSortOrder("id", sorters)} + /> + dataIndex="title" title="Title" sorter /> +
+
+ ); +}; diff --git a/examples/data-provider-appwrite-tutorial-docs/src/pages/categories/show.tsx b/examples/data-provider-appwrite-tutorial-docs/src/pages/categories/show.tsx new file mode 100644 index 000000000000..1746ab915c6c --- /dev/null +++ b/examples/data-provider-appwrite-tutorial-docs/src/pages/categories/show.tsx @@ -0,0 +1,26 @@ +import { useShow } from "@refinedev/core"; + +import { Show } from "@refinedev/antd"; + +import { Typography } from "antd"; + +import type { ICategory } from "../../interfaces"; + +const { Title, Text } = Typography; + +export const CategoryShow = () => { + const { queryResult } = useShow(); + + const { data, isLoading } = queryResult; + const record = data?.data; + + return ( + + Id + {record?.id} + + Title + {record?.title} + + ); +}; diff --git a/examples/data-provider-appwrite-tutorial-docs/src/pages/posts/create.tsx b/examples/data-provider-appwrite-tutorial-docs/src/pages/posts/create.tsx index 77b08501ce02..a60c725ed4cf 100644 --- a/examples/data-provider-appwrite-tutorial-docs/src/pages/posts/create.tsx +++ b/examples/data-provider-appwrite-tutorial-docs/src/pages/posts/create.tsx @@ -1,8 +1,12 @@ -import type { HttpError } from "@refinedev/core"; -import { Create, useForm } from "@refinedev/antd"; -import { Form, Input } from "antd"; +import { Create, useForm, useSelect } from "@refinedev/antd"; +import { Form, Input, Select, Upload } from "antd"; +import MDEditor from "@uiw/react-md-editor"; + +import { normalizeFile, statuses, storage } from "../../utility"; -import type { IPost, IPostVariables } from "../../interfaces"; +import type { HttpError } from "@refinedev/core"; +import type { RcFile } from "antd/lib/upload/interface"; +import type { IPost, IPostVariables, ICategory } from "../../interfaces"; export const PostCreate = () => { const { formProps, saveButtonProps } = useForm< @@ -11,9 +15,24 @@ export const PostCreate = () => { IPostVariables >(); + const { selectProps: categorySelectProps } = useSelect({ + resource: "categories", + optionLabel: "title", + optionValue: "id", + }); + return ( -
+ { + formProps.onFinish?.({ + ...values, + images: JSON.stringify(values.images), + }); + }} + > { > - + + + { }, ]} > - + + + + + { + try { + const rcFile = file as RcFile; + + const { $id } = await storage.createFile( + "default", + rcFile.name, + rcFile, + ); + + const url = storage.getFileView("default", $id); + + onSuccess?.({ url }, new XMLHttpRequest()); + } catch (error) { + onError?.(new Error("Upload Error")); + } + }} + > +

+ Drag & drop a file in this area +

+
+
diff --git a/examples/data-provider-appwrite-tutorial-docs/src/pages/posts/edit.tsx b/examples/data-provider-appwrite-tutorial-docs/src/pages/posts/edit.tsx index 0eec98580851..fa67d72b65dc 100644 --- a/examples/data-provider-appwrite-tutorial-docs/src/pages/posts/edit.tsx +++ b/examples/data-provider-appwrite-tutorial-docs/src/pages/posts/edit.tsx @@ -1,19 +1,54 @@ -import React from "react"; +import { Edit, useForm, useSelect } from "@refinedev/antd"; +import { Form, Input, Select, Upload } from "antd"; +import MDEditor from "@uiw/react-md-editor"; + +import { normalizeFile, storage, statuses } from "../../utility"; + import type { HttpError } from "@refinedev/core"; -import { Edit, useForm } from "@refinedev/antd"; -import { Form, Input } from "antd"; -import type { IPost, IPostVariables } from "../../interfaces"; +import type { RcFile } from "antd/lib/upload/interface"; +import type { IPost, IPostVariables, ICategory } from "../../interfaces"; export const PostEdit = () => { - const { formProps, saveButtonProps } = useForm< + const { formProps, saveButtonProps, queryResult } = useForm< IPost, HttpError, + IPostVariables, IPostVariables - >(); + >({ + queryOptions: { + select: ({ data }) => { + return { + data: { + ...data, + category: data.category.$id, + images: data.images ? JSON.parse(data.images) : undefined, + }, + }; + }, + }, + }); + + const postData = queryResult?.data?.data; + + const { selectProps: categorySelectProps } = useSelect({ + resource: "categories", + defaultValue: postData?.category, + optionLabel: "title", + optionValue: "id", + }); return ( -
+ { + formProps.onFinish?.({ + ...values, + images: JSON.stringify(values.images), + }); + }} + > { > + + + { }, ]} > - + + + + + { + try { + const rcFile = file as RcFile; + + const { $id } = await storage.createFile( + "default", + rcFile.uid, + rcFile, + ); + + const url = storage.getFileView("default", $id); + + onSuccess?.({ url }, new XMLHttpRequest()); + } catch (error) { + onError?.(new Error("Upload Error")); + } + }} + > +

+ Drag & drop a file in this area +

+
+
diff --git a/examples/data-provider-appwrite-tutorial-docs/src/pages/posts/list.tsx b/examples/data-provider-appwrite-tutorial-docs/src/pages/posts/list.tsx index 1c3eb1c2bd18..374bf100b24f 100644 --- a/examples/data-provider-appwrite-tutorial-docs/src/pages/posts/list.tsx +++ b/examples/data-provider-appwrite-tutorial-docs/src/pages/posts/list.tsx @@ -4,7 +4,7 @@ import { EditButton, ShowButton, getDefaultSortOrder, - DeleteButton, + TagField, } from "@refinedev/antd"; import { Table, Space } from "antd"; @@ -14,7 +14,7 @@ export const PostList = () => { const { tableProps, sorters } = useTable({ initialSorter: [ { - field: "$id", + field: "$createdAt", order: "asc", }, ], @@ -30,17 +30,25 @@ export const PostList = () => { width={100} defaultSortOrder={getDefaultSortOrder("id", sorters)} /> - - + + + } + /> title="Actions" dataIndex="actions" - fixed="right" render={(_, record) => ( - )} /> diff --git a/examples/data-provider-appwrite-tutorial-docs/src/pages/posts/show.tsx b/examples/data-provider-appwrite-tutorial-docs/src/pages/posts/show.tsx index 0974fc3a1ee7..24761a4861d3 100644 --- a/examples/data-provider-appwrite-tutorial-docs/src/pages/posts/show.tsx +++ b/examples/data-provider-appwrite-tutorial-docs/src/pages/posts/show.tsx @@ -1,8 +1,8 @@ import { useShow } from "@refinedev/core"; +import { Show, MarkdownField, ImageField } from "@refinedev/antd"; +import { Typography, Space } from "antd"; -import { Show, MarkdownField } from "@refinedev/antd"; -import { Typography } from "antd"; -import type { IPost } from "../../interfaces"; +import type { IPost, IFile } from "../../interfaces"; const { Title, Text } = Typography; @@ -11,16 +11,35 @@ export const PostShow = () => { const { data, isLoading } = queryResult; const record = data?.data; + const images = record?.images ? (JSON.parse(record.images) as IFile[]) : []; + return ( Id {record?.id} - Title {record?.title} - + Category + {record?.category.title} + Status + {record?.status} Content + Images + + {record?.images ? ( + images.map((img) => ( + + )) + ) : ( + Not found any images + )} + ); }; diff --git a/examples/data-provider-appwrite-tutorial-docs/src/utility/appwriteClient.ts b/examples/data-provider-appwrite-tutorial-docs/src/utility/appwriteClient.ts index 8512592b4aa7..3dd3777e78af 100644 --- a/examples/data-provider-appwrite-tutorial-docs/src/utility/appwriteClient.ts +++ b/examples/data-provider-appwrite-tutorial-docs/src/utility/appwriteClient.ts @@ -1,7 +1,7 @@ import { Account, Appwrite, Storage } from "@refinedev/appwrite"; const APPWRITE_URL = "https://cloud.appwrite.io/v1"; -const APPWRITE_PROJECT = "6537bcde069d747b2abf"; +const APPWRITE_PROJECT = "6697687d002cbd31ba6b"; const appwriteClient = new Appwrite(); @@ -10,9 +10,3 @@ const account = new Account(appwriteClient); const storage = new Storage(appwriteClient); export { appwriteClient, account, storage }; - -/* const client = new Client(); - -client - .setEndpoint('https://cloud.appwrite.io/v1') - .setProject('6537bcde069d747b2abf'); */ diff --git a/examples/data-provider-appwrite-tutorial-docs/src/utility/authProvider.ts b/examples/data-provider-appwrite-tutorial-docs/src/utility/auth-provider.ts similarity index 77% rename from examples/data-provider-appwrite-tutorial-docs/src/utility/authProvider.ts rename to examples/data-provider-appwrite-tutorial-docs/src/utility/auth-provider.ts index d6ee8a8969f7..8cbe33bceae6 100644 --- a/examples/data-provider-appwrite-tutorial-docs/src/utility/authProvider.ts +++ b/examples/data-provider-appwrite-tutorial-docs/src/utility/auth-provider.ts @@ -1,7 +1,7 @@ +import { account } from "./appwriteClient"; + import type { AuthProvider } from "@refinedev/core"; import type { AppwriteException } from "@refinedev/appwrite"; -import { v4 as uuidv4 } from "uuid"; -import { account } from "./"; export const authProvider: AuthProvider = { login: async ({ email, password }) => { @@ -22,24 +22,6 @@ export const authProvider: AuthProvider = { }; } }, - register: async ({ email, password }) => { - try { - await account.create(uuidv4(), email, password); - return { - success: true, - redirectTo: "/login", - }; - } catch (error) { - const { type, message, code } = error as AppwriteException; - return { - success: false, - error: { - message, - name: `${code} - ${type}`, - }, - }; - } - }, logout: async () => { try { await account.deleteSession("current"); diff --git a/examples/data-provider-appwrite-tutorial-docs/src/utility/index.ts b/examples/data-provider-appwrite-tutorial-docs/src/utility/index.ts index f4d25db5696f..28b9ebd8b105 100644 --- a/examples/data-provider-appwrite-tutorial-docs/src/utility/index.ts +++ b/examples/data-provider-appwrite-tutorial-docs/src/utility/index.ts @@ -1,3 +1,4 @@ export * from "./normalize"; export * from "./appwriteClient"; -export * from "./authProvider"; +export * from "./status"; +export * from "./auth-provider"; diff --git a/examples/data-provider-appwrite-tutorial-docs/src/utility/status.ts b/examples/data-provider-appwrite-tutorial-docs/src/utility/status.ts new file mode 100644 index 000000000000..caa42409c1b7 --- /dev/null +++ b/examples/data-provider-appwrite-tutorial-docs/src/utility/status.ts @@ -0,0 +1,14 @@ +export const statuses = [ + { + label: "Draft", + value: "draft", + }, + { + label: "Published", + value: "published", + }, + { + label: "Rejected", + value: "rejected", + }, +]; diff --git a/examples/data-provider-appwrite/src/App.tsx b/examples/data-provider-appwrite/src/App.tsx index 7eda725badd5..3bba39d82e96 100644 --- a/examples/data-provider-appwrite/src/App.tsx +++ b/examples/data-provider-appwrite/src/App.tsx @@ -5,17 +5,8 @@ import { ThemedLayoutV2, useNotificationProvider, } from "@refinedev/antd"; -import { - type AppwriteException, - dataProvider, - liveProvider, -} from "@refinedev/appwrite"; -import { - type AuthProvider, - Authenticated, - GitHubBanner, - Refine, -} from "@refinedev/core"; +import { dataProvider, liveProvider } from "@refinedev/appwrite"; +import { Authenticated, GitHubBanner, Refine } from "@refinedev/core"; import routerProvider, { CatchAllNavigate, DocumentTitleHandler, @@ -27,92 +18,15 @@ import { BrowserRouter, Outlet, Route, Routes } from "react-router-dom"; import "@refinedev/antd/dist/reset.css"; import { App as AntdApp, ConfigProvider } from "antd"; -import { account, appwriteClient } from "./utility"; +import { appwriteClient, authProvider } from "./utility"; import { PostCreate, PostEdit, PostList, PostShow } from "./pages/posts"; - -const authProvider: AuthProvider = { - login: async ({ email, password }) => { - try { - await account.createEmailSession(email, password); - return { - success: true, - redirectTo: "/", - }; - } catch (e) { - const { type, message, code } = e as AppwriteException; - return { - success: false, - error: { - message, - name: `${code} - ${type}`, - }, - }; - } - }, - logout: async () => { - try { - await account.deleteSession("current"); - } catch (error: any) { - return { - success: false, - error, - }; - } - - return { - success: true, - redirectTo: "/login", - }; - }, - onError: async (error) => { - if (error?.code === 401) { - return { - logout: true, - }; - } - - return { error }; - }, - check: async () => { - try { - const session = await account.get(); - - if (session) { - return { - authenticated: true, - }; - } - } catch (error: any) { - return { - authenticated: false, - error: error, - logout: true, - redirectTo: "/login", - }; - } - - return { - authenticated: false, - error: { - message: "Check failed", - name: "Session not found", - }, - logout: true, - redirectTo: "/login", - }; - }, - getPermissions: async () => null, - getIdentity: async () => { - const user = await account.get(); - - if (user) { - return user; - } - - return null; - }, -}; +import { + CategoryCreate, + CategoryList, + CategoryShow, + CategoryEdit, +} from "./pages/categories"; const App: React.FC = () => { return ( @@ -131,13 +45,23 @@ const App: React.FC = () => { routerProvider={routerProvider} resources={[ { - name: "61c43ad33b857", + name: "blog_posts", list: "/posts", create: "/posts/create", edit: "/posts/edit/:id", show: "/posts/show/:id", meta: { - label: "Post", + label: "Blog Posts", + }, + }, + { + name: "categories", + list: "/categories", + create: "/categories/create", + show: "/categories/show/:id", + edit: "/categories/edit/:id", + meta: { + label: "Categories", }, }, ]} @@ -163,7 +87,7 @@ const App: React.FC = () => { > } + element={} /> @@ -172,12 +96,18 @@ const App: React.FC = () => { } /> } /> + + } /> + } /> + } /> + } /> + }> - + } > diff --git a/examples/data-provider-appwrite/src/interfaces/index.d.ts b/examples/data-provider-appwrite/src/interfaces/index.d.ts index a2710d8463e6..8d20355b71d0 100644 --- a/examples/data-provider-appwrite/src/interfaces/index.d.ts +++ b/examples/data-provider-appwrite/src/interfaces/index.d.ts @@ -3,6 +3,8 @@ export interface ICategory { title: string; } +export const IStatus = "draft" | "published" | "rejected"; + export interface IFile { name: string; percent: number; @@ -17,7 +19,11 @@ export interface IPost { id: string; title: string; content: string; - categoryId: string; + status: IStatus; + category: { + $id: string; + title: string; + }; images: string; } @@ -25,6 +31,6 @@ export interface IPostVariables { id: string; title: string; content: string; - categoryId: string; + category: string; images: string; } diff --git a/examples/data-provider-appwrite/src/pages/categories/create.tsx b/examples/data-provider-appwrite/src/pages/categories/create.tsx new file mode 100644 index 000000000000..eb1e0d43dbea --- /dev/null +++ b/examples/data-provider-appwrite/src/pages/categories/create.tsx @@ -0,0 +1,31 @@ +import { Create, useForm } from "@refinedev/antd"; +import { Form, Input } from "antd"; + +import type { HttpError } from "@refinedev/core"; +import type { ICategory } from "../../interfaces"; + +export const CategoryCreate = () => { + const { formProps, saveButtonProps } = useForm< + ICategory, + HttpError, + ICategory + >(); + + return ( + +
+ + + +
+
+ ); +}; diff --git a/examples/data-provider-appwrite/src/pages/categories/edit.tsx b/examples/data-provider-appwrite/src/pages/categories/edit.tsx new file mode 100644 index 000000000000..b3ea84614aba --- /dev/null +++ b/examples/data-provider-appwrite/src/pages/categories/edit.tsx @@ -0,0 +1,31 @@ +import { Edit, useForm } from "@refinedev/antd"; +import { Form, Input } from "antd"; + +import type { HttpError } from "@refinedev/core"; +import type { ICategory } from "../../interfaces"; + +export const CategoryEdit = () => { + const { formProps, saveButtonProps } = useForm< + ICategory, + HttpError, + ICategory + >({}); + + return ( + +
+ + + +
+
+ ); +}; diff --git a/examples/data-provider-appwrite/src/pages/categories/index.tsx b/examples/data-provider-appwrite/src/pages/categories/index.tsx new file mode 100644 index 000000000000..817d7f6b8a12 --- /dev/null +++ b/examples/data-provider-appwrite/src/pages/categories/index.tsx @@ -0,0 +1,4 @@ +export * from "./list"; +export * from "./show"; +export * from "./create"; +export * from "./edit"; diff --git a/examples/data-provider-appwrite/src/pages/categories/list.tsx b/examples/data-provider-appwrite/src/pages/categories/list.tsx new file mode 100644 index 000000000000..ef608988e2d3 --- /dev/null +++ b/examples/data-provider-appwrite/src/pages/categories/list.tsx @@ -0,0 +1,22 @@ +import { List, useTable, getDefaultSortOrder } from "@refinedev/antd"; +import { Table } from "antd"; + +import type { ICategory } from "../../interfaces"; + +export const CategoryList = () => { + const { tableProps, sorters } = useTable(); + + return ( + + + + dataIndex="id" + title="ID" + sorter + defaultSortOrder={getDefaultSortOrder("id", sorters)} + /> + dataIndex="title" title="Title" sorter /> +
+
+ ); +}; diff --git a/examples/data-provider-appwrite/src/pages/categories/show.tsx b/examples/data-provider-appwrite/src/pages/categories/show.tsx new file mode 100644 index 000000000000..1746ab915c6c --- /dev/null +++ b/examples/data-provider-appwrite/src/pages/categories/show.tsx @@ -0,0 +1,26 @@ +import { useShow } from "@refinedev/core"; + +import { Show } from "@refinedev/antd"; + +import { Typography } from "antd"; + +import type { ICategory } from "../../interfaces"; + +const { Title, Text } = Typography; + +export const CategoryShow = () => { + const { queryResult } = useShow(); + + const { data, isLoading } = queryResult; + const record = data?.data; + + return ( + + Id + {record?.id} + + Title + {record?.title} + + ); +}; diff --git a/examples/data-provider-appwrite/src/pages/posts/create.tsx b/examples/data-provider-appwrite/src/pages/posts/create.tsx index 4edfbb5d1257..a60c725ed4cf 100644 --- a/examples/data-provider-appwrite/src/pages/posts/create.tsx +++ b/examples/data-provider-appwrite/src/pages/posts/create.tsx @@ -1,14 +1,12 @@ -import type { HttpError } from "@refinedev/core"; - import { Create, useForm, useSelect } from "@refinedev/antd"; - -import type { RcFile } from "antd/lib/upload/interface"; import { Form, Input, Select, Upload } from "antd"; - import MDEditor from "@uiw/react-md-editor"; +import { normalizeFile, statuses, storage } from "../../utility"; + +import type { HttpError } from "@refinedev/core"; +import type { RcFile } from "antd/lib/upload/interface"; import type { IPost, IPostVariables, ICategory } from "../../interfaces"; -import { normalizeFile, storage } from "../../utility"; export const PostCreate = () => { const { formProps, saveButtonProps } = useForm< @@ -18,7 +16,7 @@ export const PostCreate = () => { >(); const { selectProps: categorySelectProps } = useSelect({ - resource: "61c43adc284ac", + resource: "categories", optionLabel: "title", optionValue: "id", }); @@ -48,7 +46,7 @@ export const PostCreate = () => { { > + { const { formProps, saveButtonProps, queryResult } = useForm< IPost, HttpError, + IPostVariables, IPostVariables >({ queryOptions: { @@ -22,6 +20,7 @@ export const PostEdit = () => { return { data: { ...data, + category: data.category.$id, images: data.images ? JSON.parse(data.images) : undefined, }, }; @@ -30,9 +29,10 @@ export const PostEdit = () => { }); const postData = queryResult?.data?.data; + const { selectProps: categorySelectProps } = useSelect({ - resource: "61c43adc284ac", - defaultValue: postData?.categoryId, + resource: "categories", + defaultValue: postData?.category, optionLabel: "title", optionValue: "id", }); @@ -62,7 +62,7 @@ export const PostEdit = () => { { > + { const { $id } = await storage.createFile( "default", - rcFile.name, + rcFile.uid, rcFile, ); diff --git a/examples/data-provider-appwrite/src/pages/posts/list.tsx b/examples/data-provider-appwrite/src/pages/posts/list.tsx index b800ff46802f..374bf100b24f 100644 --- a/examples/data-provider-appwrite/src/pages/posts/list.tsx +++ b/examples/data-provider-appwrite/src/pages/posts/list.tsx @@ -1,38 +1,25 @@ -import { useMany } from "@refinedev/core"; - import { List, - TextField, useTable, EditButton, ShowButton, getDefaultSortOrder, + TagField, } from "@refinedev/antd"; - import { Table, Space } from "antd"; -import type { IPost, ICategory } from "../../interfaces"; +import type { IPost } from "../../interfaces"; export const PostList = () => { - const { tableProps, sorter } = useTable({ + const { tableProps, sorters } = useTable({ initialSorter: [ { - field: "$id", + field: "$createdAt", order: "asc", }, ], }); - const categoryIds = - tableProps?.dataSource?.map((item) => item.categoryId) ?? []; - const { data, isLoading } = useMany({ - resource: "61c43adc284ac", - ids: categoryIds, - queryOptions: { - enabled: categoryIds.length > 0, - }, - }); - return ( @@ -40,23 +27,20 @@ export const PostList = () => { dataIndex="id" title="ID" sorter - defaultSortOrder={getDefaultSortOrder("id", sorter)} + width={100} + defaultSortOrder={getDefaultSortOrder("id", sorters)} /> - { - if (isLoading) { - return ; - } - - return ( - item.id === value)?.title} - /> - ); - }} + dataIndex="title" + title="Title" + sorter + defaultSortOrder={getDefaultSortOrder("title", sorters)} + /> + + } /> title="Actions" diff --git a/examples/data-provider-appwrite/src/pages/posts/show.tsx b/examples/data-provider-appwrite/src/pages/posts/show.tsx index 6d00ebcbcef9..24761a4861d3 100644 --- a/examples/data-provider-appwrite/src/pages/posts/show.tsx +++ b/examples/data-provider-appwrite/src/pages/posts/show.tsx @@ -1,10 +1,8 @@ -import { useShow, useOne } from "@refinedev/core"; - +import { useShow } from "@refinedev/core"; import { Show, MarkdownField, ImageField } from "@refinedev/antd"; - import { Typography, Space } from "antd"; -import type { IPost, ICategory, IFile } from "../../interfaces"; +import type { IPost, IFile } from "../../interfaces"; const { Title, Text } = Typography; @@ -15,29 +13,18 @@ export const PostShow = () => { const images = record?.images ? (JSON.parse(record.images) as IFile[]) : []; - const { data: categoryData, isLoading: categoryIsLoading } = - useOne({ - resource: "61c43adc284ac", - id: record?.categoryId || "", - queryOptions: { - enabled: !!record, - }, - }); - return ( Id {record?.id} - Title {record?.title} - Category - {categoryIsLoading ? "Loading..." : categoryData?.data.title} - + {record?.category.title} + Status + {record?.status} Content - Images {record?.images ? ( diff --git a/examples/data-provider-appwrite/src/utility/appwriteClient.ts b/examples/data-provider-appwrite/src/utility/appwriteClient.ts index 6c36280cb492..3dd3777e78af 100644 --- a/examples/data-provider-appwrite/src/utility/appwriteClient.ts +++ b/examples/data-provider-appwrite/src/utility/appwriteClient.ts @@ -1,7 +1,7 @@ import { Account, Appwrite, Storage } from "@refinedev/appwrite"; -const APPWRITE_URL = "https://refine.appwrite.org/v1"; -const APPWRITE_PROJECT = "61c4368b4e349"; +const APPWRITE_URL = "https://cloud.appwrite.io/v1"; +const APPWRITE_PROJECT = "6697687d002cbd31ba6b"; const appwriteClient = new Appwrite(); diff --git a/examples/data-provider-appwrite/src/utility/auth-provider.ts b/examples/data-provider-appwrite/src/utility/auth-provider.ts new file mode 100644 index 000000000000..8cbe33bceae6 --- /dev/null +++ b/examples/data-provider-appwrite/src/utility/auth-provider.ts @@ -0,0 +1,87 @@ +import { account } from "./appwriteClient"; + +import type { AuthProvider } from "@refinedev/core"; +import type { AppwriteException } from "@refinedev/appwrite"; + +export const authProvider: AuthProvider = { + login: async ({ email, password }) => { + try { + await account.createEmailSession(email, password); + return { + success: true, + redirectTo: "/", + }; + } catch (e) { + const { type, message, code } = e as AppwriteException; + return { + success: false, + error: { + message, + name: `${code} - ${type}`, + }, + }; + } + }, + logout: async () => { + try { + await account.deleteSession("current"); + } catch (error: any) { + return { + success: false, + error, + }; + } + + return { + success: true, + redirectTo: "/login", + }; + }, + onError: async (error) => { + if (error?.code === 401) { + return { + logout: true, + }; + } + + return { error }; + }, + check: async () => { + try { + const session = await account.get(); + + if (session) { + return { + authenticated: true, + }; + } + } catch (error: any) { + return { + authenticated: false, + error: error, + logout: true, + redirectTo: "/login", + }; + } + + return { + authenticated: false, + error: { + message: "Check failed", + name: "Session not found", + }, + logout: true, + redirectTo: "/login", + }; + }, + getPermissions: async () => null, + getIdentity: async () => { + const user = await account.get(); + + if (user) { + return user; + } + + return null; + }, +}; diff --git a/examples/data-provider-appwrite/src/utility/index.ts b/examples/data-provider-appwrite/src/utility/index.ts index f6323c2be868..28b9ebd8b105 100644 --- a/examples/data-provider-appwrite/src/utility/index.ts +++ b/examples/data-provider-appwrite/src/utility/index.ts @@ -1,2 +1,4 @@ export * from "./normalize"; export * from "./appwriteClient"; +export * from "./status"; +export * from "./auth-provider"; diff --git a/examples/data-provider-appwrite/src/utility/status.ts b/examples/data-provider-appwrite/src/utility/status.ts new file mode 100644 index 000000000000..caa42409c1b7 --- /dev/null +++ b/examples/data-provider-appwrite/src/utility/status.ts @@ -0,0 +1,14 @@ +export const statuses = [ + { + label: "Draft", + value: "draft", + }, + { + label: "Published", + value: "published", + }, + { + label: "Rejected", + value: "rejected", + }, +]; From 0892c756e447b0744e7c07be31e8a6f50b986fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Emir=20=C5=9Een?= Date: Fri, 26 Jul 2024 14:57:01 +0300 Subject: [PATCH 05/23] docs(blog): update blog image quality --- documentation/src/components/blog/blog-post-page/index.js | 2 +- .../src/components/blog/featured-blog-post-item/index.js | 2 +- documentation/src/theme/BlogPostItem/index.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/src/components/blog/blog-post-page/index.js b/documentation/src/components/blog/blog-post-page/index.js index 742af01fc730..bd2cb0bc6536 100644 --- a/documentation/src/components/blog/blog-post-page/index.js +++ b/documentation/src/components/blog/blog-post-page/index.js @@ -111,7 +111,7 @@ export const BlogPostPageView = ({ children }) => { src={`https://refine-web.imgix.net${frontMatter.image?.replace( "https://refine.ams3.cdn.digitaloceanspaces.com", "", - )}?w=800`} + )}?w=1788`} alt={title} /> diff --git a/documentation/src/components/blog/featured-blog-post-item/index.js b/documentation/src/components/blog/featured-blog-post-item/index.js index 768aa32b71c0..2cc53613fffa 100644 --- a/documentation/src/components/blog/featured-blog-post-item/index.js +++ b/documentation/src/components/blog/featured-blog-post-item/index.js @@ -35,7 +35,7 @@ export const FeaturedBlogPostItem = () => { src={`https://refine-web.imgix.net${frontMatter.image?.replace( "https://refine.ams3.cdn.digitaloceanspaces.com", "", - )}?h=256`} + )}?h=668`} alt={title} className={clsx( "absolute inset-0 mt-0 h-full w-full rounded-[10px] object-cover", diff --git a/documentation/src/theme/BlogPostItem/index.js b/documentation/src/theme/BlogPostItem/index.js index ea8f61b42528..c6fc9542e256 100644 --- a/documentation/src/theme/BlogPostItem/index.js +++ b/documentation/src/theme/BlogPostItem/index.js @@ -38,7 +38,7 @@ export default function BlogPostItem({ className }) { src={`https://refine-web.imgix.net${frontMatter.image?.replace( "https://refine.ams3.cdn.digitaloceanspaces.com", "", - )}?h=432`} + )}?h=668`} alt={title} className={clsx( "absolute inset-0 mt-0 h-full w-full rounded-[10px] object-cover", From 4dc7534c7a2f2f2833a2e586205009ae80cdbc0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Necati=20=C3=96zmen?= Date: Fri, 26 Jul 2024 17:17:03 +0300 Subject: [PATCH 06/23] docs(blog): add refine new post (#6189) Co-authored-by: Batuhan Wilhelm --- .../blog/2023-10-02-refine-crm-overview.md | 1 - .../blog/2024-07-25-refine-new-part-1.md | 358 ++++++++++++++++++ documentation/blog/authors.yml | 7 + 3 files changed, 365 insertions(+), 1 deletion(-) create mode 100644 documentation/blog/2024-07-25-refine-new-part-1.md diff --git a/documentation/blog/2023-10-02-refine-crm-overview.md b/documentation/blog/2023-10-02-refine-crm-overview.md index 4424feb39e60..94e6f94edb5a 100644 --- a/documentation/blog/2023-10-02-refine-crm-overview.md +++ b/documentation/blog/2023-10-02-refine-crm-overview.md @@ -4,7 +4,6 @@ description: We'll explore the key features of our CRM app, the technologies we slug: react-crm-with-refine authors: necati tags: [Refine, react] -is_featured: true image: https://refine.ams3.cdn.digitaloceanspaces.com/blog/2023-10-02-refine-crm-overview/social.jpg hide_table_of_contents: false --- diff --git a/documentation/blog/2024-07-25-refine-new-part-1.md b/documentation/blog/2024-07-25-refine-new-part-1.md new file mode 100644 index 000000000000..147468314fe1 --- /dev/null +++ b/documentation/blog/2024-07-25-refine-new-part-1.md @@ -0,0 +1,358 @@ +--- +title: Refine.new - Technical Architecture and Engineering Decisions Explored Part - 1 +description: We'll discuss the technical architecture, key-concepts, and engineering design decisions made during the development process of Refine.new. +slug: refine-new-explored-part-1 +authors: batuhan +tags: [Refine] +image: https://refine.ams3.cdn.digitaloceanspaces.com/blog/2024-07-25-refine-new-part-1/social.png +is_featured: true +hide_table_of_contents: false +--- + +## Introduction + +Hi I'm Batuhan, Tech Lead @ Refine. Today we'll discuss the technical architecture, key-concepts, and engineering design decisions made during the development process of Refine.new. We'll provide insights for developers on the challenges we faced, the solutions we implemented, and the overall journey of building this tool. + +This is the first of a two-part series. In part one, we will take time to share the planning and decision-making process involved in creating Refine.new as well as dealing with two big problems we identified. In part two, we get right into the detail of how these plans were brought to life in the implementation stage. + +## What is Refine.new? + +[refine.new](http://refine.new) is a tool where you can generate [Refine](https://refine.dev/) boilerplates on the browser with a combination of different libraries. You can instantly see the app preview in the browser, share it with others and download the source code. + +
+ + refine new + +
+ +Steps we'll cover: + +- [What are the key features?](#what-are-the-key-features) +- [Why we build Refine.new?](#why-we-build-refinenew) +- [Designing the Technical Architecture](#designing-the-technical-architecture) +- [Implementation phase](#implementation-phase) + +## What are the key features? + +Before diving into the details, let's quickly highlight some of the key features that make Refine.new a powerful tool for developers. + +**Streamlined Library Integration** + +You can mix and match different frameworks, libraries with your Refine applications. + +- React platform ([Vite.js](https://vitejs.dev/), [Next.js](https://nextjs.org/) or [Remix](https://remix.run/)), +- UI framework ( [Ant Design](https://ant.design/), [Material UI](https://mui.com/material-ui/getting-started/overview/), [Mantine](https://mantine.dev/), and [Chakra UI](https://chakra-ui.com/), or headless UI option), +- Data Provider (REST API, [Supabase](https://supabase.com/), [Strapi](https://strapi.io/), [NestJs](https://nestjs.com/), [Appwrite](https://appwrite.io/), or [Airtable](https://www.airtable.com/)), +- Authentication provider (Google Auth, Keycloak, Auth0, Supabase, Appwrite, Strapi, custom auth). + +Considering the build step options provided by refine.new, there are numerous possible project variations. With three React platforms, 5 UI frameworks, 6 data providers, and 8 authentication providers to choose from, you can create 720 different combinations, each tailored to your specific project needs. + +**Account-Based Boilerplate Management** + +You can also save these boilerplates into your account to be downloaded in the future. + +You can download the complete project code and use as a starting point for your project. + +**Continuous Updates and Maintenance** + +Stay up to date. The next time you download your boilerplate, it will include the latest Refine updates. This means you can create a boilerplate with your favorite libraries once and download it in the future with updated versions. + +**Batteries loaded** + +The generated application comes with a fully working Authentication, Dashboard and CRUD pages. + +**Quickly share previews** + +You can easily share URLs of your boilerplates with your library combinations. + +**Configure in the browser** + +You can change the theme color, logo and preview the app in real-time. + +At the time of this post being written, 30.000+ users created more than 110.000 boilerplates using Refine.new. + +## Why we build Refine.new? + +[Refine](https://github.com/refinedev/refine) is an open-source React meta-framework designed to create CRUD-centric web applications. We’ve started Refine because we wanted to have a framework that we can deliver high quality apps without compromising the flexibility. We didn’t like being vendor locked to a certain UI library, react framework, any authentication or data layer. + +Due to Refine’s flexible nature, there are thousands different ways you can use Refine by mixing and matching different libraries with different UI, Data, Auth layers. + +Starting a greenfield project is fun, but it’s also stressful. The decisions you are making in the beginning, could either simplify people’s lives or make them suffer for years to come. For this reason, It’s a good practice to evaluate different ideas, build POCs and make the decision based on these examples. Going through this process avoid bike-shedding and allows you to make better, more based decisions. + +If you already made your decisions and start a new project is also tedious thing to do. You need to do things repetitively over and over again. Install packages, do some imports, make some config etc.. For this reason, almost every framework has their generator to speed up this process. + +But that doesn’t solve the entire problem. Starter project for the frameworks includes bare-minimum code. Then you would need to setup your frameworks, libraries manually again. + +### At this point, the create-refine-app comes into play + +With `create-refine-app`, we simplified this process by making it possible for Refine users to not only start a bare project, but also a way to generate a project with various libraries and frameworks already set-up. We believe it’s a good DX improvement. You spend time on actually building features you need, instead of setting up things. + +You can try the command like below: + +``` +npm create refine-app@latest +``` + +
+ +
+ refine new +
+ +
+ +We also provide an option `Install example pages` which adds Blog Posts and Categories CRUD pages to your application. These create, list, edit pages are consist of tables, cards and forms. Including common components like tables, pagination, navigation buttons, forms saves even more time, and only add your customizations on top. + +While `create-refine-app` makes it quicker to generate Refine applications, there are more than 700+ combinations that you can try. Setting up an app, installing packages, running them locally could be tedious, if you want to explore many different options. + +On top of that, it’s not easy to show these apps to your colleagues as you play around. We wanted to provide a way for our users to easily share previews online, without a deployment steps. + +Being sweet spot between low-code and full-code, we are aiming to make it easier for developers to build applications, without sacrificing flexibility or limiting their options. + +So, speed-running the tedious application creation process in the browser sounds like a perfect idea for us. At the end of the day, you still have the code to add, remove, or change as you wish, but a big part is already set up. With create-refine-app simplifying project setup, developers can focus more on building features. + +Now, let's dive into the technical architecture of Refine.new, exploring the design choices and solutions that made it possible. + +## Designing the Technical Architecture + +Once the requirements were clear, we identified two critical features: + +**1. Generating boilerplates in the backend.** +**2. Rendering the app in the browser.** + +These features would enable us to build the rest of the project. We needed a backend to orchestrate processes and a frontend to handle user choices and communication with the backend. Then, we would optimize performance and cloud usage to ensure scalability before delivering the final product. + +### Generating boilerplate application based on user choices + +To streamline the process of generating boilerplate applications, we decided to use the `create-refine-app` and its templates, allowing us to reuse most of the business logic and templates without maintaining additional repositories. + +However, adapting the CLI tool to work with a NodeJS backend and keeping the backend in sync with the git repository templates created some challenges: + +- **CLI Tool Adaptation:** Since `create-refine-app` was originally built as a CLI tool, it required modifications to work with a NodeJS backend. +- **Template Synchronization:** We use a git repository for templates, so the backend must stay updated with these templates to keep everything consistent. + +We will detail the solutions to these challenges in the following sections. + +### Rendering the Boilerplate Application in the Browser + +To be able to render boilerplates in the browser, we decided to use our existing solution: Live Previews https://previews.refine.dev. + +We are already using live-previews in our documentation to render simple examples.Live previews application accepts encoded `lz-string`. + +`https://previews.refine.dev/preview?code={lz string encoded code here}` + +And then decodes it, renders using `react-live` package. + +While we can use live previews for that, it had several challenges: + +- Adapting the setup to render entire applications, which we hadn't done before. +- Ensuring the previews are responsive, fast, and reflect user changes in real-time. +- Bundling all application TypeScript and CSS files into a single JS file. +- Avoiding duplicate imports and exports. +- Handling the potential length of the encoded strings. +- Rendering NextJS and Remix applications end-to-end. +- Managing OAuth logins. + +### Planning the User Flow + +After identifying the solutions and addressing the challenges, the next step was to create a simple, happy-path user flow: + +We designed this initial flow in three parts: + +#### User + +1. Navigates to [Refine.new](http://refine.new/). +2. Selects libraries (Platform, UI Library, Data Provider, Auth Provider) + +#### Backend + +1. Generates boilerplate files using `create-refine-app` and it’s templates. +2. ZIPs the files and uploads them to a bucket. +3. Compiles application source code into a single file. +4. Encodes the file content with `lz-string`. +5. Stores `downloadURL` and `previewString`. + +#### Refine.new + +1. Shows a button to download boilerplate using `downloadURL` +2. Uses [`live-previews.refine.dev`](http://live-previews.refine.dev) in an iFrame to render preview. + +We can represent this flow in a diagram as follows: + +
+ +
+ refine new user flow +
+ +
+ +Having this flow decided allowed us to start planning the implementation. While this was a basic flow without much details, doing this would allow us to build additional features, improvements around it. + +Next, let's move into the implementation phase, starting with modifying the create-refine-app for NodeJS. + +## Implementation phase + +### Modify create-refine-app for NodeJS + +As mentioned earlier, we wanted to reuse `create-refine-app`. Since we are already maintaining this project and it’s templates, our updates would be useful for both create-refine app and also refine.new + +`create-refine-app` has 2 main parts. + +
+ +
+ cli flow +
+ +
+ +#### CLI layer + +It’s responsible for asking initial questions like project name and also can modify SAO variables, which git repository to get templates, which git branch, having presets and passing them to SAO. + +#### SAO package + +It takes care of prompting additional, template specific questions to user from provided templates, providing answers as variables to the templates, generating the files, doing package installation and so on. + +Eventually, after Commander starts the flow, it initializes a SAO instance. + +```tsx +const sao = new SAO({ + generator, + outDir: process.cwd(), + logLevel: program.debug ? 4 : 1, + appName: appName, + answers: hasAnswers, + extras: { + debug: !!program.debug, + commitMessage: process.env.INITIAL_COMMIT_MESSAGE, + disableTelemetry: !!program.disableTelemetry, + projectType, + paths: { + sourcePath, + }, + presetAnswers, + }, +} as Options); + +await sao.run().catch((err) => { + // ... +}); +``` + +We had to do some modifications to the SAO logic to make it compatible with API. + +- Skip additional prompts: API will already provide all required answers. +- Disable debug mode. +- Disable optional telemetry by default: It will be ran by our backend. +- Skip post installation: No need to install dependencies and commit the files. + +So we’ve added an extra prop to `extras` field called `apiMode`. Passing it true would take care of these changes in SAOFile. + +Eventually, our `api.ts` looked like this: + +```tsx +export interface IPreset { + name: string; + type: string; + answers: Record; +} + +const generator = path.resolve(__dirname, "./"); + +export const api = async ( + applicationName: string, + outDir: string, + sourcePath: string, + preset: IPreset, // Instead of getting from Commander, NodeJS sends these params. +): Promise => { + const sao = new SAO({ + generator, + outDir, + logLevel: 1, + appName: applicationName, + answers: true, + extras: { + apiMode: true, + debug: false, + projectType: preset.type, + paths: { + sourcePath, + }, + presetAnswers: preset.answers, + }, + } as Options); + + await sao.run().catch((err) => { + console.error("Error happened", err); + }); +}; +``` + +### Prepare lz-string + +Now we can create boilerplate files, next thing was to generate what we call `previewString`. + +For that, we used `rollup` with some additional plugins. Rollup allows us to bundle the project into a single file. Additionally, we used `rollup-plugin-import-css`, `rollup-plugin-tsconfig-paths`, `@rollup/plugin-node-resolve`, `@rollup/plugin-sucrase` . These plugins allowed us to bundle app and eventually. + +```tsx +function bundleBoilerplate(boilerplate: Boilerplate) { + const bundle = await rollup({ + // eslint-disable-next-line @typescript-eslint/no-empty-function + onwarn: () => {}, // Skip bloated logs. + input: inputPath, + maxParallelFileOps: 1, // Single thread. + treeshake: false, + cache: false, + perf: false, + plugins: [ + css(), + tsConfigPaths({ + tsConfigPath: `tmp/boilerplates/${boilerplate.id}/${boilerplate.projectName}/tsconfig.json`, + }), + resolve({ + extensions: [".ts", ".tsx"], + }), + sucrase({ + exclude: ["**/*.css"], + transforms: ["typescript", "jsx"], + }), + ], + external: ["axios", "./reportWebVitals"], + }); + + const { output } = await bundle.generate({}); + + await bundle.close(); + + const codeOutputRaw = output[0].code; + + const codeOutputArr = codeOutputRaw.split("\n"); + + codeOutputArr.push("render()"); + + const codeOutput = codeOutputArr.join("\n"); + + const compressedString = compressToEncodedURIComponent(codeOutput); + + return compressedString; +} +``` + +Finally, we used `lz-string`'s `compressToEncodedURIComponent` function to encode the file into a URL compatible string to pass to our preview. + +## Conclusion + +In this part of the series, we explored the technical architecture and engineering decisions behind Refine.new. . We covered the process of generating boilerplate applications with `create-refine-app` and the challenges of adapting it for NodeJS. + +We also detailed rendering live previews in the browser using `react-live` and addressed the associated challenges. Lastly, we outlined the initial user flow and began the implementation phase, including modifications to `create-refine-app` and generating the `previewString`. + +These steps provide a comprehensive overview of how Refine.new was designed and implemented to streamline the development process for developers. + +In the second article of this series, we will delve into the back-end components, exploring the following topics: + +- Orchestrating the backend processes +- Handling user choices and communication with the backend +- Optimizing performance and cloud usage for scalability +- Implementing the backend architecture and addressing challenges diff --git a/documentation/blog/authors.yml b/documentation/blog/authors.yml index 4a0a0d72d22f..0766872a8b0d 100644 --- a/documentation/blog/authors.yml +++ b/documentation/blog/authors.yml @@ -223,3 +223,10 @@ samuel_ogunleye: name: Samuel Ogunleye title: Frontend Engineer image_url: https://github.com/Sproff.png + +batuhan: + name: Batuhan Özdemir + title: Tech Lead at Refine + image_url: https://github.com/BatuhanW.png + github: https://github.com/BatuhanW + twitter: https://twitter.com/kakamelatte From c61c4143dd1e343c7323a015d8aee858c5d24dee Mon Sep 17 00:00:00 2001 From: Arielton Oberek Date: Fri, 26 Jul 2024 15:08:29 -0300 Subject: [PATCH 07/23] fix(docs): correct import path and notification provider usage (#6195) --- .../docs/notification/notification-provider/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/docs/notification/notification-provider/index.md b/documentation/docs/notification/notification-provider/index.md index b6f5af56bc55..5e615700a6fc 100644 --- a/documentation/docs/notification/notification-provider/index.md +++ b/documentation/docs/notification/notification-provider/index.md @@ -134,12 +134,12 @@ return ( ```tsx -import { useNotificationProvider } from "@refinedev/chakra"; +import { useNotificationProvider } from "@refinedev/chakra-ui"; return ( ); ``` From 013e975f15087561435b9e31c813b9d119d19d39 Mon Sep 17 00:00:00 2001 From: Gabriel Fernandes Date: Sun, 28 Jul 2024 08:11:01 +0100 Subject: [PATCH 08/23] docs(blog): fix typo in refine pixels part 3 (#6200) --- documentation/blog/2023-02-16-refine-pixels-3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/blog/2023-02-16-refine-pixels-3.md b/documentation/blog/2023-02-16-refine-pixels-3.md index 2e13dcbdeafb..4f58365d8007 100644 --- a/documentation/blog/2023-02-16-refine-pixels-3.md +++ b/documentation/blog/2023-02-16-refine-pixels-3.md @@ -87,7 +87,7 @@ The `auth.users` table is concerned with authentication in our app. It is create
-So, in order to create the `pubic.users` table, go ahead and run this SQL script in the SQL Editor of your **Supabase** project dashboard: +So, in order to create the `public.users` table, go ahead and run this SQL script in the SQL Editor of your **Supabase** project dashboard: ```sql -- Create a table for public users From 17aeea3fe9aa9cf726b57c1dc5d8f1c6515a6acc Mon Sep 17 00:00:00 2001 From: Bozhen <89320434+bzhn@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:22:05 +0300 Subject: [PATCH 09/23] fix(docs): small markdown formatting fix (#6204) --- .../docs/authentication/hooks/use-permissions/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/authentication/hooks/use-permissions/index.md b/documentation/docs/authentication/hooks/use-permissions/index.md index 9f6cbd2ca038..76e64e3bafe2 100644 --- a/documentation/docs/authentication/hooks/use-permissions/index.md +++ b/documentation/docs/authentication/hooks/use-permissions/index.md @@ -35,7 +35,7 @@ const authProvider: AuthProvider = { }; ``` -Get permissions data in the list page with `usePermissions` and check if the user has `"admin`" role: +Get permissions data in the list page with `usePermissions` and check if the user has `"admin"` role: ```tsx title="pages/post/list" // highlight-next-line From aa3f05174da1871713c81d00ace31e1bf21749f9 Mon Sep 17 00:00:00 2001 From: Batuhan Wilhelm Date: Mon, 29 Jul 2024 16:15:29 +0300 Subject: [PATCH 10/23] feat(docs): render wizard when playground param is browser (#6083) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ali Emir Şen --- documentation/src/css/custom.css | 9 ---- .../refine-theme/landing-playground-modal.tsx | 13 ++--- .../refine-theme/landing-try-it-section.tsx | 50 ++++++++++++------- 3 files changed, 40 insertions(+), 32 deletions(-) diff --git a/documentation/src/css/custom.css b/documentation/src/css/custom.css index f1ba92553880..2ab458347592 100644 --- a/documentation/src/css/custom.css +++ b/documentation/src/css/custom.css @@ -202,10 +202,6 @@ ---tutorial-card-bg-hover: var(--ifm-card-background-color); } -html[data-active-page="index"] { - scroll-behavior: smooth; -} - html.docs-wrapper { scroll-behavior: initial !important; } @@ -632,11 +628,6 @@ html.docs-wrapper { @apply hidden xl:inline-block; } -html[data-page="index"] { - scroll-snap-type: y mandatory; - scroll-behavior: smooth; -} - #__docusaurus .text-color-base { color: var(--ifm-font-color-base); } diff --git a/documentation/src/refine-theme/landing-playground-modal.tsx b/documentation/src/refine-theme/landing-playground-modal.tsx index 5d6c76255140..0aa84970b9dc 100644 --- a/documentation/src/refine-theme/landing-playground-modal.tsx +++ b/documentation/src/refine-theme/landing-playground-modal.tsx @@ -81,14 +81,15 @@ export const LandingPlaygroundModal = ({ visible, close }) => { React.useEffect(() => { if (isActive && visible) { if (typeof document !== "undefined" && document.documentElement) { + const previous = document.documentElement.style.scrollBehavior; document.documentElement.style.scrollBehavior = "unset"; + + return () => { + setTimeout(() => { + document.documentElement.style.scrollBehavior = previous; + }, 300); + }; } - } else { - setTimeout(() => { - if (typeof document !== "undefined" && document.documentElement) { - document.documentElement.style.scrollBehavior = "smooth"; - } - }, 300); } }, [isActive, visible]); diff --git a/documentation/src/refine-theme/landing-try-it-section.tsx b/documentation/src/refine-theme/landing-try-it-section.tsx index 43ed36f5ed80..3f5daae8875d 100644 --- a/documentation/src/refine-theme/landing-try-it-section.tsx +++ b/documentation/src/refine-theme/landing-try-it-section.tsx @@ -31,19 +31,28 @@ export const LandingTryItSection = ({ className }: { className?: string }) => { }, [search]); const scrollToItem = React.useCallback(() => { - const playgroundElement = document.getElementById("playground"); - if (playgroundElement) { - playgroundElement.scrollIntoView({ - behavior: "smooth", - block: "start", - inline: "nearest", - }); - } + const scroller = () => { + const playgroundElement = document.getElementById("playground"); + if (playgroundElement) { + playgroundElement.scrollIntoView({ + behavior: "smooth", + block: "start", + inline: "nearest", + }); + } + }; + + scroller(); + setTimeout(scroller, 300); }, []); React.useEffect(() => { if (params.playground || hash === "#playground") { - scrollToItem(); + if (params.playground === "browser") { + setWizardOpen(true); + } else { + scrollToItem(); + } } }, [params.playground, hash]); @@ -308,19 +317,23 @@ const LandingTryItWizardSection = ({ const iframeRef = React.useRef(null); const { colorMode } = useColorMode(); + const postColorMode = React.useCallback(() => { + iframeRef.current.contentWindow?.postMessage( + { + type: "colorMode", + colorMode, + }, + "*", + ); + }, [colorMode]); + React.useEffect(() => { // when color mode changes, post a message to the iframe // to update its color mode if (iframeRef.current) { - iframeRef.current.contentWindow?.postMessage( - { - type: "colorMode", - colorMode, - }, - "*", - ); + postColorMode(); } - }, [colorMode, visible]); + }, [postColorMode]); return (
{ + setTimeout(postColorMode, 50); + }} className={clsx( "scrollbar-hidden", "transition-opacity", From c3a75139f82de022b54855e87e200ab38c803af5 Mon Sep 17 00:00:00 2001 From: Alican Erdurmaz Date: Mon, 29 Jul 2024 16:24:05 +0300 Subject: [PATCH 11/23] fix(cli): update command doesn't respect semver specifier (#6135) --- .changeset/flat-boats-knock.md | 5 + packages/cli/jest.config.js | 3 + packages/cli/package.json | 2 + .../src/commands/check-updates/index.test.tsx | 173 +++++++++++++++ .../cli/src/commands/check-updates/index.tsx | 101 ++++++++- .../install-required-packages/index.ts | 2 +- packages/cli/src/commands/update/index.ts | 202 +++++++++++++----- .../update/interactive/index.test.tsx | 2 +- .../src/commands/update/interactive/index.ts | 30 ++- .../components/update-warning-table/table.ts | 16 ++ packages/cli/src/definitions/package.ts | 44 +++- packages/cli/src/utils/package/index.ts | 89 ++++++-- packages/cli/src/utils/refine/index.test.tsx | 8 +- packages/codemod/package.json | 3 +- pnpm-lock.yaml | 42 +++- 15 files changed, 615 insertions(+), 107 deletions(-) create mode 100644 .changeset/flat-boats-knock.md diff --git a/.changeset/flat-boats-knock.md b/.changeset/flat-boats-knock.md new file mode 100644 index 000000000000..0e853968d6fe --- /dev/null +++ b/.changeset/flat-boats-knock.md @@ -0,0 +1,5 @@ +--- +"@refinedev/cli": patch +--- + +fix: `yarn refine update` removes semver range specifiers(`^`, `~`) from `package.json`. #6134 diff --git a/packages/cli/jest.config.js b/packages/cli/jest.config.js index ed5afe0a146a..b62df0a5caf3 100644 --- a/packages/cli/jest.config.js +++ b/packages/cli/jest.config.js @@ -1,6 +1,7 @@ const { pathsToModuleNameMapper } = require("ts-jest"); const { compilerOptions } = require("./tsconfig"); +/** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */ module.exports = { preset: "ts-jest", rootDir: "./", @@ -10,4 +11,6 @@ module.exports = { moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths), modulePaths: ["", "src"], moduleDirectories: ["node_modules", "src"], + resetMocks: true, + clearMocks: true, }; diff --git a/packages/cli/package.json b/packages/cli/package.json index 8a2dfff196df..c52084f82946 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -33,6 +33,7 @@ "types": "node ../shared/generate-declarations.js" }, "dependencies": { + "@npmcli/package-json": "^5.2.0", "@refinedev/devtools-server": "1.1.34", "boxen": "^5.1.2", "cardinal": "^2.1.1", @@ -84,6 +85,7 @@ "@types/marked": "^5.0.1", "@types/marked-terminal": "^3.1.5", "@types/node-fetch": "^2.6.11", + "@types/npmcli__package-json": "^4.0.4", "@types/pluralize": "^0.0.29", "@types/prettier": "^2.7.3", "@types/semver": "^7.5.8", diff --git a/packages/cli/src/commands/check-updates/index.test.tsx b/packages/cli/src/commands/check-updates/index.test.tsx index 9323cb82ac74..64e485523707 100644 --- a/packages/cli/src/commands/check-updates/index.test.tsx +++ b/packages/cli/src/commands/check-updates/index.test.tsx @@ -3,6 +3,8 @@ import type { RefinePackageInstalledVersionData, } from "@definitions/package"; import * as checkUpdates from "./index"; +import * as packageUtils from "@utils/package"; + const { getOutdatedRefinePackages } = checkUpdates; test("Get outdated refine packages", async () => { @@ -16,31 +18,43 @@ test("Get outdated refine packages", async () => { current: "1.0.0", wanted: "1.0.1", latest: "2.0.0", + dependent: "", + location: "", }, "@refinedev/cli": { current: "1.1.1", wanted: "1.1.1", latest: "1.1.0", + dependent: "", + location: "", }, "@pankod/canvas2video": { current: "1.1.1", wanted: "1.1.1", latest: "1.1.1", + dependent: "", + location: "", }, "@owner/package-name": { current: "1.1.1", wanted: "1.1.1", latest: "1.1.0", + dependent: "", + location: "", }, "@owner/package-name1": { current: "N/A", wanted: "undefined", latest: "NaN", + dependent: "", + location: "", }, "@owner/refine-react": { current: "1.0.0", wanted: "1.0.1", latest: "2.0.0", + dependent: "", + location: "", }, }, output: [ @@ -50,6 +64,8 @@ test("Get outdated refine packages", async () => { wanted: "1.0.1", latest: "2.0.0", changelog: "https://c.refine.dev/core", + dependent: "", + location: "", }, ], }, @@ -64,3 +80,160 @@ test("Get outdated refine packages", async () => { expect(result).toEqual(testCase.output); } }); + +describe("getWantedWithPreferredWildcard", () => { + it("package not found in package.json", () => { + jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({}); + + const result = checkUpdates.getWantedWithPreferredWildcard( + "@refinedev/core", + "1.0.1", + ); + expect(result).toEqual("^1.0.1"); + }); + + it("with carret", () => { + jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({ + "@refinedev/core": "^1.0.0", + }); + + const result = checkUpdates.getWantedWithPreferredWildcard( + "@refinedev/core", + "1.0.1", + ); + expect(result).toEqual("^1.0.1"); + }); + + it("with tilda", () => { + jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({ + "@refinedev/core": "~1.0.0", + }); + + const result = checkUpdates.getWantedWithPreferredWildcard( + "@refinedev/core", + "1.0.1", + ); + expect(result).toEqual("~1.0.1"); + }); + + it("without caret and tilda", () => { + jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({ + "@refinedev/core": "1.0.0", + }); + + const result = checkUpdates.getWantedWithPreferredWildcard( + "@refinedev/core", + "1.0.1", + ); + expect(result).toEqual("1.0.1"); + }); + + it("with `.x.x`", () => { + jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({ + "@refinedev/core": "1.x.x", + }); + + const result = checkUpdates.getWantedWithPreferredWildcard( + "@refinedev/core", + "1.10.1", + ); + expect(result).toEqual("1.x.x"); + }); + + it("with `.x`", () => { + jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({ + "@refinedev/core": "1.1.x", + }); + + const result = checkUpdates.getWantedWithPreferredWildcard( + "@refinedev/core", + "1.1.10", + ); + expect(result).toEqual("1.1.x"); + }); + + it("with `latest`", () => { + jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({ + "@refinedev/core": "latest", + }); + + const result = checkUpdates.getWantedWithPreferredWildcard( + "@refinedev/core", + "3.1.1", + ); + expect(result).toEqual("latest"); + }); + + it("with range", () => { + jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({ + "@refinedev/core": ">=1.0.0 <=1.1.9", + }); + + const result = checkUpdates.getWantedWithPreferredWildcard( + "@refinedev/core", + "1.0.0-rc.10", + ); + expect(result).toEqual(">=1.0.0 <=1.1.9"); + }); + + it("multiple sets", () => { + jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({ + "@refinedev/core": "^2 <2.2 || > 2.3", + }); + + const result = checkUpdates.getWantedWithPreferredWildcard( + "@refinedev/core", + "2.3.1", + ); + expect(result).toEqual("^2 <2.2 || > 2.3"); + }); + + it("with `*`", () => { + jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({ + "@refinedev/core": "*", + }); + + const result = checkUpdates.getWantedWithPreferredWildcard( + "@refinedev/core", + "3.1.1", + ); + expect(result).toEqual("*"); + }); +}); + +describe("getLatestMinorVersionOfPackage", () => { + it.each([ + { + versionList: [ + "1.0.0", + "1.0.1", + "1.0.2", + "1.1.0", + "1.1.1", + "1.1.2", + "2.0.0", + ], + currentVersion: "1.1.0", + expected: "1.1.2", + }, + { + versionList: ["1.0.0", "1.0.1"], + currentVersion: "1.0.1", + expected: "1.0.1", + }, + { + versionList: [], + currentVersion: "1.0.1", + expected: "1.0.1", + }, + ])("should return %p", async ({ versionList, currentVersion, expected }) => { + jest + .spyOn(packageUtils, "getAllVersionsOfPackage") + .mockResolvedValueOnce(versionList); + const result = await checkUpdates.getLatestMinorVersionOfPackage( + "@refinedev/core", + currentVersion, + ); + expect(result).toEqual(expected); + }); +}); diff --git a/packages/cli/src/commands/check-updates/index.tsx b/packages/cli/src/commands/check-updates/index.tsx index b950aae70af9..d754a031e2e7 100644 --- a/packages/cli/src/commands/check-updates/index.tsx +++ b/packages/cli/src/commands/check-updates/index.tsx @@ -1,6 +1,10 @@ import type { Command } from "commander"; import { printUpdateWarningTable } from "@components/update-warning-table"; -import { pmCommands } from "@utils/package"; +import { + getAllVersionsOfPackage, + getDependenciesWithVersion, + pmCommands, +} from "@utils/package"; import execa from "execa"; import spinner from "@utils/spinner"; import type { @@ -8,6 +12,7 @@ import type { RefinePackageInstalledVersionData, } from "@definitions/package"; import semverDiff from "semver-diff"; +import { maxSatisfying } from "semver"; const load = (program: Command) => { return program @@ -38,25 +43,25 @@ export const isRefineUptoDate = async () => { return refinePackages; }; +/** + * Uses `npm outdated` command to get the list of outdated packages. + * @returns `[]` if no Refine package found. + * @returns `Refine` packages that have updates. + */ export const getOutdatedRefinePackages = async () => { const packages = await getOutdatedPackageList(); if (!packages) return []; const list: RefinePackageInstalledVersionData[] = []; - let changelog: string | undefined = undefined; Object.keys(packages).forEach((packageName) => { const dependency = packages[packageName]; if (packageName.includes("@refinedev")) { - changelog = packageName.replace(/@refinedev\//, "https://c.refine.dev/"); - list.push({ + ...dependency, name: packageName, - current: dependency.current, - wanted: dependency.wanted, - latest: dependency.latest, - changelog, + changelog: packageName.replace(/@refinedev\//, "https://c.refine.dev/"), }); } }); @@ -72,6 +77,9 @@ export const getOutdatedRefinePackages = async () => { return filteredList; }; +/** + * @returns `npm outdated` command response + */ export const getOutdatedPackageList = async () => { const pm = "npm"; @@ -89,4 +97,81 @@ export const getOutdatedPackageList = async () => { return JSON.parse(stdout) as NpmOutdatedResponse | null; }; +/** + * The `npm outdated` command's `wanted` field shows the desired update version (e.g., `^1.2.0` in `package.json` resolves to `1.2.1`). + * This function returns the version that matches the semver range in `package.json` (e.g., `^1.2.0` resolves to `^1.2.1`). + * + * @param packageName The name of the package. + * @param versionWanted The version that the user wants to update to. Wihtout semver range. + * @returns The version that satisfies the semver range in `package.json` with the preferred wildcard. + */ +export const getWantedWithPreferredWildcard = ( + packageName: RefinePackageInstalledVersionData["name"], + versionWanted: RefinePackageInstalledVersionData["wanted"], +): string => { + const dependencies = getDependenciesWithVersion(); + const versionInPackageJson = dependencies[packageName]; + + if (!versionInPackageJson) { + return `^${versionWanted}`; + } + + if (versionInPackageJson === "latest") { + return "latest"; + } + + if (versionInPackageJson === "*") { + return "*"; + } + + // has range + // if the version in the package.json has a range, it means the user has installed the package with a range. + // in that case, we should not change the version. package manager will install the latest version that satisfies the semver range. + if ( + [">", "<", ">=", "<=", "||"].some((char) => + versionInPackageJson.includes(char), + ) + ) { + return versionInPackageJson; + } + + // has `x` + // if the version in the package.json has `x` in it, it means the user has installed the package with a wildcard. + // in that case, we should not change the version. package manager will install the latest version that satisfies the semver range. + if (versionInPackageJson?.includes("x")) { + return `${versionInPackageJson}`; + } + + // has tilda + if (versionInPackageJson?.startsWith("~")) { + return `~${versionWanted}`; + } + + // has caret + if (versionInPackageJson?.startsWith("^")) { + return `^${versionWanted}`; + } + + return versionWanted; +}; + +/** + * + * @param packageName to get the latest minor version of the package available on npm. + * @param version current installed version of the package. This will be used to calculate the latest minor version. + * @returns The latest minor version of the package available on npm. + */ +export const getLatestMinorVersionOfPackage = async ( + packageName: RefinePackageInstalledVersionData["name"], + version: RefinePackageInstalledVersionData["wanted"], +) => { + const versionAll = await getAllVersionsOfPackage(packageName); + + /** + * The `semver` package's `maxSatisfying` function returns the highest version in the list that satisfies the range. + */ + const versionLatest = maxSatisfying(versionAll, `^${version}`); + return versionLatest ?? version; +}; + export default load; diff --git a/packages/cli/src/commands/swizzle/install-required-packages/index.ts b/packages/cli/src/commands/swizzle/install-required-packages/index.ts index 34d227a90f92..2ffecb5ae317 100644 --- a/packages/cli/src/commands/swizzle/install-required-packages/index.ts +++ b/packages/cli/src/commands/swizzle/install-required-packages/index.ts @@ -39,7 +39,7 @@ export const displayManualInstallationCommand = async ( requiredPackages: string[], ) => { const pm = await getPreferedPM(); - const pmCommand = pmCommands[pm.name].install.join(" "); + const pmCommand = pmCommands[pm.name].add.join(" "); const packages = requiredPackages.join(" "); const command = `${pm.name} ${pmCommand} ${packages}`; diff --git a/packages/cli/src/commands/update/index.ts b/packages/cli/src/commands/update/index.ts index b597c82efeea..a0c45acb0331 100644 --- a/packages/cli/src/commands/update/index.ts +++ b/packages/cli/src/commands/update/index.ts @@ -1,17 +1,34 @@ import inquirer from "inquirer"; import center from "center-align"; import { type Command, Option } from "commander"; +import PackageJson from "@npmcli/package-json"; import spinner from "@utils/spinner"; -import { isRefineUptoDate } from "@commands/check-updates"; +import { + getLatestMinorVersionOfPackage, + getWantedWithPreferredWildcard, + isRefineUptoDate, +} from "@commands/check-updates"; import { getPreferedPM, installPackages, pmCommands } from "@utils/package"; import { promptInteractiveRefineUpdate } from "@commands/update/interactive"; -import type { RefinePackageInstalledVersionData } from "@definitions/package"; +import type { + PackageDependency, + RefinePackageInstalledVersionData, +} from "@definitions/package"; import { getVersionTable } from "@components/version-table"; +import chalk from "chalk"; + +type SelectedPackage = PackageDependency; enum Tag { - Wanted = "wanted", - Latest = "latest", - Next = "next", + WANTED = "wanted", + LATEST = "latest", + NEXT = "next", +} + +enum InstallationType { + WANTED = "wanted", + MINOR = "minor", + INTERACTIVE = "interactive", } interface OptionValues { @@ -56,83 +73,160 @@ const action = async (options: OptionValues) => { return; } - let selectedPackages: string[] | null | undefined = null; + let selectedPackages: SelectedPackage | null | undefined = null; if (all) { - runAll(tag, packages); - } else { - const { table, width } = getVersionTable(packages) ?? ""; - - console.log(center("Available Updates", width)); - console.log(table); - - const { allByPrompt } = await inquirer.prompt<{ allByPrompt: boolean }>([ - { - type: "list", - name: "allByPrompt", - message: - "Do you want to update all Refine packages for minor and patch versions?", - choices: [ - { - name: "Yes (Recommended)", - value: true, - }, - { - name: "No, use interactive mode", - value: false, - }, - ], - }, - ]); - - if (allByPrompt) { - selectedPackages = runAll(tag, packages); - } else { - selectedPackages = await promptInteractiveRefineUpdate(packages); + const selectedPackages = await preparePackagesToInstall(tag, packages); + if (!selectedPackages) return; + + if (dryRun) { + printInstallCommand(selectedPackages); + return; } + + runInstallation(selectedPackages); + return; + } + + // print the table of available updates + const { table, width } = getVersionTable(packages) ?? ""; + console.log(center("Available Updates", width)); + console.log(table); + console.log( + `- ${chalk.yellow( + chalk.bold("Current"), + )}: The version of the package that is currently installed`, + ); + console.log( + `- ${chalk.yellow( + chalk.bold("Wanted"), + )}: The maximum version of the package that satisfies the semver range specified in \`package.json\``, + ); + console.log( + `- ${chalk.yellow( + chalk.bold("Latest"), + )}: The latest version of the package available on npm`, + ); + console.log(); + + const { installationType } = await inquirer.prompt<{ + installationType: InstallationType; + }>([ + { + type: "list", + name: "installationType", + message: + "Do you want to update all Refine packages for minor and patch versions?", + choices: [ + { + name: `Update all packages to latest "minor" version without any breaking changes.`, + value: "minor", + }, + { + name: "Update all packages to the latest version that satisfies the semver(`wanted`) range specified in `package.json`", + value: "wanted", + }, + { + name: `Use interactive mode. Choose this option for "major" version updates.`, + value: "interactive", + }, + ], + }, + ]); + + if (installationType === InstallationType.INTERACTIVE) { + selectedPackages = await promptInteractiveRefineUpdate(packages); + } + if (installationType === InstallationType.WANTED) { + selectedPackages = await preparePackagesToInstall(Tag.WANTED, packages); + } + if (installationType === InstallationType.MINOR) { + selectedPackages = await preparePackagesToInstall( + InstallationType.MINOR, + packages, + ); } if (!selectedPackages) return; if (dryRun) { printInstallCommand(selectedPackages); - return; + } else { + runInstallation(selectedPackages); } - - pmInstall(selectedPackages); }; -const runAll = (tag: Tag, packages: RefinePackageInstalledVersionData[]) => { - if (tag === Tag.Wanted) { +const preparePackagesToInstall = async ( + tag: Tag | InstallationType, + packages: RefinePackageInstalledVersionData[], +): Promise => { + if (tag === Tag.WANTED) { const isAllPackagesAtWantedVersion = packages.every( (pkg) => pkg.current === pkg.wanted, ); if (isAllPackagesAtWantedVersion) { - console.log( - "All `Refine` packages are up to date with the wanted version 🎉", - ); + console.log(); + console.log("✅ All `Refine` packages are already at the wanted version"); return null; } } - const packagesWithVersion = packages.map((pkg) => { - const version = tag === Tag.Wanted ? pkg.wanted : tag; - return `${pkg.name}@${version}`; - }); + // empty line for better readability + console.log(); + + const packagesWithVersion: PackageDependency = {}; + for (const pkg of packages) { + let version = pkg.latest; + + if (tag === InstallationType.MINOR) { + const latestMinorVersion = await spinner( + () => getLatestMinorVersionOfPackage(pkg.name, pkg.wanted), + `Checking for the latest minor version of ${pkg.name}`, + ); + version = `^${latestMinorVersion}`; + } + if (tag === Tag.WANTED) { + version = getWantedWithPreferredWildcard(pkg.name, pkg.wanted); + } + if (tag === Tag.LATEST) { + version = `^${pkg.latest}`; + } + if (tag === Tag.NEXT) { + version = Tag.NEXT; + } + + packagesWithVersion[pkg.name] = version; + } return packagesWithVersion; }; -const printInstallCommand = async (packages: string[]) => { +const printInstallCommand = async (packages: SelectedPackage) => { const pm = await getPreferedPM(); - const commandInstall = pmCommands[pm.name].install; - console.log(`${pm.name} ${commandInstall.join(" ")} ${packages.join(" ")}`); + const commandInstall = pmCommands[pm.name].add; + let packagesListAsString = ""; + for (const [name, version] of Object.entries(packages)) { + packagesListAsString += `${name}@${version} `; + } + console.log(); + console.log(`${pm.name} ${commandInstall.join(" ")} ${packagesListAsString}`); }; -const pmInstall = (packages: string[]) => { +const runInstallation = async (packagesToInstall: SelectedPackage) => { console.log("Updating `Refine` packages..."); - console.log(packages.map((pkg) => ` - ${pkg}`).join("\n")); - installPackages(packages); + + // to install packages, first we need to manipulate the package.json file, then install the packages + const packageJson = await PackageJson.load(process.cwd()); + + packageJson.update({ + dependencies: { + ...((packageJson.content.dependencies ?? {}) as { [x: string]: string }), + ...(packagesToInstall ?? {}), + }, + }); + await packageJson.save(); + + installPackages([], "all"); }; export default load; diff --git a/packages/cli/src/commands/update/interactive/index.test.tsx b/packages/cli/src/commands/update/interactive/index.test.tsx index e0cce5cba7d8..0a8e6aa72d99 100644 --- a/packages/cli/src/commands/update/interactive/index.test.tsx +++ b/packages/cli/src/commands/update/interactive/index.test.tsx @@ -28,7 +28,7 @@ test("Validate interactive prompt", () => { test("Categorize UI Group", () => { const testCases = [ { - input: [], + input: [] as any, output: null, }, { diff --git a/packages/cli/src/commands/update/interactive/index.ts b/packages/cli/src/commands/update/interactive/index.ts index 9c85489e5c5d..c05afbded89f 100644 --- a/packages/cli/src/commands/update/interactive/index.ts +++ b/packages/cli/src/commands/update/interactive/index.ts @@ -3,7 +3,10 @@ import semverDiff from "semver-diff"; import chalk from "chalk"; import { findDuplicates } from "@utils/array"; import { parsePackageNameAndVersion } from "@utils/package"; -import type { RefinePackageInstalledVersionData } from "@definitions/package"; +import type { + PackageDependency, + RefinePackageInstalledVersionData, +} from "@definitions/package"; type UIGroup = { patch: { @@ -47,7 +50,18 @@ export const promptInteractiveRefineUpdate = async ( }, ]); - return answers.packages.length > 0 ? answers.packages : null; + if (answers.packages.length > 0) { + // convert to object for easy access + const packagesObject: PackageDependency = {}; + answers.packages.forEach((pckg) => { + const { name, version } = parsePackageNameAndVersion(pckg); + packagesObject[name] = version ?? "latest"; + }); + + return packagesObject; + } + + return null; }; export const validatePrompt = (input: string[]) => { @@ -159,8 +173,8 @@ const createInquirerUI = (uiGroup: UIGroup) => { choices.push({ name: `${pckg.name.padEnd(maxNameLength)} ${pckg.from.padStart( maxFromLength, - )} -> ${pckg.to}`, - value: `${pckg.name}@${pckg.to}`, + )} -> ^${pckg.to}`, + value: `${pckg.name}@^${pckg.to}`, }); }); } @@ -173,8 +187,8 @@ const createInquirerUI = (uiGroup: UIGroup) => { choices.push({ name: `${pckg.name.padEnd(maxNameLength)} ${pckg.from.padStart( maxFromLength, - )} -> ${pckg.to}`, - value: `${pckg.name}@${pckg.to}`, + )} -> ^${pckg.to}`, + value: `${pckg.name}@^${pckg.to}`, }); }); } @@ -187,8 +201,8 @@ const createInquirerUI = (uiGroup: UIGroup) => { choices.push({ name: `${pckg.name.padEnd(maxNameLength)} ${pckg.from.padStart( maxFromLength, - )} -> ${pckg.to}`, - value: `${pckg.name}@${pckg.to}`, + )} -> ^${pckg.to}`, + value: `${pckg.name}@^${pckg.to}`, }); }); } diff --git a/packages/cli/src/components/update-warning-table/table.ts b/packages/cli/src/components/update-warning-table/table.ts index 6f505d34e577..cfd1520a5a99 100644 --- a/packages/cli/src/components/update-warning-table/table.ts +++ b/packages/cli/src/components/update-warning-table/table.ts @@ -18,6 +18,22 @@ export const printUpdateWarningTable = async ( const { table, width } = getVersionTable(data); console.log(); console.log(center("Update Available", width)); + console.log(); + console.log( + `- ${chalk.yellow( + chalk.bold("Current"), + )}: The version of the package that is currently installed`, + ); + console.log( + `- ${chalk.yellow( + chalk.bold("Wanted"), + )}: The maximum version of the package that satisfies the semver range specified in \`package.json\``, + ); + console.log( + `- ${chalk.yellow( + chalk.bold("Latest"), + )}: The latest version of the package available on npm`, + ); console.log(table); console.log( center( diff --git a/packages/cli/src/definitions/package.ts b/packages/cli/src/definitions/package.ts index 46b3cecbd82a..3b57f4e7b1aa 100644 --- a/packages/cli/src/definitions/package.ts +++ b/packages/cli/src/definitions/package.ts @@ -4,20 +4,62 @@ export enum PackageManagerTypes { PNPM = "pnpm", } +/** + * type of `npm outdated` command response + */ export type NpmOutdatedResponse = Record< string, { current: string; wanted: string; latest: string; - dependet?: string; + dependent: string; + location: string; } >; export type RefinePackageInstalledVersionData = { name: string; + /** + * version of the package that is currently installed. Without semver range wildcard. + */ current: string; + /** + * version that the user wants to update to. Without semver range wildcard. + * e.g. `^1.0.0` in `package.json` resolves to `1.0.1` in the this field. + */ wanted: string; + /** + * latest version of the package available on npm + */ latest: string; + /** + * changelog url + */ changelog?: string; + /** + * dependent package name + */ + dependent: string; + /** + * location of the package + */ + location: string; +}; + +/** + * key is the script name and value is the script command + */ +export type PackageDependency = Record; + +export type PackageJson = { + name: string; + version: string; + scripts?: Record; + dependencies?: PackageDependency; + devDependencies?: PackageDependency; + peerDependencies?: PackageDependency; + refine?: { + projectId?: string; + }; }; diff --git a/packages/cli/src/utils/package/index.ts b/packages/cli/src/utils/package/index.ts index 0944188301ab..380d5bb890d4 100644 --- a/packages/cli/src/utils/package/index.ts +++ b/packages/cli/src/utils/package/index.ts @@ -4,9 +4,9 @@ import { existsSync, pathExists, readFileSync, readJSON } from "fs-extra"; import globby from "globby"; import path from "path"; import preferredPM from "preferred-pm"; +import type { PackageJson } from "@definitions/package"; -// TODO: Add package.json type -export const getPackageJson = (): any => { +export const getPackageJson = (): PackageJson => { if (!existsSync("package.json")) { throw new Error("./package.json not found"); } @@ -14,28 +14,28 @@ export const getPackageJson = (): any => { return JSON.parse(readFileSync("package.json", "utf8")); }; -export const getDependencies = (): string[] => { +export const getDependencies = () => { const packageJson = getPackageJson(); return Object.keys(packageJson.dependencies || {}); }; -export const getDependenciesWithVersion = (): string[] => { +export const getDependenciesWithVersion = () => { const packageJson = getPackageJson(); - return packageJson.dependencies; + return packageJson?.dependencies || {}; }; -export const getDevDependencies = (): string[] => { +export const getDevDependencies = () => { const packageJson = getPackageJson(); return Object.keys(packageJson.devDependencies || {}); }; -export const getAllDependencies = (): string[] => { +export const getAllDependencies = () => { return [...getDependencies(), ...getDependencies()]; }; -export const getScripts = (): Record => { +export const getScripts = () => { const packageJson = getPackageJson(); - return packageJson.scripts; + return packageJson?.scripts || {}; }; export const getInstalledRefinePackages = async () => { @@ -125,24 +125,28 @@ export const isPackageHaveRefineConfig = async (packagePath: string) => { export const pmCommands = { npm: { - install: ["install", "--save"], - installDev: ["install", "--save-dev"], + add: ["install", "--save"], + addDev: ["install", "--save-dev"], outdatedJson: ["outdated", "--json"], + install: ["install"], }, yarn: { - install: ["add"], - installDev: ["add", "-D"], + add: ["add"], + addDev: ["add", "-D"], outdatedJson: ["outdated", "--json"], + install: ["install"], }, pnpm: { - install: ["add"], - installDev: ["add", "-D"], + add: ["add"], + addDev: ["add", "-D"], outdatedJson: ["outdated", "--format", "json"], + install: ["install"], }, bun: { - install: ["add"], - installDev: ["add", "--dev"], + add: ["add"], + addDev: ["add", "--dev"], outdatedJson: ["outdated", "--format", "json"], + install: ["install"], }, }; @@ -159,11 +163,15 @@ export const getPreferedPM = async () => { return pm; }; -export const installPackages = async (packages: string[]) => { +export const installPackages = async ( + packages: string[], + type: "all" | "add" = "all", +) => { const pm = await getPreferedPM(); try { - const installCommand = pmCommands[pm.name].install; + const installCommand = + type === "all" ? pmCommands[pm.name].install : pmCommands[pm.name].add; const execution = execa(pm.name, [...installCommand, ...packages], { stdio: "inherit", @@ -194,7 +202,7 @@ export const installPackagesSync = async (packages: string[]) => { const pm = await getPreferedPM(); try { - const installCommand = pmCommands[pm.name].install; + const installCommand = pmCommands[pm.name].add; const execution = execa.sync(pm.name, [...installCommand, ...packages], { stdio: "inherit", @@ -285,3 +293,44 @@ export const hasIncomatiblePackages = (packages: string[]): boolean => { return false; }; + +export const getAllVersionsOfPackage = async ( + packageName: string, +): Promise => { + const pm = "npm"; + + const { stdout, timedOut } = await execa( + pm, + ["view", packageName, "versions", "--json"], + { + reject: false, + timeout: 25 * 1000, + }, + ); + + if (timedOut) { + console.log("❌ Timed out while checking for updates."); + process.exit(1); + } + + let result: + | string[] + | { + error: { + code: string; + }; + } = []; + + try { + result = JSON.parse(stdout); + if (!result || "error" in result) { + console.log("❌ Something went wrong while checking for updates."); + process.exit(1); + } + } catch (error) { + console.log("❌ Something went wrong while checking for updates."); + process.exit(1); + } + + return result; +}; diff --git a/packages/cli/src/utils/refine/index.test.tsx b/packages/cli/src/utils/refine/index.test.tsx index 99a9dac7aa05..711d6a42ea2c 100644 --- a/packages/cli/src/utils/refine/index.test.tsx +++ b/packages/cli/src/utils/refine/index.test.tsx @@ -56,9 +56,11 @@ test("Has default script", () => { ]; testCases.forEach((testCase) => { - jest - .spyOn(utilsPackage, "getPackageJson") - .mockReturnValueOnce(testCase.input); + jest.spyOn(utilsPackage, "getPackageJson").mockReturnValueOnce({ + name: "test", + version: "1.0.0", + ...testCase.input, + }); const result = hasDefaultScript(); diff --git a/packages/codemod/package.json b/packages/codemod/package.json index d26090327d7d..9707b5287a6c 100644 --- a/packages/codemod/package.json +++ b/packages/codemod/package.json @@ -24,7 +24,7 @@ "prepare": "pnpm build" }, "dependencies": { - "@npmcli/package-json": "3.0.0", + "@npmcli/package-json": "^5.2.0", "chalk": "^4.1.2", "cheerio": "1.0.0-rc.9", "execa": "^5.1.1", @@ -40,6 +40,7 @@ "@types/inquirer": "^8.2.5", "@types/jest": "^29.2.4", "@types/jscodeshift": "^0.11.11", + "@types/npmcli__package-json": "^4.0.4", "jest": "^29.3.1", "ts-jest": "^29.1.2", "tslib": "^2.6.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d74c51a7728..109443f6996a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13809,6 +13809,9 @@ importers: packages/cli: dependencies: + '@npmcli/package-json': + specifier: ^5.2.0 + version: 5.2.0 '@refinedev/devtools-server': specifier: 1.1.34 version: link:../devtools-server @@ -13957,6 +13960,9 @@ importers: '@types/node-fetch': specifier: ^2.6.11 version: 2.6.11 + '@types/npmcli__package-json': + specifier: ^4.0.4 + version: 4.0.4 '@types/pluralize': specifier: ^0.0.29 version: 0.0.29 @@ -13985,8 +13991,8 @@ importers: packages/codemod: dependencies: '@npmcli/package-json': - specifier: 3.0.0 - version: 3.0.0 + specifier: ^5.2.0 + version: 5.2.0 chalk: specifier: ^4.1.2 version: 4.1.2 @@ -14027,6 +14033,9 @@ importers: '@types/jscodeshift': specifier: ^0.11.11 version: 0.11.11 + '@types/npmcli__package-json': + specifier: ^4.0.4 + version: 4.0.4 jest: specifier: ^29.3.1 version: 29.7.0(@types/node@20.5.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5)) @@ -20684,14 +20693,14 @@ packages: resolution: {integrity: sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - '@npmcli/package-json@3.0.0': - resolution: {integrity: sha512-NnuPuM97xfiCpbTEJYtEuKz6CFbpUHtaT0+5via5pQeI25omvQDFbp1GcGJ/c4zvL/WX0qbde6YiLgfZbWFgvg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - '@npmcli/package-json@4.0.1': resolution: {integrity: sha512-lRCEGdHZomFsURroh522YvA/2cVb9oPIJrjHanCJZkiasz1BzcnLr3tBJhlV7S86MBJBuAQ33is2D60YitZL2Q==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + '@npmcli/package-json@5.2.0': + resolution: {integrity: sha512-qe/kiqqkW0AGtvBjL8TJKZk/eBBSpnJkUWvHdQ9jM2lKHXRYYJuyNpJPlJw3c8QjC2ow6NZYiLExhUaeJelbxQ==} + engines: {node: ^16.14.0 || >=18.0.0} + '@npmcli/promise-spawn@6.0.2': resolution: {integrity: sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -23048,6 +23057,9 @@ packages: '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + '@types/npmcli__package-json@4.0.4': + resolution: {integrity: sha512-6QjlFUSHBmZJWuC08bz1ZCx6tm4t+7+OJXAdvM6tL2pI7n6Bh5SIp/YxQvnOLFf8MzCXs2ijyFgrzaiu1UFBGA==} + '@types/papaparse@5.3.14': resolution: {integrity: sha512-LxJ4iEFcpqc6METwp9f6BV6VVc43m6MfH0VqFosHvrUgfXiFe6ww7R3itkOQ+TCK6Y+Iv/+RnnvtRZnkc5Kc9g==} @@ -43250,10 +43262,6 @@ snapshots: '@npmcli/node-gyp@3.0.0': {} - '@npmcli/package-json@3.0.0': - dependencies: - json-parse-even-better-errors: 3.0.1 - '@npmcli/package-json@4.0.1': dependencies: '@npmcli/git': 4.1.0 @@ -43266,6 +43274,18 @@ snapshots: transitivePeerDependencies: - bluebird + '@npmcli/package-json@5.2.0': + dependencies: + '@npmcli/git': 5.0.6 + glob: 10.4.2 + hosted-git-info: 7.0.1 + json-parse-even-better-errors: 3.0.1 + normalize-package-data: 6.0.0 + proc-log: 4.2.0 + semver: 7.6.0 + transitivePeerDependencies: + - bluebird + '@npmcli/promise-spawn@6.0.2': dependencies: which: 3.0.1 @@ -46425,6 +46445,8 @@ snapshots: '@types/normalize-package-data@2.4.4': {} + '@types/npmcli__package-json@4.0.4': {} + '@types/papaparse@5.3.14': dependencies: '@types/node': 18.19.31 From de808b32cd342047e23a943f2aab5ed3372d0de5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Necati=20=C3=96zmen?= Date: Mon, 29 Jul 2024 16:39:18 +0300 Subject: [PATCH 12/23] docs(blog): update diff post (#6203) --- ...-12-git-diff.md => 2024-07-29-git-diff.md} | 62 ++++++++++++++++--- 1 file changed, 55 insertions(+), 7 deletions(-) rename documentation/blog/{2023-05-12-git-diff.md => 2024-07-29-git-diff.md} (88%) diff --git a/documentation/blog/2023-05-12-git-diff.md b/documentation/blog/2024-07-29-git-diff.md similarity index 88% rename from documentation/blog/2023-05-12-git-diff.md rename to documentation/blog/2024-07-29-git-diff.md index 103c5bcf0a55..97ed15fab8e8 100644 --- a/documentation/blog/2023-05-12-git-diff.md +++ b/documentation/blog/2024-07-29-git-diff.md @@ -4,10 +4,12 @@ description: We'll explore Git's 'git diff' which helps you track changes throug slug: git-diff-command authors: muhammad_khabbab tags: [git, dev-tools] -image: https://refine.ams3.cdn.digitaloceanspaces.com/blog/2023-05-12-git-diff/social.png +image: https://refine.ams3.cdn.digitaloceanspaces.com/blog/2023-05-12-git-diff/social-2.png hide_table_of_contents: false --- +_This article was last updated on July 29, 2024, to add sections for security and using Git Diff with other Git Commands._ + ## Introduction Git can be thrilling and, at the same time, intimidating as well. Don't worry, we are here to make things easier for you. Today, we're exploring Git's '`git diff'` command, which developers use on a daily basis. @@ -399,12 +401,58 @@ You can use commit hash to compare different commits using git Diff (Sample Belo
-
-
- - discord banner - -
+### Using `--base` with Git Diff + +Merge conflicts mostly arise when several developers work on the same codebase. Running `git diff` can point out conflicting changes and help you address them smoothly: + +```sh +git diff --base file.txt +``` + +This command compares the conflicting file with the base version, making the conflicts clearer to understand and solve. + +By the insertion of these additional sections, this article is going to be a more complete guide on how to use `git diff`, which I believe should not only deal with technical usage but practical application. + +## Security Features with Git Diff + +Now, I'd like to share a few thoughts on the security measures we can put in place to enhance our Git workflow: + +### Pre-commit Ho + +I will be able to use Git hooks in order to make automatic checks of security issues before commits. For instance, pre-commit hooks can check for sensitive data such as API keys or passwords. + +```bash +# .git/hooks +if grep -q "API_KEY" *.js; then +echo "API keys found in the code. Please remove before commit." +exit 1 +fi +``` + +### Commit Signing + +We can sign our commits even with GPG keys, making sure that our commits are authentic and helping in the verification of the trusted source of commits. + +```bash +git config --global user.signingkey YOUR-GPG-KEY-ID +git commit -S -m "Your commit message" +``` + +### Regular Audits + +Periodic auditing of the codebase can be done through the use of the `git diff` tool to identify any security glitches in the code and fix them during checks. We get to see what changes are brought in and what is fishy. + +```bash +git diff HEAD~5 +``` + +The previous 5 commits are compared to the state currently being looked at, to review changes within recent history. + +### Access Controls + +The access controls on our repositories need to be strict, in a manner such that only the rightly authorized people possess the capability of pushing changes. This can be governed within GitHub and GitLab access controls and other hosting services for Git. + +Those are the steps that can help us maintain a more secure codebase and prevent a potential breach of security. ## Conclusion From 20499cac091de94dcb4856a5253c68f216f022aa Mon Sep 17 00:00:00 2001 From: Aiswarya PM <31305967+aishuarya@users.noreply.github.com> Date: Mon, 29 Jul 2024 19:15:51 +0530 Subject: [PATCH 13/23] fix(nestjsx-crud): handle empty filters (#6160) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ali Emir Şen --- .changeset/itchy-houses-provide.md | 9 +++++++++ packages/nestjsx-crud/src/utils/handleFilter.ts | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 .changeset/itchy-houses-provide.md diff --git a/.changeset/itchy-houses-provide.md b/.changeset/itchy-houses-provide.md new file mode 100644 index 000000000000..c3081b601071 --- /dev/null +++ b/.changeset/itchy-houses-provide.md @@ -0,0 +1,9 @@ +--- +"@refinedev/nestjsx-crud": patch +--- + +fix: update empty filter checks + +Updated the conditions to properly check empty filters + +[Fixes #6146](https://github.com/refinedev/refine/issues/6146) diff --git a/packages/nestjsx-crud/src/utils/handleFilter.ts b/packages/nestjsx-crud/src/utils/handleFilter.ts index 2f650865009f..4f466b5c4c29 100644 --- a/packages/nestjsx-crud/src/utils/handleFilter.ts +++ b/packages/nestjsx-crud/src/utils/handleFilter.ts @@ -36,7 +36,7 @@ export const handleFilter = ( query: RequestQueryBuilder, filters?: CrudFilters, ) => { - if (filters) { + if (Array.isArray(filters) && filters.length > 0) { query.search(generateSearchFilter(filters)); } return query; From 5a8e94aa4afe0faf3ea1de93a4b00e0b44dd1ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Emir=20=C5=9Een?= Date: Tue, 30 Jul 2024 01:35:44 +0300 Subject: [PATCH 14/23] fix(auth-page): replace wrong translation keys (#6199) --- .changeset/popular-bees-smash.md | 23 ++++++++++++++++ .changeset/warm-pots-relax.md | 7 +++++ .../auth/components/forgotPassword/index.tsx | 12 ++++++--- .../pages/auth/components/register/index.tsx | 27 ++++++++++++++----- .../auth/components/forgotPassword/index.tsx | 19 +++++++++---- .../pages/auth/components/register/index.tsx | 21 ++++++++++----- .../auth/components/forgotPassword/index.tsx | 7 +++-- .../pages/auth/components/register/index.tsx | 5 +++- .../auth/components/forgotPassword/index.tsx | 12 ++++++--- .../pages/auth/components/register/index.tsx | 18 ++++++++++--- 10 files changed, 122 insertions(+), 29 deletions(-) create mode 100644 .changeset/popular-bees-smash.md create mode 100644 .changeset/warm-pots-relax.md diff --git a/.changeset/popular-bees-smash.md b/.changeset/popular-bees-smash.md new file mode 100644 index 000000000000..207e07171d80 --- /dev/null +++ b/.changeset/popular-bees-smash.md @@ -0,0 +1,23 @@ +--- +"@refinedev/chakra-ui": patch +"@refinedev/mantine": patch +"@refinedev/antd": patch +"@refinedev/mui": patch +--- + +fix(auth-page): fix wrong translation keys in `type="register"` and `type="forgotPassword"` + +In `type="forgotPassword"`: + +- `"pages.register.buttons.haveAccount"` is replaced with `"pages.forgotPassword.buttons.haveAccount"` +- `"pages.login.signin"` is replaced with `"pages.forgotPassword.signin"` + +In `type="register"`: + +- `"pages.login.divider"` is replaced with `"pages.register.divider"` +- `"pages.login.buttons.haveAccount"` is replaced with `"pages.register.buttons.haveAccount"` +- `"pages.login.signin"` is replaced with `"pages.register.signin"` + +Wrong keys are kept as fallbacks in case the new keys are not found in the translation file. If you are using those keys in your project, make sure to update them accordingly. Fallback keys will be removed in future releases. + +[Resolves #5816](https://github.com/refinedev/refine/issues/5816) diff --git a/.changeset/warm-pots-relax.md b/.changeset/warm-pots-relax.md new file mode 100644 index 000000000000..a9bf7d8d34c6 --- /dev/null +++ b/.changeset/warm-pots-relax.md @@ -0,0 +1,7 @@ +--- +"@refinedev/chakra-ui": patch +--- + +fix(auth-page): fix wrong translation key in `type="register"` + +Previously, sign in link in Register page was using wrong translation key "pages.register.buttons.noAccount". Now it is replaced with "pages.register.buttons.haveAccount". diff --git a/packages/antd/src/components/pages/auth/components/forgotPassword/index.tsx b/packages/antd/src/components/pages/auth/components/forgotPassword/index.tsx index a603f7658369..410869b89ae6 100644 --- a/packages/antd/src/components/pages/auth/components/forgotPassword/index.tsx +++ b/packages/antd/src/components/pages/auth/components/forgotPassword/index.tsx @@ -151,8 +151,11 @@ export const ForgotPasswordPage: React.FC = ({ }} > {translate( - "pages.register.buttons.haveAccount", - "Have an account? ", + "pages.forgotPassword.buttons.haveAccount", + translate( + "pages.register.buttons.haveAccount", + "Have an account? ", + ), )}{" "} = ({ }} to="/login" > - {translate("pages.login.signin", "Sign in")} + {translate( + "pages.forgotPassword.signin", + translate("pages.login.signin", "Sign in"), + )} )} diff --git a/packages/antd/src/components/pages/auth/components/register/index.tsx b/packages/antd/src/components/pages/auth/components/register/index.tsx index c6e92d2b8a0f..b36c9d84b9b5 100644 --- a/packages/antd/src/components/pages/auth/components/register/index.tsx +++ b/packages/antd/src/components/pages/auth/components/register/index.tsx @@ -123,7 +123,10 @@ export const RegisterPage: React.FC = ({ color: token.colorTextLabel, }} > - {translate("pages.login.divider", "or")} + {translate( + "pages.register.divider", + translate("pages.login.divider", "or"), + )} )} @@ -208,8 +211,11 @@ export const RegisterPage: React.FC = ({ }} > {translate( - "pages.login.buttons.haveAccount", - "Have an account?", + "pages.register.buttons.haveAccount", + translate( + "pages.login.buttons.haveAccount", + "Have an account?", + ), )}{" "} = ({ }} to="/login" > - {translate("pages.login.signin", "Sign in")} + {translate( + "pages.register.signin", + translate("pages.login.signin", "Sign in"), + )} )} @@ -251,7 +260,10 @@ export const RegisterPage: React.FC = ({ fontSize: 12, }} > - {translate("pages.login.buttons.haveAccount", "Have an account?")}{" "} + {translate( + "pages.register.buttons.haveAccount", + translate("pages.login.buttons.haveAccount", "Have an account?"), + )}{" "} = ({ }} to="/login" > - {translate("pages.login.signin", "Sign in")} + {translate( + "pages.register.signin", + translate("pages.login.signin", "Sign in"), + )}
diff --git a/packages/chakra-ui/src/components/pages/auth/components/forgotPassword/index.tsx b/packages/chakra-ui/src/components/pages/auth/components/forgotPassword/index.tsx index c6838aa940d1..a5d12b4cc6a6 100644 --- a/packages/chakra-ui/src/components/pages/auth/components/forgotPassword/index.tsx +++ b/packages/chakra-ui/src/components/pages/auth/components/forgotPassword/index.tsx @@ -117,8 +117,11 @@ export const ForgotPasswordPage: React.FC = ({ pattern: { value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, message: translate( - "pages.login.errors.validEmail", - "Invalid email address", + "pages.forgotPassword.errors.validEmail", + translate( + "pages.login.errors.validEmail", + "Invalid email address", + ), ), }, })} @@ -130,12 +133,18 @@ export const ForgotPasswordPage: React.FC = ({ {translate( - "pages.register.buttons.haveAccount", - "Have an account?", + "pages.forgotPassword.buttons.haveAccount", + translate( + "pages.register.buttons.haveAccount", + "Have an account?", + ), )} - {translate("pages.login.signin", "Sign in")} + {translate( + "pages.forgotPassword.signin", + translate("pages.login.signin", "Sign in"), + )} )} diff --git a/packages/chakra-ui/src/components/pages/auth/components/register/index.tsx b/packages/chakra-ui/src/components/pages/auth/components/register/index.tsx index ce52fb89ee70..69c489d79dce 100644 --- a/packages/chakra-ui/src/components/pages/auth/components/register/index.tsx +++ b/packages/chakra-ui/src/components/pages/auth/components/register/index.tsx @@ -195,8 +195,11 @@ export const RegisterPage: React.FC = ({ > {translate( - "pages.login.buttons.haveAccount", - "Have an account?", + "pages.register.buttons.haveAccount", + translate( + "pages.login.buttons.haveAccount", + "Have an account?", + ), )} = ({ as={Link} to="/login" > - {translate("pages.login.signin", "Sign in")} + {translate( + "pages.register.signin", + translate("pages.login.signin", "Sign in"), + )} )} @@ -217,8 +223,8 @@ export const RegisterPage: React.FC = ({ {translate( - "pages.login.buttons.noAccount", - "Don’t have an account?", + "pages.register.buttons.haveAccount", + "Have an account?", )} = ({ fontWeight="bold" to="/login" > - {translate("pages.login.signin", "Sign in")} + {translate( + "pages.register.signin", + translate("pages.login.signin", "Sign in"), + )} )} diff --git a/packages/mantine/src/components/pages/auth/components/forgotPassword/index.tsx b/packages/mantine/src/components/pages/auth/components/forgotPassword/index.tsx index 7cfbb3a9a163..1b720a768e39 100644 --- a/packages/mantine/src/components/pages/auth/components/forgotPassword/index.tsx +++ b/packages/mantine/src/components/pages/auth/components/forgotPassword/index.tsx @@ -122,8 +122,11 @@ export const ForgotPasswordPage: React.FC = ({ {translate( - "pages.login.forgotPassword.haveAccount", - "Have an account?", + "pages.forgotPassword.buttons.haveAccount", + translate( + "pages.login.forgotPassword.haveAccount", + "Have an account? ", + ), )}{" "} {translate("pages.forgotPassword.signin", "Sign in")} diff --git a/packages/mantine/src/components/pages/auth/components/register/index.tsx b/packages/mantine/src/components/pages/auth/components/register/index.tsx index 6f44f1a46491..efb9d3a49d6c 100644 --- a/packages/mantine/src/components/pages/auth/components/register/index.tsx +++ b/packages/mantine/src/components/pages/auth/components/register/index.tsx @@ -123,7 +123,10 @@ export const RegisterPage: React.FC = ({ )} diff --git a/packages/mui/src/components/pages/auth/components/forgotPassword/index.tsx b/packages/mui/src/components/pages/auth/components/forgotPassword/index.tsx index 73ea8bd9be96..af25b487f103 100644 --- a/packages/mui/src/components/pages/auth/components/forgotPassword/index.tsx +++ b/packages/mui/src/components/pages/auth/components/forgotPassword/index.tsx @@ -140,8 +140,11 @@ export const ForgotPasswordPage: React.FC = ({ {translate( - "pages.register.buttons.haveAccount", - "Have an account?", + "pages.forgotPassword.buttons.haveAccount", + translate( + "pages.register.buttons.haveAccount", + "Have an account? ", + ), )} {" "} = ({ fontSize="12px" color="primary.light" > - {translate("pages.login.signin", "Sign in")} + {translate( + "pages.forgotPassword.signin", + translate("pages.login.signin", "Sign in"), + )} )} diff --git a/packages/mui/src/components/pages/auth/components/register/index.tsx b/packages/mui/src/components/pages/auth/components/register/index.tsx index 664264a094cb..3a69faa8563d 100644 --- a/packages/mui/src/components/pages/auth/components/register/index.tsx +++ b/packages/mui/src/components/pages/auth/components/register/index.tsx @@ -131,7 +131,10 @@ export const RegisterPage: React.FC = ({ marginY: "16px", }} > - {translate("pages.login.divider", "or")} + {translate( + "pages.register.divider", + translate("pages.login.divider", "or"), + )} )} @@ -238,7 +241,13 @@ export const RegisterPage: React.FC = ({ }} > - {translate("pages.login.buttons.haveAccount", "Have an account?")} + {translate( + "pages.register.buttons.haveAccount", + translate( + "pages.login.buttons.haveAccount", + "Have an account?", + ), + )} = ({ fontSize="12px" fontWeight="bold" > - {translate("pages.login.signin", "Sign in")} + {translate( + "pages.register.signin", + translate("pages.login.signin", "Sign in"), + )} )} From e2b467528f6a799c3219e3a8fefd4834a0ca0431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Emir=20=C5=9Een?= Date: Tue, 30 Jul 2024 01:38:35 +0300 Subject: [PATCH 15/23] feat(cli): add example translation file to i18n provider template (#6196) --- .changeset/quick-rats-eat.md | 11 ++ .../sub-commands/provider/create-providers.ts | 58 +++++- .../cli/templates/assets/i18n/locale/en.json | 174 ++++++++++++++++++ .../public/locales/en/common.json | 6 +- 4 files changed, 245 insertions(+), 4 deletions(-) create mode 100644 .changeset/quick-rats-eat.md create mode 100644 packages/cli/templates/assets/i18n/locale/en.json diff --git a/.changeset/quick-rats-eat.md b/.changeset/quick-rats-eat.md new file mode 100644 index 000000000000..2085f2fded76 --- /dev/null +++ b/.changeset/quick-rats-eat.md @@ -0,0 +1,11 @@ +--- +"@refinedev/cli": patch +--- + +feat(cli): create base translation files for i18n provider in add provider command + +Currently `refine add provider i18n` command is only creating a demo i18n provider implementation but misses the translation files. This PR adds the base translation files for the i18n provider which is used by Refine internally in hooks, notifications and components. + +Now `locale/en.json` will be added with primarily used translation keys and values for the i18n provider. + +[Resolves #5918](https://github.com/refinedev/refine/issues/5918) diff --git a/packages/cli/src/commands/add/sub-commands/provider/create-providers.ts b/packages/cli/src/commands/add/sub-commands/provider/create-providers.ts index a1c2d9c27506..71cdc3024e05 100644 --- a/packages/cli/src/commands/add/sub-commands/provider/create-providers.ts +++ b/packages/cli/src/commands/add/sub-commands/provider/create-providers.ts @@ -1,7 +1,13 @@ import { getProjectType } from "@utils/project"; -import { getProviderPath } from "@utils/resource"; -import { copySync, mkdirSync, pathExistsSync } from "fs-extra"; -import { join } from "path"; +import { getFilesPathByProject, getProviderPath } from "@utils/resource"; +import { + copySync, + existsSync, + mkdirSync, + pathExistsSync, + readdirSync, +} from "fs-extra"; +import { join, relative } from "path"; import { availableProviders, type Provider, @@ -9,6 +15,7 @@ import { } from "./providers"; const baseTemplatePath = `${__dirname}/../templates/provider`; +const baseAssetsPath = `${__dirname}/../templates/assets`; export const createProviders = ( providerIds: ProviderId[], @@ -16,9 +23,11 @@ export const createProviders = ( ) => { providerIds.forEach((providerId) => { const { fileName, templateFileName } = getProviderOptions(providerId); + const projectFilesBasePath = getFilesPathByProject(getProjectType()); const folderPath = pathFromArgs ?? getDefaultPath(); const filePath = join(folderPath, fileName); const fullPath = join(process.cwd(), folderPath, fileName); + const projectFilesPath = join(process.cwd(), projectFilesBasePath); if (pathExistsSync(fullPath)) { console.error(`❌ Provider (${filePath}) already exist!`); @@ -32,6 +41,49 @@ export const createProviders = ( copySync(`${baseTemplatePath}/${templateFileName}`, fullPath); console.log(`🎉 Provider (${filePath}) created successfully!`); + + const copiedAssets: string[] = []; + const failedAssets: string[] = []; + + if (pathExistsSync(`${baseAssetsPath}/${providerId}`)) { + try { + readdirSync(`${baseAssetsPath}/${providerId}`, { + recursive: true, + withFileTypes: true, + }).forEach((file) => { + if (!file.isDirectory()) { + const fromPath = join(file.path, file.name); + const destinationPath = join( + projectFilesPath, + relative(join(baseAssetsPath, providerId), file.path), + file.name, + ); + const relativeDestinationPath = join( + projectFilesBasePath, + relative(projectFilesPath, destinationPath), + ); + + if (existsSync(destinationPath)) { + failedAssets.push(relativeDestinationPath); + } else { + try { + copySync(fromPath, destinationPath); + copiedAssets.push(relativeDestinationPath); + } catch (error) { + failedAssets.push(relativeDestinationPath); + } + } + } + }); + } catch (error) {} + } + + if (copiedAssets.length) { + console.log(`🎉 Created additional assets: ${copiedAssets.join(", ")}`); + } + if (failedAssets.length) { + console.error(`❌ Failed to create assets: ${failedAssets.join(", ")}`); + } }); }; diff --git a/packages/cli/templates/assets/i18n/locale/en.json b/packages/cli/templates/assets/i18n/locale/en.json new file mode 100644 index 000000000000..ba17fb597de4 --- /dev/null +++ b/packages/cli/templates/assets/i18n/locale/en.json @@ -0,0 +1,174 @@ +{ + "pages": { + "login": { + "__description": "These keys are used by the components.", + "title": "Sign in to your account", + "signin": "Sign in", + "signup": "Sign up", + "divider": "or", + "fields": { + "email": "Email", + "password": "Password" + }, + "errors": { + "validEmail": "Invalid email address", + "requiredEmail": "Email is required", + "requiredPassword": "Password is required" + }, + "buttons": { + "submit": "Login", + "forgotPassword": "Forgot password?", + "noAccount": "Don’t have an account?", + "rememberMe": "Remember me" + } + }, + "forgotPassword": { + "__description": "These key are used by the components.", + "title": "Forgot your password?", + "signin": "Sign in", + "fields": { + "email": "Email" + }, + "errors": { + "validEmail": "Invalid email address", + "requiredEmail": "Email is required" + }, + "buttons": { + "submit": "Send reset instructions", + "haveAccount": "Have an account?" + } + }, + "register": { + "__description": "These keys are used by the components.", + "title": "Sign up for your account", + "signin": "Sign in", + "divider": "or", + "fields": { + "email": "Email", + "password": "Password" + }, + "errors": { + "validEmail": "Invalid email address", + "requiredEmail": "Email is required", + "requiredPassword": "Password is required" + }, + "buttons": { + "submit": "Register", + "haveAccount": "Have an account?" + } + }, + "updatePassword": { + "__description": "These keys are used by the components.", + "title": "Update password", + "fields": { + "password": "New Password", + "confirmPassword": "Confirm new password" + }, + "errors": { + "confirmPasswordNotMatch": "Passwords do not match", + "requiredPassword": "Password required", + "requiredConfirmPassword": "Confirm password is required" + }, + "buttons": { + "submit": "Update" + } + }, + "error": { + "__description": "These keys are used by the components.", + "info": "You may have forgotten to add the {{action}} component to {{resource}} resource.", + "404": "Sorry, the page you visited does not exist.", + "resource404": "Are you sure you have created the {{resource}} resource.", + "backHome": "Back Home" + } + }, + "actions": { + "__description": "These keys are used by the @refinedev/kbar package.", + "list": "List", + "create": "Create", + "edit": "Edit", + "show": "Show" + }, + "buttons": { + "__description": "These keys are used by the buttons and breadcrumbs.", + "create": "Create", + "save": "Save", + "logout": "Logout", + "delete": "Delete", + "edit": "Edit", + "cancel": "Cancel", + "confirm": "Are you sure?", + "filter": "Filter", + "clear": "Clear", + "refresh": "Refresh", + "show": "Show", + "undo": "Undo", + "import": "Import", + "clone": "Clone", + "notAccessTitle": "You don't have permission to access" + }, + "warnWhenUnsavedChanges": "Are you sure you want to leave? You have unsaved changes.", + "notifications": { + "__description": "These keys are used by the notification provider.", + "success": "Successful", + "error": "Error (status code: {{statusCode}})", + "undoable": "You have {{seconds}} seconds to undo", + "createSuccess": "Successfully created {{resource}}", + "createError": "There was an error creating {{resource}} (status code: {{statusCode}})", + "deleteSuccess": "Successfully deleted {{resource}}", + "deleteError": "Error when deleting {{resource}} (status code: {{statusCode}})", + "editSuccess": "Successfully edited {{resource}}", + "editError": "Error when editing {{resource}} (status code: {{statusCode}})", + "importProgress": "Importing: {{processed}}/{{total}}" + }, + "loading": "Loading", + "dashboard": { + "__description": "This key is used by components to display the dashboard item in sidebar.", + "title": "Dashboard" + }, + "posts": { + "__description": "These keys are used in translations for the 'posts' resource.", + "posts": "Posts", + "fields": { + "id": "Id", + "title": "Title", + "category": "Category", + "status": { + "title": "Status", + "published": "Published", + "draft": "Draft", + "rejected": "Rejected" + }, + "content": "Content", + "createdAt": "Created At" + }, + "titles": { + "create": "Create Post", + "edit": "Edit Post", + "list": "Posts", + "show": "Show Post" + } + }, + "table": { + "__description": "This key is used by Inferencer components in 'list' views.", + "actions": "Actions" + }, + "documentTitle": { + "__description": "These keys are used by the components.", + "default": "refine", + "suffix": " | Refine", + "post": { + "list": "Posts | Refine", + "show": "#{{id}} Show Post | Refine", + "edit": "#{{id}} Edit Post | Refine", + "create": "Create new Post | Refine", + "clone": "#{{id}} Clone Post | Refine" + } + }, + "autoSave": { + "__description": "These keys are used by the and the auto-save feature.", + "success": "saved", + "error": "auto save failure", + "loading": "saving...", + "idle": "waiting for changes" + } +} diff --git a/packages/live-previews/public/locales/en/common.json b/packages/live-previews/public/locales/en/common.json index c0c9995c9282..48db44d759ca 100644 --- a/packages/live-previews/public/locales/en/common.json +++ b/packages/live-previews/public/locales/en/common.json @@ -23,6 +23,7 @@ }, "forgotPassword": { "title": "Forgot your password?", + "signin": "Sign in", "fields": { "email": "Email" }, @@ -31,11 +32,14 @@ "requiredEmail": "Email is required" }, "buttons": { - "submit": "Send reset instructions" + "submit": "Send reset instructions", + "haveAccount": "Have an account?" } }, "register": { "title": "Sign up for your account", + "signin": "Sign in", + "divider": "or", "fields": { "email": "Email", "password": "Password" From 399ff2f9b785a1eebf59235fe6a5663d8b943ad3 Mon Sep 17 00:00:00 2001 From: Sergio Rene Tapia-Fikes <47071119+Sergio16T@users.noreply.github.com> Date: Tue, 30 Jul 2024 07:33:52 -0500 Subject: [PATCH 16/23] feat(supabase): add support for between in dataProvider. maps values to gte & lte. (#6206) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ali Emir Şen --- .changeset/hot-cougars-dream.md | 7 +++++++ packages/supabase/src/utils/generateFilter.ts | 9 +++++++++ 2 files changed, 16 insertions(+) create mode 100644 .changeset/hot-cougars-dream.md diff --git a/.changeset/hot-cougars-dream.md b/.changeset/hot-cougars-dream.md new file mode 100644 index 000000000000..0dba6d21ea30 --- /dev/null +++ b/.changeset/hot-cougars-dream.md @@ -0,0 +1,7 @@ +--- +"@refinedev/supabase": patch +--- + +feat: added support for between filter in supabase dataProvider. Maps values to gte & lte. + +[Feat #6119](https://github.com/refinedev/refine/issues/6119) diff --git a/packages/supabase/src/utils/generateFilter.ts b/packages/supabase/src/utils/generateFilter.ts index 9c43d9e3ce33..e1f96ca00010 100644 --- a/packages/supabase/src/utils/generateFilter.ts +++ b/packages/supabase/src/utils/generateFilter.ts @@ -26,6 +26,15 @@ export const generateFilter = (filter: CrudFilter, query: any) => { return query.lt(filter.field, filter.value); case "lte": return query.lte(filter.field, filter.value); + case "between": + if (filter.value.length !== 2) { + throw new Error( + `[@refinedev/supabase]: Unexpected length ${filter.value.length}. Between operator expects a range between 2 values.`, + ); + } + return query + .gte(filter.field, filter.value[0]) + .lte(filter.field, filter.value[1]); case "contains": return query.ilike(filter.field, `%${filter.value}%`); case "containss": From 261021c2710e2163e93d79b09ebb1e9a10db372f Mon Sep 17 00:00:00 2001 From: Alican Erdurmaz Date: Tue, 30 Jul 2024 15:34:58 +0300 Subject: [PATCH 17/23] feat(devtools): improve onboarding page design (#6202) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ali Emir Şen --- .changeset/odd-rats-travel.md | 9 ++ packages/devtools-ui/package.json | 1 + .../src/components/add-package-drawer.tsx | 5 +- .../src/components/add-package.item.tsx | 6 +- .../devtools-ui/src/components/button.tsx | 44 ++++++- packages/devtools-ui/src/components/input.tsx | 43 ++++++- .../src/components/monitor-filters.tsx | 10 +- .../devtools-ui/src/components/packages.tsx | 12 +- packages/devtools-ui/src/pages/onboarding.tsx | 116 +++++++++++++----- pnpm-lock.yaml | 45 +++---- 10 files changed, 216 insertions(+), 75 deletions(-) create mode 100644 .changeset/odd-rats-travel.md diff --git a/.changeset/odd-rats-travel.md b/.changeset/odd-rats-travel.md new file mode 100644 index 000000000000..5b91a7efd66d --- /dev/null +++ b/.changeset/odd-rats-travel.md @@ -0,0 +1,9 @@ +--- +"@refinedev/devtools-ui": patch +--- + +feat: Onboarding page of the DevTools improved. + +- Loading animation added. +- Client-side form validation added. +- Error handling improved. diff --git a/packages/devtools-ui/package.json b/packages/devtools-ui/package.json index 5d60e7311dac..d5b46b7ccbb2 100644 --- a/packages/devtools-ui/package.json +++ b/packages/devtools-ui/package.json @@ -55,6 +55,7 @@ "lodash-es": "^4.17.21", "prism-react-renderer": "^1.3.5", "react-gravatar": "^2.6.3", + "react-hook-form": "^7.43.5", "react-json-view-lite": "^1.3.0", "react-router-dom": "^6.8.1", "semver-diff": "^3.1.1" diff --git a/packages/devtools-ui/src/components/add-package-drawer.tsx b/packages/devtools-ui/src/components/add-package-drawer.tsx index 386405144d14..ceb85b323495 100644 --- a/packages/devtools-ui/src/components/add-package-drawer.tsx +++ b/packages/devtools-ui/src/components/add-package-drawer.tsx @@ -338,14 +338,13 @@ export const AddPackageDrawer = ({ > ); }, diff --git a/packages/devtools-ui/src/components/input.tsx b/packages/devtools-ui/src/components/input.tsx index ae408c9214c7..9b9a507e525b 100644 --- a/packages/devtools-ui/src/components/input.tsx +++ b/packages/devtools-ui/src/components/input.tsx @@ -8,6 +8,10 @@ type Props = { value?: string; onChange?: (value: string) => void; className?: string; + onBlur?: React.FocusEventHandler | undefined; + error?: string; + disabled?: boolean; + loading?: boolean; }; export const Input = ({ @@ -17,9 +21,13 @@ export const Input = ({ value, onChange, className, + disabled, + error, + onBlur, + loading, }: Props) => { return ( -