Compare commits

...

483 Commits

Author SHA1 Message Date
Szilárd Dóró
52d4b5de45 Merge pull request #1347 from nhost/changeset-release/main
chore: update versions
2022-12-13 10:29:25 +01:00
Szilárd Dóró
6b4ab50f74 fix(packages): nextjs and react-apollo versions 2022-12-13 09:50:42 +01:00
github-actions[bot]
ceba605d0b chore: update versions 2022-12-13 08:47:12 +00:00
Szilárd Dóró
9249a85ee5 Merge pull request #1371 from nhost/fix/aws-name-null-reference
fix(dashboard): don't break UI when region is nullish
2022-12-13 09:45:32 +01:00
Szilárd Dóró
f4d70f88e9 chore(dashboard): add changeset 2022-12-12 22:43:06 +01:00
Szilárd Dóró
0d09b80b12 fix(dashboard): fix nullish region in local mode 2022-12-12 22:42:09 +01:00
Pilou
b09930c8a4 Merge pull request #1369 from nhost/contributors-readme-action-TvJAKaD1lO
docs(contributor): contributors readme action update
2022-12-12 19:06:31 +01:00
github-actions[bot]
687951281e docs(contributor): contrib-readme-action has updated readme 2022-12-12 18:05:20 +00:00
Pilou
c0f05acd9b Merge pull request #1365 from badgifter/patch-windowslive-url
Windows Live Provider displayed link updated to match backend url
2022-12-12 19:05:01 +01:00
Pilou
4a9471cc16 Create rude-singers-carry.md 2022-12-12 14:42:55 +01:00
Bad Gifter
567e370bdc microsoft changed to windowslive 2022-12-12 14:04:35 +03:00
Bad Gifter
a91f2db0e2 Windows Live Provider displayed link updated to match backend url 2022-12-12 13:49:08 +03:00
Szilárd Dóró
aea99ad2c8 Merge pull request #1361 from nhost/fix/apple-sign-in-mutation
fix(dashboard): do not show error when submitting Apple provider settings
2022-12-09 09:45:33 +01:00
Szilárd Dóró
594488e435 chore(dashboard): add changeset 2022-12-09 09:29:32 +01:00
Szilárd Dóró
bb83b0f81a fix(dashboard): do not show error when submitting Apple provider settings 2022-12-09 09:18:46 +01:00
Pilou
0384d7c7c4 Merge pull request #1360 from nhost/contributors-readme-action-cDMbyYcNKN
docs(contributor): contributors readme action update
2022-12-08 19:40:40 +01:00
Pilou
7e356a9604 Merge pull request #1357 from nhost/contributors-readme-action-sNfze0TcKw
docs(contributor): contributors readme action update
2022-12-08 19:29:19 +01:00
github-actions[bot]
013e55a307 docs(contributor): contrib-readme-action has updated readme 2022-12-08 18:28:57 +00:00
Pilou
2a71257cde Merge pull request #1358 from nhost/fix/workspace-packages
fix: use workspace to resolve internal dependencies
2022-12-08 19:28:41 +01:00
Pierre-Louis Mercereau
583a4401d0 docs: add explanation to the CI workflow 2022-12-08 18:50:01 +01:00
Pierre-Louis Mercereau
914e91a0b0 ci: correct test 2022-12-08 18:40:49 +01:00
Pierre-Louis Mercereau
98698213e2 ci: run all e2e tests when lockfile changed 2022-12-08 18:29:54 +01:00
Pierre-Louis Mercereau
756daa97cd fix: use workspace to resolve internal dependencies 2022-12-08 16:58:47 +01:00
github-actions[bot]
ab5a2b119c docs(contributor): contrib-readme-action has updated readme 2022-12-08 15:18:12 +00:00
Johan Eliasson
ffdcce1463 Merge pull request #1352 from wollerman/patch-1
Add a section on managing local seed data
2022-12-08 16:17:54 +01:00
Szilárd Dóró
a3dcb6106e Merge pull request #1355 from nhost/chore/revert-types-react-bump
chore(dashboard): revert `@types/react` bump
2022-12-08 15:03:24 +01:00
Szilárd Dóró
208d224763 chore(dashboard): revert @types/react bump 2022-12-08 14:46:36 +01:00
Matt Wollerman
564d000bfc add link to overview description of seed data 2022-12-07 20:21:09 -05:00
Matt Wollerman
266fda07ab add a section on managing local seed data 2022-12-07 08:45:40 -05:00
Pilou
6174e1ddcc Merge pull request #1341 from nhost/refactor/nhost-react-providers
refactor: nhost react providers
2022-12-06 12:57:26 +01:00
Pierre-Louis Mercereau
af2e3eae37 chore: minor bump 2022-12-06 12:39:30 +01:00
Pilou
d2b4c126f3 Merge pull request #1346 from nhost/chore/prettier-ignore-lockfile
chore: prettier ignore pnpm-lock.yaml
2022-12-06 11:01:00 +01:00
Pierre-Louis Mercereau
7f2e182c47 chore: prettier ignore pnpm-lock.yaml 2022-12-06 10:54:16 +01:00
Szilárd Dóró
ae40bd54d4 Merge pull request #1302 from nhost/renovate/react-18.x
chore(deps): update dependency @types/react to v18.0.26
2022-12-06 10:01:27 +01:00
Pilou
b5cc47078a Update .changeset/big-pumpkins-march.md
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2022-12-06 09:59:25 +01:00
Pierre-Louis Mercereau
7f251111e2 chore: 🤖 changeset 2022-12-06 09:41:40 +01:00
Pierre-Louis Mercereau
c03dacc3a3 refactor: use NhostProvider in React and Nextjs 2022-12-06 09:39:44 +01:00
Pilou
8b9e1a0ce8 Merge pull request #1339 from nhost/contributors-readme-action-oSzExU64y7
docs(contributor): contributors readme action update
2022-12-06 07:04:10 +01:00
github-actions[bot]
cf9cfec330 docs(contributor): contrib-readme-action has updated readme 2022-12-06 05:52:28 +00:00
Pilou
1c94f56c59 Merge pull request #1338 from iangabrielsanchez/patch-1
Fix typo
2022-12-06 06:52:14 +01:00
Ian Gabriel Sanchez
f06d5deba3 Fix typo 2022-12-06 10:15:59 +08:00
Nuno Pato
8ff58d7f23 Merge pull request #1330 from nhost/changeset-release/main
chore: update versions
2022-12-05 15:22:02 -01:00
github-actions[bot]
8dd1c7415b chore: update versions 2022-12-05 15:56:54 +00:00
Nuno Pato
ebd2749e38 Merge pull request #1329 from nhost/fix/hasura-duplicate-slug
fix(dashboard): use correct Hasura slug
2022-12-05 14:55:28 -01:00
Szilárd Dóró
80b604adda fix(dashboard): use correct Hasura slug 2022-12-05 16:35:35 +01:00
Nuno Pato
9d73050792 Merge pull request #1322 from nhost/changeset-release/main
chore: update versions
2022-12-05 14:20:47 -01:00
renovate[bot]
2a86b8876c chore(deps): update dependency @types/react to v18.0.26 2022-12-05 14:47:07 +00:00
github-actions[bot]
91a1a41f5d chore: update versions 2022-12-05 14:41:11 +00:00
Nuno Pato
22e9c27c81 Merge pull request #1328 from nhost/fix/graphiql-editor
fix(dashboard): prevent error on GraphQL page
2022-12-05 13:39:28 -01:00
Szilárd Dóró
2d2beb53d2 fix(dashboard): prevent error on GraphQL page 2022-12-05 15:26:37 +01:00
Pilou
b403b0d6a0 Merge pull request #1327 from nhost/ci/unambiguous-git-log
chore: don't use PAT in renovate post PR
2022-12-05 14:21:43 +01:00
Pierre-Louis Mercereau
4dbac55cb4 chore: don't use PAT in renovate post PR 2022-12-05 14:20:56 +01:00
Pilou
c6e31ac741 Merge pull request #1324 from nhost/renovate/xstate-vue-2.x
fix(deps): update dependency @xstate/vue to v2
2022-12-05 13:06:56 +01:00
Pilou
0d3e8b3992 Merge pull request #1325 from nhost/ci/unambiguous-git-log
chore(gh-actions): `git log` unabiguously
2022-12-05 12:58:15 +01:00
Pierre-Louis Mercereau
b2afd14d61 chore(gh-actions): git log unabiguously 2022-12-05 12:57:46 +01:00
renovate[bot]
f28f28b6ee fix(deps): update dependency @xstate/vue to v2 2022-12-05 11:52:06 +00:00
Pilou
834b959271 Merge pull request #1323 from nhost/renovate/vueuse-core-9.x
fix(deps): update dependency @vueuse/core to v9
2022-12-05 12:44:43 +01:00
renovate[bot]
4dbc9ccc87 fix(deps): update dependency @vueuse/core to v9 2022-12-05 11:23:06 +00:00
Nuno Pato
1666ca2ec5 Merge pull request #1250 from nhost/chore/deprecate-old-dns-name
chore(dashboard): deprecate old DNS name
2022-12-05 10:10:37 -01:00
Pilou
346791d4d5 Merge pull request #1321 from nhost/renovate/pnpm-find-workspace-dir-5.x
fix(deps): update dependency @pnpm/find-workspace-dir to v5
2022-12-05 11:55:33 +01:00
Pierre-Louis Mercereau
94bdafe22f chore: adapt sync-versions to find-workspace-dir 5 2022-12-05 11:51:44 +01:00
renovate[bot]
33782e9d41 fix(deps): update dependency @pnpm/find-workspace-dir to v5 2022-12-05 10:46:45 +00:00
Johan Eliasson
ea02e1e104 Merge pull request #1311 from nhost/example-serverless-functions-stripe-webhooks
example(serverless functions): Stripe Webhooks
2022-12-05 11:39:36 +01:00
Johan Eliasson
98bf6e3792 Update examples/serverless-functions/functions/stripe-webhook/index.ts
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2022-12-05 11:39:26 +01:00
Johan Eliasson
d9dcafd643 Update examples/serverless-functions/functions/stripe-webhook/index.ts
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2022-12-05 11:39:20 +01:00
Johan Eliasson
4f3d97b5ad Merge pull request #1316 from nhost/elitan-patch-3
docs clarifications
2022-12-05 11:38:51 +01:00
Nuno Pato
d1801ceae9 dashboard: fix link 2022-12-05 09:12:40 -01:00
Szilárd Dóró
b6f9fe6304 Merge pull request #1301 from nhost/changeset-release/main
chore: update versions
2022-12-05 11:02:10 +01:00
github-actions[bot]
15b652d7e0 chore: update versions 2022-12-05 09:14:59 +00:00
Szilárd Dóró
cdc0047cb7 Merge pull request #1284 from nhost/renovate/graphiql-react-0.x
fix(deps): update dependency @graphiql/react to ^0.15.0
2022-12-05 10:13:19 +01:00
Pilou
7c0e71e8be Merge pull request #1303 from nhost/renovate/eslint-plugin-vue-9.x
chore(deps): update dependency eslint-plugin-vue to v9
2022-12-05 09:38:23 +01:00
Pilou
175d6d8cbc Merge pull request #1315 from plmercereau/ci/nhost-cli-action
ci: generic Nhost CLI action
2022-12-05 09:30:31 +01:00
Pierre-Louis Mercereau
2ae277409a ci: action test cleanup 2022-12-05 09:14:58 +01:00
Pierre-Louis Mercereau
ada10170b7 ci: always checkout 2022-12-05 09:08:04 +01:00
Pierre-Louis Mercereau
0ed77cbe8b ci: add cli action tests 2022-12-05 09:05:51 +01:00
Johan Eliasson
1abc68992f Update users.mdx 2022-12-05 08:15:14 +01:00
Pilou
795962e3c2 Merge pull request #1313 from nhost/ci/e2e-matrix-job-name
ci: improve the names of the e2e matrix jobs
2022-12-05 01:28:13 +01:00
Pilou
ba998eb632 Update README.md 2022-12-05 01:18:13 +01:00
Pierre-Louis Mercereau
d3fc1bbeb9 ci: simplify config injection 2022-12-05 00:07:35 +01:00
Pierre-Louis Mercereau
7b9c2016d0 ci: default is: do not start the app 2022-12-04 23:44:51 +01:00
Pierre-Louis Mercereau
ebf4070be6 ci: final adjustments 2022-12-04 23:39:01 +01:00
Pierre-Louis Mercereau
59e3f6abc6 ci: cli version 2022-12-04 23:32:04 +01:00
Pierre-Louis Mercereau
0c9a03a7ff docs: add nhost-cli action readme 2022-12-04 23:00:24 +01:00
Pierre-Louis Mercereau
42cbe27914 ci: revert 2022-12-04 22:51:04 +01:00
Pierre-Louis Mercereau
d7e7b0e51b ci: cleanup 2022-12-04 22:50:11 +01:00
Pierre-Louis Mercereau
036181dd75 ci: clean 2022-12-04 22:48:19 +01:00
Pierre-Louis Mercereau
2828a9fe01 ci: silence curl 2022-12-04 22:41:16 +01:00
Pierre-Louis Mercereau
88bc71dffc ci: try another version 2022-12-04 22:39:49 +01:00
Pierre-Louis Mercereau
17699870a6 ci: correct curl 2022-12-04 22:34:41 +01:00
Pierre-Louis Mercereau
5c0304ab73 ci: remove sS 2022-12-04 22:30:41 +01:00
Pierre-Louis Mercereau
637265e3d9 ci: again 2022-12-04 22:29:34 +01:00
Pierre-Louis Mercereau
229604d8e1 ci: correct retry 2022-12-04 22:25:45 +01:00
Pierre-Louis Mercereau
1165a33079 ci: --retry-connrefused 2022-12-04 22:22:26 +01:00
Pierre-Louis Mercereau
82c0dd9d87 ci: retry again 2022-12-04 22:17:00 +01:00
Pierre-Louis Mercereau
a9bfab1778 ci: 16.1 2022-12-04 22:15:29 +01:00
Pierre-Louis Mercereau
60b7a664d2 ci: improve wait-for 2022-12-04 22:14:53 +01:00
Pierre-Louis Mercereau
4f708d04d6 ci: correct path 2022-12-04 21:56:24 +01:00
Pierre-Louis Mercereau
a02af56056 ci: config 2022-12-04 21:52:17 +01:00
Pierre-Louis Mercereau
575136dcfb ci: don't fail, and fail 2022-12-04 16:45:44 +01:00
Pierre-Louis Mercereau
2bc6346cbc ci: not siltent 2022-12-04 16:42:17 +01:00
Pierre-Louis Mercereau
91cd494a3d ci: correct nhost dev 2022-12-04 16:37:21 +01:00
Pierre-Louis Mercereau
d57e0a5287 ci: path 2022-12-04 16:36:02 +01:00
Pierre-Louis Mercereau
dd5e7093f0 ci: start 2022-12-04 16:34:47 +01:00
Pierre-Louis Mercereau
3e9bb84f07 ci: don't build packages 2022-12-04 16:34:05 +01:00
Pierre-Louis Mercereau
6665b58ec8 ci: dedicated wip action 2022-12-04 16:33:13 +01:00
Pierre-Louis Mercereau
456e893cd6 ci: change branch reference 2022-12-04 16:24:47 +01:00
Pierre-Louis Mercereau
fd76170ca6 ci: nhost-cli action 2022-12-04 16:23:52 +01:00
Pierre-Louis Mercereau
682aef2d94 ci: improve the names of the e2e matrix jobs 2022-12-03 23:01:46 +01:00
Pilou
cb53f71d4a Merge pull request #1312 from plmercereau/main
Chore(docker-compose): fix tests
2022-12-03 17:28:42 +01:00
Pierre-Louis Mercereau
4fc0b40cb4 chore(docker-compose): increase timeout 2022-12-03 17:20:48 +01:00
Pierre-Louis Mercereau
9da2d01c55 chore(docker-compose): describe 2022-12-03 17:16:11 +01:00
Pierre-Louis Mercereau
0d5e7850f8 chore(docker-compose): retry 2022-12-03 17:14:23 +01:00
Johan Eliasson
8ed965c669 keep migrations file here, otherwise nhost dev won't work 2022-12-03 14:44:22 +01:00
Pilou
cb284b40b1 Merge pull request #1308 from nhost/ci/docker-compose
chore: improve docker-compose example
2022-12-03 13:59:10 +01:00
Johan Eliasson
c59f622feb fix 2022-12-03 13:48:40 +01:00
Johan Eliasson
9a30edd038 example 2022-12-03 13:47:42 +01:00
Pilou
b8b4e36175 Merge pull request #1307 from nhost/renovate/uuid-9.x
chore(deps): update dependency uuid to v9
2022-12-03 11:33:51 +01:00
Pierre-Louis Mercereau
6e47ef68d5 Merge branch 'ci/docker-compose' of https://github.com/nhost/nhost into ci/docker-compose 2022-12-02 23:11:24 +01:00
Pilou
2697414637 Create fair-students-enjoy.md 2022-12-02 23:08:13 +01:00
Pierre-Louis Mercereau
e753b2faed chore: quiet docker pull 2022-12-02 23:01:55 +01:00
Pierre-Louis Mercereau
6f62ec7d2a fix: use docker-compose v2 syntax 2022-12-02 22:43:32 +01:00
Pierre-Louis Mercereau
d365ef1953 test: 💍 create e2e tests for the docker-compose example 2022-12-02 22:20:12 +01:00
Szilárd Dóró
99ee9fd10d fix(dashboard): move Brazilian flag 2022-12-02 20:29:58 +01:00
Nuno Pato
9608a327c9 dashboard: add brazilian flag 2022-12-02 18:27:14 -01:00
Szilárd Dóró
492b83ef58 fix(dashboard): correct GraphQL URL 2022-12-02 20:25:11 +01:00
renovate[bot]
09f53ae43f chore(deps): update dependency uuid to v9 2022-12-02 18:30:25 +00:00
renovate[bot]
7724ac7e06 chore(deps): update dependency eslint-plugin-vue to v9 2022-12-02 15:56:01 +00:00
renovate[bot]
02343fe171 fix(deps): update dependency @graphiql/react to ^0.15.0 2022-12-02 15:52:13 +00:00
Szilárd Dóró
98dd8d039c Merge pull request #1305 from nhost/fix/pnpm-lock
fix(deps): correct pnpm-lock.yaml
2022-12-02 16:35:13 +01:00
Szilárd Dóró
456e057497 Merge remote-tracking branch 'origin/main' into fix/pnpm-lock 2022-12-02 16:34:46 +01:00
Pilou
93e9b58a58 Merge pull request #1289 from nhost/renovate/faker-6.x
chore(deps): update dependency @types/faker to v6
2022-12-02 16:33:33 +01:00
Szilárd Dóró
019a7c2335 fix: correct pnpm-lock.yaml 2022-12-02 16:26:51 +01:00
Pilou
64882a8e16 Merge pull request #1279 from nhost/renovate/prettier-plugin-tailwindcss-0.x
chore(deps): update dependency prettier-plugin-tailwindcss to ^0.2.0
2022-12-02 15:59:13 +01:00
Pilou
1ed572fe39 Merge pull request #1275 from nhost/renovate/msw-0.x
chore(deps): update dependency msw to ^0.49.0
2022-12-02 15:58:56 +01:00
Pilou
e06271a8ae Merge pull request #1282 from nhost/renovate/vitest-monorepo
chore(deps): update dependency vitest to ^0.25.0
2022-12-02 15:56:09 +01:00
Szilárd Dóró
bca239ebb2 Merge pull request #1296 from nhost/chore/cleanup-deps
chore(deps): remove unused deps, sync @types/react and @types/react-dom
2022-12-02 15:51:40 +01:00
Szilárd Dóró
e33caa046d Merge pull request #1273 from nhost/fix/settings-env-var-empty-list
fix(dashboard): correct section paddings when no env vars
2022-12-02 15:51:05 +01:00
renovate[bot]
cbbd331341 chore(deps): update dependency prettier-plugin-tailwindcss to ^0.2.0 2022-12-02 14:42:50 +00:00
Pierre-Louis Mercereau
c41bfaffdd chore: 🤖 remove unused @types/faker 2022-12-02 15:39:20 +01:00
renovate[bot]
f2596b0b14 chore(deps): update dependency msw to ^0.49.0 2022-12-02 14:31:58 +00:00
Pilou
4d0c3111d1 Merge pull request #1299 from nhost/chore/labeler
chore: don't remove existing labels
2022-12-02 15:29:18 +01:00
Pierre-Louis Mercereau
7ff9644ac7 chore: don't remove existing labels 2022-12-02 15:28:35 +01:00
renovate[bot]
87fdaa7144 chore(deps): update dependency @types/faker to v6 2022-12-02 13:56:59 +00:00
Szilárd Dóró
bc23d051ba Merge pull request #1274 from nhost/changeset-release/main
chore: update versions
2022-12-02 13:40:52 +01:00
Szilárd Dóró
132a4f4be9 chore(deps): synchronize @types/react-dom and @types/react versions 2022-12-02 13:36:26 +01:00
github-actions[bot]
c330dc1c00 chore: update versions 2022-12-02 12:34:54 +00:00
Pilou
4bebd9842b Merge pull request #1297 from nhost/chore/labeler
chore: rename labeler.yaml to .yml
2022-12-02 13:33:09 +01:00
Pierre-Louis Mercereau
541f2d250c chore: rename labeler.yaml to .yml 2022-12-02 13:32:02 +01:00
Szilárd Dóró
6b31e31430 Merge branch 'main' into chore/cleanup-deps 2022-12-02 13:31:09 +01:00
Szilárd Dóró
9ba2208dd7 chore(dashboard): remove unused dependencies 2022-12-02 13:30:02 +01:00
Szilárd Dóró
538541fc79 Merge pull request #1268 from nhost/renovate/netlify-functions-0.x
chore(deps): remove dependency @netlify/functions
2022-12-02 13:18:38 +01:00
Pilou
cb0aab48b3 Merge pull request #1292 from nhost/chore/labeler
chore: use correct PAT
2022-12-02 13:18:16 +01:00
Pierre-Louis Mercereau
dcc760b6bb chore: use correct PAT 2022-12-02 13:17:39 +01:00
Pilou
d2227be0d0 Merge pull request #1291 from nhost/chore/labeler
chore: add labeler workflow
2022-12-02 12:05:35 +01:00
Pierre-Louis Mercereau
93e807edc4 chore: add labeler workflow 2022-12-02 12:04:55 +01:00
Pilou
14738d4b58 Merge pull request #1283 from plmercereau/main
ci: create renovate changeset `after` the pull request is merged
2022-12-02 11:39:06 +01:00
Szilárd Dóró
6ac4cfa80c Merge branch 'main' into renovate/netlify-functions-0.x 2022-12-02 11:32:08 +01:00
Szilárd Dóró
295c6c9110 chore(dashboard): remove @netlify/functions 2022-12-02 11:31:58 +01:00
Szilárd Dóró
02911e4a54 Merge pull request #1267 from nhost/renovate/critters-0.x
chore(deps): update dependency critters to ^0.0.16
2022-12-02 11:19:39 +01:00
Szilárd Dóró
2be825de08 Merge pull request #1285 from nhost/renovate/docusaurus-monorepo
fix(deps): update docusaurus monorepo to v2.2.0
2022-12-02 11:19:14 +01:00
Szilárd Dóró
65afad1b91 fix(deps): pnpm-lock 2022-12-02 11:08:38 +01:00
Pilou
dbea13a1d2 Merge pull request #1287 from nhost/chore/labeler
chore: add labels to pull requests
2022-12-02 10:59:37 +01:00
Szilárd Dóró
5e2a419700 Merge branch 'main' into renovate/critters-0.x 2022-12-02 10:42:52 +01:00
Pierre-Louis Mercereau
9e6660450c chore: add an examples label 2022-12-02 09:19:04 +01:00
Pierre-Louis Mercereau
2db9be03ff chore: 🤖 add labels to pull requests 2022-12-01 23:11:06 +01:00
renovate[bot]
1b8f505050 fix(deps): update docusaurus monorepo to v2.2.0 2022-12-01 21:41:57 +00:00
Pilou
6ab6bccded Merge pull request #1278 from nhost/ci/renovate-label
ci: add renovate label
2022-12-01 22:37:11 +01:00
Pilou
b12bc1c27f Merge pull request #1277 from nhost/chore/missing-version
chore: add missing version field to the vue-starter example
2022-12-01 22:31:55 +01:00
Pierre-Louis Mercereau
04d1641cf7 revert: initial versions 2022-12-01 22:22:06 +01:00
Pierre-Louis Mercereau
e91c09aa9c revert: initial versions 2022-12-01 22:20:46 +01:00
Pierre-Louis Mercereau
7c04aad870 revert: initial bumps 2022-12-01 22:19:13 +01:00
Pilou
520044c5ab Merge branch 'nhost:main' into main 2022-12-01 22:16:18 +01:00
plmercereau
22fed668d6 chore(deps): update dependency uuid to v9 2022-12-01 21:07:31 +00:00
Pilou
ae14fb1a2f Merge pull request #50 from plmercereau/renovate/uuid-9.x
chore(deps): update dependency uuid to v9
2022-12-01 22:06:13 +01:00
Pilou
f4ca0aab42 Merge pull request #52 from plmercereau/ci/origin
test: 💍 origin
2022-12-01 22:04:55 +01:00
Pierre-Louis Mercereau
d8a0d196af test: 💍 origin 2022-12-01 22:04:23 +01:00
Pierre-Louis Mercereau
cd53718631 ci: origin 2022-12-01 22:03:49 +01:00
Pilou
1fcd3f1851 Merge pull request #49 from plmercereau/renovate/last-sha
test: 💍 last sha
2022-12-01 21:59:26 +01:00
Pierre-Louis Mercereau
67d370feaa test: 💍 last sha 2022-12-01 21:58:01 +01:00
Pierre-Louis Mercereau
0173ec356b ci: last sha 2022-12-01 21:56:45 +01:00
renovate[bot]
2fb43d417a chore(deps): update dependency uuid to v9 2022-12-01 20:41:02 +00:00
Pilou
966b8158c2 Merge pull request #48 from plmercereau/renovate/debug
test: 💍 debug
2022-12-01 21:33:56 +01:00
Pierre-Louis Mercereau
800fac6093 test: 💍 debug 2022-12-01 21:33:20 +01:00
Pierre-Louis Mercereau
20eb04cd46 ci: debug 2022-12-01 21:32:22 +01:00
Pilou
5d31a88556 Merge pull request #45 from plmercereau/renovate/base_ref
test: 💍 base_ref
2022-12-01 21:27:33 +01:00
Pierre-Louis Mercereau
06019ae7ba test: 💍 base_ref 2022-12-01 21:25:36 +01:00
Pierre-Louis Mercereau
523af2901f ci: base_ref 2022-12-01 21:24:52 +01:00
Pilou
1e0a2c0c43 Merge pull request #1264 from nhost/renovate/react-dom-18.x
chore(deps): update dependency @types/react-dom to v18.0.9
2022-12-01 21:06:41 +01:00
Pilou
9b60f9ca48 Merge pull request #1276 from nhost/renovate/nextjs-monorepo
chore(deps): update dependency next to v12.3.4
2022-12-01 21:06:01 +01:00
Pilou
90bc800c92 Update .github/renovate.json 2022-12-01 21:05:42 +01:00
renovate[bot]
023060cee6 chore(deps): update dependency vitest to ^0.25.0 2022-12-01 20:02:01 +00:00
Szilárd Dóró
0bb3b127d0 chore(dashboard): change local package versions 2022-12-01 20:53:30 +01:00
Pilou
cd914d30ed Merge pull request #44 from plmercereau/renovate/after
test: 💍 add changeset after renovate merge
2022-12-01 20:11:15 +01:00
Pilou
71dddccb9c Merge pull request #41 from plmercereau/renovate/dotenv-16.x
chore(deps): update dependency dotenv to v16
2022-12-01 19:59:03 +01:00
Pierre-Louis Mercereau
2a846040ef test: 💍 add changeset after renovate merge 2022-12-01 19:58:18 +01:00
Pierre-Louis Mercereau
8b580d05c4 ci: add changeset after the renovate pr is merged 2022-12-01 19:57:33 +01:00
Pilou
24d70e8ea8 Update .changeset/tenancy-smothering-cataleptic.md 2022-12-01 19:32:47 +01:00
renovate[bot]
8212432983 chore(deps): update dependency next to v12.3.4 2022-12-01 18:07:07 +00:00
Pierre-Louis Mercereau
7df00a3291 fix: 🐛 disable @antfu's lint rule 2022-12-01 19:04:47 +01:00
Pierre-Louis Mercereau
0186aa5c98 ci: add renovate label 2022-12-01 18:59:38 +01:00
Pierre-Louis Mercereau
67e5cc839c chore: add missing version field to the vue-starter example 2022-12-01 18:56:08 +01:00
Pilou
25c5bd1ccd Merge pull request #1269 from plmercereau/main
ci: improvements on the renovate changeset workflow
2022-12-01 18:51:39 +01:00
renovate[bot]
e5fa48d818 chore(deps): update dependency next to v12.3.4 2022-12-01 17:41:33 +00:00
Pilou
f34da3a715 Merge pull request #1270 from nhost/renovate/xstate-inspect-0.x
chore(deps): update dependency @xstate/inspect to ^0.7.0
2022-12-01 18:38:17 +01:00
Pilou
fab330ce55 Merge pull request #35 from plmercereau/renovate/glob-8.x
chore(deps): update dependency @types/glob to v8
2022-12-01 18:34:47 +01:00
renovate[bot]
3a76080e40 chore(deps): update dependency @types/glob to v8 2022-12-01 16:37:05 +00:00
Szilárd Dóró
db57572f38 fix(dashboard): correct section paddings when no env vars 2022-12-01 15:40:16 +01:00
renovate[bot]
b030eae999 chore(deps): update dependency @xstate/inspect to ^0.7.0 2022-12-01 14:13:16 +00:00
renovate[bot]
b10f4fa631 chore(deps): update dependency @xstate/inspect to ^0.7.0 2022-12-01 14:11:35 +00:00
Pilou
4a6e8c9945 Merge pull request #1263 from nhost/renovate/akhilmhdh-contributors-readme-action-2.x
chore(deps): update akhilmhdh/contributors-readme-action action to v2.3.6
2022-12-01 15:06:20 +01:00
Pierre-Louis Mercereau
3a4d087e6f chore: remove changeset 2022-12-01 15:04:52 +01:00
Pilou
c408b38f28 Merge branch 'nhost:main' into main 2022-12-01 14:54:02 +01:00
Pierre-Louis Mercereau
b2336b2b33 ci: clean 2022-12-01 14:53:49 +01:00
Pierre-Louis Mercereau
fcf68d5c32 ci: separate workflow 2022-12-01 14:48:54 +01:00
Pierre-Louis Mercereau
9e71677cd2 ci: use PAT 2022-12-01 14:36:48 +01:00
Pierre-Louis Mercereau
ce48ce0860 ci: amend 2022-12-01 14:30:57 +01:00
renovate[bot]
9b93cf95ec chore(deps): update dependency @netlify/functions to ^0.11.0 2022-12-01 13:21:48 +00:00
renovate[bot]
34d85e54d6 chore(deps): update dependency critters to ^0.0.16 2022-12-01 13:20:08 +00:00
renovate[bot]
6ae9d6dd01 chore(deps): update dependency @netlify/functions to ^0.11.0 2022-12-01 13:19:41 +00:00
renovate[bot]
1e8d8afe70 chore(deps): update dependency critters to ^0.0.16 2022-12-01 13:18:19 +00:00
Pierre-Louis Mercereau
80478e39e0 ci: put ifs back 2022-12-01 14:11:23 +01:00
Szilárd Dóró
ee7338cc12 Merge pull request #1266 from nhost/changeset-release/main
chore: update versions
2022-12-01 14:10:20 +01:00
renovate[bot]
3a39970d28 chore(deps): update dependency dotenv to v16 2022-12-01 13:09:28 +00:00
Pierre-Louis Mercereau
b0607673ed ci: remove more ifs 2022-12-01 13:57:27 +01:00
Pierre-Louis Mercereau
d9f27c6c65 ci: deactivate 2022-12-01 13:46:15 +01:00
github-actions[bot]
ea0b35fe84 chore: update versions 2022-12-01 12:30:09 +00:00
Szilárd Dóró
4fbd22f232 Merge pull request #1258 from nhost/fix/settings-env-var-character-limit
fix(dashboard): remove character limit from env var inputs
2022-12-01 13:28:30 +01:00
Pierre-Louis Mercereau
c92142845f ci: simplify syntax 2022-12-01 13:27:29 +01:00
Pierre-Louis Mercereau
c93f818315 ci: not the dependencies 2022-12-01 12:42:19 +01:00
Pierre-Louis Mercereau
d20ed86219 ci: don't create changeset if no package is impacted 2022-12-01 12:36:10 +01:00
renovate[bot]
e04390307c chore(deps): update dependency @types/react-dom to v18.0.9 2022-12-01 10:49:12 +00:00
renovate[bot]
5c931f14ae chore(deps): update dependency @types/react-dom to v18.0.9 2022-12-01 10:47:47 +00:00
renovate[bot]
a7ab1243a4 chore(deps): update akhilmhdh/contributors-readme-action action to v2.3.6 2022-12-01 10:43:54 +00:00
Pilou
5638b5e770 Merge pull request #1262 from nhost/ci/adjust-workflows
ci: adjust workflows
2022-12-01 11:43:09 +01:00
renovate[bot]
c5ce895c97 chore(deps): update akhilmhdh/contributors-readme-action action to v2.3.6 2022-12-01 10:42:20 +00:00
Pierre-Louis Mercereau
2be99c0e55 ci: adjust workflows 2022-12-01 11:34:59 +01:00
Pilou
f6299b537d Merge pull request #1261 from plmercereau/main
ci: set Renovate bot
2022-12-01 11:33:30 +01:00
Pilou
11d06cce4b Merge branch 'nhost:main' into main 2022-12-01 10:49:46 +01:00
Pierre-Louis Mercereau
e9e0ad6d3c ci: clean renovate 2022-12-01 10:46:28 +01:00
Pierre-Louis Mercereau
3ad2d5ced8 ci: clean-up 2022-12-01 10:44:47 +01:00
Szilárd Dóró
42bfa7c16d Merge pull request #1259 from nhost/chore/issue-templates
chore: create issue templates
2022-12-01 10:37:51 +01:00
Pierre-Louis Mercereau
0dea974a2e ci: fine-tune 2022-12-01 10:28:13 +01:00
Szilárd Dóró
8165b8f13e chore: create issue templates 2022-12-01 10:25:30 +01:00
Pierre-Louis Mercereau
1c8864c992 ci: debug ref_head 2022-12-01 10:22:40 +01:00
Pierre-Louis Mercereau
dd6921676f ci: correct sed 2022-12-01 10:12:54 +01:00
Szilárd Dóró
a1193da4fb fix(dashboard): remove character limit from env var inputs 2022-12-01 10:05:56 +01:00
Pierre-Louis Mercereau
ff83a29dcc ci: fix filter 2022-12-01 10:03:15 +01:00
Pierre-Louis Mercereau
4771bff65c ci: don't use ambiguous redirection 2022-12-01 09:56:57 +01:00
Pierre-Louis Mercereau
6106265d87 ci: install pnpm 2022-12-01 09:48:48 +01:00
Pierre-Louis Mercereau
3c2455b7e1 ci: echo 2022-12-01 09:39:59 +01:00
Pierre-Louis Mercereau
7bc8de103b ci: correct 2022-12-01 09:36:26 +01:00
Pierre-Louis Mercereau
e49ba4bd02 ci: don't use paste 2022-12-01 09:23:24 +01:00
Pierre-Louis Mercereau
44ac1a0095 ci: shuf 2022-12-01 09:10:41 +01:00
Pierre-Louis Mercereau
04ce686e7c Merge branch 'main' of https://github.com/plmercereau/nhost 2022-12-01 08:46:29 +01:00
Pierre-Louis Mercereau
ca01224083 ci: alway patch 2022-12-01 08:46:02 +01:00
Pilou
d489232cbc Update packages.yaml 2022-12-01 07:43:32 +01:00
Pierre-Louis Mercereau
9efc0522c3 fix(examples): rename test script to e2e:test 2022-11-30 23:54:57 +01:00
Pierre-Louis Mercereau
4c86ed787e ci: shorter job title 2022-11-30 23:50:28 +01:00
Pierre-Louis Mercereau
9a3c05f69b fix(examples): correct examples to pass the build step 2022-11-30 23:48:11 +01:00
Pierre-Louis Mercereau
2b03f80d59 ci: avoid double negative 2022-11-30 23:37:40 +01:00
Pierre-Louis Mercereau
baa3f0531d ci: typo 2022-11-30 23:35:49 +01:00
Pierre-Louis Mercereau
f9bbc78241 ci: correct test 2022-11-30 23:34:37 +01:00
Pierre-Louis Mercereau
7f62c8b7c2 refactor(examples): remove obsolete test 2022-11-30 23:31:04 +01:00
Pierre-Louis Mercereau
de2bcb6320 ci: fix 2022-11-30 23:18:42 +01:00
Pierre-Louis Mercereau
64a6d934e7 ci: dict 2022-11-30 23:17:26 +01:00
Pierre-Louis Mercereau
f455022e6d ci: debug 2022-11-30 23:15:26 +01:00
Pierre-Louis Mercereau
baac8a3605 ci: debug 2022-11-30 23:03:39 +01:00
Pierre-Louis Mercereau
be20cd5cdb ci: remove if 2022-11-30 22:58:17 +01:00
Pilou
1de4137369 Merge pull request #4 from plmercereau/renovate/configure
Configure Renovate
2022-11-30 22:54:17 +01:00
Pierre-Louis Mercereau
18fae1a891 ci(renovate): ignore node and pnpm 2022-11-30 22:54:04 +01:00
Pierre-Louis Mercereau
03c58613b8 ci: adapt renovate 2022-11-30 22:49:46 +01:00
Pierre-Louis Mercereau
fb23dd6b39 ci: prepare renovate 2022-11-30 22:45:33 +01:00
renovate[bot]
c12bd632fc chore(deps): add renovate.json 2022-11-30 20:11:34 +00:00
Pilou
a71eea09a9 Merge pull request #1254 from nhost/ci/create-cli-pr
ci: PR to the CLI with the new dashboard version
2022-11-30 18:25:47 +01:00
Szilárd Dóró
c9f88326b2 chore(dashboard): migrate app URLs to service URLs 2022-11-30 17:12:58 +01:00
Pierre-Louis Mercereau
d1f7842169 chore: set fetch-depth to 0 2022-11-30 16:50:32 +01:00
Pierre-Louis Mercereau
4566c2a202 chore: rephrase 2022-11-30 16:40:31 +01:00
Pierre-Louis Mercereau
c678918e73 ci: PR to the CLI with the new dashboard version 2022-11-30 16:38:03 +01:00
Szilárd Dóró
ac8efcbdd5 chore(dashboard): add changeset 2022-11-30 16:13:30 +01:00
Szilárd Dóró
9bc346e8d4 fix(dashboard): minor layout fixes in provider alert 2022-11-30 16:12:11 +01:00
Nuno Pato
efed987d31 fix linter 2022-11-30 12:12:48 -01:00
Szilárd Dóró
7e113bfb1f Merge pull request #1253 from nhost/changeset-release/main
chore: update versions
2022-11-30 14:11:45 +01:00
Nuno Pato
bdab7da7d3 fix linter 2022-11-30 12:02:10 -01:00
Nuno Pato
c2d9993968 asd 2022-11-30 11:53:30 -01:00
Nuno Pato
508ba62207 update copy 2022-11-30 11:52:41 -01:00
Nuno Pato
a3318de06e incorporate minor changes from comments 2022-11-30 11:49:25 -01:00
github-actions[bot]
f1d358d77c chore: update versions 2022-11-30 11:16:45 +00:00
Szilárd Dóró
d558ef9ecf Merge pull request #1249 from nhost/chore/dashboard-cleanup
chore(dashboard): cleanup unused files
2022-11-30 12:08:39 +01:00
Szilárd Dóró
75c7ba7f12 Merge pull request #1252 from nhost/changeset-release/main
chore: update versions
2022-11-30 08:29:25 +01:00
github-actions[bot]
d62dfe19a2 chore: update versions 2022-11-30 07:28:00 +00:00
Szilárd Dóró
0dbef188b1 Merge pull request #1247 from nhost/chore/sidebar-menu-item-height
chore(dashboard): update settings sidebar menu item density
2022-11-30 08:26:39 +01:00
Nuno Pato
fa9f7ca052 Deprecate old DNS name 2022-11-30 03:01:17 -01:00
Szilárd Dóró
8857314e22 Merge pull request #1237 from nhost/changeset-release/main
chore: update versions
2022-11-29 20:57:22 +01:00
github-actions[bot]
85f1c4a98e chore: update versions 2022-11-29 19:33:07 +00:00
Pilou
efa6b5755d Merge pull request #1248 from nhost/chore/remove-sort-lint-rule
chore: remove eslint plugin simple-import-sort
2022-11-29 20:31:24 +01:00
Szilárd Dóró
44f13f6240 chore(dashboard): cleanup unused files 2022-11-29 20:29:25 +01:00
Pierre-Louis Mercereau
2b19416787 chore: remove eslint plugin simple-import-sort 2022-11-29 20:28:13 +01:00
Szilárd Dóró
e01cb2ed49 chore(dashboard): add changeset 2022-11-29 17:18:48 +01:00
Szilárd Dóró
388eef041f chore(dashboard): change settings sidebar menu item density 2022-11-29 17:18:09 +01:00
Szilárd Dóró
4e5d43f300 Merge pull request #1245 from nhost/chore/settings-roles-and-permissions-refactor
chore(dashboard): refactor Roles and Permissions settings sections
2022-11-29 17:00:12 +01:00
Szilárd Dóró
db342f453e chore(dashboard): add changesets 2022-11-29 16:15:45 +01:00
Szilárd Dóró
54386a3b56 chore(dashboard): simplify env var dialog props 2022-11-29 16:14:20 +01:00
Pilou
ff40b99f84 Merge pull request #1242 from nhost/fix/set-access-token
fix: 🐛 Distribute the access token to all the sub-clients
2022-11-29 16:08:00 +01:00
Szilárd Dóró
33f8f1d78a Merge remote-tracking branch 'origin/main' into chore/settings-roles-and-permissions-refactor 2022-11-29 16:07:19 +01:00
Szilárd Dóró
c50fe47ab4 Merge pull request #1215 from nhost/feat/settings-environment-variables
feat(dashboard): add Environment Variables page
2022-11-29 16:04:09 +01:00
Pilou
0580f832c8 Merge pull request #1239 from nhost/chore/version-bumps
chore: 🤖 bump to axios 1.2.0
2022-11-29 15:59:38 +01:00
Szilárd Dóró
7d1eb099c0 fix(dashboard): correct system environment variables 2022-11-29 15:53:11 +01:00
Pierre-Louis Mercereau
e15322296b chore: lint 2022-11-29 15:52:25 +01:00
Pierre-Louis Mercereau
91a2bf905b refactor: simplify 2022-11-29 15:37:31 +01:00
Szilárd Dóró
0f9393fe27 fix(dashboard): change system env vars layout 2022-11-29 15:10:37 +01:00
Szilárd Dóró
aebb822549 feat(dashboard): extend system variables 2022-11-29 15:05:58 +01:00
Pierre-Louis Mercereau
1e2be6fadf Merge branch 'main' into chore/version-bumps 2022-11-29 13:41:00 +01:00
Pilou
aafbf5173d Merge pull request #1240 from nhost/test/nhost-js
test: 💍 `nhost.graphql.request` as an authenticated user
2022-11-29 13:00:46 +01:00
Pierre-Louis Mercereau
01e13e2f8c chore: 🤖 lint 2022-11-29 12:52:34 +01:00
Pierre-Louis Mercereau
4364647501 fix: 🐛 accept any encoding 2022-11-29 12:16:35 +01:00
Pierre-Louis Mercereau
ef117c284e fix: 🐛 Distribute the access token to all the sub-clients 2022-11-29 11:49:59 +01:00
Szilárd Dóró
3f919c0a80 chore(dashboard): refactor system variable layout 2022-11-29 11:30:31 +01:00
Pierre-Louis Mercereau
49e447e7b7 test: 💍 nhost.graphql.request as an authenticated user 2022-11-29 11:04:56 +01:00
Pierre-Louis Mercereau
66b4f3d0be chore: 🤖 bump to axios 1.2.0 2022-11-29 10:29:51 +01:00
Szilárd Dóró
aa7fdafe8b chore(dashboard): split Permission Variable dialog 2022-11-28 21:45:15 +01:00
Pilou
7d6de3b289 Merge pull request #1234 from nhost/chore/bump-hasura-auth
chore: 🤖 bump hasura-auth version
2022-11-28 19:51:10 +01:00
Pilou
57e41f77a9 Merge pull request #1238 from nhost/ci/turbo-team
ci: hardcode turbo team
2022-11-28 19:36:48 +01:00
Pierre-Louis Mercereau
f5c2a0ef4f ci: hardcode turbo team 2022-11-28 18:38:03 +01:00
Szilárd Dóró
d52bc8cca5 chore(dashboard): refactor role delete and default 2022-11-28 16:58:28 +01:00
Pilou
04a3e4c965 Merge pull request #1236 from nhost/refactor/state-snapshots
refactor: use state snapshots
2022-11-28 16:52:27 +01:00
Szilárd Dóró
853c0c5775 chore(dashboard): split RoleForm into multiple forms 2022-11-28 16:44:02 +01:00
Pierre-Louis Mercereau
2e6923dc73 refactor: use state snapshots 2022-11-28 16:41:53 +01:00
Pierre-Louis Mercereau
7d6d70d0c7 chore: 🤖 bump hasura-auth version 2022-11-28 15:59:04 +01:00
Szilárd Dóró
7a2100cc17 fix(dashboard): do not try to autofocus a disable input 2022-11-28 15:19:42 +01:00
Szilárd Dóró
5d55f3fa60 chore(dashboard): remove async form loading 2022-11-28 15:13:16 +01:00
Szilárd Dóró
8b0c44a93c chore(dashboard): rename env var forms
- chore(dashboard): do not convert env vars to uppercase by default
2022-11-28 14:59:27 +01:00
Szilárd Dóró
e0cc7cce0a chore(dashboard): update env var dialog subtitle 2022-11-28 13:40:25 +01:00
Szilárd Dóró
6e7d5e0dd4 fix(dashboard): change env var name placeholder 2022-11-28 13:19:17 +01:00
Szilárd Dóró
54c143ebf6 fix(dashboard): allow lowercase letters, convert variable to uppercase 2022-11-28 13:18:16 +01:00
Szilárd Dóró
8b9fa0b150 feat(dashboard): env var validation 2022-11-28 13:05:42 +01:00
Szilárd Dóró
c3bb79e1dd chore(dashboard): refactor env var forms 2022-11-28 11:15:38 +01:00
Szilárd Dóró
128d21e4ec Merge branch 'main' into feat/settings-environment-variables 2022-11-28 09:21:56 +01:00
Szilárd Dóró
40e503c356 Merge pull request #1227 from nhost/fix/vercel-deployment-token
fix(changesets): add Vercel deployment token to CI
2022-11-28 08:28:33 +01:00
Szilárd Dóró
d007e0ade8 chore(changesets): do not create dedicated env var for deploy token 2022-11-28 08:26:11 +01:00
Pilou
fa32513ba7 Merge pull request #1231 from nhost/docs/docker-compose-dashboard
docs: run hasura console from the cli to run the dashboard
2022-11-27 22:50:14 +01:00
Pierre-Louis Mercereau
8893d9e010 docs: run hasura console from the cli to run the dashboard 2022-11-27 21:37:37 +01:00
Szilárd Dóró
81d2fd865c fix(changesets): add Vercel deployment token to CI 2022-11-27 19:51:07 +01:00
Szilárd Dóró
fe3c462099 Merge pull request #1217 from nhost/fix/vercel-pipeline
fix(changesets): add missing `pnpm` command, pre-build project
2022-11-26 11:49:08 +01:00
Szilárd Dóró
f8b082cb02 chore(changesets): split Vercel CLI command 2022-11-25 16:59:56 +01:00
Szilárd Dóró
0c748e6ee6 feat(dashboard): add env var management 2022-11-25 16:57:22 +01:00
Pilou
e2c4ca85b3 Merge pull request #1219 from nhost/docs/docker-compose-dashboard
docs(docker-compose): add the dashboard to the docker-compose example
2022-11-25 16:38:54 +01:00
Szilárd Dóró
0165b998c2 chore(changesets): create separate step for Vercel 2022-11-25 16:33:09 +01:00
Pierre-Louis Mercereau
5d970cc229 feat(docs): add the dashboard to the docker-compose example 2022-11-25 16:14:46 +01:00
Szilárd Dóró
7167170663 feat(dashboard): add env var management dialog 2022-11-25 15:47:34 +01:00
Szilárd Dóró
0f77de2dd0 feat(dashboard): add menu to env vars 2022-11-25 15:13:54 +01:00
Szilárd Dóró
6ae91e48d1 feat(dashboard): list env vars 2022-11-25 14:52:47 +01:00
Szilárd Dóró
69db1594cc fix(changesets): add missing pnpm command, pre-build project 2022-11-25 14:36:54 +01:00
Szilárd Dóró
158cf0da49 Merge pull request #1216 from nhost/changeset-release/main
chore: update versions
2022-11-25 14:12:10 +01:00
github-actions[bot]
7992fc3baa chore: update versions 2022-11-25 13:09:54 +00:00
Szilárd Dóró
85d9596956 Merge branch 'main' into feat/settings-environment-variables 2022-11-25 14:08:46 +01:00
Szilárd Dóró
16d383516e Merge pull request #1206 from nhost/feat/settings-roles-and-permissions
feat(dashboard): Roles and Permissions
2022-11-25 14:08:13 +01:00
Szilárd Dóró
2ca193ccf3 chore(dashboard): improve inline secrets 2022-11-25 14:07:44 +01:00
Szilárd Dóró
ab8e12003d feat(dashboard): show JWT secret 2022-11-25 14:02:48 +01:00
Szilárd Dóró
29cdf6b125 Merge pull request #1214 from nhost/elitan-patch-3
Update serverless-functions.mdx
2022-11-25 13:51:24 +01:00
Szilárd Dóró
41cc3dc5d0 feat(dashboard): settings page for Environment Variables 2022-11-25 13:50:08 +01:00
Johan Eliasson
6b67c9996a Update serverless-functions.mdx 2022-11-25 12:50:39 +01:00
Szilárd Dóró
23274dee41 chore(docs): add changeset 2022-11-25 11:59:42 +01:00
Szilárd Dóró
a5b55c2667 chore(docs): update permission variables image 2022-11-25 11:58:46 +01:00
Szilárd Dóró
1263676eb3 fix(dashboard): permission variable validation 2022-11-25 11:49:46 +01:00
Szilárd Dóró
b1b647ad96 fix(dashboard): reset default role on role removal 2022-11-25 11:34:51 +01:00
Szilárd Dóró
21bbaf5e95 feat(dashboard): add docs link to roles section 2022-11-25 10:56:14 +01:00
Szilárd Dóró
eef9c91403 feat(dashboard): add support for default roles
- remove unnecessary helper labels from roles and permissions
2022-11-25 10:55:20 +01:00
Johan Eliasson
1742cb444d Merge pull request #1212 from nhost/szilarddoro-patch-1
chore(docs): add WorkOS to Authentication page
2022-11-25 10:49:45 +01:00
Szilárd Dóró
c4f374d7f3 Merge remote-tracking branch 'origin/main' into feat/settings-roles-and-permissions 2022-11-25 09:36:57 +01:00
Szilárd Dóró
369ec13070 chore(docs): add WorkOS to Authentication page 2022-11-25 09:36:21 +01:00
Szilárd Dóró
101129eef2 Update dashboard/src/components/settings/permissions/PermissionVariableSettings/PermissionVariableSettings.tsx
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2022-11-25 09:33:33 +01:00
Szilárd Dóró
228fda0364 Merge pull request #1210 from nhost/fix/vercel-deployment 2022-11-25 09:05:57 +01:00
Szilárd Dóró
74085c67a2 fix(dashboard): correct production deployment 2022-11-24 22:31:11 +01:00
Szilárd Dóró
a273725419 Merge pull request #1209 from nhost/fix/precommit-hook
fix(docgen): prevent docgen from breaking the pre-commit hook
2022-11-24 21:07:40 +01:00
Pierre-Louis Mercereau
c5240f8d74 docs: ✏️ remove irrelevant workaround to fixed docgen git hook 2022-11-24 20:53:17 +01:00
Szilárd Dóró
4490068257 chore(docgen): copy binary to node_modules
execute `pnpm i` to copy the `docgen` binary to every package
2022-11-24 20:41:59 +01:00
Szilárd Dóró
3601de3f85 Merge pull request #1208 from nhost/changeset-release/main
chore: update versions
2022-11-24 20:20:45 +01:00
github-actions[bot]
ac9404610b chore: update versions 2022-11-24 19:09:19 +00:00
Szilárd Dóró
63570db57c Merge pull request #1207 from nhost/contributors-readme-action-MBHUTcRQyD
contributors readme action update
2022-11-24 20:08:33 +01:00
github-actions[bot]
538ed78f5a contrib-readme-action has updated readme 2022-11-24 19:07:02 +00:00
Szilárd Dóró
b1a31ecb00 Merge pull request #1181 from nhost/fix/dashboard-custom-local-ports
feat(dashboard): make backend port configurable
2022-11-24 20:06:44 +01:00
Szilárd Dóró
3d151c448c chore(precommit): extend precommit hook with docgen build 2022-11-24 19:42:23 +01:00
Siarhei Lipchyk
bac8ace434 Add support for custom ports via placeholders 2022-11-24 19:03:02 +01:00
Szilárd Dóró
fdd417ed25 feat(dashboard): add router cancellation to permission form
- chore(dashboard): update terminology
2022-11-24 18:03:04 +01:00
Pilou
a402fc17de Merge pull request #1192 from nhost/changeset-release/main
chore: update versions
2022-11-24 17:21:07 +01:00
Szilárd Dóró
4416ceb9cf chore(dashboard): cleanup unused files 2022-11-24 17:00:35 +01:00
Szilárd Dóró
4762ebf61e fix(dashboard): correct original value for role editing 2022-11-24 16:54:01 +01:00
Szilárd Dóró
73e28b5831 feat(dashboard): simplify role management form 2022-11-24 16:52:26 +01:00
Szilárd Dóró
2a7dc5060f feat(dashboard): add support for permission variable management 2022-11-24 16:25:14 +01:00
Szilárd Dóró
9b8ede40a9 feat(dashboard): prevent invalid characters for variables 2022-11-24 15:50:14 +01:00
Szilárd Dóró
f005c20d99 feat(dashboard): add modals for permission variable management 2022-11-24 15:23:30 +01:00
Szilárd Dóró
4adfd613b6 feat(dashboard): support variable listing
- chore(dashboard): rename RolesSettings to RoleSettings
2022-11-24 15:01:51 +01:00
Szilárd Dóró
b6da82c8e3 fix(dashboard): wrong tooltip appearance 2022-11-24 14:25:49 +01:00
Szilárd Dóró
816456edc4 fix(dashboard): remove unnecessary manual focus 2022-11-24 14:24:07 +01:00
Szilárd Dóró
deaf0e86d4 fix(dashboard): changed role form's logic 2022-11-24 14:16:02 +01:00
Szilárd Dóró
23f8206f18 feat(dashboard): add support for role deletion 2022-11-24 12:46:41 +01:00
Szilárd Dóró
9dde4d7988 feat(dashboard): add support for role editing 2022-11-24 12:31:48 +01:00
Szilárd Dóró
26385b9cf9 fix(dashboard): fix ESLint working directories 2022-11-24 12:17:50 +01:00
Szilárd Dóró
6d318206ef feat(dashboard): finalize Create Role modal
- fix(dashboard): lint errors
- chore(dashboard): reduce max allowed linter warnings
2022-11-24 12:11:41 +01:00
Szilárd Dóró
4d727b78a1 feat(dashboard): updated Roles and Permissions page
- created modal for role creation
2022-11-24 11:46:56 +01:00
github-actions[bot]
de0a125e98 chore: update versions 2022-11-24 09:38:59 +00:00
Pilou
ea1ad29031 Merge pull request #1200 from nhost/plmercereau-patch-storage-tag
Update docker-compose.yaml
2022-11-24 10:37:25 +01:00
Szilárd Dóró
3da40e5712 Merge pull request #1205 from nhost/fix/dashboard-meta
fix(dashboard): update terminology
2022-11-24 10:35:48 +01:00
Szilárd Dóró
b9087a4add fix(dashboard): console / dashboard terminology 2022-11-24 09:54:44 +01:00
Szilárd Dóró
1b7a6d0252 fix(dashboard): update terminology 2022-11-24 09:23:08 +01:00
Szilárd Dóró
1417d3e794 Merge pull request #1203 from nhost/contributors-readme-action-FFIU1CawPP
contributors readme action update
2022-11-24 08:55:41 +01:00
github-actions[bot]
e187923858 contrib-readme-action has updated readme 2022-11-24 07:52:33 +00:00
Johan Eliasson
8a60ed4074 Merge pull request #1199 from nhost/functions-more-examples
examples(serverless-functions): smtp + async/await
2022-11-24 08:52:19 +01:00
Pilou
d7d11a44a7 Update docker-compose.yaml
There's no latest tag on storage
2022-11-23 22:04:40 +01:00
Johan Eliasson
062e4691cd added examples 2022-11-23 21:53:25 +01:00
Pilou
a95d49fa2c Merge pull request #1197 from nhost/docs/custom-claims-singleton-array
docs: custom claims and singleton arrays
2022-11-23 19:06:34 +01:00
Johan Eliasson
d14fc96899 Merge pull request #1036 from ejkkan/feat/add-charges-to-stripe-package
feat(stripe-graphql-js): add charges, payment intents and connected accounts
2022-11-23 19:03:37 +01:00
Johan Eliasson
93db718254 Create blue-ghosts-accept.md 2022-11-23 19:03:12 +01:00
Pierre-Louis Mercereau
c367bd58b9 docs: custom claims and singleton arrays 2022-11-23 19:01:29 +01:00
Pilou
0bfed4d9e1 Merge pull request #1196 from nhost/plmercereau-patch-1
Update docker-compose.yaml
2022-11-23 18:43:36 +01:00
Pilou
1f3aecd379 Merge pull request #1193 from nhost/chore/bump-service-versions
ci: 🎡 bump services versions and trigger CI
2022-11-23 18:43:20 +01:00
Pilou
42306ea3bb Update docker-compose.yaml 2022-11-23 18:19:41 +01:00
Pilou
1b12a175f6 Update docker-compose.yaml 2022-11-23 18:18:06 +01:00
Szilárd Dóró
32060aaea0 Merge pull request #1195 from nhost/chore/dashboard-version
chore(dashboard): add changeset
2022-11-23 16:00:18 +01:00
Szilárd Dóró
f94cace5f2 Merge pull request #1194 from nhost/feat/dashboard-vercel-deployment
feat(dashboard): add Vercel deployment
2022-11-23 15:22:09 +01:00
Szilárd Dóró
5de965d9a5 chore(dashboard): add changeset 2022-11-23 15:15:08 +01:00
Szilárd Dóró
e10b3adc11 chore(changesets): incorporate Vercel deployment into publish process 2022-11-23 15:08:21 +01:00
Szilárd Dóró
457db76b06 Merge pull request #1191 from nhost/fix/sign-in-methods-order
fix(dashboard): alphabetic ordering of providers
2022-11-23 15:04:26 +01:00
Szilárd Dóró
1e952a026e chore(changesets): update publish step's name 2022-11-23 15:01:55 +01:00
Szilárd Dóró
2f4c040789 feat(dashboard): add Vercel deployment 2022-11-23 14:33:00 +01:00
Pierre-Louis Mercereau
74648752b4 ci: use correct hasura version 2022-11-23 13:12:32 +01:00
Szilárd Dóró
09d218a3fe fix(dashboard): sign-in method phrasing 2022-11-23 13:12:24 +01:00
Szilárd Dóró
2e8938dbb0 fix(dashboard): add missing Twilio icon 2022-11-23 13:00:12 +01:00
Pierre-Louis Mercereau
ec60d03536 ci: 🎡 bump services versions and trigger CI 2022-11-23 12:57:04 +01:00
Johan Eliasson
2f3767552f Merge pull request #1189 from nhost/docs-workos
docs(workos): WorkOS Docs
2022-11-23 12:14:57 +01:00
Szilárd Dóró
bc401c0dd2 fix(dashboard): alphabetic ordering of providers
- fixes #1188
- fix checkbox font size on the Settings page
2022-11-23 12:12:39 +01:00
Pilou
2907ecb7ff Merge pull request #1179 from nhost/changeset-release/main
chore: update versions
2022-11-23 11:24:55 +01:00
Pierre-Louis Mercereau
05d7f5207f chore: no major bump of peer dependencies 2022-11-23 11:22:55 +01:00
github-actions[bot]
07a053ee80 chore: update versions 2022-11-23 09:58:39 +00:00
Szilárd Dóró
2145243b19 chore(dashboard): update environment variables 2022-11-23 10:56:47 +01:00
Pilou
61e4414a8f Merge pull request #1190 from nhost/changeset-react-components
react components changeset added
2022-11-23 10:55:04 +01:00
Johan Eliasson
4601d84e0e changeset added 2022-11-23 10:29:19 +01:00
Johan Eliasson
ca012d790c Create tidy-teachers-flow.md 2022-11-23 10:27:23 +01:00
Johan Eliasson
aeda14ef53 docs link 2022-11-23 10:25:18 +01:00
Johan Eliasson
3fa5e2005a updates 2022-11-23 10:23:40 +01:00
Johan Eliasson
4dd2e99159 Merge pull request #1187 from nhost/docs-git-8g712asd
docs(git): git docs updates
2022-11-23 08:27:17 +01:00
Johan Eliasson
282c6c6d24 git docs update 2022-11-23 08:16:55 +01:00
Johan Eliasson
beadd84adb moving files 2022-11-23 07:52:57 +01:00
Johan Eliasson
f8f55d2b99 Merge branch 'main' into feat/add-charges-to-stripe-package 2022-11-23 07:49:17 +01:00
Johan Eliasson
03a98d4f3a config updates 2022-11-23 07:36:31 +01:00
Johan Eliasson
8ed8e04ab6 merge 2022-11-23 07:31:37 +01:00
Erik Magnusson
587efd4551 re-applied isAdmin checks for connected account field types 2022-11-23 08:15:44 +02:00
Johan Eliasson
c78227b085 Merge pull request #1185 from nhost/docs/node-16
docs: update developer's guide
2022-11-23 07:06:10 +01:00
Pierre-Louis Mercereau
d87e520307 docs: lessons learned from Sheena and Chris 2022-11-22 21:44:48 +01:00
Pilou
bbed04e4da Merge pull request #1158 from nhost/fix/use-user-roles
fix: 🐛 make `useUserRoles` reactive
2022-11-22 21:35:06 +01:00
Pilou
273afc9740 Merge pull request #1184 from nhost/contributors-readme-action-h-P6q9XKTD
contributors readme action update
2022-11-22 21:32:07 +01:00
github-actions[bot]
f4083aa4b3 contrib-readme-action has updated readme 2022-11-22 20:12:23 +00:00
Pilou
ddd2641726 Merge pull request #1183 from massless/min-version-node
Update minimum version of node
2022-11-22 21:12:04 +01:00
Chris Wetherell
4658aeb31e Update minimum version of node 2022-11-22 12:08:47 -08:00
Johan Eliasson
cc8e5fe4a9 Merge pull request #1180 from nhost/react-components-iy8gasd9
React Auth components: SignedIn and SignedOut
2022-11-22 19:54:19 +01:00
Szilárd Dóró
85c897c717 chore(docs): update source code references 2022-11-22 18:32:48 +01:00
Szilárd Dóró
c99e5552e6 chore(react): simplify component signature 2022-11-22 18:31:39 +01:00
Szilárd Dóró
97a2520ea1 feat(docgen): add support for components 2022-11-22 18:24:23 +01:00
Johan Eliasson
964af2912b inline docs 2022-11-22 17:04:39 +01:00
Szilárd Dóró
a48dd5bf74 chore(dashboard): add changeset 2022-11-22 14:32:53 +01:00
Szilárd Dóró
ef53df5cb3 feat(dashboard): make backend port configurable
- additionally: improve the way Hasura service ports are. configured through environment variables
2022-11-22 14:32:13 +01:00
Johan Eliasson
afea682a8c merge 2022-11-22 14:09:38 +01:00
Pierre-Louis Mercereau
fefa2baa2e refactor: readability 2022-11-22 13:28:09 +01:00
Pilou
f09b3cfd24 Merge pull request #1178 from nhost/ci/freeze-pnpm-version
ci: set explicit pnpm version in the dashboard dockerfile
2022-11-22 13:20:35 +01:00
Pilou
dd3b2c41f1 Merge pull request #1171 from nhost/chore/vue-file-upload-changeset
chore: add changeset to vue, and correct inline documentation
2022-11-22 13:20:21 +01:00
Szilárd Dóró
aaced20f31 Merge pull request #1173 from nhost/fix/dashboard-provider-redirect-url
fix(dashboard): correct redirect URL input opacity
2022-11-22 13:15:35 +01:00
Pierre-Louis Mercereau
3e91c19e13 ci: set explicit pnpm version in the dashboard dockerfile 2022-11-22 13:11:00 +01:00
Johan Eliasson
d4fd4ec3e9 signedin and signedout 2022-11-22 10:48:24 +01:00
Szilárd Dóró
89bd37bc28 chore(dashboard): add changeset 2022-11-22 10:20:52 +01:00
Szilárd Dóró
0df73a41c9 fix(dashboard): redirect URL opacity 2022-11-22 10:20:09 +01:00
Pierre-Louis Mercereau
f6d2042adb chore: add changeset to vue, and correct inline documentation 2022-11-22 09:34:47 +01:00
Pierre-Louis Mercereau
843087cb11 fix: 🐛 make useUserRoles reactive 2022-11-21 15:58:55 +01:00
Erik Magnusson
7055ffc37a Merge branch 'feat/add-charges-to-stripe-package' of https://github.com/ejkkan/nhost into feat/add-charges-to-stripe-package 2022-10-20 20:57:56 +02:00
Erik Magnusson
c68ce6d480 removed isAdmin checks for connected accounts related schema fields 2022-10-20 20:57:01 +02:00
Erik Magnusson
98a149c8bf Update packages/stripe-graphql-js/src/schema/charge.ts
Co-authored-by: Johan Eliasson <johan@eliasson.me>
2022-10-18 08:48:58 +02:00
Erik Magnusson
ceb558975e Update packages/stripe-graphql-js/src/schema/charge.ts
Co-authored-by: Johan Eliasson <johan@eliasson.me>
2022-10-18 08:48:46 +02:00
Erik Magnusson
7a87321a7e fixd imports and linting 2022-10-14 21:21:25 +02:00
Erik Magnusson
9349766c0a corrected spelling 2022-10-14 21:19:23 +02:00
Erik Magnusson
31655191a3 added connected accounts 2022-10-14 21:05:57 +02:00
Erik Magnusson
e3b91efa84 feat/added charges 2022-10-14 20:13:35 +02:00
Erik Magnusson
cfe736776a feat/added charges 2022-10-14 20:03:19 +02:00
Erik Magnusson
481bf237cc updated naming convention to camel casing 2022-10-14 10:11:02 +02:00
Erik Magnusson
33ce9bf1b9 added payment intents type for customer and invoice objects 2022-10-04 18:10:02 +02:00
371 changed files with 7900 additions and 8258 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -5,6 +5,10 @@ inputs:
description: 'Turborepo token'
TURBO_TEAM:
description: 'Turborepo team'
BUILD:
description: 'Build packages'
default: 'default'
runs:
using: 'composite'
steps:
@@ -35,6 +39,14 @@ runs:
# * They are reused through the Turborepo cache
- shell: bash
name: Build packages
if: ${{ inputs.BUILD == 'all' }}
run: pnpm 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
env:
TURBO_TOKEN: ${{ inputs.TURBO_TOKEN }}

