From cc5f119970852f24145afc54ae37f088ee0e8d59 Mon Sep 17 00:00:00 2001 From: Timi Duban Date: Tue, 20 Jul 2021 17:12:29 +0200 Subject: [PATCH] crud page v1 --- package.json | 1 + scripts/link-vulcan.sh | 2 +- src/pages/_app.tsx | 5 +- src/pages/admin/crud/VulcanUser.tsx | 235 +++++++++++++++++++++++++++ src/pages/admin/crud/[modelName].tsx | 143 ++++++++++++++++ src/pages/admin/crud/index.tsx | 22 +++ yarn.lock | 114 ++++++++++++- 7 files changed, 518 insertions(+), 4 deletions(-) create mode 100644 src/pages/admin/crud/VulcanUser.tsx create mode 100644 src/pages/admin/crud/[modelName].tsx create mode 100644 src/pages/admin/crud/index.tsx diff --git a/package.json b/package.json index 43535a5c..35257799 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "@vulcanjs/meteor-legacy": "^0.1.8", "@vulcanjs/mongo": "^0.1.8", "@vulcanjs/react-hooks": "^0.1.8", + "@vulcanjs/react-ui": "^0.2.1", "apollo-server-express": "2.14.2", "babel-jest": "26.0.1", "babel-plugin-istanbul": "6.0.0", diff --git a/scripts/link-vulcan.sh b/scripts/link-vulcan.sh index 8d8bfc9b..1e381b57 100755 --- a/scripts/link-vulcan.sh +++ b/scripts/link-vulcan.sh @@ -18,7 +18,7 @@ ./node_modules/.bin/yalc link @vulcanjs/mongo ./node_modules/.bin/yalc link @vulcanjs/multi-env-demo ./node_modules/.bin/yalc link @vulcanjs/permissions -./node_modules/.bin/yalc link @vulcanjs/react-components +./node_modules/.bin/yalc link @vulcanjs/react-ui ./node_modules/.bin/yalc link @vulcanjs/react-hooks ./node_modules/.bin/yalc link @vulcanjs/schema ./node_modules/.bin/yalc link @vulcanjs/utils \ No newline at end of file diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 9ab11504..79313a9e 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -5,6 +5,7 @@ import { appWithTranslation } from "~/lib/i18n"; import { useMuiApp } from "@vulcanjs/next-material-ui"; import { MuiThemeProvider } from "~/components/providers"; import Head from "next/head"; +import { VulcanComponentsProvider } from "@vulcanjs/react-ui"; import debug from "debug"; import AppLayout from "~/components/layout/AppLayout"; @@ -30,7 +31,7 @@ function VNApp({ Component, pageProps }: AppProps) { !!process.env.NEXT_PUBLIC_CROSS_DOMAIN_GRAPHQL_URI || false, }); // you can also easily setup ApolloProvider on a per-page basis return ( - <> + Vulcan Next - + ); } diff --git a/src/pages/admin/crud/VulcanUser.tsx b/src/pages/admin/crud/VulcanUser.tsx new file mode 100644 index 00000000..01b09b11 --- /dev/null +++ b/src/pages/admin/crud/VulcanUser.tsx @@ -0,0 +1,235 @@ +import React from "react"; +import Router from "next/router"; +import { Button, FormControl, InputLabel, MenuItem, Select } from "@material-ui/core"; + +import { User } from "~/models/user"; +import { useUser } from "~/components/user/hooks"; +import { + useCreate, + useMulti, + useUpdate, + useDelete, +} from "@vulcanjs/react-hooks"; + + +export default function UserCrudPage() { + // Auth + const user = useUser({ redirectTo: "/login" }); + + // Create + /* + + Before creating a user with useCreate, we need to modify the password fields. That's what is done by the vulcan-next API, so we use the it here. + You can see below this page an example with useCreate that will throw because of the password field. + + */ + async function handleCreateSubmit(e) { + e.preventDefault(); + + const body = { + email: e.currentTarget.email.value, + password: e.currentTarget.password.value, + isAdmin: e.currentTarget.isAdmin.checked || null, + groups: e.currentTarget.groups.value || null, + }; + + try { + const res = await fetch("/api/signup", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + if (!(res.status === 200)) { + throw new Error(await res.text()); + } + Router.reload(); // reload to refresh the users list because useMulti doesn't update after user operations + } catch (error) { + console.error("An unexpected error happened occurred:", error); + } + } + // Read + const readFields = ['_id', 'createdAt', 'email', 'username', 'isAdmin', 'groups']; + const rawResult = useMulti({ model: User }); + const usersResult = rawResult.documents || []; + + // Select + const [selectedUserId, setSelectedUserId] = React.useState(''); + const selectedUser = usersResult.find(user => user._id === selectedUserId); + const handleSelectChange = (event) => { + setSelectedUserId(event.target.value); + }; + + // Update + const [updateUser] = useUpdate({ model: User }); + async function handleUpdateSubmit(e) { + e.preventDefault(); + + if (!selectedUserId) { + throw new Error("Trying to update without userId, this shouldn't happen"); + } + + const input = { + id: selectedUserId, + data: { + email: e.currentTarget.updateEmail.value || selectedUser.email, + username: e.currentTarget.updateUsername.value || null, + isAdmin: e.currentTarget.updateIsAdmin.checked || null, + groups: e.currentTarget.updateGroups.value || null + } + }; + + await updateUser({ input }); + Router.reload(); // reload to refresh the users list because useMulti doesn't update after user operations + } + + // Delete + const [deleteUser] = useDelete({ model: User }); + const handleDelete = async () => { + if (!selectedUserId) { + throw new Error("Trying to delete without id, this shouldn't happen"); + } + const id = selectedUserId; + setSelectedUserId('') + const input = { + id + }; + await deleteUser({ input }); + Router.reload(); // reload to refresh the users list because useMulti doesn't update after user operations + } + + return ( +
+

