Compare commits

..

196 Commits

Author SHA1 Message Date
Szilárd Dóró
decb0b057c Merge pull request #1677 from nhost/changeset-release/main
chore: update versions
2023-02-28 11:39:26 +01:00
github-actions[bot]
fc79b890df chore: update versions 2023-02-28 10:06:25 +00:00
Szilárd Dóró
211eb42af5 Merge pull request #1622 from nhost/chore/improved-dialogs
chore(dashboard): improve Dialog and Drawer API
2023-02-28 11:05:03 +01:00
Szilárd Dóró
a7398451e3 fix(dashboard): add dirty state checking to user form 2023-02-28 10:47:42 +01:00
Szilárd Dóró
4b4f0d0150 chore(dashboard): add changeset 2023-02-28 10:31:47 +01:00
Szilárd Dóró
f37e2a23e2 Merge remote-tracking branch 'origin/main' into chore/improved-dialogs 2023-02-28 10:31:10 +01:00
Johan Eliasson
1a4a061284 Merge pull request #1674 from nhost/changeset-release/main
chore: update versions
2023-02-27 11:55:28 +01:00
github-actions[bot]
78555c7e85 chore: update versions 2023-02-27 08:22:19 +00:00
Johan Eliasson
01ded8ffff Merge pull request #1670 from nhost/functions-tests
Functions fix + tests
2023-02-27 09:21:05 +01:00
Johan Eliasson
3c7cf92edf Create .changeset/eighty-mugs-flash.md 2023-02-27 09:20:49 +01:00
Johan Eliasson
bb4301fd34 more tests 2023-02-26 17:49:19 +01:00
Szilárd Dóró
c8c8948755 Merge pull request #1667 from nhost/changeset-release/main
chore: update versions
2023-02-24 12:45:06 +01:00
github-actions[bot]
17e9e5899e chore: update versions 2023-02-24 11:44:31 +00:00
Szilárd Dóró
bd22c48131 Merge pull request #1666 from nhost/fix/workspace-invitation
fix(nhost-js): use correct URL for functions requests
2023-02-24 12:43:02 +01:00
Szilárd Dóró
89a239ff3a fix(nhost-js): improve code readability 2023-02-24 12:22:54 +01:00
Szilárd Dóró
0131886011 fix(nhost-js): use correct URL for functions requests 2023-02-24 12:06:30 +01:00
Szilárd Dóró
340c014fe8 Merge pull request #1664 from nhost/update-codeowners
chore: update codeowners
2023-02-24 11:06:07 +01:00
Szilárd Dóró
bc9c8b9456 chore: update codeowners 2023-02-24 11:05:35 +01:00
Nuno Pato
c22b2621ba Merge pull request #1661 from nhost/changeset-release/main
chore: update versions
2023-02-23 20:33:34 -01:00
github-actions[bot]
726746c4d3 chore: update versions 2023-02-23 19:19:03 +00:00
Nuno Pato
c431570783 Merge pull request #1662 from nhost/fix/file-upload
fix(hasura-storage-js): fix forbidden error
2023-02-23 18:17:47 -01:00
Szilárd Dóró
445d8ef449 fix(hasura-storage-js): fix forbidden error 2023-02-23 18:16:22 +01:00
Szilárd Dóró
0f4ea18e42 Merge pull request #1655 from nhost/feat/auth-storage-permissions
fix(dashboard): allow permission editing for auth and storage schemas
2023-02-23 15:22:05 +01:00
Szilárd Dóró
dae7c5d517 Merge pull request #1660 from nhost/fix/user-creation-content-type
fix(dashboard): set correct Content-Type for user creation
2023-02-23 14:38:16 +01:00
Szilárd Dóró
f673adea00 fix(dashboard): set correct Content-Type for user creation 2023-02-23 12:50:00 +01:00
Szilárd Dóró
1c6f1e3b33 Merge pull request #1656 from nhost/changeset-release/main
chore: update versions
2023-02-23 11:56:07 +01:00
github-actions[bot]
d1365ea516 chore: update versions 2023-02-23 10:20:53 +00:00
Szilárd Dóró
72dbba7881 Merge pull request #1659 from nhost/chore/revert-graphql-client
chore(graphql-js): revert GraphQL Client
2023-02-23 11:19:26 +01:00
Szilárd Dóró
a3f3991d5a Merge pull request #1658 from nhost/fix/user-creation
fix(dashboard): false positive message on user creation
2023-02-23 11:01:44 +01:00
Szilárd Dóró
c71fe2cf72 Revert "chore(graphql-js): revert GraphQL client for now"
This reverts commit 9a0ab5b887.
2023-02-23 10:15:02 +01:00
Szilárd Dóró
24c5ed3ea4 chore(graphql-js): cleanup 2023-02-23 10:14:43 +01:00
Szilárd Dóró
2d9145f918 chore(graphql-js): revert GraphQL client for now 2023-02-23 10:10:14 +01:00
Szilárd Dóró
9a0ab5b887 chore(graphql-js): revert GraphQL client for now 2023-02-23 10:06:58 +01:00
Szilárd Dóró
1ddf704c5b fix(dashboard): false positive message on user creation 2023-02-23 09:39:32 +01:00
Szilárd Dóró
6f4ee845c6 Merge pull request #1643 from nhost/fix/auth-last-seen
fix(dashboard): use correct date for last seen
2023-02-22 20:01:18 +01:00
Szilárd Dóró
0368663dea fix(dashboard): allow permission editing for auth and storage schemas
fixes #1555
2023-02-22 19:59:20 +01:00
Pilou
76ce7d7b6e Merge pull request #1653 from nhost/changeset-release/main
chore: update versions
2023-02-22 15:43:41 +01:00
github-actions[bot]
538bfbcb3e chore: update versions 2023-02-22 14:23:55 +00:00
Pilou
07b35d1e5f Merge pull request #1650 from nhost/docs/graphql-client-readme
GraphQL client: fix snake_case bug and improve readme
2023-02-22 15:22:34 +01:00
Pierre-Louis Mercereau
2200a0ed07 fix: correct types on snake case operations 2023-02-22 15:13:43 +01:00
Szilárd Dóró
df23d97126 Merge pull request #1652 from nhost/changeset-release/main
chore: update versions
2023-02-22 14:44:46 +01:00
github-actions[bot]
104f149369 chore: update versions 2023-02-22 13:26:08 +00:00
Szilárd Dóró
01228583a0 Merge pull request #1651 from nhost/fix/permission-editor-dropdown
fix(dashboard): prevent permission editor dropdown from being always open
2023-02-22 14:24:51 +01:00
Pierre-Louis Mercereau
93309dd851 docs: strictNullChecks 2023-02-22 14:06:10 +01:00
Szilárd Dóró
2cc18dcb51 fix(dashboard): prevent permission editor dropdown from being always open 2023-02-22 13:54:31 +01:00
Pierre-Louis Mercereau
3b48a62790 docs: ✏️ improve readme instructions 2023-02-22 13:44:15 +01:00
Pierre-Louis Mercereau
8897dec056 docs: add package.json script instructions 2023-02-22 12:23:54 +01:00
Pierre-Louis Mercereau
324dda8309 docs: streamline readme instructions 2023-02-22 11:58:51 +01:00
Pilou
95f62bed07 Merge pull request #1644 from nhost/changeset-release/main
chore: update versions
2023-02-22 08:50:38 +01:00
Pierre-Louis Mercereau
0e4d8ff118 chore: manually bump @nhost/react-apollo 2023-02-21 18:20:41 +01:00
github-actions[bot]
baec5bada7 chore: update versions 2023-02-21 17:03:03 +00:00
Pilou
4e56cfc628 Merge pull request #1348 from nhost/sdk-next-major
Sdk next major version
2023-02-21 18:01:13 +01:00
Pierre-Louis Mercereau
54bc91923f docs: update changeset 2023-02-21 12:40:52 +01:00
Szilárd Dóró
77b12feb95 Merge pull request #1639 from nhost/renovate/testing-library-react-14.x
chore(deps): update dependency @testing-library/react to v14
2023-02-20 16:30:11 +01:00
Pierre-Louis Mercereau
32d4670bbb Merge branch 'main' into sdk-next-major 2023-02-20 16:05:23 +01:00
Pilou
1dc09942d2 Merge pull request #1587 from nhost/next-client
Next GraphQL client
2023-02-20 16:01:49 +01:00
Szilárd Dóró
3343a36358 chore(deps): bump @testing-library 2023-02-20 15:52:18 +01:00
Szilárd Dóró
b755e9086c fix(dashboard): use correct date for last seen 2023-02-20 14:19:56 +01:00
Pierre-Louis Mercereau
48866d0ee1 chore: fail on runtime when passing an invalid field 2023-02-20 12:45:52 +01:00
renovate[bot]
5b3b76bd41 chore(deps): update dependency @testing-library/react to v14 2023-02-20 10:08:36 +00:00
Pierre-Louis Mercereau
284ef7e7f2 test: multiple where 2023-02-17 08:57:02 +01:00
Pierre-Louis Mercereau
6d5c202da9 changes 2023-02-17 08:44:48 +01:00
Szilárd Dóró
962563d6a0 chore(dashboard): cleanup 2023-02-16 16:51:40 +01:00
Szilárd Dóró
8bf58ba26b chore(dashboard): migrate remaining dialogs 2023-02-16 16:37:44 +01:00
Szilárd Dóró
0c175e7a11 chore(dashboard): migrate additional dialogs to the new API 2023-02-16 16:13:47 +01:00
Szilárd Dóró
70f2fbcfc2 chore(dashboard): partially migrate dialogs to new API 2023-02-16 15:59:35 +01:00
Szilárd Dóró
d2c4ad3260 chore(dashboard): cleanup dialog provider 2023-02-16 13:32:21 +01:00
Szilárd Dóró
a9ca2c2946 chore(dashboard): migrate drawers to use new API 2023-02-16 13:25:23 +01:00
Szilárd Dóró
d854dd74b1 chore(dashboard): improve dialog management 2023-02-16 12:54:21 +01:00
Pierre-Louis Mercereau
9342937440 readme 2023-02-15 13:39:24 +01:00
Pierre-Louis Mercereau
e89cd4e262 simple mutation example 2023-02-15 13:30:16 +01:00
Pierre-Louis Mercereau
a05438352b inline documentation 2023-02-15 13:28:04 +01:00
Pierre-Louis Mercereau
78437959bb dew 2023-02-15 13:08:20 +01:00
Pierre-Louis Mercereau
e1a7433adb docs: static doc for nhost.functions 2023-02-15 12:41:54 +01:00
Pierre-Louis Mercereau
e23cf74975 fix tests 2023-02-10 22:11:53 +01:00
Pierre-Louis Mercereau
a3d01c4fad fix lint config 2023-02-10 21:33:26 +01:00
Pierre-Louis Mercereau
4cdcef9ef5 fix broken links 2023-02-10 19:12:29 +01:00
Pierre-Louis Mercereau
df894ef7e2 upgrade typescript version 2023-02-10 15:51:59 +01:00
Pierre-Louis Mercereau
f7dd6a9fc6 deactivate functions doc 2023-02-10 14:42:56 +01:00
Pierre-Louis Mercereau
2949ff0f62 fix docgen 2023-02-10 14:33:56 +01:00
Pierre-Louis Mercereau
1527b0a455 remove type-fest dependency 2023-02-06 14:18:13 +01:00
Pierre-Louis Mercereau
375e53a3f0 update ts 2023-02-06 13:35:33 +01:00
Pierre-Louis Mercereau
96e3ca5a32 basic readme 2023-02-06 12:53:11 +01:00
Pierre-Louis Mercereau
0e570df9c5 missing dependency 2023-02-06 12:11:02 +01:00
Pierre-Louis Mercereau
1f4c67283e lockfile 2023-02-06 10:49:47 +01:00
Pierre-Louis Mercereau
fc1c4861a3 cleanup 2023-02-06 10:49:02 +01:00
Pierre-Louis Mercereau
74feaf6add unions 2023-02-02 11:47:29 +01:00
Pierre-Louis Mercereau
8cd97206cc Merge branch 'next-client' of https://github.com/nhost/nhost into next-client 2023-01-31 21:06:17 +01:00
Pierre-Louis Mercereau
02197639f2 dew 2023-01-31 20:59:14 +01:00
Pilou
38b594aef9 Update README.md 2023-01-31 19:40:31 +01:00
Pierre-Louis Mercereau
f3a8886cd0 dew 2023-01-31 19:08:19 +01:00
Pierre-Louis Mercereau
8d76cf8d40 simplify 2023-01-31 17:37:44 +01:00
Pierre-Louis Mercereau
3e1fb974e4 dew 2023-01-31 16:37:40 +01:00
Pierre-Louis Mercereau
f74871d872 dew 2023-01-31 15:01:54 +01:00
Pierre-Louis Mercereau
3f26056688 Merge branch 'main' into sdk-next-major 2023-01-30 11:11:51 +01:00
Pierre-Louis Mercereau
6a7801be93 chore: update changeset 2023-01-10 09:10:33 +01:00
Pierre-Louis Mercereau
7bc5bb857c chore: add the deprecated useAxios option 2023-01-10 09:02:45 +01:00
Pierre-Louis Mercereau
c957039d75 chore: update lock file 2023-01-09 20:17:12 +01:00
Pierre-Louis Mercereau
96c4032424 Merge branch 'main' into sdk-next-major 2023-01-09 20:13:13 +01:00
Pierre-Louis Mercereau
ec70126b56 fix: iso 8859-1 2023-01-09 19:25:02 +01:00
Pierre-Louis Mercereau
86b9f9040c Merge branch 'main' into sdk-next-major 2023-01-09 16:31:04 +01:00
Pierre-Louis Mercereau
222f03725b fix(dashboard): use fetch instead of axios 2023-01-06 16:42:40 +01:00
Pierre-Louis Mercereau
10b786e5c6 chore: fix lockfile 2023-01-06 16:30:22 +01:00
Pierre-Louis Mercereau
aa8ae88d12 Merge branch 'main' into sdk-next-major 2023-01-06 16:25:12 +01:00
Pilou
0f2c86b41a Merge pull request #1351 from nhost/refactor/cross-fetch
refactor: replace axios by cross-fetch
2023-01-06 16:17:03 +01:00
Pilou
a4c76892dd Update packages/hasura-storage-js/src/hasura-storage-api.ts
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2023-01-03 10:36:33 +01:00
Pierre-Louis Mercereau
00d278b2cc refactor: adjustments from review 2023-01-02 17:32:34 +01:00
Pilou
cb6b5faeb9 Update packages/hasura-storage-js/src/hasura-storage-client.ts
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2023-01-02 17:29:21 +01:00
Pierre-Louis Mercereau
7c4c847b91 Merge branch 'sdk-next-major' into refactor/cross-fetch 2022-12-26 12:54:40 +01:00
Pierre-Louis Mercereau
908887d8c5 Merge branch 'main' into sdk-next-major 2022-12-26 12:54:19 +01:00
Pierre-Louis Mercereau
a2d67bc2db fix(dashboard): js error from error payload 2022-12-26 12:45:33 +01:00
Pierre-Louis Mercereau
1a6cd78254 Merge branch 'sdk-next-major' into refactor/cross-fetch 2022-12-21 16:42:58 +01:00
Pierre-Louis Mercereau
6500629c4b Merge branch 'main' into sdk-next-major 2022-12-21 16:22:28 +01:00
Pierre-Louis Mercereau
add3c2c10e Merge branch 'sdk-next-major' into refactor/cross-fetch 2022-12-21 14:29:00 +01:00
Pierre-Louis Mercereau
dd29b06260 Merge branch 'main' into sdk-next-major 2022-12-21 14:27:48 +01:00
Pierre-Louis Mercereau
490cb25a0f Merge branch 'sdk-next-major' into refactor/cross-fetch 2022-12-20 10:13:18 +01:00
Pierre-Louis Mercereau
0df0dd741e Merge branch 'main' into sdk-next-major 2022-12-20 10:12:56 +01:00
Pierre-Louis Mercereau
2172946879 Merge branch 'sdk-next-major' into refactor/cross-fetch 2022-12-20 09:17:50 +01:00
Pierre-Louis Mercereau
40e50f0e75 Merge branch 'main' into sdk-next-major 2022-12-20 09:15:30 +01:00
Pierre-Louis Mercereau
65cf0888b5 chore: extract patch changes 2022-12-16 12:14:38 +01:00
Pierre-Louis Mercereau
21833019ca chore: correct error from several mergers 2022-12-14 21:14:48 +01:00
Pierre-Louis Mercereau
b3171ba3e9 chore: remove lockfile 2022-12-14 20:40:16 +01:00
Pierre-Louis Mercereau
6f01f19d02 refactor: remove docgen 2022-12-14 20:33:03 +01:00
Pierre-Louis Mercereau
ce92b01eac Merge branch 'main' into sdk-next-major 2022-12-14 19:25:15 +01:00
Pierre-Louis Mercereau
e24a177434 Merge branch 'main' into sdk-next-major 2022-12-14 19:06:50 +01:00
Pierre-Louis Mercereau
56a52b6d48 chore: remove duplicate changesets 2022-12-12 16:45:42 +01:00
Pierre-Louis Mercereau
92bfa8c723 chore: phrasing 2022-12-12 16:03:07 +01:00
Pierre-Louis Mercereau
2a52aaa4a6 Merge branch 'sdk-next-major' into refactor/cross-fetch 2022-12-12 14:46:39 +01:00
Pierre-Louis Mercereau
8280a3e9d8 chore: use explicit changeset links 2022-12-12 14:30:38 +01:00
Pierre-Louis Mercereau
523f60bf68 Merge branch 'sdk-next-major' into refactor/cross-fetch 2022-12-12 14:20:28 +01:00
Pierre-Louis Mercereau
19b11d4084 chore: details on removed deprecations 2022-12-12 14:15:08 +01:00
Pierre-Louis Mercereau
805bae1507 chore: bump nextjs as major as React removed deprecated methods are reexported 2022-12-12 13:36:25 +01:00
Pierre-Louis Mercereau
f6c014c06f chore: correct changeset 2022-12-12 13:34:19 +01:00
Pierre-Louis Mercereau
c5794f4596 chore: improve changesets 2022-12-12 13:32:09 +01:00
Pierre-Louis Mercereau
fc28817380 chore: explain xhr in changeset 2022-12-12 10:06:56 +01:00
Pierre-Louis Mercereau
80bbd3a165 chore: changesets 2022-12-12 10:02:21 +01:00
Pierre-Louis Mercereau
7a10617a72 test: simplify email verification test 2022-12-12 09:26:02 +01:00
Pierre-Louis Mercereau
f0b6dca1a5 Merge branch 'sdk-next-major' into refactor/cross-fetch 2022-12-08 14:28:03 +01:00
Szilárd Dóró
5db20adc34 fix(react): eliminate error when testing the project 2022-12-08 12:01:05 +01:00
Pierre-Louis Mercereau
12dc41a517 test: TypedDocumentNode 2022-12-08 09:07:46 +01:00
Pierre-Louis Mercereau
768fd56891 feat: graphql-typed-document-node 2022-12-07 21:53:21 +01:00
Pierre-Louis Mercereau
8a508cb1cc chore: run CI on evey pull request, not only the ones to main 2022-12-07 20:47:26 +01:00
Pierre-Louis Mercereau
34f6a8eef4 refactor: upload progress with XHR 2022-12-07 19:48:34 +01:00
Pierre-Louis Mercereau
c9d2d31a9b feat: nhost.graphql.httpUrl and nhost.graphql.wsUrl 2022-12-07 18:53:58 +01:00
Pierre-Louis Mercereau
68fb23a361 refactor: remove last pieces of axios 2022-12-07 17:16:42 +01:00
Pierre-Louis Mercereau
476139e528 refactor: remove axios from hasura-storage-js, with breaking change 2022-12-07 13:27:34 +01:00
Pierre-Louis Mercereau
6a850818a0 Merge branch 'sdk-next-major' into refactor/cross-fetch 2022-12-07 11:41:55 +01:00
Pilou
3970dbba0d Merge pull request #1342 from nhost/chore/improve-internal-dependencies
chore: improve internal dependencies
2022-12-07 11:40:02 +01:00
Pierre-Louis Mercereau
8ee2166f0d fix: correct nhost clients 2022-12-07 11:08:54 +01:00
Szilárd Dóró
e13500a185 fix(docgen): correct class references when used as interface members 2022-12-07 10:47:04 +01:00
Pierre-Louis Mercereau
411f574a51 refactor: re-export @nhost/react in @nhost/nextjs 2022-12-07 09:47:45 +01:00
Szilárd Dóró
7fc91b992e chore: cleanup dependencies, fix references 2022-12-07 08:59:06 +01:00
Pierre-Louis Mercereau
b840012be0 fix: correct fetch errors 2022-12-07 08:56:11 +01:00
Pierre-Louis Mercereau
645c51a9dc refactor: 💡 replace axios by cross-fetch, step 1
Changes not done yet in `nhost.auth.graphql` (will soon use
graphql-request instead) and the upload machines (depends on prrogress)
2022-12-06 22:08:46 +01:00
Pierre-Louis Mercereau
0ce6f05539 Merge branch 'sdk-next-major' into chore/improve-internal-dependencies 2022-12-06 14:10:19 +01:00
Pierre-Louis Mercereau
8b1188af53 Merge branch 'main' into sdk-next-major 2022-12-06 14:08:55 +01:00
Pilou
12b01f8dee Merge pull request #1298 from nhost/fix/react-strict-mode
fix: react strict mode
2022-12-06 14:06:07 +01:00
Pierre-Louis Mercereau
60f4faf409 chore: remove @nhost/core from changeset 2022-12-06 13:05:45 +01:00
Pierre-Louis Mercereau
528dff3f1b Merge branch 'sdk-next-major' into fix/react-strict-mode 2022-12-06 12:52:33 +01:00
Pierre-Louis Mercereau
d429fb4a3e Merge branch 'sdk-next-major' into chore/improve-internal-dependencies 2022-12-06 12:41:25 +01:00
Pilou
816c916709 Merge pull request #1233 from nhost/docs/hide-old-deprecations
chore: remove deprecated methods
2022-12-06 12:40:42 +01:00
Pierre-Louis Mercereau
b7a2b8b537 docs: update installation instructions 2022-12-06 10:50:40 +01:00
Pierre-Louis Mercereau
261d8cf434 merge 2022-12-06 10:10:13 +01:00
Pierre-Louis Mercereau
41f49bde76 Merge branch 'sdk-next-major' into chore/improve-internal-dependencies 2022-12-06 10:06:25 +01:00
Pilou
65f685bdb2 Update packages/core/src/client.ts
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2022-12-06 09:57:29 +01:00
Pilou
f52a7f4aac Update packages/core/src/machines/index.ts
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2022-12-06 09:57:01 +01:00
Pierre-Louis Mercereau
e71b9903d9 Merge branch 'sdk-next-major' into docs/hide-old-deprecations 2022-12-06 09:50:40 +01:00
Pilou
325fd08aef Merge pull request #1246 from nhost/refactor/merge-core
refactor: decommission `@nhost/core`
2022-12-06 09:46:50 +01:00
Pierre-Louis Mercereau
3888704464 chore: remove comment 2022-12-06 09:23:31 +01:00
Pierre-Louis Mercereau
38e8a10a29 chore: 🤖 remove lockfile 2022-12-06 09:19:02 +01:00
Pierre-Louis Mercereau
d8545eae12 chore: 🤖 add changeset 2022-12-06 09:18:16 +01:00
Pierre-Louis Mercereau
3d5bfd87d2 refactor: simplify 2022-12-06 08:59:11 +01:00
Pierre-Louis Mercereau
e66c5626bd fix: don't fetch session when present in initial state 2022-12-05 23:14:17 +01:00
Pierre-Louis Mercereau
a227c6561e refactor: work with nextjs 2022-12-05 23:09:26 +01:00
Pierre-Louis Mercereau
e885c159df refactor: simplify onStart 2022-12-05 14:53:10 +01:00
Pierre-Louis Mercereau
09fcb74bef refactor: use Johan's function name 2022-12-05 14:31:03 +01:00
Pierre-Louis Mercereau
a089197197 Merge branch 'main' into fix/react-strict-mode 2022-12-05 14:29:27 +01:00
Pierre-Louis Mercereau
34f843875b Merge branch 'main' into fix/react-strict-mode 2022-12-05 12:15:01 +01:00
Pierre-Louis Mercereau
ca278a8c39 Merge branch 'main' into fix/react-strict-mode 2022-12-05 11:54:01 +01:00
Pierre-Louis Mercereau
75603786e0 refactor: 💡 Remove unused immer dependency 2022-12-05 11:43:47 +01:00
Pierre-Louis Mercereau
4e4e699b94 refactor: adapt to nextjs 2022-12-05 11:39:36 +01:00
Pierre-Louis Mercereau
da31fa9fba ci: explicit pnpm run 2022-12-02 20:46:25 +01:00
Pierre-Louis Mercereau
95e2afaf47 Merge branch 'main' into fix/react-strict-mode 2022-12-02 15:46:13 +01:00
Pierre-Louis Mercereau
958a56dde9 fix: adapt to nextjs 2022-12-02 15:11:08 +01:00
Pierre-Louis Mercereau
74cb15930e Merge branch 'main' into fix/react-strict-mode 2022-12-02 13:35:53 +01:00
Pierre-Louis Mercereau
aa37a98424 Merge branch 'main' into fix/react-strict-mode 2022-12-02 13:27:37 +01:00
Pierre-Louis Mercereau
11cbdda3a5 Merge branch 'main' into fix/react-strict-mode 2022-12-02 13:20:55 +01:00
Pierre-Louis Mercereau
6d1f4adf10 chore: update changeset 2022-12-02 13:16:31 +01:00
Pierre-Louis Mercereau
ddbc50c15e refactor: avoid ambiguous interpreter getter/setter 2022-12-02 13:15:04 +01:00
Pierre-Louis Mercereau
b2cbf570a3 fix: 🐛 don't throw error when re-setting the interpreter 2022-12-02 12:08:52 +01:00
Pierre-Louis Mercereau
22b8e65031 fix: 🐛 onstart only when interpreter is initialised 2022-12-02 11:26:14 +01:00
Pierre-Louis Mercereau
63c94d2036 chore: fine-tune (peer) dependencies 2022-11-30 12:56:46 +01:00
Pierre-Louis Mercereau
010df48c1e chore: remove deprecations 2022-11-28 20:44:24 +01:00
Pierre-Louis Mercereau
fdc11db93d Merge branch 'main' into docs/hide-old-deprecations 2022-11-28 20:18:26 +01:00
Pierre-Louis Mercereau
cb4749f168 chore: fix lint 2022-11-28 15:53:54 +01:00
Pierre-Louis Mercereau
46a8fcf471 docs: hide > 6m.o. deprecations in the generated documentation 2022-11-28 15:34:09 +01:00
219 changed files with 2997 additions and 2208 deletions

