Compare commits

..

257 Commits

Author SHA1 Message Date
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
Szilárd Dóró
7f7e7ea7d4 Merge pull request #1638 from nhost/changeset-release/main
chore: update versions
2023-02-20 11:05:47 +01:00
github-actions[bot]
aaaf2dc9c5 chore: update versions 2023-02-20 10:02:33 +00:00
Szilárd Dóró
fa9c1ea28c Merge pull request #1626 from nhost/renovate/react-18.x
chore(deps): update dependency @types/react to v18.0.28
2023-02-20 11:01:06 +01:00
Szilárd Dóró
87eda76e2b chore(dashboard): bump @types/react-dom to v18.0.11 2023-02-20 10:17:41 +01:00
Szilárd Dóró
8a596f2a9e Merge pull request #1621 from nhost/feat/show-version
feat(dashboard): show dashboard version
2023-02-20 09:13:40 +01:00
Szilárd Dóró
89b70eb93c Merge pull request #1627 from nhost/changeset-release/main
chore: update versions
2023-02-17 13:09:56 +01:00
renovate[bot]
d6d2381598 chore(deps): update dependency @types/react to v18.0.28 2023-02-17 11:24:40 +00:00
github-actions[bot]
860d872d07 chore: update versions 2023-02-17 11:18:24 +00:00
Szilárd Dóró
b9917c0c69 Merge pull request #1628 from nhost/chore/react-monorepo-18
chore(deps): bump `react` to v18
2023-02-17 12:16:58 +01:00
Szilárd Dóró
bf1e4071db chore(deps): bump react to v18 2023-02-17 10:36:25 +01:00
Szilárd Dóró
e5601581f5 Merge pull request #1624 from nhost/renovate-changesets
chore: create changesest from Renovate bumps
2023-02-17 10:03:53 +01:00
Szilárd Dóró
5013213bc3 fix changesets 2023-02-17 10:03:22 +01:00
szilarddoro
8be094be54 fix(deps): update docusaurus monorepo to v2.3.1 2023-02-17 08:56:50 +00:00
Szilárd Dóró
43e5221119 Merge pull request #1559 from nhost/renovate/docusaurus-monorepo
fix(deps): update docusaurus monorepo to v2.3.1
2023-02-17 09:55:40 +01:00
Szilárd Dóró
6f8feaffc5 Merge pull request #1518 from nhost/renovate/commander-10.x
fix(deps): update dependency commander to v10
2023-02-17 09:30:45 +01: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ó
cf347341d4 Merge pull request #1620 from nhost/changeset-release/main
chore: update versions
2023-02-16 09:54:38 +01:00
renovate[bot]
5262fac6d5 fix(deps): update docusaurus monorepo to v2.3.1 2023-02-16 08:39:58 +00:00
github-actions[bot]
d6c670a78b chore: update versions 2023-02-16 08:32:50 +00:00
Szilárd Dóró
197e406209 Merge pull request #1614 from nhost/fix/deployments-are-undefined
fix(dashboard): don't break the UI when deployments are unavailable
2023-02-16 09:30:53 +01:00
Szilárd Dóró
6f0ac5706c feat(dashboard): show dashboard version 2023-02-15 19:19:15 +01:00
Szilárd Dóró
5fc78964dc fix(dashboard): fix deployments on the deployments page 2023-02-15 18:32:55 +01:00
Szilárd Dóró
1bb71df7a1 Merge pull request #1616 from nhost/changeset-release/main
chore: update versions
2023-02-15 15:30:21 +01:00
Szilárd Dóró
5880f0cd17 chore(hasura-auth-js): bump msw version 2023-02-15 15:14:15 +01:00
github-actions[bot]
d7404e2247 chore: update versions 2023-02-15 14:04:47 +00:00
Szilárd Dóró
2aa91cf266 Merge pull request #1613 from nhost/fix/header-navlinks
fix(dashboard): fix header link color
2023-02-15 15:03:27 +01:00
Szilárd Dóró
b2b17d71aa fix(dashboard): fix build error 2023-02-15 14:16:54 +01:00
Szilárd Dóró
0785a41070 fix lockfile 2023-02-15 13:52:52 +01:00
Szilárd Dóró
c6c8569088 chore(dashboard): extended deployment tests 2023-02-15 13:51:56 +01:00
Szilárd Dóró
c38f60740e chore(dashboard): added deployment list tests 2023-02-15 13:45:32 +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
Szilárd Dóró
a37a430b9f fix(dashboard): don't break UI when deployments are unavailable 2023-02-15 11:42:28 +01:00
Szilárd Dóró
7b970e6858 fix(dashboard): fix header link color 2023-02-15 09:58:53 +01:00
Szilárd Dóró
5bdb2c605d Merge pull request #1612 from nhost/changeset-release/main 2023-02-15 09:08:22 +01:00
Johan Eliasson
a08cfc696d Merge pull request #1611 from nhost/docs-ytg89ji
CLI docs updates.
2023-02-15 08:09:41 +01:00
Johan Eliasson
9c4223c547 Update docs/docs/reference/cli/up.mdx
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2023-02-15 08:09:12 +01:00
github-actions[bot]
7de3ed5323 chore: update versions 2023-02-14 15:52:32 +00:00
Szilárd Dóró
83f4e32972 Merge pull request #1610 from nhost/feat/new-sign-in-page 2023-02-14 16:50:48 +01:00
Szilárd Dóró
7b86f38ba3 fix(dashboard): add autofocus to reset input 2023-02-14 16:37:23 +01:00
Szilárd Dóró
a689b0dbe0 fix(dashboard): fix auth pages button feedback 2023-02-14 12:04:21 +01:00
Szilárd Dóró
14235d08bc chore(dashboard): add autofocus to sign in and sign up input fields 2023-02-14 11:55:56 +01:00
Szilárd Dóró
e0a987248f fix(dashboard): fix mobile responsive layout 2023-02-14 11:29:53 +01:00
Szilárd Dóró
1303fc60ac feat(dashboard): update sign in and sign up pages 2023-02-14 11:24:12 +01:00
Johan Eliasson
172cab87c7 updates 2023-02-14 09:38:26 +01:00
Johan Eliasson
a463d5c3bf updates 2023-02-14 08:47:47 +01:00
Szilárd Dóró
041385f19a chore(dashboard): cleanup
- reset original page route structure
2023-02-13 14:08:43 +01:00
Szilárd Dóró
47949ed4af Update logo.svg 2023-02-13 11:57:10 +01:00
Szilárd Dóró
6a52a267ff Update logo.svg 2023-02-13 11:56:27 +01:00
Szilárd Dóró
f33242f2c4 chore(dashboard): add changeset 2023-02-13 11:51:03 +01:00
Szilárd Dóró
b9013e2f6e feat(dashboard): add reset password page 2023-02-13 11:50:22 +01:00
Szilárd Dóró
bbe6fb39c8 feat(dashboard): update sign in page 2023-02-13 11:35:31 +01:00
Szilárd Dóró
955e3e1316 feat(dashboard): finalize sign up pages 2023-02-13 11:07:11 +01:00
Szilárd Dóró
2dba783ad3 feat(dashboard): updated sign up pages 2023-02-13 10:59:40 +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
Szilárd Dóró
dbc2fadd87 Merge pull request #1606 from nhost/changeset-release/main
chore: update versions
2023-02-10 14:57:35 +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
github-actions[bot]
4578065807 chore: update versions 2023-02-10 12:30:32 +00:00
Szilárd Dóró
feb0c20eb3 Merge pull request #1574 from nhost/fix/dashboard-dark-mode
fix(dashboard): use correct theme color in dark mode
2023-02-10 13:29:17 +01:00
Szilárd Dóró
e3efed65a4 Merge pull request #1605 from nhost/changeset-release/main
chore: update versions
2023-02-09 14:54:24 +01:00
github-actions[bot]
194dec1f00 chore: update versions 2023-02-09 13:22:00 +00:00
Szilárd Dóró
0a1b36e600 Merge pull request #1604 from nhost/fix/logs-allow-hasura
fix(dashboard): re-enable Hasura on logs page
2023-02-09 14:20:41 +01:00
Szilárd Dóró
902f486bbe fix(dashboard): re-enable Hasura on logs page 2023-02-09 14:18:52 +01:00
Szilárd Dóró
fa544cc696 Merge pull request #1601 from nhost/changeset-release/main
chore: update versions
2023-02-09 11:45:11 +01:00
Johan Eliasson
7f959b3544 Merge pull request #1545 from nhost/docs-general-9ashd9asd
docs(graphql): updates
2023-02-09 08:18:43 +01:00
github-actions[bot]
bf5056f14b chore: update versions 2023-02-08 17:10:41 +00:00
Szilárd Dóró
b3e674ced0 Merge pull request #1600 from nhost/fix/permissions
fix(dashboard): apply select permissions properly
2023-02-08 18:09:25 +01:00
Szilárd Dóró
1f9720fa25 fix(dashboard): apply select permissions properly 2023-02-08 15:42:15 +01:00
Szilárd Dóró
747bc1104a Merge pull request #1599 from nhost/changeset-release/main
chore: update versions
2023-02-08 10:38:40 +01:00
github-actions[bot]
887c168b1b chore: update versions 2023-02-08 09:37:42 +00:00
Szilárd Dóró
038d903555 Merge pull request #1598 from nhost/country-code-fix
Use country code instead of name
2023-02-08 10:36:31 +01:00
Szilárd Dóró
deb14b510b chore(dashboard): add changeset 2023-02-08 10:35:52 +01:00
Johan Eliasson
8ee23c9303 use code 2023-02-08 10:22:56 +01:00
Pilou
bca1835ecd Merge pull request #1586 from nhost/docs/typo
Correct useMultipleFilesUpload inline documentation
2023-02-06 14:28:28 +01:00
Pierre-Louis Mercereau
1527b0a455 remove type-fest dependency 2023-02-06 14:18:13 +01:00
Pilou
d537103ede Merge pull request #1588 from nhost/changeset-release/main
chore: update versions
2023-02-06 14:10:09 +01:00
github-actions[bot]
a27a45012f chore: update versions 2023-02-06 13:07:38 +00:00
Pilou
3a4f92be81 Merge pull request #1585 from nhost/fix/timers
exponential backoff
2023-02-06 14:06:11 +01:00
Pierre-Louis Mercereau
375e53a3f0 update ts 2023-02-06 13:35:33 +01:00
Pierre-Louis Mercereau
2c72f80c9f increase timeout again 2023-02-06 13:31:22 +01:00
Pierre-Louis Mercereau
25212e3651 increase timeout in cypress 2023-02-06 13:10:37 +01:00
Pierre-Louis Mercereau
96e3ca5a32 basic readme 2023-02-06 12:53:11 +01:00
Pierre-Louis Mercereau
82bb97f379 increase timeouts 2023-02-06 12:13:15 +01:00
Pierre-Louis Mercereau
0e570df9c5 missing dependency 2023-02-06 12:11:02 +01:00
Pierre-Louis Mercereau
d1ba284d94 increase intervals 2023-02-06 11:55:12 +01:00
Pierre-Louis Mercereau
75b913fc4f increase timeout 2023-02-06 10:53:12 +01:00
Pierre-Louis Mercereau
8e74cee080 remove only 2023-02-06 10:52:08 +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
eb55408f85 typo 2023-02-06 10:36:46 +01:00
Pierre-Louis Mercereau
9996acf824 pow 2023-02-06 10:24:46 +01:00
Pierre-Louis Mercereau
12ff631386 exponential backoff 2023-02-06 10:17:27 +01:00
Pierre-Louis Mercereau
74feaf6add unions 2023-02-02 11:47:29 +01:00
Szilárd Dóró
e9c8909c6b fix(dashboard): use correct theme color in dark mode 2023-02-01 18:31:04 +01:00
Johan Eliasson
d539a103d9 Update docs/docs/graphql/permissions.mdx
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2023-02-01 09:38:31 +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
Johan Eliasson
1bc3c30f85 formatting 2023-01-27 22:26:29 +01:00
Johan Eliasson
4be6406a1e updates 2023-01-27 22:18:44 +01:00
renovate[bot]
88a992ba36 fix(deps): update dependency commander to v10 2023-01-14 04:54:59 +00: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
252 changed files with 4966 additions and 4140 deletions

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,117 @@
# @nhost/dashboard
## 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
- 87eda76e: chore(dashboard): bump `@types/react` to v18.0.28 and `@types/react-dom` to v18.0.11
- 6f0ac570: feat(dashboard): show dashboard version in account menu
## 0.11.11
### Patch Changes
- bf1e4071: chore(dashboard): bump `react-is` version to `18.2.0`
- Updated dependencies [bf1e4071]
- Updated dependencies [5013213b]
- @nhost/nextjs@1.13.5
- @nhost/react-apollo@4.13.5
## 0.11.10
### Patch Changes
- a37a430b: fix(dashboard): don't break UI when deployments are unavailable
- @nhost/react-apollo@4.13.4
- @nhost/nextjs@1.13.4
## 0.11.9
### Patch Changes
- 7b970e68: fix(dashboard): fix header link color
## 0.11.8
### Patch Changes
- f33242f2: feat(dashboard): add new sign up, sign in and reset password pages
## 0.11.7
### Patch Changes
- e9c8909c: fix(dashboard): use correct theme color in dark mode
## 0.11.6
### Patch Changes
- 902f486b: fix(dashboard): re-enable Hasura on logs page
## 0.11.5
### Patch Changes
- 1f9720fa: fix(dashboard): apply select permissions properly
## 0.11.4
### Patch Changes
- deb14b51: fix(dashboard): don't break billing form
## 0.11.3
### Patch Changes
- @nhost/react-apollo@4.13.3
- @nhost/nextjs@1.13.3
## 0.11.2
### Patch Changes

View File