108
.github/actions/nhost-cli/README.md vendored Normal file
View File

@@ -0,0 +1,108 @@
# Nhost CLI GitHub Action
## Usage
```yaml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install the Nhost CLI
uses: ./.github/actions/nhost-cli
```
### Install the CLI and start the app
```yaml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Nhost CLI and start the application
uses: ./.github/actions/nhost-cli
with:
start: true
```
### Set another working directory
```yaml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Nhost CLI
uses: ./.github/actions/nhost-cli
with:
path: examples/react-apollo
start: true
```
### Don't wait for the app to be ready
```yaml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Nhost CLI and start app
uses: ./.github/actions/nhost-cli
with:
start: true
wait: false
```
### Stop the app
```yaml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Start app
uses: ./.github/actions/nhost-cli
with:
start: true
- name: Do something
cmd: echo "do something"
- name: Stop
uses: ./.github/actions/nhost-cli
with:
stop: true
```
### Install a given value of the CLI
```yaml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Nhost CLI
uses: ./.github/actions/nhost-cli
with:
version: v0.8.10
```
### Inject values into nhost/config.yaml
```yaml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Nhost CLI
uses: ./.github/actions/nhost-cli
with:
config: |
services:
auth:
image: nhost/hasura-auth:0.16.1
```

75
.github/actions/nhost-cli/action.yaml vendored Normal file
View File