18
.github/CODEOWNERS vendored
View File

@@ -1,14 +1,14 @@
# Documentation
# https://help.github.com/en/articles/about-code-owners
/packages @plmercereau @szilarddoro
/packages @szilarddoro
/packages/docgen @szilarddoro
/integrations/stripe-graphql-js @elitan
/.github @plmercereau
/dashboard/ @szilarddoro @guicurcio
/docs/ @guicurcio @elitan
/config/ @plmercereau @szilarddoro
/examples/ @plmercereau
/examples/codegen-react-apollo @elitan @plmercereau
/examples/codegen-react-query @elitan @plmercereau
/examples/react-apollo-crm @elitan @plmercereau
/.github @szilarddoro
/dashboard/ @szilarddoro
/docs/ @elitan
/config/ @szilarddoro
/examples/ @szilarddoro
/examples/codegen-react-apollo @elitan @szilarddoro
/examples/codegen-react-query @elitan @szilarddoro
/examples/react-apollo-crm @elitan @szilarddoro

View File

@@ -40,14 +40,14 @@ runs:
- shell: bash
name: Build packages
if: ${{ inputs.BUILD == 'all' }}
run: pnpm build:all
run: pnpm run build:all
env:
TURBO_TOKEN: ${{ inputs.TURBO_TOKEN }}
TURBO_TEAM: ${{ inputs.TURBO_TEAM }}
- shell: bash
name: Build everything in the monorepo
if: ${{ inputs.BUILD == 'default' }}
run: pnpm build
run: pnpm run build
env:
TURBO_TOKEN: ${{ inputs.TURBO_TOKEN }}
TURBO_TEAM: ${{ inputs.TURBO_TEAM }}

View File

@@ -8,7 +8,6 @@ on:
- '**.md'
- 'LICENSE'
pull_request:
branches: [main]
types: [opened, synchronize]
paths-ignore:
- 'assets/**'
@@ -56,7 +55,7 @@ jobs:
| xargs -I@ realpath --relative-to=$PWD @ \
| xargs -I@ jq "if (.scripts.e2e | length) != 0 then {name: .name, path: \"@\"} else null end" @/package.json \
| awk "!/null/" \
| jq -c --slurp)
| jq -c --slurp 'map(select(length > 0))')
echo "matrix=$PACKAGES" >> $GITHUB_OUTPUT
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}

View File

@@ -45,6 +45,9 @@
"@nhost/docgen": [
"../packages/docgen/src/index.ts"
],
"@nhost/graphql-js": [
"../packages/graphql-js/src/index.ts"
],
"@nhost/hasura-auth-js": [
"../packages/hasura-auth-js/src/index.ts"
],

View File