User Crud page

+

Create

+
+ + + + + +
+ + +

Read

+ {} + +
+ +

Choose the user

+ + + User + + + + + + {selectedUserId && ( +
+

Update {selectedUserId}

+
+ + + + + +
+ +

Delete

+ +
+ )} + +
+ ); +}; + + +// useCreate example +/* + +const [createUser] = useCreate({ model: User }); +async function handleCreateSubmit(e) { + e.preventDefault(); + + const input = { + data: { + email: e.currentTarget.email.value, + password: e.currentTarget.password.value, + isAdmin: e.currentTarget.isAdmin.value || null, + groups: e.currentTarget.groups.value || null, + } + }; + + await createUser({ input }); +} + +*/ \ No newline at end of file diff --git a/src/pages/admin/crud/[modelName].tsx b/src/pages/admin/crud/[modelName].tsx new file mode 100644 index 00000000..ba2f041a --- /dev/null +++ b/src/pages/admin/crud/[modelName].tsx @@ -0,0 +1,143 @@ +import React from "react"; +import { GetStaticPropsContext } from 'next' +import { VulcanGraphqlModel } from "@vulcanjs/graphql"; +import { Button, FormControl, InputLabel, MenuItem, Select } from "@material-ui/core"; +import { SmartForm } from "@vulcanjs/react-ui" +import models from "~/models"; + +import { getReadableFields } from "@vulcanjs/schema"; +import { useUser } from "~/components/user/hooks"; +import { + useMulti, + useDelete +} from "@vulcanjs/react-hooks"; + +export default function CrudPage({ modelName }) { + // Auth + const user = useUser({ redirectTo: "/login" }); + + // Model + const getCurrentModel = (modelName: string) => { + const model = models.find(model => model.name === modelName); + if (!model) { // Shouldn't be reachable thanks to getStaticPaths + throw new Error( + "No model found with this name, please use the index if you don't know the model name." + ); + } + return model; + } + const model: VulcanGraphqlModel = getCurrentModel(modelName); + + // Read + const readFields = getReadableFields(model.schema); + const documentsRawResult = useMulti({ model }); + const documentsResult = documentsRawResult.documents || [] + + // Select + const [selectedDocumentId, setSelectedDocumentId] = React.useState(''); + const handleSelectDocumentChange = (event) => { + setSelectedDocumentId(event.target.value); + }; + + // Delete + const [deleteDocument] = useDelete({ model }); + const handleDelete = async () => { + if (!selectedDocumentId) { + throw new Error("Trying to delete without id, this shouldn't happen"); + } + const id = selectedDocumentId; + setSelectedDocumentId('') + const input = { + id + }; + await deleteDocument({ input }); + } + + return ( +
+