@@ -0,0 +1,75 @@
name: Nhost CLI
description: 'Action to install the Nhost CLI and to run an application'
inputs:
start:
description: "Start the application. If false, the application won't be started"
default: 'false'
wait:
description: 'If starting the application, wait until it is ready'
default: 'true'
stop:
description: 'Stop the application'
default: 'false'
path:
description: 'Path to the application'
default: '.'
version:
description: 'Version of the Nhost CLI'
default: 'latest'
config:
description: 'Values to be injected into nhost/config.yaml'
runs:
using: 'composite'
steps:
- name: Check if Nhost CLI is already installed
id: check-nhost-cli
shell: bash
# TODO check if the version is the same
run: |
if [ -z "$(which nhost)" ]
then
echo "installed=false" >> $GITHUB_OUTPUT
else
echo "installed=true" >> $GITHUB_OUTPUT
fi
- name: Install Nhost CLI
if: ${{ steps.check-nhost-cli.outputs.installed == 'false' }}
shell: bash
run: bash <(curl --silent -L https://raw.githubusercontent.com/nhost/cli/main/get.sh) ${{ inputs.version }}
- name: Set custom configuration
if: ${{ inputs.config }}
shell: bash
working-directory: ${{ inputs.path }}
run: config="${{ inputs.config }}" yq -i '. *= env(config)' nhost/config.yaml
- name: Start the application
if: ${{ inputs.start == 'true' }}
shell: bash
working-directory: ${{ inputs.path }}
run: nhost dev --no-browser &
- name: Wait for the app to be ready
id: wait
if: ${{ inputs.start == 'true' && inputs.wait == 'true' }}
shell: bash
working-directory: ${{ inputs.path }}
continue-on-error: true
run: |
curl -sSf --connect-timeout 3 \
--max-time 5 \
--retry 300 \
--retry-delay 1 \
--retry-max-time 300 \
--retry-connrefused \
'http://localhost:9695' > /dev/null
- name: Log on failure
if: steps.wait.outcome == 'failure'
shell: bash
working-directory: ${{ inputs.path }}
run: |
nhost logs
exit 1
- name: Stop the application
if: ${{ inputs.stop == 'true' }}
shell: bash
working-directory: ${{ inputs.path }}
run: nhost down

22
.github/labeler.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
dashboard:
- dashboard/**/*
documentation:
- any:
- docs/**/*
- '!docs/docs/reference/docgen/**/*'
examples:
- examples/**/*
sdk:
- packages/**/*
react:
- '{packages,examples}/*react*/**/*'
nextjs:
- '{packages,examples}/*next*/**/*'
vue:
- '{packages,examples}/*vue*/**/*'

16
.github/renovate.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"docker-compose": {
"enabled": true
},
"ignoreDeps": [
"pnpm",
"node"
],
"labels": [
"dependencies"
]
}

View File

@@ -13,11 +13,12 @@ on:
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TEAM: nhost
DASHBOARD_PACKAGE: '@nhost/dashboard'
jobs:
version:
name: Version
runs-on: ubuntu-latest
outputs:
hasChangesets: ${{ steps.changesets.outputs.hasChangesets }}
@@ -30,8 +31,8 @@ jobs:
- name: Install Node and dependencies
uses: ./.github/actions/install-dependencies
with:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TOKEN: ${{ env.TURBO_TOKEN }}
TURBO_TEAM: ${{ env.TURBO_TEAM }}
- name: Create PR or Publish release
id: changesets
uses: changesets/action@v1
@@ -60,7 +61,8 @@ jobs:
uses: ./.github/workflows/dashboard.yaml
secrets: inherit
publish:
publish-docker:
name: Publish to Docker Hub
runs-on: ubuntu-latest
needs:
- test
@@ -120,3 +122,59 @@ jobs:
- name: Remove tag on failure
if: failure()
run: git push --delete origin ${{ env.DASHBOARD_PACKAGE }}@${{ needs.version.outputs.dashboardVersion }}
publish-vercel:
name: Publish to Vercel
runs-on: ubuntu-latest
needs:
- publish-docker
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Node and dependencies
uses: ./.github/actions/install-dependencies
with:
TURBO_TOKEN: ${{ env.TURBO_TOKEN }}
TURBO_TEAM: ${{ env.TURBO_TEAM }}
- name: Setup Vercel CLI
run: pnpm add -g vercel
- name: Trigger a Vercel deployment
env:
VERCEL_ORG_ID: ${{ secrets.DASHBOARD_VERCEL_TEAM_ID }}
VERCEL_PROJECT_ID: ${{ secrets.DASHBOARD_VERCEL_PROJECT_ID }}
run: |
vercel pull --environment=production --token=${{ secrets.DASHBOARD_VERCEL_DEPLOY_TOKEN }}
vercel build --prod --token=${{ secrets.DASHBOARD_VERCEL_DEPLOY_TOKEN }}
vercel deploy --prebuilt --prod --token=${{ secrets.DASHBOARD_VERCEL_DEPLOY_TOKEN }}
bump-cli:
name: Bump Dashboard version in the Nhost CLI
runs-on: ubuntu-latest
needs:
- version
- publish-docker
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
repository: nhost/cli
token: ${{ secrets.GH_PAT }}
fetch-depth: 0
- name: Bump version in source code
run: |
IMAGE=$(echo ${{ env.DASHBOARD_PACKAGE }} | sed 's/@\(.\+\)\/\(.\+\)/\1\\\/\2/g')
VERSION="${{ needs.version.outputs.dashboardVersion }}"
EXPRESSION='s/"'$IMAGE':[0-9]\+\.[0-9]\+\.[0-9]\+"/"'$IMAGE':'$VERSION'"/g'
find ./ -type f -exec sed -i -e $EXPRESSION {} \;
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.GH_PAT }}
commit-message: 'chore: bump nhost/dashboard to ${{ needs.version.outputs.dashboardVersion }}'
branch: bump-dashboard-version
delete-branch: true
title: 'chore: bump nhost/dashboard to ${{ needs.version.outputs.dashboardVersion }}'
body: |
This PR bumps the Nhost Dashboard Docker image to version ${{ needs.version.outputs.dashboardVersion }}.

View File

@@ -1,11 +1,9 @@
name: Packages
name: Continuous Integration
on:
push:
branches: [main]
paths-ignore:
- 'dashboard/**'
- 'docs/**'
- 'assets/**'
- '**.md'
- 'LICENSE'
@@ -13,15 +11,16 @@ on:
branches: [main]
types: [opened, synchronize]
paths-ignore:
- 'dashboard/**'
- 'docs/**'
- 'assets/**'
- '**.md'
- 'LICENSE'
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TEAM: nhost
NEXT_PUBLIC_ENV: dev
NEXT_TELEMETRY_DISABLED: 1
NEXT_PUBLIC_NHOST_BACKEND_URL: http://localhost:1337
jobs:
build:
name: Build @nhost packages
@@ -36,21 +35,34 @@ jobs:
with:
TURBO_TOKEN: ${{ env.TURBO_TOKEN }}
TURBO_TEAM: ${{ env.TURBO_TEAM }}
BUILD: 'all'
- name: Check if the pnpm lockfile changed
id: changed-lockfile
uses: tj-actions/changed-files@v34
with:
files: pnpm-lock.yaml
# * Determine a pnpm filter argument for packages that have been modified.
# * If the lockfile has changed, we don't filter anything in order to run all the e2e tests.
- name: filter packages
id: filter-packages
if: steps.changed-lockfile.outputs.any_changed != 'true' && github.event_name == 'pull_request'
run: echo "filter=${{ format('--filter=...[origin/{0}]', github.base_ref) }}" >> $GITHUB_OUTPUT
# * List packagesthat has an `e2e` script, except the root, and return an array of their name and path
# * In a PR, only include packages that have been modified, and their dependencies
- name: List examples with an e2e script
id: set-matrix
run: |
FILTER_MODIFIED="${{ github.event_name == 'pull_request' && format('--filter=...[origin/{0}]', github.base_ref) || '' }}"
PACKAGES=$(pnpm recursive list --depth -1 --parseable --filter='!nhost-root' $FILTER_MODIFIED \
PACKAGES=$(pnpm recursive list --depth -1 --parseable --filter='!nhost-root' ${{ steps.filter-packages.outputs.filter }} \
| 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)
echo "matrix=$PACKAGES" >> $GITHUB_OUTPUT
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
e2e:
name: 'e2e: ${{ matrix.package.name }}'
name: 'e2e (${{ matrix.package.path }})'
needs: build
if: ${{ needs.build.outputs.matrix != '[]' && needs.build.outputs.matrix != '' }}
strategy:
@@ -70,7 +82,7 @@ jobs:
# * Install Nhost CLI if a `nhost/config.yaml` file is found
- name: Install Nhost CLI
if: hashFiles(format('{0}/nhost/config.yaml', matrix.package.path)) != ''
run: curl -L https://raw.githubusercontent.com/nhost/cli/main/get.sh | bash
uses: ./.github/actions/nhost-cli
# * Run the `ci` script of the current package of the matrix. Dependencies build is cached by Turborepo
- name: Run e2e test
run: pnpm --filter="${{ matrix.package.name }}" run e2e
@@ -89,6 +101,7 @@ jobs:
path: |
${{format('{0}/cypress/screenshots/**', matrix.package.path)}}
${{format('{0}/cypress/videos/**', matrix.package.path)}}
unit:
name: Unit tests
needs: build
@@ -103,7 +116,7 @@ jobs:
TURBO_TEAM: ${{ env.TURBO_TEAM }}
# * Run every `test` script in the workspace . Dependencies build is cached by Turborepo
- name: Run unit tests
run: pnpm run test
run: pnpm run test:all
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
@@ -113,6 +126,7 @@ jobs:
run: |
echo '### Code coverage' >> $GITHUB_STEP_SUMMARY
echo 'Visit [codecov](https://app.codecov.io/gh/nhost/nhost/) to see the code coverage reports' >> $GITHUB_STEP_SUMMARY
lint:
name: Lint
needs: build
@@ -127,4 +141,4 @@ jobs:
TURBO_TEAM: ${{ env.TURBO_TEAM }}
# * Run every `lint` script in the workspace . Dependencies build is cached by Turborepo
- name: Lint
run: pnpm run lint
run: pnpm run lint:all

View File

@@ -10,6 +10,6 @@ jobs:
name: A job to automate contrib in readme
steps:
- name: Contribute List
uses: akhilmhdh/contributors-readme-action@v2.3.4
uses: akhilmhdh/contributors-readme-action@v2.3.6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -2,24 +2,17 @@ name: 'Dashboard'
on:
workflow_call:
pull_request:
branches: [main]
types: [opened, synchronize]
paths:
- 'packages/**'
- 'dashboard/**'
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TEAM: nhost
NEXT_PUBLIC_ENV: dev
NEXT_TELEMETRY_DISABLED: 1
NEXT_PUBLIC_NHOST_BACKEND_URL: http://localhost:1337
jobs:
build:
name: Build
runs-on: ubuntu-latest
env:
NEXT_PUBLIC_ENV: dev
NEXT_TELEMETRY_DISABLED: 1
NEXT_PUBLIC_NHOST_BACKEND_URL: http://localhost:1337
steps:
- uses: actions/checkout@v3
- name: Install Node and dependencies
@@ -33,10 +26,6 @@ jobs:
tests:
name: Tests
runs-on: ubuntu-latest
env:
NEXT_PUBLIC_ENV: dev
NEXT_TELEMETRY_DISABLED: 1
NEXT_PUBLIC_NHOST_BACKEND_URL: http://localhost:1337
steps:
- uses: actions/checkout@v3
- name: Install Node and dependencies
@@ -50,10 +39,6 @@ jobs:
lint:
name: Lint
runs-on: ubuntu-latest
env:
NEXT_PUBLIC_ENV: dev
NEXT_TELEMETRY_DISABLED: 1
NEXT_PUBLIC_NHOST_BACKEND_URL: http://localhost:1337
steps:
- uses: actions/checkout@v3
- name: Install Node and dependencies

15
.github/workflows/labeler.yaml vendored Normal file
View File

@@ -0,0 +1,15 @@
name: 'Pull Request Labeler'
on:
- pull_request_target
jobs:
triage:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4
with:
repo-token: '${{ secrets.GH_PAT }}'
sync-labels: ''

68
.github/workflows/renovate.yaml vendored Normal file
View File

@@ -0,0 +1,68 @@
name: Renovate
on:
pull_request:
branches: [main]
types: [closed]
paths-ignore:
- 'assets/**'
- '**.md'
- 'LICENSE'
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: nhost
jobs:
renovate-changeset:
name: Add changeset
if: github.event.pull_request.merged == true && startsWith(github.head_ref, 'renovate/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
# * Install Node and dependencies. Package downloads will be cached for the next jobs.
- name: Install Node and dependencies
uses: ./.github/actions/install-dependencies
with:
TURBO_TOKEN: ${{ env.TURBO_TOKEN }}
TURBO_TEAM: ${{ env.TURBO_TEAM }}
BUILD: 'none'
- name: Determine bumps
id: bumps
run: |
LAST_NON_PR_SHA=$(git log --no-merges main origin/${{ github.head_ref }} --format=format:%h -- | head -2 | tail -1)
echo "result<<EOF" >> $GITHUB_OUTPUT
pnpm recursive list --depth -1 --parseable \
--filter='!nhost-root' \
--filter=[$LAST_NON_PR_SHA] \
| xargs -I@ jq ".name" @/package.json \
| sort \
| uniq -u \
| awk '$0=$0": patch"' \
>> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
- name: Install dictionary
if: steps.bumps.outputs.result != ''
run: sudo apt-get install wbritish
- name: Generate changeset file name
id: file_name
if: steps.bumps.outputs.result != ''
run: |
FILE_NAME=$(shuf -n 3 /usr/share/dict/words | tr '\n' '-' | sed 's/-$//' | sed 's/'"'"'s//g' | tr '[:upper:]' '[:lower:]')
echo "result=./.changeset/${FILE_NAME}.md" >> $GITHUB_OUTPUT
- name: Create changeset file
if: steps.bumps.outputs.result != ''
run: |
cat <<EOF > ${{ steps.file_name.outputs.result }}
---
${{ steps.bumps.outputs.result }}
---
${{ github.event.pull_request.title }}
EOF
- uses: stefanzweifel/git-auto-commit-action@v4
if: steps.bumps.outputs.result != ''
with:
commit_message: ${{ github.event.pull_request.title }}
branch: main

View File

@@ -0,0 +1,104 @@
name: Test Nhost CLI action
on:
pull_request:
branches: [main]
types: [opened, synchronize]
paths:
- '.github/actions/nhost-cli/**'
- '!.github/actions/nhost-cli/**/*.md'
jobs:
install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install the Nhost CLI
uses: ./.github/actions/nhost-cli
- name: should succeed running the nhost command
run: nhost
start:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install the Nhost CLI and start the application
uses: ./.github/actions/nhost-cli
with:
path: packages/nhost-js
start: true
- name: should be running
run: curl -sSf 'http://localhost:9695' > /dev/null
stop:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install the Nhost CLI, start and stop the application
uses: ./.github/actions/nhost-cli
with:
path: packages/nhost-js
start: true
stop: true
- name: should have no live docker container
run: |
if [ -z "docker ps -q" ]; then
echo "Some docker containers are still running"
docker ps
exit 1
fi
wait:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install the Nhost CLI and start the application
uses: ./.github/actions/nhost-cli
with:
path: packages/nhost-js
start: true
wait: false
- name: should not be ready
run: curl -sSf -o /dev/null 'http://localhost:9695' > /dev/null && exit 1 || true
- name: should eventually be ready
run: |
curl -sSf --connect-timeout 3 \
--max-time 5 \
--retry 300 \
--retry-delay 1 \
--retry-max-time 300 \
--retry-connrefused \
'http://localhost:9695' > /dev/null
config:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install the Nhost CLI and run the application
uses: ./.github/actions/nhost-cli
with:
path: packages/nhost-js
start: true
config: |
services:
auth:
image: nhost/hasura-auth:0.15.0
- name: should find the injected hasura-auth version
run: |
VERSION=$(curl -sSf 'http://localhost:1337/v1/auth/version')
EXPECTED_VERSION='{"version":"v0.15.0"}'
if [ "$VERSION" != "$EXPECTED_VERSION" ]; then
echo "Expected version $EXPECTED_VERSION but got $VERSION"
exit 1
fi
version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install the Nhost CLI
uses: ./.github/actions/nhost-cli
with:
version: v0.8.10
- name: should find the correct version
run: nhost version | head -n 1 | grep v0.8.10 || exit 1

4
.gitignore vendored
View File

@@ -48,6 +48,10 @@ todo.md
.netlify
.monorepo-example
# Local Vercel folder
.vercel
# Next.js build output
.next
# TypeDoc output

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
prefer-workspace-packages = true

View File

@@ -14,4 +14,5 @@ package.json
tsconfig.json
tsconfig.*.json
*.d.ts
.next
.next
**/pnpm-lock.yaml

View File

@@ -1,5 +1,6 @@
{
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
}
},
"eslint.workingDirectories": ["./dashboard"]
}

View File