@@ -1,7 +1,6 @@
import replace from '@rollup/plugin-replace'
import fs from 'fs'
import path from 'path'
import { defineConfig } from 'vite'
import dts from 'vite-plugin-dts'
import tsconfigPaths from 'vite-tsconfig-paths'
@@ -61,7 +60,6 @@ export default defineConfig({
'@apollo/client/utilities': '@apollo/client/utilities',
'graphql-ws': 'graphql-ws',
xstate: 'xstate',
axios: 'axios',
'js-cookie': 'Cookies',
react: 'React',
'react-dom': 'ReactDOM',

View File

@@ -1,5 +1,71 @@
# @nhost/dashboard
## 0.11.20
### Patch Changes
- 4b4f0d01: chore(dashboard): improve dialog management
## 0.11.19
### Patch Changes
- @nhost/react-apollo@5.0.6
- @nhost/nextjs@1.13.11
## 0.11.18
### Patch Changes
- 01318860: fix(nhost-js): use correct URL for functions requests
- Updated dependencies [01318860]
- @nhost/react-apollo@5.0.5
- @nhost/nextjs@1.13.10
## 0.11.17
### Patch Changes
- f673adea: fix(dashboard): set correct Content-Type for user creation
- 445d8ef4: chore(deps): bump `@nhost/react-apollo` to 5.0.4
- 445d8ef4: chore(deps): bump `@nhost/nextjs` to 1.13.9
- 0368663d: fix(dashboard): allow permission editing for auth and storage schemas
- Updated dependencies [445d8ef4]
- Updated dependencies [445d8ef4]
- @nhost/react-apollo@5.0.4
- @nhost/nextjs@1.13.9
## 0.11.16
### Patch Changes
- b755e908: fix(dashboard): use correct date for last seen
- 2d9145f9: chore(deps): revert GraphQL client
- 1ddf704c: fix(dashboard): don't show false positive message for failed user creation
- @nhost/react-apollo@5.0.3
- @nhost/nextjs@1.13.8
## 0.11.15
### Patch Changes
- @nhost/react-apollo@5.0.2
- @nhost/nextjs@1.13.7
## 0.11.14
### Patch Changes
- 2cc18dcb: fix(dashboard): prevent permission editor dropdown from being always open
## 0.11.13
### Patch Changes
- 3343a363: chore(dashboard): bump `@testing-library/react` to v14 and `@testing-library/dom` to v9
- @nhost/react-apollo@5.0.1
- @nhost/nextjs@1.13.6
## 0.11.12
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "0.11.12",
"version": "0.11.20",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -44,7 +44,6 @@
"@tanstack/react-table": "^8.5.30",
"@tanstack/react-virtual": "^3.0.0-beta.23",
"analytics-node": "^6.2.0",
"axios": "^0.27.2",
"bcryptjs": "^2.4.3",
"clsx": "^1.2.1",
"cross-fetch": "^3.1.5",
@@ -98,9 +97,9 @@
"@storybook/manager-webpack5": "^6.5.14",
"@storybook/react": "^6.5.14",
"@storybook/testing-library": "^0.0.13",
"@testing-library/dom": "^8.19.0",
"@testing-library/dom": "^9.0.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/lodash.debounce": "^4.0.7",
"@types/node": "^16.11.7",

View File

@@ -52,7 +52,9 @@ function ControlledAutocomplete(
return (
<Autocomplete
inputValue={typeof field.value === 'string' ? field.value : undefined}
inputValue={
typeof field.value !== 'object' ? field.value.toString() : undefined
}
{...props}
{...field}
ref={mergeRefs([field.ref, ref])}

View File

@@ -1,31 +1,8 @@
import type { DialogFormProps } from '@/types/common';
import type { CommonDialogProps } from '@/ui/v2/Dialog';
import type { ReactNode } from 'react';
import type { ReactElement, ReactNode } from 'react';
import { createContext } from 'react';
/**
* Available dialog types.
*/
export type DialogType =
| 'EDIT_WORKSPACE_NAME'
| 'CREATE_RECORD'
| 'CREATE_COLUMN'
| 'EDIT_COLUMN'
| 'CREATE_TABLE'
| 'EDIT_TABLE'
| 'EDIT_PERMISSIONS'
| 'CREATE_FOREIGN_KEY'
| 'EDIT_FOREIGN_KEY'
| 'CREATE_ROLE'
| 'EDIT_ROLE'
| 'CREATE_USER'
| 'CREATE_PERMISSION_VARIABLE'
| 'EDIT_PERMISSION_VARIABLE'
| 'CREATE_ENVIRONMENT_VARIABLE'
| 'EDIT_ENVIRONMENT_VARIABLE'
| 'EDIT_USER'
| 'EDIT_USER_PASSWORD'
| 'EDIT_JWT_SECRET';
export interface DialogConfig<TPayload = unknown> {
/**
* Title of the dialog.
@@ -41,21 +18,36 @@ export interface DialogConfig<TPayload = unknown> {
payload?: TPayload;
}
export interface OpenDialogOptions {
/**
* Title of the dialog.
*/
title: ReactNode;
/**
* Component to render inside the dialog skeleton.
*/
component: ReactElement<{
location?: 'drawer' | 'dialog';
onCancel?: () => void;
onSubmit?: (args?: any) => Promise<any> | void;
}>;
/**
* Props to pass to the root dialog component.
*/
props?: Partial<CommonDialogProps>;
}
export interface DialogContextProps {
/**
* Call this function to open a dialog.
* Call this function to open a dialog. It will automatically apply the
* necessary functionality to the dialog.
*/
openDialog: <TPayload = unknown>(
type: DialogType,
config?: DialogConfig<TPayload>,
) => void;
openDialog: (options: OpenDialogOptions) => void;
/**
* Call this function to open a drawer.
* Call this function to open a drawer. It will automatically apply the
* necessary functionality to the drawer.
*/
openDrawer: <TPayload = unknown>(
type: DialogType,
config?: DialogConfig<TPayload>,
) => void;
openDrawer: (options: OpenDialogOptions) => void;
/**
* Call this function to open an alert dialog.
*/
@@ -87,7 +79,7 @@ export interface DialogContextProps {
*/
onDirtyStateChange: (
isDirty: boolean,
location?: 'drawer' | 'dialog',
location?: DialogFormProps['location'],
) => void;
/**
* Call this function to open a dirty confirmation dialog.

View File

@@ -1,30 +1,12 @@
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
import CreateForeignKeyForm from '@/components/dataBrowser/CreateForeignKeyForm';
import EditForeignKeyForm from '@/components/dataBrowser/EditForeignKeyForm';
import EditWorkspaceNameForm from '@/components/home/EditWorkspaceNameForm';
import CreateEnvironmentVariableForm from '@/components/settings/environmentVariables/CreateEnvironmentVariableForm';
import EditEnvironmentVariableForm from '@/components/settings/environmentVariables/EditEnvironmentVariableForm';
import EditJwtSecretForm from '@/components/settings/environmentVariables/EditJwtSecretForm';
import CreatePermissionVariableForm from '@/components/settings/permissions/CreatePermissionVariableForm';
import EditPermissionVariableForm from '@/components/settings/permissions/EditPermissionVariableForm';
import CreateRoleForm from '@/components/settings/roles/CreateRoleForm';
import EditRoleForm from '@/components/settings/roles/EditRoleForm';
import CreateUserForm from '@/components/users/CreateUserForm';
import EditUserForm from '@/components/users/EditUserForm';
import EditUserPasswordForm from '@/components/users/EditUserPasswordForm';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import AlertDialog from '@/ui/v2/AlertDialog';
import { BaseDialog } from '@/ui/v2/Dialog';
import Drawer from '@/ui/v2/Drawer';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/router';
import type {
BaseSyntheticEvent,
DetailedHTMLProps,
HTMLProps,
PropsWithChildren,
} from 'react';
import type { BaseSyntheticEvent, PropsWithChildren } from 'react';
import {
cloneElement,
isValidElement,
useCallback,
useEffect,
useMemo,
@@ -33,7 +15,7 @@ import {
useState,
} from 'react';
import { twMerge } from 'tailwind-merge';
import type { DialogConfig, DialogType } from './DialogContext';
import type { DialogConfig, OpenDialogOptions } from './DialogContext';
import DialogContext from './DialogContext';
import {
alertDialogReducer,
@@ -41,67 +23,11 @@ import {
drawerReducer,
} from './dialogReducers';
function LoadingComponent({
className,
...props
}: DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> = {}) {
return (
<div
{...props}
className={twMerge(
'grid items-center justify-center px-6 py-4',
className,
)}
>
<ActivityIndicator
circularProgressProps={{ className: 'w-5 h-5' }}
label="Loading form..."
/>
</div>
);
}
const CreateRecordForm = dynamic(
() => import('@/components/dataBrowser/CreateRecordForm'),
{ ssr: false, loading: () => LoadingComponent() },
);
const CreateColumnForm = dynamic(
() => import('@/components/dataBrowser/CreateColumnForm'),
{ ssr: false, loading: () => LoadingComponent() },
);
const EditColumnForm = dynamic(
() => import('@/components/dataBrowser/EditColumnForm'),
{ ssr: false, loading: () => LoadingComponent() },
);
const CreateTableForm = dynamic(
() => import('@/components/dataBrowser/CreateTableForm'),
{ ssr: false, loading: () => LoadingComponent() },
);
const EditTableForm = dynamic(
() => import('@/components/dataBrowser/EditTableForm'),
{ ssr: false, loading: () => LoadingComponent() },
);
const EditPermissionsForm = dynamic(
() => import('@/components/dataBrowser/EditPermissionsForm'),
{ ssr: false, loading: () => LoadingComponent() },
);
function DialogProvider({ children }: PropsWithChildren<unknown>) {
const router = useRouter();
const [
{
open: dialogOpen,
activeDialogType,
dialogProps,
title: dialogTitle,
payload: dialogPayload,
},
{ open: dialogOpen, title: dialogTitle, activeDialog, dialogProps },
dialogDispatch,
] = useReducer(dialogReducer, {
open: false,
@@ -110,10 +36,9 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
const [
{
open: drawerOpen,
activeDialogType: activeDrawerType,
dialogProps: drawerProps,
title: drawerTitle,
payload: drawerPayload,
activeDialog: activeDrawer,
dialogProps: drawerProps,
},
drawerDispatch,
] = useReducer(drawerReducer, {
@@ -136,12 +61,9 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
const isDialogDirty = useRef(false);
const [showDirtyConfirmation, setShowDirtyConfirmation] = useState(false);
const openDialog = useCallback(
<TConfig,>(type: DialogType, config?: DialogConfig<TConfig>) => {
dialogDispatch({ type: 'OPEN_DIALOG', payload: { type, config } });
},
[],
);
const openDialog = useCallback((options: OpenDialogOptions) => {
dialogDispatch({ type: 'OPEN_DIALOG', payload: options });
}, []);
const closeDialog = useCallback(() => {
dialogDispatch({ type: 'HIDE_DIALOG' });
@@ -152,12 +74,9 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
dialogDispatch({ type: 'CLEAR_DIALOG_CONTENT' });
}, []);
const openDrawer = useCallback(
<TConfig,>(type: DialogType, config?: DialogConfig<TConfig>) => {
drawerDispatch({ type: 'OPEN_DRAWER', payload: { type, config } });
},
[],
);
const openDrawer = useCallback((options: OpenDialogOptions) => {
drawerDispatch({ type: 'OPEN_DRAWER', payload: options });
}, []);
const closeDrawer = useCallback(() => {
drawerDispatch({ type: 'HIDE_DRAWER' });
@@ -228,9 +147,6 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
[closeDialog, openDirtyConfirmation],
);
// We are coupling this logic with the location of the dialog content which is
// not ideal. We shoule figure out a better logic for tracking the dirty
// state in the future.
const onDirtyStateChange = useCallback(
(dirty: boolean, location: 'drawer' | 'dialog' = 'drawer') => {
if (location === 'dialog') {
@@ -271,25 +187,6 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
],
);
const sharedDialogProps = {
...dialogPayload,
onSubmit: async (values: any) => {
await dialogPayload?.onSubmit?.(values);
closeDialog();
},
onCancel: closeDialogWithDirtyGuard,
};
const sharedDrawerProps = {
onSubmit: async () => {
await drawerPayload?.onSubmit();
closeDrawer();
},
onCancel: closeDrawerWithDirtyGuard,
};
useEffect(() => {
function handleCloseDrawerAndDialog() {
if (isDrawerDirty.current || isDialogDirty.current) {
@@ -367,56 +264,20 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
<RetryableErrorBoundary
errorMessageProps={{ className: 'pt-0 pb-5 px-6' }}
>
{activeDialogType === 'EDIT_WORKSPACE_NAME' && (
<EditWorkspaceNameForm {...sharedDialogProps} />
)}
{activeDialogType === 'CREATE_FOREIGN_KEY' && (
<CreateForeignKeyForm {...sharedDialogProps} />
)}
{activeDialogType === 'EDIT_FOREIGN_KEY' && (
<EditForeignKeyForm {...sharedDialogProps} />
)}
{activeDialogType === 'CREATE_ROLE' && (
<CreateRoleForm {...sharedDialogProps} />
)}
{activeDialogType === 'EDIT_ROLE' && (
<EditRoleForm {...sharedDialogProps} />
)}
{activeDialogType === 'CREATE_USER' && (
<CreateUserForm {...sharedDialogProps} />
)}
{activeDialogType === 'CREATE_PERMISSION_VARIABLE' && (
<CreatePermissionVariableForm {...sharedDialogProps} />
)}
{activeDialogType === 'EDIT_PERMISSION_VARIABLE' && (
<EditPermissionVariableForm {...sharedDialogProps} />
)}
{activeDialogType === 'CREATE_ENVIRONMENT_VARIABLE' && (
<CreateEnvironmentVariableForm {...sharedDialogProps} />
)}
{activeDialogType === 'EDIT_ENVIRONMENT_VARIABLE' && (
<EditEnvironmentVariableForm {...sharedDialogProps} />
)}
{activeDialogType === 'EDIT_USER_PASSWORD' && (
<EditUserPasswordForm
{...sharedDialogProps}
user={sharedDialogProps?.user}
/>
)}
{activeDialogType === 'EDIT_JWT_SECRET' && (
<EditJwtSecretForm {...sharedDialogProps} />
)}
{isValidElement(activeDialog)
? cloneElement(activeDialog, {
...activeDialog.props,
location: 'dialog',
onSubmit: async (values?: any) => {
await activeDialog?.props?.onSubmit?.(values);
closeDialog();
},
onCancel: () => {
activeDialog?.props?.onCancel?.();
closeDialogWithDirtyGuard();
},
})
: null}
</RetryableErrorBoundary>
</BaseDialog>
@@ -436,51 +297,20 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
}}
>
<RetryableErrorBoundary>
{activeDrawerType === 'CREATE_RECORD' && (
<CreateRecordForm
{...sharedDrawerProps}
columns={drawerPayload?.columns}
/>
)}
{activeDrawerType === 'CREATE_COLUMN' && (
<CreateColumnForm {...sharedDrawerProps} />
)}
{activeDrawerType === 'EDIT_COLUMN' && (
<EditColumnForm
{...sharedDrawerProps}
column={drawerPayload?.column}
/>
)}
{activeDrawerType === 'CREATE_TABLE' && (
<CreateTableForm
{...sharedDrawerProps}
schema={drawerPayload?.schema}
/>
)}
{activeDrawerType === 'EDIT_TABLE' && (
<EditTableForm
{...sharedDrawerProps}
table={drawerPayload?.table}
schema={drawerPayload?.schema}
/>
)}
{activeDrawerType === 'EDIT_PERMISSIONS' && (
<EditPermissionsForm
{...sharedDrawerProps}
disabled={drawerPayload?.disabled}
schema={drawerPayload?.schema}
table={drawerPayload?.table}
/>
)}
{activeDrawerType === 'EDIT_USER' && (
<EditUserForm {...sharedDrawerProps} {...drawerPayload} />
)}
{isValidElement(activeDrawer)
? cloneElement(activeDrawer, {
...activeDrawer.props,
location: 'drawer',
onSubmit: async (values?: any) => {
await activeDrawer?.props?.onSubmit?.(values);
closeDrawer();
},
onCancel: () => {
activeDrawer?.props?.onCancel?.();
closeDrawerWithDirtyGuard();
},
})
: null}
</RetryableErrorBoundary>
</Drawer>

View File

@@ -1,6 +1,6 @@
import type { CommonDialogProps } from '@/ui/v2/Dialog';
import type { ReactNode } from 'react';
import type { DialogConfig, DialogType } from './DialogContext';
import type { ReactElement, ReactNode } from 'react';
import type { DialogConfig, OpenDialogOptions } from './DialogContext';
export interface DialogState {
/**
@@ -12,9 +12,13 @@ export interface DialogState {
*/
open?: boolean;
/**
* Type of the currently active dialog.
* Component to render inside the dialog skeleton.
*/
activeDialogType?: DialogType;
activeDialog?: ReactElement<{
location?: 'drawer' | 'dialog';
onCancel?: () => void;
onSubmit?: (args?: any) => Promise<any> | void;
}>;
/**
* Props passed to the currently active dialog.
*/
@@ -27,10 +31,7 @@ export interface DialogState {
}
export type DialogAction =
| {
type: 'OPEN_DIALOG';
payload: { type: DialogType; config?: DialogConfig };
}
| { type: 'OPEN_DIALOG'; payload: OpenDialogOptions }
| { type: 'HIDE_DIALOG' }
| { type: 'CLEAR_DIALOG_CONTENT' };
@@ -50,10 +51,9 @@ export function dialogReducer(
return {
...state,
open: true,
activeDialogType: action.payload?.type,
dialogProps: action.payload.config?.props,
title: action.payload.config?.title,
payload: action.payload.config?.payload,
title: action.payload.title,
activeDialog: action.payload.component,
dialogProps: action.payload.props,
};
case 'HIDE_DIALOG':
return {
@@ -64,8 +64,7 @@ export function dialogReducer(
return {
...state,
title: undefined,
payload: undefined,
activeDialogType: undefined,
activeDialog: undefined,
dialogProps: undefined,
};
default:
@@ -74,10 +73,7 @@ export function dialogReducer(
}
export type DrawerAction =
| {
type: 'OPEN_DRAWER';
payload: { type: DialogType; config?: DialogConfig };
}
| { type: 'OPEN_DRAWER'; payload: OpenDialogOptions }
| { type: 'HIDE_DRAWER' }
| { type: 'CLEAR_DRAWER_CONTENT' };
@@ -97,10 +93,9 @@ export function drawerReducer(
return {
...state,
open: true,
activeDialogType: action.payload?.type,
dialogProps: action.payload.config?.props,
title: action.payload.config?.title,
payload: action.payload.config?.payload,
title: action.payload.title,
activeDialog: action.payload.component,
dialogProps: action.payload.props,
};
case 'HIDE_DRAWER':
return {
@@ -111,8 +106,7 @@ export function drawerReducer(
return {
...state,
title: undefined,
payload: undefined,
activeDialogType: undefined,
activeDialog: undefined,
dialogProps: undefined,
};
default:

View File

@@ -0,0 +1,26 @@
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import type { BoxProps } from '@/ui/v2/Box';
import Box from '@/ui/v2/Box';
import { twMerge } from 'tailwind-merge';
export interface FormActivityIndicatorProps extends BoxProps {}
export default function FormActivityIndicator({
className,
...props
}: FormActivityIndicatorProps) {
return (
<Box
{...props}
className={twMerge(
'grid items-center justify-center px-6 py-4',
className,
)}
>
<ActivityIndicator
circularProgressProps={{ className: 'w-5 h-5' }}
label="Loading form..."
/>
</Box>
);
}

View File

@@ -0,0 +1,2 @@
export * from './FormActivityIndicator';
export { default } from './FormActivityIndicator';

View File

@@ -3,6 +3,7 @@ import ControlledCheckbox from '@/components/common/ControlledCheckbox';
import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form';
import InlineCode from '@/components/common/InlineCode';
import type { DialogFormProps } from '@/types/common';
import type { ColumnType, DatabaseColumn } from '@/types/dataBrowser';
import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button';
@@ -22,7 +23,7 @@ import ForeignKeyEditor from './ForeignKeyEditor';
export type BaseColumnFormValues = DatabaseColumn;
export interface BaseColumnFormProps {
export interface BaseColumnFormProps extends DialogFormProps {
/**
* Function to be called when the form is submitted.
*/
@@ -60,6 +61,7 @@ export default function BaseColumnForm({
onSubmit: handleExternalSubmit,
onCancel,
submitButtonText = 'Save',
location,
}: BaseColumnFormProps) {
const { onDirtyStateChange } = useDialog();
@@ -91,8 +93,8 @@ export default function BaseColumnForm({
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, 'drawer');
}, [isDirty, onDirtyStateChange]);
onDirtyStateChange(isDirty, location);
}, [isDirty, location, onDirtyStateChange]);
return (
<Form

View File

@@ -1,5 +1,6 @@
import { useDialog } from '@/components/common/DialogProvider';
import type { BaseForeignKeyFormValues } from '@/components/dataBrowser/BaseForeignKeyForm';
import CreateForeignKeyForm from '@/components/dataBrowser/CreateForeignKeyForm';
import EditForeignKeyForm from '@/components/dataBrowser/EditForeignKeyForm';
import type { DatabaseColumn } from '@/types/dataBrowser';
import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button';
@@ -29,7 +30,7 @@ const ForeignKeyEditorInput = forwardRef(
) => {
const { openDialog } = useDialog();
const { setValue } = useFormContext();
const column = useWatch<Partial<DatabaseColumn>>();
const column = useWatch() as DatabaseColumn;
const { foreignKeyRelation } = column;
if (!column.foreignKeyRelation) {
@@ -39,8 +40,8 @@ const ForeignKeyEditorInput = forwardRef(
className="py-1"
disabled={!column.name || !column.type}
ref={ref}
onClick={() =>
openDialog('CREATE_FOREIGN_KEY', {
onClick={() => {
openDialog({
title: (
<span className="grid grid-flow-row">
<span>Add a Foreign Key Relation</span>
@@ -51,16 +52,18 @@ const ForeignKeyEditorInput = forwardRef(
</Text>
</span>
),
payload: {
selectedColumn: column.name,
availableColumns: [column],
onSubmit: (values: BaseForeignKeyFormValues) => {
setValue('foreignKeyRelation', values);
onCreateSubmit();
},
},
})
}
component: (
<CreateForeignKeyForm
selectedColumn={column.name}
availableColumns={[column]}
onSubmit={(values) => {
setValue('foreignKeyRelation', values);
onCreateSubmit();
}}
/>
),
});
}}
>
Add Foreign Key
</Button>
@@ -86,20 +89,22 @@ const ForeignKeyEditorInput = forwardRef(
<div className="grid grid-flow-col">
<Button
ref={ref}
onClick={() =>
openDialog('EDIT_FOREIGN_KEY', {
onClick={() => {
openDialog({
title: 'Edit Foreign Key Relation',
payload: {
foreignKeyRelation,
availableColumns: [column],
selectedColumn: column.name,
onSubmit: (values: BaseForeignKeyFormValues) => {
setValue('foreignKeyRelation', values);
onEditSubmit();
},
},
})
}
component: (
<EditForeignKeyForm
foreignKeyRelation={foreignKeyRelation}
selectedColumn={column.name}
availableColumns={[column]}
onSubmit={(values) => {
setValue('foreignKeyRelation', values);
onEditSubmit();
}}
/>
),
});
}}
variant="borderless"
className="min-w-[initial] py-1 px-2"
>

View File

@@ -2,6 +2,7 @@ import ControlledSelect from '@/components/common/ControlledSelect';
import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form';
import useDatabaseQuery from '@/hooks/dataBrowser/useDatabaseQuery';
import type { DialogFormProps } from '@/types/common';
import type { DatabaseColumn, ForeignKeyRelation } from '@/types/dataBrowser';
import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button';
@@ -23,7 +24,7 @@ export interface BaseForeignKeyFormValues extends ForeignKeyRelation {
disableOriginColumn?: boolean;
}
export interface BaseForeignKeyFormProps {
export interface BaseForeignKeyFormProps extends DialogFormProps {
/**
* Available columns in the table.
*/
@@ -64,6 +65,7 @@ export function BaseForeignKeyForm({
onSubmit: handleExternalSubmit,
onCancel,
submitButtonText = 'Save',
location,
}: BaseForeignKeyFormProps) {
const { onDirtyStateChange } = useDialog();
@@ -86,8 +88,8 @@ export function BaseForeignKeyForm({
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, 'dialog');
}, [isDirty, onDirtyStateChange]);
onDirtyStateChange(isDirty, location);
}, [isDirty, location, onDirtyStateChange]);
return (
<Form

View File

@@ -1,6 +1,7 @@
import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form';
import DatabaseRecordInputGroup from '@/components/dataBrowser/DatabaseRecordInputGroup';
import type { DialogFormProps } from '@/types/common';
import type {
ColumnInsertOptions,
DataBrowserGridColumn,
@@ -10,7 +11,7 @@ import Button from '@/ui/v2/Button';
import { useEffect } from 'react';
import { useFormContext } from 'react-hook-form';
export interface BaseRecordFormProps {
export interface BaseRecordFormProps extends DialogFormProps {
/**
* The columns of the table.
*/
@@ -36,6 +37,7 @@ export default function BaseRecordForm({
onSubmit: handleExternalSubmit,
onCancel,
submitButtonText = 'Save',
location,
}: BaseRecordFormProps) {
const { onDirtyStateChange } = useDialog();
const { requiredColumns, optionalColumns } = columns.reduce(
@@ -70,8 +72,8 @@ export default function BaseRecordForm({
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, 'drawer');
}, [isDirty, onDirtyStateChange]);
onDirtyStateChange(isDirty, location);
}, [isDirty, location, onDirtyStateChange]);
// Stores columns in a map to have constant time lookup. This is necessary
// for tables with many columns.

View File

@@ -1,6 +1,7 @@
import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form';
import { baseColumnValidationSchema } from '@/components/dataBrowser/BaseColumnForm';
import type { DialogFormProps } from '@/types/common';
import type { DatabaseTable, ForeignKeyRelation } from '@/types/dataBrowser';
import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button';
@@ -30,7 +31,7 @@ export interface BaseTableFormValues
foreignKeyRelations?: ForeignKeyRelation[];
}
export interface BaseTableFormProps {
export interface BaseTableFormProps extends DialogFormProps {
/**
* Function to be called when the form is submitted.
*/
@@ -99,7 +100,9 @@ function NameInput() {
function FormFooter({
onCancel,
submitButtonText,
}: Pick<BaseTableFormProps, 'onCancel' | 'submitButtonText'>) {
location,
}: Pick<BaseTableFormProps, 'onCancel' | 'submitButtonText'> &
Pick<DialogFormProps, 'location'>) {
const { onDirtyStateChange } = useDialog();
const { isSubmitting, dirtyFields } = useFormState();
@@ -108,8 +111,8 @@ function FormFooter({
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, 'drawer');
}, [isDirty, onDirtyStateChange]);
onDirtyStateChange(isDirty, location);
}, [isDirty, location, onDirtyStateChange]);
return (
<Box className="grid flex-shrink-0 grid-flow-col justify-between gap-3 border-t-1 p-2">
@@ -135,6 +138,7 @@ function FormFooter({
}
export default function BaseTableForm({
location,
onSubmit: handleExternalSubmit,
onCancel,
submitButtonText = 'Save',
@@ -168,7 +172,11 @@ export default function BaseTableForm({
<ForeignKeyEditorSection />
</div>
<FormFooter onCancel={onCancel} submitButtonText={submitButtonText} />
<FormFooter
onCancel={onCancel}
submitButtonText={submitButtonText}
location={location}
/>
</Form>
);
}

View File

@@ -1,5 +1,7 @@
import { useDialog } from '@/components/common/DialogProvider';
import type { BaseForeignKeyFormValues } from '@/components/dataBrowser/BaseForeignKeyForm';
import CreateForeignKeyForm from '@/components/dataBrowser/CreateForeignKeyForm';
import EditForeignKeyForm from '@/components/dataBrowser/EditForeignKeyForm';
import type { DatabaseColumn, ForeignKeyRelation } from '@/types/dataBrowser';
import Button from '@/ui/v2/Button';
import PlusIcon from '@/ui/v2/icons/PlusIcon';
@@ -68,18 +70,19 @@ export default function ForeignKeyEditorSection() {
onEdit={() => {
const primaryKeyIndex = getValues('primaryKeyIndex');
openDialog('EDIT_FOREIGN_KEY', {
openDialog({
title: 'Edit Foreign Key Relation',
payload: {
foreignKeyRelation: fields[index],
availableColumns: columns.map((column, columnIndex) =>
columnIndex === primaryKeyIndex
? { ...column, isPrimary: true }
: column,
),
onSubmit: (values: BaseForeignKeyFormValues) =>
handleEdit(values, index),
},
component: (
<EditForeignKeyForm
foreignKeyRelation={fields[index] as ForeignKeyRelation}
availableColumns={columns.map((column, columnIndex) =>
columnIndex === primaryKeyIndex
? { ...column, isPrimary: true }
: column,
)}
onSubmit={(values) => handleEdit(values, index)}
/>
),
});
}}
onDelete={() => remove(index)}
@@ -105,7 +108,7 @@ export default function ForeignKeyEditorSection() {
onClick={() => {
const primaryKeyIndex = getValues('primaryKeyIndex');
openDialog('CREATE_FOREIGN_KEY', {
openDialog({
title: (
<span className="grid grid-flow-row">
<span>Add a Foreign Key Relation</span>
@@ -116,14 +119,16 @@ export default function ForeignKeyEditorSection() {
</Text>
</span>
),
payload: {
availableColumns: columns.map((column, index) =>
index === primaryKeyIndex
? { ...column, isPrimary: true }
: column,
),
onSubmit: handleCreate,
},
component: (
<CreateForeignKeyForm
availableColumns={columns.map((column, index) =>
index === primaryKeyIndex
? { ...column, isPrimary: true }
: column,
)}
onSubmit={handleCreate}
/>
),
});
}}
>

View File

@@ -15,11 +15,11 @@ import { useRouter } from 'next/router';
import { FormProvider, useForm } from 'react-hook-form';
export interface CreateColumnFormProps
extends Pick<BaseColumnFormProps, 'onCancel'> {
extends Pick<BaseColumnFormProps, 'onCancel' | 'location'> {
/**
* Function to be called when the form is submitted.
*/
onSubmit?: () => Promise<void>;
onSubmit?: (args?: any) => Promise<any>;
}
export default function CreateColumnForm({

View File

@@ -13,7 +13,10 @@ import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
export interface CreateForeignKeyFormProps
extends Pick<BaseForeignKeyFormProps, 'onCancel' | 'availableColumns'> {
extends Pick<
BaseForeignKeyFormProps,
'onCancel' | 'availableColumns' | 'location'
> {
/**
* Column selected by default.
*/
@@ -21,7 +24,7 @@ export interface CreateForeignKeyFormProps
/**
* Function to be called when the form is submitted.
*/
onSubmit?: (values: BaseForeignKeyFormValues) => Promise<void>;
onSubmit?: (values: BaseForeignKeyFormValues) => Promise<void> | void;
}
export default function CreateForeignKeyForm({
@@ -51,9 +54,7 @@ export default function CreateForeignKeyForm({
setError(undefined);
try {
if (onSubmit) {
await onSubmit(values);
}
await onSubmit?.(values);
} catch (submitError) {
if (submitError && submitError instanceof Error) {
setError(submitError);

View File

@@ -10,11 +10,11 @@ import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form';
export interface CreateRecordFormProps
extends Pick<BaseRecordFormProps, 'columns' | 'onCancel'> {
extends Pick<BaseRecordFormProps, 'columns' | 'onCancel' | 'location'> {
/**
* Function to be called when the form is submitted.
*/
onSubmit?: () => Promise<void>;
onSubmit?: (args?: any) => Promise<any>;
}
export default function CreateRecordForm({

View File

@@ -17,7 +17,7 @@ import { useRouter } from 'next/router';
import { FormProvider, useForm } from 'react-hook-form';
export interface CreateTableFormProps
extends Pick<BaseTableFormProps, 'onCancel'> {
extends Pick<BaseTableFormProps, 'onCancel' | 'location'> {
/**
* Schema where the table should be created.
*/
@@ -25,7 +25,7 @@ export interface CreateTableFormProps
/**
* Function to be called when the form is submitted.
*/
onSubmit?: () => Promise<void>;
onSubmit?: (args?: any) => Promise<any>;
}
export default function CreateTableForm({

View File

@@ -5,6 +5,7 @@ import DataGridDateCell from '@/components/common/DataGridDateCell';
import DataGridNumericCell from '@/components/common/DataGridNumericCell';
import DataGridTextCell from '@/components/common/DataGridTextCell';
import { useDialog } from '@/components/common/DialogProvider';
import FormActivityIndicator from '@/components/common/FormActivityIndicator';
import InlineCode from '@/components/common/InlineCode';
import DataBrowserEmptyState from '@/components/dataBrowser/DataBrowserEmptyState';
import DataBrowserGridControls from '@/components/dataBrowser/DataBrowserGridControls';
@@ -28,9 +29,25 @@ import {
} from '@/utils/dataBrowser/postgresqlConstants';
import { isSchemaLocked } from '@/utils/dataBrowser/schemaHelpers';
import { useQueryClient } from '@tanstack/react-query';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/router';
import { useEffect, useMemo, useRef, useState } from 'react';
const CreateColumnForm = dynamic(
() => import('@/components/dataBrowser/CreateColumnForm'),
{ ssr: false, loading: () => <FormActivityIndicator /> },
);
const EditColumnForm = dynamic(
() => import('@/components/dataBrowser/EditColumnForm'),
{ ssr: false, loading: () => <FormActivityIndicator /> },
);
const CreateRecordForm = dynamic(
() => import('@/components/dataBrowser/CreateRecordForm'),
{ ssr: false, loading: () => <FormActivityIndicator /> },
);
export interface DataBrowserGridProps extends Partial<DataGridProps<any>> {}
export function createDataGridColumn(
@@ -273,33 +290,36 @@ export default function DataBrowserGrid({
const memoizedData = useMemo(() => rows, [rows]);
async function handleInsertRowClick() {
openDrawer('CREATE_RECORD', {
openDrawer({
title: 'Insert a New Row',
payload: {
columns: memoizedColumns,
onSubmit: refetch,
},
component: (
<CreateRecordForm
// TODO: Create proper typings for data browser columns
columns={memoizedColumns as unknown as DataBrowserGridColumn[]}
onSubmit={refetch}
/>
),
});
}
async function handleInsertColumnClick() {
openDrawer('CREATE_COLUMN', {
openDrawer({
title: 'Insert a New Column',
payload: {
onSubmit: refetch,
},
component: <CreateColumnForm onSubmit={refetch} />,
});
}
async function handleEditColumnClick(
column: DataBrowserGridColumn<NormalizedQueryDataRow>,
) {
openDrawer('EDIT_COLUMN', {
openDrawer({
title: 'Edit Column',
payload: {
column,
onSubmit: () => queryClient.refetchQueries([currentTablePath]),
},
component: (
<EditColumnForm
column={column}
onSubmit={() => queryClient.refetchQueries([currentTablePath])}
/>
),
});
}

View File

@@ -1,4 +1,5 @@
import { useDialog } from '@/components/common/DialogProvider';
import FormActivityIndicator from '@/components/common/FormActivityIndicator';
import InlineCode from '@/components/common/InlineCode';
import NavLink from '@/components/common/NavLink';
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
@@ -31,11 +32,36 @@ import Select from '@/ui/v2/Select';
import Text from '@/ui/v2/Text';
import { isSchemaLocked } from '@/utils/dataBrowser/schemaHelpers';
import { useQueryClient } from '@tanstack/react-query';
import dynamic from 'next/dynamic';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { twMerge } from 'tailwind-merge';
const CreateTableForm = dynamic(
() => import('@/components/dataBrowser/CreateTableForm'),
{
ssr: false,
loading: () => <FormActivityIndicator />,
},
);
const EditTableForm = dynamic(
() => import('@/components/dataBrowser/EditTableForm'),
{
ssr: false,
loading: () => <FormActivityIndicator />,
},
);
const EditPermissionsForm = dynamic(
() => import('@/components/dataBrowser/EditPermissionsForm'),
{
ssr: false,
loading: () => <FormActivityIndicator />,
},
);
export interface DataBrowserSidebarProps extends Omit<BoxProps, 'children'> {
/**
* Function to be called when a sidebar item is clicked.
@@ -200,7 +226,7 @@ function DataBrowserSidebarContent({
table: string,
disabled?: boolean,
) {
openDrawer('EDIT_PERMISSIONS', {
openDrawer({
title: (
<span className="inline-grid grid-flow-col items-center gap-2">
Permissions
@@ -208,22 +234,18 @@ function DataBrowserSidebarContent({
<Chip label="Preview" size="small" color="info" component="span" />
</span>
),
component: (
<EditPermissionsForm
disabled={disabled}
schema={schema}
table={table}
/>
),
props: {
PaperProps: {
className: 'lg:w-[65%] lg:max-w-7xl',
},
},
payload: {
onSubmit: async () => {
await queryClient.refetchQueries([
`${dataSourceSlug}.${schema}.${table}`,
]);
await refetch();
},
disabled,
schema,
table,
},
});
}
@@ -296,9 +318,11 @@ function DataBrowserSidebarContent({
endIcon={<PlusIcon />}
className="mt-1 w-full justify-between px-2"
onClick={() => {
openDrawer('CREATE_TABLE', {
openDrawer({
title: 'Create a New Table',
payload: { onSubmit: refetch, schema: selectedSchema },
component: (
<CreateTableForm onSubmit={refetch} schema={selectedSchema} />
),
});
onSidebarItemClick();
@@ -328,69 +352,68 @@ function DataBrowserSidebarContent({
className="group"
key={tablePath}
secondaryAction={
!isSelectedSchemaLocked && (
<Dropdown.Root
id="table-management-menu"
onOpen={() => setSidebarMenuTable(tablePath)}
onClose={() => setSidebarMenuTable(undefined)}
<Dropdown.Root
id="table-management-menu"
onOpen={() => setSidebarMenuTable(tablePath)}
onClose={() => setSidebarMenuTable(undefined)}
>
<Dropdown.Trigger
asChild
hideChevron
disabled={tablePath === removableTable}
>
<Dropdown.Trigger
asChild
hideChevron
disabled={tablePath === removableTable}
<IconButton
variant="borderless"
color={isSelected ? 'primary' : 'secondary'}
className={twMerge(
!isSelected &&
'opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 group-active:opacity-100',
)}
>
<IconButton
variant="borderless"
color={isSelected ? 'primary' : 'secondary'}
className={twMerge(
!isSelected &&
'opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 group-active:opacity-100',
)}
<DotsHorizontalIcon />
</IconButton>
</Dropdown.Trigger>
<Dropdown.Content menu PaperProps={{ className: 'w-52' }}>
{isGitHubConnected ? (
<Dropdown.Item
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
onClick={() =>
handleEditPermissionClick(
table.table_schema,
table.table_name,
true,
)
}
>
<DotsHorizontalIcon />
</IconButton>
</Dropdown.Trigger>
<UsersIcon
className="h-4 w-4"
sx={{ color: 'text.secondary' }}
/>
<Dropdown.Content
menu
PaperProps={{ className: 'w-52' }}
>
{isGitHubConnected ? (
<Dropdown.Item
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
onClick={() =>
handleEditPermissionClick(
table.table_schema,
table.table_name,
true,
)
}
>
<UsersIcon
className="h-4 w-4"
sx={{ color: 'text.secondary' }}
/>
<span>View Permissions</span>
</Dropdown.Item>
) : (
[
<span>View Permissions</span>
</Dropdown.Item>
) : (
[
!isSelectedSchemaLocked && (
<Dropdown.Item
key="edit-table"
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
onClick={() =>
openDrawer('EDIT_TABLE', {
openDrawer({
title: 'Edit Table',
payload: {
onSubmit: async () => {
await queryClient.refetchQueries([
`${dataSourceSlug}.${table.table_schema}.${table.table_name}`,
]);
await refetch();
},
schema: table.table_schema,
table,
},
component: (
<EditTableForm
onSubmit={async () => {
await queryClient.refetchQueries([
`${dataSourceSlug}.${table.table_schema}.${table.table_name}`,
]);
await refetch();
}}
schema={table.table_schema}
table={table}
/>
),
})
}
>
@@ -400,32 +423,38 @@ function DataBrowserSidebarContent({
/>
<span>Edit Table</span>
</Dropdown.Item>,
</Dropdown.Item>
),
!isSelectedSchemaLocked && (
<Divider
key="edit-table-separator"
component="li"
/>,
<Dropdown.Item
key="edit-permissions"
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
onClick={() =>
handleEditPermissionClick(
table.table_schema,
table.table_name,
)
}
>
<UsersIcon
className="h-4 w-4"
sx={{ color: 'text.secondary' }}
/>
/>
),
<Dropdown.Item
key="edit-permissions"
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
onClick={() =>
handleEditPermissionClick(
table.table_schema,
table.table_name,
)
}
>
<UsersIcon
className="h-4 w-4"
sx={{ color: 'text.secondary' }}
/>
<span>Edit Permissions</span>
</Dropdown.Item>,
<span>Edit Permissions</span>
</Dropdown.Item>,
!isSelectedSchemaLocked && (
<Divider
key="edit-permissions-separator"
component="li"
/>,
/>
),
!isSelectedSchemaLocked && (
<Dropdown.Item
key="delete-table"
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
@@ -443,12 +472,12 @@ function DataBrowserSidebarContent({
/>
<span>Delete Table</span>
</Dropdown.Item>,
]
)}
</Dropdown.Content>
</Dropdown.Root>
)
</Dropdown.Item>
),
]
)}
</Dropdown.Content>
</Dropdown.Root>
}
>
<ListItem.Button

View File

@@ -18,7 +18,7 @@ import { useRouter } from 'next/router';
import { FormProvider, useForm } from 'react-hook-form';
export interface EditColumnFormProps
extends Pick<BaseColumnFormProps, 'onCancel'> {
extends Pick<BaseColumnFormProps, 'onCancel' | 'location'> {
/**
* Column to be edited.
*/

View File

@@ -14,7 +14,10 @@ import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
export interface EditForeignKeyFormProps
extends Pick<BaseForeignKeyFormProps, 'onCancel' | 'availableColumns'> {
extends Pick<
BaseForeignKeyFormProps,
'onCancel' | 'availableColumns' | 'location'
> {
/**
* Foreign key relation to be edited.
*/
@@ -26,7 +29,7 @@ export interface EditForeignKeyFormProps
/**
* Function to be called when the form is submitted.
*/
onSubmit?: (values: BaseForeignKeyFormValues) => Promise<void>;
onSubmit?: (values: BaseForeignKeyFormValues) => Promise<void> | void;
}
export default function EditForeignKeyForm({
@@ -57,9 +60,7 @@ export default function EditForeignKeyForm({
setError(undefined);
try {
if (onSubmit) {
await onSubmit(values);
}
await onSubmit?.(values);
} catch (submitError) {
if (submitError && submitError instanceof Error) {
setError(submitError);

View File

@@ -3,6 +3,7 @@ import useMetadataQuery from '@/hooks/dataBrowser/useMetadataQuery';
import useTableQuery from '@/hooks/dataBrowser/useTableQuery';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
import type { DialogFormProps } from '@/types/common';
import type {
DatabaseAccessLevel,
DatabaseAction,
@@ -30,7 +31,7 @@ import { twMerge } from 'tailwind-merge';
import RolePermissionEditorForm from './RolePermissionEditorForm';
import RolePermissionsRow from './RolePermissionsRow';
export interface EditPermissionsFormProps {
export interface EditPermissionsFormProps extends DialogFormProps {
/**
* Determines whether the form is disabled or not.
*/
@@ -54,6 +55,7 @@ export default function EditPermissionsForm({
schema,
table,
onCancel,
location,
}: EditPermissionsFormProps) {
const [role, setRole] = useState<string>();
const [action, setAction] = useState<DatabaseAction>();
@@ -181,6 +183,7 @@ export default function EditPermissionsForm({
return (
<RolePermissionEditorForm
location={location}
resourceVersion={metadata?.resourceVersion}
disabled={disabled}
schema={schema}

View File

@@ -2,6 +2,7 @@ import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form';
import HighlightedText from '@/components/common/HighlightedText';
import useManagePermissionMutation from '@/hooks/dataBrowser/useManagePermissionMutation';
import type { DialogFormProps } from '@/types/common';
import type {
DatabaseAction,
HasuraMetadataPermission,
@@ -72,7 +73,7 @@ export interface RolePermissionEditorFormValues {
computedFields?: string[];
}
export interface RolePermissionEditorFormProps {
export interface RolePermissionEditorFormProps extends DialogFormProps {
/**
* Determines whether or not the form is disabled.
*/
@@ -169,6 +170,7 @@ export default function RolePermissionEditorForm({
onCancel,
permission,
disabled,
location,
}: RolePermissionEditorFormProps) {
const queryClient = useQueryClient();
const {
@@ -214,8 +216,8 @@ export default function RolePermissionEditorForm({
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, 'drawer');
}, [isDirty, onDirtyStateChange]);
onDirtyStateChange(isDirty, location);
}, [isDirty, location, onDirtyStateChange]);
async function handleSubmit(values: RolePermissionEditorFormValues) {
const managePermissionPromise = managePermission({
@@ -245,7 +247,7 @@ export default function RolePermissionEditorForm({
: permission?.check,
backend_only: values.backendOnly,
computed_fields:
permission?.computed_fields.length > 0
permission?.computed_fields?.length > 0
? permission?.computed_fields
: null,
},
@@ -261,7 +263,7 @@ export default function RolePermissionEditorForm({
getToastStyleProps(),
);
onDirtyStateChange(false, 'drawer');
onDirtyStateChange(false, location);
onSubmit?.();
}
@@ -270,7 +272,7 @@ export default function RolePermissionEditorForm({
openDirtyConfirmation({
props: {
onPrimaryAction: () => {
onDirtyStateChange(false, 'drawer');
onDirtyStateChange(false, location);
onCancel?.();
},
},
@@ -300,7 +302,7 @@ export default function RolePermissionEditorForm({
getToastStyleProps(),
);
onDirtyStateChange(false, 'drawer');
onDirtyStateChange(false, location);
onSubmit?.();
}

View File

@@ -6,6 +6,7 @@ import Input from '@/ui/v2/Input';
import Radio from '@/ui/v2/Radio';
import RadioGroup from '@/ui/v2/RadioGroup';
import Text from '@/ui/v2/Text';
import type { FocusEvent } from 'react';
import { useState } from 'react';
import { useFormContext } from 'react-hook-form';
import PermissionSettingsSection from './PermissionSettingsSection';
@@ -130,7 +131,13 @@ export default function RowPermissionsSection({
{action === 'select' && (
<Input
{...register('limit')}
{...register('limit', {
onBlur: (event: FocusEvent<HTMLInputElement>) => {
if (!event.target.value) {
setValue('limit', null);
}
},
})}
disabled={disabled}
id="limit"
type="number"

View File

@@ -43,7 +43,10 @@ const baseValidationSchema = Yup.object().shape({
});
const selectValidationSchema = baseValidationSchema.shape({
limit: Yup.number().min(0, 'Limit must not be negative.').nullable(true),
limit: Yup.number()
.label('Limit')
.min(0, 'Limit must not be negative.')
.nullable(true),
allowAggregations: Yup.boolean().nullable(true),
queryRootFields: Yup.array().of(Yup.string()).nullable(true),
subscriptionRootFields: Yup.array().of(Yup.string()).nullable(true),

View File

@@ -23,7 +23,7 @@ import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
export interface EditTableFormProps
extends Pick<BaseTableFormProps, 'onCancel'> {
extends Pick<BaseTableFormProps, 'onCancel' | 'location'> {
/**
* Schema where the table is located.
*/

View File

@@ -6,6 +6,7 @@ import ColumnAutocomplete from '@/components/dataBrowser/ColumnAutocomplete';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import type { HasuraOperator } from '@/types/dataBrowser';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import type { AutocompleteOption } from '@/ui/v2/Autocomplete';
import type { InputProps } from '@/ui/v2/Input';
import { inputClasses } from '@/ui/v2/Input';
import Option from '@/ui/v2/Option';
@@ -211,12 +212,13 @@ export default function RuleValueInput({
<ControlledAutocomplete
disabled={disabled}
freeSolo={!isHasuraInput}
autoSelect={!isHasuraInput}
autoHighlight={isHasuraInput}
open
isOptionEqualToValue={(option, value) => {
if (typeof value === 'string') {
return option.value.toLowerCase() === (value as string).toLowerCase();
isOptionEqualToValue={(
option,
value: string | number | AutocompleteOption<string>,
) => {
if (typeof value !== 'object') {
return option.value.toLowerCase() === value?.toString().toLowerCase();
}
return option.value.toLowerCase() === value.value.toLowerCase();

View File

@@ -277,7 +277,7 @@ export default function FilesDataGrid(props: FilesDataGridProps) {
}
if (fileError) {
throw fileError;
throw new Error(fileError.message);
}
triggerToast(`File has been uploaded successfully (${fileMetadata?.id})`);

View File

@@ -1,4 +1,6 @@
import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form';
import type { DialogFormProps } from '@/types/common';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import { slugifyString } from '@/utils/helpers';
@@ -11,11 +13,12 @@ import {
import { yupResolver } from '@hookform/resolvers/yup';
import { useUserData } from '@nhost/nextjs';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import * as Yup from 'yup';
export interface EditWorkspaceNameFormProps {
export interface EditWorkspaceNameFormProps extends DialogFormProps {
/**
* The current workspace name if this is an edit operation.
*/
@@ -44,14 +47,7 @@ export interface EditWorkspaceNameFormProps {
onCancel?: VoidFunction;
}
export interface EditWorkspaceNameFormValues {
/**
* New workspace name.
*/
newWorkspaceName: string;
}
const validationSchema = Yup.object().shape({
const validationSchema = Yup.object({
newWorkspaceName: Yup.string()
.required('Workspace name is required.')
.min(4, 'The new Workspace name must be at least 4 characters.')
@@ -71,14 +67,20 @@ const validationSchema = Yup.object().shape({
),
});
export default function EditWorkspaceName({
export type EditWorkspaceNameFormValues = Yup.InferType<
typeof validationSchema
>;
export default function EditWorkspaceNameForm({
disabled,
onSubmit,
onCancel,
currentWorkspaceName,
currentWorkspaceId,
submitButtonText = 'Create',
location,
}: EditWorkspaceNameFormProps) {
const { onDirtyStateChange } = useDialog();
const currentUser = useUserData();
const [insertWorkspace, { client }] = useInsertWorkspaceMutation();
const [updateWorkspaceName] = useUpdateWorkspaceMutation({
@@ -105,6 +107,10 @@ export default function EditWorkspaceName({
} = form;
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, location);
}, [isDirty, location, onDirtyStateChange]);
async function handleSubmit({
newWorkspaceName,
}: EditWorkspaceNameFormValues) {
@@ -112,6 +118,8 @@ export default function EditWorkspaceName({
try {
if (currentWorkspaceId) {
onDirtyStateChange(false, location);
// In this bit of code we spread the props of the current path (e.g. /workspace/...) and add one key-value pair: `mutating: true`.
// We want to indicate that the currently we're in the process of running a mutation state that will affect the routing behaviour of the website
// i.e. redirecting to 404 if there's no workspace/project with that slug.
@@ -186,6 +194,9 @@ export default function EditWorkspaceName({
include: ['getOneUser'],
});
// The form has been submitted, it's not dirty anymore
onDirtyStateChange(false, location);
await router.push(slug);
onSubmit?.();
}
@@ -194,9 +205,9 @@ export default function EditWorkspaceName({
<FormProvider {...form}>
<Form
onSubmit={handleSubmit}
className="flex flex-col content-between flex-auto pt-2 pb-6 overflow-hidden"
className="flex flex-auto flex-col content-between overflow-hidden pt-2 pb-6"
>
<div className="flex-auto px-6 overflow-y-auto">
<div className="flex-auto overflow-y-auto px-6">
<Input
{...register('newWorkspaceName')}
error={Boolean(errors.newWorkspaceName?.message)}

View File

@@ -58,16 +58,19 @@ export function InviteAnnounce() {
error: null,
loading: true,
});
const res = await nhost.functions.call('/accept-workspace-invite', {
workspaceMemberInviteId: invite.id,
isAccepted: true,
});
const { res, error: acceptError } = await nhost.functions.call(
'/accept-workspace-invite',
{
workspaceMemberInviteId: invite.id,
isAccepted: true,
},
);
if (res?.res?.status !== 200) {
if (res?.status !== 200) {
triggerToast('An error occurred when trying to accept the invitation.');
return setSubmitState({
error: res.error,
error: new Error(acceptError.message),
loading: false,
});
}
@@ -90,7 +93,7 @@ export function InviteAnnounce() {
error: null,
});
const res = await nhost.functions.call(
const { error: ignoreError } = await nhost.functions.call(
'/accept-workspace-invite',
{
workspaceMemberInviteId: inviteId,
@@ -99,12 +102,12 @@ export function InviteAnnounce() {
{ useAxios: false },
);
if (res?.error) {
if (ignoreError) {
triggerToast('An error occurred when trying to ignore the invitation.');
setIgnoreState({
loading: false,
error: new Error(res.error.message),
error: new Error(ignoreError.message),
});
return;

View File

@@ -1,5 +1,6 @@
import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form';
import type { DialogFormProps } from '@/types/common';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import Text from '@/ui/v2/Text';
@@ -26,7 +27,7 @@ export interface BaseEnvironmentVariableFormValues {
prodValue: string;
}
export interface BaseEnvironmentVariableFormProps {
export interface BaseEnvironmentVariableFormProps extends DialogFormProps {
/**
* Determines the mode of the form.
*
@@ -89,6 +90,7 @@ export default function BaseEnvironmentVariableForm({
onSubmit,
onCancel,
submitButtonText = 'Save',
location,
}: BaseEnvironmentVariableFormProps) {
const { onDirtyStateChange } = useDialog();
const form = useFormContext<BaseEnvironmentVariableFormValues>();
@@ -103,8 +105,8 @@ export default function BaseEnvironmentVariableForm({
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, 'dialog');
}, [isDirty, onDirtyStateChange]);
onDirtyStateChange(isDirty, location);
}, [isDirty, location, onDirtyStateChange]);
return (
<div className="grid grid-flow-row gap-6 px-6 pb-6">

View File

@@ -17,7 +17,7 @@ import { FormProvider, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
export interface CreateEnvironmentVariableFormProps
extends Pick<BaseEnvironmentVariableFormProps, 'onCancel'> {
extends Pick<BaseEnvironmentVariableFormProps, 'onCancel' | 'location'> {
/**
* Function to be called when the form is submitted.
*/

View File

@@ -18,7 +18,7 @@ import { FormProvider, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
export interface EditEnvironmentVariableFormProps
extends Pick<BaseEnvironmentVariableFormProps, 'onCancel'> {
extends Pick<BaseEnvironmentVariableFormProps, 'onCancel' | 'location'> {
/**
* The environment variable to edit.
*/

View File

@@ -1,6 +1,7 @@
import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import type { DialogFormProps } from '@/types/common';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
@@ -14,7 +15,7 @@ import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import * as Yup from 'yup';
export interface EditJwtSecretFormProps {
export interface EditJwtSecretFormProps extends DialogFormProps {
/**
* Initial JWT secret.
*/
@@ -39,14 +40,7 @@ export interface EditJwtSecretFormProps {
onCancel?: VoidFunction;
}
export interface EditJwtSecretFormValues {
/**
* JWT secret.
*/
jwtSecret: string;
}
const validationSchema = Yup.object().shape({
const validationSchema = Yup.object({
jwtSecret: Yup.string()
.nullable()
.required('This field is required.')
@@ -60,12 +54,15 @@ const validationSchema = Yup.object().shape({
}),
});
export type EditJwtSecretFormValues = Yup.InferType<typeof validationSchema>;
export default function EditJwtSecretForm({
disabled,
jwtSecret,
onSubmit,
onCancel,
submitButtonText = 'Save',
location,
}: EditJwtSecretFormProps) {
const { currentApplication } = useCurrentWorkspaceAndApplication();
const [updateApplication] = useUpdateApplicationMutation({
@@ -89,8 +86,8 @@ export default function EditJwtSecretForm({
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, 'dialog');
}, [isDirty, onDirtyStateChange]);
onDirtyStateChange(isDirty, location);
}, [isDirty, location, onDirtyStateChange]);
async function handleSubmit(values: EditJwtSecretFormValues) {
const updateAppPromise = updateApplication({
@@ -121,7 +118,7 @@ export default function EditJwtSecretForm({
onSubmit={handleSubmit}
className="flex flex-auto flex-col content-between overflow-hidden pb-4"
>
<div className="px-6 overflow-y-auto flex-auto">
<div className="flex-auto overflow-y-auto px-6">
<Input
{...register('jwtSecret')}
error={Boolean(errors.jwtSecret?.message)}

View File

@@ -1,4 +1,6 @@
import { useDialog } from '@/components/common/DialogProvider';
import CreateEnvironmentVariableForm from '@/components/settings/environmentVariables/CreateEnvironmentVariableForm';
import EditEnvironmentVariableForm from '@/components/settings/environmentVariables/EditEnvironmentVariableForm';
import SettingsContainer from '@/components/settings/SettingsContainer';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import type { EnvironmentVariable } from '@/types/application';
@@ -23,9 +25,9 @@ import { Fragment } from 'react';
import toast from 'react-hot-toast';
import { twMerge } from 'tailwind-merge';
export interface PermissionVariableSettingsFormValues {
export interface EnvironmentVariableSettingsFormValues {
/**
* Permission variables.
* Environment variables.
*/
environmentVariables: EnvironmentVariable[];
}
@@ -75,8 +77,9 @@ export default function EnvironmentVariableSettings() {
}
function handleOpenCreator() {
openDialog('CREATE_ENVIRONMENT_VARIABLE', {
openDialog({
title: 'Create Environment Variable',
component: <CreateEnvironmentVariableForm />,
props: {
titleProps: { className: '!pb-0' },
PaperProps: { className: 'gap-2 max-w-sm' },
@@ -85,9 +88,13 @@ export default function EnvironmentVariableSettings() {
}
function handleOpenEditor(originalVariable: EnvironmentVariable) {
openDialog('EDIT_ENVIRONMENT_VARIABLE', {
title: 'Edit Environment Variables',
payload: { originalEnvironmentVariable: originalVariable },
openDialog({
title: 'Edit Environment Variable',
component: (
<EditEnvironmentVariableForm
originalEnvironmentVariable={originalVariable}
/>
),
props: {
titleProps: { className: '!pb-0' },
PaperProps: { className: 'gap-2 max-w-sm' },

View File

@@ -1,5 +1,6 @@
import { useDialog } from '@/components/common/DialogProvider';
import InlineCode from '@/components/common/InlineCode';
import EditJwtSecretForm from '@/components/settings/environmentVariables/EditJwtSecretForm';
import SettingsContainer from '@/components/settings/SettingsContainer';
import useIsPlatform from '@/hooks/common/useIsPlatform';
import { useAppClient } from '@/hooks/useAppClient';
@@ -50,7 +51,7 @@ export default function SystemEnvironmentVariableSettings() {
}
function showViewJwtSecretModal() {
openDialog('EDIT_JWT_SECRET', {
openDialog({
title: (
<span className="grid grid-flow-row">
<span>Auth JWT Secret</span>
@@ -61,15 +62,17 @@ export default function SystemEnvironmentVariableSettings() {
</Text>
</span>
),
payload: {
disabled: true,
jwtSecret: data?.app?.hasuraGraphqlJwtSecret,
},
component: (
<EditJwtSecretForm
disabled
jwtSecret={data?.app?.hasuraGraphqlJwtSecret}
/>
),
});
}
function showEditJwtSecretModal() {
openDialog('EDIT_JWT_SECRET', {
openDialog({
title: (
<span className="grid grid-flow-row">
<span>Edit JWT Secret</span>
@@ -80,9 +83,9 @@ export default function SystemEnvironmentVariableSettings() {
</Text>
</span>
),
payload: {
jwtSecret: data?.app?.hasuraGraphqlJwtSecret,
},
component: (
<EditJwtSecretForm jwtSecret={data?.app?.hasuraGraphqlJwtSecret} />
),
});
}
@@ -107,7 +110,7 @@ export default function SystemEnvironmentVariableSettings() {
),
},
{ key: 'NHOST_AUTH_URL', value: appClient.auth.url },
{ key: 'NHOST_GRAPHQL_URL', value: appClient.graphql.url },
{ key: 'NHOST_GRAPHQL_URL', value: appClient.graphql.httpUrl },
{ key: 'NHOST_STORAGE_URL', value: appClient.storage.url },
{ key: 'NHOST_FUNCTIONS_URL', value: appClient.functions.url },
];

View File

@@ -1,5 +1,6 @@
import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form';
import type { DialogFormProps } from '@/types/common';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import Text from '@/ui/v2/Text';
@@ -7,18 +8,7 @@ import { useEffect } from 'react';
import { useFormContext } from 'react-hook-form';
import * as Yup from 'yup';
export interface BasePermissionVariableFormValues {
/**
* Permission variable key.
*/
key: string;
/**
* Permission variable value.
*/
value: string;
}
export interface BasePermissionVariableFormProps {
export interface BasePermissionVariableFormProps extends DialogFormProps {
/**
* Function to be called when the form is submitted.
*/
@@ -40,10 +30,15 @@ export const basePermissionVariableValidationSchema = Yup.object({
value: Yup.string().required('This field is required.'),
});
export type BasePermissionVariableFormValues = Yup.InferType<
typeof basePermissionVariableValidationSchema
>;
export default function BasePermissionVariableForm({
onSubmit,
onCancel,
submitButtonText = 'Save',
location,
}: BasePermissionVariableFormProps) {
const { onDirtyStateChange } = useDialog();
const form = useFormContext<BasePermissionVariableFormValues>();
@@ -56,8 +51,8 @@ export default function BasePermissionVariableForm({
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, 'dialog');
}, [isDirty, onDirtyStateChange]);
onDirtyStateChange(isDirty, location);
}, [isDirty, location, onDirtyStateChange]);
return (
<div className="grid grid-flow-row gap-2 px-6 pb-6">

View File

@@ -19,7 +19,7 @@ import { FormProvider, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
export interface CreatePermissionVariableFormProps
extends Pick<BasePermissionVariableFormProps, 'onCancel'> {
extends Pick<BasePermissionVariableFormProps, 'onCancel' | 'location'> {
/**
* Function to be called when the form is submitted.
*/

View File

@@ -20,7 +20,7 @@ import { FormProvider, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
export interface EditPermissionVariableFormProps
extends Pick<BasePermissionVariableFormProps, 'onCancel'> {
extends Pick<BasePermissionVariableFormProps, 'onCancel' | 'location'> {
/**
* The permission variable to be edited.
*/

View File

@@ -1,4 +1,6 @@
import { useDialog } from '@/components/common/DialogProvider';
import CreatePermissionVariableForm from '@/components/settings/permissions/CreatePermissionVariableForm';
import EditPermissionVariableForm from '@/components/settings/permissions/EditPermissionVariableForm';
import SettingsContainer from '@/components/settings/SettingsContainer';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import type { CustomClaim } from '@/types/application';
@@ -88,8 +90,9 @@ export default function PermissionVariableSettings() {
}
function handleOpenCreator() {
openDialog('CREATE_PERMISSION_VARIABLE', {
openDialog({
title: 'Create Permission Variable',
component: <CreatePermissionVariableForm />,
props: {
titleProps: { className: '!pb-0' },
PaperProps: { className: 'max-w-sm' },
@@ -98,9 +101,11 @@ export default function PermissionVariableSettings() {
}
function handleOpenEditor(originalVariable: CustomClaim) {
openDialog('EDIT_PERMISSION_VARIABLE', {
openDialog({
title: 'Edit Permission Variable',
payload: { originalVariable },
component: (
<EditPermissionVariableForm originalVariable={originalVariable} />
),
props: {
titleProps: { className: '!pb-0' },
PaperProps: { className: 'max-w-sm' },
@@ -136,7 +141,7 @@ export default function PermissionVariableSettings() {
description="Permission variables are used to define permission rules in the GraphQL API."
docsLink="https://docs.nhost.io/graphql/permissions"
rootClassName="gap-0"
className="px-0 my-2"
className="my-2 px-0"
slotProps={{ submitButton: { className: 'invisible' } }}
>
<Box className="grid grid-cols-2 border-b-1 px-4 py-3">
@@ -149,7 +154,7 @@ export default function PermissionVariableSettings() {
{availablePermissionVariables.map((customClaim, index) => (
<Fragment key={customClaim.key}>
<ListItem.Root
className="px-4 grid grid-cols-2"
className="grid grid-cols-2 px-4"
secondaryAction={
<Dropdown.Root>
<Tooltip
@@ -215,7 +220,7 @@ export default function PermissionVariableSettings() {
<>
X-Hasura-{customClaim.key}{' '}
{customClaim.isSystemClaim && (
<LockIcon className="w-4 h-4" />
<LockIcon className="h-4 w-4" />
)}
</>
}
@@ -237,7 +242,7 @@ export default function PermissionVariableSettings() {
</List>
<Button
className="justify-self-start mx-4"
className="mx-4 justify-self-start"
variant="borderless"
startIcon={<PlusIcon />}
onClick={handleOpenCreator}

View File

@@ -1,5 +1,6 @@
import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form';
import type { DialogFormProps } from '@/types/common';
import { Alert } from '@/ui/Alert';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
@@ -8,14 +9,7 @@ import { useEffect } from 'react';
import { useFormContext } from 'react-hook-form';
import * as Yup from 'yup';
export interface BaseRoleFormValues {
/**
* The name of the role.
*/
name: string;
}
export interface BaseRoleFormProps {
export interface BaseRoleFormProps extends DialogFormProps {
/**
* Function to be called when the form is submitted.
*/
@@ -36,10 +30,15 @@ export const baseRoleFormValidationSchema = Yup.object({
name: Yup.string().required('This field is required.'),
});
export type BaseRoleFormValues = Yup.InferType<
typeof baseRoleFormValidationSchema
>;
export default function BaseRoleForm({
onSubmit,
onCancel,
submitButtonText = 'Save',
location,
}: BaseRoleFormProps) {
const { onDirtyStateChange } = useDialog();
const form = useFormContext<BaseRoleFormValues>();
@@ -52,8 +51,8 @@ export default function BaseRoleForm({
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, 'dialog');
}, [isDirty, onDirtyStateChange]);
onDirtyStateChange(isDirty, location);
}, [isDirty, location, onDirtyStateChange]);
return (
<div className="grid grid-flow-row gap-3 px-6 pb-6">

View File

@@ -18,7 +18,7 @@ import { FormProvider, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
export interface CreateRoleFormProps
extends Pick<BaseRoleFormProps, 'onCancel'> {
extends Pick<BaseRoleFormProps, 'onCancel' | 'location'> {
/**
* Function to be called when the form is submitted.
*/

View File

@@ -18,7 +18,8 @@ import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
export interface EditRoleFormProps extends Pick<BaseRoleFormProps, 'onCancel'> {
export interface EditRoleFormProps
extends Pick<BaseRoleFormProps, 'onCancel' | 'location'> {
/**
* The role to be edited.
*/

View File

@@ -1,4 +1,6 @@
import { useDialog } from '@/components/common/DialogProvider';
import CreateRoleForm from '@/components/settings/roles/CreateRoleForm';
import EditRoleForm from '@/components/settings/roles/EditRoleForm';
import SettingsContainer from '@/components/settings/SettingsContainer';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import type { Role } from '@/types/application';
@@ -108,8 +110,9 @@ export default function RoleSettings() {
}
function handleOpenCreator() {
openDialog('CREATE_ROLE', {
openDialog({
title: 'Create Allowed Role',
component: <CreateRoleForm />,
props: {
titleProps: { className: '!pb-0' },
PaperProps: { className: 'max-w-sm' },
@@ -118,9 +121,9 @@ export default function RoleSettings() {
}
function handleOpenEditor(originalRole: Role) {
openDialog('EDIT_ROLE', {
openDialog({
title: 'Edit Allowed Role',
payload: { originalRole },
component: <EditRoleForm originalRole={originalRole} />,
props: {
titleProps: { className: '!pb-0' },
PaperProps: { className: 'max-w-sm' },

View File

@@ -222,9 +222,9 @@ function Autocomplete(
inputValue: inputValue || '',
getOptionLabel: props.getOptionLabel
? props.getOptionLabel
: (option) => {
if (typeof option === 'string') {
return option;
: (option: string | number | AutocompleteOption<string>) => {
if (typeof option !== 'object') {
return option.toString();
}
return option.label ?? option.dropdownLabel;
@@ -284,33 +284,46 @@ function Autocomplete(
}}
PopperComponent={AutocompletePopper}
popupIcon={<ChevronDownIcon sx={{ width: 12, height: 12 }} />}
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option;
getOptionLabel={(
option: string | number | AutocompleteOption<string>,
) => {
if (!option) {
return '';
}
if (typeof option !== 'object') {
return option.toString();
}
return option.label ?? option.dropdownLabel;
}}
isOptionEqualToValue={(option, value) => {
isOptionEqualToValue={(
option,
value: string | number | AutocompleteOption<string>,
) => {
if (!value) {
return false;
}
if (typeof value === 'string') {
return option.value === value;
if (typeof value !== 'object') {
return option.value.toString() === value.toString();
}
return option.value === value.value && option.custom === value.custom;
}}
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<StyledTag
deleteIcon={<XIcon />}
size="small"
label={typeof option === 'string' ? option : option.value}
{...getTagProps({ index })}
/>
))
value.map(
(option: string | number | AutocompleteOption<string>, index) => (
<StyledTag
deleteIcon={<XIcon />}
size="small"
label={
typeof option !== 'object' ? option.toString() : option.value
}
{...getTagProps({ index })}
/>
),
)
}
renderGroup={({ group, key, children }) =>
group ? (
@@ -323,9 +336,12 @@ function Autocomplete(
<div key={key}>{children}</div>
)
}
renderOption={(optionProps, option) => {
if (typeof option === 'string') {
return <OptionBase {...optionProps}>{option}</OptionBase>;
renderOption={(
optionProps,
option: string | number | AutocompleteOption<string>,
) => {
if (typeof option !== 'object') {
return <OptionBase {...optionProps}>{option.toString()}</OptionBase>;
}
return (

View File

@@ -1,4 +1,5 @@
import Backdrop from '@/ui/v2/Backdrop';
import type { DialogTitleProps } from '@/ui/v2/Dialog';
import { DialogTitle } from '@/ui/v2/Dialog';
import { styled } from '@mui/material';
import type { DrawerProps as MaterialDrawerProps } from '@mui/material/Drawer';
@@ -10,6 +11,10 @@ export interface DrawerProps extends Omit<MaterialDrawerProps, 'title'> {
* Title of the drawer.
*/
title?: ReactNode;
/**
* Props to pass to the title component.
*/
titleProps?: DialogTitleProps;
/**
* Determines whether or not a close button is hidden in the drawer.
*
@@ -33,13 +38,18 @@ function Drawer({
children,
onClose,
title,
titleProps: { sx: titleSx, ...titleProps } = {},
...props
}: DrawerProps) {
return (
<StyledDrawer components={{ Backdrop }} onClose={onClose} {...props}>
{onClose && !hideCloseButton && (
<DialogTitle
sx={{ padding: (theme) => theme.spacing(2.5, 3) }}
{...titleProps}
sx={[
...(Array.isArray(titleSx) ? titleSx : [titleSx]),
{ padding: (theme) => theme.spacing(2.5, 3) },
]}
onClose={(event) => onClose(event, 'escapeKeyDown')}
>
{title}

View File

@@ -1,29 +1,20 @@
import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import type { DialogFormProps } from '@/types/common';
import { Alert } from '@/ui/Alert';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import { yupResolver } from '@hookform/resolvers/yup';
import axios from 'axios';
import { useState } from 'react';
import fetch from 'cross-fetch';
import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import * as Yup from 'yup';
export interface CreateUserFormValues {
/**
* Email of the user to add to this project.
*/
email: string;
/**
* Password for the user.
*/
password: string;
}
export interface CreateUserFormProps {
export interface CreateUserFormProps extends DialogFormProps {
/**
* Function to be called when the operation is cancelled.
*/
@@ -31,10 +22,10 @@ export interface CreateUserFormProps {
/**
* Function to be called when the submit is successful.
*/
onSuccess?: VoidFunction;
onSubmit?: VoidFunction | ((args?: any) => Promise<any>);
}
export const CreateUserFormValidationSchema = Yup.object({
export const validationSchema = Yup.object({
email: Yup.string()
.min(5, 'Email must be at least 5 characters long.')
.email('Invalid email address')
@@ -45,10 +36,14 @@ export const CreateUserFormValidationSchema = Yup.object({
.required('This field is required.'),
});
export type CreateUserFormValues = Yup.InferType<typeof validationSchema>;
export default function CreateUserForm({
onSuccess,
onSubmit,
onCancel,
location,
}: CreateUserFormProps) {
const { onDirtyStateChange } = useDialog();
const { currentApplication } = useCurrentWorkspaceAndApplication();
const [createUserFormError, setCreateUserFormError] = useState<Error | null>(
null,
@@ -57,15 +52,21 @@ export default function CreateUserForm({
const form = useForm<CreateUserFormValues>({
defaultValues: {},
reValidateMode: 'onSubmit',
resolver: yupResolver(CreateUserFormValidationSchema),
resolver: yupResolver(validationSchema),
});
const {
register,
formState: { errors, isSubmitting },
formState: { errors, isSubmitting, dirtyFields },
setError,
} = form;
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, location);
}, [isDirty, location, onDirtyStateChange]);
const baseAuthUrl = generateAppServiceUrl(
currentApplication?.subdomain,
currentApplication?.region?.awsName,
@@ -79,28 +80,37 @@ export default function CreateUserForm({
try {
await toast.promise(
axios.post(signUpUrl, {
email,
password,
fetch(signUpUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
}).then(async (res) => {
const data = await res.json();
if (res.ok) {
return data;
}
if (res.status === 409) {
setError('email', { message: data?.message });
}
throw new Error(data?.message || 'Something went wrong.');
}),
{
loading: 'Creating user...',
success: 'User created successfully.',
error: 'An error occurred while trying to create the user.',
error: (arg) =>
arg?.message
? `Error: ${arg.message}`
: 'An error occurred while trying to create the user.',
},
getToastStyleProps(),
);
onSuccess?.();
onSubmit?.();
} catch (error) {
if (error.response?.status === 409) {
setError('email', {
message: error.response.data.message,
});
return;
}
setCreateUserFormError(
new Error(error.response.data.message || 'Something went wrong.'),
);
// Note: The error is already handled by the toast promise.
}
}
@@ -137,7 +147,7 @@ export default function CreateUserForm({
{createUserFormError && (
<Alert
severity="error"
className="grid items-center justify-between grid-flow-col px-4 py-3"
className="grid grid-flow-col items-center justify-between px-4 py-3"
>
<span className="text-left">
<strong>Error:</strong> {createUserFormError.message}

View File

@@ -2,8 +2,10 @@ import ControlledCheckbox from '@/components/common/ControlledCheckbox';
import ControlledSelect from '@/components/common/ControlledSelect';
import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form';
import EditUserPasswordForm from '@/components/users/EditUserPasswordForm';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
import type { DialogFormProps } from '@/types/common';
import Avatar from '@/ui/v2/Avatar';
import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button';
@@ -20,6 +22,7 @@ import { copy } from '@/utils/copy';
import getUserRoles from '@/utils/settings/getUserRoles';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import {
RemoteAppGetUsersDocument,
useGetRolesQuery,
useUpdateRemoteAppUserMutation,
} from '@/utils/__generated__/graphql';
@@ -34,7 +37,7 @@ import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import * as Yup from 'yup';
export interface EditUserFormProps {
export interface EditUserFormProps extends DialogFormProps {
/**
* This is the selected user from the user's table.
*/
@@ -42,10 +45,7 @@ export interface EditUserFormProps {
/**
* Function to be called when the form is submitted.
*/
onEditUser?: (
values: EditUserFormValues,
user: RemoteAppUser,
) => Promise<void>;
onSubmit?: (values: EditUserFormValues) => Promise<void>;
/**
* Function to be called when the operation is cancelled.
*/
@@ -53,19 +53,15 @@ export interface EditUserFormProps {
/**
* Function to be called when banning the user.
*/
onBanUser?: (user: RemoteAppUser) => Promise<void>;
onBanUser?: (user: RemoteAppUser) => Promise<void> | void;
/**
* Function to be called when deleting the user.
*/
onDeleteUser: (user: RemoteAppUser) => Promise<void>;
onDeleteUser: (user: RemoteAppUser) => Promise<void> | void;
/**
* User roles
*/
roles: { [key: string]: boolean }[];
/**
* Function to be called after a successful action.
*/
onSuccessfulAction?: () => Promise<void> | void;
}
export const EditUserFormValidationSchema = Yup.object({
@@ -87,12 +83,12 @@ export type EditUserFormValues = Yup.InferType<
>;
export default function EditUserForm({
location,
user,
onEditUser,
onSubmit,
onCancel,
onDeleteUser,
roles,
onSuccessfulAction,
}: EditUserFormProps) {
const theme = useTheme();
const { onDirtyStateChange, openDialog } = useDialog();
@@ -104,6 +100,7 @@ export default function EditUserForm({
const [updateUser] = useUpdateRemoteAppUserMutation({
client: remoteProjectGQLClient,
refetchQueries: [RemoteAppGetUsersDocument],
});
const form = useForm<EditUserFormValues>({
@@ -124,20 +121,19 @@ export default function EditUserForm({
const {
register,
handleSubmit,
formState: { errors, dirtyFields, isSubmitting, isValidating },
} = form;
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, 'drawer');
}, [isDirty, onDirtyStateChange]);
onDirtyStateChange(isDirty, location);
}, [isDirty, location, onDirtyStateChange]);
function handleChangeUserPassword() {
openDialog('EDIT_USER_PASSWORD', {
openDialog({
title: 'Change Password',
payload: { user },
component: <EditUserPasswordForm user={user} />,
});
}
@@ -156,11 +152,13 @@ export default function EditUserForm({
* both having to refetch this single user from the database again or causing a re-render of the drawer.
*/
async function handleUserDisabledStatus() {
const shouldBan = !isUserBanned;
const banUser = updateUser({
variables: {
id: user.id,
user: {
disabled: !isUserBanned,
disabled: shouldBan,
},
},
});
@@ -168,26 +166,23 @@ export default function EditUserForm({
await toast.promise(
banUser,
{
loading: user.disabled ? 'Unbanning user...' : 'Banning user...',
success: user.disabled
? 'User unbanned successfully.'
: 'User banned successfully',
error: user.disabled
? 'An error occurred while trying to unban the user.'
: 'An error occurred while trying to ban the user.',
loading: shouldBan ? 'Banning user...' : 'Unbanning user...',
success: shouldBan
? 'User banned successfully'
: 'User unbanned successfully.',
error: shouldBan
? 'An error occurred while trying to ban the user.'
: 'An error occurred while trying to unban the user.',
},
getToastStyleProps(),
);
await onSuccessfulAction();
}
return (
<FormProvider {...form}>
<Form
className="flex flex-col overflow-hidden border-t-1 lg:flex-auto lg:content-between"
onSubmit={handleSubmit(async (values) => {
await onEditUser(values, user);
})}
onSubmit={onSubmit}
>
<Box className="flex-auto divide-y overflow-y-auto">
<Box
@@ -268,7 +263,7 @@ export default function EditUserForm({
Created At
</InputLabel>
<Text className="col-span-3 font-medium">
{format(new Date(user.createdAt), 'yyyy-MM-dd hh:mm:ss')}
{format(new Date(user.createdAt), 'yyyy-MM-dd HH:mm:ss')}
</Text>
<InputLabel as="h3" className="col-span-1 self-center ">
@@ -276,7 +271,7 @@ export default function EditUserForm({
</InputLabel>
<Text className="col-span-3 font-medium">
{user.lastSeen
? `${format(new Date(user.lastSeen), 'yyyy-mm-dd hh:mm:ss')}`
? `${format(new Date(user.lastSeen), 'yyyy-MM-dd HH:mm:ss')}`
: '-'}
</Text>
</Box>

View File

@@ -1,6 +1,7 @@
import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form';
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
import type { DialogFormProps } from '@/types/common';
import { Alert } from '@/ui/Alert';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
@@ -14,18 +15,7 @@ import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import * as Yup from 'yup';
export interface EditUserPasswordFormValues {
/**
* Password for the user.
*/
password: string;
/**
* Confirm Password for the user.
*/
cpassword: string;
}
export interface EditUserPasswordFormProps {
export interface EditUserPasswordFormProps extends DialogFormProps {
/**
* Function to be called when the operation is cancelled.
*/
@@ -36,7 +26,7 @@ export interface EditUserPasswordFormProps {
user: RemoteAppGetUsersQuery['users'][0];
}
export const EditUserPasswordFormValidationSchema = Yup.object().shape({
export const validationSchema = Yup.object({
password: Yup.string()
.label('Users Password')
.min(8, 'Password must be at least 8 characters long.')
@@ -47,6 +37,8 @@ export const EditUserPasswordFormValidationSchema = Yup.object().shape({
.oneOf([Yup.ref('password')], 'Passwords do not match'),
});
export type EditUserPasswordFormValues = Yup.InferType<typeof validationSchema>;
export default function EditUserPasswordForm({
onCancel,
user,
@@ -63,7 +55,7 @@ export default function EditUserPasswordForm({
const form = useForm<EditUserPasswordFormValues>({
defaultValues: {},
reValidateMode: 'onSubmit',
resolver: yupResolver(EditUserPasswordFormValidationSchema),
resolver: yupResolver(validationSchema),
});
const handleSubmit = async ({ password }: EditUserPasswordFormValues) => {

View File

@@ -1,4 +1,5 @@
import { useDialog } from '@/components/common/DialogProvider';
import FormActivityIndicator from '@/components/common/FormActivityIndicator';
import type { EditUserFormValues } from '@/components/users/EditUserForm';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
@@ -17,7 +18,6 @@ import Text from '@/ui/v2/Text';
import getReadableProviderName from '@/utils/common/getReadableProviderName';
import getUserRoles from '@/utils/settings/getUserRoles';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import type { RemoteAppGetUsersQuery } from '@/utils/__generated__/graphql';
import {
useDeleteRemoteAppUserRolesMutation,
useGetRolesQuery,
@@ -25,16 +25,21 @@ import {
useRemoteAppDeleteUserMutation,
useUpdateRemoteAppUserMutation,
} from '@/utils/__generated__/graphql';
import type { ApolloQueryResult } from '@apollo/client';
import { useTheme } from '@mui/material';
import { formatDistance } from 'date-fns';
import kebabCase from 'just-kebab-case';
import dynamic from 'next/dynamic';
import Image from 'next/image';
import type { RemoteAppUser } from 'pages/[workspaceSlug]/[appSlug]/users';
import { Fragment, useMemo } from 'react';
import toast from 'react-hot-toast';
export interface UsersBodyProps<T = {}> {
const EditUserForm = dynamic(() => import('@/components/users/EditUserForm'), {
ssr: false,
loading: () => <FormActivityIndicator />,
});
export interface UsersBodyProps {
/**
* The users fetched from entering the users page given a limit and offset.
* @remark users will be an empty array if there are no users.
@@ -46,13 +51,10 @@ export interface UsersBodyProps<T = {}> {
* @example onSuccessfulAction={() => refetch()}
* @example onSuccessfulAction={() => router.reload()}
*/
onSuccessfulAction?: () => Promise<void> | void | Promise<T>;
onSubmit?: () => Promise<any>;
}
export default function UsersBody({
users,
onSuccessfulAction,
}: UsersBodyProps<ApolloQueryResult<RemoteAppGetUsersQuery>>) {
export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
const theme = useTheme();
const { openAlertDialog, openDrawer, closeDrawer } = useDialog();
const { currentApplication } = useCurrentWorkspaceAndApplication();
@@ -151,7 +153,8 @@ export default function UsersBody({
},
getToastStyleProps(),
);
await onSuccessfulAction?.();
await onSubmit?.();
closeDrawer();
}
@@ -181,7 +184,7 @@ export default function UsersBody({
getToastStyleProps(),
);
await onSuccessfulAction();
await onSubmit();
closeDrawer();
},
primaryButtonColor: 'error',
@@ -191,20 +194,20 @@ export default function UsersBody({
}
function handleViewUser(user: RemoteAppUser) {
openDrawer('EDIT_USER', {
openDrawer({
title: 'User Details',
payload: {
user,
onEditUser: handleEditUser,
onDeleteUser: handleDeleteUser,
onSuccessfulAction,
roles: allAvailableProjectRoles.map((role) => ({
[role.name]: user.roles.some(
(userRole) => userRole.role === role.name,
),
})),
},
component: (
<EditUserForm
user={user}
onSubmit={(values) => handleEditUser(values, user)}
onDeleteUser={handleDeleteUser}
roles={allAvailableProjectRoles.map((role) => ({
[role.name]: user.roles.some(
(userRole) => userRole.role === role.name,
),
}))}
/>
),
});
}

View File

@@ -1,4 +1,5 @@
import { useDialog } from '@/components/common/DialogProvider';
import EditWorkspaceNameForm from '@/components/home/EditWorkspaceNameForm';
import RemoveWorkspaceModal from '@/components/workspace/RemoveWorkspaceModal';
import { useUI } from '@/context/UIContext';
import { useGetWorkspace } from '@/hooks/use-GetWorkspace';
@@ -114,7 +115,7 @@ export default function WorkspaceHeader() {
<Dropdown.Item
className="py-2"
onClick={() => {
openDialog('EDIT_WORKSPACE_NAME', {
openDialog({
title: (
<span className="grid grid-flow-row">
<span>Change Workspace Name</span>
@@ -124,10 +125,12 @@ export default function WorkspaceHeader() {
</Text>
</span>
),
payload: {
currentWorkspaceName: currentWorkspace.name,
currentWorkspaceId: currentWorkspace.id,
},
component: (
<EditWorkspaceNameForm
currentWorkspaceId={currentWorkspace.id}
currentWorkspaceName={currentWorkspace.name}
/>
),
});
}}
>

View File

@@ -1,4 +1,5 @@
import { useDialog } from '@/components/common/DialogProvider';
import EditWorkspaceNameForm from '@/components/home/EditWorkspaceNameForm';
import Button from '@/ui/v2/Button';
import PlusCircleIcon from '@/ui/v2/icons/PlusCircleIcon';
import Text from '@/ui/v2/Text';
@@ -16,7 +17,7 @@ export function WorkspaceSection() {
variant="borderless"
color="secondary"
onClick={() => {
openDialog('EDIT_WORKSPACE_NAME', {
openDialog({
title: (
<span className="grid grid-flow-row">
<span>New Workspace</span>
@@ -26,6 +27,7 @@ export function WorkspaceSection() {
</Text>
</span>
),
component: <EditWorkspaceNameForm />,
});
}}
startIcon={<PlusCircleIcon />}

View File

@@ -2,6 +2,7 @@ import { useDialog } from '@/components/common/DialogProvider';
import Pagination from '@/components/common/Pagination';
import Container from '@/components/layout/Container';
import ProjectLayout from '@/components/layout/ProjectLayout';
import CreateUserForm from '@/components/users/CreateUserForm';
import UsersBody from '@/components/users/UsersBody';
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
@@ -25,7 +26,7 @@ export type RemoteAppUser = Exclude<
>;
export default function UsersPage() {
const { openDialog, closeDialog } = useDialog();
const { openDialog } = useDialog();
const remoteProjectGQLClient = useRemoteApplicationGQLClient();
const [searchString, setSearchString] = useState<string>('');
@@ -200,14 +201,9 @@ export default function UsersPage() {
);
function openCreateUserDialog() {
openDialog('CREATE_USER', {
openDialog({
title: 'Create User',
payload: {
onSuccess: async () => {
await refetchProjectUsers();
closeDialog();
},
},
component: <CreateUserForm onSubmit={refetchProjectUsers} />,
});
}
@@ -228,16 +224,16 @@ export default function UsersPage() {
if (loadingRemoteAppUsersQuery) {
return (
<Container
className="flex flex-col max-w-9xl h-full"
className="flex h-full max-w-9xl flex-col"
rootClassName="h-full"
>
<div className="flex flex-row place-content-between shrink-0 grow-0">
<div className="flex shrink-0 grow-0 flex-row place-content-between">
<Input
className="rounded-sm"
placeholder="Search users"
startAdornment={
<SearchIcon
className="w-4 h-4 ml-2 -mr-1 shrink-0"
className="ml-2 -mr-1 h-4 w-4 shrink-0"
sx={{ color: 'text.disabled' }}
/>
}
@@ -245,14 +241,14 @@ export default function UsersPage() {
/>
<Button
onClick={openCreateUserDialog}
startIcon={<PlusIcon className="w-4 h-4" />}
startIcon={<PlusIcon className="h-4 w-4" />}
size="small"
>
Create User
</Button>
</div>
<div className="overflow-hidden flex items-center justify-center flex-auto">
<div className="flex flex-auto items-center justify-center overflow-hidden">
<ActivityIndicator label="Loading users..." />
</div>
</Container>
@@ -260,14 +256,14 @@ export default function UsersPage() {
}
return (
<Container className="mx-auto space-y-5 overflow-x-hidden max-w-9xl">
<Container className="mx-auto max-w-9xl space-y-5 overflow-x-hidden">
<div className="flex flex-row place-content-between">
<Input
className="rounded-sm"
placeholder="Search users"
startAdornment={
<SearchIcon
className="w-4 h-4 ml-2 -mr-1 shrink-0"
className="ml-2 -mr-1 h-4 w-4 shrink-0"
sx={{ color: 'text.disabled' }}
/>
}
@@ -275,21 +271,21 @@ export default function UsersPage() {
/>
<Button
onClick={openCreateUserDialog}
startIcon={<PlusIcon className="w-4 h-4" />}
startIcon={<PlusIcon className="h-4 w-4" />}
size="small"
>
Create User
</Button>
</div>
{usersCount === 0 ? (
<Box className="flex flex-col items-center justify-center px-48 py-12 space-y-5 border rounded-lg shadow-sm">
<Box className="flex flex-col items-center justify-center space-y-5 rounded-lg border px-48 py-12 shadow-sm">
<UserIcon
strokeWidth={1}
className="w-10 h-10"
className="h-10 w-10"
sx={{ color: 'text.disabled' }}
/>
<div className="flex flex-col space-y-1">
<Text className="font-medium text-center" variant="h3">
<Text className="text-center font-medium" variant="h3">
There are no users yet
</Text>
<Text variant="subtitle1" className="text-center">
@@ -302,34 +298,34 @@ export default function UsersPage() {
color="primary"
className="w-full"
onClick={openCreateUserDialog}
startIcon={<PlusIcon className="w-4 h-4" />}
startIcon={<PlusIcon className="h-4 w-4" />}
>
Create User
</Button>
</div>
</Box>
) : (
<div className="grid grid-flow-row gap-2 lg:w-9xl">
<div className="grid w-full h-full grid-flow-row pb-4 overflow-hidden">
<Box className="grid w-full p-2 border-b md:grid-cols-6">
<div className="lg:w-9xl grid grid-flow-row gap-2">
<div className="grid h-full w-full grid-flow-row overflow-hidden pb-4">
<Box className="grid w-full border-b p-2 md:grid-cols-6">
<Text className="font-medium md:col-span-2">Name</Text>
<Text className="hidden font-medium md:block">Signed up at</Text>
<Text className="hidden font-medium md:block">Last Seen</Text>
<Text className="hidden col-span-2 font-medium md:block">
<Text className="col-span-2 hidden font-medium md:block">
OAuth Providers
</Text>
</Box>
{dataRemoteAppUsers?.filteredUsersAggreggate.aggregate.count ===
0 &&
usersCount !== 0 && (
<Box className="flex flex-col items-center justify-center px-48 py-12 space-y-5 border-b border-x">
<Box className="flex flex-col items-center justify-center space-y-5 border-x border-b px-48 py-12">
<UserIcon
strokeWidth={1}
className="w-10 h-10"
className="h-10 w-10"
sx={{ color: 'text.disabled' }}
/>
<div className="flex flex-col space-y-1">
<Text className="font-medium text-center" variant="h3">
<Text className="text-center font-medium" variant="h3">
No results for &quot;{searchString}&quot;
</Text>
<Text variant="subtitle1" className="text-center">
@@ -340,10 +336,7 @@ export default function UsersPage() {
)}
{thereAreUsers && (
<div className="grid grid-flow-row gap-4">
<UsersBody
users={users}
onSuccessfulAction={refetchProjectUsers}
/>
<UsersBody users={users} onSubmit={refetchProjectUsers} />
<Pagination
className="px-2"
totalNrOfPages={nrOfPages}

View File

@@ -0,0 +1,10 @@
/**
* This interface is used to define the basic properties of a form that is
* rendered inside a drawer or a dialog.
*/
export interface DialogFormProps {
/**
* Determines whether the form is rendered inside a drawer or a dialog.
*/
location?: 'drawer' | 'dialog';
}

View File

@@ -28,7 +28,7 @@ The GraphQL API is available at `https://[subdomain].graphql.[region].nhost.run/
## GraphQL Clients for JavaScript
The [Nhost JavaScript client](/reference/javascript) comes with a simple [GraphQL client](/reference/javascript/nhost-js/graphql) that works well for the backend or simple applications.
The [Nhost JavaScript client](/reference/javascript) comes with a simple [GraphQL client](/reference/javascript/graphql) that works well for the backend or simple applications.
When building more complex frontend applications, we recommend using a more advanced GraphQL client such as:

View File

@@ -10,8 +10,8 @@ In this section:
- [Overview](/reference/javascript)
- [Authentication](/reference/javascript/auth)
- [Storage](/reference/javascript/storage)
- [Functions](/reference/javascript/nhost-js/functions)
- [GraphQL](/reference/javascript/nhost-js/graphql)
- [Functions](/reference/javascript/functions)
- [GraphQL](/reference/javascript/graphql)
### React

View File

@@ -0,0 +1,60 @@
---
title: call()
sidebar_label: call()
slug: /reference/javascript/functions/call
description: Use `nhost.functions.call` to call (sending a POST request to) a serverless function.
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/nhost-js/src/clients/functions/index.ts#L55
---
# `call()`
## Overload 1 of 2
Use `nhost.functions.call` to call (sending a POST request to) a serverless function.
:::caution Deprecated
Axios will be replaced by cross-fetch in the near future. Only the headers configuration will be kept.
:::
### Parameters
---
**<span className="parameter-name">url</span>** <span className="optional-status">required</span> <code>string</code>
---
**<span className="parameter-name">data</span>** <span className="optional-status">optional</span> <code>D</code>
---
**<span className="parameter-name">config</span>** <span className="optional-status">optional</span> <code>AxiosRequestConfig&lt;any&gt; &amp; { useAxios: "true" } &amp; [`NhostFunctionCallConfig`](/reference/javascript/functions/types/nhost-function-call-config) &amp; { useAxios: "true" }</code>
---
## Overload 2 of 2
Use `nhost.functions.call` to call (sending a POST request to) a serverless function.
```ts
await nhost.functions.call('send-welcome-email', {
email: 'joe@example.com',
name: 'Joe Doe'
})
```
### Parameters
---
**<span className="parameter-name">url</span>** <span className="optional-status">required</span> <code>string</code>
---
**<span className="parameter-name">data</span>** <span className="optional-status">required</span> <code>D</code>
---
**<span className="parameter-name">config</span>** <span className="optional-status">optional</span> <code>[`NhostFunctionCallConfig`](/reference/javascript/functions/types/nhost-function-call-config) &amp; { useAxios: "false" }</code>
---

View File

@@ -0,0 +1,23 @@
---
title: setAccessToken()
sidebar_label: setAccessToken()
slug: /reference/javascript/functions/set-access-token
description: Use `nhost.functions.setAccessToken` to a set an access token to be used in subsequent functions requests. Note that if you're signin in users with `nhost.auth.signIn()` the access token will be set automatically.
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/nhost-js/src/clients/functions/index.ts#L155
---
# `setAccessToken()`
Use `nhost.functions.setAccessToken` to a set an access token to be used in subsequent functions requests. Note that if you're signin in users with `nhost.auth.signIn()` the access token will be set automatically.
```ts
nhost.functions.setAccessToken('some-access-token')
```
## Parameters
---
**<span className="parameter-name">accessToken</span>** <span className="optional-status">required</span> <code>undefined &#124; string</code>
---

View File

@@ -0,0 +1,22 @@
---
title: NhostFunctionsClient
sidebar_label: Functions
description: No description provided.
slug: /reference/javascript/functions
custom_edit_url: https://github.com/nhost/nhost/edit/main/docs/docs/reference/javascript/functions/index.mdx
---
# `NhostFunctionsClient`
## Parameters
---
**<span className="parameter-name">params</span>** <span className="optional-status">required</span> [`NhostFunctionsConstructorParams`](/reference/javascript/functions/types/nhost-functions-constructor-params)
| Property | Type | Required | Notes |
| :--------------------------------------------------------------------------------------------- | :------------------ | :------: | :---------------------------------------------------------------------------------------- |
| <span className="parameter-name"><span className="light-grey">params.</span>url</span> | <code>string</code> | ✔️ | Serverless Functions endpoint. |
| <span className="parameter-name"><span className="light-grey">params.</span>adminSecret</span> | <code>string</code> | | Admin secret. When set, it is sent as an `x-hasura-admin-secret` header for all requests. |
---

View File

@@ -0,0 +1,19 @@
---
title: NhostFunctionCallConfig
sidebar_label: NhostFunctionCallConfig
description: Subset of RequestInit parameters that are supported by the functions client
displayed_sidebar: referenceSidebar
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/nhost-js/src/clients/functions/types.ts#L41
---
# `NhostFunctionCallConfig`
Subset of RequestInit parameters that are supported by the functions client
## Parameters
---
**<span className="parameter-name">headers</span>** <span className="optional-status">optional</span> <code>Record&lt;string, string&gt;</code>
---

View File

@@ -0,0 +1,15 @@
---
title: NhostFunctionCallResponse
sidebar_label: NhostFunctionCallResponse
description: No description provided.
displayed_sidebar: referenceSidebar
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/nhost-js/src/clients/functions/types.ts#L15
---
# `NhostFunctionCallResponse`
```ts
type NhostFunctionCallResponse =
| { res: { data: T; status: number; statusText: string }; error: null }
| { res: null; error: ErrorPayload }
```

View File

@@ -0,0 +1,25 @@
---
title: NhostFunctionsConstructorParams
sidebar_label: NhostFunctionsConstructorParams
description: No description provided.
displayed_sidebar: referenceSidebar
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/nhost-js/src/clients/functions/types.ts#L4
---
# `NhostFunctionsConstructorParams`
## Parameters
---
**<span className="parameter-name">url</span>** <span className="optional-status">required</span> <code>string</code>
Serverless Functions endpoint.
---
**<span className="parameter-name">adminSecret</span>** <span className="optional-status">optional</span> <code>string</code>
Admin secret. When set, it is sent as an `x-hasura-admin-secret` header for all requests.
---

View File

@@ -10,12 +10,12 @@ The Nhost JavaScript client is the primary way of interacting with your Nhost pr
- [Authentication](/reference/javascript/auth)
- [Storage](/reference/javascript/storage)
- [Functions](/reference/javascript/nhost-js/functions)
- [GraphQL](/reference/javascript/nhost-js/graphql)
- [Functions](/reference/javascript/functions)
- [GraphQL](/reference/javascript/graphql)
## Installation
Install the the Nhost client together with GraphQL:
Install the Nhost client together with GraphQL:
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>

View File

@@ -111,12 +111,12 @@ const sidebars = {
label: 'Functions',
link: {
type: 'doc',
id: 'reference/docgen/javascript/nhost-js/content/nhost-functions-client/index'
id: 'reference/javascript/functions/index'
},
items: [
{
type: 'autogenerated',
dirName: 'reference/docgen/javascript/nhost-js/content/nhost-functions-client/content'
dirName: 'reference/javascript/functions/content'
}
]
},
@@ -125,12 +125,12 @@ const sidebars = {
label: 'GraphQL',
link: {
type: 'doc',
id: 'reference/docgen/javascript/nhost-js/content/nhost-graphql-client/index'
id: 'reference/docgen/javascript/graphql/content/nhost-graphql-client/index'
},
items: [
{
type: 'autogenerated',
dirName: 'reference/docgen/javascript/nhost-js/content/nhost-graphql-client/content'
dirName: 'reference/docgen/javascript/graphql/content/nhost-graphql-client/content'
}
]
}

View File

@@ -1,5 +1,26 @@
# @nhost-examples/codegen-react-apollo
## 0.1.7
### Patch Changes
- 01318860: fix(nhost-js): use correct URL for functions requests
- Updated dependencies [01318860]
- @nhost/react-apollo@5.0.5
- @nhost/react@2.0.4
## 0.1.6
### Patch Changes
- 445d8ef4: chore(deps): bump `@nhost/react` to 2.0.3
- 445d8ef4: chore(deps): bump `@nhost/react-apollo` to 5.0.4
- Updated dependencies [445d8ef4]
- Updated dependencies [445d8ef4]
- Updated dependencies [445d8ef4]
- @nhost/react-apollo@5.0.4
- @nhost/react@2.0.3
## 0.1.5
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/codegen-react-apollo",
"version": "0.1.5",
"version": "0.1.7",
"private": true,
"scripts": {
"codegen": "graphql-codegen",

View File

@@ -1,5 +1,21 @@
# @nhost-examples/codegen-react-query
## 0.1.7
### Patch Changes
- 01318860: fix(nhost-js): use correct URL for functions requests
- Updated dependencies [01318860]
- @nhost/react@2.0.4
## 0.1.6
### Patch Changes
- 445d8ef4: chore(deps): bump `@nhost/react` to 2.0.3
- Updated dependencies [445d8ef4]
- @nhost/react@2.0.3
## 0.1.5
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/codegen-react-query",
"version": "0.1.5",
"version": "0.1.7",
"private": true,
"scripts": {
"codegen": "graphql-codegen",

View File

@@ -1,5 +1,24 @@
# @nhost-examples/react-urql
## 0.0.4
### Patch Changes
- 01318860: fix(nhost-js): use correct URL for functions requests
- Updated dependencies [01318860]
- @nhost/react-urql@2.0.4
- @nhost/react@2.0.4
## 0.0.3
### Patch Changes
- 445d8ef4: chore(deps): bump `@nhost/react` to 2.0.3
- Updated dependencies [445d8ef4]
- Updated dependencies [445d8ef4]
- @nhost/react-urql@2.0.3
- @nhost/react@2.0.3
## 0.0.2
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@nhost-examples/codegen-react-urql",
"private": true,
"version": "0.0.2",
"version": "0.0.4",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",

View File

@@ -1,5 +1,11 @@
# @nhost-examples/docker-compose
## 0.0.5
### Patch Changes
- 01318860: fix(nhost-js): use correct URL for functions requests
## 0.0.4
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/docker-compose",
"version": "0.0.4",
"version": "0.0.5",
"private": true,
"scripts": {
"e2e": "vitest run"

View File

@@ -1,5 +1,21 @@
# @nhost-examples/multi-tenant-one-to-many
## 1.0.3
### Patch Changes
- 01318860: fix(nhost-js): use correct URL for functions requests
- Updated dependencies [01318860]
- @nhost/nhost-js@2.0.4
## 1.0.2
### Patch Changes
- 445d8ef4: chore(deps): bump `@nhost/nhost-js` version to 2.0.3
- Updated dependencies [445d8ef4]
- @nhost/nhost-js@2.0.3
## 1.0.1
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@nhost-examples/multi-tenant-one-to-many",
"private": true,
"version": "1.0.1",
"version": "1.0.3",
"description": "",
"main": "index.js",
"scripts": {},

View File

@@ -1,5 +1,29 @@
# @nhost-examples/nextjs
## 0.1.7
### Patch Changes
- 01318860: fix(nhost-js): use correct URL for functions requests
- Updated dependencies [01318860]
- @nhost/react-apollo@5.0.5
- @nhost/nextjs@1.13.10
- @nhost/react@2.0.4
## 0.1.6
### Patch Changes
- 445d8ef4: chore(deps): bump `@nhost/react` to 2.0.3
- 445d8ef4: chore(deps): bump `@nhost/react-apollo` to 5.0.4
- 445d8ef4: chore(deps): bump `@nhost/nextjs` to 1.13.9
- Updated dependencies [445d8ef4]
- Updated dependencies [445d8ef4]
- Updated dependencies [445d8ef4]
- @nhost/react-apollo@5.0.4
- @nhost/nextjs@1.13.9
- @nhost/react@2.0.3
## 0.1.5
### Patch Changes

View File

@@ -18,6 +18,7 @@ export function authProtected(Comp) {
if (isLoading) {
return <div>Loading...</div>
}
return <Comp {...props} />
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/nextjs",
"version": "0.1.5",
"version": "0.1.7",
"private": true,
"scripts": {
"dev": "next dev",
@@ -35,8 +35,8 @@
"@types/react": "18.0.25",
"@xstate/inspect": "^0.6.2",
"eslint-config-next": "12.0.10",
"typescript": "4.5.5",
"typescript": "^4.8.2",
"ws": "^8.8.1",
"xstate": "^4.33.5"
}
}
}

View File

@@ -1,5 +1,26 @@
# @nhost-examples/react-apollo
## 0.1.9
### Patch Changes
- 01318860: fix(nhost-js): use correct URL for functions requests
- Updated dependencies [01318860]
- @nhost/react-apollo@5.0.5
- @nhost/react@2.0.4
## 0.1.8
### Patch Changes
- 445d8ef4: chore(deps): bump `@nhost/react` to 2.0.3
- 445d8ef4: chore(deps): bump `@nhost/react-apollo` to 5.0.4
- Updated dependencies [445d8ef4]
- Updated dependencies [445d8ef4]
- Updated dependencies [445d8ef4]
- @nhost/react-apollo@5.0.4
- @nhost/react@2.0.3
## 0.1.7
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/react-apollo",
"version": "0.1.7",
"version": "0.1.9",
"private": true,
"dependencies": {
"@apollo/client": "^3.6.9",

View File

@@ -1,5 +1,21 @@
# @nhost-examples/react-gqty
## 0.0.6
### Patch Changes
- 01318860: fix(nhost-js): use correct URL for functions requests
- Updated dependencies [01318860]
- @nhost/react@2.0.4
## 0.0.5
### Patch Changes
- 445d8ef4: chore(deps): bump `@nhost/react` to 2.0.3
- Updated dependencies [445d8ef4]
- @nhost/react@2.0.3
## 0.0.4
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@nhost-examples/react-gqty",
"private": true,
"version": "0.0.4",
"version": "0.0.6",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -0,0 +1,7 @@
# @nhost-examples/seed-data-storage
## 0.0.2
### Patch Changes
- 01318860: fix(nhost-js): use correct URL for functions requests

View File

@@ -1,7 +1,7 @@
{
"name": "@nhost-examples/seed-data-storage",
"private": true,
"version": "0.0.1",
"version": "0.0.2",
"scripts": {
"seed-storage": "./seed-storage.sh"
}

View File

@@ -1,5 +1,13 @@
# @nhost-examples/serverless-functions
## 0.0.7
### Patch Changes
- 01318860: fix(nhost-js): use correct URL for functions requests
- Updated dependencies [01318860]
- @nhost/stripe-graphql-js@1.0.2
## 0.0.6
### Patch Changes

View File

@@ -1,13 +1,13 @@
{
"name": "@nhost-examples/serverless-functions",
"private": true,
"version": "0.0.6",
"version": "0.0.7",
"devDependencies": {
"@types/express": "^4.17.13"
},
"dependencies": {
"@graphql-yoga/node": "^2.13.13",
"@nhost/stripe-graphql-js": "^1.0.1",
"@nhost/stripe-graphql-js": "^1.0.2",
"@pothos/core": "^3.21.0",
"cross-fetch": "^3.1.5",
"graphql": "15.7.2",

View File

@@ -1,5 +1,23 @@
# @nhost-examples/vue-apollo
## 0.0.7
### Patch Changes
- 01318860: fix(nhost-js): use correct URL for functions requests
- Updated dependencies [01318860]
- @nhost/apollo@5.0.4
- @nhost/vue@1.13.10
## 0.0.6
### Patch Changes
- 445d8ef4: chore(deps): bump `@nhost/apollo` to 5.0.3
- Updated dependencies [445d8ef4]
- @nhost/apollo@5.0.3
- @nhost/vue@1.13.9
## 0.0.5
### Patch Changes

View File

@@ -1,10 +1,10 @@
{
"name": "@nhost-examples/vue-apollo",
"private": true,
"version": "0.0.5",
"version": "0.0.7",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"build": "vite build",
"preview": "vite preview",
"prettier": "prettier --check src/",
"prettier:fix": "prettier --write src/",
@@ -34,7 +34,7 @@
"@vitejs/plugin-vue": "^4.0.0",
"@xstate/inspect": "^0.6.2",
"sass": "1.32.0",
"typescript": "^4.8.4",
"typescript": "4.9.4",
"vite": "^4.0.2",
"vue-tsc": "^0.38.9"
},

View File

@@ -1,5 +1,23 @@
# @nhost-examples/vue-quickstart
## 0.0.6
### Patch Changes
- 01318860: fix(nhost-js): use correct URL for functions requests
- Updated dependencies [01318860]
- @nhost/apollo@5.0.4
- @nhost/vue@1.13.10
## 0.0.5
### Patch Changes
- 445d8ef4: chore(deps): bump `@nhost/apollo` to 5.0.3
- Updated dependencies [445d8ef4]
- @nhost/apollo@5.0.3
- @nhost/vue@1.13.9
## 0.0.4
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/vue-quickstart",
"version": "0.0.4",
"version": "0.0.6",
"private": true,
"scripts": {
"build": "vite build",

View File

@@ -1,5 +1,51 @@
# @nhost/apollo
## 5.0.5
### Patch Changes
- Updated dependencies [3c7cf92e]
- @nhost/nhost-js@2.0.5
## 5.0.4
### Patch Changes
- 01318860: fix(nhost-js): use correct URL for functions requests
- Updated dependencies [01318860]
- @nhost/nhost-js@2.0.4
## 5.0.3
### Patch Changes
- 445d8ef4: chore(deps): bump `@nhost/nhost-js` version to 2.0.3
- Updated dependencies [445d8ef4]
- @nhost/nhost-js@2.0.3
## 5.0.2
### Patch Changes
- Updated dependencies [2d9145f9]
- @nhost/nhost-js@2.0.2
## 5.0.1
### Patch Changes
- @nhost/nhost-js@2.0.1
## 5.0.0
### Patch Changes
- Updated dependencies [c9d2d31a]
- Updated dependencies [80bbd3a1]
- Updated dependencies [80bbd3a1]
- Updated dependencies [2949ff0f]
- @nhost/nhost-js@2.0.0
## 4.13.4
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/apollo",
"version": "4.13.4",
"version": "5.0.5",
"description": "Nhost Apollo Client library",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,11 @@
# @nhost/google-translation
## 0.0.3
### Patch Changes
- 01318860: fix(nhost-js): use correct URL for functions requests
## 0.0.2
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/google-translation",
"version": "0.0.2",
"version": "0.0.3",
"description": "Google Translation GraphQL API",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,54 @@
# @nhost/react-apollo
## 5.0.6
### Patch Changes
- @nhost/apollo@5.0.5
- @nhost/react@2.0.5
## 5.0.5
### Patch Changes
- 01318860: fix(nhost-js): use correct URL for functions requests
- Updated dependencies [01318860]
- @nhost/apollo@5.0.4
- @nhost/react@2.0.4
## 5.0.4
### Patch Changes
- 445d8ef4: chore(deps): bump `@nhost/react` to 2.0.3
- 445d8ef4: chore(deps): bump `@nhost/apollo` to 5.0.3
- Updated dependencies [445d8ef4]
- @nhost/apollo@5.0.3
- @nhost/react@2.0.3
## 5.0.3
### Patch Changes
- @nhost/apollo@5.0.2
- @nhost/react@2.0.2
## 5.0.2
### Patch Changes
- @nhost/apollo@5.0.1
- @nhost/react@2.0.1
## 5.0.1
### Patch Changes
- Updated dependencies [19b11d40]
- Updated dependencies [19b11d40]
- @nhost/react@2.0.0
- @nhost/apollo@5.0.0
## 4.13.5
### Patch Changes

Some files were not shown because too many files have changed in this diff Show More