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 (
+
+ );
+};
+
+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==