@@ -2,6 +2,8 @@
## Requirements
- This repository works with **Node 16**
- We use [pnpm](https://pnpm.io/) as a package manager to speed up development and builds, and as a basis for our monorepo. You need to make sure it's installed on your machine. There are [several ways to install it](https://pnpm.io/installation), but the easiest way is with `npm`:
```sh
@@ -97,6 +99,7 @@ You can take a look at the changeset documentation: [How to add a changeset](htt
You'll notice that `git commit` takes a few seconds to run. We set a commit hook that scans the changes in the code, automatically generates documentation from the inline [TSDoc](https://tsdoc.org/) annotations, and adds these generated documentation files to the commit. They automatically update the [reference documentation](https://docs.nhost.io/reference).
<!-- ## Good practices
- lint
- prettier

View File

@@ -179,14 +179,21 @@ Here are some ways of contributing to making Nhost better:
<sub><b>Grégory D'Angelo</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ejkkan">
<img src="https://avatars.githubusercontent.com/u/32518962?v=4" width="100;" alt="ejkkan"/>
<br />
<sub><b>Erik Magnusson</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/guicurcio">
<img src="https://avatars.githubusercontent.com/u/20285232?v=4" width="100;" alt="guicurcio"/>
<br />
<sub><b>Guido Curcio</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/subatuba21">
<img src="https://avatars.githubusercontent.com/u/34824571?v=4" width="100;" alt="subatuba21"/>
@@ -221,15 +228,15 @@ Here are some ways of contributing to making Nhost better:
<br />
<sub><b>Christopher Möller</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/GavanWilhite">
<img src="https://avatars.githubusercontent.com/u/2085119?v=4" width="100;" alt="GavanWilhite"/>
<br />
<sub><b>Gavan Wilhite</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/FuzzyReason">
<img src="https://avatars.githubusercontent.com/u/62517920?v=4" width="100;" alt="FuzzyReason"/>
@@ -237,13 +244,6 @@ Here are some ways of contributing to making Nhost better:
<sub><b>Vadim Smirnov</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ejkkan">
<img src="https://avatars.githubusercontent.com/u/32518962?v=4" width="100;" alt="ejkkan"/>
<br />
<sub><b>Erik Magnusson</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/macmac49">
<img src="https://avatars.githubusercontent.com/u/831190?v=4" width="100;" alt="macmac49"/>
@@ -258,21 +258,28 @@ Here are some ways of contributing to making Nhost better:
<sub><b>Subhendu Kundu</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/badgifter">
<img src="https://avatars.githubusercontent.com/u/50094885?v=4" width="100;" alt="badgifter"/>
<br />
<sub><b>Bad Gifter</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/heygambo">
<img src="https://avatars.githubusercontent.com/u/449438?v=4" width="100;" alt="heygambo"/>
<br />
<sub><b>Christian Gambardella</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/dbarrosop">
<img src="https://avatars.githubusercontent.com/u/6246622?v=4" width="100;" alt="dbarrosop"/>
<br />
<sub><b>David Barroso</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/hajek-raven">
<img src="https://avatars.githubusercontent.com/u/7288737?v=4" width="100;" alt="hajek-raven"/>
@@ -294,13 +301,21 @@ Here are some ways of contributing to making Nhost better:
<sub><b>Jerry Jäppinen</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/wollerman">
<img src="https://avatars.githubusercontent.com/u/1610241?v=4" width="100;" alt="wollerman"/>
<br />
<sub><b>Matt Wollerman</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/mdp18">
<img src="https://avatars.githubusercontent.com/u/11698527?v=4" width="100;" alt="mdp18"/>
<br />
<sub><b>Max</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/mustafa-hanif">
<img src="https://avatars.githubusercontent.com/u/30019262?v=4" width="100;" alt="mustafa-hanif"/>
@@ -314,8 +329,7 @@ Here are some ways of contributing to making Nhost better:
<br />
<sub><b>Nicolas Bourdin</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/piromsurang">
<img src="https://avatars.githubusercontent.com/u/17776837?v=4" width="100;" alt="piromsurang"/>
@@ -343,7 +357,8 @@ Here are some ways of contributing to making Nhost better:
<br />
<sub><b>Muttenzer</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/alexander-mart">
<img src="https://avatars.githubusercontent.com/u/14993551?v=4" width="100;" alt="alexander-mart"/>
@@ -357,8 +372,7 @@ Here are some ways of contributing to making Nhost better:
<br />
<sub><b>Amir Ahmic</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/akd-io">
<img src="https://avatars.githubusercontent.com/u/30059155?v=4" width="100;" alt="akd-io"/>
@@ -380,6 +394,14 @@ Here are some ways of contributing to making Nhost better:
<sub><b>Chris</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/massless">
<img src="https://avatars.githubusercontent.com/u/44389?v=4" width="100;" alt="massless"/>
<br />
<sub><b>Chris Wetherell</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/rustyb">
<img src="https://avatars.githubusercontent.com/u/53086?v=4" width="100;" alt="rustyb"/>
@@ -400,8 +422,7 @@ Here are some ways of contributing to making Nhost better:
<br />
<sub><b>Dmitry Minkovsky</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/dohomi">
<img src="https://avatars.githubusercontent.com/u/489221?v=4" width="100;" alt="dohomi"/>
@@ -422,7 +443,8 @@ Here are some ways of contributing to making Nhost better:
<br />
<sub><b>Helio Alves</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/nkhdo">
<img src="https://avatars.githubusercontent.com/u/26102306?v=4" width="100;" alt="nkhdo"/>
@@ -430,6 +452,13 @@ Here are some ways of contributing to making Nhost better:
<sub><b>Hoang Do</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/iangabrielsanchez">
<img src="https://avatars.githubusercontent.com/u/9511946?v=4" width="100;" alt="iangabrielsanchez"/>
<br />
<sub><b>Ian Gabriel Sanchez</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/eltociear">
<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="100;" alt="eltociear"/>
@@ -443,8 +472,7 @@ Here are some ways of contributing to making Nhost better:
<br />
<sub><b>Jacob Duval</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/leothorp">
<img src="https://avatars.githubusercontent.com/u/12928449?v=4" width="100;" alt="leothorp"/>
@@ -458,7 +486,8 @@ Here are some ways of contributing to making Nhost better:
<br />
<sub><b>Lucas Bois</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/MarcelloTheArcane">
<img src="https://avatars.githubusercontent.com/u/21159570?v=4" width="100;" alt="MarcelloTheArcane"/>
@@ -486,15 +515,22 @@ Here are some ways of contributing to making Nhost better:
<br />
<sub><b>Quentin Decré</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/elephant3">
<img src="https://avatars.githubusercontent.com/u/48279149?v=4" width="100;" alt="elephant3"/>
<br />
<sub><b>Siarhei Lipchyk</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/altschuler">
<img src="https://avatars.githubusercontent.com/u/956928?v=4" width="100;" alt="altschuler"/>
<br />
<sub><b>Simon Altschuler</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/atapas">
<img src="https://avatars.githubusercontent.com/u/3633137?v=4" width="100;" alt="atapas"/>
@@ -529,8 +565,7 @@ Here are some ways of contributing to making Nhost better:
<br />
<sub><b>Komninos</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/meesvandongen">
<img src="https://avatars.githubusercontent.com/u/35409045?v=4" width="100;" alt="meesvandongen"/>

View File

@@ -21,7 +21,7 @@ module.exports = {
'tests/**/*.ts',
'tests/**/*.d.ts'
],
plugins: ['@typescript-eslint', 'simple-import-sort', 'cypress'],
plugins: ['@typescript-eslint', 'cypress'],
extends: ['plugin:cypress/recommended'],
parserOptions: {
ecmaVersion: 2020,
@@ -30,31 +30,6 @@ module.exports = {
rules: {
'react/prop-types': 'off',
'no-use-before-define': 'off',
'simple-import-sort/exports': 'error',
'simple-import-sort/imports': [
'error',
{
groups: [
// Node.js builtins. You could also generate this regex if you use a `.js` config.
// For example: `^(${require("module").builtinModules.join("|")})(/|$)`
[
'^(assert|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|https|module|net|os|path|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|tty|url|util|vm|zlib|freelist|v8|process|async_hooks|http2|perf_hooks)(/.*|$)'
],
// Packages
['^\\w'],
// Internal packages.
['^(@|config/)(/*|$)'],
// Side effect imports.
['^\\u0000'],
// Parent imports. Put `..` last.
['^\\.\\.(?!/?$)', '^\\.\\./?$'],
// Other relative imports. Put same-folder imports and `.` last.
['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
// Style imports.
['^.+\\.s?css$']
]
}
],
'import/no-anonymous-default-export': [
'error',
{

View File

@@ -1,3 +1,4 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
root: true,
extends: ['next', 'airbnb', 'airbnb-typescript', 'airbnb/hooks', 'prettier'],

View File

@@ -1,5 +1,124 @@
# @nhost/dashboard
## 0.7.8
### Patch Changes
- 7f251111: Use `NhostProvider` instead of `NhostReactProvider` and `NhostNextProvider`
`NhostReactProvider` and `NhostNextProvider` are now deprecated
- f4d70f88: fix(dashboard): do not break when region is nullish
- 4a9471cc: Windows Live Provider displayed link updated to match backend url
- 594488e4: fix(dashboard): do not show error when submitting Apple provider settings
- Updated dependencies [7f251111]
- @nhost/nextjs@1.10.0
- @nhost/react@0.16.0
- @nhost/react-apollo@4.10.0
## 0.7.7
### Patch Changes
- 80b604ad: fix(dashboard): use correct Hasura slug
## 0.7.6
### Patch Changes
- 2d2beb53: fix(dashboard): prevent error on GraphQL page
- ac8efcbd: chore(dashboard): deprecate old DNS name
## 0.7.5
### Patch Changes
- 132a4f4b: chore(dashboard): remove unused dependencies
- 132a4f4b: chore(deps): synchronize @types/react-dom and @types/react versions
- db57572f: fix(dashboard): correct section paddings when no env vars
- Updated dependencies [132a4f4b]
- @nhost/react@0.15.2
- @nhost/react-apollo@4.9.2
- @nhost/nextjs@1.9.3
## 0.7.4
### Patch Changes
- 34d85e54: chore(deps): update dependency critters to ^0.0.16
- 9b93cf95: chore(deps): update dependency @netlify/functions to ^0.11.0
- e0439030: chore(deps): update dependency @types/react-dom to v18.0.9
- Updated dependencies [82124329]
- @nhost/nextjs@1.9.2
## 0.7.3
### Patch Changes
- a1193da4: fix(dashboard): remove character limit from env var inputs
## 0.7.2
### Patch Changes
- 44f13f62: chore(dashboard): cleanup unused files
## 0.7.1
### Patch Changes
- e01cb2ed: chore(dashboard): change settings sidebar menu item density
## 0.7.0
### Minor Changes
- db342f45: chore(dashboard): refactor Roles and Permissions settings sections
- 8b9fa0b1: feat(dashboard): add Environment Variables page
### Patch Changes
- Updated dependencies [66b4f3d0]
- Updated dependencies [2e6923dc]
- Updated dependencies [ef117c28]
- Updated dependencies [aebb8225]
- @nhost/core@0.9.4
- @nhost/nhost-js@1.6.2
- @nhost/nextjs@1.9.1
- @nhost/react@0.15.1
- @nhost/react-apollo@4.9.1
## 0.6.0
### Minor Changes
- eef9c914: feat(dashboard): add Roles and Permissions page
## 0.5.0
### Minor Changes
- a48dd5bf: feat(dashboard): make backend port configurable
## 0.4.3
### Patch Changes
- 5de965d9: fix(dashboard): alphabetic ordering of providers
- b9087a4a: fix(dashboard): console -> dashboard terminology
- ca012d79: docs(workos): WorkOS Docs
## 0.4.2
### Patch Changes
- 89bd37bc: fix(dashboard): correct redirect URL input opacity
- Updated dependencies [4601d84e]
- Updated dependencies [843087cb]
- @nhost/react@0.15.0
- @nhost/nextjs@1.9.0
- @nhost/react-apollo@4.9.0
## 0.4.1
### Patch Changes

View File

@@ -1,4 +1,3 @@
FROM node:16-alpine AS pruner
RUN apk add --no-cache libc6-compat
RUN apk update
@@ -17,12 +16,15 @@ RUN apk update
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED 1
ENV NEXT_PUBLIC_NHOST_PLATFORM false
ENV NEXT_PUBLIC_NHOST_MIGRATIONS_URL http://localhost:9693
ENV NEXT_PUBLIC_NHOST_HASURA_URL http://localhost:9695
ENV NEXT_PUBLIC_ENV dev
ENV NEXT_PUBLIC_NHOST_PLATFORM false
RUN yarn global add pnpm
# placeholders for ports, will be replaced on runtime by entrypoint script
ENV NEXT_PUBLIC_NHOST_MIGRATIONS_PORT __NEXT_PUBLIC_NHOST_MIGRATIONS_PORT__
ENV NEXT_PUBLIC_NHOST_HASURA_PORT __NEXT_PUBLIC_NHOST_HASURA_PORT__
ENV NEXT_PUBLIC_NHOST_LOCAL_BACKEND_PORT __NEXT_PUBLIC_NHOST_LOCAL_BACKEND_PORT__
RUN yarn global add pnpm@7.17.0
COPY .gitignore .gitignore
COPY --from=pruner /app/out/json/ .
COPY --from=pruner /app/out/pnpm-*.yaml .
@@ -40,11 +42,14 @@ RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs
COPY --from=builder /app/dashboard/next.config.js .
COPY --from=builder /app/dashboard/package.json .
COPY --from=builder /app/dashboard/public ./dashboard/public
COPY --chown=nextjs:nodejs dashboard/docker-entrypoint.sh .
COPY --from=builder --chown=nextjs:nodejs /app/dashboard/next.config.js .
COPY --from=builder --chown=nextjs:nodejs /app/dashboard/package.json .
COPY --from=builder --chown=nextjs:nodejs /app/dashboard/public ./dashboard/public
COPY --from=builder --chown=nextjs:nodejs /app/dashboard/.next/standalone/app ./
COPY --from=builder --chown=nextjs:nodejs /app/dashboard/.next/static ./dashboard/.next/static
CMD node dashboard/server.js
ENTRYPOINT ["./docker-entrypoint.sh"]
CMD ["node", "dashboard/server.js"]

View File

@@ -30,31 +30,27 @@ First, you need to run the following command to start your backend locally:
cd <your_nhost_project> && nhost dev
```
Two environment variables are required to connect the Nhost Dashboard to your local backend:
You can connect the Nhost Dashboard to your locally running backend by setting the following environment variables in `.env.development.local`:
- `NEXT_PUBLIC_NHOST_PLATFORM` should be set to `false`, because otherwise the Nhost Dashboard will try to connect to the Nhost platform.
- `NEXT_PUBLIC_NHOST_MIGRATIONS_URL` should be set to `http://localhost:9693` unless Hasura is configured to run on a different port. This is the URL of Hasura's migrations endpoint.
Example:
```
```bash
NEXT_PUBLIC_ENV=dev
NEXT_PUBLIC_NHOST_PLATFORM=false
NEXT_PUBLIC_NHOST_MIGRATIONS_URL=http://localhost:9693
```
### Full list of environment variables
| Name | Description |
| ------------------------------------ | ------------------------------------------------------------------------------------------------ |
| `NEXT_PUBLIC_NHOST_PLATFORM` | This should be set to `false` to connect the Nhost Dashboard to a locally running Nhost backend. |
| `NEXT_PUBLIC_NHOST_MIGRATIONS_URL` | URL of Hasura's migrations endpoint. Used only if local development is enabled. |
| `NEXT_PUBLIC_NHOST_HASURA_URL` | URL of the Hasura Console. Used only when `NEXT_PUBLIC_ENV` is `dev`. |
| `NEXT_PUBLIC_ENV` | `dev`, `staging` or `prod`. Should be set to `dev` in most cases. |
| `NEXT_PUBLIC_NHOST_BACKEND_URL` | Backend URL. Not necessary for local development. |
| `NEXT_PUBLIC_STRIPE_PK` | Stripe public key. Not necessary for local development. |
| `NEXT_PUBLIC_GITHUB_APP_INSTALL_URL` | URL of the GitHub application. Not necessary for local development. |
| `NEXT_PUBLIC_ANALYTICS_WRITE_KEY` | Analytics key. Not necessary for local development. |
| `NEXT_PUBLIC_NHOST_BRAGI_WEBSOCKET` | URL of the Bragi websocket. Not necessary for local development. |
| Name | Description |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NEXT_PUBLIC_ENV` | `dev`, `staging` or `prod`. Should be set to `dev` in most cases. |
| `NEXT_PUBLIC_NHOST_PLATFORM` | This should be set to `false` to connect the Nhost Dashboard to a locally running Nhost backend. Setting this to `true` turns off local development. |
| `NEXT_PUBLIC_NHOST_LOCAL_MIGRATIONS_PORT` | Custom port that was passed to the CLI. Used only if local development is enabled. Default: `9693` |
| `NEXT_PUBLIC_NHOST_LOCAL_HASURA_PORT` | Custom port that was passed to the CLI. Used only if local development is enabled and `NEXT_PUBLIC_ENV` is `dev`. Default: `9695` |
| `NEXT_PUBLIC_NHOST_LOCAL_BACKEND_PORT` | Custom port that was passed to the CLI. Used only if local development is enabled. Default: `1337` |
| `NEXT_PUBLIC_NHOST_BACKEND_URL` | Backend URL. Not necessary for local development. |
| `NEXT_PUBLIC_STRIPE_PK` | Stripe public key. Not necessary for local development. |
| `NEXT_PUBLIC_GITHUB_APP_INSTALL_URL` | URL of the GitHub application. Not necessary for local development. |
| `NEXT_PUBLIC_ANALYTICS_WRITE_KEY` | Analytics key. Not necessary for local development. |
| `NEXT_PUBLIC_NHOST_BRAGI_WEBSOCKET` | URL of the Bragi websocket. Not necessary for local development. |
## ESLint Rules
@@ -67,6 +63,7 @@ NEXT_PUBLIC_NHOST_MIGRATIONS_URL=http://localhost:9693
| `import/extensions` | JS / TS files should be imported without file extensions. |
| `react/jsx-filename-extension` | JSX should only appear in `.jsx` and `.tsx` files. |
| `react/jsx-no-bind` | Further investigation must be made on the performance impact of functions directly passed as props to components. |
| `import/order` | Until we have a better auto-formatter, we disable this rule. |
| `import/no-extraneous-dependencies` | `devDependencies` should be excluded from the list of disallowed imports. |
| `curly` | By default it only enforces curly braces for multi-line blocks, but it should be enforced for single-line blocks as well. |
| `no-restricted-exports` | `export { default } from './module'` is used heavily in `@/ui/v2` which is a restricted export by default. |

15
dashboard/docker-entrypoint.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/bin/sh
set -e
# read ports from env variables or use defaults
NEXT_PUBLIC_NHOST_MIGRATIONS_PORT="${NEXT_PUBLIC_NHOST_MIGRATIONS_PORT:=9693}"
NEXT_PUBLIC_NHOST_HASURA_PORT="${NEXT_PUBLIC_NHOST_HASURA_PORT:=9695}"
NEXT_PUBLIC_NHOST_LOCAL_BACKEND_PORT="${NEXT_PUBLIC_NHOST_LOCAL_BACKEND_PORT:=1337}"
# replace placeholders
find dashboard -type f -exec sed -i "s/__NEXT_PUBLIC_NHOST_MIGRATIONS_PORT__/${NEXT_PUBLIC_NHOST_MIGRATIONS_PORT}/g" {} +
find dashboard -type f -exec sed -i "s/__NEXT_PUBLIC_NHOST_HASURA_PORT__/${NEXT_PUBLIC_NHOST_HASURA_PORT}/g" {} +
find dashboard -type f -exec sed -i "s/__NEXT_PUBLIC_NHOST_LOCAL_BACKEND_PORT__/${NEXT_PUBLIC_NHOST_LOCAL_BACKEND_PORT}/g" {} +
exec "$@"

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "0.4.1",
"version": "0.7.8",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -8,7 +8,7 @@
"build": "next build --no-lint",
"analyze": "ANALYZE=true pnpm build --no-lint",
"start": "next start",
"lint": "next lint --max-warnings 6",
"lint": "next lint --max-warnings 3",
"test": "vitest",
"codegen": "graphql-codegen --config graphql.config.yaml --errors-only",
"nhost:dev": "nhost dev -d",
@@ -25,7 +25,7 @@
"@emotion/styled": "^11.10.5",
"@fontsource/inter": "^4.5.14",
"@fontsource/roboto-mono": "^4.5.8",
"@graphiql/react": "^0.14.0",
"@graphiql/react": "^0.15.0",
"@graphiql/toolkit": "^0.8.0",
"@headlessui/react": "^1.6.5",
"@heroicons/react": "^1.0.6",
@@ -34,12 +34,14 @@
"@mui/material": "^5.10.14",
"@mui/system": "^5.10.14",
"@mui/x-date-pickers": "^5.0.8",
"@nhost/core": "^0.9.3",
"@nhost/nextjs": "^1.8.3",
"@nhost/nhost-js": "^1.6.1",
"@nhost/react": "^0.14.3",
"@nhost/react-apollo": "^4.8.3",
"@nhost/core": "workspace:*",
"@nhost/nextjs": "workspace:*",
"@nhost/nhost-js": "workspace:*",
"@nhost/react": "workspace:*",
"@nhost/react-apollo": "workspace:*",
"@segment/snippet": "^4.15.3",
"@stripe/react-stripe-js": "^1.10.0",
"@stripe/stripe-js": "^1.35.0",
"@tailwindcss/forms": "^0.5.3",
"@tanstack/react-query": "^4.16.1",
"@tanstack/react-table": "^8.5.30",
@@ -51,21 +53,20 @@
"cross-fetch": "^3.1.5",
"date-fns": "^2.29.3",
"generate-password": "^1.7.0",
"graphiql": "^2.1.0",
"graphiql": "^2.2.0",
"graphql": "^16.6.0",
"graphql-request": "^4.3.0",
"graphql-tag": "^2.12.6",
"graphql-ws": "^5.11.2",
"jsonwebtoken": "^8.5.1",
"lodash.debounce": "^4.0.8",
"next": "^12.3.1",
"next-seo": "^5.14.1",
"node-pg-format": "^1.3.5",
"pluralize": "^8.0.0",
"prettysize": "^2.0.0",
"randomstring": "^1.2.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-error-boundary": "^3.1.4",
"react-hook-form": "^7.39.5",
"react-hot-toast": "^2.4.0",
"react-is": "17.0.2",
@@ -75,7 +76,6 @@
"react-table": "^7.8.0",
"sharp": "^0.31.2",
"slugify": "^1.6.5",
"smartlook-client": "^6.0.0",
"stripe": "^10.17.0",
"tailwind-merge": "^1.8.0",
"utility-types": "^3.10.0",
@@ -90,7 +90,6 @@
"@graphql-codegen/typescript-graphql-request": "^4.5.1",
"@graphql-codegen/typescript-operations": "^2.5.1",
"@graphql-codegen/typescript-react-apollo": "^3.3.1",
"@netlify/functions": "^0.7.2",
"@next/bundle-analyzer": "^12.3.1",
"@storybook/addon-actions": "^6.5.13",
"@storybook/addon-essentials": "^6.5.13",
@@ -101,18 +100,15 @@
"@storybook/manager-webpack5": "^6.5.13",
"@storybook/react": "^6.5.13",
"@storybook/testing-library": "^0.0.13",
"@stripe/react-stripe-js": "^1.10.0",
"@stripe/stripe-js": "^1.35.0",
"@testing-library/dom": "^8.19.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"@types/express": "^4.17.14",
"@types/lodash.debounce": "^4.0.7",
"@types/node": "^18.11.9",
"@types/pluralize": "^0.0.29",
"@types/react": "18.0.25",
"@types/react-dom": "18.0.8",
"@types/react-dom": "18.0.9",
"@types/react-table": "^7.7.12",
"@types/testing-library__jest-dom": "^5.14.5",
"@types/validator": "^13.7.10",
@@ -123,10 +119,7 @@
"autoprefixer": "^10.4.13",
"babel-loader": "^8.3.0",
"babel-plugin-transform-remove-console": "^6.9.4",
"concurrently": "^6.3.0",
"critters": "^0.0.10",
"csstype": "^3.0.10",
"dotenv": "^10.0.0",
"eslint": "^8.28.0",
"eslint-config-airbnb": "19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0",
@@ -136,20 +129,17 @@
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-react": "^7.31.11",
"eslint-plugin-react-hooks": "^4.6.0",
"express": "^4.18.2",
"express-validator": "^6.14.2",
"jsdom": "^20.0.3",
"lint-staged": ">=13",
"msw": "^0.49.0",
"postcss": "^8.4.19",
"postmark": "^2.7.8",
"prettier": "^2.7.1",
"prettier-plugin-organize-imports": "^3.2.0",
"prettier-plugin-tailwindcss": "^0.1.13",
"prettier-plugin-tailwindcss": "^0.2.0",
"react-date-fns-hooks": "^0.9.4",
"react-error-boundary": "^3.1.4",
"require-from-string": "^2.0.2",
"tailwindcss": "^3.1.2",
"ts-node": "^10.9.1",
"tsconfig-paths-webpack-plugin": "^4.0.0",
"typescript": "^4.8.4",
"vite": "^3.2.4",
@@ -169,4 +159,4 @@
"last 1 safari version"
]
}
}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1 @@
<svg width="21" height="21" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10.08 0c5.578 0 10.08 4.507 10.08 10.09 0 5.584-4.502 10.09-10.08 10.09A10.072 10.072 0 0 1 0 10.09C0 4.507 4.503 0 10.08 0Zm0 2.69a7.375 7.375 0 0 0-7.392 7.4c0 4.104 3.293 7.4 7.392 7.4 4.1 0 7.392-3.296 7.392-7.4 0-4.103-3.293-7.4-7.392-7.4Zm-2.486 7.804c1.142 0 2.083.942 2.083 2.085 0 1.144-.94 2.086-2.083 2.086a2.095 2.095 0 0 1-2.083-2.086c0-1.143.94-2.085 2.083-2.085Zm4.973 0c1.142 0 2.083.942 2.083 2.085 0 1.144-.94 2.086-2.083 2.086a2.095 2.095 0 0 1-2.084-2.086c0-1.143.941-2.085 2.084-2.085Zm0-4.978c1.142 0 2.083.942 2.083 2.085 0 1.144-.94 2.086-2.083 2.086A2.095 2.095 0 0 1 10.483 7.6c0-1.143.941-2.085 2.084-2.085Zm-4.973 0c1.142 0 2.083.942 2.083 2.085 0 1.144-.94 2.086-2.083 2.086A2.095 2.095 0 0 1 5.51 7.6c0-1.143.94-2.085 2.083-2.085Z" fill="#F22F46"/></svg>

After

Width:  |  Height:  |  Size: 869 B

View File

@@ -1,107 +0,0 @@
import { useWorkspaceContext } from '@/context/workspace-context';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import { Alert } from '@/ui/Alert';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import Text from '@/ui/v2/Text';
import { discordAnnounce } from '@/utils/discordAnnounce';
import { inputErrorMessages } from '@/utils/getErrorMessage';
import { slugifyString } from '@/utils/helpers';
import { triggerToast } from '@/utils/toast';
import { updateOwnCache } from '@/utils/updateOwnCache';
import { useUpdateApplicationMutation } from '@/utils/__generated__/graphql';
import { useRouter } from 'next/router';
import React, { useState } from 'react';
export function ChangeApplicationName({ close }: any) {
const [updateAppName, { client }] = useUpdateApplicationMutation({});
const { workspaceContext } = useWorkspaceContext();
const [name, setName] = useState(workspaceContext.appName);
const [applicationError, setApplicationError] = useState<any>('');
const { currentWorkspace, currentApplication } =
useCurrentWorkspaceAndApplication();
const router = useRouter();
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const slug = slugifyString(name);
if (slug.length < 4 || slug.length > 32) {
setApplicationError('Slug should be within 4 and 32 characters.');
return;
}
if (!inputErrorMessages(name, setName, setApplicationError, 'project')) {
return;
}
try {
await updateAppName({
variables: {
appId: currentApplication.id,
app: {
name,
slug,
},
},
});
triggerToast('Project name changed');
} catch (error) {
await discordAnnounce(
`Error trying to delete project: ${currentApplication.name}`,
);
}
await updateOwnCache(client);
await router.push(`/${currentWorkspace.slug}/${slug}`);
}
return (
<div className="w-modal px-6 py-6 text-left">
<div className="flex flex-col">
<Text variant="h3" component="h2">
Change Project Name
</Text>
<form onSubmit={handleSubmit}>
<div className="mt-4 grid grid-flow-row gap-2">
<Input
label="New Project Name"
id="projectName"
value={name}
onChange={(e) => {
setName(e.target.value);
setApplicationError('');
}}
fullWidth
autoFocus
helperText={`https://app.nhost.io/${
currentWorkspace.slug
}/${slugifyString(name)}`}
/>
{applicationError && (
<Alert severity="error">{applicationError}</Alert>
)}
</div>
<div className="mt-4 grid grid-flow-row gap-2">
<Button type="submit" disabled={applicationError}>
Save
</Button>
<Button
type="button"
variant="outlined"
color="secondary"
onClick={close}
>
Close
</Button>
</div>
</form>
</div>
</div>
);
}
export default ChangeApplicationName;

View File

@@ -1,166 +0,0 @@
import { useWorkspaceContext } from '@/context/workspace-context';
import { Modal } from '@/ui/Modal';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import Text from '@/ui/v2/Text';
import { triggerToast } from '@/utils/toast';
import type { EnvironmentVariableFragment } from '@/utils/__generated__/graphql';
import {
refetchGetEnvironmentVariablesWhereQuery,
useDeleteEnvironmentVariableMutation,
useUpdateEnvironmentVariableMutation,
} from '@/utils/__generated__/graphql';
import React, { useState } from 'react';
type EnvModalProps = {
show: boolean;
close: VoidFunction;
envVar: EnvironmentVariableFragment;
};
export default function EditEnvVarModal({
show,
close,
envVar,
}: EnvModalProps) {
const { workspaceContext } = useWorkspaceContext();
const { appId } = workspaceContext;
const [updateEnvVar, { loading: updateLoading }] =
useUpdateEnvironmentVariableMutation({
refetchQueries: [
refetchGetEnvironmentVariablesWhereQuery({
where: {
appId: {
_eq: appId,
},
},
}),
],
});
const [deleteEnvVar, { loading: deleteLoading }] =
useDeleteEnvironmentVariableMutation({
refetchQueries: [
refetchGetEnvironmentVariablesWhereQuery({
where: {
appId: {
_eq: appId,
},
},
}),
],
});
const [prodValue, setProdValue] = useState(envVar.prodValue || '');
const [devValue, setDevValue] = useState(envVar.devValue || '');
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
e.preventDefault();
try {
await updateEnvVar({
variables: {
id: envVar.id,
environmentVariable: {
prodValue,
devValue,
},
},
});
} catch (error) {
close();
triggerToast('Error updating environment variable');
return;
}
triggerToast(`Environment variable ${envVar.name} updated successfully`);
close();
};
const handleDelete = async () => {
try {
await deleteEnvVar({
variables: {
id: envVar.id,
},
});
} catch (error) {
close();
triggerToast('Error deleting environment variable');
return;
}
triggerToast(`Environment variable ${envVar.name} removed successfully`);
close();
};
return (
<Modal showModal={show} close={close}>
<form onSubmit={handleSubmit}>
<div className="w-modal px-6 py-6 text-left">
<div className="grid grid-flow-row gap-1">
<Text variant="h3" component="h2">
{envVar.name}
</Text>
<Text variant="subtitle2">
The default value will be available in all environments, unless
you override it. All values are encrypted.
</Text>
<div className="my-2 grid grid-flow-row gap-2">
<Input
id="name"
label="Name"
autoFocus
disabled
autoComplete="off"
defaultValue={envVar.name}
fullWidth
hideEmptyHelperText
/>
<Input
id="prodValue"
label="Production Value"
fullWidth
placeholder="Enter a value"
value={prodValue}
onChange={(event) => setProdValue(event.target.value)}
hideEmptyHelperText
/>
<Input
id="devValue"
label="Development Value"
fullWidth
placeholder="Enter a value"
value={devValue}
onChange={(event) => setDevValue(event.target.value)}
hideEmptyHelperText
/>
</div>
<div className="grid grid-flow-row gap-2">
<Button type="submit" loading={updateLoading}>
Save
</Button>
<Button
variant="outlined"
color="error"
loading={deleteLoading}
onClick={handleDelete}
>
Delete
</Button>
<Button onClick={close} variant="outlined" color="secondary">
Close
</Button>
</div>
</div>
</div>
</form>
</Modal>
);
}

View File

@@ -1,327 +0,0 @@
import ErrorBoundaryFallback from '@/components/common/ErrorBoundaryFallback';
import TwilioIcon from '@/components/icons/TwilioIcon';
import {
useGetSmsSettingsQuery,
useUpdateAppMutation,
} from '@/generated/graphql';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import { Button, Input } from '@/ui';
import { Alert } from '@/ui/Alert';
import DelayedLoading from '@/ui/DelayedLoading';
import { Text } from '@/ui/Text';
import { showLoadingToast, triggerToast } from '@/utils/toast';
import { useApolloClient } from '@apollo/client';
import { useEffect } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import {
Controller,
FormProvider,
useForm,
useFormContext,
} from 'react-hook-form';
import toast from 'react-hot-toast';
export function EditSMSSettingsForm({
close,
isAlreadyEnabled,
}: {
close: () => void;
isAlreadyEnabled: boolean;
}) {
const { currentApplication } = useCurrentWorkspaceAndApplication();
const {
handleSubmit,
watch,
formState: { isSubmitting, errors },
} = useFormContext<EditSMSSettingsFormData>();
const { control } = useFormContext<EditSMSSettingsFormData>();
const [updateApp] = useUpdateAppMutation();
let toastId: string;
const client = useApolloClient();
const isNotCompleted =
!watch('accountSID') ||
!watch('authToken') ||
!watch('messagingServiceSID');
const handleEditSMSSettings = async (data: EditSMSSettingsFormData) => {
try {
toastId = showLoadingToast('Updating SMS settings...');
await updateApp({
variables: {
id: currentApplication.id,
app: {
authSmsTwilioAccountSid: data.accountSID,
authSmsTwilioAuthToken: data.authToken,
authSmsTwilioMessagingServiceId: data.messagingServiceSID,
authSmsPasswordlessEnabled: true,
},
},
});
await client.refetchQueries({ include: ['getSMSSettings'] });
toast.remove(toastId);
triggerToast('SMS settings updated successfully.');
close();
} catch (error) {
if (toastId) {
toast.remove(toastId);
}
throw error;
}
};
return (
<form
onSubmit={handleSubmit(handleEditSMSSettings)}
className="flex w-full flex-col pb-1"
autoComplete="off"
>
{errors &&
Object.entries(errors).map(([type, error]) => (
<Alert key={type} className="mb-4" severity="error">
{error.message}
</Alert>
))}
<div>
<div className="flex flex-row place-content-between border-t border-b px-2 py-2.5">
<div className="flex w-full flex-row">
<Text
color="greyscaleDark"
className="self-center font-medium"
size="normal"
>
Account SID
</Text>
</div>
<div className="flex w-full">
<Controller
name="accountSID"
control={control}
rules={{
required: true,
pattern: {
value: /^[a-zA-Z0-9-_]+$/,
message:
'The Account SID must contain only letters, hyphens, and numbers.',
},
}}
render={({ field }) => (
<Input
{...field}
id="accountSID"
placeholder="Account SID"
required
value={field.value || ''}
onChange={(value: string) => {
if (value && !/^[a-zA-Z0-9-_]+$/gi.test(value)) {
// prevent the user from entering invalid characters
return;
}
field.onChange(value);
}}
/>
)}
/>
</div>
</div>
<div className="flex flex-row place-content-between border-b px-2 py-2.5">
<div className="flex w-full flex-row">
<Text
color="greyscaleDark"
className="self-center font-medium"
size="normal"
>
Auth Token
</Text>
</div>
<div className="flex w-full">
<Controller
name="authToken"
control={control}
rules={{
required: true,
pattern: {
value: /^[a-zA-Z0-9-_]+$/,
message:
'The Auth Token must contain only letters, hyphens, and numbers.',
},
}}
render={({ field }) => (
<Input
{...field}
id="authToken"
placeholder="Auth Token"
required
value={field.value || ''}
onChange={(value: string) => {
if (value && !/^[a-zA-Z0-9-_/.]+$/gi.test(value)) {
return;
}
field.onChange(value);
}}
/>
)}
/>
</div>
</div>
<div className="flex flex-row place-content-between border-b px-2 py-2.5">
<div className="flex w-full flex-row">
<Text
color="greyscaleDark"
className="self-center font-medium"
size="normal"
>
Messaging Service SID
</Text>
</div>
<div className="flex w-full">
<Controller
name="messagingServiceSID"
control={control}
rules={{
required: true,
pattern: {
value: /^[+a-zA-Z0-9-_/.]+$/,
message:
'The Messaging Service SID must either be a valid phone number or contain only letters, hyphens, and numbers.',
},
}}
render={({ field }) => (
<Input
{...field}
id="messagingServiceSID"
required
placeholder="Messaging Service SID"
value={field.value || ''}
onChange={(value: string) => {
if (value && !/^[+a-zA-Z0-9-_/.]+$/gi.test(value)) {
return;
}
field.onChange(value);
}}
/>
)}
/>
</div>
</div>
</div>
<div className="flex flex-col">
<Button
variant="primary"
type="submit"
className="text-grayscaleDark mt-2 border text-sm+ font-normal"
loading={isSubmitting}
disabled={isSubmitting || isNotCompleted}
>
{isAlreadyEnabled ? 'Update SMS Settings' : 'Enable SMS'}
</Button>
</div>
</form>
);
}
export function EditSMSSettingsModal({
close,
isAlreadyEnabled,
}: {
close: () => void;
isAlreadyEnabled: boolean;
}) {
return (
<div className="w-modal px-6 py-4 text-left">
<div className="flex flex-col">
<div className="mx-auto mt-2.5">
<TwilioIcon className=" text-greyscaleDark" />
</div>
<Text
variant="subHeading"
color="greyscaleDark"
size="large"
className="mt-3 text-center"
>
Set up Twilio SMS Service
</Text>
<Text
variant="body"
color="greyscaleDark"
size="small"
className="mt-0.5 mb-6 text-center font-normal"
>
SMS messages are sent through Twilio. Create an account and a
messaging service at https://console.twilio.com.
</Text>
<div>
<ErrorBoundary fallbackRender={ErrorBoundaryFallback}>
<EditSMSSettingsForm
close={close}
isAlreadyEnabled={isAlreadyEnabled}
/>
</ErrorBoundary>
</div>
</div>
</div>
);
}
export interface EditSMSSettingsProps {
close: () => void;
}
export interface EditSMSSettingsFormData {
accountSID: string;
authToken: string;
messagingServiceSID: string;
}
export function EditSMSSettings({ close }: EditSMSSettingsProps) {
const { currentApplication } = useCurrentWorkspaceAndApplication();
const form = useForm<EditSMSSettingsFormData>({
reValidateMode: 'onSubmit',
defaultValues: {
accountSID: '',
authToken: '',
messagingServiceSID: '',
},
});
const { data, loading, error } = useGetSmsSettingsQuery({
variables: {
id: currentApplication.id,
},
});
useEffect(() => {
if (!data) {
return;
}
form.setValue('accountSID', data.app.authSmsTwilioAccountSid);
form.setValue('authToken', data.app.authSmsTwilioAuthToken);
form.setValue(
'messagingServiceSID',
data.app.authSmsTwilioMessagingServiceId,
);
}, [data, form]);
if (loading) {
return <DelayedLoading delay={500} />;
}
if (error) {
throw error;
}
return (
<FormProvider {...form}>
<EditSMSSettingsModal
close={close}
isAlreadyEnabled={data.app.authSmsPasswordlessEnabled}
/>
</FormProvider>
);
}

View File