@@ -2,6 +2,7 @@ const path = require('path');
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
const { version } = require('./package.json');
module.exports = withBundleAnalyzer({
reactStrictMode: true,
@@ -10,6 +11,9 @@ module.exports = withBundleAnalyzer({
experimental: {
outputFileTracingRoot: path.join(__dirname, '../../'),
},
publicRuntimeConfig: {
version,
},
eslint: {
dirs: ['src'],
},

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "0.11.2",
"version": "0.11.17",
"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",
@@ -67,7 +66,7 @@
"react-error-boundary": "^3.1.4",
"react-hook-form": "^7.39.5",
"react-hot-toast": "^2.4.0",
"react-is": "17.0.2",
"react-is": "18.2.0",
"react-loading-skeleton": "^2.2.0",
"react-merge-refs": "^1.1.0",
"react-syntax-highlighter": "^15.4.5",
@@ -98,15 +97,15 @@
"@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",
"@types/pluralize": "^0.0.29",
"@types/react": "18.0.25",
"@types/react-dom": "18.0.10",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"@types/react-table": "^7.7.12",
"@types/testing-library__jest-dom": "^5.14.5",
"@types/validator": "^13.7.10",
@@ -129,7 +128,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"jsdom": "^21.0.0",
"lint-staged": ">=13",
"msw": "^0.49.0",
"msw": "^1.0.1",
"msw-storybook-addon": "^1.6.3",
"postcss": "^8.4.19",
"prettier": "^2.7.1",

View File

@@ -1,17 +0,0 @@
<svg width="142" height="48" viewBox="0 0 142 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M75.985 22.8273H83.1731V17.1251H86.9756V31.7529H83.1731V26.0506H75.985V31.7529H72.3164V17.1251H75.985V22.8273ZM101.48 17.1251H95.6099C93.9842 17.1251 92.9947 17.4319 92.2458 18.1792C91.4999 18.9235 91.2137 19.8684 91.2137 21.5362V27.3478C91.2137 29.0157 91.4999 29.9575 92.2458 30.7048C92.9917 31.4522 93.9811 31.759 95.6099 31.759H101.48C103.105 31.759 104.095 31.4522 104.844 30.7048C105.593 29.9605 105.876 29.0157 105.876 27.3478V21.5362C105.876 19.8684 105.59 18.9266 104.844 18.1792C104.095 17.4319 103.105 17.1251 101.48 17.1251ZM102.204 27.4329C102.204 28.2896 101.918 28.5296 100.886 28.5296H96.2036C95.1715 28.5296 94.8853 28.2896 94.8853 27.4329V21.4451C94.8853 20.5884 95.1715 20.3484 96.2036 20.3484H100.886C101.942 20.3484 102.204 20.5671 102.204 21.4451V27.4329ZM114.972 22.9792H120.224C121.85 22.9792 122.861 23.2861 123.588 24.0334C124.246 24.6927 124.62 25.7651 124.62 26.9286V27.8066C124.62 28.9701 124.246 30.0213 123.588 30.7018C122.861 31.4491 121.871 31.756 120.224 31.756H110.202V28.5327H119.609C120.641 28.5327 120.927 28.2927 120.927 27.4359V26.9985C120.927 26.1418 120.641 25.9018 119.609 25.9018H114.357C112.729 25.9018 111.718 25.5919 110.993 24.8476C110.336 24.1884 109.961 23.1159 109.961 21.9524V21.0744C109.961 19.9109 110.333 18.8597 110.993 18.1792C111.721 17.4319 112.707 17.1251 114.357 17.1251H123.856V20.3484H114.972C113.94 20.3484 113.654 20.5884 113.654 21.4451V21.8825C113.654 22.7392 113.94 22.9792 114.972 22.9792ZM126.946 20.3484V17.1251H141.891V20.3484H136.265V31.7529H132.594V20.3484H126.946Z" fill="#21324B"/>
<path d="M63.5321 17.0096H57.6624C56.0366 17.0096 55.0471 17.3164 54.2982 18.0638C53.5523 18.8081 53.2661 19.7529 53.2661 21.4177V23.6385V24.322V31.7499H56.9378V24.3251V23.6415V21.3327C56.9378 20.4759 57.224 20.2359 58.256 20.2359H62.9385C63.9949 20.2359 64.2567 20.4547 64.2567 21.3327V23.6415V24.3251V31.7529H67.9284V24.3251V23.6415V21.4208C67.9284 19.7529 67.6422 18.8111 66.8963 18.0668C66.1504 17.3164 65.1609 17.0096 63.5321 17.0096Z" fill="#0052CD"/>
<g clip-path="url(#clip1)">
<path d="M40.8114 10.2866L24.0819 0.647089C22.581 -0.215696 20.7178 -0.215696 19.2138 0.647089C17.7129 1.51291 16.7813 3.12304 16.7813 4.85165V6.10937L15.6913 5.48051C14.1904 4.61772 12.3272 4.61772 10.8232 5.48051C9.32224 6.34633 8.39063 7.95646 8.39063 9.6881V10.9458L7.3007 10.317C5.79976 9.45418 3.93653 9.45418 2.43255 10.317C0.931612 11.1828 0 12.7929 0 14.5246V44.7281C0 45.597 0.505383 46.4051 1.29086 46.7818C2.0733 47.1615 3.02318 47.0582 3.70515 46.5205L12.0014 39.9919L24.7944 47.362C25.1475 47.5656 25.5433 47.6658 25.9391 47.6658C26.3349 47.6658 26.7307 47.5625 27.0838 47.362C27.7901 46.9549 28.2285 46.1985 28.2285 45.3843V27.2081C28.2285 24.2248 26.6211 21.4481 24.0332 19.9565L19.8379 17.5382V4.85468C19.8379 4.21063 20.185 3.60911 20.7452 3.28709C21.3054 2.96506 21.9995 2.96506 22.5597 3.28709L39.2892 12.9235C40.9363 13.8714 41.9592 15.6425 41.9592 17.5382V40.1711C41.9592 40.8152 41.6121 41.4167 41.052 41.7387L36.6192 44.2937V22.3716C36.6192 19.3884 35.0117 16.6116 32.4239 15.12L22.1243 9.18684V12.7018L30.8986 17.757C32.5456 18.7048 33.5686 20.4729 33.5686 22.3716V45.6091C33.5686 46.4203 34.007 47.1797 34.7133 47.5868C35.0665 47.7904 35.4623 47.8906 35.8581 47.8906C36.2538 47.8906 36.6496 47.7873 37.0028 47.5868L42.5803 44.3727C44.0812 43.5068 45.0128 41.8967 45.0128 40.1651V17.5322C45.0068 14.558 43.3993 11.7782 40.8114 10.2866ZM22.5019 22.5934C24.1489 23.5413 25.1719 25.3094 25.1719 27.2081V44.0689L14.577 37.9656L17.9777 35.2921C19.156 34.3656 19.8318 32.9772 19.8318 31.4795V21.0592L22.5019 22.5934ZM16.7813 19.2972V31.4734C16.7813 32.0324 16.5286 32.5519 16.0902 32.8952L3.05058 43.1544V14.5215C3.05058 13.8775 3.39766 13.2759 3.95784 12.9539C4.51803 12.6319 5.21217 12.6319 5.77236 12.9539L8.39063 14.4608V36.0456L11.4412 33.6456V9.6881C11.4412 9.04405 11.7883 8.44253 12.3485 8.12051C12.9087 7.79848 13.6028 7.79848 14.163 8.12051L16.7813 9.62734V15.7792L13.7307 14.0203V17.5382L16.7813 19.2972Z" fill="#0052CD"/>
</g>
</g>
<defs>
<clipPath id="clip0">
<rect width="141.873" height="48" fill="white"/>
</clipPath>
<clipPath id="clip1">
<rect width="45.0128" height="47.8906" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 4.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -1 +0,0 @@
<svg width="150" height="30" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M123.477 14.573c0-2.369 1.633-3.936 3.759-3.936s3.758 1.567 3.758 3.936c0 2.369-1.632 3.936-3.758 3.936-2.126-.037-3.759-1.604-3.759-3.936Zm4.252 7.361c3.758 0 7.023-3.134 7.023-7.398 0-4.263-3.265-7.397-7.023-7.397-2.468 0-3.91 1.42-3.91 1.42V2h-3.797v19.789h2.772l.645-1.422c.038 0 1.671 1.567 4.29 1.567Zm-14.73-7.361c0 2.369-1.632 3.936-3.758 3.936-2.127 0-3.759-1.567-3.759-3.936 0-2.369 1.632-3.936 3.759-3.936 2.126 0 3.758 1.567 3.758 3.936Zm-4.252 7.361c2.62 0 4.252-1.567 4.252-1.567l.645 1.422h2.772V7.357h-2.772l-.645 1.421s-1.632-1.567-4.252-1.567c-3.759 0-7.024 3.134-7.024 7.398 0 4.264 3.265 7.325 7.024 7.325ZM98.952 2h-3.758v19.789h3.758V2Zm-7.365 5.175c-2.278 0-3.606 1.713-3.606 1.713l-.646-1.567h-2.771v14.431h3.758V14.1c0-1.895 1.29-3.134 3.113-3.134.835 0 1.291.146 1.291.146V7.357s-.493-.182-1.139-.182Zm-17.008 3.462c1.974 0 3.265 1.24 3.607 2.843h-7.214c.304-1.604 1.633-2.843 3.607-2.843Zm2.771 6.742s-.835 1.093-2.771 1.093-3.113-1.093-3.417-2.514H81.64s.152-.802.152-1.421c0-4.41-3.265-7.399-7.213-7.399-4.1 0-7.517 3.28-7.517 7.399 0 4.118 3.417 7.397 7.517 7.397 3.417 0 5.885-2.368 6.72-4.555H77.35Zm-23.575-2.806c0-2.369 1.632-3.936 3.758-3.936 2.126 0 3.758 1.567 3.758 3.936 0 2.369-1.632 3.936-3.758 3.936-2.126-.037-3.758-1.604-3.758-3.936Zm4.252-7.398c-2.62 0-4.252 1.567-4.252 1.567l-.646-1.421h-2.771v19.46h3.758v-6.268s1.48 1.421 3.91 1.421c3.759 0 7.024-3.134 7.024-7.398 0-4.263-3.265-7.361-7.023-7.361ZM39.081 21.169l-.341.802c-.456 1.093-1.291 1.895-2.962 1.895v2.988s.646.146 1.291.146c2.468 0 3.759-1.239 5.24-4.701.341-.802 6.036-14.905 6.036-14.905h-3.873l-3.416 9.438-3.797-9.475h-3.91l5.732 13.812ZM16 4.515v17.274h3.91v-7.216h7.365v7.216h3.91V4.515h-3.91v6.596H19.91V4.515H16Z" fill="#000"/></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -1 +0,0 @@
<svg width="150" height="30" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M123.477 14.573c0-2.369 1.633-3.936 3.759-3.936s3.758 1.567 3.758 3.936c0 2.369-1.632 3.936-3.758 3.936-2.126-.037-3.759-1.604-3.759-3.936Zm4.252 7.361c3.758 0 7.023-3.134 7.023-7.398 0-4.263-3.265-7.397-7.023-7.397-2.468 0-3.91 1.42-3.91 1.42V2h-3.797v19.789h2.772l.645-1.422c.038 0 1.671 1.567 4.29 1.567Zm-14.73-7.361c0 2.369-1.632 3.936-3.758 3.936-2.127 0-3.759-1.567-3.759-3.936 0-2.369 1.632-3.936 3.759-3.936 2.126 0 3.758 1.567 3.758 3.936Zm-4.252 7.361c2.62 0 4.252-1.567 4.252-1.567l.645 1.422h2.772V7.357h-2.772l-.645 1.421s-1.632-1.567-4.252-1.567c-3.759 0-7.024 3.134-7.024 7.398 0 4.264 3.265 7.325 7.024 7.325ZM98.952 2h-3.758v19.789h3.758V2Zm-7.365 5.175c-2.278 0-3.606 1.713-3.606 1.713l-.646-1.567h-2.771v14.431h3.758V14.1c0-1.895 1.29-3.134 3.113-3.134.835 0 1.291.146 1.291.146V7.357s-.493-.182-1.139-.182Zm-17.008 3.462c1.974 0 3.265 1.24 3.607 2.843h-7.214c.304-1.604 1.633-2.843 3.607-2.843Zm2.771 6.742s-.835 1.093-2.771 1.093-3.113-1.093-3.417-2.514H81.64s.152-.802.152-1.421c0-4.41-3.265-7.399-7.213-7.399-4.1 0-7.517 3.28-7.517 7.399 0 4.118 3.417 7.397 7.517 7.397 3.417 0 5.885-2.368 6.72-4.555H77.35Zm-23.575-2.806c0-2.369 1.632-3.936 3.758-3.936 2.126 0 3.758 1.567 3.758 3.936 0 2.369-1.632 3.936-3.758 3.936-2.126-.037-3.758-1.604-3.758-3.936Zm4.252-7.398c-2.62 0-4.252 1.567-4.252 1.567l-.646-1.421h-2.771v19.46h3.758v-6.268s1.48 1.421 3.91 1.421c3.759 0 7.024-3.134 7.024-7.398 0-4.263-3.265-7.361-7.023-7.361ZM39.081 21.169l-.341.802c-.456 1.093-1.291 1.895-2.962 1.895v2.988s.646.146 1.291.146c2.468 0 3.759-1.239 5.24-4.701.341-.802 6.036-14.905 6.036-14.905h-3.873l-3.416 9.438-3.797-9.475h-3.91l5.732 13.812ZM16 4.515v17.274h3.91v-7.216h7.365v7.216h3.91V4.515h-3.91v6.596H19.91V4.515H16Z" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="142" height="48" fill="none"><g clip-path="url(#a)"><path fill="#fff" fill-rule="evenodd" d="M75.985 22.827h7.188v-5.702h3.803v14.628h-3.803V26.05h-7.188v5.702h-3.669V17.125h3.669v5.702Zm25.495-5.702h-5.87c-1.626 0-2.615.307-3.364 1.054-.746.745-1.032 1.69-1.032 3.357v5.812c0 1.668.286 2.61 1.032 3.357.746.747 1.735 1.054 3.364 1.054h5.87c1.625 0 2.615-.307 3.364-1.054.749-.744 1.032-1.69 1.032-3.357v-5.812c0-1.668-.286-2.61-1.032-3.357-.749-.747-1.739-1.054-3.364-1.054Zm.724 10.308c0 .857-.286 1.097-1.318 1.097h-4.682c-1.032 0-1.319-.24-1.319-1.097v-5.988c0-.857.286-1.097 1.319-1.097h4.682c1.056 0 1.318.22 1.318 1.097v5.988Zm12.768-4.454h5.252c1.626 0 2.637.307 3.364 1.054.658.66 1.032 1.732 1.032 2.896v.878c0 1.163-.374 2.214-1.032 2.895-.727.747-1.717 1.054-3.364 1.054h-10.022v-3.223h9.407c1.032 0 1.318-.24 1.318-1.097v-.438c0-.856-.286-1.096-1.318-1.096h-5.252c-1.628 0-2.639-.31-3.364-1.054-.657-.66-1.032-1.732-1.032-2.896v-.878c0-1.163.372-2.214 1.032-2.895.728-.747 1.714-1.054 3.364-1.054h9.499v3.223h-8.884c-1.032 0-1.318.24-1.318 1.097v.438c0 .856.286 1.096 1.318 1.096Zm11.974-2.63v-3.224h14.945v3.223h-5.626v11.405h-3.671V20.348h-5.648Z" clip-rule="evenodd"/><path fill="#3888ff" d="M63.532 17.01h-5.87c-1.625 0-2.615.306-3.364 1.054-.746.744-1.032 1.689-1.032 3.354V31.75h3.672V21.332c0-.856.286-1.096 1.318-1.096h4.682c1.057 0 1.319.219 1.319 1.097v10.42h3.671V21.42c0-1.668-.286-2.61-1.032-3.354-.746-.75-1.735-1.057-3.364-1.057Z"/><g clip-path="url(#b)"><path fill="#3888ff" d="M40.811 10.287 24.081.647a4.901 4.901 0 0 0-4.867 0 4.868 4.868 0 0 0-2.433 4.205v1.257l-1.09-.628a4.901 4.901 0 0 0-4.868 0 4.869 4.869 0 0 0-2.432 4.207v1.258l-1.09-.629a4.901 4.901 0 0 0-4.868 0A4.869 4.869 0 0 0 0 14.525v30.203a2.29 2.29 0 0 0 1.29 2.054c.783.38 1.733.276 2.415-.261l8.296-6.53 12.793 7.371a2.29 2.29 0 0 0 1.145.304 2.288 2.288 0 0 0 2.29-2.281V27.207a8.392 8.392 0 0 0-4.196-7.252l-4.195-2.418V4.855a1.813 1.813 0 0 1 2.722-1.568l16.73 9.637a5.342 5.342 0 0 1 2.67 4.614v22.633c0 .644-.348 1.246-.908 1.568l-4.433 2.555V22.372a8.393 8.393 0 0 0-4.195-7.252l-10.3-5.933v3.515l8.775 5.055a5.338 5.338 0 0 1 2.67 4.615v23.237c0 .811.438 1.57 1.144 1.978a2.29 2.29 0 0 0 1.145.304c.396 0 .792-.104 1.145-.304l5.577-3.214a4.87 4.87 0 0 0 2.433-4.208V17.532a8.412 8.412 0 0 0-4.202-7.245Zm-18.31 12.306a5.339 5.339 0 0 1 2.67 4.615v16.86l-10.594-6.102 3.4-2.674a4.82 4.82 0 0 0 1.855-3.813V21.06l2.67 1.534Zm-5.72-3.296v12.176c0 .56-.252 1.079-.69 1.422L3.05 43.155V14.52a1.814 1.814 0 0 1 2.721-1.567l2.619 1.507v21.585l3.05-2.4V9.688a1.813 1.813 0 0 1 2.722-1.567l2.618 1.506v6.152l-3.05-1.759v3.518l3.05 1.76Z"/></g></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h141.873v48H0z"/></clipPath><clipPath id="b"><path fill="#fff" d="M0 0h45.013v47.891H0z"/></clipPath></defs></svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="142" height="48" fill="none"><g clip-path="url(#a)"><path fill="#21324B" fill-rule="evenodd" d="M75.985 22.827h7.188v-5.702h3.803v14.628h-3.803V26.05h-7.188v5.702h-3.669V17.125h3.669v5.702Zm25.495-5.702h-5.87c-1.626 0-2.615.307-3.364 1.054-.746.745-1.032 1.69-1.032 3.357v5.812c0 1.668.286 2.61 1.032 3.357.746.747 1.735 1.054 3.364 1.054h5.87c1.625 0 2.615-.307 3.364-1.054.749-.744 1.032-1.69 1.032-3.357v-5.812c0-1.668-.286-2.61-1.032-3.357-.749-.747-1.739-1.054-3.364-1.054Zm.724 10.308c0 .857-.286 1.097-1.318 1.097h-4.682c-1.032 0-1.319-.24-1.319-1.097v-5.988c0-.857.286-1.097 1.319-1.097h4.682c1.056 0 1.318.22 1.318 1.097v5.988Zm12.768-4.454h5.252c1.626 0 2.637.307 3.364 1.054.658.66 1.032 1.732 1.032 2.896v.878c0 1.163-.374 2.214-1.032 2.895-.727.747-1.717 1.054-3.364 1.054h-10.022v-3.223h9.407c1.032 0 1.318-.24 1.318-1.097v-.438c0-.856-.286-1.096-1.318-1.096h-5.252c-1.628 0-2.639-.31-3.364-1.054-.657-.66-1.032-1.732-1.032-2.896v-.878c0-1.163.372-2.214 1.032-2.895.728-.747 1.714-1.054 3.364-1.054h9.499v3.223h-8.884c-1.032 0-1.318.24-1.318 1.097v.438c0 .856.286 1.096 1.318 1.096Zm11.974-2.63v-3.224h14.945v3.223h-5.626v11.405h-3.671V20.348h-5.648Z" clip-rule="evenodd"/><path fill="#0052CD" d="M63.532 17.01h-5.87c-1.625 0-2.615.306-3.364 1.054-.746.744-1.032 1.689-1.032 3.354V31.75h3.672V21.332c0-.856.286-1.096 1.318-1.096h4.682c1.057 0 1.319.219 1.319 1.097v10.42h3.671V21.42c0-1.668-.286-2.61-1.032-3.354-.746-.75-1.735-1.057-3.364-1.057Z"/><g clip-path="url(#b)"><path fill="#0052CD" d="M40.811 10.287 24.081.647a4.901 4.901 0 0 0-4.867 0 4.868 4.868 0 0 0-2.433 4.205v1.257l-1.09-.628a4.901 4.901 0 0 0-4.868 0 4.869 4.869 0 0 0-2.432 4.207v1.258l-1.09-.629a4.901 4.901 0 0 0-4.868 0A4.869 4.869 0 0 0 0 14.525v30.203a2.29 2.29 0 0 0 1.29 2.054c.783.38 1.733.276 2.415-.261l8.296-6.53 12.793 7.371a2.29 2.29 0 0 0 1.145.304 2.288 2.288 0 0 0 2.29-2.281V27.207a8.392 8.392 0 0 0-4.196-7.252l-4.195-2.418V4.855a1.813 1.813 0 0 1 2.722-1.568l16.73 9.637a5.342 5.342 0 0 1 2.67 4.614v22.633c0 .644-.348 1.246-.908 1.568l-4.433 2.555V22.372a8.393 8.393 0 0 0-4.195-7.252l-10.3-5.933v3.515l8.775 5.055a5.338 5.338 0 0 1 2.67 4.615v23.237c0 .811.438 1.57 1.144 1.978a2.29 2.29 0 0 0 1.145.304c.396 0 .792-.104 1.145-.304l5.577-3.214a4.87 4.87 0 0 0 2.433-4.208V17.532a8.412 8.412 0 0 0-4.202-7.245Zm-18.31 12.306a5.339 5.339 0 0 1 2.67 4.615v16.86l-10.594-6.102 3.4-2.674a4.82 4.82 0 0 0 1.855-3.813V21.06l2.67 1.534Zm-5.72-3.296v12.176c0 .56-.252 1.079-.69 1.422L3.05 43.155V14.52a1.814 1.814 0 0 1 2.721-1.567l2.619 1.507v21.585l3.05-2.4V9.688a1.813 1.813 0 0 1 2.722-1.567l2.618 1.506v6.152l-3.05-1.759v3.518l3.05 1.76Z"/></g></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h141.873v48H0z"/></clipPath><clipPath id="b"><path fill="#fff" d="M0 0h45.013v47.891H0z"/></clipPath></defs></svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1 @@
<svg width="1003" height="644" fill="none" xmlns="http://www.w3.org/2000/svg"><g opacity=".24"><mask id="b" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="128" y="0" width="921" height="644"><ellipse cx="588.5" cy="322" rx="460.5" ry="322" fill="url(#a)" fill-opacity=".32"/></mask><g mask="url(#b)" stroke="#fff" stroke-opacity=".88"><path d="M141.5 17v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609m32-609v609M-51 33.5h1280m-1280 32h1280m-1280 32h1280m-1280 32h1280m-1280 32h1280m-1280 32h1280m-1280 32h1280m-1280 32h1280m-1280 32h1280m-1280 32h1280m-1280 32h1280m-1280 32h1280m-1280 32h1280m-1280 32h1280m-1280 32h1280m-1280 32h1280m-1280 32h1280m-1280 32h1280m-1280 32h1280"/></g></g><defs><radialGradient id="a" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 322 -460.5 0 588.5 322)"><stop/><stop offset="1" stop-opacity="0"/></radialGradient></defs></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg width="119" height="40" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)" fill="#fff"><path fill-rule="evenodd" clip-rule="evenodd" d="M63.32 19.023h5.99V14.27h3.17v12.19h-3.17V21.71h-5.99v4.752h-3.058v-12.19h3.057v4.752h.002Zm21.245-4.752h-4.89c-1.356 0-2.18.255-2.804.879-.622.62-.86 1.408-.86 2.798v4.842c0 1.39.237 2.175.86 2.797.62.623 1.446.88 2.803.88h4.891c1.356 0 2.18-.257 2.804-.88.625-.62.86-1.407.86-2.797v-4.842c0-1.39-.237-2.175-.86-2.8-.625-.622-1.448-.877-2.803-.877Zm.604 8.59c0 .713-.237.913-1.099.913h-3.9c-.86 0-1.1-.2-1.1-.913v-4.99c0-.713.24-.913 1.1-.913h3.9c.882 0 1.1.18 1.1.912v4.991Zm10.64-3.712h4.376c1.355 0 2.198.256 2.804.878.549.55.86 1.444.86 2.413v.732c0 .97-.312 1.845-.86 2.413-.606.622-1.431.879-2.804.879h-8.35v-2.688h7.838c.86 0 1.1-.2 1.1-.912v-.365c0-.714-.24-.914-1.1-.914h-4.375c-1.359 0-2.2-.259-2.804-.879-.549-.55-.861-1.442-.861-2.412v-.732c0-.97.31-1.846.861-2.412.606-.624 1.428-.879 2.803-.879h7.916v2.686h-7.404c-.86 0-1.099.2-1.099.913v.365c0 .715.24.915 1.1.915v-.001Zm9.979-2.192V14.27h12.454v2.688h-4.688v9.503h-3.06v-9.503h-4.707.001Z"/><path d="M52.946 14.175h-4.892c-1.355 0-2.18.255-2.803.879-.622.62-.86 1.407-.86 2.795v8.61h3.058v-8.684c0-.712.24-.912 1.1-.912h3.902c.88 0 1.098.182 1.098.912v8.685h3.06v-8.61c0-1.39-.238-2.175-.86-2.795-.622-.625-1.446-.881-2.803-.881v.001ZM34.01 8.573 20.069.538a4.085 4.085 0 0 0-4.058 0 4.057 4.057 0 0 0-2.026 3.503V5.09l-.909-.525a4.085 4.085 0 0 0-4.057 0 4.058 4.058 0 0 0-2.027 3.51v1.048l-.908-.524a4.085 4.085 0 0 0-4.056 0A4.058 4.058 0 0 0 0 12.104v25.17a1.907 1.907 0 0 0 3.087 1.494L10 33.325l10.663 6.143a1.936 1.936 0 0 0 1.907 0c.587-.34.954-.97.954-1.65V22.676a6.995 6.995 0 0 0-3.497-6.044l-3.496-2.015V4.046A1.513 1.513 0 0 1 18.8 2.738l13.941 8.031a4.452 4.452 0 0 1 2.225 3.845v18.861c0 .538-.29 1.038-.756 1.307l-3.694 2.128V18.643A6.994 6.994 0 0 0 27.02 12.6l-8.582-4.944v2.93l7.31 4.212a4.448 4.448 0 0 1 2.226 3.845v19.365c0 .675.365 1.308.953 1.648a1.936 1.936 0 0 0 1.908 0l4.649-2.678a4.058 4.058 0 0 0 2.027-3.507v-18.86a7.01 7.01 0 0 0-3.501-6.038ZM18.75 18.828a4.448 4.448 0 0 1 2.225 3.846v14.05l-8.828-5.086 2.834-2.228a4.018 4.018 0 0 0 1.545-3.177V17.55l2.225 1.279-.001-.001Zm-4.766-2.747v10.147c0 .466-.211.9-.576 1.185L2.54 35.963V12.1a1.512 1.512 0 0 1 2.268-1.306l2.183 1.256v17.988l2.542-2V8.073a1.511 1.511 0 0 1 2.268-1.307l2.182 1.255v5.128l-2.54-1.465v2.931l2.542 1.466h-.002Z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h118.75v40H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

View File

@@ -1,6 +0,0 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="200" fill="white" fill-opacity="0.15"/>
<rect width="200" height="200" fill="#263245" fill-opacity="0.08"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M71 84C71 67.9837 83.9837 55 100 55C116.016 55 129 67.9837 129 84C129 100.016 116.016 113 100 113C83.9837 113 71 100.016 71 84ZM100 49C80.67 49 65 64.67 65 84C65 97.6014 72.7585 109.391 84.0914 115.184C79.3584 116.509 74.7892 118.425 70.496 120.903C61.5257 126.08 54.0757 133.527 48.8946 142.495C48.0657 143.929 48.5568 145.764 49.9914 146.593C51.4261 147.422 53.261 146.931 54.0898 145.496C58.7443 137.44 65.4368 130.75 73.4952 126.099C81.5536 121.448 90.694 119 99.9982 119C109.302 119 118.443 121.449 126.501 126.1C134.559 130.751 141.252 137.441 145.906 145.497C146.735 146.932 148.57 147.423 150.004 146.594C151.439 145.765 151.93 143.93 151.101 142.496C145.92 133.527 138.471 126.081 129.5 120.903C125.208 118.426 120.639 116.509 115.907 115.185C127.241 109.392 135 97.6021 135 84C135 64.67 119.33 49 100 49Z" fill="white" fill-opacity="0.15"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M71 84C71 67.9837 83.9837 55 100 55C116.016 55 129 67.9837 129 84C129 100.016 116.016 113 100 113C83.9837 113 71 100.016 71 84ZM100 49C80.67 49 65 64.67 65 84C65 97.6014 72.7585 109.391 84.0914 115.184C79.3584 116.509 74.7892 118.425 70.496 120.903C61.5257 126.08 54.0757 133.527 48.8946 142.495C48.0657 143.929 48.5568 145.764 49.9914 146.593C51.4261 147.422 53.261 146.931 54.0898 145.496C58.7443 137.44 65.4368 130.75 73.4952 126.099C81.5536 121.448 90.694 119 99.9982 119C109.302 119 118.443 121.449 126.501 126.1C134.559 130.751 141.252 137.441 145.906 145.497C146.735 146.932 148.57 147.423 150.004 146.594C151.439 145.765 151.93 143.93 151.101 142.496C145.92 133.527 138.471 126.081 129.5 120.903C125.208 118.426 120.639 116.509 115.907 115.185C127.241 109.392 135 97.6021 135 84C135 64.67 119.33 49 100 49Z" fill="#263245" fill-opacity="0.25"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -111,9 +111,9 @@ export default function AppDeployments(props: AppDeploymentsProps) {
throw error;
}
const { deployments } = deploymentPageData || {};
const { deployments } = deploymentPageData || { deployments: [] };
const { deployments: scheduledOrPendingDeployments } =
scheduledOrPendingDeploymentsData || {};
scheduledOrPendingDeploymentsData || { deployments: [] };
const latestDeployment = latestDeploymentData?.deployments[0];
const latestLiveDeployment = latestLiveDeploymentData?.deployments[0];
@@ -135,7 +135,7 @@ export default function AppDeployments(props: AppDeploymentsProps) {
deployment={deployment}
isLive={liveDeploymentId === deployment.id}
showRedeploy={latestDeployment.id === deployment.id}
disableRedeploy={scheduledOrPendingDeployments.length > 0}
disableRedeploy={scheduledOrPendingDeployments?.length > 0}
/>
{index !== deployments.length - 1 && <Divider component="li" />}
@@ -143,7 +143,7 @@ export default function AppDeployments(props: AppDeploymentsProps) {
))}
</List>
<div className="mt-8 flex w-full justify-center">
<div className="grid grid-flow-col gap-2 items-center">
<div className="grid grid-flow-col items-center gap-2">
<NextPrevPageLink
direction="prev"
prevAllowed={page !== 1}

View File

@@ -58,7 +58,7 @@ export default function ApplicationErrored() {
const { currentDate } = useCurrentDate();
const user = useUserData();
const isOwner = currentWorkspace.members.some(
({ userId, type }) => userId === user.id && type === 'owner',
({ userId, type }) => userId === user?.id && type === 'owner',
);
const { appCreatedAt } = useAppCreatedAt();

View File

@@ -1,3 +1,4 @@
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
import Container from '@/components/layout/Container';
import { features } from '@/components/overview/features';
import { frameworks } from '@/components/overview/frameworks';
@@ -55,7 +56,9 @@ export default function ApplicationLive() {
<div className="grid grid-cols-1 gap-12 pt-3 lg:grid-cols-3">
<div className="order-2 grid grid-flow-row gap-12 lg:order-1 lg:col-span-2">
<OverviewDeployments />
<RetryableErrorBoundary>
<OverviewDeployments />
</RetryableErrorBoundary>
<OverviewDocumentation
title="Pick your favorite framework and start learning"

View File

@@ -18,7 +18,7 @@ export default function ApplicationUnknown() {
const [showDeleteModal, setShowDeleteModal] = useState(false);
const user = useUserData();
const isOwner = currentWorkspace.members.some(
({ userId, type }) => userId === user.id && type === 'owner',
({ userId, type }) => userId === user?.id && type === 'owner',
);
return (

View File

@@ -31,6 +31,7 @@ export default function Breadcrumbs({ className, ...props }: BreadcrumbsProps) {
<NavLink
href="/local/local"
className="truncate text-[13px] hover:underline sm:text-sm"
sx={{ color: 'text.primary' }}
>
local
</NavLink>
@@ -53,6 +54,7 @@ export default function Breadcrumbs({ className, ...props }: BreadcrumbsProps) {
<NavLink
href={`/${currentWorkspace.slug}`}
className="truncate text-[13px] hover:underline sm:text-sm"
sx={{ color: 'text.primary' }}
>
{currentWorkspace.name}
</NavLink>
@@ -66,6 +68,7 @@ export default function Breadcrumbs({ className, ...props }: BreadcrumbsProps) {
<NavLink
href={`/${currentWorkspace.slug}/${currentApplication.slug}`}
className="truncate text-[13px] hover:underline sm:text-sm"
sx={{ color: 'text.primary' }}
>
{currentApplication.name}
</NavLink>

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,14 +1,13 @@
import Breadcrumbs from '@/components/common/Breadcrumbs';
import FeedbackForm from '@/components/common/FeedbackForm';
import LocalAccountMenu from '@/components/common/LocalAccountMenu';
import Logo from '@/components/common/Logo';
import MobileNav from '@/components/common/MobileNav';
import ThemeSwitcher from '@/components/common/ThemeSwitcher';
import NavLink from '@/components/common/NavLink';
import { AccountMenu } from '@/components/dashboard/AccountMenu';
import useIsPlatform from '@/hooks/common/useIsPlatform';
import Box from '@/ui/v2/Box';
import { Dropdown } from '@/ui/v2/Dropdown';
import Link from '@/ui/v2/Link';
import NavLink from 'next/link';
import { useRouter } from 'next/router';
import type { DetailedHTMLProps, HTMLProps, PropsWithoutRef } from 'react';
import { twMerge } from 'tailwind-merge';
@@ -32,10 +31,8 @@ export default function Header({ className, ...props }: HeaderProps) {
{...props}
>
<div className="grid grid-flow-col items-center gap-3">
<NavLink href="/" passHref>
<Link href="/" className="w-12">
<Logo className="mx-auto cursor-pointer" />
</Link>
<NavLink href="/" className="w-12">
<Logo className="mx-auto cursor-pointer" />
</NavLink>
{(router.query.workspaceSlug || router.query.appSlug) && (
@@ -63,25 +60,20 @@ export default function Header({ className, ...props }: HeaderProps) {
)}
<NavLink
underline="none"
href="https://docs.nhost.io"
passHref
className="mr-2 rounded-md px-2.5 py-1.5 text-sm motion-safe:transition-colors"
sx={{
color: 'text.primary',
'&:hover': { backgroundColor: 'grey.200' },
}}
target="_blank"
rel="noopener noreferrer"
>
<Link
underline="none"
href="https://docs.nhost.io"
className="mr-2 rounded-md px-2.5 py-1.5 text-sm motion-safe:transition-colors"
sx={{
color: 'text.primary',
'&:hover': { backgroundColor: 'grey.200' },
}}
>
Docs
</Link>
Docs
</NavLink>
{isPlatform ? <AccountMenu /> : <ThemeSwitcher className="w-52" />}
{isPlatform ? <AccountMenu /> : <LocalAccountMenu />}
</div>
<MobileNav className="sm:hidden" />

View File

@@ -1,12 +1,11 @@
import type { NavLinkProps } from '@/components/common/NavLink';
import NavLink from '@/components/common/NavLink';
import type { SvgIconProps } from '@/ui/v2/icons/SvgIcon';
import Link from '@/ui/v2/Link';
import NavLink from 'next/link';
import type { ForwardedRef, ReactElement } from 'react';
import type { ForwardedRef, PropsWithoutRef, ReactElement } from 'react';
import { cloneElement, forwardRef, isValidElement } from 'react';
import { twMerge } from 'tailwind-merge';
export interface IconLinkProps extends Omit<NavLinkProps, 'ref'> {
export interface IconLinkProps extends PropsWithoutRef<NavLinkProps> {
/**
* The icon to display.
*/
@@ -58,49 +57,49 @@ function IconLink(
}
return (
<NavLink ref={ref} passHref href={href} {...props}>
<Link
href={href}
underline="none"
className={twMerge(
'grid grid-flow-row justify-items-center gap-1 rounded-md py-2.5 px-0.5 text-center font-medium motion-safe:transition-colors',
className,
)}
sx={{
fontSize: (theme) => theme.typography.pxToRem(10),
lineHeight: (theme) => theme.typography.pxToRem(15),
backgroundColor: active ? 'primary.light' : 'transparent',
color: active ? 'primary.main' : 'text.primary',
[`&:hover`]: {
backgroundColor: active ? 'primary.light' : 'action.hover',
},
}}
>
{isValidElement(icon)
? cloneElement(icon, {
...icon.props,
className: twMerge('w-4 h-4', icon.props.className),
sx: [
...(Array.isArray(icon.props?.sx)
? icon.props.sx
: [icon.props?.sx]),
{
color: (theme) => {
if (active) {
return 'primary.main';
}
<NavLink
ref={ref}
href={href}
underline="none"
className={twMerge(
'grid grid-flow-row justify-items-center gap-1 rounded-md py-2.5 px-0.5 text-center font-medium motion-safe:transition-colors',
className,
)}
sx={{
fontSize: (theme) => theme.typography.pxToRem(10),
lineHeight: (theme) => theme.typography.pxToRem(15),
backgroundColor: active ? 'primary.light' : 'transparent',
color: active ? 'primary.main' : 'text.primary',
[`&:hover`]: {
backgroundColor: active ? 'primary.light' : 'action.hover',
},
}}
{...props}
>
{isValidElement(icon)
? cloneElement(icon, {
...icon.props,
className: twMerge('w-4 h-4', icon.props.className),
sx: [
...(Array.isArray(icon.props?.sx)
? icon.props.sx
: [icon.props?.sx]),
{
color: (theme) => {
if (active) {
return 'primary.main';
}
return theme.palette.mode === 'dark'
? 'text.secondary'
: 'text.primary';
},
return theme.palette.mode === 'dark'
? 'text.secondary'
: 'text.primary';
},
],
})
: null}
},
],
})
: null}
{children}
</Link>
{children}
</NavLink>
);
}

View File

@@ -1,10 +1,50 @@
import type { ActivityIndicatorProps } from '@/ui/v2/ActivityIndicator';
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 function LoadingScreen() {
export interface LoadingScreenProps extends BoxProps {
/**
* Props passed to individual component slots.
*/
slotProps?: {
/**
* Props passed to the `<Box />` component.
*/
root?: BoxProps;
/**
* Props passed to the `<ActivityIndicator />` component.
*/
activityIndicator?: ActivityIndicatorProps;
};
}
export function LoadingScreen({
className,
slotProps = { root: {}, activityIndicator: {} },
...props
}: LoadingScreenProps) {
return (
<Box className="absolute top-0 left-0 bottom-0 right-0 flex items-center justify-center z-50 h-full w-full">
<ActivityIndicator circularProgressProps={{ className: 'w-5 h-5' }} />
<Box
className={twMerge(
'absolute top-0 left-0 bottom-0 right-0 z-50 flex h-full w-full items-center justify-center',
className,
slotProps?.root?.className,
)}
{...slotProps?.root}
{...props}
>
<ActivityIndicator
{...slotProps?.activityIndicator}
circularProgressProps={{
...slotProps?.activityIndicator?.circularProgressProps,
className: twMerge(
'w-5 h-5',
slotProps?.activityIndicator?.circularProgressProps?.className,
),
}}
/>
</Box>
);
}

View File

@@ -0,0 +1,39 @@
import ThemeSwitcher from '@/components/common/ThemeSwitcher';
import { Dropdown } from '@/ui/v2/Dropdown';
import IconButton from '@/ui/v2/IconButton';
import UserIcon from '@/ui/v2/icons/UserIcon';
import Text from '@/ui/v2/Text';
import getConfig from 'next/config';
export default function LocalAccountMenu() {
const { publicRuntimeConfig } = getConfig();
return (
<Dropdown.Root className="justify-self-center">
<Dropdown.Trigger hideChevron asChild>
<IconButton
variant="borderless"
color="secondary"
className="h-7 w-7 rounded-full"
sx={{
backgroundColor: (theme) => `${theme.palette.grey[300]} !important`,
}}
>
<UserIcon className="h-4 w-4" />
</IconButton>
</Dropdown.Trigger>
<Dropdown.Content
PaperProps={{
className: 'mt-1 p-6 grid grid-flow-row gap-4 w-full max-w-xs',
}}
>
<ThemeSwitcher label="Theme" />
<Text className="text-center text-xs" color="disabled">
Dashboard Version: {publicRuntimeConfig?.version || 'n/a'}
</Text>
</Dropdown.Content>
</Dropdown.Root>
);
}

View File

@@ -0,0 +1 @@
export { default } from './LocalAccountMenu';

View File

@@ -20,6 +20,7 @@ import type { ListItemButtonProps } from '@/ui/v2/ListItem';
import { ListItem } from '@/ui/v2/ListItem';
import Text from '@/ui/v2/Text';
import { useSignOut } from '@nhost/nextjs';
import getConfig from 'next/config';
import { useRouter } from 'next/router';
import type { ReactNode } from 'react';
import { cloneElement, Fragment, isValidElement, useState } from 'react';
@@ -89,6 +90,7 @@ export default function MobileNav({ className, ...props }: MobileNavProps) {
const { signOut } = useSignOut();
const { setUserContext } = useUserDataContext();
const router = useRouter();
const { publicRuntimeConfig } = getConfig();
return (
<>
@@ -256,6 +258,10 @@ export default function MobileNav({ className, ...props }: MobileNavProps) {
</ListItem.Button>
</ListItem.Root>
</List>
<Text className="text-center text-xs" color="secondary">
Dashboard Version: {publicRuntimeConfig?.version || 'n/a'}
</Text>
</section>
)}
</Drawer>

View File

@@ -1,21 +1,27 @@
import Link from 'next/link';
import type { DetailedHTMLProps, ForwardedRef, HTMLProps } from 'react';
import type { LinkProps } from '@/ui/v2/Link';
import Link from '@/ui/v2/Link';
import NextLink from 'next/link';
import type { ForwardedRef, PropsWithoutRef } from 'react';
import { forwardRef } from 'react';
import { twMerge } from 'tailwind-merge';
export interface NavLinkProps
extends DetailedHTMLProps<HTMLProps<HTMLAnchorElement>, HTMLAnchorElement> {}
export interface NavLinkProps extends PropsWithoutRef<LinkProps> {
/**
* Determines whether or not the link should be disabled.
*/
disabled?: boolean;
}
function NavLink(
{ className, children, href, ...props }: NavLinkProps,
ref: ForwardedRef<HTMLAnchorElement>,
) {
return (
<Link href={href} passHref>
<a className={twMerge('font-display', className)} ref={ref} {...props}>
<NextLink href={href} passHref>
<Link className={twMerge('font-display', className)} ref={ref} {...props}>
{children}
</a>
</Link>
</Link>
</NextLink>
);
}

View File

@@ -21,6 +21,13 @@ export default function ThemeSwitcher({
onChange?.(event, value);
}}
slotProps={{
listbox: { className: 'min-w-0 w-full' },
popper: {
disablePortal: false,
className: 'z-[10000] w-[270px] w-full',
},
}}
>
<Option value="light">Light</Option>
<Option value="dark">Dark</Option>

View File

@@ -10,6 +10,7 @@ import Text from '@/ui/v2/Text';
import { nhost } from '@/utils/nhost';
import { useApolloClient } from '@apollo/client';
import { useUserData } from '@nhost/nextjs';
import getConfig from 'next/config';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
@@ -24,23 +25,10 @@ function AccountMenuContent({
const router = useRouter();
const client = useApolloClient();
const { handleClose } = useDropdown();
const { publicRuntimeConfig } = getConfig();
return (
<Box className="relative grid w-account grid-flow-row gap-5 p-6">
<Button
variant="borderless"
color="secondary"
className="absolute top-6 right-4 grid grid-flow-col items-center gap-px self-start font-medium"
onClick={async () => {
router.push('/signin');
await nhost.auth.signOut();
await client.resetStore();
}}
endIcon={<PowerIcon className="w-4 h-4 mr-1" />}
>
Sign Out
</Button>
<Box className="relative grid w-full grid-flow-row gap-5 p-6">
<div className="grid grid-flow-row justify-center">
<Avatar
className="mx-auto mb-2 h-16 w-16 rounded-full"
@@ -72,9 +60,26 @@ function AccountMenuContent({
<Button color="error" disabled>
Remove Account
</Button>
<Button
variant="outlined"
color="secondary"
onClick={async () => {
await nhost.auth.signOut();
router.push('/signin');
await client.resetStore();
}}
endIcon={<PowerIcon className="mr-1 h-4 w-4" />}
>
Sign Out
</Button>
</div>
<ThemeSwitcher label="Theme" fullWidth />
<ThemeSwitcher label="Theme" />
<Text className="text-center text-xs" color="disabled">
Dashboard Version: {publicRuntimeConfig?.version || 'n/a'}
</Text>
</Box>
);
}
@@ -107,7 +112,7 @@ export function AccountMenu() {
/>
</Dropdown.Trigger>
<Dropdown.Content PaperProps={{ className: 'mt-1' }}>
<Dropdown.Content PaperProps={{ className: 'mt-1 max-w-xs w-full' }}>
<AccountMenuContent
onChangePasswordClick={() => setChangePasswordModal(true)}
/>

View File

@@ -24,7 +24,7 @@ export function CountrySelector({ value, onChange }: CountrySelectorProps) {
placeholder="Select Country"
>
{countries?.map((country) => (
<Option key={country.name} value={country.name}>
<Option key={country.name} value={country.code}>
{country.name}
</Option>
))}

View File

@@ -328,53 +328,50 @@ 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"
@@ -400,32 +397,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 +446,12 @@ function DataBrowserSidebarContent({
/>
<span>Delete Table</span>
</Dropdown.Item>,
]
)}
</Dropdown.Content>
</Dropdown.Root>
)
</Dropdown.Item>
),
]
)}
</Dropdown.Content>
</Dropdown.Root>
}
>
<ListItem.Button

View File

@@ -181,6 +181,7 @@ export default function EditPermissionsForm({
return (
<RolePermissionEditorForm
resourceVersion={metadata?.resourceVersion}
disabled={disabled}
schema={schema}
table={table}
@@ -203,7 +204,7 @@ export default function EditPermissionsForm({
sx={{ backgroundColor: 'background.default' }}
>
<div className="flex-auto">
<Box className="grid grid-flow-row gap-6 content-start overflow-y-auto p-6 border-b-1">
<Box className="grid grid-flow-row content-start gap-6 overflow-y-auto border-b-1 p-6">
<div className="grid grid-flow-row gap-2">
<Text component="h2" className="!font-bold">
Roles & Actions overview
@@ -215,24 +216,24 @@ export default function EditPermissionsForm({
</Text>
</div>
<div className="grid grid-flow-col gap-4 items-center justify-start">
<div className="grid grid-flow-col items-center justify-start gap-4">
<Text
variant="subtitle2"
className="grid items-center grid-flow-col gap-1"
className="grid grid-flow-col items-center gap-1"
>
full access <FullPermissionIcon />
</Text>
<Text
variant="subtitle2"
className="grid items-center grid-flow-col gap-1"
className="grid grid-flow-col items-center gap-1"
>
partial access <PartialPermissionIcon />
</Text>
<Text
variant="subtitle2"
className="grid items-center grid-flow-col gap-1"
className="grid grid-flow-col items-center gap-1"
>
no access <NoPermissionIcon />
</Text>
@@ -262,7 +263,7 @@ export default function EditPermissionsForm({
</TableRow>
</TableHead>
<TableBody className="rounded-sm+ block border-1">
<TableBody className="block rounded-sm+ border-1">
<RolePermissionsRow
name="admin"
disabled

View File

@@ -66,6 +66,10 @@ export interface RolePermissionEditorFormValues {
* Whether the mutation should be restricted to trusted backends.
*/
backendOnly?: boolean;
/**
* Computed fields to be allowed for the role.
*/
computedFields?: string[];
}
export interface RolePermissionEditorFormProps {
@@ -85,6 +89,10 @@ export interface RolePermissionEditorFormProps {
* The role that is being edited.
*/
role: string;
/**
* The resource version of the metadata.
*/
resourceVersion: number;
/**
* The action that is being edited.
*/
@@ -155,6 +163,7 @@ export default function RolePermissionEditorForm({
schema,
table,
role,
resourceVersion,
action,
onSubmit,
onCancel,
@@ -189,6 +198,7 @@ export default function RolePermissionEditorForm({
permission?.subscription_root_fields?.length > 0,
queryRootFields: permission?.query_root_fields || [],
subscriptionRootFields: permission?.subscription_root_fields || [],
computedFields: permission?.computed_fields || [],
columnPresets: getColumnPresets(permission?.set || {}),
backendOnly: permission?.backend_only || false,
},
@@ -213,13 +223,18 @@ export default function RolePermissionEditorForm({
action,
mode: permission ? 'update' : 'insert',
originalPermission: permission,
resourceVersion,
permission: {
set: convertToColumnPresetObject(values.columnPresets),
columns: values.columns,
limit: values.limit,
allow_aggregations: values.allowAggregations,
query_root_fields: values.queryRootFields,
subscription_root_fields: values.subscriptionRootFields,
query_root_fields:
values.queryRootFields.length > 0 ? values.queryRootFields : null,
subscription_root_fields:
values.subscriptionRootFields.length > 0
? values.subscriptionRootFields
: null,
filter:
action !== 'insert'
? convertToHasuraPermissions(values.filter as RuleGroup)
@@ -229,6 +244,10 @@ export default function RolePermissionEditorForm({
? convertToHasuraPermissions(values.filter as RuleGroup)
: permission?.check,
backend_only: values.backendOnly,
computed_fields:
permission?.computed_fields?.length > 0
? permission?.computed_fields
: null,
},
});
@@ -266,6 +285,7 @@ export default function RolePermissionEditorForm({
const deletePermissionPromise = managePermission({
role,
action,
resourceVersion,
originalPermission: permission,
mode: 'delete',
});
@@ -331,10 +351,10 @@ export default function RolePermissionEditorForm({
className="flex flex-auto flex-col content-between overflow-hidden border-t-1"
sx={{ backgroundColor: 'background.default' }}
>
<div className="grid grid-flow-row gap-6 content-start flex-auto py-4 overflow-auto">
<div className="grid flex-auto grid-flow-row content-start gap-6 overflow-auto py-4">
<PermissionSettingsSection
title="Selected role & action"
className="justify-between grid-flow-col"
className="grid-flow-col justify-between"
>
<div className="grid grid-flow-col gap-4">
<Text>
@@ -387,7 +407,7 @@ export default function RolePermissionEditorForm({
{action !== 'select' && <BackendOnlySection disabled={disabled} />}
</div>
<Box className="grid flex-shrink-0 sm:grid-flow-col sm:justify-between gap-2 border-t-1 p-2">
<Box className="grid flex-shrink-0 gap-2 border-t-1 p-2 sm:grid-flow-col sm:justify-between">
<Button
variant="borderless"
color="secondary"
@@ -398,7 +418,7 @@ export default function RolePermissionEditorForm({
</Button>
{!disabled && (
<Box className="grid grid-flow-row sm:grid-flow-col gap-2">
<Box className="grid grid-flow-row gap-2 sm:grid-flow-col">
{Boolean(permission) && (
<Button
variant="outlined"

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

@@ -6,12 +6,12 @@ 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';
import getPermissionVariablesArray from '@/utils/settings/getPermissionVariablesArray';
import { useGetAppCustomClaimsQuery } from '@/utils/__generated__/graphql';
import clsx from 'clsx';
import { useController, useFormContext, useWatch } from 'react-hook-form';
import useRuleGroupEditor from './useRuleGroupEditor';
@@ -212,12 +212,13 @@ export default function RuleValueInput({
<ControlledAutocomplete
disabled={disabled}
freeSolo={!isHasuraInput}
autoSelect={!isHasuraInput}
autoHighlight={isHasuraInput}
filterSelectedOptions
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();
@@ -230,7 +231,7 @@ export default function RuleValueInput({
sx: sharedInputSx,
},
formControl: { className: '!bg-transparent' },
paper: { className: clsx(!isHasuraInput && 'hidden') },
paper: { className: 'empty:border-transparent' },
}}
fullWidth
loading={loading}

View File

@@ -58,9 +58,10 @@ export default function DeploymentListItem({
return (
<ListItem.Root>
<ListItem.Button
className="grid grid-flow-col items-center justify-between gap-2 px-2 py-2"
className="grid grid-flow-col items-center justify-between gap-2 rounded-none px-2 py-2"
component={NavLink}
href={`/${currentWorkspace.slug}/${currentApplication.slug}/deployments/${deployment.id}`}
aria-label={commitMessage || 'No commit message'}
>
<div className="flex cursor-pointer flex-row items-center justify-center space-x-2 self-center">
<ListItem.Avatar>
@@ -83,10 +84,14 @@ export default function DeploymentListItem({
/>
</div>
<div className="grid grid-flow-col gap-2 items-center">
<div className="grid grid-flow-col items-center gap-2">
{showRedeploy && (
<Tooltip
title="Deployments cannot be re-triggered when a deployment is in progress."
title={
!disableRedeploy && !loading
? 'Deployments cannot be re-triggered when a deployment is in progress.'
: ''
}
hasDisabledChildren={disableRedeploy || loading}
disableHoverListener={!disableRedeploy}
>
@@ -123,9 +128,10 @@ export default function DeploymentListItem({
);
}}
startIcon={
<ArrowCounterclockwiseIcon className={twMerge('w-4 h-4')} />
<ArrowCounterclockwiseIcon className={twMerge('h-4 w-4')} />
}
className="rounded-full py-1 px-2 text-xs"
aria-label="Redeploy"
>
Redeploy
</Button>
@@ -133,7 +139,7 @@ export default function DeploymentListItem({
)}
{isLive && (
<div className="w-12 flex justify-end">
<div className="flex w-12 justify-end">
<Chip size="small" color="success" label="Live" />
</div>
)}

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

@@ -67,7 +67,7 @@ export function InviteAnnounce() {
triggerToast('An error occurred when trying to accept the invitation.');
return setSubmitState({
error: res.error,
error: new Error(res.error.message),
loading: false,
});
}

View File

@@ -45,12 +45,6 @@ export default function AuthenticatedLayout({
return;
}
if (router.pathname === '/') {
router.push('/signup');
return;
}
router.push('/signin');
}, [isLoading, isAuthenticated, router, isPlatform]);
@@ -84,7 +78,7 @@ export default function AuthenticatedLayout({
<Container
rootClassName="h-full"
className="grid justify-center max-w-md grid-flow-row gap-2 my-12 text-center"
className="my-12 grid max-w-md grid-flow-row justify-center gap-2 text-center"
>
<div className="mx-auto">
<Image
@@ -121,7 +115,7 @@ export default function AuthenticatedLayout({
}
return (
<BaseLayout className="flex flex-col h-full" {...props}>
<BaseLayout className="flex h-full flex-col" {...props}>
<Header className="flex max-h-[59px] flex-auto" />
<InviteAnnounce />

View File

@@ -2,9 +2,14 @@ import { LoadingScreen } from '@/components/common/LoadingScreen';
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
import type { BaseLayoutProps } from '@/components/layout/BaseLayout';
import BaseLayout from '@/components/layout/BaseLayout';
import Container from '@/components/layout/Container';
import useIsPlatform from '@/hooks/common/useIsPlatform';
import { useCleanWorkspaceContext } from '@/hooks/use-cleanWorkspaceContext';
import Box from '@/ui/v2/Box';
import ThemeProvider from '@/ui/v2/ThemeProvider';
import GlobalStyles from '@mui/material/GlobalStyles';
import { useAuthenticationStatus } from '@nhost/nextjs';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
@@ -28,14 +33,76 @@ export default function UnauthenticatedLayout({
if (!isPlatform || isLoading || isAuthenticated) {
return (
<BaseLayout {...props}>
<LoadingScreen />
<LoadingScreen
sx={{ backgroundColor: (theme) => theme.palette.background.default }}
slotProps={{
activityIndicator: {
className: 'text-white',
},
}}
/>
</BaseLayout>
);
}
return (
<BaseLayout {...props}>
<RetryableErrorBoundary>{children}</RetryableErrorBoundary>
</BaseLayout>
<ThemeProvider color="dark">
<BaseLayout {...props}>
<GlobalStyles
styles={{
'html, body': {
backgroundColor: `#000 !important`,
},
'#__next': {
overflow: 'auto',
},
}}
/>
<RetryableErrorBoundary>
<Box
className="flex min-h-screen items-center"
sx={{ backgroundColor: (theme) => theme.palette.common.black }}
>
<Container
rootClassName="bg-transparent h-full"
className="grid h-full w-full items-center justify-items-center gap-12 bg-transparent pt-8 pb-12 lg:grid-cols-2 lg:gap-4 lg:pt-0 lg:pb-0"
>
<div className="relative z-10 order-2 grid w-full max-w-[544px] grid-flow-row gap-12 lg:order-1">
{children}
</div>
<div className="relative z-0 order-1 flex h-full w-full items-center justify-center md:min-h-[150px] lg:order-2 lg:min-h-[none]">
<div className="absolute top-0 left-0 right-0 bottom-0 mx-auto flex h-full w-full max-w-xl items-center justify-center opacity-70">
<Image
priority
src="/assets/line-grid.svg"
width={1003}
height={644}
alt="Transparent lines"
objectFit="fill"
className="h-full w-full scale-[200%]"
/>
</div>
<Box
className="backface-hidden absolute left-0 right-0 z-0 mx-auto h-20 w-20 transform-gpu rounded-full opacity-80 blur-[56px]"
sx={{
backgroundColor: (theme) => theme.palette.primary.main,
}}
/>
<Image
src="/assets/logo.svg"
width={119}
height={40}
alt="Nhost Logo"
/>
</div>
</Container>
</Box>
</RetryableErrorBoundary>
</BaseLayout>
</ThemeProvider>
);
}

View File

@@ -184,7 +184,6 @@ export default function LogsHeader({
key={value}
value={value}
className="text-sm+ font-medium"
disabled={label === 'Hasura'}
>
{label}
</Option>

View File

@@ -0,0 +1,238 @@
import { UserDataProvider } from '@/context/workspace1-context';
import type { Application } from '@/types/application';
import { ApplicationStatus } from '@/types/application';
import type { Workspace } from '@/types/workspace';
import { render, screen, waitForElementToBeRemoved } from '@/utils/testUtils';
import { graphql, rest } from 'msw';
import { setupServer } from 'msw/node';
import { afterAll, beforeAll, vi } from 'vitest';
import OverviewDeployments from '.';
vi.mock('next/router', () => ({
useRouter: vi.fn().mockReturnValue({
basePath: '',
pathname: '/test-workspace/test-application',
route: '/[workspaceSlug]/[appSlug]',
asPath: '/test-workspace/test-application',
isLocaleDomain: false,
isReady: true,
isPreview: false,
query: {
workspaceSlug: 'test-workspace',
appSlug: 'test-application',
},
push: vi.fn(),
replace: vi.fn(),
reload: vi.fn(),
back: vi.fn(),
prefetch: vi.fn(),
beforePopState: vi.fn(),
events: {
on: vi.fn(),
off: vi.fn(),
emit: vi.fn(),
},
isFallback: false,
}),
}));
const mockApplication: Application = {
id: '1',
name: 'Test Application',
slug: 'test-application',
appStates: [],
hasuraGraphqlAdminSecret: 'nhost-admin-secret',
subdomain: '',
isProvisioned: true,
region: {
awsName: 'us-east-1',
city: 'New York',
countryCode: 'US',
id: '1',
},
createdAt: new Date().toISOString(),
deployments: [],
desiredState: ApplicationStatus.Live,
featureFlags: [],
providersUpdated: true,
githubRepository: { fullName: 'test/git-project' },
};
const mockWorkspace: Workspace = {
id: '1',
name: 'Test Workspace',
slug: 'test-workspace',
members: [],
applications: [mockApplication],
};
const mockGraphqlLink = graphql.link('http://localhost:1337/v1/graphql');
const server = setupServer(
rest.get('http://localhost:1337/v1/graphql', (req, res, ctx) =>
res(ctx.status(200)),
),
mockGraphqlLink.operation(async (req, res, ctx) =>
res(
ctx.data({
deployments: [],
}),
),
),
);
beforeAll(() => {
process.env.NEXT_PUBLIC_NHOST_PLATFORM = 'true';
process.env.NEXT_PUBLIC_ENV = 'production';
server.listen();
});
afterEach(() => server.resetHandlers());
afterAll(() => {
server.close();
vi.restoreAllMocks();
});
test('should render an empty state when GitHub is not connected', () => {
render(
<UserDataProvider
initialWorkspaces={[
{
...mockWorkspace,
applications: [{ ...mockApplication, githubRepository: null }],
},
]}
>
<OverviewDeployments />
</UserDataProvider>,
);
expect(screen.getByText(/no deployments/i)).toBeInTheDocument();
expect(
screen.getByRole('button', { name: /connect to github/i }),
).toBeInTheDocument();
});
test('should render an empty state when GitHub is connected, but there are no deployments', async () => {
render(
<UserDataProvider initialWorkspaces={[mockWorkspace]}>
<OverviewDeployments />
</UserDataProvider>,
);
expect(screen.getByText(/^deployments$/i)).toBeInTheDocument();
expect(screen.getByRole('link', { name: /view all/i })).toBeInTheDocument();
await waitForElementToBeRemoved(() => screen.queryByRole('progressbar'));
expect(screen.getByText(/no deployments/i)).toBeInTheDocument();
expect(screen.getByText(/test\/git-project/i)).toBeInTheDocument();
expect(screen.getByRole('link', { name: /edit/i })).toHaveAttribute(
'href',
'/test-workspace/test-application/settings/git',
);
});
test('should render a list of deployments', async () => {
server.use(
mockGraphqlLink.operation(async (req, res, ctx) => {
const requestPayload = await req.json();
if (requestPayload.operationName === 'ScheduledOrPendingDeploymentsSub') {
return res(ctx.data({ deployments: [] }));
}
return res(
ctx.data({
deployments: [
{
id: '1',
commitSHA: 'abc123',
deploymentStartedAt: '2021-08-01T00:00:00.000Z',
deploymentEndedAt: '2021-08-01T00:05:00.000Z',
deploymentStatus: 'DEPLOYED',
commitUserName: 'test.user',
commitUserAvatarUrl: 'http://images.example.com/avatar.png',
commitMessage: 'Test commit message',
},
],
}),
);
}),
);
render(
<UserDataProvider initialWorkspaces={[mockWorkspace]}>
<OverviewDeployments />
</UserDataProvider>,
);
await waitForElementToBeRemoved(() => screen.queryByRole('progressbar'));
expect(screen.getByText(/test commit message/i)).toBeInTheDocument();
expect(screen.getByLabelText(/avatar/i)).toHaveStyle(
'background-image: url(http://images.example.com/avatar.png)',
);
expect(
screen.getByRole('link', {
name: /test commit message/i,
}),
).toHaveAttribute('href', '/test-workspace/test-application/deployments/1');
expect(screen.getByText(/5m 0s/i)).toBeInTheDocument();
expect(screen.getByText(/live/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /redeploy/i })).not.toBeDisabled();
});
test('should disable redeployments if a deployment is already in progress', async () => {
server.use(
mockGraphqlLink.operation(async (req, res, ctx) => {
const requestPayload = await req.json();
if (requestPayload.operationName === 'ScheduledOrPendingDeploymentsSub') {
return res(
ctx.data({
deployments: [
{
id: '2',
commitSHA: 'abc234',
deploymentStartedAt: '2021-08-02T00:00:00.000Z',
deploymentEndedAt: null,
deploymentStatus: 'PENDING',
commitUserName: 'test.user',
commitUserAvatarUrl: 'http://images.example.com/avatar.png',
commitMessage: 'Test commit message',
},
],
}),
);
}
return res(
ctx.data({
deployments: [
{
id: '1',
commitSHA: 'abc123',
deploymentStartedAt: '2021-08-01T00:00:00.000Z',
deploymentEndedAt: '2021-08-01T00:05:00.000Z',
deploymentStatus: 'DEPLOYED',
commitUserName: 'test.user',
commitUserAvatarUrl: 'http://images.example.com/avatar.png',
commitMessage: 'Test commit message',
},
],
}),
);
}),
);
render(
<UserDataProvider initialWorkspaces={[mockWorkspace]}>
<OverviewDeployments />
</UserDataProvider>,
);
await waitForElementToBeRemoved(() => screen.queryByRole('progressbar'));
expect(screen.getByRole('button', { name: /redeploy/i })).toBeDisabled();
});

View File

@@ -6,6 +6,7 @@ import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button';
import Divider from '@/ui/v2/Divider';
import ChevronRightIcon from '@/ui/v2/icons/ChevronRightIcon';
import RocketIcon from '@/ui/v2/icons/RocketIcon';
import List from '@/ui/v2/List';
import Text from '@/ui/v2/Text';
@@ -14,7 +15,6 @@ import {
useGetDeploymentsSubSubscription,
useScheduledOrPendingDeploymentsSubSubscription,
} from '@/utils/__generated__/graphql';
import { ChevronRightIcon } from '@heroicons/react/solid';
import NavLink from 'next/link';
import { Fragment } from 'react';
@@ -22,16 +22,16 @@ function OverviewDeploymentsTopBar() {
const { currentWorkspace, currentApplication } =
useCurrentWorkspaceAndApplication();
const { githubRepository } = currentApplication;
const { githubRepository } = currentApplication || {};
return (
<div className="grid grid-flow-col gap-2 items-center place-content-between pb-4">
<div className="grid grid-flow-col place-content-between items-center gap-2 pb-4">
<Text variant="h3" className="font-medium">
Deployments
</Text>
<NavLink
href={`/${currentWorkspace.slug}/${currentApplication.slug}/deployments`}
href={`/${currentWorkspace?.slug}/${currentApplication?.slug}/deployments`}
passHref
>
<Button variant="borderless" disabled={!githubRepository}>
@@ -43,20 +43,12 @@ function OverviewDeploymentsTopBar() {
);
}
interface OverviewDeploymentsProps {
projectId: string;
githubRepository: { fullName: string };
}
function OverviewDeployments({
projectId,
githubRepository,
}: OverviewDeploymentsProps) {
function OverviewDeploymentList() {
const { currentWorkspace, currentApplication } =
useCurrentWorkspaceAndApplication();
const { data, loading } = useGetDeploymentsSubSubscription({
variables: {
id: projectId,
id: currentApplication?.id,
limit: 5,
offset: 0,
},
@@ -67,23 +59,23 @@ function OverviewDeployments({
loading: scheduledOrPendingDeploymentsLoading,
} = useScheduledOrPendingDeploymentsSubSubscription({
variables: {
appId: projectId,
appId: currentApplication?.id,
},
});
if (loading || scheduledOrPendingDeploymentsLoading) {
return (
<Box className="h-[323px] p-2 border-1 rounded-lg">
<Box className="h-[323px] rounded-lg border-1 p-2">
<ActivityIndicator label="Loading deployments..." />
</Box>
);
}
const { deployments } = data;
const { deployments } = data || { deployments: [] };
if (deployments.length === 0) {
if (!deployments?.length) {
return (
<Box className="grid grid-flow-row gap-5 items-center justify-items-center rounded-lg py-12 px-48 shadow-sm border-1">
<Box className="grid grid-flow-row items-center justify-items-center gap-5 overflow-hidden rounded-lg border-1 py-12 px-48 shadow-sm">
<RocketIcon
strokeWidth={1}
className="h-10 w-10"
@@ -100,16 +92,16 @@ function OverviewDeployments({
</div>
<Box
className="mt-6 flex flex-row place-content-between rounded-lg py-2 px-2 max-w-sm w-full"
className="mt-6 flex w-full max-w-sm flex-row place-content-between rounded-lg py-2 px-2"
sx={{ backgroundColor: 'grey.200' }}
>
<Box
className="grid grid-flow-col gap-1.5 ml-2"
className="ml-2 grid grid-flow-col gap-1.5"
sx={{ backgroundColor: 'transparent' }}
>
<GithubIcon className="h-4 w-4 self-center" />
<Text variant="body1" className="self-center font-normal">
{githubRepository.fullName}
{currentApplication?.githubRepository?.fullName}
</Text>
</Box>
@@ -128,20 +120,20 @@ function OverviewDeployments({
const liveDeploymentId = getLastLiveDeployment(deployments);
const { deployments: scheduledOrPendingDeployments } =
scheduledOrPendingDeploymentsData;
scheduledOrPendingDeploymentsData || { deployments: [] };
return (
<List
className="rounded-x-lg flex flex-col rounded-lg"
className="rounded-x-lg flex flex-col overflow-hidden rounded-lg"
sx={{ borderColor: 'grey.300', borderWidth: 1 }}
>
{deployments.map((deployment, index) => (
{deployments?.map((deployment, index) => (
<Fragment key={deployment.id}>
<DeploymentListItem
deployment={deployment}
isLive={deployment.id === liveDeploymentId}
showRedeploy={index === 0}
disableRedeploy={scheduledOrPendingDeployments.length > 0}
disableRedeploy={scheduledOrPendingDeployments?.length > 0}
/>
{index !== deployments.length - 1 && <Divider component="li" />}
@@ -151,21 +143,18 @@ function OverviewDeployments({
);
}
export default function OverviewDeploymentsPage() {
export default function OverviewDeployments() {
const { currentApplication } = useCurrentWorkspaceAndApplication();
const { openGitHubModal } = useGitHubModal();
const { githubRepository } = currentApplication;
const { githubRepository } = currentApplication || {};
// GitHub repo connected. Show deployments
if (githubRepository) {
return (
<div className="flex flex-col">
<OverviewDeploymentsTopBar />
<OverviewDeployments
projectId={currentApplication.id}
githubRepository={githubRepository}
/>
<OverviewDeploymentList />
</div>
);
}
@@ -175,7 +164,7 @@ export default function OverviewDeploymentsPage() {
<div className="flex flex-col">
<OverviewDeploymentsTopBar />
<Box className="grid grid-flow-row gap-5 items-center justify-items-center rounded-lg py-12 px-48 shadow-sm border-1">
<Box className="grid grid-flow-row items-center justify-items-center gap-5 rounded-lg border-1 py-12 px-48 shadow-sm">
<RocketIcon strokeWidth={1} className="h-10 w-10" />
<div className="grid grid-flow-row gap-1">

View File

@@ -107,7 +107,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

@@ -59,6 +59,7 @@ export function Avatar({
<Box
style={Object.assign(style, { backgroundImage: `url(${avatarUrl})` })}
className={classes}
aria-label="Avatar"
{...rest}
/>
);

View File

@@ -63,7 +63,9 @@ function ActivityIndicator({
// We are rendering a span instead of null in order to keep the layout
// intact in certain cases (e.g. when elements have a "space-between"
// position).
return <span />;
return (
<span role="progressbar" aria-label="Activity indicator placeholder" />
);
}
return (

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

@@ -40,7 +40,7 @@ const StyledMenu = styled(MaterialMenu)(({ theme }) => ({
borderColor:
theme.palette.mode === 'dark'
? `${theme.palette.grey[400]} !important`
: 'none',
: 'transparent',
boxShadow:
theme.palette.mode === 'light'
? '0px 4px 10px rgba(33, 50, 75, 0.25)'

View File

@@ -1,16 +1,24 @@
import type { LinkProps as MaterialLinkProps } from '@mui/material/Link';
import MaterialLink from '@mui/material/Link';
import MaterialLink, {
linkClasses as materialLinkClasses,
} from '@mui/material/Link';
import type { ForwardedRef } from 'react';
import { forwardRef } from 'react';
export interface LinkProps extends MaterialLinkProps {}
export interface LinkProps extends MaterialLinkProps {
/**
* Controls when the link should have an underline.
* @default 'hover'
*/
underline?: MaterialLinkProps['underline'];
}
function Link(
{ children, ...props }: LinkProps,
{ children, underline = 'hover', ...props }: LinkProps,
ref: ForwardedRef<HTMLAnchorElement>,
) {
return (
<MaterialLink ref={ref} {...props}>
<MaterialLink underline={underline} ref={ref} {...props}>
{children}
</MaterialLink>
);
@@ -18,4 +26,6 @@ function Link(
Link.displayName = 'NhostLink';
export { materialLinkClasses as linkClasses };
export default forwardRef(Link);

View File

@@ -12,6 +12,7 @@ export interface OptionProps<TValue extends {}>
const StyledOption = styled(OptionUnstyled)(({ theme }) => ({
transition: theme.transitions.create(['background-color']),
color: theme.palette.text.primary,
[`&.${optionUnstyledClasses.selected}`]: {
backgroundColor:
theme.palette.mode === 'dark'

View File

@@ -53,7 +53,7 @@ const StyledListbox = styled('ul')(({ theme }) => ({
? `1px solid ${theme.palette.grey[300]}`
: 'none',
borderWidth: theme.palette.mode === 'dark' ? 1 : 0,
borderColor: theme.palette.mode === 'dark' ? 'grey.400' : 'none',
borderColor: theme.palette.mode === 'dark' ? theme.palette.grey[400] : 'none',
'&:focus': {
outline: 'none',
},

View File

@@ -19,6 +19,10 @@ export type TextProps<
* @default 'primary'
*/
color?: 'primary' | 'secondary' | 'disabled' | 'error';
/**
* The component used for the root node.
*/
component?: D;
};
const textClasses = {

View File

@@ -4,23 +4,31 @@ import useColorPreference from '@/ui/v2/useColorPreference';
import CssBaseline from '@mui/material/CssBaseline';
import GlobalStyles from '@mui/material/GlobalStyles';
import { ThemeProvider as MaterialThemeProvider } from '@mui/material/styles';
import Head from 'next/head';
import type { PropsWithChildren } from 'react';
function ThemeProviderContent({ children }: PropsWithChildren<unknown>) {
function ThemeProviderContent({
children,
color: manualColor,
}: PropsWithChildren<{ color?: 'light' | 'dark' }>) {
const { color } = useColorPreference();
const theme = createTheme(color);
const theme = createTheme(manualColor || color);
return (
<MaterialThemeProvider theme={theme}>
<CssBaseline />
<GlobalStyles
styles={{
body: {
backgroundColor: theme.palette.background.default,
'html, body': {
backgroundColor: `${theme.palette.background.default} !important`,
},
}}
/>
<Head>
<meta name="theme-color" content={theme.palette.background.paper} />
</Head>
{children}
</MaterialThemeProvider>
);
@@ -33,17 +41,22 @@ export interface ThemeProviderProps extends PropsWithChildren<unknown> {
* @default 'color-mode'
*/
colorPreferenceStorageKey?: string;
/**
* Manually set the color preference.
*/
color?: 'light' | 'dark';
}
function ThemeProvider({
children,
color,
colorPreferenceStorageKey = 'color-preference',
}: ThemeProviderProps) {
return (
<ColorPreferenceProvider
colorPreferenceStorageKey={colorPreferenceStorageKey}
>
<ThemeProviderContent>{children}</ThemeProviderContent>
<ThemeProviderContent color={color}>{children}</ThemeProviderContent>
</ColorPreferenceProvider>
);
}

View File

@@ -6,7 +6,7 @@ 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 fetch from 'cross-fetch';
import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
@@ -79,28 +79,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?.();
} 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.'),
);
} catch {
// Note: Error is already handled by toast.promise
}
}
@@ -137,7 +146,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

@@ -268,7 +268,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 +276,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

@@ -25,10 +25,25 @@ export const UserDataContext = createContext<UserDataContent>({
setUserContext: () => {},
});
export function UserDataProvider({ children }: PropsWithChildren<unknown>) {
export interface UserDataProviderProps {
/**
* Initial workspaces to be used in the context.
*/
initialWorkspaces?: Workspace[];
/**
* Initial metadata to be used in the context.
*/
initialMetadata?: Record<string, any>;
}
export function UserDataProvider({
children,
initialWorkspaces,
initialMetadata,
}: PropsWithChildren<UserDataProviderProps>) {
const [userContext, setUserContext] = useState({
workspaces: [],
metadata: {},
workspaces: initialWorkspaces || [],
metadata: initialMetadata || {},
});
const value = useMemo(

View File

@@ -31,6 +31,7 @@ test('should throw an error if permission object is incorrectly provided', async
action: 'select',
role: 'user',
mode: 'update',
resourceVersion: 1,
}),
).rejects.toThrowError(
new Error(

View File

@@ -17,6 +17,10 @@ export interface ManagePermissionVariables {
* The action to manage the permission for.
*/
action: DatabaseAction;
/**
* The resource version of the metadata.
*/
resourceVersion: number;
/**
* Permission to insert or update.
*/
@@ -45,6 +49,7 @@ export default async function managePermission({
permission,
role,
action,
resourceVersion,
mode = 'update',
}: ManagePermissionOptions & ManagePermissionVariables) {
if (mode !== 'delete' && !permission) {
@@ -87,7 +92,7 @@ export default async function managePermission({
args,
type: 'bulk',
source: dataSource,
version: 1,
resource_version: resourceVersion,
}),
});

View File

@@ -18,6 +18,10 @@ export interface ManagePermissionMigrationVariables {
* The action to manage the permission for.
*/
action: DatabaseAction;
/**
* The resource version of the metadata.
*/
resourceVersion: number;
/**
* Permission to insert or update.
*/

View File

@@ -8,7 +8,12 @@ import fetch from 'cross-fetch';
export interface FetchMetadataOptions
extends Omit<MutationOrQueryBaseOptions, 'schema' | 'table'> {}
export interface FetchMetadataReturnType extends HasuraMetadataSource {}
export interface FetchMetadataReturnType extends Partial<HasuraMetadataSource> {
/**
* The resource version of the metadata.
*/
resourceVersion: number;
}
/**
* Fetch Hasura metadata using the Metadata API.
@@ -33,8 +38,9 @@ export default async function fetchMetadata({
}),
});
const responseData: Record<string, HasuraMetadata> | QueryError =
await response.json();
const responseData:
| { metadata: HasuraMetadata; resource_version: number }
| QueryError = await response.json();
if (!response.ok || 'error' in responseData) {
if ('internal' in responseData) {
@@ -48,9 +54,11 @@ export default async function fetchMetadata({
}
}
const { metadata } = responseData;
const { metadata, resource_version: resourceVersion } = responseData;
const currentSource =
metadata?.sources?.find((source) => source.name === dataSource) || null;
return (
metadata?.sources?.find((source) => source.name === dataSource) || null
);
return currentSource
? { ...currentSource, resourceVersion }
: { resourceVersion };
}

View File

@@ -35,15 +35,15 @@ export default function useNotFoundRedirect() {
}
if (noResolvedWorkspace && notIn404Already) {
router.push('/404');
router.replace('/404');
}
if (noResolvedApplication && notIn404Already) {
router.push('/404');
router.replace('/404');
}
if (isProjectUsingRDS && inSettingsDatabasePage) {
router.push('/404');
router.replace('/404');
}
}, [
updating,

View File

@@ -12,13 +12,11 @@ export function authProtected<P>(Comp: ComponentType<P>) {
const { isAuthenticated, isLoading } = useAuthenticationStatus();
useEffect(() => {
if (!isLoading) {
if (!isAuthenticated && router.pathname === '/') {
router.push('/signup');
} else if (!isAuthenticated) {
router.push('/signin');
}
if (isLoading || isAuthenticated) {
return;
}
router.push('/signin');
}, [isLoading, isAuthenticated, router]);
if (isLoading) {

View File

@@ -1,38 +1,67 @@
import Form from '@/components/common/Form';
import NavLink from '@/components/common/NavLink';
import UnauthenticatedLayout from '@/components/layout/UnauthenticatedLayout';
import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import Link from '@/ui/v2/Link';
import Input, { inputClasses } from '@/ui/v2/Input';
import Text from '@/ui/v2/Text';
import { useTheme } from '@mui/material';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import { yupResolver } from '@hookform/resolvers/yup';
import { styled } from '@mui/material';
import { useResetPassword } from '@nhost/nextjs';
import Image from 'next/image';
import NavLink from 'next/link';
import type { ReactElement } from 'react';
import { useForm } from 'react-hook-form';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import * as Yup from 'yup';
type ResetPasswordFormProps = {
email: string;
};
const validationSchema = Yup.object({
email: Yup.string().label('Email').email().required(),
});
function ResetPasswordForm() {
const { resetPassword, isSent, isLoading, isError, error } =
useResetPassword();
export type ResetPasswordFormValues = Yup.InferType<typeof validationSchema>;
const { register, handleSubmit, getValues } = useForm<ResetPasswordFormProps>(
{
reValidateMode: 'onSubmit',
defaultValues: {
email: '',
},
const StyledInput = styled(Input)({
backgroundColor: 'transparent',
[`& .${inputClasses.input}`]: {
backgroundColor: 'transparent !important',
},
});
export default function ResetPasswordPage() {
const { resetPassword, error, isSent } = useResetPassword();
const form = useForm<ResetPasswordFormValues>({
reValidateMode: 'onSubmit',
defaultValues: {
email: '',
},
);
resolver: yupResolver(validationSchema),
});
const onSubmit = async (data: ResetPasswordFormProps) => {
const { email } = data;
const { register, formState, getValues } = form;
await resetPassword(email);
};
useEffect(() => {
if (!error) {
return;
}
toast.error(
error?.message || 'An error occurred while signing in. Please try again.',
getToastStyleProps(),
);
}, [error]);
async function handleSubmit({ email }: ResetPasswordFormValues) {
try {
await resetPassword(email);
} catch {
toast.error(
'An error occurred while signing up. Please try again.',
getToastStyleProps(),
);
}
}
if (isSent) {
return (
@@ -44,102 +73,54 @@ function ResetPasswordForm() {
}
return (
<div className="flex max-w-2xl flex-col items-center">
<form
onSubmit={handleSubmit(onSubmit)}
className="flex w-full flex-col space-y-3"
<>
<Text
variant="h2"
component="h1"
className="text-center text-3.5xl font-semibold lg:text-4.5xl"
>
<Input
{...register('email')}
autoFocus
id="email"
label="Email"
placeholder="Email"
required
fullWidth
inputProps={{ min: 2, max: 128 }}
spellCheck="false"
autoCapitalize="none"
type="email"
/>
Reset Password
</Text>
<div className="flex flex-col">
<Button type="submit" disabled={isLoading} loading={isLoading}>
Send Reset Instructions
</Button>
</div>
</form>
{isError && (
<div className="my-3">
<Text className="font-medium" color="error">
Error: {error.message}
</Text>
</div>
)}
</div>
);
}
export default function ResetPasswordPage() {
const theme = useTheme();
return (
<div className="flex h-screen items-center justify-center">
<div className="flex max-w-3xl flex-col">
<div className="z-30 mb-8 flex justify-center">
<a href="https://nhost.io" tabIndex={-1}>
<Image
src={
theme.palette.mode === 'dark'
? '/assets/brands/light/nhost-with-text.svg'
: '/assets/brands/nhost-with-text.svg'
}
alt="Nhost Logo"
width={185}
height={64}
<Box className="grid grid-flow-row gap-4 rounded-md border bg-transparent p-6 lg:p-12">
<FormProvider {...form}>
<Form
onSubmit={handleSubmit}
className="grid grid-flow-row gap-4 bg-transparent"
>
<StyledInput
{...register('email')}
type="email"
id="email"
label="Email"
placeholder="Email"
fullWidth
autoFocus
inputProps={{ min: 2, max: 128 }}
error={!!formState.errors.email}
helperText={formState.errors.email?.message}
/>
</a>
</div>
<div className="flex items-center justify-center">
<div className="z-30">
<Box
className="rounded-lg border px-12 py-4"
style={{ width: '480px' }}
<Button
className="!bg-white !text-black disabled:!text-black disabled:!text-opacity-60"
size="large"
type="submit"
disabled={formState.isSubmitting}
loading={formState.isSubmitting}
>
<div className="my-4">
<div className="mb-4 flex justify-center text-lg font-semibold">
Reset your password
</div>
<ResetPasswordForm />
</div>
</Box>
Send Reset Instructions
</Button>
</Form>
</FormProvider>
</Box>
<div className="mt-3 flex justify-center">
<div className="grid grid-flow-col items-center justify-center gap-1">
<Text className="text-sm">Is your password okay?</Text>
<NavLink href="/signin" passHref>
<Link href="signin" underline="hover">
Sign in
</Link>
</NavLink>
</div>
</div>
</div>
<div className="absolute z-0 w-full max-w-[887px]">
<Image
src="/assets/signup/bg-gradient.svg"
alt="Gradient background"
width={887}
height={620}
layout="responsive"
/>
</div>
</div>
</div>
</div>
<Text color="secondary" className="text-center text-base lg:text-lg">
Is your password okay?{' '}
<NavLink href="/signin/email" color="white" className="font-medium">
Sign In
</NavLink>
</Text>
</>
);
}

View File

@@ -1,238 +1,100 @@
import Form from '@/components/common/Form';
import NavLink from '@/components/common/NavLink';
import GithubIcon from '@/components/icons/GithubIcon';
import UnauthenticatedLayout from '@/components/layout/UnauthenticatedLayout';
import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import Link from '@/ui/v2/Link';
import Divider from '@/ui/v2/Divider';
import Text from '@/ui/v2/Text';
import { nhost } from '@/utils/nhost';
import { useTheme } from '@mui/material';
import { useSignInEmailPassword } from '@nhost/nextjs';
import Image from 'next/image';
import NavLink from 'next/link';
import { useRouter } from 'next/router';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import type { ReactElement } from 'react';
import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
function SignInWithGithub({ setSignInMethod }: any) {
const [isLoading, setIsLoading] = useState(false);
export default function SignUpPage() {
const [loading, setLoading] = useState(false);
return (
<div className="flex flex-col items-center">
<button
type="button"
className="flex flex-row items-center space-x-4 rounded-sm+ bg-github px-6 py-3 font-display font-medium text-white transition-all duration-200 ease-in-out disabled:opacity-40"
disabled={isLoading}
onClick={() => {
setIsLoading(true);
nhost.auth.signIn({ provider: 'github' });
}}
<>
<Text
variant="h2"
component="h1"
className="text-center text-3.5xl font-semibold lg:text-4.5xl"
>
<GithubIcon className="h-6 w-6" />
<div>Continue with GitHub</div>
</button>
<div className="mt-2 grid grid-flow-col items-center justify-center gap-px">
<span>or</span>
It&apos;s time to build
</Text>
<Box className="grid grid-flow-row gap-4 rounded-md border bg-transparent p-6 lg:p-12">
<Button
variant="borderless"
type="button"
size="small"
onClick={() => setSignInMethod('email')}
className="hover:bg-transparent hover:underline"
>
sign in with email
</Button>
</div>
</div>
);
}
className="!bg-white !text-black hover:ring-2 hover:ring-white hover:ring-opacity-50 disabled:!text-black disabled:!text-opacity-60"
startIcon={<GithubIcon />}
size="large"
disabled={loading}
loading={loading}
onClick={async () => {
setLoading(true);
type SignInFormProps = {
email: string;
password: string;
};
function SignInWithEmail({ setSignInMethod }: any) {
const { signInEmailPassword, isLoading, isSuccess, isError, error } =
useSignInEmailPassword();
const form = useForm<SignInFormProps>({
reValidateMode: 'onSubmit',
defaultValues: {
email: '',
password: '',
},
});
const { register } = form;
const router = useRouter();
async function onSubmit({ email, password }: SignInFormProps) {
await signInEmailPassword(email, password);
}
if (isSuccess) {
router.push('/');
}
return (
<Box className="flex max-w-2xl flex-col items-center">
<FormProvider register={register} {...form}>
<Form onSubmit={onSubmit} className="grid w-full grid-flow-row gap-3">
<Input
{...register('email')}
autoFocus
id="email"
label="Email"
placeholder="Email"
required
fullWidth
inputProps={{
min: 2,
max: 128,
}}
spellCheck="false"
autoCapitalize="none"
type="email"
hideEmptyHelperText
/>
<Input
{...register('password')}
id="password"
placeholder="Password"
required
fullWidth
label={
<span className="grid grid-flow-col justify-between">
<span>Password</span>
<NavLink href="/reset-password" passHref>
<Link
href="reset-password"
tabIndex={-1}
className="text-xs"
underline="hover"
>
Forgot your password?
</Link>
</NavLink>
</span>
try {
await nhost.auth.signIn({ provider: 'github' });
} catch {
toast.error(
`An error occurred while trying to sign in using GitHub. Please try again later.`,
getToastStyleProps(),
);
} finally {
setLoading(false);
}
inputProps={{ min: 2, max: 128 }}
spellCheck="false"
autoCapitalize="none"
type="password"
hideEmptyHelperText
/>
}}
>
Continue with GitHub
</Button>
<div className="flex flex-col">
<Button type="submit" disabled={isLoading} loading={isLoading}>
Sign In
</Button>
</div>
</Form>
</FormProvider>
{isError && (
<Text className="my-3 font-medium" color="error">
Error: {error.message}
</Text>
)}
<div className="mt-2 grid grid-flow-col items-center justify-center gap-px">
<span>or</span>
<Button
variant="borderless"
type="button"
size="small"
onClick={() => setSignInMethod('github')}
className="hover:bg-transparent hover:underline"
className="!text-white hover:!bg-white hover:!bg-opacity-10 focus:!bg-white focus:!bg-opacity-10"
size="large"
href="/signin/email"
LinkComponent={NavLink}
>
sign in with GitHub
Continue with Email
</Button>
</div>
</Box>
<Divider className="!my-2" />
<Text color="secondary" className="text-center text-sm">
By clicking continue, you agree to our{' '}
<NavLink
href="https://nhost.io/legal/terms-of-service"
target="_blank"
rel="noopener noreferrer"
className="font-semibold"
color="white"
>
Terms of Service
</NavLink>{' '}
and{' '}
<NavLink
href="https://nhost.io/legal/privacy-policy"
target="_blank"
rel="noopener noreferrer"
className="font-semibold"
color="white"
>
Privacy Policy
</NavLink>
</Text>
</Box>
<Text color="secondary" className="text-center lg:text-lg">
Don&apos;t have an account?{' '}
<NavLink href="/signup" color="white" className="font-medium">
Sign Up
</NavLink>
</Text>
</>
);
}
function SignInController() {
const [signInMethod, setSignInMethod] = useState('github');
if (signInMethod === 'github') {
return <SignInWithGithub setSignInMethod={setSignInMethod} />;
}
if (signInMethod === 'email') {
return <SignInWithEmail setSignInMethod={setSignInMethod} />;
}
return null;
}
export default function SignInPage() {
const theme = useTheme();
return (
<div className="flex h-screen items-center justify-center">
<div className="flex max-w-3xl flex-col">
<div className="z-30 mb-8 flex justify-center">
<a href="https://nhost.io" tabIndex={-1}>
<Image
src={
theme.palette.mode === 'dark'
? '/assets/brands/light/nhost-with-text.svg'
: '/assets/brands/nhost-with-text.svg'
}
alt="Nhost Logo"
width={185}
height={64}
/>
</a>
</div>
<div className="flex items-center justify-center">
<div className="z-10">
<Box
className="grid grid-flow-row gap-4 rounded-lg border px-12 py-8"
style={{ width: '480px' }}
>
<Text variant="h1" className="text-center text-lg font-semibold">
Sign in to Nhost
</Text>
<SignInController />
</Box>
<div className="mt-3 flex justify-center">
<div className="grid grid-flow-col items-center justify-center gap-1">
<Text className="text-sm">Don&apos;t have an account?</Text>
<NavLink href="/signup" passHref>
<Link href="signup" underline="hover">
Sign up
</Link>
</NavLink>
</div>
</div>
</div>
<div className="absolute z-0 w-full max-w-[887px]">
<Image
src="/assets/signup/bg-gradient.svg"
alt="Gradient background"
width={887}
height={620}
layout="responsive"
/>
</div>
</div>
</div>
</div>
);
}
SignInPage.getLayout = function getLayout(page: ReactElement) {
SignUpPage.getLayout = function getLayout(page: ReactElement) {
return <UnauthenticatedLayout title="Sign In">{page}</UnauthenticatedLayout>;
};

View File

@@ -0,0 +1,155 @@
import Form from '@/components/common/Form';
import NavLink from '@/components/common/NavLink';
import UnauthenticatedLayout from '@/components/layout/UnauthenticatedLayout';
import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button';
import Input, { inputClasses } from '@/ui/v2/Input';
import Text from '@/ui/v2/Text';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import { yupResolver } from '@hookform/resolvers/yup';
import { styled } from '@mui/material';
import { useSignInEmailPassword } from '@nhost/nextjs';
import type { ReactElement } from 'react';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import * as Yup from 'yup';
const validationSchema = Yup.object({
email: Yup.string().label('Email').email().required(),
password: Yup.string().label('Password').required(),
});
export type EmailSignUpFormValues = Yup.InferType<typeof validationSchema>;
const StyledInput = styled(Input)({
backgroundColor: 'transparent',
[`& .${inputClasses.input}`]: {
backgroundColor: 'transparent !important',
},
});
export default function EmailSignUpPage() {
const { signInEmailPassword, error } = useSignInEmailPassword();
const form = useForm<EmailSignUpFormValues>({
reValidateMode: 'onSubmit',
defaultValues: {
email: '',
password: '',
},
resolver: yupResolver(validationSchema),
});
const { register, formState } = form;
useEffect(() => {
if (!error) {
return;
}
toast.error(
error?.message || 'An error occurred while signing in. Please try again.',
getToastStyleProps(),
);
}, [error]);
async function handleSubmit({ email, password }: EmailSignUpFormValues) {
try {
await signInEmailPassword(email, password);
} catch {
toast.error(
'An error occurred while signing in. Please try again.',
getToastStyleProps(),
);
}
}
return (
<>
<Text
variant="h2"
component="h1"
className="text-center text-3.5xl font-semibold lg:text-4.5xl"
>
Sign In
</Text>
<Box className="grid grid-flow-row gap-4 rounded-md border bg-transparent p-6 lg:p-12">
<FormProvider {...form}>
<Form
onSubmit={handleSubmit}
className="grid grid-flow-row gap-4 bg-transparent"
>
<StyledInput
{...register('email')}
id="email"
placeholder="Email"
inputProps={{ min: 2, max: 128 }}
spellCheck="false"
autoCapitalize="none"
type="email"
label="Email"
hideEmptyHelperText
fullWidth
error={!!formState.errors.email}
helperText={formState.errors.email?.message}
autoFocus
/>
<StyledInput
{...register('password')}
id="password"
placeholder="Password"
inputProps={{ min: 2, max: 128 }}
spellCheck="false"
autoCapitalize="none"
type="password"
label="Password"
hideEmptyHelperText
fullWidth
error={!!formState.errors.password}
helperText={formState.errors.password?.message}
/>
<NavLink
href="/reset-password"
color="white"
className="justify-self-start font-semibold"
>
Forgot password?
</NavLink>
<Button
className="!bg-white !text-black disabled:!text-black disabled:!text-opacity-60"
size="large"
disabled={formState.isSubmitting}
loading={formState.isSubmitting}
type="submit"
>
Sign In
</Button>
<Text color="secondary" className="text-center">
or{' '}
<NavLink color="white" className="font-semibold" href="/signin">
sign in with GitHub
</NavLink>
</Text>
</Form>
</FormProvider>
</Box>
<Text color="secondary" className="text-center text-base lg:text-lg">
Don&apos;t have an account?{' '}
<NavLink href="/signup" color="white">
Sign Up
</NavLink>
</Text>
</>
);
}
EmailSignUpPage.getLayout = function getLayout(page: ReactElement) {
return <UnauthenticatedLayout title="Sign In">{page}</UnauthenticatedLayout>;
};

View File

@@ -1,342 +1,214 @@
import Form from '@/components/common/Form';
import NavLink from '@/components/common/NavLink';
import GithubIcon from '@/components/icons/GithubIcon';
import UnauthenticatedLayout from '@/components/layout/UnauthenticatedLayout';
import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import Link from '@/ui/v2/Link';
import Divider from '@/ui/v2/Divider';
import Input, { inputClasses } from '@/ui/v2/Input';
import Text from '@/ui/v2/Text';
import { nhost } from '@/utils/nhost';
import { useTheme } from '@mui/material';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import { yupResolver } from '@hookform/resolvers/yup';
import { styled } from '@mui/material';
import { useSignUpEmailPassword } from '@nhost/nextjs';
import kebabCase from 'just-kebab-case';
import Image from 'next/image';
import NavLink from 'next/link';
import { useRouter } from 'next/router';
import type { ReactElement } from 'react';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import * as Yup from 'yup';
const sellingPoints = [
'Awesome Free Plan',
'Postgres Database',
'Instant GraphQL API with Hasura',
'Authentication and Storage',
'TypeScript Client',
'Serverless Functions',
'Push code to deploy with our GitHub Integration',
];
const validationSchema = Yup.object({
email: Yup.string().label('Email').email().required(),
password: Yup.string().label('Password').required(),
displayName: Yup.string().label('Name').required(),
});
const companies = [
'Revtron',
'HyperLab',
'Orthopy',
'Celsia',
'ServeHub',
'Teamtailor',
];
export type SignUpFormValues = Yup.InferType<typeof validationSchema>;
function SignUpWithGithub({ setSignUpMethod }: any) {
const [isLoading, setIsLoading] = useState(false);
const StyledInput = styled(Input)({
backgroundColor: 'transparent',
[`& .${inputClasses.input}`]: {
backgroundColor: 'transparent !important',
},
});
return (
<div className="flex flex-col items-center">
<button
type="button"
className="flex flex-row items-center space-x-6 rounded-sm+ bg-github px-6 py-3 font-display font-medium text-white transition-all duration-200 ease-in-out disabled:opacity-40"
disabled={isLoading}
onClick={() => {
setIsLoading(true);
nhost.auth.signIn({ provider: 'github' });
}}
>
<GithubIcon className="h-6 w-6 text-white" />
<div>Sign Up with GitHub</div>
</button>
<div className="mt-2 grid grid-flow-col items-center justify-center gap-px">
<span>or</span>
<Button
variant="borderless"
type="button"
size="small"
onClick={() => setSignUpMethod('email')}
className="hover:bg-transparent hover:underline"
>
sign up with email
</Button>
</div>
</div>
);
}
export default function SignUpPage() {
const { signUpEmailPassword, error } = useSignUpEmailPassword();
const [loading, setLoading] = useState(false);
type SignUpFormProps = {
displayName: string;
email: string;
password: string;
};
function SignUpWithEmail({ setSignUpMethod }: any) {
const { signUpEmailPassword, isLoading, isSuccess, isError, error } =
useSignUpEmailPassword();
const form = useForm<SignUpFormProps>({
const form = useForm<SignUpFormValues>({
reValidateMode: 'onSubmit',
defaultValues: {
displayName: '',
email: '',
password: '',
},
resolver: yupResolver(validationSchema),
});
const { register } = form;
const { register, formState } = form;
const router = useRouter();
useEffect(() => {
if (!error) {
return;
}
async function onSubmit({ email, password, displayName }: SignUpFormProps) {
await signUpEmailPassword(email, password, {
displayName,
});
}
toast.error(
error?.message || 'An error occurred while signing up. Please try again.',
getToastStyleProps(),
);
}, [error]);
if (isSuccess) {
router.push('/');
async function handleSubmit({
email,
password,
displayName,
}: SignUpFormValues) {
try {
await signUpEmailPassword(email, password, { displayName });
} catch {
toast.error(
'An error occurred while signing up. Please try again.',
getToastStyleProps(),
);
}
}
return (
<div className="grid grid-flow-row items-center justify-items-center gap-2">
<Text variant="h1" className="text-lg font-semibold">
Sign Up with Email
<>
<Text
variant="h2"
component="h1"
className="text-center text-3.5xl font-semibold lg:text-4.5xl"
>
Sign Up
</Text>
<FormProvider register={register} {...form}>
<Form onSubmit={onSubmit} className="grid w-full grid-flow-row gap-3">
<Input
{...register('displayName')}
id="displayName"
placeholder="Name"
required
inputProps={{
min: 2,
max: 128,
}}
spellCheck="false"
autoCapitalize="none"
type="text"
autoFocus
label="Name"
hideEmptyHelperText
fullWidth
autoComplete="off"
/>
<Input
{...register('email')}
id="email"
placeholder="Email"
required
inputProps={{
min: 2,
max: 128,
}}
spellCheck="false"
autoCapitalize="none"
type="email"
label="Email"
hideEmptyHelperText
fullWidth
/>
<Input
{...register('password')}
id="password"
placeholder="Password"
required
inputProps={{
min: 2,
max: 128,
}}
spellCheck="false"
autoCapitalize="none"
type="password"
label="Password"
hideEmptyHelperText
fullWidth
/>
<div className="flex flex-col">
<Button type="submit" disabled={isLoading} loading={isLoading}>
Sign Up
</Button>
</div>
</Form>
</FormProvider>
{isError && (
<Text className="font-medium" color="error">
Error: {error.message}
</Text>
)}
<div className="mt-2 grid grid-flow-col items-center justify-center gap-px">
<span>or</span>
<Box className="grid grid-flow-row gap-4 rounded-md border bg-transparent p-6 lg:p-12">
<Button
variant="borderless"
type="button"
size="small"
onClick={() => setSignUpMethod('github')}
className="hover:bg-transparent hover:underline"
className="!bg-white !text-black hover:ring-2 hover:ring-white hover:ring-opacity-50 disabled:!text-black disabled:!text-opacity-60"
startIcon={<GithubIcon />}
disabled={loading}
loading={loading}
size="large"
onClick={async () => {
setLoading(true);
try {
await nhost.auth.signIn({ provider: 'github' });
} catch {
toast.error(
`An error occurred while trying to sign up using GitHub. Please try again.`,
getToastStyleProps(),
);
} finally {
setLoading(false);
}
}}
>
sign up with GitHub
Sign Up with GitHub
</Button>
</div>
</div>
);
}
function SignUpController() {
const [signUpMethod, setSignUpMethod] = useState('github');
<div className="relative py-2">
<Text
className="absolute left-0 right-0 top-1/2 mx-auto w-12 -translate-y-1/2 bg-black px-2 text-center text-sm"
color="disabled"
>
OR
</Text>
if (signUpMethod === 'github') {
return <SignUpWithGithub setSignUpMethod={setSignUpMethod} />;
}
if (signUpMethod === 'email') {
return <SignUpWithEmail setSignUpMethod={setSignUpMethod} />;
}
<Divider />
</div>
return null;
}
export default function SignUpPage() {
const theme = useTheme();
return (
<div className="flex h-screen items-center justify-center">
<div className="grid max-w-6xl grid-cols-1 gap-x-20 gap-y-14 px-5 md:grid-cols-2">
<div className="flex items-center justify-center md:order-1">
<div className="z-30">
<div className="block md:hidden">
<div className="mb-5">
<Image
src={
theme.palette.mode === 'dark'
? '/assets/brands/light/nhost-with-text.svg'
: '/assets/brands/nhost-with-text.svg'
}
alt="Nhost Logo"
width={185}
height={64}
/>
</div>
<div className="mb-4 text-3xl font-semibold">
Build the App of Your Dreams
</div>
</div>
<Box className="rounded-lg border px-12 py-4">
<div className="my-4">
<SignUpController />
</div>
<Text className="mt-4 text-center text-xs" color="secondary">
By signing up, you agree to our{' '}
<Link
href="https://nhost.io/legal/terms-of-service"
target="_blank"
rel="noreferrer"
underline="hover"
>
Terms of Service
</Link>{' '}
and{' '}
<Link
href="https://nhost.io/legal/privacy-policy"
target="_blank"
rel="noreferrer"
underline="hover"
>
Privacy Policy
</Link>
.
</Text>
</Box>
<div className="mt-3 flex justify-center">
<div className="grid grid-flow-col items-center justify-center gap-1">
<Text className="text-sm">Already have an account?</Text>
<NavLink href="/signin" passHref>
<Link href="signin" underline="hover">
Sign in
</Link>
</NavLink>
</div>
</div>
</div>
<div className="absolute w-full max-w-[887px]">
<Image
src="/assets/signup/bg-gradient.svg"
alt="Gradient background"
width={887}
height={620}
layout="responsive"
<FormProvider {...form}>
<Form
onSubmit={handleSubmit}
className="grid grid-flow-row gap-4 bg-transparent"
>
<StyledInput
{...register('displayName')}
id="displayName"
label="Name"
placeholder="Name"
fullWidth
autoFocus
inputProps={{ min: 2, max: 128 }}
error={!!formState.errors.email}
helperText={formState.errors.email?.message}
/>
</div>
</div>
<div className="">
<div className="hidden md:block">
<div className="mb-10">
<Image
src={
theme.palette.mode === 'dark'
? '/assets/brands/light/nhost-with-text.svg'
: '/assets/brands/nhost-with-text.svg'
}
alt="Nhost Logo"
width={185}
height={64}
/>
</div>
<div className="mb-4 text-2xl font-semibold">
Build the App of Your Dreams
</div>
</div>
<div className="my-4 flex flex-col space-y-3">
{sellingPoints.map((sellingPoint) => (
<div key={sellingPoint} className="flex items-center space-x-2">
<Image
src="/assets/signup/CircleWavyCheck.svg"
alt="Check"
width={24}
height={24}
/>
<Text className="!text-xl" color="secondary">
{sellingPoint}
</Text>
</div>
))}
</div>
<Box
className="my-14 h-2 opacity-20"
sx={{ backgroundColor: 'primary.main' }}
/>
<div className="my-4 grid grid-cols-3 items-center gap-x-6 gap-y-6 opacity-40">
{companies.map((company) => (
<div key={company} className="h-[25px] w-[150px]">
<Image
src={
theme.palette.mode === 'dark'
? `/assets/brands/light/${kebabCase(company)}.svg`
: `/assets/brands/${kebabCase(company)}.svg`
}
alt={`Logo of ${company}`}
width={150}
height={25}
className="object-cover"
/>
</div>
))}
</div>
</div>
</div>
</div>
<StyledInput
{...register('email')}
type="email"
id="email"
label="Email"
placeholder="Email"
fullWidth
inputProps={{ min: 2, max: 128 }}
error={!!formState.errors.email}
helperText={formState.errors.email?.message}
/>
<StyledInput
{...register('password')}
type="password"
id="password"
label="Password"
placeholder="Password"
fullWidth
inputProps={{ min: 2, max: 128 }}
error={!!formState.errors.password}
helperText={formState.errors.password?.message}
/>
<Button
variant="outlined"
color="secondary"
className="hover:!bg-white hover:!bg-opacity-10 focus:ring-0"
size="large"
type="submit"
disabled={formState.isSubmitting}
loading={formState.isSubmitting}
>
Sign Up
</Button>
</Form>
</FormProvider>
<Divider className="!my-2" />
<Text color="secondary" className="text-center text-sm">
By signing up, you agree to our{' '}
<NavLink
href="https://nhost.io/legal/terms-of-service"
target="_blank"
rel="noopener noreferrer"
className="font-semibold"
color="white"
>
Terms of Service
</NavLink>{' '}
and{' '}
<NavLink
href="https://nhost.io/legal/privacy-policy"
target="_blank"
rel="noopener noreferrer"
className="font-semibold"
color="white"
>
Privacy Policy
</NavLink>
</Text>
</Box>
<Text color="secondary" className="text-center text-base lg:text-lg">
Already have an account?{' '}
<NavLink href="/signin" color="white" className="font-medium">
Sign In
</NavLink>
</Text>
</>
);
}

View File

@@ -1,18 +1,6 @@
@tailwind base;
@tailwind components;
@media (prefers-color-scheme: dark) {
#__next {
background-color: #151a22;
}
}
@media (prefers-color-scheme: light) {
#__next {
background-color: #fafafa;
}
}
.sb-show-main {
overflow: auto;
}

View File

@@ -62,6 +62,7 @@ export interface HasuraMetadataPermission {
allow_aggregations: boolean;
query_root_fields: string[];
subscription_root_fields: string[];
computed_fields: string[];
set: Record<string, any>;
backend_only: boolean;
}>;

View File

@@ -4,7 +4,11 @@ import slugify from 'slugify';
import { LOCAL_BACKEND_URL } from './env';
import type { DeploymentRowFragment } from './__generated__/graphql';
export function getLastLiveDeployment(deployments: DeploymentRowFragment[]) {
export function getLastLiveDeployment(deployments?: DeploymentRowFragment[]) {
if (!deployments) {
return '';
}
return (
deployments.find((deployment) => deployment.deploymentStatus === 'DEPLOYED')
?.id || ''

View File

@@ -5,6 +5,7 @@ import { ManagedUIContext } from '@/context/UIContext';
import { WorkspaceProvider } from '@/context/workspace-context';
import { UserDataProvider } from '@/context/workspace1-context';
import createTheme from '@/ui/v2/createTheme';
import { createHttpLink } from '@apollo/client';
import { CacheProvider } from '@emotion/react';
import { ThemeProvider } from '@mui/material/styles';
import { NhostProvider } from '@nhost/nextjs';
@@ -33,7 +34,7 @@ const queryClient = new QueryClient({
global.fetch = fetch;
const mockRouter: NextRouter = {
export const mockRouter: NextRouter = {
basePath: '',
pathname: '/',
route: '/',
@@ -65,7 +66,12 @@ function Providers({ children }: PropsWithChildren<{}>) {
<QueryClientProvider client={queryClient}>
<CacheProvider value={emotionCache}>
<NhostProvider nhost={nhost}>
<NhostApolloProvider nhost={nhost}>
<NhostApolloProvider
nhost={nhost}
link={createHttpLink({
uri: 'http://localhost:1337/v1/graphql',
})}
>
<WorkspaceProvider>
<UserDataProvider>
<ManagedUIContext>

View File

@@ -111,10 +111,10 @@ module.exports = {
},
fontSize: {
'xs-': ['0.6875rem', '0.875rem'],
'sm-': '13px',
'sm-': ['0.8125rem', '1rem'],
'sm+': ['0.9375rem', '1.25rem'],
'2.5xl': '26px',
'5.5xl': '56px',
'3.5xl': ['2rem', '2.5rem'],
'4.5xl': ['2.5rem', '2.5rem'],
},
maxWidth: {
2: '0.5rem',

View File

@@ -1,5 +1,12 @@
# @nhost/docs
## 0.0.13
### Patch Changes
- bf1e4071: chore(deps): bump `react` to v18
- 8be094be: fix(deps): update docusaurus monorepo to v2.3.1
## 0.0.12
### Patch Changes

View File

@@ -5,6 +5,9 @@ sidebar_label: 'Overview'
image: /img/og/graphql.png
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
A GraphQL API is automatically and instantly available based on the tables and columns in your [database](/database).
The GraphQL API has support for inserting, selecting, updating, and deleting data, which usually accounts for 80% of all API operations you need.
@@ -21,11 +24,11 @@ Building your GraphQL API is a lot of work, but with Nhost it's easy because eve
## Endpoint
The GraphQL API is available at `https://[subdomain].nhost.run/v1/graphql` When using the [CLI](/cli) the GraphQL API is available at `http://localhost:1337/v1/graphql`.
The GraphQL API is available at `https://[subdomain].graphql.[region].nhost.run/v1` When using the [CLI](/cli) the GraphQL API is available at `http://localhost:1337/v1/graphql`.
## GraphQL Clients for JavaScript
The Nhost JavaScript client 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:
@@ -38,7 +41,7 @@ When building more complex frontend applications, we recommend using a more adva
## Queries
A GraphQL query is used to fetch data from the database.
A query is used to fetch data from the GraphQL API.
:::tip
The [Queries documentation from Hasura](https://hasura.io/docs/latest/graphql/core/databases/postgres/queries/index/) is applicable since we're using Hasura's GraphQL Engine for your project.
@@ -46,7 +49,10 @@ The [Queries documentation from Hasura](https://hasura.io/docs/latest/graphql/co
**Example:** A GraphQL query to select `title`, `body`, and `isCompleted` for every row in the `todos` table.
```graphql title=GraphQL
<Tabs >
<TabItem value="request" label="Request" default>
```graphql
query GetTodos {
todos {
title
@@ -56,9 +62,10 @@ query GetTodos {
}
```
**Response:**
</TabItem>
<TabItem value="response" label="Response">
```json title=Response
```json
{
"data": {
"todos": [
@@ -72,13 +79,19 @@ query GetTodos {
}
```
</TabItem>
</Tabs>
#### Filtering and sorting
More complex queries utilize filters, limits, sorting, and aggregation.
Here's an example of a more complex GraphQL query that selects all items in the `todos` table that are not completed, with the total number of comments and the last five comments:
```graphql title=GraphQL
<Tabs >
<TabItem value="request" label="Request" default>
```graphql
query GetTodosWithLatestComments {
todos(where: { isCompleted: { _eq: false } }) {
title
@@ -97,7 +110,10 @@ query GetTodosWithLatestComments {
}
```
```json title=Response
</TabItem>
<TabItem value="response" label="Response">
```json
{
"data": {
"todos": [
@@ -127,6 +143,9 @@ query GetTodosWithLatestComments {
}
```
</TabItem>
</Tabs>
## Mutations
A GraphQL mutation is used to insert, upsert, update, or delete data.
@@ -139,7 +158,10 @@ The [Mutations documentation from Hasura](https://hasura.io/docs/latest/graphql/
**Example:** A GraphQL mutation to insert data:
```graphql title=GraphQL
<Tabs >
<TabItem value="request" label="Request" default>
```graphql
mutation InsertTodo {
insert_todos(
objects: [{ title: "Delete Firebase account", body: "Migrate to Nhost", isCompleted: false }]
@@ -154,7 +176,10 @@ mutation InsertTodo {
}
```
```json title=Reponse
</TabItem>
<TabItem value="response" label="Response">
```json
{
"data": {
"insert_todos": [
@@ -169,6 +194,9 @@ mutation InsertTodo {
}
```
</TabItem>
</Tabs>
#### Insert Multiple Rows
Use an array of objects to insert multiple rows at the same time.
@@ -303,9 +331,7 @@ mutation DeleteDoneTodos {
}
```
If you have set up foreign keys which will restrict a delete violation, you will get an error and will not be able to delete the data until all violations are solved. The simplest way to solve this is by set `On Delete Violation` to `CASCADE` when you set up a foreign Key.
---
If you have set up foreign keys which will restrict a delete violation, you will get an error and will not be able to delete the data until all violations are solved. The simplest way to solve this is by set `On Delete Violation` to `CASCADE` when you set up a foreign key.
## Subscriptions

View File

@@ -8,35 +8,19 @@ The GraphQL API is protected by a role-based permission system.
For each **role**, you create **rules** for the **select**, **insert**, **update**, and **delete** operations.
**Example:** Let's say you have a `posts` table, and you want users to only access their own posts. This is how you would do it:
**Example:** Let's say you have a `posts` table with `id` `title` and `user_id` columns, and you want users to only access their own posts. This is how you would do it:
```sql title="Posts Table"
CREATE TABLE "public"."posts" (
"id" serial NOT NULL PRIMARY KEY,
"title" text NOT NULL,
"user_id" uuid NOT NULL,
);
```
![Permission Variables](/img/graphql/permissions/permission-example.png)
```json title="Hasura Permission Rule"
{
"user_id": {
"_eq": "X-Hasura-User-Id"
}
}
```
The rule above make it so users can only select posts where the value of `user_id` is equal (`_eq`) to their user ID (`x-hasura-user-id`).
The permission above makes sure users can only select their own posts, because the value of `user_id` must be equal (`_eq`) to the authenticated user's ID (`x-hasura-user-id`).
## What is `x-hasura-user-id`?
`x-hasura-user-id` is a permission variable that is used to create permission rules in Hasura. The permission variable comes from the [access token](/authentication/tokens#access-token) that authenticated users have.
The `x-hasura-user-id` permission variable is always available for all authenticated users. You can add [permission variables](#permission-variables) to create more complex permission rules unique to your project.
`x-hasura-user-id` is a permission variable that is used to create permission rules. The permission variable comes from the [access token](/authentication/tokens#access-token) that all authenticated users have. You can add [custom permission variables](#permission-variables) to create more complex permission rules unique to your project.
## Permission Variables
You can add permission variables in the Nhost Dashboard under **Settings -> Roles and permissions**. These permission variables are automatically added to users' [access tokens](/authentication/tokens#access-token). This way, permission variables are available when creating permissions for your GraphQL API in the Hasura Console.
You can add permission variables in the Nhost Dashboard under **Settings -> Roles and permissions -> Permission Variables**. These permission variables are automatically added to users' [access tokens](/authentication/tokens#access-token). This way, permission variables are available when creating permissions for your GraphQL API.
![Permission Variables](/img/permission-variables-preview.png)
@@ -137,37 +121,28 @@ GraphQL requests from unauthenticated users resolve permissions using the `publi
Here is a popular approach for insert permission for authenticated users.
1. At the top of the page, click **"insert"** on the **"user"** role.
2. Select **"Without any checks"**.
3. Select the columns you want to allow users to insert.
In our example, we only mark `title`, because the other columns should not be inserted by the user.
We also want every new record's `user_id` value to be set to the ID of the user making the request. We can tell Hasura to do this using **Column presets**.
4. Under **Column presets**, set `user_id` to `x-hasura-user-id`.
1. Select **"Without any checks"**.
1. Select the columns you want to allow users to insert. In our example, we only mark `title`, because that's the only column that should be inserted by the user. The `id` is automatically generated by the database and `user_id` is set using a column preset.
1. Under **Column presets**, set `user_id` to `x-hasura-user-id`. This way, every new record's `user_id` value is set to the ID of the user making the request.
Now, authenticated users are allowed to insert posts. Users are allowed to add a title when inserting a post. The post's `id` is automatically generated by the database and the `user_id` is automatically set to the user's id using the `user_id = x-hasura-user-id` column preset.
## Select, Update and Delete Permissions
Select, update, and delete permissions usually follows the same pattern. Here's an example of how to add select permissions:
Select, update, and delete permissions usually follow the same pattern. Here's an example of how to add select permissions:
![Select permissions](/img/permission-select.png)
![Select permissions](/img/graphql/permissions/select-permissions.png)
One of the most common permission requirements is that authenticated users should only be able to read their own data. This is how to do that:
1. Go to **Hasura Console**
1. Select your table and open the **Permissions** tab
1. At the top of the page, click **"select"** on the **"user"** role.
1. Go to the **Database** section in the Nhost Dashboard.
1. In the context menu of the table you want to edit, click on **Edit Permissions**.
1. Click on the **role** and **operation** you want to set.
1. Select **"With custom check"** to create a new rule
1. Enter `user_id`, `_eq` and `x-hasura-user-id` into the rule form.
This means that in order for users to read data, the user ID value in the database row must be the same as the user ID in the access token.
To further refine this rule, do the following:
1. Limit the number of rows to 100 (or some other relevant number).
1. Select the columns you want the user to be able to read. In our case, we'll allow the user to read all columns.
1. Enter `user_id`, `_eq` and `x-hasura-user-id` into the rule form. This means that in order for users to read data, the user ID value in the database row must be the same as the user ID in the access token.
1. **Limit the number of rows** to 100 (or some other relevant number).
1. Select the **columns** you want the user to be able to read. In our case, we'll allow the user to read all columns.
1. Click **Save**.
## Next Steps

View File

@@ -9,10 +9,16 @@ Initialize a local Nhost project.
nhost init
```
If you already have a Nhost project in Nhost Cloud you can use that project as a starting point by appending `--remote` to the command.
If you have an existing Nhost project in Nhost Cloud that you want to use as a starting point for local development and for the [Git-based workflow](/platform/git), run `nhost init --remote`.
```
nhost init --remote
```
The `nhost init --remote` command does the following:
This will reset the local database migrations and Hasura metadata, and pull the database migrations and Hasura metadata from the Nhost Cloud project locally for you to use as a starting point. While doing `nhost init --remote` it's also recommended to setup [Git](/platform/git) to activate automated deplouments on `git push` for your Nhost project.
- Creates a new local Nhost project.
- Pulls the database migrations and Hasura metadata from the Nhost Cloud project.
- Resets the remote Nhost Cloud project's database migrations.
:::warning
The `nhost init --remote` command should only be run **once**. Running it multiple times will reset the remote Nhost Cloud project's database migrations which can cause migration conflict issues in your development team.
:::

View File

@@ -3,7 +3,7 @@ title: 'list'
sidebar_position: 7
---
List all your Nhost projects in Nhost Cloud.
List projects in Nhost Cloud.
```bash
nhost list

View File

@@ -3,7 +3,7 @@ title: 'logs'
sidebar_position: 9
---
Output logs of any service container
View logs of all services.
```bash
nhost logs

View File

@@ -3,10 +3,10 @@ title: 'up'
sidebar_position: 2
---
Launch the development environment for your project. Once the environment is up, the command will:
To launch the development environment for your project, use the command `nhost up`. Once the environment is running, this command will
- Apply database migrations.
- Apply the Hasura metadata.
- Apply Hasura metadata.
```bash
nhost up

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>
---

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