{model.name} Crud page

+

Create

+ + +

Read

+ {
    + {documentsResult.map((result) => ( +
  • + {readFields.map((field) => ( + result[field] + ?

    {field}: {result[field]}

    + : null + ))} +
  • + ))} +
} + +
+ +

Choose your document

+ + + Document + + + + + {selectedDocumentId && ( +
+

Update

+ + +

Delete

+ + +
+ )} +
+ ); +}; + + +interface PathsProps { + params: { modelName: string } +} + +export async function getStaticPaths() { + const paths = spreadPaths(); + + return { + paths, + fallback: false + }; +} +function spreadPaths(): Array { + const paths: Array = []; + models.forEach(model => { + paths.push({ params: { modelName: model.name } }); + }); + return paths; +} + +export async function getStaticProps(context: GetStaticPropsContext) { + return { props: { modelName: context.params?.modelName } }; +} \ No newline at end of file diff --git a/src/pages/admin/crud/index.tsx b/src/pages/admin/crud/index.tsx new file mode 100644 index 00000000..a272b3e7 --- /dev/null +++ b/src/pages/admin/crud/index.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import models from "~/models"; +import Link from 'next/link'; + +const ModelsPage = () => { + return ( +
+

Admin page

+ +
+ ); +}; + +export default ModelsPage; diff --git a/yarn.lock b/yarn.lock index 236f8b0e..de1634c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3991,6 +3991,15 @@ debug "^4.3.1" lodash "^4.17.21" +"@vulcanjs/core@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@vulcanjs/core/-/core-0.2.0.tgz#8023f68e2056cd99f40853c3dda849d12e8e1e78" + integrity sha512-stNQAbs0slSvikXSpYh3yPEmHgdCsLPEkMUYVdDkDZyYwtu53ioWzxfPaUtxBu0oYz0rUyImyfQrkdcUJIS6UA== + dependencies: + "@vulcanjs/utils" "^0.2.0" + debug "^4.3.1" + lodash "^4.17.21" + "@vulcanjs/demo@^0.0.7": version "0.0.7" resolved "https://registry.yarnpkg.com/@vulcanjs/demo/-/demo-0.0.7.tgz#4a57f286ee856beb20fb262aae291e2571265982" @@ -4014,6 +4023,24 @@ graphql-type-json "^0.3.2" lodash "^4.17.21" +"@vulcanjs/graphql@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@vulcanjs/graphql/-/graphql-0.2.0.tgz#daa2a94754a9c21277a47a3d10d454353e677edc" + integrity sha512-WvlqthkVU89CAveEtP+d/BMQXNusQEX+vknxpmopbOxMDKrPcCqnPAAElZqQGZW6oHeA9sPXEI9M2H6eQRa50w== + dependencies: + "@vulcanjs/core" "^0.2.0" + "@vulcanjs/i18n" "^0.2.0" + "@vulcanjs/model" "^0.2.0" + "@vulcanjs/permissions" "^0.2.0" + "@vulcanjs/schema" "^0.2.0" + "@vulcanjs/utils" "^0.2.0" + apollo-server "^2.18.2" + debug "^4.3.1" + deepmerge "^4.2.2" + graphql-date "^1.0.3" + graphql-type-json "^0.3.2" + lodash "^4.17.21" + "@vulcanjs/i18n@^0.1.13": version "0.1.13" resolved "https://registry.yarnpkg.com/@vulcanjs/i18n/-/i18n-0.1.13.tgz#bfb3f0b9f8ff879e39f02a0d5298dea40c59056b" @@ -4024,6 +4051,16 @@ prop-types "^15.7.2" simpl-schema "^1.12.0" +"@vulcanjs/i18n@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@vulcanjs/i18n/-/i18n-0.2.0.tgz#59f4883eafd0c65db402fc5ed1797ec2c3184dcc" + integrity sha512-fNqyyEaLJZAR8xO7BQWa5ul4vbW8wZIzLwrimys8slNJMPi77SfZBB2t8dO1UsvGQ82rg4MePt6wBoxt499ZWA== + dependencies: + "@vulcanjs/schema" "^0.2.0" + "@vulcanjs/utils" "^0.2.0" + prop-types "^15.7.2" + simpl-schema "^1.12.0" + "@vulcanjs/mdx@^0.2.3-alpha.0": version "0.2.3-alpha.0" resolved "https://registry.yarnpkg.com/@vulcanjs/mdx/-/mdx-0.2.3-alpha.0.tgz#fb85eb1ef378f0748860a08a94775da84d93b23b" @@ -4044,6 +4081,13 @@ dependencies: "@vulcanjs/schema" "^0.1.13" +"@vulcanjs/model@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@vulcanjs/model/-/model-0.2.0.tgz#fd1f17a2ee5c59d174a029262c0505f6d8e52399" + integrity sha512-9zixFTdT0UT05QwB/qInj7hCo91dyFhEdszGeqFa+yrA54uftVO+2Onjsn2W3qpxp9Zgnd2GpOxDhHxjfGBO9w== + dependencies: + "@vulcanjs/schema" "^0.2.0" + "@vulcanjs/mongo@^0.1.13", "@vulcanjs/mongo@^0.1.8": version "0.1.13" resolved "https://registry.yarnpkg.com/@vulcanjs/mongo/-/mongo-0.1.13.tgz#e69a215be351851ef080dd6447a42829c38007b6" @@ -4055,6 +4099,17 @@ escape-string-regexp "4.0.0" lodash "^4.17.20" +"@vulcanjs/mongo@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@vulcanjs/mongo/-/mongo-0.2.0.tgz#103307d4eeb7c5bf943eac2e30a1dfecb6ad8f72" + integrity sha512-bVEx8uO2R0hR7tlTcY6yzF4WmQFqg71zRRHSZujo/P+q2+RPVKb/h1RqlgSmM0I69LNH4WdzIBfkQjF3zvi08w== + dependencies: + "@vulcanjs/graphql" "^0.2.0" + "@vulcanjs/model" "^0.2.0" + "@vulcanjs/utils" "^0.2.0" + escape-string-regexp "4.0.0" + lodash "^4.17.20" + "@vulcanjs/permissions@^0.1.13": version "0.1.13" resolved "https://registry.yarnpkg.com/@vulcanjs/permissions/-/permissions-0.1.13.tgz#1580e2fb9a655f83e04e69877693a22bb685525b" @@ -4064,6 +4119,15 @@ "@vulcanjs/schema" "^0.1.13" lodash "^4.17.21" +"@vulcanjs/permissions@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@vulcanjs/permissions/-/permissions-0.2.0.tgz#0bb02a55939c4641a89b446b109cad04060938a5" + integrity sha512-Wsq761DgnjCSFJwV6haRDSKKaEdnM1zcx0zypcGf1WcTRjEQ2yC+z9C+RVsdRCwg7oxiI8TARROfgsnOHT1tlQ== + dependencies: + "@vulcanjs/model" "^0.2.0" + "@vulcanjs/schema" "^0.2.0" + lodash "^4.17.21" + "@vulcanjs/react-hooks@^0.1.8": version "0.1.13" resolved "https://registry.yarnpkg.com/@vulcanjs/react-hooks/-/react-hooks-0.1.13.tgz#62c9df86459424fa9a922721be0885fb62e84b35" @@ -4076,6 +4140,36 @@ lodash "^4.17.20" mingo "^3.0.6" +"@vulcanjs/react-hooks@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@vulcanjs/react-hooks/-/react-hooks-0.2.1.tgz#dadde936f0d5d0f964f9baed7f390ceb3abaf2bc" + integrity sha512-IzywZ7tKMFSvISBd87OyOPrWw5JD4ZXypKDHNrD9vMYo2tsrK6vBAumMe6Ozv1XD4qCXaZSBv8xqniGIyjQh6A== + dependencies: + "@vulcanjs/graphql" "^0.2.0" + "@vulcanjs/mongo" "^0.2.0" + debug "^4.3.1" + graphql "^15.5.0" + lodash "^4.17.20" + mingo "^3.0.6" + +"@vulcanjs/react-ui@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@vulcanjs/react-ui/-/react-ui-0.2.1.tgz#1b2ecff28235862db8352758f4707b90b2c8a9e0" + integrity sha512-E1A114cAB5SR1yzbNlRjcx3PYrlzLmCe0LSgaAUinKe0Zk+FdyS4lZHtrW1lr3Qq3p4hSEPXzFCSYUlZhpF9BQ== + dependencies: + "@vulcanjs/core" "^0.2.0" + "@vulcanjs/graphql" "^0.2.0" + "@vulcanjs/i18n" "^0.2.0" + "@vulcanjs/model" "^0.2.0" + "@vulcanjs/permissions" "^0.2.0" + "@vulcanjs/react-hooks" "^0.2.1" + "@vulcanjs/schema" "^0.2.0" + "@vulcanjs/utils" "^0.2.0" + classnames "^2.3.1" + debug "^4.3.1" + lodash "^4.17.21" + simpl-schema "^1.12.0" + "@vulcanjs/schema@^0.1.13": version "0.1.13" resolved "https://registry.yarnpkg.com/@vulcanjs/schema/-/schema-0.1.13.tgz#b0f72cd9d58bbf4622b9062762f3784d26f5a5c6" @@ -4085,6 +4179,15 @@ moment-timezone "^0.5.33" simpl-schema "^1.12.0" +"@vulcanjs/schema@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@vulcanjs/schema/-/schema-0.2.0.tgz#24782306218eedc54baae05d563401e85656a06c" + integrity sha512-bGuaYTc3E7UcB87Bix+rDyehvNvIpDD5xrWLanY96XgooWFb6Z0pKn2Yi8J1E/V5/RUQl8jV2kSqHr+KHULMCw== + dependencies: + lodash "^4.17.21" + moment-timezone "^0.5.33" + simpl-schema "^1.12.0" + "@vulcanjs/utils@^0.1.13": version "0.1.13" resolved "https://registry.yarnpkg.com/@vulcanjs/utils/-/utils-0.1.13.tgz#6124c35078fbfbfef6ca0aca5c6f1ccf1dce7c0b" @@ -4093,6 +4196,15 @@ "@vulcanjs/model" "^0.1.13" lodash "^4.17.21" +"@vulcanjs/utils@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@vulcanjs/utils/-/utils-0.2.0.tgz#b98f2c7593d22f82cb185b3e926b830226383e7f" + integrity sha512-N1xCOY9rGkN3cffD1Q3MaxV2B7fzhxIyCtUblOCJp99MEfTgzGYNvpe2u/GZp0/3y9aoGYVtmac1os0+s1nZCw== + dependencies: + "@vulcanjs/model" "^0.2.0" + debug "^4.3.1" + lodash "^4.17.21" + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -6172,7 +6284,7 @@ classnames@2.2.6: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== -classnames@^2.2.5, classnames@^2.2.6: +classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==