@@ -1,134 +0,0 @@
import {
useGetSmsSettingsQuery,
useUpdateAppMutation,
} from '@/generated/graphql';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import { Text, Toggle } from '@/ui';
import DelayedLoading from '@/ui/DelayedLoading';
import { showLoadingToast, triggerToast } from '@/utils/toast';
import { useApolloClient } from '@apollo/client';
import clsx from 'clsx';
import Image from 'next/image';
import { useEffect, useState } from 'react';
import toast from 'react-hot-toast';
export function EnableSMSSignIn({ openSMSSettingsModal }: any) {
const { currentApplication } = useCurrentWorkspaceAndApplication();
const [updateApp] = useUpdateAppMutation();
const { data, loading, error } = useGetSmsSettingsQuery({
variables: {
id: currentApplication.id,
},
});
const [enableSMSLoginMethod, setEnableSMSLoginMethod] = useState(false);
const client = useApolloClient();
let toastId: string;
useEffect(() => {
if (!data) {
return;
}
setEnableSMSLoginMethod(data.app.authSmsPasswordlessEnabled);
}, [data]);
if (loading) {
return <DelayedLoading delay={500} />;
}
if (error) {
throw error;
}
const handleDisable = async () => {
try {
toastId = showLoadingToast('Disabling SMS login...');
await updateApp({
variables: {
id: currentApplication.id,
app: {
authSmsPasswordlessEnabled: false,
},
},
});
setEnableSMSLoginMethod(false);
await client.refetchQueries({ include: ['getSMSSettings'] });
toast.remove(toastId);
triggerToast('Passwordless SMS disabled.');
} catch (updateError) {
if (toastId) {
toast.remove(toastId);
}
throw updateError;
}
};
return (
<div className="mt-20">
<div className="mx-auto font-display">
<div className="flex flex-col place-content-between">
<div className="">
<div className="flex flex-row place-content-between">
<div className="relative flex flex-row">
<Image
src="/assets/SMS.svg"
width={24}
height={24}
alt="Phone Number (SMS)"
/>
<Text
variant="body"
size="large"
className="ml-2 font-medium"
color="greyscaleDark"
>
Phone Number (SMS)
</Text>
<div>
<button
type="button"
className={clsx(
'ml-2 align-bottom text-sm- font-medium text-blue transition-opacity duration-300',
!enableSMSLoginMethod && 'invisible opacity-0',
enableSMSLoginMethod && 'opacity-100',
)}
onClick={() => openSMSSettingsModal()}
>
Edit SMS settings
</button>
</div>
</div>
<div className="flex flex-row">
<Toggle
checked={enableSMSLoginMethod}
onChange={async () => {
if (enableSMSLoginMethod) {
await handleDisable();
} else {
openSMSSettingsModal();
}
}}
/>
</div>
</div>
<div className="mt-3 flex flex-row self-center align-middle">
<Text
variant="body"
size="normal"
color="greyscaleDark"
className="self-center"
>
Sign in users with Phone Number (SMS).
</Text>
</div>
</div>
</div>
</div>
</div>
);
}
export default EnableSMSSignIn;

View File

@@ -5,7 +5,8 @@ import Button from '@/ui/v2/Button';
import ArrowSquareOutIcon from '@/ui/v2/icons/ArrowSquareOutIcon';
import Link from '@/ui/v2/Link';
import Text from '@/ui/v2/Text';
import { generateRemoteAppUrl } from '@/utils/helpers';
import { LOCAL_HASURA_URL } from '@/utils/env';
import { generateAppServiceUrl } from '@/utils/helpers';
import Image from 'next/image';
interface HasuraDataProps {
@@ -24,8 +25,12 @@ export function HasuraData({ close }: HasuraDataProps) {
const hasuraUrl =
process.env.NEXT_PUBLIC_ENV === 'dev'
? process.env.NEXT_PUBLIC_NHOST_HASURA_URL || 'http://localhost:9695'
: generateRemoteAppUrl(currentApplication.subdomain);
? LOCAL_HASURA_URL
: `${generateAppServiceUrl(
currentApplication?.subdomain,
currentApplication?.region.awsName,
'hasura',
)}/console`;
return (
<div className="mx-auto w-full max-w-md px-6 py-4 text-left">
@@ -59,7 +64,7 @@ export function HasuraData({ close }: HasuraDataProps) {
<div className="mt-6 grid grid-flow-row gap-2">
<Link
href={`${hasuraUrl}/console`}
href={hasuraUrl}
target="_blank"
rel="noreferrer noopener"
className="grid grid-flow-col items-center justify-center gap-1 rounded-[4px] bg-btn p-2 text-sm+ font-medium text-white hover:ring-2 motion-safe:transition-all"

View File

@@ -1,224 +0,0 @@
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import { useSubmitState } from '@/hooks/useSubmitState';
import { Alert } from '@/ui/Alert';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import Text from '@/ui/v2/Text';
import { triggerToast } from '@/utils/toast';
import {
refetchGetAppInjectedVariablesQuery,
useUpdateApplicationMutation,
} from '@/utils/__generated__/graphql';
import { useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
export interface EditCustomUserJWTTokenData {
customUserJWTToken: string;
}
export type JWTSecretModalState = 'SHOW' | 'EDIT';
export interface JWTSecretModalProps {
close: () => void;
data?: any;
jwtSecret: string;
initialModalState?: JWTSecretModalState;
}
export function EditJWTSecretModal({ close }: any) {
const { currentApplication } = useCurrentWorkspaceAndApplication();
const { submitState, setSubmitState } = useSubmitState();
const [updateApplication] = useUpdateApplicationMutation({
refetchQueries: [
refetchGetAppInjectedVariablesQuery({ id: currentApplication.id }),
],
});
const {
handleSubmit,
control,
formState: { isSubmitting },
} = useForm<EditCustomUserJWTTokenData>({
reValidateMode: 'onSubmit',
defaultValues: {
customUserJWTToken: '',
},
});
const handleEditJWTSecret = async (data: EditCustomUserJWTTokenData) => {
setSubmitState({
error: null,
loading: false,
fieldsWithError: [],
});
try {
JSON.parse(data.customUserJWTToken);
} catch (error) {
setSubmitState({
error: new Error('The custom JWT token should be valid json.'),
loading: false,
fieldsWithError: [],
});
return;
}
try {
await updateApplication({
variables: {
appId: currentApplication.id,
app: {
hasuraGraphqlJwtSecret: data.customUserJWTToken,
},
},
});
triggerToast(
`Successfully added custom JWT token to ${currentApplication.name}.`,
);
close();
} catch (error) {
triggerToast(
`Error adding custom JWT token to ${currentApplication.name}`,
);
setSubmitState({ error, loading: false, fieldsWithError: [] });
}
};
return (
<form
className="w-modal px-6 py-4"
onSubmit={handleSubmit(handleEditJWTSecret)}
>
<div className="grid grid-flow-row gap-2">
<div className="grid grid-flow-row text-left">
<Text variant="h3" component="h2">
Add Custom JWT Secret
</Text>
<Text variant="subtitle2">
You can add your custom JWT key here. Hasura will use this key to
validate the identity of your users.
</Text>
</div>
{submitState.error && (
<Alert severity="error">{submitState.error.message}</Alert>
)}
<Controller
name="customUserJWTToken"
control={control}
rules={{
required: true,
}}
render={({ field }) => (
<Input
{...field}
placeholder="Paste your custom JWT token here..."
componentsProps={{
inputRoot: {
className: 'font-mono bg-header',
},
}}
aria-label="Custom JWT token"
type="text"
value={field.value}
onBlur={() =>
setSubmitState({
error: null,
loading: false,
fieldsWithError: [],
})
}
multiline
rows={6}
fullWidth
hideEmptyHelperText
/>
)}
/>
<div className="grid grid-flow-row gap-2">
<Button type="submit" loading={isSubmitting}>
Save Changes
</Button>
<Button variant="outlined" color="secondary" onClick={close}>
Cancel
</Button>
</div>
</div>
</form>
);
}
export function ShowJWTTokenModal({ JWTKey, editJWTSecret }: any) {
return (
<div className="w-modal px-6 py-4">
<div className="grid grid-flow-row gap-2">
<div className="grid grid-flow-row text-left">
<Text variant="h3" component="h2">
Auth JWT Secret
</Text>
<Text variant="subtitle2">
This is the key used for generating JWTs. It&apos;s HMAC-SHA-based
and the same as configured in Hasura.
</Text>
</div>
<div>
<Input
defaultValue={JWTKey}
multiline
disabled
fullWidth
hideEmptyHelperText
rows={6}
componentsProps={{
inputRoot: { className: 'font-mono' },
}}
/>
</div>
<div className="mx-auto max-w-sm text-center">
<Text variant="subtitle2">
Already using a third party auth service? <br />
<button
type="button"
className="mt-0.5 ml-0.5 text-xs font-medium text-blue"
onClick={() => {
editJWTSecret();
}}
>
Add your custom JWT token
</button>
</Text>
</div>
</div>
</div>
);
}
export function JWTSecretModal({
close,
data,
jwtSecret,
initialModalState,
}: any) {
const [jwtSecretModalState, setJwtSecretModalState] =
useState<JWTSecretModalState>(initialModalState || 'SHOW');
const editJWTSecret = () => {
setJwtSecretModalState('EDIT');
};
if (jwtSecretModalState === 'EDIT') {
return <EditJWTSecretModal close={close} />;
}
return (
<ShowJWTTokenModal
JWTKey={jwtSecret || data}
editJWTSecret={editJWTSecret}
/>
);
}

View File

@@ -1,28 +0,0 @@
import { Avatar } from '@/ui/Avatar';
import { Text } from '@/ui/Text';
import { nhost } from '@/utils/nhost';
import Image from 'next/image';
export function SelectedWorkspaceOnNewApp({ current }: any) {
const user = nhost.auth.getUser();
return (
<div className="flex flex-row space-x-2 self-center">
{current.name === 'Default Workspace' ? (
<Avatar
className="h-5 w-5 self-center rounded-full"
name={user?.displayName}
avatarUrl={user?.avatarUrl}
/>
) : (
<div className="h-5 w-5 overflow-hidden rounded-md">
<Image src="/logos/new.svg" alt="Nhost Logo" width={20} height={20} />
</div>
)}
<Text size="small" color="greyscaleDark" className="font-normal">
{current.name}
</Text>
</div>
);
}
export default SelectedWorkspaceOnNewApp;

View File

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

View File

@@ -1,71 +0,0 @@
import type { SelectorOption } from '@/ui/Selector';
import Selector from '@/ui/Selector';
import { Text } from '@/ui/Text';
import { Toggle } from '@/ui/Toggle';
import clsx from 'clsx';
export interface PermissionSettingsProps {
text: string;
desc?: string;
toggle?: boolean;
onChange?: any;
checked?: boolean;
options?: any;
value?: SelectorOption;
} // @TODO: Fix alt attribute on images.
// @FIX: Double border
export function PermissionSetting({
text,
desc,
toggle,
checked = false,
onChange,
options,
value,
}: PermissionSettingsProps) {
return (
<div className="flex flex-row place-content-between py-2">
<div
className={clsx(
'flex flex-col space-y-1 self-center px-0.5',
!desc && 'py-3.5',
desc && 'py-2',
)}
>
<Text
variant="body"
size="normal"
className="font-medium"
color="greyscaleDark"
>
{text}
</Text>
{desc && (
<Text
variant="body"
size="tiny"
className="font-normal"
color="greyscaleDark"
>
{desc}
</Text>
)}
</div>
{toggle ? (
<div className="flex flex-row">
<Toggle checked={checked} onChange={onChange} />
</div>
) : (
<div className="flex flex-row self-center">
<Selector
width="w-28"
options={options}
onChange={onChange}
value={value}
/>
</div>
)}
</div>
);
}

View File

@@ -1,58 +0,0 @@
import type { Provider as ProviderType } from '@/types/providers';
import Status, { StatusEnum } from '@/ui/Status';
import { Text } from '@/ui/Text';
import { ChevronRightIcon } from '@heroicons/react/solid';
import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/router';
interface ProviderProps {
provider: ProviderType;
enabled: boolean;
}
export function Provider({ provider, enabled }: ProviderProps) {
const { name, logo } = provider;
const {
query: { workspaceSlug, appSlug },
} = useRouter();
const nameLowerCase = name.toLowerCase();
return (
<Link
href={`/${workspaceSlug}/${appSlug}/settings/sign-in-methods/${nameLowerCase}`}
passHref
>
<a
href={`${workspaceSlug}/${appSlug}/settings/sign-in-methods/${nameLowerCase}`}
className="flex cursor-pointer flex-row place-content-between border-t py-2.5"
>
<div className="grid grid-flow-col items-center gap-2">
<div className="h-6 w-6">
<Image
src={logo}
alt={`Logo of ${name}`}
width={24}
height={24}
layout="responsive"
/>
</div>
<Text className="font-medium" color="greyscaleDark" size="normal">
{name}
</Text>
</div>
<div className="flex flex-row">
{enabled ? (
<Status status={StatusEnum.Live}>Enabled</Status>
) : (
<Status status={StatusEnum.Closed}>Disabled</Status>
)}
<ChevronRightIcon className="ml-2 h-4 w-4 cursor-pointer self-center" />
</div>
</a>
</Link>
);
}
export default Provider;

View File

@@ -1,183 +0,0 @@
import { CreateUserRoleModal } from '@/components/applications/users/roles/CreateRoleModal';
import { EditUserRoleModal } from '@/components/applications/users/roles/EditUserRoleModal';
import Lock from '@/components/icons/Lock';
import type { GetRolesQuery } from '@/generated/graphql';
import { Modal } from '@/ui';
import { Text } from '@/ui/Text';
import { ChevronRightIcon } from '@heroicons/react/solid';
import clsx from 'clsx';
import type { Dispatch, MouseEvent, MouseEventHandler } from 'react';
import { useReducer } from 'react';
function RolesTableHead() {
return (
<thead>
<tr>
<th className="w-64 py-3 text-left font-medium text-base">
<Text className="text-xs font-bold text-greyscaleDark">Role</Text>
</th>
</tr>
</thead>
);
}
interface UserRoleProps {
role: string;
isSystemRole: boolean;
onClick?: MouseEventHandler<HTMLTableRowElement>;
}
function UserRole({ role, isSystemRole, onClick }: UserRoleProps) {
return (
<tr
className={clsx(isSystemRole ? 'cursor-not-allowed' : 'cursor-pointer')}
onClick={onClick}
>
<td className="py-2">
<Text
size="normal"
className={clsx(
isSystemRole ? 'text-greyscaleGrey' : 'text-greyscaleDark',
'pl-1 font-medium',
)}
>
{role}
</Text>
</td>
<td className="text-right">
{isSystemRole ? (
<div className="inline-flex pr-1">
<Text
size="tiny"
className=" font-mono text-xs font-medium uppercase tracking-wide text-greyscaleGrey"
>
System Role
</Text>
<Lock className="ml-1 h-5 w-5 text-greyscaleGrey" />
</div>
) : (
<div className="inline-flex self-center py-2 pr-1.5">
<ChevronRightIcon className="h-4.5 w-4.5 text-greyscaleDark" />
</div>
)}
</td>
</tr>
);
}
export type UserRoleDetails = {
name: string;
isSystemRole: boolean;
};
export const getUserRoles = (data): UserRoleDetails[] => {
const authUserDefaultAllowedRoles =
data.app.authUserDefaultAllowedRoles.split(',');
return authUserDefaultAllowedRoles.map((role: string) => ({
name: role,
isSystemRole: ['user', 'me'].includes(role),
}));
};
type ModalState = {
visible: boolean;
type: 'create' | 'edit';
payload: UserRoleDetails;
};
type ModalAction = {
type: 'OPEN_CREATE_MODAL' | 'OPEN_EDIT_MODAL' | 'CLOSE_MODAL';
payload?: UserRoleDetails;
};
function modalStateReducer(state: ModalState, action: ModalAction): ModalState {
switch (action.type) {
case 'OPEN_CREATE_MODAL':
return { ...state, visible: true, type: 'create', payload: null };
case 'OPEN_EDIT_MODAL':
return { ...state, visible: true, type: 'edit', payload: action.payload };
case 'CLOSE_MODAL':
return { ...state, visible: false };
default:
throw new Error(`Action type ${action.type} is not supported.`);
}
}
function AddNewUserRole({ dispatch }: { dispatch: Dispatch<ModalAction> }) {
return (
<tr className="cursor-pointer border-y-1 border-solid border-gray-300">
<td className="p-2">
<button
type="button"
onClick={() => dispatch({ type: 'OPEN_CREATE_MODAL' })}
>
<Text className="text-sm+ font-medium text-blue">
Create New Role
</Text>
</button>
</td>
<td />
</tr>
);
}
function RolesTableBody({ data }: { data: GetRolesQuery }) {
const userRoles = getUserRoles(data);
const [
{ visible: modalVisible, type: modalType, payload: modalPayload },
dispatch,
] = useReducer(modalStateReducer, {
visible: false,
type: null,
payload: null,
});
function handleRoleEdit(event: MouseEvent<HTMLTableRowElement>, role: any) {
dispatch({ type: 'OPEN_EDIT_MODAL', payload: role });
}
return (
<>
<Modal
showModal={modalVisible}
close={() => dispatch({ type: 'CLOSE_MODAL' })}
>
{modalType === 'create' ? (
<CreateUserRoleModal
onClose={() => dispatch({ type: 'CLOSE_MODAL' })}
/>
) : (
<EditUserRoleModal
onClose={() => dispatch({ type: 'CLOSE_MODAL' })}
payload={modalPayload}
/>
)}
</Modal>
<tbody className="divide-y-1 border-t-1 border-b-1 border-solid border-gray-300 ">
{userRoles.map((role) => (
<UserRole
key={role.name}
role={role.name}
isSystemRole={role.isSystemRole}
onClick={
role.isSystemRole
? undefined
: (event) => handleRoleEdit(event, role)
}
/>
))}
<AddNewUserRole dispatch={dispatch} />
</tbody>
</>
);
}
export function RolesTable({ data }: { data: GetRolesQuery }) {
return (
<table className="w-full table-fixed overflow-x-auto">
<RolesTableHead />
<RolesTableBody data={data} />
</table>
);
}

View File

@@ -1,73 +0,0 @@
import type { TextProps } from '@/ui/Text';
import { Text } from '@/ui/Text';
import type { PropsWithChildren, ReactNode } from 'react';
import { twMerge } from 'tailwind-merge';
export interface SettingsSectionProps {
/**
* Title of this section.
*/
title: ReactNode;
/**
* Props to be passed to the title component.
*/
titleProps?: TextProps;
/**
* Props to be passed to the wrapper component.
*/
wrapperProps?: TextProps;
/**
* Description of this section.
*/
desc?: ReactNode;
/**
* Props to be passed to the description component.
*/
descriptionProps?: TextProps;
}
export function SettingsSection({
children,
title,
titleProps,
descriptionProps,
desc,
wrapperProps,
}: PropsWithChildren<SettingsSectionProps>) {
const { className: titleClassName, ...restTitleProps } = titleProps || {};
const { className: wrapperClassName } = wrapperProps || {};
const { className: descriptionClassName, ...restDescriptionProps } =
descriptionProps || {};
return (
<div className={twMerge('mt-10', wrapperClassName)}>
<div className="mx-auto font-display">
<div className="flex flex-col place-content-between">
<div>
<Text
size="large"
variant="heading"
className={twMerge('mb-1.5 font-medium', titleClassName)}
color="greyscaleDark"
{...restTitleProps}
>
{title}
</Text>
{desc && (
<Text
variant="body"
size="normal"
color="greyscaleDark"
className={twMerge('mb-3 font-normal', descriptionClassName)}
{...restDescriptionProps}
>
{desc}
</Text>
)}
</div>
</div>
{children}
</div>
</div>
);
}

View File

@@ -1,110 +0,0 @@
import { useWorkspaceContext } from '@/context/workspace-context';
import useCustomClaims from '@/hooks/useCustomClaims';
import type { CustomClaim } from '@/types/application';
import { Alert } from '@/ui/Alert';
import { triggerToast } from '@/utils/toast';
import {
refetchGetAppCustomClaimsQuery,
useUpdateAppMutation,
} from '@/utils/__generated__/graphql';
import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import type {
CreatePermissionVariableBaseFormData,
CreatePermissionVariableModalBaseProps,
} from './CreatePermissionVariableModalBase';
import CreatePermissionVariableModalBase from './CreatePermissionVariableModalBase';
export type CreatePermissionVariableFormData =
CreatePermissionVariableBaseFormData;
export type CreatePermissionVariableModalProps = Pick<
CreatePermissionVariableModalBaseProps,
'onClose'
>;
export default function CreatePermissionVariableModal({
onClose,
}: CreatePermissionVariableModalProps) {
const [error, setError] = useState<Error>();
const form = useForm<CreatePermissionVariableFormData>({
reValidateMode: 'onSubmit',
});
const {
workspaceContext: { appId },
} = useWorkspaceContext();
const { data: customClaims } = useCustomClaims({ appId });
const [updateApp] = useUpdateAppMutation({
refetchQueries: [refetchGetAppCustomClaimsQuery({ id: appId })],
});
async function handleSubmit(permissionVariable: CustomClaim) {
setError(undefined);
try {
if (
customClaims.some(
(claim) =>
claim.key.toLowerCase() === permissionVariable.key.toLowerCase(),
)
) {
throw new Error(
'Permission variable with this field name already exists.',
);
}
await updateApp({
variables: {
id: appId,
app: {
authJwtCustomClaims: [...customClaims, permissionVariable]
.filter((claim) => !claim.system)
.reduce(
(authJwtCustomClaims, claim) => ({
...authJwtCustomClaims,
[claim.key]: claim.value,
}),
{},
),
},
},
});
triggerToast('Permission variable created');
if (!onClose) {
return;
}
onClose();
} catch (updateError) {
if (updateError instanceof Error) {
setError(updateError);
} else {
setError(new Error(updateError));
}
}
}
return (
<FormProvider {...form}>
<CreatePermissionVariableModalBase
title="Create Permission Variable"
type="create"
onSubmit={handleSubmit}
onClose={onClose}
errorComponent={
error && (
<Alert className="mt-4" severity="error">
{error.message}
</Alert>
)
}
/>
</FormProvider>
);
}

View File

@@ -1,187 +0,0 @@
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import Text from '@/ui/v2/Text';
import type { ChangeEvent, MouseEventHandler, ReactNode } from 'react';
import type { SubmitHandler } from 'react-hook-form';
import { useFormContext } from 'react-hook-form';
export interface CreatePermissionVariableBaseFormData {
key: string;
value: string;
}
export interface CreateModalBaseProps<T> {
/**
* Title of this modal.
*/
title: string;
/**
* Type of this modal.
*/
type?: 'create' | 'edit';
/**
* Callback to be called when the modal is closed.
*/
onClose?: VoidFunction;
/**
* Callback to be called when remove button is clicked.
*/
onRemove?: MouseEventHandler<HTMLButtonElement>;
/**
* Callback to be called when the form is submitted.
*/
onSubmit: SubmitHandler<T>;
/**
* Error to be displayed.
*/
errorComponent?: ReactNode;
}
export type CreatePermissionVariableModalBaseProps =
CreateModalBaseProps<CreatePermissionVariableBaseFormData>;
export default function CreatePermissionVariableModalBase({
title,
type,
onClose,
onRemove,
onSubmit,
errorComponent,
}: CreatePermissionVariableModalBaseProps) {
const {
handleSubmit,
watch,
register,
formState: { isSubmitting, errors },
} = useFormContext<CreatePermissionVariableBaseFormData>();
const keyHandlers = register('key', {
required: true,
pattern: {
value: /^[a-zA-Z-]+$/i,
message: 'Must contain only letters and hyphens',
},
});
const valueHandlers = register('value', {
required: true,
pattern: {
value: /^[a-zA-Z0-9._[\]]+$/i,
message: 'Must contain only letters, dots, brackets, and underscores',
},
});
const isComplete = !!watch('key') && !!watch('value');
return (
<div className="w-modal p-6 text-left">
<div className="grid w-full grid-flow-col items-center justify-between">
<Text variant="h3" component="h2">
{title}
</Text>
{type === 'edit' && onRemove && (
<Button variant="borderless" color="error" onClick={onRemove}>
Remove
</Button>
)}
</div>
<Text className="mt-2 text-sm+ text-greyscaleDark">
Enter the field name and the path you want to use in this permission
variable.
</Text>
{errorComponent}
<form onSubmit={handleSubmit(onSubmit)} autoComplete="off">
<div className="my-4 grid grid-flow-row divide-y-1 divide-solid divide-gray-200 border-y border-gray-200">
<Input
{...keyHandlers}
value={watch('key')}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
if (
event.target.value &&
!/^[a-zA-Z-]+$/gi.test(event.target.value)
) {
// prevent the user from entering invalid characters
return;
}
keyHandlers.onChange(event);
}}
id="key"
variant="inline"
inlineInputProportion="66%"
label="Field name"
fullWidth
startAdornment={
<Text className="min-w-[73px] text-sm+ text-greyscaleGrey">
X-Hasura-
</Text>
}
componentsProps={{
inputWrapper: { className: 'my-1' },
input: {
className: 'border-transparent focus-within:border-solid pl-2',
},
inputRoot: { className: '!pl-[1px]' },
}}
autoFocus
error={!!errors?.key?.message}
helperText={errors?.key?.message}
hideEmptyHelperText
/>
<Input
{...valueHandlers}
value={watch('value')}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
if (
event.target.value &&
!/^[a-zA-Z-]+$/gi.test(event.target.value)
) {
// prevent the user from entering invalid characters
return;
}
valueHandlers.onChange(event);
}}
id="value"
variant="inline"
inlineInputProportion="66%"
label="Path"
fullWidth
startAdornment={
<Text className="text-sm+ text-greyscaleGrey">user.</Text>
}
componentsProps={{
inputWrapper: { className: 'my-1' },
input: {
className: 'border-transparent focus-within:border-solid pl-2',
},
inputRoot: { className: '!pl-[1px]' },
}}
error={!!errors?.value?.message}
helperText={errors?.value?.message}
hideEmptyHelperText
/>
</div>
<div className="grid gap-2">
<Button
type="submit"
loading={isSubmitting}
disabled={isSubmitting || !isComplete}
>
{type === 'create' ? 'Create Permission Variable' : 'Save Changes'}
</Button>
<Button variant="outlined" color="secondary" onClick={onClose}>
Close
</Button>
</div>
</form>
</div>
);
}

View File

@@ -1,209 +0,0 @@
import { useWorkspaceContext } from '@/context/workspace-context';
import useCustomClaims from '@/hooks/useCustomClaims';
import type { CustomClaim } from '@/types/application';
import { Alert } from '@/ui/Alert';
import { Modal } from '@/ui/Modal';
import Button from '@/ui/v2/Button';
import Text from '@/ui/v2/Text';
import { triggerToast } from '@/utils/toast';
import {
refetchGetAppCustomClaimsQuery,
useUpdateAppMutation,
} from '@/utils/__generated__/graphql';
import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import type {
CreatePermissionVariableBaseFormData,
CreatePermissionVariableModalBaseProps,
} from './CreatePermissionVariableModalBase';
import CreatePermissionVariableModalBase from './CreatePermissionVariableModalBase';
export type EditPermissionVariableFormData =
CreatePermissionVariableBaseFormData;
export type EditPermissionVariableModalProps = Pick<
CreatePermissionVariableModalBaseProps,
'onClose'
> & {
/**
* The permission variable to edit.
*/
payload: CustomClaim;
};
export default function EditPermissionVariableModal({
payload: originalCustomClaim,
...props
}: EditPermissionVariableModalProps) {
const [error, setError] = useState<Error>();
const [showRemoveModal, setShowRemoveModal] = useState(false);
const form = useForm<EditPermissionVariableFormData>({
reValidateMode: 'onSubmit',
defaultValues: {
key: originalCustomClaim.key || '',
value: originalCustomClaim.value || '',
},
});
const {
workspaceContext: { appId },
} = useWorkspaceContext();
const { data: customClaims } = useCustomClaims({ appId });
const [updateApp] = useUpdateAppMutation({
refetchQueries: [refetchGetAppCustomClaimsQuery({ id: appId })],
});
async function handleSubmit(permissionVariable: CustomClaim) {
setError(undefined);
try {
if (
originalCustomClaim.key.toLowerCase() !==
permissionVariable.key.toLowerCase() &&
customClaims.some(
(claim) =>
claim.key.toLowerCase() === permissionVariable.key.toLowerCase(),
)
) {
throw new Error(
'Permission variable with this field name already exists.',
);
}
// we need to preserve the original position of the permission variable
const currentIndex = customClaims.findIndex(
(claim) =>
claim.key.toLowerCase() === originalCustomClaim.key.toLowerCase(),
);
await updateApp({
variables: {
id: appId,
app: {
authJwtCustomClaims: customClaims
.slice(0, currentIndex)
.concat(permissionVariable)
.concat(customClaims.slice(currentIndex + 1))
.filter((claim) => !claim.system)
.reduce(
(authJwtCustomClaims, claim) => ({
...authJwtCustomClaims,
[claim.key]: claim.value,
}),
{},
),
},
},
});
triggerToast(`Permission variable updated`);
if (props.onClose) {
props.onClose();
}
} catch (updateError) {
if (updateError instanceof Error) {
setError(updateError);
} else {
setError(new Error(updateError));
}
}
}
async function handleRemove() {
setError(undefined);
try {
await updateApp({
variables: {
id: appId,
app: {
authJwtCustomClaims: customClaims
.filter(
(claim) =>
claim.key !== originalCustomClaim.key && !claim.system,
)
.reduce(
(authJwtCustomClaims, claim) => ({
...authJwtCustomClaims,
[claim.key]: claim.value,
}),
{},
),
},
},
});
setShowRemoveModal(false);
triggerToast('Permission variable removed');
if (props.onClose) {
props.onClose();
}
} catch (updateError) {
if (updateError instanceof Error) {
setError(updateError);
} else {
setError(new Error(updateError));
}
}
}
return (
<>
<Modal
showModal={showRemoveModal}
close={() => setShowRemoveModal(false)}
>
<div className="grid w-96 grid-flow-row gap-2 p-6 text-left text-greyscaleDark">
<Text variant="h3" component="h2">
Remove {originalCustomClaim.key}?
</Text>
<Text>You will not be able to use it in permissions anymore.</Text>
<Text>
If you have permission checks currently using this property, they
will never resolve to true.
</Text>
<div className="mt-2 grid grid-flow-row gap-2">
<Button color="error" onClick={handleRemove} className="w-full">
Remove Permission Variable
</Button>
<Button
variant="outlined"
color="secondary"
onClick={() => setShowRemoveModal(false)}
className="w-full"
>
Cancel
</Button>
</div>
</div>
</Modal>
<FormProvider {...form}>
<CreatePermissionVariableModalBase
title="Edit Permission Variable"
type="edit"
onSubmit={handleSubmit}
onRemove={() => setShowRemoveModal(true)}
errorComponent={
error && (
<Alert className="mt-4" severity="error">
{error.message}
</Alert>
)
}
{...props}
/>
</FormProvider>
</>
);
}

View File

@@ -1,101 +0,0 @@
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import { Alert } from '@/ui/Alert';
import Loading from '@/ui/Loading';
import {
refetchGetRolesQuery,
useGetRolesQuery,
useUpdateAppMutation,
} from '@/utils/__generated__/graphql';
import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import type {
CreateUserRoleBaseFormData,
CreateUserRoleModalBaseProps,
} from './CreateUserRoleModalBase';
import { CreateUserRoleModalBase } from './CreateUserRoleModalBase';
export type CreateUserRoleFormData = CreateUserRoleBaseFormData;
export type CreateUserRoleModalProps = Pick<
CreateUserRoleModalBaseProps,
'onClose'
>;
export function CreateUserRoleModal({ onClose }: CreateUserRoleModalProps) {
const [error, setError] = useState<Error>();
const { currentApplication } = useCurrentWorkspaceAndApplication();
const form = useForm<CreateUserRoleBaseFormData>({
reValidateMode: 'onSubmit',
});
const [updateApp] = useUpdateAppMutation({
refetchQueries: [refetchGetRolesQuery({ id: currentApplication.id })],
});
const {
data: currentRolesData,
loading,
error: getRolesError,
} = useGetRolesQuery({
variables: {
id: currentApplication.id,
},
});
if (loading) {
return <Loading />;
}
if (getRolesError) {
return (
<div className="mx-auto max-w-2.5xl">
<Alert severity="error">{error.message}</Alert>
</div>
);
}
async function handleSubmit(data) {
setError(undefined);
const newAuthUserDefaultAllowedRoles = `${currentRolesData.app.authUserDefaultAllowedRoles},${data.roleName}`;
try {
await updateApp({
variables: {
id: currentApplication.id,
app: {
authUserDefaultAllowedRoles: newAuthUserDefaultAllowedRoles,
},
},
});
if (!onClose) {
return;
}
onClose();
} catch (updateError) {
setError(updateError);
}
}
return (
<FormProvider {...form}>
<CreateUserRoleModalBase
title="Create New Role"
type="create"
onSubmit={handleSubmit}
onClose={onClose}
errorComponent={
error && (
<Alert className="mt-4" severity="error">
{error.message}
</Alert>
)
}
/>
</FormProvider>
);
}

View File

@@ -1,102 +0,0 @@
import type { CreateModalBaseProps } from '@/components/applications/users/permissions/modal/CreatePermissionVariableModalBase';
import { Input } from '@/ui';
import { Button } from '@/ui/Button';
import { Text } from '@/ui/Text';
import { Controller, useFormContext } from 'react-hook-form';
export interface CreateUserRoleBaseFormData {
roleName: string;
}
export type CreateUserRoleModalBaseProps =
CreateModalBaseProps<CreateUserRoleBaseFormData>;
export type CreateUserRoleModal = Pick<CreateUserRoleModalBaseProps, 'onClose'>;
export function CreateUserRoleModalBase({
title,
type,
onRemove,
onSubmit,
errorComponent,
}: CreateUserRoleModalBaseProps) {
const {
control,
handleSubmit,
formState: { isSubmitting },
} = useFormContext<CreateUserRoleBaseFormData>();
return (
<div className="w-modal- p-6 text-left">
<div className="mx-auto items-center justify-between">
<Text
variant="heading"
className="text-center text-lg font-medium text-greyscaleDark"
>
{title}
</Text>
</div>
{errorComponent}
<form onSubmit={handleSubmit(onSubmit)} autoComplete="off">
<div className="mt-3 mb-3 divide-y border-t border-b py-1">
<div className="flex flex-row place-content-between py-2">
<div className="flex w-full flex-row">
<Text
color="greyscaleDark"
className="self-center font-medium"
size="normal"
>
New Role Name
</Text>
</div>
<div className="flex w-full">
<Controller
name="roleName"
control={control}
rules={{
required: true,
pattern: {
value: /^[a-zA-Z0-9-_]+$/,
message: 'Must contain only letters, hyphens, and numbers.',
},
}}
render={({ field }) => (
<Input
{...field}
id="roleName"
required
value={field.value || ''}
onChange={(value: string) => {
if (value && !/^[a-zA-Z0-9-_]+$/gi.test(value)) {
// prevent the user from entering invalid characters
return;
}
field.onChange(value);
}}
/>
)}
/>
</div>
</div>
</div>
<div className="grid gap-2">
<Button
variant="primary"
type="submit"
loading={isSubmitting}
disabled={isSubmitting}
>
{type === 'create' ? 'Create New User Role' : 'Save Changes'}
</Button>
{type === 'edit' && onRemove && (
<Button variant="menu" border onClick={onRemove}>
<Text className="text-sm+ font-medium text-red">Remove Role</Text>
</Button>
)}
</div>
</form>
</div>
);
}

View File

@@ -1,190 +0,0 @@
import type { GetRolesQuery } from '@/generated/graphql';
import {
refetchGetRolesQuery,
useGetRolesQuery,
useUpdateAppMutation,
} from '@/generated/graphql';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import { Alert } from '@/ui/Alert';
import { Button } from '@/ui/Button';
import Loading from '@/ui/Loading';
import { Modal } from '@/ui/Modal';
import { Text } from '@/ui/Text';
import { triggerToast } from '@/utils/toast';
import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import type {
CreateUserRoleBaseFormData,
CreateUserRoleModalBaseProps,
} from './CreateUserRoleModalBase';
import { CreateUserRoleModalBase } from './CreateUserRoleModalBase';
export type EditUserRoleFormData = CreateUserRoleBaseFormData;
export type EditUserRoleModalProps = Pick<
CreateUserRoleModalBaseProps,
'onClose'
> & {
/**
* The permission variable to edit.
*/
payload: any;
};
export function EditUserRoleModal({
payload: originalRole,
...props
}: EditUserRoleModalProps) {
const [error, setError] = useState<Error>();
const [showRemoveModal, setShowRemoveModal] = useState(false);
const { currentApplication } = useCurrentWorkspaceAndApplication();
const form = useForm<EditUserRoleFormData>({
reValidateMode: 'onSubmit',
defaultValues: {
roleName: originalRole.name || '',
},
});
const [updateApp, { loading: loadingUpdateAppMutation }] =
useUpdateAppMutation({
refetchQueries: [refetchGetRolesQuery({ id: currentApplication.id })],
});
const {
data: currentRolesData,
loading,
error: getRolesError,
} = useGetRolesQuery({
variables: {
id: currentApplication.id,
},
});
if (loading) {
return <Loading />;
}
if (getRolesError) {
return (
<div className="mx-auto max-w-2.5xl">
<Alert severity="error">{error.message}</Alert>
</div>
);
}
async function handleSubmit(data: EditUserRoleFormData) {
setError(undefined);
const currentUserRoles =
currentRolesData.app.authUserDefaultAllowedRoles.split(',');
const roleBeingEdited = currentUserRoles.find(
(role) => role === originalRole.name,
);
const indexofRoleBeingEdited = currentUserRoles.indexOf(roleBeingEdited);
const newRoleName = data.roleName;
const newAuthUserDefaultAllowedRoles = currentUserRoles.slice();
if (data.roleName !== originalRole.name) {
newAuthUserDefaultAllowedRoles[indexofRoleBeingEdited] = newRoleName;
}
try {
await updateApp({
variables: {
id: currentApplication.id,
app: {
authUserDefaultAllowedRoles:
newAuthUserDefaultAllowedRoles.join(','),
},
},
});
triggerToast(`Role "${data.roleName}" updated successfully`);
props.onClose();
} catch (updateError) {
setError(updateError);
}
}
async function handleRemove(data: GetRolesQuery) {
setError(undefined);
// Get the current roles of this application.
const currentUserRoles = data.app.authUserDefaultAllowedRoles.split(',');
// Remove the role from the current roles.
const filteredCurrentUserRoles = currentUserRoles.filter(
(role) => role !== originalRole.name,
);
const newAuthUserDefaultAllowedRoles = filteredCurrentUserRoles.join(',');
try {
await updateApp({
variables: {
id: currentApplication.id,
app: {
authUserDefaultAllowedRoles: newAuthUserDefaultAllowedRoles,
},
},
});
props.onClose();
triggerToast(`Role "${originalRole.name}" removed successfully`);
} catch (updateError) {
setError(updateError);
}
}
return (
<>
<Modal
showModal={showRemoveModal}
close={() => setShowRemoveModal(false)}
>
<div className="px-6 pt-5 text-center text-greyscaleDark">
<Text variant="heading" className="mb-2 text-lg font-medium">
Remove Role &quot;{originalRole.name}&quot;?
</Text>
<div className="my-4">
<Button
variant="danger"
onClick={() => handleRemove(currentRolesData)}
className="w-full"
loading={loadingUpdateAppMutation}
>
Remove Role
</Button>
<Button
onClick={() => setShowRemoveModal(false)}
className="w-full"
>
Cancel
</Button>
</div>
</div>
</Modal>
<FormProvider {...form}>
<CreateUserRoleModalBase
title="Edit Role"
type="edit"
onSubmit={handleSubmit}
onRemove={() => setShowRemoveModal(true)}
errorComponent={
error && (
<Alert className="mt-4" severity="error">
{error.message}
</Alert>
)
}
{...props}
/>
</FormProvider>
</>
);
}

View File

@@ -1,201 +0,0 @@
import { Alert } from '@/ui/Alert';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import Text from '@/ui/v2/Text';
import { useState } from 'react';
type EnvModalProps = {
onSubmit: (props: {
name: string;
prodValue: string;
devValue: string;
}) => Promise<void>;
name?: string;
prodValue?: string;
devValue?: string;
close: VoidFunction;
};
interface AddEnvVarModalVariablesError {
hasError: boolean;
message: string;
}
const DISABLED_START_ENV_VARIABLES = [
'NHOST_',
'HASURA_',
'AUTH_',
'STORAGE_',
'POSTGRES_',
];
const DISABLED_ENV_VARIABLES = [
'PATH',
'NODE_PATH',
'PYTHONPATH',
'GEM_PATH',
'HOSTNAME',
'TERM',
'NODE_VERSION',
'YARN_VERSION',
'NODE_ENV',
'HOME',
];
export default function AddEnvVarModal({
name: externalName,
prodValue: externalProdValue,
devValue: externalDevValue,
close,
onSubmit,
}: EnvModalProps) {
const [name, setName] = useState(externalName || '');
const [prodValue, setProdValue] = useState(externalProdValue || '');
const [devValue, setDevValue] = useState(externalDevValue || '');
const [error, setError] = useState<AddEnvVarModalVariablesError>({
hasError: false,
message: '',
});
const noError: AddEnvVarModalVariablesError = {
hasError: false,
message: '',
};
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
setError({ hasError: false, message: '' });
e.preventDefault();
if (
DISABLED_START_ENV_VARIABLES.some((envVar) =>
name.toUpperCase().startsWith(envVar),
)
) {
setError({
hasError: true,
message:
'The environment variable name cannot start with a value that is reserved for an internal environment variable.',
});
return;
}
if (
DISABLED_ENV_VARIABLES.some((envVar) => envVar === name.toUpperCase())
) {
setError({
hasError: true,
message:
'The environment variable name cannot be a value that is reserved for internal use.',
});
return;
}
// only allow alphabet characters and underscores
const onlyLettersWithNumbersStartsWithLetter = /^[a-zA-Z_]+[a-zA-Z0-9_]*$/;
if (!onlyLettersWithNumbersStartsWithLetter.test(name)) {
setError({
hasError: true,
message:
'The name contains invalid characters. Only letters, digits, and underscores are allowed. Furthermore, the name should start with a letter.',
});
return;
}
if (!name) {
setError({ hasError: true, message: 'Variable name is required.' });
return;
}
if (!prodValue) {
setError({ hasError: true, message: 'Production value is required.' });
return;
}
if (!devValue) {
setError({ hasError: true, message: 'Development value is required.' });
return;
}
await onSubmit({
name,
prodValue,
devValue,
});
close();
};
return (
<form onSubmit={handleSubmit}>
<div className="w-modal px-6 py-6 text-left">
<div className="grid grid-flow-row gap-1">
<Text variant="h3" component="h2">
{name || 'EXAMPLE_NAME'}
</Text>
<Text variant="subtitle2">
The default value will be available in all environments, unless you
override it. All values are encrypted.
</Text>
<div className="my-2 grid grid-flow-row gap-2">
<Input
id="name"
label="Name"
autoFocus
autoComplete="off"
fullWidth
placeholder="EXAMPLE_NAME"
value={name}
onChange={(event) => {
setError(noError);
setName(event.target.value);
}}
hideEmptyHelperText
/>
<Input
id="prodValue"
label="Production Value"
fullWidth
placeholder="Enter a value"
value={prodValue}
onChange={(event) => {
setError(noError);
setProdValue(event.target.value);
}}
hideEmptyHelperText
/>
<Input
id="devValue"
label="Development Value"
fullWidth
placeholder="Enter a value"
value={devValue}
onChange={(event) => {
setError(noError);
setDevValue(event.target.value);
}}
hideEmptyHelperText
/>
</div>
{error.hasError && (
<Alert severity="warning" className="mb-2">
<Text className="font-medium">Warning</Text>
<Text>{error.message}</Text>
</Alert>
)}
<div className="grid grid-flow-row gap-2">
<Button type="submit">Add</Button>
<Button onClick={close} variant="outlined" color="secondary">
Close
</Button>
</div>
</div>
</div>
</form>
);
}

View File

@@ -12,7 +12,13 @@ export type DialogType =
| 'CREATE_TABLE'
| 'EDIT_TABLE'
| 'CREATE_FOREIGN_KEY'
| 'EDIT_FOREIGN_KEY';
| 'EDIT_FOREIGN_KEY'
| 'CREATE_ROLE'
| 'EDIT_ROLE'
| 'CREATE_PERMISSION_VARIABLE'
| 'EDIT_PERMISSION_VARIABLE'
| 'CREATE_ENVIRONMENT_VARIABLE'
| 'EDIT_ENVIRONMENT_VARIABLE';
export interface DialogConfig<TPayload = unknown> {
/**

View File

@@ -1,13 +1,25 @@
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
import { CreateForeignKeyForm } from '@/components/data-browser/CreateForeignKeyForm';
import { EditForeignKeyForm } from '@/components/data-browser/EditForeignKeyForm';
import CreateForeignKeyForm from '@/components/data-browser/CreateForeignKeyForm';
import EditForeignKeyForm from '@/components/data-browser/EditForeignKeyForm';
import CreateEnvironmentVariableForm from '@/components/settings/environmentVariables/CreateEnvironmentVariableForm';
import EditEnvironmentVariableForm from '@/components/settings/environmentVariables/EditEnvironmentVariableForm';
import CreatePermissionVariableForm from '@/components/settings/permissions/CreatePermissionVariableForm';
import EditPermissionVariableForm from '@/components/settings/permissions/EditPermissionVariableForm';
import CreateRoleForm from '@/components/settings/roles/CreateRoleForm';
import EditRoleForm from '@/components/settings/roles/EditRoleForm';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import AlertDialog from '@/ui/v2/AlertDialog';
import { BaseDialog } from '@/ui/v2/Dialog';
import Drawer from '@/ui/v2/Drawer';
import dynamic from 'next/dynamic';
import type { BaseSyntheticEvent, PropsWithChildren } from 'react';
import type {
BaseSyntheticEvent,
DetailedHTMLProps,
HTMLProps,
PropsWithChildren,
} from 'react';
import { useCallback, useMemo, useReducer, useRef, useState } from 'react';
import { twMerge } from 'tailwind-merge';
import type { DialogConfig, DialogType } from './DialogContext';
import DialogContext from './DialogContext';
import {
@@ -16,13 +28,21 @@ import {
drawerReducer,
} from './dialogReducers';
function LoadingComponent() {
function LoadingComponent({
className,
...props
}: DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> = {}) {
return (
<div className="grid items-center justify-center px-6 py-4">
<div
{...props}
className={twMerge(
'grid items-center justify-center px-6 py-4',
className,
)}
>
<ActivityIndicator
circularProgressProps={{ className: 'w-5 h-5' }}
label="Loading form..."
delay={500}
/>
</div>
);
@@ -30,27 +50,27 @@ function LoadingComponent() {
const CreateRecordForm = dynamic(
() => import('@/components/data-browser/CreateRecordForm'),
{ ssr: false, loading: LoadingComponent },
{ ssr: false, loading: () => LoadingComponent() },
);
const CreateColumnForm = dynamic(
() => import('@/components/data-browser/CreateColumnForm'),
{ ssr: false, loading: LoadingComponent },
{ ssr: false, loading: () => LoadingComponent() },
);
const EditColumnForm = dynamic(
() => import('@/components/data-browser/EditColumnForm'),
{ ssr: false, loading: LoadingComponent },
{ ssr: false, loading: () => LoadingComponent() },
);
const CreateTableForm = dynamic(
() => import('@/components/data-browser/CreateTableForm'),
{ ssr: false, loading: LoadingComponent },
{ ssr: false, loading: () => LoadingComponent() },
);
const EditTableForm = dynamic(
() => import('@/components/data-browser/EditTableForm'),
{ ssr: false, loading: LoadingComponent },
{ ssr: false, loading: () => LoadingComponent() },
);
function DialogProvider({ children }: PropsWithChildren<unknown>) {
@@ -209,6 +229,25 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
[closeDialog, closeDrawer, onDirtyStateChange, openDialog, openDrawer],
);
const sharedDialogProps = {
...dialogPayload,
onSubmit: async (values: any) => {
await dialogPayload?.onSubmit?.(values);
closeDialog();
},
onCancel: closeDialogWithDirtyGuard,
};
const sharedDrawerProps = {
onSubmit: async () => {
await drawerPayload?.onSubmit();
closeDrawer();
},
onCancel: closeDrawerWithDirtyGuard,
};
return (
<DialogContext.Provider value={contextValue}>
<AlertDialog
@@ -249,33 +288,47 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
open={dialogOpen}
onClose={closeDialogWithDirtyGuard}
TransitionProps={{ onExited: clearDialogContent, unmountOnExit: false }}
PaperProps={{ className: 'max-w-md w-full' }}
PaperProps={{
...dialogProps?.PaperProps,
className: twMerge(
'max-w-md w-full',
dialogProps?.PaperProps?.className,
),
}}
>
<RetryableErrorBoundary
errorMessageProps={{ className: 'pt-0 pb-5 px-6' }}
>
{activeDialogType === 'CREATE_FOREIGN_KEY' && (
<CreateForeignKeyForm
{...dialogPayload}
onSubmit={async (values) => {
await dialogPayload?.onSubmit(values);
closeDialog();
}}
onCancel={closeDialogWithDirtyGuard}
/>
<CreateForeignKeyForm {...sharedDialogProps} />
)}
{activeDialogType === 'EDIT_FOREIGN_KEY' && (
<EditForeignKeyForm
{...dialogPayload}
onSubmit={async (values) => {
await dialogPayload?.onSubmit(values);
<EditForeignKeyForm {...sharedDialogProps} />
)}
closeDialog();
}}
onCancel={closeDialogWithDirtyGuard}
/>
{activeDialogType === 'CREATE_ROLE' && (
<CreateRoleForm {...sharedDialogProps} />
)}
{activeDialogType === 'EDIT_ROLE' && (
<EditRoleForm {...sharedDialogProps} />
)}
{activeDialogType === 'CREATE_PERMISSION_VARIABLE' && (
<CreatePermissionVariableForm {...sharedDialogProps} />
)}
{activeDialogType === 'EDIT_PERMISSION_VARIABLE' && (
<EditPermissionVariableForm {...sharedDialogProps} />
)}
{activeDialogType === 'CREATE_ENVIRONMENT_VARIABLE' && (
<CreateEnvironmentVariableForm {...sharedDialogProps} />
)}
{activeDialogType === 'EDIT_ENVIRONMENT_VARIABLE' && (
<EditEnvironmentVariableForm {...sharedDialogProps} />
)}
</RetryableErrorBoundary>
</BaseDialog>
@@ -292,61 +345,34 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
<RetryableErrorBoundary>
{activeDrawerType === 'CREATE_RECORD' && (
<CreateRecordForm
{...sharedDrawerProps}
columns={drawerPayload?.columns}
onSubmit={async () => {
await drawerPayload?.onSubmit();
closeDrawer();
}}
onCancel={closeDrawerWithDirtyGuard}
/>
)}
{activeDrawerType === 'CREATE_COLUMN' && (
<CreateColumnForm
onSubmit={async () => {
await drawerPayload?.onSubmit();
closeDrawer();
}}
onCancel={closeDrawerWithDirtyGuard}
/>
<CreateColumnForm {...sharedDrawerProps} />
)}
{activeDrawerType === 'EDIT_COLUMN' && (
<EditColumnForm
{...sharedDrawerProps}
column={drawerPayload?.column}
onSubmit={async () => {
await drawerPayload?.onSubmit();
closeDrawer();
}}
onCancel={closeDrawerWithDirtyGuard}
/>
)}
{activeDrawerType === 'CREATE_TABLE' && (
<CreateTableForm
{...sharedDrawerProps}
schema={drawerPayload?.schema}
onSubmit={async () => {
await drawerPayload?.onSubmit();
closeDrawer();
}}
onCancel={closeDrawerWithDirtyGuard}
/>
)}
{activeDrawerType === 'EDIT_TABLE' && (
<EditTableForm
{...sharedDrawerProps}
table={drawerPayload?.table}
schema={drawerPayload?.schema}
onSubmit={async () => {
await drawerPayload?.onSubmit();
closeDrawer();
}}
onCancel={closeDrawerWithDirtyGuard}
/>
)}
</RetryableErrorBoundary>

View File

@@ -24,7 +24,7 @@ export interface CreateForeignKeyFormProps
onSubmit?: (values: BaseForeignKeyFormValues) => Promise<void>;
}
export function CreateForeignKeyForm({
export default function CreateForeignKeyForm({
onSubmit,
selectedColumn,
...props

View File

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

View File

@@ -29,7 +29,7 @@ export interface EditForeignKeyFormProps
onSubmit?: (values: BaseForeignKeyFormValues) => Promise<void>;
}
export function EditForeignKeyForm({
export default function EditForeignKeyForm({
foreignKeyRelation,
selectedColumn,
onSubmit,

View File

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

View File

@@ -1,17 +0,0 @@
import { Text } from '@/ui/Text';
interface ErrorComponentProps {
message: string;
}
function ErrorComponent({ message }: ErrorComponentProps) {
return (
<div className="my-4 rounded-md bg-warning px-4 py-2 text-dark">
<Text className="font-medium text-textOrange">Error</Text>
<Text className="pt-2 font-medium text-dimBlack" size="normal">
{message}
</Text>
</div>
);
}
export default ErrorComponent;

View File

@@ -1,22 +0,0 @@
function Copy({ stroke = '#21324B', ...props }: any) {
return (
<svg fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M10.5 10.5h3v-8h-8v3"
stroke={stroke}
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M10.5 5.5h-8v8h8v-8z"
stroke={stroke}
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
export default Copy;

View File

@@ -1,29 +0,0 @@
import * as React from 'react';
function Eye(props: JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
{...props}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
/>
</svg>
);
}
export default Eye;

View File

@@ -1,25 +0,0 @@
import * as React from 'react';
function EyeOff(
props: JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>,
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
{...props}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
/>
</svg>
);
}
export default EyeOff;

View File

@@ -1,14 +0,0 @@
export default function Lock(
props: JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>,
) {
return (
<svg fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8 1.75a1.5 1.5 0 0 0-1.5 1.5v1.5h3v-1.5A1.5 1.5 0 0 0 8 1.75Zm-3 1.5v1.5H3c-.69 0-1.25.56-1.25 1.25v7c0 .69.56 1.25 1.25 1.25h10c.69 0 1.25-.56 1.25-1.25V6c0-.69-.56-1.25-1.25-1.25h-2v-1.5a3 3 0 0 0-6 0Zm-1.75 3h9.5v6.5h-9.5v-6.5ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
fill="#21324B"
/>
</svg>
);
}

View File

@@ -1,21 +0,0 @@
import * as React from 'react';
function Plus(props: any) {
return (
<svg
viewBox="0 0 32 32"
stroke="currentColor"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16 4.75C9.787 4.75 4.75 9.787 4.75 16S9.787 27.25 16 27.25 27.25 22.213 27.25 16 22.213 4.75 16 4.75zM3.25 16C3.25 8.958 8.958 3.25 16 3.25S28.75 8.958 28.75 16 23.042 28.75 16 28.75 3.25 23.042 3.25 16zm12 .75H10v-1.5h5.25V10h1.5v5.25H22v1.5h-5.25V22h-1.5v-5.25z"
/>
</svg>
);
}
export default Plus;

View File

@@ -1,20 +0,0 @@
import * as React from 'react';
function TwilioIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
width={108}
height={32}
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M16.864 0c8.844 0 15.984 7.147 15.984 16s-7.14 16-15.984 16C8.019 32 .88 24.853.88 16S8.02 0 16.864 0Zm0 4.267c-6.5 0-11.722 5.226-11.722 11.733a11.694 11.694 0 0 0 11.722 11.733c6.5 0 11.721-5.226 11.721-11.733A11.694 11.694 0 0 0 16.864 4.267Zm27.173 2.026c.213-.106.426.107.426.214v4.586h8.525c.106 0 .32.214.32.32l.639 2.667.639 2.667.107.32.106-.32 1.599-5.334c0-.213.213-.32.32-.32h4.262c.106 0 .32.214.32.32l1.704 5.654.107-.32 1.385-5.334c0-.213.213-.32.32-.32h10.869c.106 0 .213.214.213.32V25.6c0 .213-.213.32-.32.32h-5.434c-.213 0-.32-.213-.32-.32V12.16l-4.05 13.44c0 .187-.162.292-.275.315l-.044.005H60.98c-.107 0-.32-.213-.32-.32l-.853-2.88-.959-3.093L56.93 25.6c0 .213-.213.32-.32.32h-4.475c-.106 0-.32-.213-.32-.32l-4.049-13.44v3.413c0 .214-.213.32-.32.32h-3.09v3.627c0 1.067.533 1.493 1.492 1.493.426 0 .96-.106 1.492-.32.106-.106.32 0 .32.214v4.266c-.96.534-2.345.854-3.836.854-3.517 0-5.435-1.6-5.435-5.12v-5.014h-1.385c-.213 0-.32-.213-.32-.32V11.52c0-.213.213-.32.32-.32h1.385V8.32c0-.213.106-.32.32-.32l5.328-1.707Zm54.984 4.48c4.689 0 8.099 3.52 8.099 7.574 0 4.053-3.41 7.573-8.205 7.573-4.689 0-8.099-3.52-8.099-7.573 0-4.054 3.41-7.574 8.205-7.574Zm-16.197-4.48c.213 0 .32.107.32.214v18.986c0 .214-.213.32-.32.32H77.39c-.213 0-.32-.213-.32-.32V6.613c0-.213.213-.32.32-.32h5.434Zm7.14 4.8c.213 0 .32.214.32.32v13.974c0 .213-.214.32-.32.32h-5.435c-.213 0-.32-.214-.32-.32V11.413c0-.213.214-.32.32-.32h5.435ZM12.92 16.64a3.322 3.322 0 0 1 3.303 3.307 3.322 3.322 0 0 1-3.303 3.306 3.322 3.322 0 0 1-3.303-3.306 3.322 3.322 0 0 1 3.303-3.307Zm7.886 0a3.322 3.322 0 0 1 3.303 3.307 3.322 3.322 0 0 1-3.303 3.306 3.322 3.322 0 0 1-3.304-3.306 3.322 3.322 0 0 1 3.304-3.307Zm78.214-.747c-1.385 0-2.344 1.174-2.344 2.56 0 1.387 1.066 2.56 2.344 2.56 1.386 0 2.345-1.173 2.345-2.56 0-1.493-1.066-2.666-2.345-2.56ZM20.807 8.747a3.322 3.322 0 0 1 3.303 3.306 3.322 3.322 0 0 1-3.303 3.307 3.322 3.322 0 0 1-3.304-3.307 3.322 3.322 0 0 1 3.304-3.306Zm-7.886 0a3.322 3.322 0 0 1 3.303 3.306 3.322 3.322 0 0 1-3.303 3.307 3.322 3.322 0 0 1-3.303-3.307 3.322 3.322 0 0 1 3.303-3.306Zm62.87-2.454c.107 0 .213.107.32.214V9.92c0 .213-.213.32-.32.32h-5.647c-.213 0-.32-.213-.32-.32V6.613c0-.213.213-.32.32-.32h5.647Zm14.28 0c.213 0 .319.107.319.214V9.92c0 .213-.213.32-.32.32h-5.647c-.213 0-.32-.213-.32-.32V6.613c0-.213.213-.32.32-.32h5.647Z"
fill="#F22F46"
/>
</svg>
);
}
export default TwilioIcon;

View File

@@ -4,19 +4,19 @@ import { twMerge } from 'tailwind-merge';
export interface ContainerProps
extends DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> {
/**
* Class name passed to the wrapper element.
* Class name passed to the root element.
*/
wrapperClassName?: string;
rootClassName?: string;
}
export default function Container({
children,
className,
wrapperClassName,
rootClassName,
...props
}: ContainerProps) {
return (
<div className={twMerge('mx-auto w-full bg-white', wrapperClassName)}>
<div className={twMerge('mx-auto w-full bg-white', rootClassName)}>
<div
className={twMerge(
'mx-auto max-w-7xl bg-white px-5 pt-6 pb-20',

View File

@@ -5,6 +5,9 @@ import Image from 'next/image';
export default function OverviewProjectInfo() {
const { currentApplication } = useCurrentWorkspaceAndApplication();
const { region, subdomain } = currentApplication;
const isRegionAvailable =
region?.awsName && region?.countryCode && region?.city;
return (
<div className="grid grid-flow-row content-start gap-6">
@@ -16,28 +19,28 @@ export default function OverviewProjectInfo() {
<div className="grid grid-flow-row gap-3">
<InfoCard
title="Region"
value={currentApplication.region?.awsName}
value={region?.awsName}
customValue={
currentApplication.region && (
region.countryCode &&
region.city && (
<div className="grid grid-flow-col items-center gap-1 self-center">
<Image
src={`/assets/${currentApplication.region.countryCode}.svg`}
alt={`Logo of ${currentApplication.region.countryCode}`}
src={`/assets/${region.countryCode}.svg`}
alt={`Logo of ${region.countryCode}`}
width={16}
height={16}
/>
<Text className="text-sm font-medium text-greyscaleDark">
{currentApplication.region.city} (
{currentApplication.region.awsName})
{region.city} ({region.awsName})
</Text>
</div>
)
}
disableCopy={!currentApplication.region}
disableCopy={!isRegionAvailable}
/>
<InfoCard title="Subdomain" value={currentApplication.subdomain} />
<InfoCard title="Subdomain" value={subdomain} />
</div>
)}
</div>

View File

@@ -11,7 +11,7 @@ import Input from '@/ui/v2/Input';
import InputAdornment from '@/ui/v2/InputAdornment';
import { copy } from '@/utils/copy';
import { discordAnnounce } from '@/utils/discordAnnounce';
import generateRandomDatabasePassword from '@/utils/settings/generateRandomDatabasePassword/generateRandomDatabasePassword';
import generateRandomDatabasePassword from '@/utils/settings/generateRandomDatabasePassword';
import { resetDatabasePasswordValidationSchema } from '@/utils/settings/resetDatabasePasswordValidationSchema';
import { triggerToast } from '@/utils/toast';
import { yupResolver } from '@hookform/resolvers/yup';

View File

@@ -8,7 +8,6 @@ import Switch from '@/ui/v2/Switch';
import Text from '@/ui/v2/Text';
import Image from 'next/image';
import type { DetailedHTMLProps, HTMLProps, ReactNode } from 'react';
import { twMerge } from 'tailwind-merge';
export interface SettingsContainerProps
@@ -31,7 +30,7 @@ export interface SettingsContainerProps
/**
* The description for the section.
*/
description: string | ReactNode;
description?: string | ReactNode;
/**
* Link to the documentation.
*
@@ -40,6 +39,8 @@ export interface SettingsContainerProps
docsLink?: string;
/**
* Props for the primary action.
*
* @deprecated Use `slotProps.submitButton` instead.
*/
primaryActionButtonProps?: ButtonProps;
/**
@@ -75,9 +76,26 @@ export interface SettingsContainerProps
*/
className?: string;
/**
* Props to be passed to the Switch component.
* Props to be passed to different slots inside the component.
*/
switchProps?: SwitchProps;
slotProps?: {
/**
* Props to be passed to the root element.
*/
root?: DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement>;
/**
* Props to be passed to the `<Switch />` component.
*/
switch?: SwitchProps;
/**
* Props to be passed to the footer element.
*/
submitButton?: ButtonProps;
/**
* Props to be passed to the footer element.
*/
footer?: DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement>;
};
}
export default function SettingsContainer({
@@ -94,14 +112,15 @@ export default function SettingsContainer({
switchId,
showSwitch = false,
rootClassName,
switchProps,
docsTitle,
slotProps: { root, switch: switchSlot, submitButton, footer } = {},
}: SettingsContainerProps) {
return (
<div
{...root}
className={twMerge(
'grid grid-flow-row gap-4 rounded-lg border-1 border-gray-200 bg-white py-4',
rootClassName,
root?.className || rootClassName,
)}
>
<div className="grid grid-flow-col place-content-between gap-3 px-4">
@@ -131,14 +150,14 @@ export default function SettingsContainer({
checked={enabled}
onChange={(e) => onEnabledChange(e.target.checked)}
className="self-center"
{...switchProps}
{...switchSlot}
/>
)}
{switchId && showSwitch && (
<ControlledSwitch
className="self-center"
name={switchId}
{...switchProps}
{...switchSlot}
/>
)}
</div>
@@ -148,9 +167,11 @@ export default function SettingsContainer({
</div>
<div
{...footer}
className={twMerge(
'grid grid-flow-col items-center gap-x-2 border-t border-gray-200 px-4 pt-3.5',
docsLink ? 'place-content-between' : 'justify-end',
footer?.className,
)}
>
{docsLink && (
@@ -173,11 +194,17 @@ export default function SettingsContainer({
<Button
variant={
primaryActionButtonProps?.disabled ? 'outlined' : 'contained'
(submitButton || primaryActionButtonProps)?.disabled
? 'outlined'
: 'contained'
}
color={
(submitButton || primaryActionButtonProps)?.disabled
? 'secondary'
: 'primary'
}
color={primaryActionButtonProps?.disabled ? 'secondary' : 'primary'}
type="submit"
{...primaryActionButtonProps}
{...(submitButton || primaryActionButtonProps)}
>
{submitButtonText}
</Button>

View File

@@ -1,3 +1,4 @@
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
import type { ProjectLayoutProps } from '@/components/layout/ProjectLayout';
import ProjectLayout from '@/components/layout/ProjectLayout';
import type { SettingsSidebarProps } from '@/components/settings/SettingsSidebar';
@@ -33,8 +34,8 @@ export default function SettingsLayout({
{...sidebarProps}
/>
<div className="flex w-full flex-auto flex-col overflow-x-hidden">
{children}
<div className="flex w-full flex-auto flex-col overflow-x-hidden bg-[#fafafa]">
<RetryableErrorBoundary>{children}</RetryableErrorBoundary>
</div>
</ProjectLayout>
);

View File

@@ -48,6 +48,7 @@ function SettingsNavLink({
return (
<ListItem.Root>
<ListItem.Button
dense
href={finalUrl}
component={NavLink}
selected={active}

View File

@@ -0,0 +1,182 @@
import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import Text from '@/ui/v2/Text';
import { useEffect } from 'react';
import { useFormContext } from 'react-hook-form';
import * as Yup from 'yup';
export interface BaseEnvironmentVariableFormValues {
/**
* Identifier of the environment variable.
*/
id: string;
/**
* The name of the role.
*/
name: string;
/**
* Development environment variable value.
*/
devValue: string;
/**
* Production environment variable value.
*/
prodValue: string;
}
export interface BaseEnvironmentVariableFormProps {
/**
* Determines the mode of the form.
*
* @default 'edit'
*/
mode?: 'edit' | 'create';
/**
* Function to be called when the form is submitted.
*/
onSubmit: (values: BaseEnvironmentVariableFormValues) => void;
/**
* Function to be called when the operation is cancelled.
*/
onCancel?: VoidFunction;
/**
* Submit button text.
*
* @default 'Save'
*/
submitButtonText?: string;
}
export const baseEnvironmentVariableFormValidationSchema = Yup.object({
name: Yup.string()
.required('This field is required.')
.test(
'isEnvVarPermitted',
'This is a reserved name.',
(value) =>
![
'PATH',
'NODE_PATH',
'PYTHONPATH',
'GEM_PATH',
'HOSTNAME',
'TERM',
'NODE_VERSION',
'YARN_VERSION',
'NODE_ENV',
'HOME',
].includes(value),
)
.test(
'isEnvVarPrefixPermitted',
`The name can't start with NHOST_, HASURA_, AUTH_, STORAGE_ or POSTGRES_.`,
(value) =>
['NHOST_', 'HASURA_', 'AUTH_', 'STORAGE_', 'POSTGRES_'].every(
(prefix) => !value.startsWith(prefix),
),
)
.test('isEnvVarValid', `The name must start with a letter.`, (value) =>
/^[a-zA-Z]{1,}[a-zA-Z0-9_]*$/i.test(value),
),
devValue: Yup.string().required('This field is required.'),
prodValue: Yup.string().required('This field is required.'),
});
export default function BaseEnvironmentVariableForm({
mode = 'edit',
onSubmit,
onCancel,
submitButtonText = 'Save',
}: BaseEnvironmentVariableFormProps) {
const { onDirtyStateChange } = useDialog();
const form = useFormContext<BaseEnvironmentVariableFormValues>();
const {
register,
formState: { errors, dirtyFields, isSubmitting },
} = form;
// react-hook-form's isDirty gets true even if an input field is focused, then
// immediately unfocused - we can't rely on that information
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, 'dialog');
}, [isDirty, onDirtyStateChange]);
return (
<div className="grid grid-flow-row gap-6 px-6 pb-6">
<Text variant="subtitle1" component="span">
Environment Variables are made available to all your services. All
values are encrypted.
</Text>
<Form onSubmit={onSubmit} className="grid grid-flow-row gap-4">
<Input
{...register('name', {
onChange: (event) => {
if (
event.target.value &&
!/^[a-zA-Z]{1,}[a-zA-Z0-9_]*$/g.test(event.target.value)
) {
// we need to prevent invalid characters from being entered
// eslint-disable-next-line no-param-reassign
event.target.value = event.target.value.replace(
/[^a-zA-Z0-9_]/g,
'',
);
}
},
})}
id="name"
label="Name"
placeholder="EXAMPLE_NAME"
hideEmptyHelperText
error={!!errors.name}
helperText={errors?.name?.message}
fullWidth
autoComplete="off"
autoFocus={mode === 'create'}
disabled={mode === 'edit'}
/>
<Input
{...register('prodValue')}
id="prodValue"
label="Production Value"
placeholder="Enter value"
hideEmptyHelperText
error={!!errors.prodValue}
helperText={errors?.prodValue?.message}
fullWidth
autoComplete="off"
autoFocus={mode === 'edit'}
/>
<Input
{...register('devValue')}
id="devValue"
label="Development Value"
placeholder="Enter value"
hideEmptyHelperText
error={!!errors.devValue}
helperText={errors?.devValue?.message}
fullWidth
autoComplete="off"
/>
<div className="grid grid-flow-row gap-2">
<Button type="submit" loading={isSubmitting}>
{submitButtonText}
</Button>
<Button variant="outlined" color="secondary" onClick={onCancel}>
Cancel
</Button>
</div>
</Form>
</div>
);
}

View File

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

View File

@@ -0,0 +1,117 @@
import type {
BaseEnvironmentVariableFormProps,
BaseEnvironmentVariableFormValues,
} from '@/components/settings/environmentVariables/BaseEnvironmentVariableForm';
import BaseEnvironmentVariableForm, {
baseEnvironmentVariableFormValidationSchema,
} from '@/components/settings/environmentVariables/BaseEnvironmentVariableForm';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import { toastStyleProps } from '@/utils/settings/settingsConstants';
import {
useGetEnvironmentVariablesQuery,
useInsertEnvironmentVariablesMutation,
} from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
export interface CreateEnvironmentVariableFormProps
extends Pick<BaseEnvironmentVariableFormProps, 'onCancel'> {
/**
* Function to be called when the form is submitted.
*/
onSubmit?: () => Promise<void>;
}
export default function CreateEnvironmentVariableForm({
onSubmit,
...props
}: CreateEnvironmentVariableFormProps) {
const form = useForm<BaseEnvironmentVariableFormValues>({
defaultValues: {
name: '',
devValue: '',
prodValue: '',
},
reValidateMode: 'onSubmit',
resolver: yupResolver(baseEnvironmentVariableFormValidationSchema),
});
const { currentApplication } = useCurrentWorkspaceAndApplication();
const { data, loading, error } = useGetEnvironmentVariablesQuery({
variables: {
id: currentApplication?.id,
},
fetchPolicy: 'cache-only',
});
const [insertEnvironmentVariables] = useInsertEnvironmentVariablesMutation({
refetchQueries: ['getEnvironmentVariables'],
});
if (loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading environment variables..."
/>
);
}
if (error) {
throw error;
}
const { setError } = form;
async function handleSubmit({
name,
prodValue,
devValue,
}: BaseEnvironmentVariableFormValues) {
if (
data?.environmentVariables?.some(
(environmentVariable) => environmentVariable.name === name,
)
) {
setError('name', {
message: 'This environment variable already exists.',
});
return;
}
const insertEnvironmentVariablePromise = insertEnvironmentVariables({
variables: {
environmentVariables: [
{ appId: currentApplication.id, name, prodValue, devValue },
],
},
});
await toast.promise(
insertEnvironmentVariablePromise,
{
loading: 'Creating environment variable...',
success: 'Environment variable has been created successfully.',
error: 'An error occurred while creating the environment variable.',
},
toastStyleProps,
);
onSubmit?.();
}
return (
<FormProvider {...form}>
<BaseEnvironmentVariableForm
mode="create"
submitButtonText="Create"
onSubmit={handleSubmit}
{...props}
/>
</FormProvider>
);
}

View File

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

View File

@@ -0,0 +1,124 @@
import type {
BaseEnvironmentVariableFormProps,
BaseEnvironmentVariableFormValues,
} from '@/components/settings/environmentVariables/BaseEnvironmentVariableForm';
import BaseEnvironmentVariableForm, {
baseEnvironmentVariableFormValidationSchema,
} from '@/components/settings/environmentVariables/BaseEnvironmentVariableForm';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import type { EnvironmentVariable } from '@/types/application';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import { toastStyleProps } from '@/utils/settings/settingsConstants';
import {
useGetEnvironmentVariablesQuery,
useUpdateEnvironmentVariableMutation,
} from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
export interface EditEnvironmentVariableFormProps
extends Pick<BaseEnvironmentVariableFormProps, 'onCancel'> {
/**
* The environment variable to edit.
*/
originalEnvironmentVariable: EnvironmentVariable;
/**
* Function to be called when the form is submitted.
*/
onSubmit?: () => Promise<void>;
}
export default function EditEnvironmentVariableForm({
originalEnvironmentVariable,
onSubmit,
...props
}: EditEnvironmentVariableFormProps) {
const form = useForm<BaseEnvironmentVariableFormValues>({
defaultValues: {
id: originalEnvironmentVariable.id || '',
name: originalEnvironmentVariable.name || '',
devValue: originalEnvironmentVariable.devValue || '',
prodValue: originalEnvironmentVariable.prodValue || '',
},
reValidateMode: 'onSubmit',
resolver: yupResolver(baseEnvironmentVariableFormValidationSchema),
});
const { currentApplication } = useCurrentWorkspaceAndApplication();
const { data, loading, error } = useGetEnvironmentVariablesQuery({
variables: {
id: currentApplication?.id,
},
fetchPolicy: 'cache-only',
});
const [updateEnvironmentVariable] = useUpdateEnvironmentVariableMutation({
refetchQueries: ['getEnvironmentVariables'],
});
if (loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading environment variables..."
/>
);
}
if (error) {
throw error;
}
const { setError } = form;
async function handleSubmit({
id,
name,
prodValue,
devValue,
}: BaseEnvironmentVariableFormValues) {
if (
data?.environmentVariables?.some(
(environmentVariable) =>
environmentVariable.name === name &&
environmentVariable.name !== originalEnvironmentVariable.name,
)
) {
setError('name', {
message: 'This environment variable already exists.',
});
return;
}
const updateEnvironmentVariablePromise = updateEnvironmentVariable({
variables: {
id,
environmentVariable: {
prodValue,
devValue,
},
},
});
await toast.promise(
updateEnvironmentVariablePromise,
{
loading: 'Updating environment variable...',
success: 'Environment variable has been updated successfully.',
error: 'An error occurred while updating the environment variable.',
},
toastStyleProps,
);
onSubmit?.();
}
return (
<FormProvider {...form}>
<BaseEnvironmentVariableForm onSubmit={handleSubmit} {...props} />
</FormProvider>
);
}

View File

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

View File

@@ -0,0 +1,244 @@
import { useDialog } from '@/components/common/DialogProvider';
import SettingsContainer from '@/components/settings/SettingsContainer';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import type { EnvironmentVariable } from '@/types/application';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import Button from '@/ui/v2/Button';
import Divider from '@/ui/v2/Divider';
import { Dropdown } from '@/ui/v2/Dropdown';
import IconButton from '@/ui/v2/IconButton';
import DotsVerticalIcon from '@/ui/v2/icons/DotsVerticalIcon';
import PlusIcon from '@/ui/v2/icons/PlusIcon';
import List from '@/ui/v2/List';
import { ListItem } from '@/ui/v2/ListItem';
import Text from '@/ui/v2/Text';
import { toastStyleProps } from '@/utils/settings/settingsConstants';
import {
useDeleteEnvironmentVariableMutation,
useGetEnvironmentVariablesQuery,
} from '@/utils/__generated__/graphql';
import { formatDistanceToNowStrict, parseISO } from 'date-fns';
import { Fragment } from 'react';
import toast from 'react-hot-toast';
import { twMerge } from 'tailwind-merge';
export interface PermissionVariableSettingsFormValues {
/**
* Permission variables.
*/
environmentVariables: EnvironmentVariable[];
}
export default function EnvironmentVariableSettings() {
const { openDialog, openAlertDialog } = useDialog();
const { currentApplication } = useCurrentWorkspaceAndApplication();
const { data, loading, error } = useGetEnvironmentVariablesQuery({
variables: {
id: currentApplication?.id,
},
});
const [deleteEnvironmentVariable] = useDeleteEnvironmentVariableMutation({
refetchQueries: ['getEnvironmentVariables'],
});
if (loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading environment variables..."
/>
);
}
if (error) {
throw error;
}
async function handleDeleteVariable({ id }: EnvironmentVariable) {
const deleteEnvironmentVariablePromise = deleteEnvironmentVariable({
variables: {
id,
},
});
await toast.promise(
deleteEnvironmentVariablePromise,
{
loading: 'Deleting environment variable...',
success: 'Environment variable has been deleted successfully.',
error: 'An error occurred while deleting the environment variable.',
},
toastStyleProps,
);
}
function handleOpenCreator() {
openDialog('CREATE_ENVIRONMENT_VARIABLE', {
title: 'Create Environment Variable',
props: {
titleProps: { className: '!pb-0' },
PaperProps: { className: 'gap-2 max-w-sm' },
},
});
}
function handleOpenEditor(originalVariable: EnvironmentVariable) {
openDialog('EDIT_ENVIRONMENT_VARIABLE', {
title: 'Edit Environment Variables',
payload: { originalEnvironmentVariable: originalVariable },
props: {
titleProps: { className: '!pb-0' },
PaperProps: { className: 'gap-2 max-w-sm' },
},
});
}
function handleConfirmDelete(originalVariable: EnvironmentVariable) {
openAlertDialog({
title: 'Delete Environment Variable',
payload: (
<Text>
Are you sure you want to delete the &quot;
<strong>{originalVariable.name}</strong>&quot; environment variable?
This cannot be undone.
</Text>
),
props: {
primaryButtonColor: 'error',
primaryButtonText: 'Delete',
onPrimaryAction: () => handleDeleteVariable(originalVariable),
},
});
}
const availableEnvironmentVariables =
[...data.environmentVariables].sort(
(a, b) =>
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),
) || [];
return (
<SettingsContainer
title="Project Environment Variables"
description="Environment Variables are key-value pairs configured outside your source code. They are used to store environment-specific values such as API keys."
docsLink="https://docs.nhost.io/platform/environment-variables"
docsTitle="Environment Variables"
rootClassName="gap-0"
className={twMerge(
'px-0 my-2',
availableEnvironmentVariables.length === 0 && 'gap-2',
)}
slotProps={{ submitButton: { className: 'hidden' } }}
>
<div className="grid grid-cols-2 lg:grid-cols-3 gap-2 border-b-1 border-gray-200 px-4 py-3">
<Text className="font-medium">Variable Name</Text>
<Text className="font-medium lg:col-span-2">Updated</Text>
</div>
<div className="grid grid-flow-row gap-2">
{availableEnvironmentVariables.length > 0 && (
<List>
{availableEnvironmentVariables.map((environmentVariable, index) => {
const timestamp = formatDistanceToNowStrict(
parseISO(environmentVariable.updatedAt),
{ addSuffix: true, roundingMethod: 'floor' },
);
return (
<Fragment key={environmentVariable.id}>
<ListItem.Root
className="px-4 grid grid-cols-2 lg:grid-cols-3 gap-2"
secondaryAction={
<Dropdown.Root>
<Dropdown.Trigger
asChild
hideChevron
className="absolute right-4 top-1/2 -translate-y-1/2"
>
<IconButton variant="borderless" color="secondary">
<DotsVerticalIcon />
</IconButton>
</Dropdown.Trigger>
<Dropdown.Content
menu
PaperProps={{ className: 'w-32' }}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
<Dropdown.Item
onClick={() =>
handleOpenEditor(environmentVariable)
}
>
<Text className="font-medium">Edit</Text>
</Dropdown.Item>
<Divider component="li" />
<Dropdown.Item
onClick={() =>
handleConfirmDelete(environmentVariable)
}
>
<Text
className="font-medium"
sx={{
color: (theme) => theme.palette.error.main,
}}
>
Delete
</Text>
</Dropdown.Item>
</Dropdown.Content>
</Dropdown.Root>
}
>
<ListItem.Text className="truncate">
{environmentVariable.name}
</ListItem.Text>
<Text
variant="subtitle1"
className="lg:col-span-2 truncate"
>
{timestamp === '0 seconds ago' ||
timestamp === 'in 0 seconds'
? 'Now'
: timestamp}
</Text>
</ListItem.Root>
<Divider
component="li"
className={twMerge(
index === availableEnvironmentVariables.length - 1
? '!mt-4'
: '!my-4',
)}
/>
</Fragment>
);
})}
</List>
)}
<Button
className="justify-self-start mx-4"
variant="borderless"
startIcon={<PlusIcon />}
onClick={handleOpenCreator}
>
Create Environment Variable
</Button>
</div>
</SettingsContainer>
);
}

View File

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

View File

@@ -0,0 +1,214 @@
import { useDialog } from '@/components/common/DialogProvider';
import InlineCode from '@/components/common/InlineCode';
import SettingsContainer from '@/components/settings/SettingsContainer';
import { useAppClient } from '@/hooks/useAppClient';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import Button from '@/ui/v2/Button';
import Divider from '@/ui/v2/Divider';
import IconButton from '@/ui/v2/IconButton';
import EyeIcon from '@/ui/v2/icons/EyeIcon';
import EyeOffIcon from '@/ui/v2/icons/EyeOffIcon';
import Input from '@/ui/v2/Input';
import List from '@/ui/v2/List';
import { ListItem } from '@/ui/v2/ListItem';
import Text from '@/ui/v2/Text';
import { LOCAL_HASURA_URL } from '@/utils/env';
import { generateAppServiceUrl, generateRemoteAppUrl } from '@/utils/helpers';
import { useGetAppInjectedVariablesQuery } from '@/utils/__generated__/graphql';
import { Fragment, useState } from 'react';
export default function SystemEnvironmentVariableSettings() {
const [showAdminSecret, setShowAdminSecret] = useState(false);
const [showWebhookSecret, setShowWebhookSecret] = useState(false);
const { openAlertDialog } = useDialog();
const { currentApplication } = useCurrentWorkspaceAndApplication();
const { data, loading, error } = useGetAppInjectedVariablesQuery({
variables: { id: currentApplication?.id },
});
const appClient = useAppClient({ start: false });
if (loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading system environment variables..."
/>
);
}
if (error) {
throw error;
}
function showJwtSecret() {
openAlertDialog({
title: 'Auth JWT Secret',
payload: (
<div className="grid grid-flow-row gap-2">
<Text variant="subtitle2">
This is the key used for generating JWTs. It&apos;s HMAC-SHA-based
and the same as configured in Hasura.
</Text>
<Input
defaultValue={data?.app?.hasuraGraphqlJwtSecret}
disabled
fullWidth
multiline
minRows={5}
hideEmptyHelperText
inputProps={{ className: 'font-mono' }}
/>
</div>
),
props: {
hidePrimaryAction: true,
secondaryButtonText: 'Close',
},
});
}
const systemEnvironmentVariables = [
{
key: 'NHOST_BACKEND_URL',
value: generateRemoteAppUrl(currentApplication.subdomain),
},
{ key: 'NHOST_SUBDOMAIN', value: currentApplication.subdomain },
{ key: 'NHOST_REGION', value: currentApplication.region.awsName },
{
key: 'NHOST_HASURA_URL',
value:
process.env.NEXT_PUBLIC_ENV === 'dev'
? LOCAL_HASURA_URL
: `${generateAppServiceUrl(
currentApplication.subdomain,
currentApplication.region.awsName,
'hasura',
)}/console`,
},
{ key: 'NHOST_AUTH_URL', value: appClient.auth.url },
{ key: 'NHOST_GRAPHQL_URL', value: appClient.graphql.url },
{ key: 'NHOST_STORAGE_URL', value: appClient.storage.url },
{ key: 'NHOST_FUNCTIONS_URL', value: appClient.functions.url },
];
return (
<SettingsContainer
title="System Environment Variables"
description="Environment Variables are key-value pairs configured outside your source code. They are used to store environment-specific values such as API keys."
docsLink="https://docs.nhost.io/platform/environment-variables#system-environment-variables"
rootClassName="gap-0"
className="px-0 mt-2 mb-2.5"
slotProps={{ submitButton: { className: 'invisible' } }}
>
<div className="grid grid-cols-3 border-b-1 gap-2 border-gray-200 px-4 py-3">
<Text className="font-medium">Variable Name</Text>
<Text className="font-medium lg:col-span-2">Value</Text>
</div>
<List>
<ListItem.Root className="px-4 grid grid-cols-2 lg:grid-cols-3 gap-2">
<ListItem.Text>NHOST_ADMIN_SECRET</ListItem.Text>
<div className="grid grid-flow-col lg:col-span-2 gap-2 items-center justify-start">
<Text className="text-greyscaleGreyDark truncate">
{showAdminSecret ? (
<InlineCode className="!text-sm font-medium max-h-[initial] h-[initial]">
{currentApplication?.hasuraGraphqlAdminSecret}
</InlineCode>
) : (
'●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●'
)}
</Text>
<IconButton
variant="borderless"
color="secondary"
aria-label={
showAdminSecret ? 'Hide Admin Secret' : 'Show Admin Secret'
}
onClick={() => setShowAdminSecret((show) => !show)}
>
{showAdminSecret ? (
<EyeOffIcon className="w-5 h-5" />
) : (
<EyeIcon className="w-5 h-5" />
)}
</IconButton>
</div>
</ListItem.Root>
<Divider component="li" className="!my-4" />
<ListItem.Root className="px-4 grid grid-cols-2 lg:grid-cols-3 gap-2">
<ListItem.Text>NHOST_WEBHOOK_SECRET</ListItem.Text>
<div className="grid grid-flow-col gap-2 lg:col-span-2 items-center justify-start">
<Text className="text-greyscaleGreyDark truncate">
{showWebhookSecret ? (
<InlineCode className="!text-sm font-medium max-h-[initial] h-[initial]">
{data?.app?.webhookSecret}
</InlineCode>
) : (
'●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●'
)}
</Text>
<IconButton
variant="borderless"
color="secondary"
aria-label={
showWebhookSecret
? 'Hide Webhook Secret'
: 'Show Webhook Secret'
}
onClick={() => setShowWebhookSecret((show) => !show)}
>
{showWebhookSecret ? (
<EyeOffIcon className="w-5 h-5" />
) : (
<EyeIcon className="w-5 h-5" />
)}
</IconButton>
</div>
</ListItem.Root>
<Divider component="li" className="!my-4" />
{systemEnvironmentVariables.map((environmentVariable, index) => (
<Fragment key={environmentVariable.key}>
<ListItem.Root className="px-4 grid grid-cols-2 lg:grid-cols-3 gap-2">
<ListItem.Text>{environmentVariable.key}</ListItem.Text>
<Text className="truncate lg:col-span-2">
{environmentVariable.value}
</Text>
</ListItem.Root>
{index !== systemEnvironmentVariables.length - 1 && (
<Divider className="!my-4" />
)}
</Fragment>
))}
<Divider component="li" className="!mt-4 !mb-2.5" />
<ListItem.Root className="px-4 grid grid-cols-2 lg:grid-cols-3 justify-start">
<ListItem.Text>NHOST_JWT_SECRET</ListItem.Text>
<Button
variant="borderless"
onClick={showJwtSecret}
size="small"
className="justify-self-start"
>
Show JWT Secret
</Button>
</ListItem.Root>
</List>
</SettingsContainer>
);
}

View File

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

View File

@@ -0,0 +1,141 @@
import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import Text from '@/ui/v2/Text';
import { useEffect } from 'react';
import { useFormContext } from 'react-hook-form';
import * as Yup from 'yup';
export interface BasePermissionVariableFormValues {
/**
* Permission variable key.
*/
key: string;
/**
* Permission variable value.
*/
value: string;
}
export interface BasePermissionVariableFormProps {
/**
* Function to be called when the form is submitted.
*/
onSubmit: (values: BasePermissionVariableFormValues) => void;
/**
* Function to be called when the operation is cancelled.
*/
onCancel?: VoidFunction;
/**
* Submit button text.
*
* @default 'Save'
*/
submitButtonText?: string;
}
export const basePermissionVariableValidationSchema = Yup.object({
key: Yup.string().required('This field is required.'),
value: Yup.string().required('This field is required.'),
});
export default function BasePermissionVariableForm({
onSubmit,
onCancel,
submitButtonText = 'Save',
}: BasePermissionVariableFormProps) {
const { onDirtyStateChange } = useDialog();
const form = useFormContext<BasePermissionVariableFormValues>();
const {
register,
formState: { dirtyFields, errors, isSubmitting },
} = form;
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, 'dialog');
}, [isDirty, onDirtyStateChange]);
return (
<div className="grid grid-flow-row gap-2 px-6 pb-6">
<Text variant="subtitle1" component="span">
Enter the field name and the path you want to use in this permission
variable.
</Text>
<Form onSubmit={onSubmit} className="grid grid-flow-row gap-4">
<Input
{...register('key', {
onChange: (event) => {
if (
event.target.value &&
!/^[a-zA-Z-]+$/gi.test(event.target.value)
) {
// we need to prevent invalid characters from being entered
// eslint-disable-next-line no-param-reassign
event.target.value = event.target.value.replace(
/[^a-zA-Z-]/gi,
'',
);
}
},
})}
id="key"
label="Field Name"
hideEmptyHelperText
error={!!errors.key}
helperText={errors?.key?.message}
fullWidth
autoComplete="off"
autoFocus
slotProps={{ input: { className: '!pl-px' } }}
startAdornment={
<Text className="shrink-0 pl-2 text-greyscaleGrey">X-Hasura-</Text>
}
/>
<Input
{...register('value', {
onChange: (event) => {
if (
event.target.value &&
!/^[a-zA-Z-_.[\]]+$/gi.test(event.target.value)
) {
// we need to prevent invalid characters from being entered
// eslint-disable-next-line no-param-reassign
event.target.value = event.target.value.replace(
/[^a-zA-Z-.[\]]/gi,
'',
);
}
},
})}
id="value"
label="Path"
hideEmptyHelperText
error={!!errors.value}
helperText={errors?.value?.message}
fullWidth
autoComplete="off"
slotProps={{ input: { className: '!pl-px' } }}
startAdornment={
<Text className="shrink-0 pl-2 text-greyscaleGrey">user.</Text>
}
/>
<div className="grid grid-flow-row gap-2">
<Button type="submit" loading={isSubmitting}>
{submitButtonText}
</Button>
<Button variant="outlined" color="secondary" onClick={onCancel}>
Cancel
</Button>
</div>
</Form>
</div>
);
}

View File

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

View File

@@ -0,0 +1,119 @@
import type {
BasePermissionVariableFormProps,
BasePermissionVariableFormValues,
} from '@/components/settings/permissions/BasePermissionVariableForm';
import BasePermissionVariableForm, {
basePermissionVariableValidationSchema,
} from '@/components/settings/permissions/BasePermissionVariableForm';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import getPermissionVariablesArray from '@/utils/settings/getPermissionVariablesArray';
import getPermissionVariablesObject from '@/utils/settings/getPermissionVariablesObject';
import { toastStyleProps } from '@/utils/settings/settingsConstants';
import {
useGetAppCustomClaimsQuery,
useUpdateAppMutation,
} from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
export interface CreatePermissionVariableFormProps
extends Pick<BasePermissionVariableFormProps, 'onCancel'> {
/**
* Function to be called when the form is submitted.
*/
onSubmit?: () => Promise<void>;
}
export default function CreatePermissionVariableForm({
onSubmit,
...props
}: CreatePermissionVariableFormProps) {
const { currentApplication } = useCurrentWorkspaceAndApplication();
const { data, error, loading } = useGetAppCustomClaimsQuery({
variables: { id: currentApplication?.id },
fetchPolicy: 'cache-only',
});
const form = useForm<BasePermissionVariableFormValues>({
defaultValues: {
key: '',
value: '',
},
reValidateMode: 'onSubmit',
resolver: yupResolver(basePermissionVariableValidationSchema),
});
const [updateApp] = useUpdateAppMutation({
refetchQueries: ['getAppCustomClaims'],
});
if (loading) {
return (
<ActivityIndicator delay={1000} label="Loading permission variables..." />
);
}
if (error) {
throw error;
}
const { setError } = form;
const availablePermissionVariables = getPermissionVariablesArray(
data?.app?.authJwtCustomClaims,
);
async function handleSubmit({
key,
value,
}: BasePermissionVariableFormValues) {
if (
availablePermissionVariables.some(
(permissionVariable) => permissionVariable.key === key,
)
) {
setError('key', { message: 'This key is already in use.' });
return;
}
const permissionVariablesObject = getPermissionVariablesObject(
availablePermissionVariables.filter(
(permissionVariable) => !permissionVariable.isSystemClaim,
),
);
const updateAppPromise = updateApp({
variables: {
id: currentApplication?.id,
app: {
authJwtCustomClaims: {
...permissionVariablesObject,
[key]: value,
},
},
},
});
await toast.promise(
updateAppPromise,
{
loading: 'Creating permission variable...',
success: 'Permission variable has been created successfully.',
error:
'An error occurred while trying to create the permission variable.',
},
toastStyleProps,
);
await onSubmit?.();
}
return (
<FormProvider {...form}>
<BasePermissionVariableForm onSubmit={handleSubmit} {...props} />
</FormProvider>
);
}

View File

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

View File

@@ -0,0 +1,142 @@
import type {
BasePermissionVariableFormProps,
BasePermissionVariableFormValues,
} from '@/components/settings/permissions/BasePermissionVariableForm';
import BasePermissionVariableForm, {
basePermissionVariableValidationSchema,
} from '@/components/settings/permissions/BasePermissionVariableForm';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import type { CustomClaim } from '@/types/application';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import getPermissionVariables from '@/utils/settings/getPermissionVariablesArray';
import getPermissionVariablesObject from '@/utils/settings/getPermissionVariablesObject';
import { toastStyleProps } from '@/utils/settings/settingsConstants';
import {
useGetAppCustomClaimsQuery,
useUpdateAppMutation,
} from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
export interface EditPermissionVariableFormProps
extends Pick<BasePermissionVariableFormProps, 'onCancel'> {
/**
* The permission variable to be edited.
*/
originalVariable: CustomClaim;
/**
* Function to be called when the form is submitted.
*/
onSubmit?: () => Promise<void>;
}
export default function EditPermissionVariableForm({
originalVariable,
onSubmit,
...props
}: EditPermissionVariableFormProps) {
const { currentApplication } = useCurrentWorkspaceAndApplication();
const { data, error, loading } = useGetAppCustomClaimsQuery({
variables: { id: currentApplication?.id },
fetchPolicy: 'cache-only',
});
const form = useForm<BasePermissionVariableFormValues>({
defaultValues: {
key: originalVariable.key || '',
value: originalVariable.value || '',
},
reValidateMode: 'onSubmit',
resolver: yupResolver(basePermissionVariableValidationSchema),
});
const [updateApp] = useUpdateAppMutation({
refetchQueries: ['getAppCustomClaims'],
});
if (loading) {
return (
<ActivityIndicator delay={1000} label="Loading permission variables..." />
);
}
if (error) {
throw error;
}
const { setError } = form;
const availablePermissionVariables = getPermissionVariables(
data?.app?.authJwtCustomClaims,
);
async function handleSubmit({
key,
value,
}: BasePermissionVariableFormValues) {
if (
availablePermissionVariables.some(
(permissionVariable) =>
permissionVariable.key === key &&
permissionVariable.key !== originalVariable.key,
)
) {
setError('key', { message: 'This key is already in use.' });
return;
}
const originalPermissionVariableIndex =
availablePermissionVariables.findIndex(
(permissionVariable) => permissionVariable.key === originalVariable.key,
);
const updatedPermissionVariables = availablePermissionVariables.map(
(permissionVariable, index) => {
if (index === originalPermissionVariableIndex) {
return { key, value };
}
return permissionVariable;
},
);
const permissionVariablesObject = getPermissionVariablesObject(
updatedPermissionVariables.filter(
(permissionVariable) => !permissionVariable.isSystemClaim,
),
);
const updateAppPromise = updateApp({
variables: {
id: currentApplication?.id,
app: {
authJwtCustomClaims: {
...permissionVariablesObject,
[key]: value,
},
},
},
});
await toast.promise(
updateAppPromise,
{
loading: 'Updating permission variable...',
success: 'Permission variable has been updated successfully.',
error:
'An error occurred while trying to update the permission variable.',
},
toastStyleProps,
);
await onSubmit?.();
}
return (
<FormProvider {...form}>
<BasePermissionVariableForm onSubmit={handleSubmit} {...props} />
</FormProvider>
);
}

View File

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

View File

@@ -0,0 +1,249 @@
import { useDialog } from '@/components/common/DialogProvider';
import SettingsContainer from '@/components/settings/SettingsContainer';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import type { CustomClaim } from '@/types/application';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import Button from '@/ui/v2/Button';
import Divider from '@/ui/v2/Divider';
import { Dropdown } from '@/ui/v2/Dropdown';
import IconButton from '@/ui/v2/IconButton';
import DotsVerticalIcon from '@/ui/v2/icons/DotsVerticalIcon';
import LockIcon from '@/ui/v2/icons/LockIcon';
import PlusIcon from '@/ui/v2/icons/PlusIcon';
import List from '@/ui/v2/List';
import { ListItem } from '@/ui/v2/ListItem';
import Text from '@/ui/v2/Text';
import Tooltip from '@/ui/v2/Tooltip';
import getPermissionVariables from '@/utils/settings/getPermissionVariablesArray';
import { toastStyleProps } from '@/utils/settings/settingsConstants';
import {
useGetAppCustomClaimsQuery,
useUpdateAppMutation,
} from '@/utils/__generated__/graphql';
import { Fragment } from 'react';
import toast from 'react-hot-toast';
import { twMerge } from 'tailwind-merge';
export interface PermissionVariableSettingsFormValues {
/**
* Permission variables.
*/
authJwtCustomClaims: CustomClaim[];
}
export default function PermissionVariableSettings() {
const { currentApplication } = useCurrentWorkspaceAndApplication();
const { openDialog, openAlertDialog } = useDialog();
const { data, loading, error } = useGetAppCustomClaimsQuery({
variables: {
id: currentApplication?.id,
},
});
const [updateApp] = useUpdateAppMutation({
refetchQueries: ['getAppCustomClaims'],
});
if (loading) {
return (
<ActivityIndicator delay={1000} label="Loading permission variables..." />
);
}
if (error) {
throw error;
}
async function handleDeleteVariable({ key }: CustomClaim) {
const filteredCustomClaims = Object.keys(
data?.app?.authJwtCustomClaims,
).filter((customClaimKey) => customClaimKey !== key);
const updateAppPromise = updateApp({
variables: {
id: currentApplication?.id,
app: {
authJwtCustomClaims: filteredCustomClaims.reduce(
(customClaims, currentKey) => ({
...customClaims,
[currentKey]: data?.app?.authJwtCustomClaims[currentKey],
}),
{},
),
},
},
});
await toast.promise(
updateAppPromise,
{
loading: 'Deleting permission variable...',
success: 'Permission variable has been deleted successfully.',
error: 'An error occurred while trying to delete permission variable.',
},
toastStyleProps,
);
}
function handleOpenCreator() {
openDialog('CREATE_PERMISSION_VARIABLE', {
title: 'Create Permission Variable',
props: {
titleProps: { className: '!pb-0' },
PaperProps: { className: 'max-w-sm' },
},
});
}
function handleOpenEditor(originalVariable: CustomClaim) {
openDialog('EDIT_PERMISSION_VARIABLE', {
title: 'Edit Permission Variable',
payload: { originalVariable },
props: {
titleProps: { className: '!pb-0' },
PaperProps: { className: 'max-w-sm' },
},
});
}
function handleConfirmDelete(originalVariable: CustomClaim) {
openAlertDialog({
title: 'Delete Permission Variable',
payload: (
<Text>
Are you sure you want to delete the &quot;
<strong>X-Hasura-{originalVariable.key}</strong>&quot; permission
variable? This cannot be undone.
</Text>
),
props: {
onPrimaryAction: () => handleDeleteVariable(originalVariable),
primaryButtonColor: 'error',
primaryButtonText: 'Delete',
},
});
}
const availablePermissionVariables = getPermissionVariables(
data?.app?.authJwtCustomClaims,
);
return (
<SettingsContainer
title="Permission Variables"
description="Permission variables are used to define permission rules in the GraphQL API."
docsLink="https://docs.nhost.io/graphql/permissions"
rootClassName="gap-0"
className="px-0 my-2"
slotProps={{ submitButton: { className: 'invisible' } }}
>
<div className="grid grid-cols-2 border-b-1 border-gray-200 px-4 py-3">
<Text className="font-medium">Field name</Text>
<Text className="font-medium">Path</Text>
</div>
<div className="grid grid-flow-row gap-2">
<List>
{availablePermissionVariables.map((customClaim, index) => (
<Fragment key={customClaim.key}>
<ListItem.Root
className="px-4 grid grid-cols-2"
secondaryAction={
<Dropdown.Root>
<Tooltip
title={
customClaim.isSystemClaim
? "You can't edit system permission variables"
: ''
}
placement="right"
disableHoverListener={!customClaim.isSystemClaim}
hasDisabledChildren={customClaim.isSystemClaim}
className="absolute right-4 top-1/2 -translate-y-1/2"
>
<Dropdown.Trigger asChild hideChevron>
<IconButton
variant="borderless"
color="secondary"
disabled={customClaim.isSystemClaim}
>
<DotsVerticalIcon />
</IconButton>
</Dropdown.Trigger>
</Tooltip>
<Dropdown.Content
menu
PaperProps={{ className: 'w-32' }}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
<Dropdown.Item
onClick={() => handleOpenEditor(customClaim)}
>
<Text className="font-medium">Edit</Text>
</Dropdown.Item>
<Divider component="li" />
<Dropdown.Item
onClick={() => handleConfirmDelete(customClaim)}
>
<Text
className="font-medium"
sx={{
color: (theme) => theme.palette.error.main,
}}
>
Delete
</Text>
</Dropdown.Item>
</Dropdown.Content>
</Dropdown.Root>
}
>
<ListItem.Text
primary={
<>
X-Hasura-{customClaim.key}{' '}
{customClaim.isSystemClaim && (
<LockIcon className="w-4 h-4" />
)}
</>
}
/>
<Text className="font-medium">user.{customClaim.value}</Text>
</ListItem.Root>
<Divider
component="li"
className={twMerge(
index === availablePermissionVariables.length - 1
? '!mt-4'
: '!my-4',
)}
/>
</Fragment>
))}
</List>
<Button
className="justify-self-start mx-4"
variant="borderless"
startIcon={<PlusIcon />}
onClick={handleOpenCreator}
>
Create Permission Variable
</Button>
</div>
</SettingsContainer>
);
}

View File

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

View File

@@ -0,0 +1,90 @@
import { useDialog } from '@/components/common/DialogProvider';
import Form from '@/components/common/Form';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import Text from '@/ui/v2/Text';
import { useEffect } from 'react';
import { useFormContext } from 'react-hook-form';
import * as Yup from 'yup';
export interface BaseRoleFormValues {
/**
* The name of the role.
*/
name: string;
}
export interface BaseRoleFormProps {
/**
* Function to be called when the form is submitted.
*/
onSubmit: (values: BaseRoleFormValues) => void;
/**
* Function to be called when the operation is cancelled.
*/
onCancel?: VoidFunction;
/**
* Submit button text.
*
* @default 'Save'
*/
submitButtonText?: string;
}
export const baseRoleFormValidationSchema = Yup.object({
name: Yup.string().required('This field is required.'),
});
export default function BaseRoleForm({
onSubmit,
onCancel,
submitButtonText = 'Save',
}: BaseRoleFormProps) {
const { onDirtyStateChange } = useDialog();
const form = useFormContext<BaseRoleFormValues>();
const {
register,
formState: { errors, dirtyFields, isSubmitting },
} = form;
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, 'dialog');
}, [isDirty, onDirtyStateChange]);
return (
<div className="grid grid-flow-row gap-2 px-6 pb-6">
<Text variant="subtitle1" component="span">
Enter the name for the role below.
</Text>
<Form onSubmit={onSubmit} className="grid grid-flow-row gap-4">
<Input
{...register('name')}
inputProps={{ maxLength: 100 }}
id="name"
label="Name"
placeholder="Enter value"
hideEmptyHelperText
error={!!errors.name}
helperText={errors?.name?.message}
fullWidth
autoComplete="off"
autoFocus
/>
<div className="grid grid-flow-row gap-2">
<Button type="submit" loading={isSubmitting}>
{submitButtonText}
</Button>
<Button variant="outlined" color="secondary" onClick={onCancel}>
Cancel
</Button>
</div>
</Form>
</div>
);
}

View File

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

View File

@@ -0,0 +1,95 @@
import type {
BaseRoleFormProps,
BaseRoleFormValues,
} from '@/components/settings/roles/BaseRoleForm';
import BaseRoleForm, {
baseRoleFormValidationSchema,
} from '@/components/settings/roles/BaseRoleForm';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import getUserRoles from '@/utils/settings/getUserRoles';
import { toastStyleProps } from '@/utils/settings/settingsConstants';
import {
useGetRolesQuery,
useUpdateAppMutation,
} from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
export interface CreateRoleFormProps
extends Pick<BaseRoleFormProps, 'onCancel'> {
/**
* Function to be called when the form is submitted.
*/
onSubmit?: () => Promise<void>;
}
export default function CreateRoleForm({
onSubmit,
...props
}: CreateRoleFormProps) {
const { currentApplication } = useCurrentWorkspaceAndApplication();
const { data, loading, error } = useGetRolesQuery({
variables: { id: currentApplication?.id },
fetchPolicy: 'cache-only',
});
const form = useForm<BaseRoleFormValues>({
defaultValues: {},
reValidateMode: 'onSubmit',
resolver: yupResolver(baseRoleFormValidationSchema),
});
const [updateApp] = useUpdateAppMutation({ refetchQueries: ['getRoles'] });
if (loading) {
return <ActivityIndicator delay={1000} label="Loading roles..." />;
}
if (error) {
throw error;
}
const { setError } = form;
const availableRoles = getUserRoles(data?.app?.authUserDefaultAllowedRoles);
async function handleSubmit({ name }: BaseRoleFormValues) {
if (availableRoles.some((role) => role.name === name)) {
setError('name', { message: 'This role already exists.' });
return;
}
const updateAppPromise = updateApp({
variables: {
id: currentApplication?.id,
app: {
authUserDefaultAllowedRoles: `${data?.app?.authUserDefaultAllowedRoles},${name}`,
},
},
});
await toast.promise(
updateAppPromise,
{
loading: 'Creating role...',
success: 'Role has been created successfully.',
error: 'An error occurred while trying to create the role.',
},
toastStyleProps,
);
await onSubmit?.();
}
return (
<FormProvider {...form}>
<BaseRoleForm
submitButtonText="Create"
onSubmit={handleSubmit}
{...props}
/>
</FormProvider>
);
}

View File

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

View File

@@ -0,0 +1,125 @@
import type {
BaseRoleFormProps,
BaseRoleFormValues,
} from '@/components/settings/roles/BaseRoleForm';
import BaseRoleForm, {
baseRoleFormValidationSchema,
} from '@/components/settings/roles/BaseRoleForm';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import type { Role } from '@/types/application';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import getUserRoles from '@/utils/settings/getUserRoles';
import { toastStyleProps } from '@/utils/settings/settingsConstants';
import {
useGetRolesQuery,
useUpdateAppMutation,
} from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
export interface EditRoleFormProps extends Pick<BaseRoleFormProps, 'onCancel'> {
/**
* The role to be edited.
*/
originalRole: Role;
/**
* Function to be called when the form is submitted.
*/
onSubmit?: () => Promise<void>;
}
export default function EditRoleForm({
originalRole,
onSubmit,
...props
}: EditRoleFormProps) {
const { currentApplication } = useCurrentWorkspaceAndApplication();
const { data, loading, error } = useGetRolesQuery({
variables: { id: currentApplication?.id },
fetchPolicy: 'cache-only',
});
const form = useForm<BaseRoleFormValues>({
defaultValues: {
name: originalRole.name || '',
},
reValidateMode: 'onSubmit',
resolver: yupResolver(baseRoleFormValidationSchema),
});
const [updateApp] = useUpdateAppMutation({
refetchQueries: ['getRoles'],
});
if (loading) {
return <ActivityIndicator delay={1000} label="Loading roles..." />;
}
if (error) {
throw error;
}
const { setError } = form;
const availableRoles = getUserRoles(data?.app?.authUserDefaultAllowedRoles);
async function handleSubmit({ name }: BaseRoleFormValues) {
if (
availableRoles.some(
(role) => role.name === name && role.name !== originalRole.name,
)
) {
setError('name', { message: 'This role already exists.' });
return;
}
const defaultAllowedRolesList =
data?.app?.authUserDefaultAllowedRoles.split(',') || [];
const originalRoleIndex = defaultAllowedRolesList.findIndex(
(role) => role.trim() === originalRole.name,
);
const updatedDefaultAllowedRoles = defaultAllowedRolesList
.map((role, index) => {
if (index === originalRoleIndex) {
return name;
}
return role;
})
.join(',');
const updateAppPromise = updateApp({
variables: {
id: currentApplication?.id,
app: {
authUserDefaultRole:
data?.app?.authUserDefaultRole === originalRole.name
? name
: data?.app?.authUserDefaultRole,
authUserDefaultAllowedRoles: updatedDefaultAllowedRoles,
},
},
});
await toast.promise(
updateAppPromise,
{
loading: 'Updating role...',
success: 'Role has been updated successfully.',
error: 'An error occurred while trying to update the role.',
},
toastStyleProps,
);
await onSubmit?.();
}
return (
<FormProvider {...form}>
<BaseRoleForm onSubmit={handleSubmit} {...props} />
</FormProvider>
);
}

View File

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

View File

@@ -0,0 +1,270 @@
import { useDialog } from '@/components/common/DialogProvider';
import SettingsContainer from '@/components/settings/SettingsContainer';
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
import type { Role } from '@/types/application';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import Button from '@/ui/v2/Button';
import Chip from '@/ui/v2/Chip';
import Divider from '@/ui/v2/Divider';
import { Dropdown } from '@/ui/v2/Dropdown';
import IconButton from '@/ui/v2/IconButton';
import DotsVerticalIcon from '@/ui/v2/icons/DotsVerticalIcon';
import LockIcon from '@/ui/v2/icons/LockIcon';
import PlusIcon from '@/ui/v2/icons/PlusIcon';
import List from '@/ui/v2/List';
import { ListItem } from '@/ui/v2/ListItem';
import Text from '@/ui/v2/Text';
import getUserRoles from '@/utils/settings/getUserRoles';
import { toastStyleProps } from '@/utils/settings/settingsConstants';
import {
useGetRolesQuery,
useUpdateAppMutation,
} from '@/utils/__generated__/graphql';
import { Fragment } from 'react';
import toast from 'react-hot-toast';
import { twMerge } from 'tailwind-merge';
export interface RoleSettingsFormValues {
/**
* Default role.
*/
authUserDefaultRole: string;
/**
* Allowed roles for the project.
*/
authUserDefaultAllowedRoles: Role[];
}
export default function RoleSettings() {
const { currentApplication } = useCurrentWorkspaceAndApplication();
const { openDialog, openAlertDialog } = useDialog();
const { data, loading, error } = useGetRolesQuery({
variables: { id: currentApplication?.id },
});
const [updateApp] = useUpdateAppMutation({
refetchQueries: ['getRoles'],
});
if (loading) {
return <ActivityIndicator delay={1000} label="Loading user roles..." />;
}
if (error) {
throw error;
}
async function handleSetAsDefault({ name }: Role) {
const updateAppPromise = updateApp({
variables: {
id: currentApplication?.id,
app: {
authUserDefaultRole: name,
},
},
});
await toast.promise(
updateAppPromise,
{
loading: 'Updating default role...',
success: 'Default role has been updated successfully.',
error: 'An error occurred while trying to update the default role.',
},
toastStyleProps,
);
}
async function handleDeleteRole({ name }: Role) {
const filteredRoles = data?.app?.authUserDefaultAllowedRoles
.split(',')
.filter((role) => role !== name)
.join(',');
const updateAppPromise = updateApp({
variables: {
id: currentApplication?.id,
app: {
authUserDefaultAllowedRoles: filteredRoles,
authUserDefaultRole:
name === data?.app?.authUserDefaultRole
? 'user'
: data?.app?.authUserDefaultRole,
},
},
});
await toast.promise(
updateAppPromise,
{
loading: 'Deleting role...',
success: 'Role has been deleted successfully.',
error: 'An error occurred while trying to delete the role.',
},
toastStyleProps,
);
}
function handleOpenCreator() {
openDialog('CREATE_ROLE', {
title: 'Create Role',
props: {
titleProps: { className: '!pb-0' },
PaperProps: { className: 'max-w-sm' },
},
});
}
function handleOpenEditor(originalRole: Role) {
openDialog('EDIT_ROLE', {
title: 'Edit Role',
payload: { originalRole },
props: {
titleProps: { className: '!pb-0' },
PaperProps: { className: 'max-w-sm' },
},
});
}
function handleConfirmDelete(originalRole: Role) {
openAlertDialog({
title: 'Delete Role',
payload: (
<Text>
Are you sure you want to delete the &quot;
<strong>{originalRole.name}</strong>&quot; role? This cannot be
undone.
</Text>
),
props: {
onPrimaryAction: () => handleDeleteRole(originalRole),
primaryButtonColor: 'error',
primaryButtonText: 'Delete',
},
});
}
const availableRoles = getUserRoles(data?.app?.authUserDefaultAllowedRoles);
return (
<SettingsContainer
title="Roles"
description="Roles are used to control access to your application."
docsLink="https://docs.nhost.io/authentication/users#roles"
rootClassName="gap-0"
className="px-0 my-2"
slotProps={{ submitButton: { className: 'invisible' } }}
>
<div className="border-b-1 border-gray-200 px-4 py-3">
<Text className="font-medium">Name</Text>
</div>
<div className="grid grid-flow-row gap-2">
<List>
{availableRoles.map((role, index) => (
<Fragment key={role.name}>
<ListItem.Root
className="px-4"
secondaryAction={
<Dropdown.Root>
<Dropdown.Trigger
asChild
hideChevron
className="absolute right-4 top-1/2 -translate-y-1/2"
>
<IconButton variant="borderless" color="secondary">
<DotsVerticalIcon />
</IconButton>
</Dropdown.Trigger>
<Dropdown.Content
menu
PaperProps={{ className: 'w-32' }}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
<Dropdown.Item onClick={() => handleSetAsDefault(role)}>
<Text className="font-medium">Set as Default</Text>
</Dropdown.Item>
<Divider component="li" />
<Dropdown.Item
disabled={role.isSystemRole}
onClick={() => handleOpenEditor(role)}
>
<Text className="font-medium">Edit</Text>
</Dropdown.Item>
<Divider component="li" />
<Dropdown.Item
disabled={role.isSystemRole}
onClick={() => handleConfirmDelete(role)}
>
<Text
className="font-medium"
sx={{
color: (theme) => theme.palette.error.main,
}}
>
Delete
</Text>
</Dropdown.Item>
</Dropdown.Content>
</Dropdown.Root>
}
>
<ListItem.Text
primaryTypographyProps={{
className:
'inline-grid grid-flow-col gap-1 items-center h-6 font-medium',
}}
primary={
<>
{role.name}
{role.isSystemRole && <LockIcon className="w-4 h-4" />}
{data?.app?.authUserDefaultRole === role.name && (
<Chip
component="span"
color="info"
size="small"
label="Default"
/>
)}
</>
}
/>
</ListItem.Root>
<Divider
component="li"
className={twMerge(
index === availableRoles.length - 1 ? '!mt-4' : '!my-4',
)}
/>
</Fragment>
))}
</List>
<Button
className="justify-self-start mx-4"
variant="borderless"
startIcon={<PlusIcon />}
onClick={handleOpenCreator}
>
Create Role
</Button>
</div>
</SettingsContainer>
);
}

View File

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

View File

@@ -79,7 +79,7 @@ export default function AnonymousSignInSettings() {
<Form onSubmit={handlePasswordProtectionSettingsChange}>
<SettingsContainer
title="Anonymous Users"
description="Allow users to sign-in anonymously."
description="Allow users to sign in anonymously."
primaryActionButtonProps={{
disabled:
form.formState.isSubmitting ||

View File

@@ -11,7 +11,7 @@ import CopyIcon from '@/ui/v2/icons/CopyIcon';
import Input from '@/ui/v2/Input';
import InputAdornment from '@/ui/v2/InputAdornment';
import { copy } from '@/utils/copy';
import { generateRemoteAppUrl } from '@/utils/helpers';
import { generateAppServiceUrl } from '@/utils/helpers';
import { toastStyleProps } from '@/utils/settings/settingsConstants';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
@@ -23,7 +23,6 @@ export interface AppleProviderFormValues {
authAppleKeyId: string;
authAppleClientId: string;
authApplePrivateKey: string;
authAppleScope: string;
}
export default function AppleProviderSettings() {
@@ -38,7 +37,6 @@ export default function AppleProviderSettings() {
authAppleKeyId,
authAppleClientId,
authApplePrivateKey,
authAppleScope,
},
},
loading,
@@ -57,7 +55,6 @@ export default function AppleProviderSettings() {
authAppleKeyId,
authAppleClientId,
authApplePrivateKey,
authAppleScope,
authAppleEnabled,
},
});
@@ -83,9 +80,7 @@ export default function AppleProviderSettings() {
const updateAppMutation = updateApp({
variables: {
id: currentApplication.id,
app: {
...values,
},
app: values,
},
});
@@ -96,7 +91,7 @@ export default function AppleProviderSettings() {
success: `Apple settings have been updated successfully.`,
error: `An error occurred while trying to update the project's Apple settings.`,
},
{ ...toastStyleProps },
toastStyleProps,
);
form.reset(values);
@@ -107,10 +102,12 @@ export default function AppleProviderSettings() {
<Form onSubmit={handleProviderUpdate}>
<SettingsContainer
title="Apple"
description="Allows users to sign in with Apple."
primaryActionButtonProps={{
disabled: !formState.isValid || !formState.isDirty,
loading: formState.isSubmitting,
description="Allow users to sign in with Apple."
slotProps={{
submitButton: {
disabled: !formState.isValid || !formState.isDirty,
loading: formState.isSubmitting,
},
}}
docsLink="https://docs.nhost.io/authentication/sign-in-with-apple"
docsTitle="how to sign in users with Apple"
@@ -134,9 +131,9 @@ export default function AppleProviderSettings() {
hideEmptyHelperText
/>
<Input
{...register('authAppleScope')}
name="authAppleScope"
id="authAppleScope"
{...register('authAppleClientId')}
name="authAppleClientId"
id="authAppleClientId"
label="Service ID"
placeholder="Apple Service ID"
className="col-span-1"
@@ -168,9 +165,11 @@ export default function AppleProviderSettings() {
<Input
name="redirectUrl"
id="redirectUrl"
placeholder={`${generateRemoteAppUrl(
defaultValue={`${generateAppServiceUrl(
currentApplication.subdomain,
)}/v1/auth/signin/provider/apple/callback`}
currentApplication.region.awsName,
'auth',
)}/v1/signin/provider/apple/callback`}
className="col-span-2"
fullWidth
hideEmptyHelperText
@@ -185,9 +184,11 @@ export default function AppleProviderSettings() {
onClick={(e) => {
e.stopPropagation();
copy(
`${generateRemoteAppUrl(
`${generateAppServiceUrl(
currentApplication.subdomain,
)}/v1/auth/signin/provider/apple/callback`,
currentApplication.region.awsName,
'auth',
)}/v1/signin/provider/apple/callback`,
'Redirect URL',
);
}}

View File

@@ -13,7 +13,7 @@ import CopyIcon from '@/ui/v2/icons/CopyIcon';
import Input from '@/ui/v2/Input';
import InputAdornment from '@/ui/v2/InputAdornment';
import { copy } from '@/utils/copy';
import { generateRemoteAppUrl } from '@/utils/helpers';
import { generateAppServiceUrl } from '@/utils/helpers';
import { toastStyleProps } from '@/utils/settings/settingsConstants';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
@@ -88,7 +88,7 @@ export default function DiscordProviderSettings() {
<Form onSubmit={handleProviderUpdate}>
<SettingsContainer
title="Discord"
description="Allows users to sign in with Discord."
description="Allow users to sign in with Discord."
primaryActionButtonProps={{
disabled: !formState.isValid || !formState.isDirty,
loading: formState.isSubmitting,
@@ -112,9 +112,11 @@ export default function DiscordProviderSettings() {
fullWidth
hideEmptyHelperText
label="Redirect URL"
value={`${generateRemoteAppUrl(
defaultValue={`${generateAppServiceUrl(
currentApplication.subdomain,
)}/v1/auth/signin/provider/discord/callback`}
currentApplication.region.awsName,
'auth',
)}/v1/signin/provider/discord/callback`}
disabled
endAdornment={
<InputAdornment position="end" className="absolute right-2">
@@ -125,9 +127,11 @@ export default function DiscordProviderSettings() {
onClick={(e) => {
e.stopPropagation();
copy(
`${generateRemoteAppUrl(
`${generateAppServiceUrl(
currentApplication.subdomain,
)}/v1/auth/signin/provider/discord/callback`,
currentApplication.region.awsName,
'auth',
)}/v1/signin/provider/discord/callback`,
'Redirect URL',
);
}}

View File

@@ -23,7 +23,7 @@ export interface EmailAndPasswordFormValues {
authPasswordHibpEnabled: boolean;
}
export default function EmailSettings() {
export default function EmailAndPasswordSettings() {
const { currentApplication } = useCurrentWorkspaceAndApplication();
const [updateApp] = useUpdateAppMutation({
refetchQueries: [GetAppLoginDataDocument],
@@ -61,7 +61,7 @@ export default function EmailSettings() {
const { formState } = form;
const handleEmailSettingsChange = async (
const handleEmailAndPasswordSettingsChange = async (
values: EmailAndPasswordFormValues,
) => {
const updateAppMutation = updateApp({
@@ -90,29 +90,31 @@ export default function EmailSettings() {
return (
<FormProvider {...form}>
<Form onSubmit={handleEmailSettingsChange}>
<Form onSubmit={handleEmailAndPasswordSettingsChange}>
<SettingsContainer
title="Email and Password"
description="Sign in users using email and password."
description="Allow users to sign in with email and password."
docsLink="https://docs.nhost.io/authentication/sign-in-with-email-and-password"
docsTitle="how to sign in users with email and password"
className="grid grid-flow-row"
showSwitch
enabled
switchProps={{ disabled: true }}
primaryActionButtonProps={{
disabled: !formState.isValid || !formState.isDirty,
loading: formState.isSubmitting,
slotProps={{
switch: {
disabled: true,
},
submitButton: {
disabled: !formState.isValid || !formState.isDirty,
loading: formState.isSubmitting,
},
}}
>
<ControlledCheckbox
name="authEmailSigninEmailVerifiedRequired"
id="authEmailSigninEmailVerifiedRequired"
label={
<span className="inline-grid grid-flow-row gap-y-[2px] text-[15px]">
<span className="text-[15px] font-medium">
Require Verified Emails
</span>
<span className="inline-grid grid-flow-row gap-y-0.5 text-sm+">
<span className="font-medium">Require Verified Emails</span>
<span className="font-normal text-greyscaleMedium">
Users must verify their email to be able to sign in.
</span>
@@ -124,11 +126,9 @@ export default function EmailSettings() {
name="authPasswordHibpEnabled"
id="authPasswordHibpEnabled"
label={
<span className="inline-grid grid-flow-row gap-y-[2px] text-[15px]">
<span className="text-[15px] font-medium">
Password Protection
</span>
<span className="text-[12px] font-normal text-greyscaleMedium">
<span className="inline-grid grid-flow-row gap-y-0.5 text-sm+">
<span className="font-medium">Password Protection</span>
<span className="font-normal text-greyscaleMedium">
Passwords must pass haveibeenpwned.com during sign-up.
</span>
</span>

View File

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

View File

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

View File

@@ -13,7 +13,7 @@ import CopyIcon from '@/ui/v2/icons/CopyIcon';
import Input from '@/ui/v2/Input';
import InputAdornment from '@/ui/v2/InputAdornment';
import { copy } from '@/utils/copy';
import { generateRemoteAppUrl } from '@/utils/helpers';
import { generateAppServiceUrl } from '@/utils/helpers';
import { toastStyleProps } from '@/utils/settings/settingsConstants';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
@@ -88,7 +88,7 @@ export default function FacebookProviderSettings() {
<Form onSubmit={handleProviderUpdate}>
<SettingsContainer
title="Facebook"
description="Allows users to sign in with Facebook."
description="Allow users to sign in with Facebook."
primaryActionButtonProps={{
disabled: !formState.isValid || !formState.isDirty,
loading: formState.isSubmitting,
@@ -112,9 +112,11 @@ export default function FacebookProviderSettings() {
fullWidth
hideEmptyHelperText
label="Redirect URL"
value={`${generateRemoteAppUrl(
defaultValue={`${generateAppServiceUrl(
currentApplication.subdomain,
)}/v1/auth/signin/provider/facebook/callback`}
currentApplication.region.awsName,
'auth',
)}/v1/signin/provider/facebook/callback`}
disabled
endAdornment={
<InputAdornment position="end" className="absolute right-2">
@@ -125,9 +127,11 @@ export default function FacebookProviderSettings() {
onClick={(e) => {
e.stopPropagation();
copy(
`${generateRemoteAppUrl(
`${generateAppServiceUrl(
currentApplication.subdomain,
)}/v1/auth/signin/provider/facebook/callback`,
currentApplication.region.awsName,
'auth',
)}/v1/signin/provider/facebook/callback`,
'Redirect URL',
);
}}

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