Compare commits
117 Commits
@nhost/rea
...
@nhost/das
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cec04bd6f | ||
|
|
a448d7d182 | ||
|
|
948048940e | ||
|
|
5e91221d5a | ||
|
|
7278991a59 | ||
|
|
5924bc3248 | ||
|
|
c5ad634799 | ||
|
|
426b93a19f | ||
|
|
026f84f466 | ||
|
|
384fac00b1 | ||
|
|
7e9a2ce136 | ||
|
|
076fd4a7c0 | ||
|
|
9525fd74b3 | ||
|
|
8a2bc98214 | ||
|
|
dd5d262062 | ||
|
|
09962bef37 | ||
|
|
9cdecb6b23 | ||
|
|
e7eb90318e | ||
|
|
f67f22d321 | ||
|
|
87ae23ba05 | ||
|
|
b2be3642aa | ||
|
|
1230081ce6 | ||
|
|
c195c517de | ||
|
|
6f419be2c1 | ||
|
|
93ebdf844f | ||
|
|
bcd889b53a | ||
|
|
992939cdcd | ||
|
|
3c31657c50 | ||
|
|
a654d536e0 | ||
|
|
91c2bb6f53 | ||
|
|
9f2bf9ec2b | ||
|
|
d62bd0fc9a | ||
|
|
768ca17494 | ||
|
|
943831fe2e | ||
|
|
f242e4b92f | ||
|
|
863b37d313 | ||
|
|
c8a8d4fca3 | ||
|
|
311374e3fb | ||
|
|
e40a4529b4 | ||
|
|
1623e9bd20 | ||
|
|
5c47e8e675 | ||
|
|
9f9f1c64f4 | ||
|
|
981404f0b9 | ||
|
|
4ad27e9d72 | ||
|
|
778946998a | ||
|
|
6c11b75807 | ||
|
|
2dc031d16c | ||
|
|
40bd3e4572 | ||
|
|
6cb2b6331a | ||
|
|
08a7dd9894 | ||
|
|
f0a994a26e | ||
|
|
4fbd6bd4fa | ||
|
|
67fc77486c | ||
|
|
4f3fb3446e | ||
|
|
49a80c22be | ||
|
|
28676f4cdc | ||
|
|
e03f14133c | ||
|
|
150c04a4f4 | ||
|
|
bccd67b1b1 | ||
|
|
b14fd2f14c | ||
|
|
68b3d23cd9 | ||
|
|
d86e5c9c16 | ||
|
|
b2cc1411d7 | ||
|
|
407feeac37 | ||
|
|
7b25c37c26 | ||
|
|
6df4f02e95 | ||
|
|
aaae98f019 | ||
|
|
dc23dc0f49 | ||
|
|
82728da100 | ||
|
|
2d68fee54c | ||
|
|
35010353c7 | ||
|
|
aff059ec71 | ||
|
|
713d53cfc0 | ||
|
|
e0ab6d9a37 | ||
|
|
7baee8a9cc | ||
|
|
3db2999f60 | ||
|
|
3c4dd55045 | ||
|
|
92b434e840 | ||
|
|
13d359602f | ||
|
|
0d8d0eb10f | ||
|
|
ed9df85778 | ||
|
|
41617b970a | ||
|
|
c5c904b716 | ||
|
|
7db095fe92 | ||
|
|
f33e07b191 | ||
|
|
017f1a6c7b | ||
|
|
93957c8af3 | ||
|
|
2505b2e26b | ||
|
|
5f4b4d2acc | ||
|
|
71a8ce4446 | ||
|
|
5ef5189898 | ||
|
|
791b7295fb | ||
|
|
25bc4b7fd6 | ||
|
|
da20159ec5 | ||
|
|
2ae5ea8bc1 | ||
|
|
3ba485e582 | ||
|
|
e5bab6a061 | ||
|
|
be64353145 | ||
|
|
2f5913c78d | ||
|
|
757ddd901c | ||
|
|
1a61c658a7 | ||
|
|
d3d14245c7 | ||
|
|
53d2f9d3e0 | ||
|
|
8c34c69e79 | ||
|
|
b19ffed273 | ||
|
|
859efa988a | ||
|
|
3202b6b897 | ||
|
|
ba73bb4003 | ||
|
|
d5337ff5bd | ||
|
|
511ab19755 | ||
|
|
7c2a1c29fd | ||
|
|
5c9b8f0a3f | ||
|
|
b3f1f5f6ea | ||
|
|
6b8aad5c84 | ||
|
|
c36132c9bb | ||
|
|
b18edc0532 | ||
|
|
1d55d3ea38 |
82
.github/workflows/gen_schedule_update_deps.yaml
vendored
Normal file
82
.github/workflows/gen_schedule_update_deps.yaml
vendored
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
name: "gen: update depenendencies"
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 2 1 * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Configure aws
|
||||||
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
|
with:
|
||||||
|
role-to-assume: arn:aws:iam::${{ secrets.AWS_PRODUCTION_CORE_ACCOUNT_ID }}:role/github-actions-nhost-${{ github.event.repository.name }}
|
||||||
|
aws-region: eu-central-1
|
||||||
|
|
||||||
|
- uses: nixbuild/nix-quick-install-action@v26
|
||||||
|
with:
|
||||||
|
nix_version: 2.16.2
|
||||||
|
nix_conf: |
|
||||||
|
experimental-features = nix-command flakes
|
||||||
|
sandbox = false
|
||||||
|
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||||
|
substituters = https://cache.nixos.org/?priority=40 s3://nhost-nix-cache?region=eu-central-1&priority=50
|
||||||
|
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= ${{ secrets.NIX_CACHE_PUB_KEY }}
|
||||||
|
|
||||||
|
- name: Cache nix store
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: /nix
|
||||||
|
key: nix-update-deps-${{ hashFiles('flakes.nix', 'flake.lock') }}
|
||||||
|
|
||||||
|
- name: Update nix flakes
|
||||||
|
run: nix flake update
|
||||||
|
|
||||||
|
- name: Update dependencies
|
||||||
|
run: |
|
||||||
|
nix develop -c bash -c "
|
||||||
|
pnpm dedupe
|
||||||
|
pnpm update -r
|
||||||
|
pnpm dedupe
|
||||||
|
"
|
||||||
|
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@v6
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
commit-message: Update dependencies
|
||||||
|
committer: GitHub <noreply@github.com>
|
||||||
|
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||||
|
signoff: false
|
||||||
|
branch: automated/update-deps
|
||||||
|
delete-branch: true
|
||||||
|
title: '[Scheduled] Update dependencies'
|
||||||
|
body: |
|
||||||
|
Dependencies updated
|
||||||
|
|
||||||
|
Note - If you see this PR and the checks haven't run, close and reopen the PR. See https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs
|
||||||
|
labels: |
|
||||||
|
dependencies
|
||||||
|
draft: false
|
||||||
|
|
||||||
|
- name: "Cache nix store on s3"
|
||||||
|
run: |
|
||||||
|
echo ${{ secrets.NIX_CACHE_PRIV_KEY }} > cache-priv-key.pem
|
||||||
|
nix build .\#devShells.x86_64-linux.default
|
||||||
|
nix store sign --key-file cache-priv-key.pem --all
|
||||||
|
nix copy --to s3://nhost-nix-cache\?region=eu-central-1 .\#devShells.x86_64-linux.default
|
||||||
|
|
||||||
|
- run: rm cache-priv-key.pem
|
||||||
|
if: always()
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -19,7 +19,7 @@ logs/
|
|||||||
coverage/
|
coverage/
|
||||||
dist/
|
dist/
|
||||||
umd/
|
umd/
|
||||||
node_modules/
|
node_modules
|
||||||
tmp/
|
tmp/
|
||||||
.pnpm-store
|
.pnpm-store
|
||||||
.turbo
|
.turbo
|
||||||
|
|||||||
7
SECURITY.md
Normal file
7
SECURITY.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
At Nhost, we take security vulnerabilities seriously and appreciate the assistance of the community in bringing any issues to our attention. If you discover a security vulnerability, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/nhost/nhost/security/advisories/new) tab.
|
||||||
|
|
||||||
|
Once you have submitted the report, we will promptly conduct a thorough investigation within 72 hours. In case we need further information, we may contact you for additional details. Rest assured that addressing the reported vulnerability in a timely manner is our top priority.
|
||||||
3
config/.husky/pre-commit
Executable file → Normal file
3
config/.husky/pre-commit
Executable file → Normal file
@@ -1,4 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
[ -n "$CI" ] && exit 0
|
||||||
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
pnpm dlx lint-staged --config config/.lintstagedrc.js
|
pnpm dlx lint-staged --config config/.lintstagedrc.js
|
||||||
|
|||||||
@@ -1,5 +1,211 @@
|
|||||||
# @nhost/dashboard
|
# @nhost/dashboard
|
||||||
|
|
||||||
|
## 1.14.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- a448d7d: feat: allow configuring postmark and delete SMTP settings
|
||||||
|
|
||||||
|
## 1.13.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 5924bc3: fix: include password in `GetSmtpSettings` query
|
||||||
|
- c5ad634: fix: resolved an issue where one-click install links were broken on Safari
|
||||||
|
- 7278991: fix: update graphql auto-embeddings configuration to use String type for model field
|
||||||
|
|
||||||
|
## 1.13.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 026f84f: fix: use configuration server URL from environment variable
|
||||||
|
|
||||||
|
## 1.13.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 7e9a2ce: fix: resolve issue where run services form fails to open
|
||||||
|
|
||||||
|
## 1.13.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- dd5d262: feat: add model field to the auto-embeddings form
|
||||||
|
- 09962be: feat: enable settings and run services when running the dashboard locally
|
||||||
|
- 9cdecb6: feat: enable users to update their email address from the account settings page
|
||||||
|
|
||||||
|
## 1.12.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- c195c51: fix: send email upon signin for unverified users
|
||||||
|
|
||||||
|
## 1.12.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 93ebdf8: fix: use service urls when initilizaing NhostClient running local dashboard
|
||||||
|
- @nhost/react-apollo@11.0.1
|
||||||
|
- @nhost/nextjs@2.1.10
|
||||||
|
|
||||||
|
## 1.12.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- f242e4b: feat: add connect with github to the user's account settings
|
||||||
|
- 768ca17: chore: update dependencies
|
||||||
|
- d62bd0f: fix: "Track this" option within the SQL editor now correctly updates the metadata
|
||||||
|
- 91c2bb6: feat: refactor sign-in and sign-up pages to enforce email verification
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 943831f: fix: resolve an error toast issue when unpausing a project
|
||||||
|
- Updated dependencies [768ca17]
|
||||||
|
- @nhost/react-apollo@11.0.0
|
||||||
|
- @nhost/nextjs@2.1.9
|
||||||
|
|
||||||
|
## 1.11.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@10.0.2
|
||||||
|
- @nhost/nextjs@2.1.8
|
||||||
|
|
||||||
|
## 1.11.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 981404f: fix: set default value for healthCheck field validation
|
||||||
|
|
||||||
|
## 1.11.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 7789469: chore: upgrade dependency `@graphql-codegen/cli` to `5.0.2` to address vulnerability
|
||||||
|
- 6c11b75: feat: add update user displayName section in account settings
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@10.0.1
|
||||||
|
- @nhost/nextjs@2.1.7
|
||||||
|
|
||||||
|
## 1.10.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 49a80c2: chore: update dependencies
|
||||||
|
- 150c04a: feat: add healthcheck config to run services
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- e03f141: fix: allow insert, update and delete on tables in `auth` and `storage` schemas
|
||||||
|
- 28676f4: feat: add min postgres version check to enable the ai service
|
||||||
|
- Updated dependencies [49a80c2]
|
||||||
|
- @nhost/react-apollo@10.0.0
|
||||||
|
- @nhost/nextjs@2.1.6
|
||||||
|
|
||||||
|
## 1.9.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- d86e5c9: feat: add support for filtering the logs using a RegExp
|
||||||
|
|
||||||
|
## 1.8.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@9.0.3
|
||||||
|
- @nhost/nextjs@2.1.5
|
||||||
|
|
||||||
|
## 1.8.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 6df4f02: fix: use custom error toast and show correct message when sending an invite
|
||||||
|
|
||||||
|
## 1.8.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@9.0.2
|
||||||
|
- @nhost/nextjs@2.1.4
|
||||||
|
|
||||||
|
## 1.8.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 713d53c: feat: add catch-all route for workspace/project - useful for documentation
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 3db2999: fix: refresh table list after running SQL using the editor
|
||||||
|
- 3c4dd55: fix: handle `Error` objects properly in the `ErrorToast` component
|
||||||
|
- 92b434e: fix: resolve an issue where the checkbox in the data-grid header did not select all rows
|
||||||
|
- @nhost/react-apollo@9.0.1
|
||||||
|
- @nhost/nextjs@2.1.3
|
||||||
|
|
||||||
|
## 1.7.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 0d8d0eb: Update docs and dashboard references
|
||||||
|
|
||||||
|
## 1.6.9
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@9.0.0
|
||||||
|
- @nhost/nextjs@2.1.2
|
||||||
|
|
||||||
|
## 1.6.8
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@8.0.1
|
||||||
|
- @nhost/nextjs@2.1.1
|
||||||
|
|
||||||
|
## 1.6.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 5ef5189: fix: update `@apollo/client` to `3.9.4` to fix a cache bug
|
||||||
|
|
||||||
|
## 1.6.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 3ba485e: fix: added discord.com to connect-src
|
||||||
|
- e5bab6a: chore: update dependencies
|
||||||
|
- Updated dependencies [b19ffed]
|
||||||
|
- Updated dependencies [e5bab6a]
|
||||||
|
- @nhost/nextjs@2.1.0
|
||||||
|
- @nhost/react-apollo@8.0.0
|
||||||
|
|
||||||
|
## 1.6.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- ba73bb4: fix: update ErrorToast component to show the internal graphql error
|
||||||
|
- d5337ff: fix: utilize accumulator in the creation of validation schema within data grid utils
|
||||||
|
|
||||||
|
## 1.6.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 7c2a1c2: feat: show error and debug info in the error toast
|
||||||
|
|
||||||
|
## 1.6.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 6b8aad5: fix: add bare nhost.run to CSP
|
||||||
|
|
||||||
|
## 1.6.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- b18edc0: feat: added CSP and X-Frame-Options
|
||||||
|
|
||||||
## 1.6.1
|
## 1.6.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ ENV NEXT_PUBLIC_NHOST_STORAGE_URL __NEXT_PUBLIC_NHOST_STORAGE_URL__
|
|||||||
ENV NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL __NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__
|
ENV NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL __NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__
|
||||||
ENV NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL __NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__
|
ENV NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL __NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__
|
||||||
ENV NEXT_PUBLIC_NHOST_HASURA_API_URL __NEXT_PUBLIC_NHOST_HASURA_API_URL__
|
ENV NEXT_PUBLIC_NHOST_HASURA_API_URL __NEXT_PUBLIC_NHOST_HASURA_API_URL__
|
||||||
|
ENV NEXT_PUBLIC_NHOST_CONFIGSERVER_URL __NEXT_PUBLIC_NHOST_CONFIGSERVER_URL__
|
||||||
|
|
||||||
RUN yarn global add pnpm@8.10.5
|
RUN yarn global add pnpm@8.10.5
|
||||||
COPY .gitignore .gitignore
|
COPY .gitignore .gitignore
|
||||||
|
|||||||
@@ -21,5 +21,6 @@ find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_STORAGE_URL__~${NEXT_
|
|||||||
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__~${NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL}~g" {} +
|
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__~${NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL}~g" {} +
|
||||||
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__~${NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL}~g" {} +
|
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__~${NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL}~g" {} +
|
||||||
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_API_URL__~${NEXT_PUBLIC_NHOST_HASURA_API_URL}~g" {} +
|
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_API_URL__~${NEXT_PUBLIC_NHOST_HASURA_API_URL}~g" {} +
|
||||||
|
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_CONFIGSERVER_URL__~${NEXT_PUBLIC_NHOST_CONFIGSERVER_URL}~g" {} +
|
||||||
|
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|||||||
@@ -4,6 +4,20 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
|||||||
});
|
});
|
||||||
const { version } = require('./package.json');
|
const { version } = require('./package.json');
|
||||||
|
|
||||||
|
const cspHeader = `
|
||||||
|
default-src 'self' *.nhost.run ws://*.nhost.run nhost.run ws://nhost.run;
|
||||||
|
script-src 'self' 'unsafe-eval' 'unsafe-inline' cdn.segment.com js.stripe.com;
|
||||||
|
connect-src 'self' *.nhost.run ws://*.nhost.run nhost.run ws://nhost.run discord.com;
|
||||||
|
style-src 'self' 'unsafe-inline';
|
||||||
|
img-src 'self' blob: data: avatars.githubusercontent.com s.gravatar.com *.nhost.run nhost.run;
|
||||||
|
font-src 'self' data:;
|
||||||
|
object-src 'none';
|
||||||
|
base-uri 'self';
|
||||||
|
form-action 'self';
|
||||||
|
frame-ancestors 'none';
|
||||||
|
frame-src 'self' js.stripe.com;
|
||||||
|
`;
|
||||||
|
|
||||||
module.exports = withBundleAnalyzer({
|
module.exports = withBundleAnalyzer({
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
swcMinify: false,
|
swcMinify: false,
|
||||||
@@ -17,6 +31,19 @@ module.exports = withBundleAnalyzer({
|
|||||||
eslint: {
|
eslint: {
|
||||||
dirs: ['src'],
|
dirs: ['src'],
|
||||||
},
|
},
|
||||||
|
async headers() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: '/(.*)',
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: 'X-Frame-Options',
|
||||||
|
value: 'SAMEORIGIN',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
async redirects() {
|
async redirects() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/dashboard",
|
"name": "@nhost/dashboard",
|
||||||
"version": "1.6.1",
|
"version": "1.14.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
@@ -19,57 +19,58 @@
|
|||||||
"e2e": "pnpm install-browsers && pnpm playwright test"
|
"e2e": "pnpm install-browsers && pnpm playwright test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.8.9",
|
"@apollo/client": "^3.9.9",
|
||||||
"@codemirror/lang-sql": "^6.5.5",
|
"@codemirror/lang-sql": "^6.6.2",
|
||||||
"@emotion/cache": "^11.11.0",
|
"@emotion/cache": "^11.11.0",
|
||||||
"@emotion/react": "^11.11.3",
|
"@emotion/react": "^11.11.4",
|
||||||
"@emotion/server": "^11.11.0",
|
"@emotion/server": "^11.11.0",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.5",
|
||||||
"@fontsource/inter": "^5.0.16",
|
"@fontsource/inter": "^5.0.17",
|
||||||
"@fontsource/roboto-mono": "^5.0.16",
|
"@fontsource/roboto-mono": "^5.0.17",
|
||||||
"@graphiql/react": "^0.20.2",
|
"@graphiql/react": "^0.20.3",
|
||||||
"@graphiql/toolkit": "^0.9.1",
|
"@graphiql/toolkit": "^0.9.1",
|
||||||
"@headlessui/react": "^1.7.18",
|
"@headlessui/react": "^1.7.18",
|
||||||
"@heroicons/react": "^1.0.6",
|
"@heroicons/react": "^1.0.6",
|
||||||
"@hookform/resolvers": "^3.3.4",
|
"@hookform/resolvers": "^3.3.4",
|
||||||
"@mui/base": "5.0.0-beta.31",
|
"@mui/base": "5.0.0-beta.31",
|
||||||
"@mui/material": "^5.15.4",
|
"@mui/material": "^5.15.14",
|
||||||
"@mui/system": "^5.15.4",
|
"@mui/system": "^5.15.14",
|
||||||
"@mui/x-date-pickers": "^5.0.20",
|
"@mui/x-date-pickers": "^5.0.20",
|
||||||
"@nhost/nextjs": "workspace:*",
|
"@nhost/nextjs": "workspace:*",
|
||||||
"@nhost/react-apollo": "workspace:*",
|
"@nhost/react-apollo": "workspace:*",
|
||||||
"@segment/snippet": "^4.16.2",
|
"@segment/snippet": "^4.16.2",
|
||||||
"@stripe/react-stripe-js": "^2.4.0",
|
"@stripe/react-stripe-js": "^2.6.2",
|
||||||
"@stripe/stripe-js": "^1.54.2",
|
"@stripe/stripe-js": "^1.54.2",
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
"@tanstack/react-query": "^4.36.1",
|
"@tanstack/react-query": "^4.36.1",
|
||||||
"@tanstack/react-table": "^8.11.6",
|
"@tanstack/react-table": "^8.15.3",
|
||||||
"@tanstack/react-virtual": "^3.0.1",
|
"@tanstack/react-virtual": "^3.2.0",
|
||||||
"@uiw/codemirror-theme-github": "^4.21.21",
|
"@uiw/codemirror-theme-github": "^4.21.25",
|
||||||
"@uiw/react-codemirror": "^4.21.21",
|
"@uiw/react-codemirror": "^4.21.25",
|
||||||
"analytics-node": "^6.2.0",
|
"analytics-node": "^6.2.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
|
"framer-motion": "^10.18.0",
|
||||||
"generate-password": "^1.7.1",
|
"generate-password": "^1.7.1",
|
||||||
"graphiql": "^3.1.0",
|
"graphiql": "^3.1.1",
|
||||||
"graphql": "16.8.1",
|
"graphql": "16.8.1",
|
||||||
"graphql-request": "^6.1.0",
|
"graphql-request": "^6.1.0",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
"graphql-ws": "^5.14.3",
|
"graphql-ws": "^5.16.0",
|
||||||
"just-kebab-case": "^4.2.0",
|
"just-kebab-case": "^4.2.0",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"next": "^14.0.4",
|
"next": "^14.1.4",
|
||||||
"next-seo": "^6.4.0",
|
"next-seo": "^6.5.0",
|
||||||
"node-pg-format": "^1.3.5",
|
"node-pg-format": "^1.3.5",
|
||||||
"pluralize": "^8.0.0",
|
"pluralize": "^8.0.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-children-utilities": "^2.10.0",
|
"react-children-utilities": "^2.10.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-error-boundary": "^4.0.12",
|
"react-error-boundary": "^4.0.13",
|
||||||
"react-hook-form": "^7.49.3",
|
"react-hook-form": "^7.51.2",
|
||||||
"react-hot-toast": "^2.4.1",
|
"react-hot-toast": "^2.4.1",
|
||||||
"react-intersection-observer": "^9.5.3",
|
"react-intersection-observer": "^9.8.1",
|
||||||
"react-is": "18.2.0",
|
"react-is": "18.2.0",
|
||||||
"react-loading-skeleton": "^2.2.0",
|
"react-loading-skeleton": "^2.2.0",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
@@ -84,15 +85,15 @@
|
|||||||
"slugify": "^1.6.6",
|
"slugify": "^1.6.6",
|
||||||
"stripe": "^10.17.0",
|
"stripe": "^10.17.0",
|
||||||
"tailwind-merge": "^1.14.0",
|
"tailwind-merge": "^1.14.0",
|
||||||
"utility-types": "^3.10.0",
|
"utility-types": "^3.11.0",
|
||||||
"validator": "^13.11.0",
|
"validator": "^13.11.0",
|
||||||
"yup": "^1.3.3",
|
"yup": "^1.4.0",
|
||||||
"yup-password": "^0.2.2"
|
"yup-password": "^0.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.23.7",
|
"@babel/core": "^7.24.3",
|
||||||
"@faker-js/faker": "^7.6.0",
|
"@faker-js/faker": "^7.6.0",
|
||||||
"@graphql-codegen/cli": "^3.3.1",
|
"@graphql-codegen/cli": "^5.0.2",
|
||||||
"@graphql-codegen/typescript": "^3.0.4",
|
"@graphql-codegen/typescript": "^3.0.4",
|
||||||
"@graphql-codegen/typescript-operations": "^3.0.4",
|
"@graphql-codegen/typescript-operations": "^3.0.4",
|
||||||
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
|
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
|
||||||
@@ -105,50 +106,50 @@
|
|||||||
"@storybook/addon-postcss": "^2.0.0",
|
"@storybook/addon-postcss": "^2.0.0",
|
||||||
"@storybook/builder-webpack5": "^6.5.16",
|
"@storybook/builder-webpack5": "^6.5.16",
|
||||||
"@storybook/manager-webpack5": "^6.5.16",
|
"@storybook/manager-webpack5": "^6.5.16",
|
||||||
"@storybook/react": "^6.5.16",
|
"@storybook/react": "^7.6.17",
|
||||||
"@storybook/testing-library": "^0.2.2",
|
"@storybook/testing-library": "^0.2.2",
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.12",
|
||||||
"@testing-library/dom": "^9.3.4",
|
"@testing-library/dom": "^9.3.4",
|
||||||
"@testing-library/jest-dom": "^5.17.0",
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
"@testing-library/react": "^14.1.2",
|
"@testing-library/react": "^14.2.2",
|
||||||
"@testing-library/user-event": "^14.5.2",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"@types/ace": "^0.0.48",
|
"@types/ace": "^0.0.48",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"@types/jest": "^29.5.11",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/lodash.debounce": "^4.0.9",
|
"@types/lodash.debounce": "^4.0.9",
|
||||||
"@types/node": "^16.18.70",
|
"@types/node": "^16.18.93",
|
||||||
"@types/pluralize": "^0.0.30",
|
"@types/pluralize": "^0.0.30",
|
||||||
"@types/react": "^18.2.47",
|
"@types/react": "^18.2.73",
|
||||||
"@types/react-dom": "^18.2.18",
|
"@types/react-dom": "^18.2.23",
|
||||||
"@types/react-table": "^7.7.19",
|
"@types/react-table": "^7.7.20",
|
||||||
"@types/shell-quote": "^1.7.5",
|
"@types/shell-quote": "^1.7.5",
|
||||||
"@types/testing-library__jest-dom": "^5.14.9",
|
"@types/testing-library__jest-dom": "^5.14.9",
|
||||||
"@types/validator": "^13.11.8",
|
"@types/validator": "^13.11.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.18.1",
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||||
"@typescript-eslint/parser": "^6.18.1",
|
"@typescript-eslint/parser": "^6.21.0",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"@vitest/coverage-v8": "^0.32.4",
|
"@vitest/coverage-v8": "^0.32.4",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.19",
|
||||||
"babel-loader": "^8.3.0",
|
"babel-loader": "^8.3.0",
|
||||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||||
"csstype": "^3.1.3",
|
"csstype": "^3.1.3",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.4.5",
|
||||||
"encoding": "^0.1.13",
|
"encoding": "^0.1.13",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-airbnb": "19.0.4",
|
"eslint-config-airbnb": "19.0.4",
|
||||||
"eslint-config-airbnb-typescript": "^17.1.0",
|
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||||
"eslint-config-next": "^13.5.6",
|
"eslint-config-next": "^13.5.6",
|
||||||
"eslint-config-prettier": "^8.10.0",
|
"eslint-config-prettier": "^8.10.0",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint-plugin-react": "^7.34.1",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"jsdom": "^22.1.0",
|
"jsdom": "^22.1.0",
|
||||||
"lint-staged": "^15.2.0",
|
"lint-staged": "^15.2.2",
|
||||||
"msw": "^1.3.2",
|
"msw": "^1.3.3",
|
||||||
"msw-storybook-addon": "^1.10.0",
|
"msw-storybook-addon": "^1.10.0",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"postcss": "^8.4.33",
|
"postcss": "^8.4.38",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"prettier-plugin-organize-imports": "^3.2.4",
|
"prettier-plugin-organize-imports": "^3.2.4",
|
||||||
"prettier-plugin-tailwindcss": "^0.4.1",
|
"prettier-plugin-tailwindcss": "^0.4.1",
|
||||||
@@ -156,11 +157,11 @@
|
|||||||
"require-from-string": "^2.0.2",
|
"require-from-string": "^2.0.2",
|
||||||
"snake-case": "^3.0.4",
|
"snake-case": "^3.0.4",
|
||||||
"storybook-addon-next-router": "^4.0.2",
|
"storybook-addon-next-router": "^4.0.2",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.3",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsconfig-paths-webpack-plugin": "^4.1.0",
|
"tsconfig-paths-webpack-plugin": "^4.1.0",
|
||||||
"vite": "^5.0.12",
|
"vite": "^5.2.7",
|
||||||
"vite-tsconfig-paths": "^4.2.3",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
"vitest": "^0.32.4"
|
"vitest": "^0.32.4"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
|
|
||||||
|
export default function ApplyLocalSettingsDialog() {
|
||||||
|
const { closeDialog } = useDialog();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4 px-6 pb-6">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Text color="secondary">
|
||||||
|
Run{' '}
|
||||||
|
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
|
||||||
|
$ nhost up
|
||||||
|
</code>{' '}
|
||||||
|
using the cli to apply your changes
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="w-full"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => closeDialog()}
|
||||||
|
autoFocus
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as ApplyLocalSettingsDialog } from './ApplyLocalSettingsDialog';
|
||||||
@@ -35,7 +35,7 @@ function InsertPlaceholderTableRow({
|
|||||||
...props
|
...props
|
||||||
}: InsertPlaceholderTableRowProps) {
|
}: InsertPlaceholderTableRowProps) {
|
||||||
return (
|
return (
|
||||||
<Box className="h-12 border-r-1 border-b-1" {...props}>
|
<Box className="h-12 border-b-1 border-r-1" {...props}>
|
||||||
<Button
|
<Button
|
||||||
onClick={onInsertRow}
|
onClick={onInsertRow}
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
@@ -209,7 +209,7 @@ export default function DataGridBody<T extends object>({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Box
|
<Box
|
||||||
className="inline-flex h-12 items-center border-b-1 border-r-1 py-1.5 px-2 text-xs"
|
className="inline-flex h-12 items-center border-b-1 border-r-1 px-2 py-1.5 text-xs"
|
||||||
sx={{ color: 'text.secondary' }}
|
sx={{ color: 'text.secondary' }}
|
||||||
style={{
|
style={{
|
||||||
width: allowInsertColumn
|
width: allowInsertColumn
|
||||||
@@ -281,8 +281,8 @@ export default function DataGridBody<T extends object>({
|
|||||||
}}
|
}}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'h-12 font-display text-xs motion-safe:transition-colors',
|
'h-12 font-display text-xs motion-safe:transition-colors',
|
||||||
'border-r-1 border-b-1',
|
'border-b-1 border-r-1',
|
||||||
'scroll-mt-[57px] scroll-ml-8',
|
'scroll-ml-8 scroll-mt-[57px]',
|
||||||
column.id === 'selection' &&
|
column.id === 'selection' &&
|
||||||
'sticky left-0 z-20 justify-center px-0',
|
'sticky left-0 z-20 justify-center px-0',
|
||||||
)}
|
)}
|
||||||
@@ -296,7 +296,7 @@ export default function DataGridBody<T extends object>({
|
|||||||
})}
|
})}
|
||||||
|
|
||||||
{allowInsertColumn && (
|
{allowInsertColumn && (
|
||||||
<Box className="h-12 w-25 border-r-1 border-b-1" />
|
<Box className="h-12 w-25 border-b-1 border-r-1" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,15 @@ import type {
|
|||||||
DataBrowserGridCellProps,
|
DataBrowserGridCellProps,
|
||||||
} from '@/features/database/dataGrid/types/dataBrowser';
|
} from '@/features/database/dataGrid/types/dataBrowser';
|
||||||
import { triggerToast } from '@/utils/toast';
|
import { triggerToast } from '@/utils/toast';
|
||||||
import type { FocusEvent, JSXElementConstructor, KeyboardEvent, MouseEvent, ReactElement, ReactNode, ReactPortal } from 'react';
|
import type {
|
||||||
|
FocusEvent,
|
||||||
|
JSXElementConstructor,
|
||||||
|
KeyboardEvent,
|
||||||
|
MouseEvent,
|
||||||
|
ReactElement,
|
||||||
|
ReactNode,
|
||||||
|
ReactPortal,
|
||||||
|
} from 'react';
|
||||||
import {
|
import {
|
||||||
Children,
|
Children,
|
||||||
cloneElement,
|
cloneElement,
|
||||||
@@ -308,7 +316,7 @@ function DataGridCellContent<TData extends object = {}, TValue = unknown>({
|
|||||||
isEditable &&
|
isEditable &&
|
||||||
'focus-within:outline-none focus-within:ring-0 focus:ring-0',
|
'focus-within:outline-none focus-within:ring-0 focus:ring-0',
|
||||||
isSelected && 'shadow-outline',
|
isSelected && 'shadow-outline',
|
||||||
isEditing ? 'p-0.5 shadow-outline-dark' : 'py-1.5 px-2',
|
isEditing ? 'p-0.5 shadow-outline-dark' : 'px-2 py-1.5',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
@@ -320,20 +328,28 @@ function DataGridCellContent<TData extends object = {}, TValue = unknown>({
|
|||||||
sx={{ backgroundColor: 'transparent' }}
|
sx={{ backgroundColor: 'transparent' }}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{Children.map(children, (child: ReactNode | ReactPortal | ReactElement<unknown, string | JSXElementConstructor<any>>) => {
|
{Children.map(
|
||||||
if (!isValidElement(child)) {
|
children,
|
||||||
return null;
|
(
|
||||||
}
|
child:
|
||||||
|
| ReactNode
|
||||||
|
| ReactPortal
|
||||||
|
| ReactElement<unknown, string | JSXElementConstructor<any>>,
|
||||||
|
) => {
|
||||||
|
if (!isValidElement(child)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return cloneElement(child, {
|
return cloneElement(child, {
|
||||||
...child.props,
|
...child.props,
|
||||||
onSave: handleSave,
|
onSave: handleSave,
|
||||||
optimisticValue,
|
optimisticValue,
|
||||||
onOptimisticValueChange: setOptimisticValue,
|
onOptimisticValueChange: setOptimisticValue,
|
||||||
temporaryValue,
|
temporaryValue,
|
||||||
onTemporaryValueChange: setTemporaryValue,
|
onTemporaryValueChange: setTemporaryValue,
|
||||||
});
|
});
|
||||||
})}
|
},
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -96,45 +96,52 @@ export default function DataGridHeader<T extends object>({
|
|||||||
}}
|
}}
|
||||||
key={column.id}
|
key={column.id}
|
||||||
>
|
>
|
||||||
<Dropdown.Trigger
|
{column.id === 'selection' ? (
|
||||||
className={twMerge(
|
|
||||||
'focus:outline-none motion-safe:transition-colors',
|
|
||||||
)}
|
|
||||||
disabled={
|
|
||||||
column.isDisabled ||
|
|
||||||
column.id === 'selection' ||
|
|
||||||
(column.disableSortBy && !onRemoveColumn)
|
|
||||||
}
|
|
||||||
hideChevron
|
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
{...headerProps}
|
{...headerProps}
|
||||||
className="relative grid w-full grid-flow-col items-center justify-between p-2"
|
className="relative grid w-full grid-flow-col items-center justify-between p-2"
|
||||||
>
|
>
|
||||||
{column.render('Header')}
|
{column.render('Header')}
|
||||||
|
|
||||||
{allowSort && (
|
|
||||||
<Box component="span" sx={{ color: 'text.primary' }}>
|
|
||||||
{column.isSorted && !column.isSortedDesc && (
|
|
||||||
<ArrowUpIcon className="h-3 w-3" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{column.isSorted && column.isSortedDesc && (
|
|
||||||
<ArrowDownIcon className="h-3 w-3" />
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
|
) : (
|
||||||
{allowResize && !column.disableResizing && (
|
<Dropdown.Trigger
|
||||||
|
className={twMerge(
|
||||||
|
'focus:outline-none motion-safe:transition-colors',
|
||||||
|
)}
|
||||||
|
disabled={
|
||||||
|
column.isDisabled || (column.disableSortBy && !onRemoveColumn)
|
||||||
|
}
|
||||||
|
hideChevron
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
{...column.getResizerProps({
|
{...headerProps}
|
||||||
onClick: (event: Event) => event.stopPropagation(),
|
className="relative grid w-full grid-flow-col items-center justify-between p-2"
|
||||||
})}
|
>
|
||||||
className="absolute top-0 bottom-0 -right-0.5 z-10 h-full w-1.5 group-hover:bg-slate-900 group-hover:bg-opacity-20 group-active:bg-slate-900 group-active:bg-opacity-20 motion-safe:transition-colors"
|
{column.render('Header')}
|
||||||
/>
|
|
||||||
)}
|
{allowSort && (
|
||||||
</Dropdown.Trigger>
|
<Box component="span" sx={{ color: 'text.primary' }}>
|
||||||
|
{column.isSorted && !column.isSortedDesc && (
|
||||||
|
<ArrowUpIcon className="h-3 w-3" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{column.isSorted && column.isSortedDesc && (
|
||||||
|
<ArrowDownIcon className="h-3 w-3" />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{allowResize && !column.disableResizing && (
|
||||||
|
<span
|
||||||
|
{...column.getResizerProps({
|
||||||
|
onClick: (event: Event) => event.stopPropagation(),
|
||||||
|
})}
|
||||||
|
className="absolute -right-0.5 bottom-0 top-0 z-10 h-full w-1.5 group-hover:bg-slate-900 group-hover:bg-opacity-20 group-active:bg-slate-900 group-active:bg-opacity-20 motion-safe:transition-colors"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Dropdown.Trigger>
|
||||||
|
)}
|
||||||
|
|
||||||
<Dropdown.Content
|
<Dropdown.Content
|
||||||
menu
|
menu
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { useNotFoundRedirect } from '@/features/projects/common/hooks/useNotFoun
|
|||||||
import { useProjectRoutes } from '@/features/projects/common/hooks/useProjectRoutes';
|
import { useProjectRoutes } from '@/features/projects/common/hooks/useProjectRoutes';
|
||||||
import { NextSeo } from 'next-seo';
|
import { NextSeo } from 'next-seo';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export interface ProjectLayoutProps extends AuthenticatedLayoutProps {
|
export interface ProjectLayoutProps extends AuthenticatedLayoutProps {
|
||||||
@@ -48,15 +47,16 @@ function ProjectLayoutContent({
|
|||||||
|
|
||||||
useNotFoundRedirect();
|
useNotFoundRedirect();
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if (isPlatform || !router.isReady) {
|
// if (isPlatform || !router.isReady) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (isRestrictedPath) {
|
// TODO // Double check what restricted path means here
|
||||||
router.push('/local/local');
|
// if (isRestrictedPath) {
|
||||||
}
|
// router.push('/local/local');
|
||||||
}, [isPlatform, isRestrictedPath, router]);
|
// }
|
||||||
|
// }, [isPlatform, isRestrictedPath, router]);
|
||||||
|
|
||||||
if (isRestrictedPath || loading) {
|
if (isRestrictedPath || loading) {
|
||||||
return <LoadingScreen />;
|
return <LoadingScreen />;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { List } from '@/components/ui/v2/List';
|
|||||||
import type { ListItemButtonProps } from '@/components/ui/v2/ListItem';
|
import type { ListItemButtonProps } from '@/components/ui/v2/ListItem';
|
||||||
import { ListItem } from '@/components/ui/v2/ListItem';
|
import { ListItem } from '@/components/ui/v2/ListItem';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -61,6 +62,7 @@ export default function SettingsSidebar({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: SettingsSidebarProps) {
|
}: SettingsSidebarProps) {
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
|
|
||||||
@@ -95,7 +97,7 @@ export default function SettingsSidebar({
|
|||||||
<>
|
<>
|
||||||
<Backdrop
|
<Backdrop
|
||||||
open={expanded}
|
open={expanded}
|
||||||
className="absolute top-0 left-0 bottom-0 right-0 z-[34] md:hidden"
|
className="absolute bottom-0 left-0 right-0 top-0 z-[34] md:hidden"
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
onClick={() => setExpanded(false)}
|
onClick={() => setExpanded(false)}
|
||||||
@@ -112,7 +114,7 @@ export default function SettingsSidebar({
|
|||||||
<Box
|
<Box
|
||||||
component="aside"
|
component="aside"
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'absolute top-0 z-[35] h-full w-full overflow-auto border-r-1 px-2 pt-2 pb-17 motion-safe:transition-transform md:relative md:z-0 md:h-full md:py-2.5 md:transition-none',
|
'absolute top-0 z-[35] h-full w-full overflow-auto border-r-1 px-2 pb-17 pt-2 motion-safe:transition-transform md:relative md:z-0 md:h-full md:py-2.5 md:transition-none',
|
||||||
expanded ? 'translate-x-0' : '-translate-x-full md:translate-x-0',
|
expanded ? 'translate-x-0' : '-translate-x-full md:translate-x-0',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
@@ -181,7 +183,12 @@ export default function SettingsSidebar({
|
|||||||
SMTP
|
SMTP
|
||||||
</SettingsNavLink>
|
</SettingsNavLink>
|
||||||
|
|
||||||
<SettingsNavLink href="/git" exact={false} onClick={handleSelect}>
|
<SettingsNavLink
|
||||||
|
href="/git"
|
||||||
|
exact={false}
|
||||||
|
onClick={handleSelect}
|
||||||
|
disabled={!isPlatform}
|
||||||
|
>
|
||||||
Git
|
Git
|
||||||
</SettingsNavLink>
|
</SettingsNavLink>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { PlusCircleIcon } from '@/components/ui/v2/icons/PlusCircleIcon';
|
import { PlusCircleIcon } from '@/components/ui/v2/icons/PlusCircleIcon';
|
||||||
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
||||||
import type { ComponentMeta, ComponentStory } from '@storybook/react';
|
import type { Meta, StoryFn } from '@storybook/react';
|
||||||
import type { ButtonProps } from './Button';
|
import type { ButtonProps } from './Button';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
|
|
||||||
@@ -24,9 +24,9 @@ export default {
|
|||||||
control: { type: 'radio' },
|
control: { type: 'radio' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as ComponentMeta<typeof Button>;
|
} as Meta<typeof Button>;
|
||||||
|
|
||||||
const Template: ComponentStory<typeof Button> = function Template(
|
const Template: StoryFn<ButtonProps> = function TemplateFunction(
|
||||||
args: ButtonProps,
|
args: ButtonProps,
|
||||||
) {
|
) {
|
||||||
return <Button {...args} />;
|
return <Button {...args} />;
|
||||||
|
|||||||
164
dashboard/src/components/ui/v2/ErrorToast/ErrorToast.tsx
Normal file
164
dashboard/src/components/ui/v2/ErrorToast/ErrorToast.tsx
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
import { ChevronDownIcon } from '@/components/ui/v2/icons/ChevronDownIcon';
|
||||||
|
import { ChevronUpIcon } from '@/components/ui/v2/icons/ChevronUpIcon';
|
||||||
|
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
|
||||||
|
import { XIcon } from '@/components/ui/v2/icons/XIcon';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { getToastBackgroundColor } from '@/utils/constants/settings';
|
||||||
|
import { copy } from '@/utils/copy';
|
||||||
|
import type { ApolloError } from '@apollo/client';
|
||||||
|
import { useUserData } from '@nhost/nextjs';
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
interface ErrorDetails {
|
||||||
|
info: {
|
||||||
|
projectId: string;
|
||||||
|
userId: string;
|
||||||
|
url?: string;
|
||||||
|
};
|
||||||
|
error: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getInternalErrorMessage = (
|
||||||
|
error: Error | ApolloError | undefined,
|
||||||
|
): string | null => {
|
||||||
|
if (!error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.name === 'ApolloError') {
|
||||||
|
// @ts-ignore
|
||||||
|
const internalError = error.graphQLErrors?.[0]?.extensions?.internal as {
|
||||||
|
error: { message: string };
|
||||||
|
};
|
||||||
|
return internalError?.error?.message || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const errorToObject = (error: ApolloError | Error) => {
|
||||||
|
if (error.name === 'ApolloError') {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return {
|
||||||
|
name: error.name,
|
||||||
|
message: error.message,
|
||||||
|
stack: error.stack,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ErrorToast({
|
||||||
|
isVisible,
|
||||||
|
errorMessage,
|
||||||
|
error,
|
||||||
|
close,
|
||||||
|
}: {
|
||||||
|
isVisible: boolean;
|
||||||
|
errorMessage: string;
|
||||||
|
error: ApolloError | Error;
|
||||||
|
close: () => void;
|
||||||
|
}) {
|
||||||
|
const userData = useUserData();
|
||||||
|
const { asPath } = useRouter();
|
||||||
|
|
||||||
|
const [showInfo, setShowInfo] = useState(false);
|
||||||
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
|
|
||||||
|
const errorDetails: ErrorDetails = {
|
||||||
|
info: {
|
||||||
|
projectId: currentProject?.id,
|
||||||
|
userId: userData?.id || 'local',
|
||||||
|
url: asPath,
|
||||||
|
},
|
||||||
|
error: errorToObject(error),
|
||||||
|
};
|
||||||
|
|
||||||
|
const msg = getInternalErrorMessage(error) || errorMessage;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence>
|
||||||
|
{isVisible && (
|
||||||
|
<motion.div
|
||||||
|
style={{
|
||||||
|
backgroundColor: getToastBackgroundColor(),
|
||||||
|
}}
|
||||||
|
className="flex w-full max-w-xl flex-col space-y-4 rounded-lg p-4 text-white"
|
||||||
|
initial={{
|
||||||
|
opacity: 0,
|
||||||
|
y: 100,
|
||||||
|
}}
|
||||||
|
animate={{
|
||||||
|
opacity: 1,
|
||||||
|
scale: 1,
|
||||||
|
y: 0,
|
||||||
|
}}
|
||||||
|
exit={{
|
||||||
|
opacity: 0,
|
||||||
|
scale: 0,
|
||||||
|
y: 100,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
bounce: 0.1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex w-full flex-row items-center justify-between space-x-4">
|
||||||
|
<button onClick={close} type="button" aria-label="Close">
|
||||||
|
<XIcon className="h-4 w-4 text-white" />
|
||||||
|
</button>
|
||||||
|
<span>
|
||||||
|
{msg ?? 'An unkown error has occured, please try again later!'}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowInfo(!showInfo)}
|
||||||
|
className="flex flex-row items-center justify-center space-x-2 text-white"
|
||||||
|
>
|
||||||
|
<span>Info</span>
|
||||||
|
{showInfo ? (
|
||||||
|
<ChevronUpIcon className="h-3 w-3 text-white" />
|
||||||
|
) : (
|
||||||
|
<ChevronDownIcon className="h-3 w-3 text-white" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showInfo && (
|
||||||
|
<div className="flex flex-col space-y-4">
|
||||||
|
<div className="relative flex flex-col">
|
||||||
|
<div className="relative flex max-h-[400px] w-full max-w-xl flex-row justify-between overflow-x-auto rounded-lg bg-black p-4">
|
||||||
|
<pre>{JSON.stringify(errorDetails, null, 2)}</pre>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label="Copy error details"
|
||||||
|
className="absolute right-2 top-2"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
copy(
|
||||||
|
JSON.stringify(errorDetails, null, 2),
|
||||||
|
'Error details',
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CopyIcon className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
}
|
||||||
1
dashboard/src/components/ui/v2/ErrorToast/index.ts
Normal file
1
dashboard/src/components/ui/v2/ErrorToast/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default as ErrorToast } from './ErrorToast';
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Option } from '@/components/ui/v2/Option';
|
import { Option } from '@/components/ui/v2/Option';
|
||||||
import type { ComponentMeta, ComponentStory } from '@storybook/react';
|
import type { Meta, StoryFn } from '@storybook/react';
|
||||||
import type { SelectProps } from './Select';
|
import type { SelectProps } from './Select';
|
||||||
import Select from './Select';
|
import Select from './Select';
|
||||||
|
|
||||||
@@ -7,11 +7,9 @@ export default {
|
|||||||
title: 'UI Library / Select',
|
title: 'UI Library / Select',
|
||||||
component: Select,
|
component: Select,
|
||||||
argTypes: {},
|
argTypes: {},
|
||||||
} as ComponentMeta<typeof Select>;
|
} as Meta<typeof Select>;
|
||||||
|
|
||||||
const Template: ComponentStory<typeof Select> = function Template(
|
const Template: StoryFn<SelectProps<any>> = function TemplateFunction(args) {
|
||||||
args: SelectProps<any>,
|
|
||||||
) {
|
|
||||||
return (
|
return (
|
||||||
<Select className="w-64" {...args}>
|
<Select className="w-64" {...args}>
|
||||||
<Option value="value1">Value 1</Option>
|
<Option value="value1">Value 1</Option>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ComponentMeta, ComponentStory } from '@storybook/react';
|
import type { Meta, StoryFn } from '@storybook/react';
|
||||||
import type { SwitchProps } from './Switch';
|
import type { SwitchProps } from './Switch';
|
||||||
import Switch from './Switch';
|
import Switch from './Switch';
|
||||||
|
|
||||||
@@ -6,9 +6,9 @@ export default {
|
|||||||
title: 'UI Library / Switch',
|
title: 'UI Library / Switch',
|
||||||
component: Switch,
|
component: Switch,
|
||||||
argTypes: {},
|
argTypes: {},
|
||||||
} as ComponentMeta<typeof Switch>;
|
} as Meta<typeof Switch>;
|
||||||
|
|
||||||
const Template: ComponentStory<typeof Switch> = function Template(
|
const Template: StoryFn<SwitchProps> = function TemplateFunction(
|
||||||
args: SwitchProps,
|
args: SwitchProps,
|
||||||
) {
|
) {
|
||||||
return <Switch label="Accept Rules" {...args} />;
|
return <Switch label="Accept Rules" {...args} />;
|
||||||
|
|||||||
@@ -4,16 +4,14 @@ import { Box } from '@/components/ui/v2/Box';
|
|||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
import { Checkbox } from '@/components/ui/v2/Checkbox';
|
import { Checkbox } from '@/components/ui/v2/Checkbox';
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import {
|
import {
|
||||||
useDeleteUserAccountMutation,
|
useDeleteUserAccountMutation,
|
||||||
useGetAllWorkspacesAndProjectsQuery,
|
useGetAllWorkspacesAndProjectsQuery,
|
||||||
} from '@/utils/__generated__/graphql';
|
} from '@/utils/__generated__/graphql';
|
||||||
import { type ApolloError } from '@apollo/client';
|
|
||||||
import { useSignOut, useUserData } from '@nhost/nextjs';
|
import { useSignOut, useUserData } from '@nhost/nextjs';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
function ConfirmDeleteAccountModal({
|
function ConfirmDeleteAccountModal({
|
||||||
@@ -44,30 +42,19 @@ function ConfirmDeleteAccountModal({
|
|||||||
const onClickConfirm = async () => {
|
const onClickConfirm = async () => {
|
||||||
setLoadingRemove(true);
|
setLoadingRemove(true);
|
||||||
|
|
||||||
await toast.promise(
|
await execPromiseWithErrorToast(
|
||||||
deleteUserAccount(),
|
async () => {
|
||||||
{
|
await deleteUserAccount();
|
||||||
loading: 'Deleting your account...',
|
onDelete?.();
|
||||||
success: `The account has been deleted successfully.`,
|
close();
|
||||||
error: (arg: ApolloError) => {
|
},
|
||||||
// we need to get the internal error message from the GraphQL error
|
{
|
||||||
const { internal } = arg.graphQLErrors[0]?.extensions || {};
|
loadingMessage: 'Deleting your account...',
|
||||||
const { message } = (internal as Record<string, any>)?.error || {};
|
successMessage: 'The account has been deleted successfully.',
|
||||||
|
errorMessage:
|
||||||
// we use the default Apollo error message if we can't find the
|
'An error occurred while deleting your account. Please try again.',
|
||||||
// internal error message
|
|
||||||
return (
|
|
||||||
message ||
|
|
||||||
arg.message ||
|
|
||||||
'An error occurred while deleting your account. Please try again.'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
onDelete?.();
|
|
||||||
close();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { Form } from '@/components/form/Form';
|
||||||
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
|
import { useUpdateUserDisplayNameMutation } from '@/utils/__generated__/graphql';
|
||||||
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useUserData } from '@nhost/nextjs';
|
||||||
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
|
const validationSchema = Yup.object({
|
||||||
|
displayName: Yup.string()
|
||||||
|
.label('Display Name')
|
||||||
|
.required('This field is required.'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type DisplayNameSettingFormValues = Yup.InferType<
|
||||||
|
typeof validationSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export default function DisplayNameSetting() {
|
||||||
|
const { id: userID, displayName } = useUserData();
|
||||||
|
|
||||||
|
const [updateUserDisplayName] = useUpdateUserDisplayNameMutation();
|
||||||
|
|
||||||
|
const form = useForm<DisplayNameSettingFormValues>({
|
||||||
|
reValidateMode: 'onSubmit',
|
||||||
|
defaultValues: {
|
||||||
|
displayName,
|
||||||
|
},
|
||||||
|
resolver: yupResolver(validationSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { register, formState } = form;
|
||||||
|
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
||||||
|
|
||||||
|
async function handleSubmit(formValues: DisplayNameSettingFormValues) {
|
||||||
|
await execPromiseWithErrorToast(
|
||||||
|
async () => {
|
||||||
|
await updateUserDisplayName({
|
||||||
|
variables: {
|
||||||
|
id: userID,
|
||||||
|
displayName: formValues.displayName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
form.reset({ displayName: formValues.displayName });
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Updating your display name...',
|
||||||
|
successMessage: 'Your display name has been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while trying to update your display name. Please try again.',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<SettingsContainer
|
||||||
|
title="Update your display name"
|
||||||
|
slotProps={{
|
||||||
|
submitButton: {
|
||||||
|
disabled: !isDirty,
|
||||||
|
loading: formState.isSubmitting,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
className="grid grid-flow-row lg:grid-cols-5"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...register('displayName')}
|
||||||
|
className="col-span-2"
|
||||||
|
type="text"
|
||||||
|
id="display-name"
|
||||||
|
label="Display Name"
|
||||||
|
fullWidth
|
||||||
|
helperText={formState.errors.displayName?.message}
|
||||||
|
error={Boolean(formState.errors.displayName)}
|
||||||
|
/>
|
||||||
|
</SettingsContainer>
|
||||||
|
</Form>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './DisplayNameSetting';
|
||||||
|
export { default as DisplayNameSetting } from './DisplayNameSetting';
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { Form } from '@/components/form/Form';
|
||||||
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useNhostClient, useUserData } from '@nhost/nextjs';
|
||||||
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
|
const validationSchema = Yup.object({
|
||||||
|
email: Yup.string().label('Email').email().required(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type EmailSettingFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
|
export default function EmailSetting() {
|
||||||
|
const nhost = useNhostClient();
|
||||||
|
const { email } = useUserData();
|
||||||
|
|
||||||
|
const form = useForm<EmailSettingFormValues>({
|
||||||
|
reValidateMode: 'onSubmit',
|
||||||
|
defaultValues: { email },
|
||||||
|
resolver: yupResolver(validationSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { register, formState } = form;
|
||||||
|
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
||||||
|
|
||||||
|
async function handleSubmit(formValues: EmailSettingFormValues) {
|
||||||
|
await execPromiseWithErrorToast(
|
||||||
|
async () => {
|
||||||
|
await nhost.auth.changeEmail({
|
||||||
|
newEmail: formValues.email,
|
||||||
|
options: {
|
||||||
|
redirectTo: `${window.location.origin}/account`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
form.reset({ email: formValues.email });
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Updating your email...',
|
||||||
|
successMessage:
|
||||||
|
'Please check your inbox. Follow the link to finalize changing your email.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while trying to update your email. Please try again.',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<SettingsContainer
|
||||||
|
title="Update your email"
|
||||||
|
slotProps={{
|
||||||
|
submitButton: {
|
||||||
|
disabled: !isDirty,
|
||||||
|
loading: formState.isSubmitting,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
className="grid grid-flow-row lg:grid-cols-5"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...register('email')}
|
||||||
|
className="col-span-2"
|
||||||
|
id="email"
|
||||||
|
spellCheck="false"
|
||||||
|
autoCapitalize="none"
|
||||||
|
type="email"
|
||||||
|
label="Email"
|
||||||
|
hideEmptyHelperText
|
||||||
|
fullWidth
|
||||||
|
helperText={formState.errors.email?.message}
|
||||||
|
error={Boolean(formState.errors.email)}
|
||||||
|
/>
|
||||||
|
</SettingsContainer>
|
||||||
|
</Form>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './EmailSetting';
|
||||||
|
export { default as EmailSetting } from './EmailSetting';
|
||||||
@@ -15,15 +15,13 @@ import { ListItem } from '@/components/ui/v2/ListItem';
|
|||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||||
import { CreatePATForm } from '@/features/account/settings/components/CreatePATForm';
|
import { CreatePATForm } from '@/features/account/settings/components/CreatePATForm';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
|
||||||
import {
|
import {
|
||||||
GetPersonalAccessTokensDocument,
|
GetPersonalAccessTokensDocument,
|
||||||
useDeletePersonalAccessTokenMutation,
|
useDeletePersonalAccessTokenMutation,
|
||||||
useGetPersonalAccessTokensQuery,
|
useGetPersonalAccessTokensQuery,
|
||||||
} from '@/utils/__generated__/graphql';
|
} from '@/utils/__generated__/graphql';
|
||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export default function PATSettings() {
|
export default function PATSettings() {
|
||||||
@@ -59,28 +57,20 @@ export default function PATSettings() {
|
|||||||
|
|
||||||
async function handleDeletePAT({
|
async function handleDeletePAT({
|
||||||
id,
|
id,
|
||||||
}: typeof availablePersonalAccessTokens[0]) {
|
}: (typeof availablePersonalAccessTokens)[0]) {
|
||||||
const deletePATPromise = deletePAT({ variables: { patId: id } });
|
await execPromiseWithErrorToast(
|
||||||
|
() => deletePAT({ variables: { patId: id } }),
|
||||||
try {
|
{
|
||||||
await toast.promise(
|
loadingMessage: 'Deleting personal access token...',
|
||||||
deletePATPromise,
|
successMessage: 'Personal access token has been deleted successfully.',
|
||||||
{
|
errorMessage:
|
||||||
loading: 'Deleting personal access token...',
|
'An error occurred while deleting the personal access token.',
|
||||||
success: 'Personal access token has been deleted successfully.',
|
},
|
||||||
error: getServerError(
|
);
|
||||||
'An error occurred while deleting the personal access token.',
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
// Note: The toast will handle the error.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleConfirmDelete(
|
function handleConfirmDelete(
|
||||||
originalPAT: typeof availablePersonalAccessTokens[0],
|
originalPAT: (typeof availablePersonalAccessTokens)[0],
|
||||||
) {
|
) {
|
||||||
openAlertDialog({
|
openAlertDialog({
|
||||||
title: 'Delete Personal Access Token',
|
title: 'Delete Personal Access Token',
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { useChangePassword } from '@nhost/nextjs';
|
import { useChangePassword } from '@nhost/nextjs';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -38,25 +36,19 @@ export default function PasswordSettings() {
|
|||||||
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
||||||
|
|
||||||
async function handleSubmit(formValues: PasswordSettingsFormValues) {
|
async function handleSubmit(formValues: PasswordSettingsFormValues) {
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
const changePasswordPromise = changePassword(formValues.newPassword);
|
async () => {
|
||||||
|
// TODO fix changePassword should throw an error if something happens
|
||||||
await toast.promise(
|
await changePassword(formValues.newPassword);
|
||||||
changePasswordPromise,
|
form.reset();
|
||||||
{
|
},
|
||||||
loading: 'Changing password...',
|
{
|
||||||
success: 'The password has been changed successfully.',
|
loadingMessage: 'Changing password...',
|
||||||
error: getServerError(
|
successMessage: 'The password has been changed successfully.',
|
||||||
'An error occurred while trying to update the password. Please try again.',
|
errorMessage:
|
||||||
),
|
'An error occurred while trying to update the password. Please try again.',
|
||||||
},
|
},
|
||||||
getToastStyleProps(),
|
);
|
||||||
);
|
|
||||||
|
|
||||||
form.reset();
|
|
||||||
} catch {
|
|
||||||
// Note: The error is handled by the toast.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
|
import { GitHubIcon } from '@/components/ui/v2/icons/GitHubIcon';
|
||||||
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
|
import { useGetAuthUserProvidersQuery } from '@/utils/__generated__/graphql';
|
||||||
|
import { useProviderLink } from '@nhost/nextjs';
|
||||||
|
import NavLink from 'next/link';
|
||||||
|
|
||||||
|
export default function SocialProvidersSettings() {
|
||||||
|
const { data, loading, error } = useGetAuthUserProvidersQuery();
|
||||||
|
const isGithubConnected = data?.authUserProviders?.some(
|
||||||
|
(item) => item.providerId === 'github',
|
||||||
|
);
|
||||||
|
|
||||||
|
const { github } = useProviderLink({
|
||||||
|
connect: true,
|
||||||
|
redirectTo: `${window.location.origin}/account`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data && loading) {
|
||||||
|
return (
|
||||||
|
<ActivityIndicator
|
||||||
|
delay={1000}
|
||||||
|
label="Loading personal access tokens..."
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsContainer
|
||||||
|
title="Authentication providers"
|
||||||
|
description=""
|
||||||
|
rootClassName="gap-0 flex flex-col items-start"
|
||||||
|
className="my-2"
|
||||||
|
slotProps={{
|
||||||
|
submitButton: { className: 'hidden' },
|
||||||
|
footer: { className: 'hidden' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isGithubConnected ? (
|
||||||
|
<Box
|
||||||
|
sx={{ backgroundColor: 'grey.200' }}
|
||||||
|
className="flex flex-row items-center justify-start space-x-2 rounded-md p-2"
|
||||||
|
>
|
||||||
|
<GitHubIcon />
|
||||||
|
<Text className="font-medium ">Connected</Text>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box>
|
||||||
|
<NavLink
|
||||||
|
href={github}
|
||||||
|
passHref
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
legacyBehavior
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className=""
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
startIcon={<GitHubIcon />}
|
||||||
|
>
|
||||||
|
Connect with GitHub
|
||||||
|
</Button>
|
||||||
|
</NavLink>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</SettingsContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as SocialProvidersSettings } from './SocialProvidersSettings';
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
query getAuthUserProviders {
|
||||||
|
authUserProviders {
|
||||||
|
id
|
||||||
|
providerId
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
mutation updateUserDisplayName($id: uuid!, $displayName: String!) {
|
||||||
|
updateUser(pk_columns: { id: $id }, _set: { displayName: $displayName }) {
|
||||||
|
id
|
||||||
|
displayName
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,26 +10,17 @@ import { Text } from '@/components/ui/v2/Text';
|
|||||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||||
import { GraphqlDataSourcesFormSection } from '@/features/ai/AssistantForm/components/GraphqlDataSourcesFormSection';
|
import { GraphqlDataSourcesFormSection } from '@/features/ai/AssistantForm/components/GraphqlDataSourcesFormSection';
|
||||||
import { WebhooksDataSourcesFormSection } from '@/features/ai/AssistantForm/components/WebhooksDataSourcesFormSection';
|
import { WebhooksDataSourcesFormSection } from '@/features/ai/AssistantForm/components/WebhooksDataSourcesFormSection';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
|
||||||
import type { DialogFormProps } from '@/types/common';
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { getHasuraAdminSecret } from '@/utils/env';
|
|
||||||
import { removeTypename, type DeepRequired } from '@/utils/helpers';
|
import { removeTypename, type DeepRequired } from '@/utils/helpers';
|
||||||
import {
|
import {
|
||||||
useInsertAssistantMutation,
|
useInsertAssistantMutation,
|
||||||
useUpdateAssistantMutation,
|
useUpdateAssistantMutation,
|
||||||
} from '@/utils/__generated__/graphite.graphql';
|
} from '@/utils/__generated__/graphite.graphql';
|
||||||
import {
|
|
||||||
ApolloClient,
|
|
||||||
HttpLink,
|
|
||||||
InMemoryCache,
|
|
||||||
type ApolloError,
|
|
||||||
} from '@apollo/client';
|
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
export const validationSchema = Yup.object({
|
export const validationSchema = Yup.object({
|
||||||
@@ -101,32 +92,15 @@ export default function AssistantForm({
|
|||||||
}: AssistantFormProps) {
|
}: AssistantFormProps) {
|
||||||
const { onDirtyStateChange } = useDialog();
|
const { onDirtyStateChange } = useDialog();
|
||||||
|
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { adminClient } = useAdminApolloClient();
|
||||||
|
|
||||||
const serviceUrl = generateAppServiceUrl(
|
|
||||||
currentProject?.subdomain,
|
|
||||||
currentProject?.region,
|
|
||||||
'graphql',
|
|
||||||
);
|
|
||||||
|
|
||||||
const client = new ApolloClient({
|
|
||||||
cache: new InMemoryCache(),
|
|
||||||
link: new HttpLink({
|
|
||||||
uri: serviceUrl,
|
|
||||||
headers: {
|
|
||||||
'x-hasura-admin-secret':
|
|
||||||
process.env.NEXT_PUBLIC_ENV === 'dev'
|
|
||||||
? getHasuraAdminSecret()
|
|
||||||
: currentProject?.config?.hasura.adminSecret,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const [insertAssistantMutation] = useInsertAssistantMutation({
|
const [insertAssistantMutation] = useInsertAssistantMutation({
|
||||||
client,
|
client: adminClient,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [updateAssistantMutation] = useUpdateAssistantMutation({ client });
|
const [updateAssistantMutation] = useUpdateAssistantMutation({
|
||||||
|
client: adminClient,
|
||||||
|
});
|
||||||
|
|
||||||
const form = useForm<AssistantFormValues>({
|
const form = useForm<AssistantFormValues>({
|
||||||
defaultValues: initialData,
|
defaultValues: initialData,
|
||||||
@@ -186,33 +160,18 @@ export default function AssistantForm({
|
|||||||
const handleSubmit = async (
|
const handleSubmit = async (
|
||||||
values: DeepRequired<AssistantFormValues> & { assistantID: string },
|
values: DeepRequired<AssistantFormValues> & { assistantID: string },
|
||||||
) => {
|
) => {
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
createOrUpdateAutoEmbeddings(values),
|
await createOrUpdateAutoEmbeddings(values);
|
||||||
{
|
onSubmit?.();
|
||||||
loading: 'Configuring the Assistant...',
|
},
|
||||||
success: `The Assistant has been configured successfully.`,
|
{
|
||||||
error: (arg: ApolloError) => {
|
loadingMessage: 'Configuring the Assistant...',
|
||||||
// we need to get the internal error message from the GraphQL error
|
successMessage: 'The Assistant has been configured successfully.',
|
||||||
const { internal } = arg.graphQLErrors[0]?.extensions || {};
|
errorMessage:
|
||||||
const { message } = (internal as Record<string, any>)?.error || {};
|
'An error occurred while configuring the Assistant. Please try again.',
|
||||||
|
},
|
||||||
// we use the default Apollo error message if we can't find the
|
);
|
||||||
// internal error message
|
|
||||||
return (
|
|
||||||
message ||
|
|
||||||
arg.message ||
|
|
||||||
'An error occurred while configuring the Assistant. Please try again.'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
onSubmit?.();
|
|
||||||
} catch {
|
|
||||||
// Note: The toast will handle the error.
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import { ControlledSelect } from '@/components/form/ControlledSelect';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { Box } from '@/components/ui/v2/Box';
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
@@ -6,34 +7,33 @@ import { ArrowsClockwise } from '@/components/ui/v2/icons/ArrowsClockwise';
|
|||||||
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
|
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
|
||||||
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
|
import { Option } from '@/components/ui/v2/Option';
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
|
||||||
import type { DialogFormProps } from '@/types/common';
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { getHasuraAdminSecret } from '@/utils/env';
|
|
||||||
import {
|
import {
|
||||||
useInsertGraphiteAutoEmbeddingsConfigurationMutation,
|
useInsertGraphiteAutoEmbeddingsConfigurationMutation,
|
||||||
useUpdateGraphiteAutoEmbeddingsConfigurationMutation,
|
useUpdateGraphiteAutoEmbeddingsConfigurationMutation,
|
||||||
} from '@/utils/__generated__/graphite.graphql';
|
} from '@/utils/__generated__/graphite.graphql';
|
||||||
import {
|
|
||||||
ApolloClient,
|
|
||||||
HttpLink,
|
|
||||||
InMemoryCache,
|
|
||||||
type ApolloError,
|
|
||||||
} from '@apollo/client';
|
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
|
const AUTO_EMBEDDINGS_MODELS = [
|
||||||
|
'text-embedding-ada-002',
|
||||||
|
'text-embedding-3-small',
|
||||||
|
'text-embedding-3-large',
|
||||||
|
];
|
||||||
|
|
||||||
export const validationSchema = Yup.object({
|
export const validationSchema = Yup.object({
|
||||||
name: Yup.string().required('The name is required.'),
|
name: Yup.string().required('The name field is required.'),
|
||||||
schemaName: Yup.string().required('The schema is required'),
|
model: Yup.string().oneOf(AUTO_EMBEDDINGS_MODELS),
|
||||||
tableName: Yup.string().required('The table is required'),
|
schemaName: Yup.string().required('The schema field is required'),
|
||||||
columnName: Yup.string().required('The column is required'),
|
tableName: Yup.string().required('The table field is required'),
|
||||||
|
columnName: Yup.string().required('The column field is required'),
|
||||||
query: Yup.string(),
|
query: Yup.string(),
|
||||||
mutation: Yup.string(),
|
mutation: Yup.string(),
|
||||||
});
|
});
|
||||||
@@ -49,7 +49,7 @@ export interface AutoEmbeddingsFormProps extends DialogFormProps {
|
|||||||
/**
|
/**
|
||||||
* if there is initialData then it's an update operation
|
* if there is initialData then it's an update operation
|
||||||
*/
|
*/
|
||||||
initialData?: AutoEmbeddingsFormValues;
|
initialData?: AutoEmbeddingsFormValues & { model: string };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to be called when the operation is cancelled.
|
* Function to be called when the operation is cancelled.
|
||||||
@@ -70,37 +70,23 @@ export default function AutoEmbeddingsForm({
|
|||||||
}: AutoEmbeddingsFormProps) {
|
}: AutoEmbeddingsFormProps) {
|
||||||
const { onDirtyStateChange } = useDialog();
|
const { onDirtyStateChange } = useDialog();
|
||||||
|
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { adminClient } = useAdminApolloClient();
|
||||||
|
|
||||||
const serviceUrl = generateAppServiceUrl(
|
|
||||||
currentProject?.subdomain,
|
|
||||||
currentProject?.region,
|
|
||||||
'graphql',
|
|
||||||
);
|
|
||||||
|
|
||||||
const client = new ApolloClient({
|
|
||||||
cache: new InMemoryCache(),
|
|
||||||
link: new HttpLink({
|
|
||||||
uri: serviceUrl,
|
|
||||||
headers: {
|
|
||||||
'x-hasura-admin-secret':
|
|
||||||
process.env.NEXT_PUBLIC_ENV === 'dev'
|
|
||||||
? getHasuraAdminSecret()
|
|
||||||
: currentProject?.config?.hasura.adminSecret,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const [insertGraphiteAutoEmbeddingsConfiguration] =
|
const [insertGraphiteAutoEmbeddingsConfiguration] =
|
||||||
useInsertGraphiteAutoEmbeddingsConfigurationMutation({
|
useInsertGraphiteAutoEmbeddingsConfigurationMutation({
|
||||||
client,
|
client: adminClient,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [updateGraphiteAutoEmbeddingsConfiguration] =
|
const [updateGraphiteAutoEmbeddingsConfiguration] =
|
||||||
useUpdateGraphiteAutoEmbeddingsConfigurationMutation({ client });
|
useUpdateGraphiteAutoEmbeddingsConfigurationMutation({
|
||||||
|
client: adminClient,
|
||||||
|
});
|
||||||
|
|
||||||
const form = useForm<AutoEmbeddingsFormValues>({
|
const form = useForm<AutoEmbeddingsFormValues>({
|
||||||
defaultValues: initialData,
|
defaultValues: {
|
||||||
|
...initialData,
|
||||||
|
model: initialData?.model ?? 'text-embedding-ada-002',
|
||||||
|
},
|
||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
@@ -132,47 +118,34 @@ export default function AutoEmbeddingsForm({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await insertGraphiteAutoEmbeddingsConfiguration({
|
await insertGraphiteAutoEmbeddingsConfiguration({
|
||||||
variables: values,
|
variables: {
|
||||||
|
...values,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async (values: AutoEmbeddingsFormValues) => {
|
const handleSubmit = async (values: AutoEmbeddingsFormValues) => {
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
createOrUpdateAutoEmbeddings(values),
|
await createOrUpdateAutoEmbeddings(values);
|
||||||
{
|
onSubmit?.();
|
||||||
loading: 'Configuring the Auto-Embeddings...',
|
},
|
||||||
success: `The Auto-Embeddings has been configured successfully.`,
|
{
|
||||||
error: (arg: ApolloError) => {
|
loadingMessage: 'Configuring the Auto-Embeddings...',
|
||||||
// we need to get the internal error message from the GraphQL error
|
successMessage: 'The Auto-Embeddings has been configured successfully.',
|
||||||
const { internal } = arg.graphQLErrors[0]?.extensions || {};
|
errorMessage:
|
||||||
const { message } = (internal as Record<string, any>)?.error || {};
|
'An error occurred while configuring the Auto-Embeddings. Please try again.',
|
||||||
|
},
|
||||||
// we use the default Apollo error message if we can't find the
|
);
|
||||||
// internal error message
|
|
||||||
return (
|
|
||||||
message ||
|
|
||||||
arg.message ||
|
|
||||||
'An error occurred while configuring the Auto-Embeddings. Please try again.'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
onSubmit?.();
|
|
||||||
} catch {
|
|
||||||
// Note: The toast will handle the error.
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<Form
|
<Form
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
className="flex h-full flex-col gap-4 overflow-hidden"
|
className="flex flex-col h-full gap-4 overflow-hidden"
|
||||||
>
|
>
|
||||||
<div className="flex flex-1 flex-col space-y-6 overflow-auto px-6">
|
<div className="flex flex-col flex-1 px-6 space-y-6 overflow-auto">
|
||||||
<Input
|
<Input
|
||||||
{...register('name')}
|
{...register('name')}
|
||||||
id="name"
|
id="name"
|
||||||
@@ -182,7 +155,7 @@ export default function AutoEmbeddingsForm({
|
|||||||
<Tooltip title="Name of the Auto-Embeddings">
|
<Tooltip title="Name of the Auto-Embeddings">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -196,6 +169,36 @@ export default function AutoEmbeddingsForm({
|
|||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ControlledSelect
|
||||||
|
slotProps={{
|
||||||
|
popper: { disablePortal: false, className: 'z-[10000]' },
|
||||||
|
}}
|
||||||
|
id="model"
|
||||||
|
name="model"
|
||||||
|
label={
|
||||||
|
<Box className="flex flex-row items-center space-x-2">
|
||||||
|
<Text>Model</Text>
|
||||||
|
<Tooltip title="Auto-Embeddings Model">
|
||||||
|
<InfoIcon
|
||||||
|
aria-label="Info"
|
||||||
|
className="w-4 h-4"
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
fullWidth
|
||||||
|
error={!!errors?.model?.message}
|
||||||
|
helperText={errors?.model?.message}
|
||||||
|
>
|
||||||
|
{AUTO_EMBEDDINGS_MODELS.map((model) => (
|
||||||
|
<Option key={model} value={model}>
|
||||||
|
{model}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</ControlledSelect>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
{...register('schemaName')}
|
{...register('schemaName')}
|
||||||
id="schemaName"
|
id="schemaName"
|
||||||
@@ -205,7 +208,7 @@ export default function AutoEmbeddingsForm({
|
|||||||
<Tooltip title={<span>Schema where the table belongs to</span>}>
|
<Tooltip title={<span>Schema where the table belongs to</span>}>
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -227,7 +230,7 @@ export default function AutoEmbeddingsForm({
|
|||||||
<Tooltip title="Table Name">
|
<Tooltip title="Table Name">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -249,7 +252,7 @@ export default function AutoEmbeddingsForm({
|
|||||||
<Tooltip title="Column name">
|
<Tooltip title="Column name">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -271,7 +274,7 @@ export default function AutoEmbeddingsForm({
|
|||||||
<Tooltip title="Query">
|
<Tooltip title="Query">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -295,7 +298,7 @@ export default function AutoEmbeddingsForm({
|
|||||||
<Tooltip title="Mutation">
|
<Tooltip title="Mutation">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -312,7 +315,7 @@ export default function AutoEmbeddingsForm({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Box className="flex w-full flex-row justify-between rounded border-t px-6 py-4">
|
<Box className="flex flex-row justify-between w-full px-6 py-4 border-t rounded">
|
||||||
<Button variant="outlined" color="secondary" onClick={onCancel}>
|
<Button variant="outlined" color="secondary" onClick={onCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -2,20 +2,11 @@ import { Box } from '@/components/ui/v2/Box';
|
|||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
import { Checkbox } from '@/components/ui/v2/Checkbox';
|
import { Checkbox } from '@/components/ui/v2/Checkbox';
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
|
||||||
import { getHasuraAdminSecret } from '@/utils/env';
|
|
||||||
import { useDeleteAssistantMutation } from '@/utils/__generated__/graphite.graphql';
|
import { useDeleteAssistantMutation } from '@/utils/__generated__/graphite.graphql';
|
||||||
import {
|
|
||||||
ApolloClient,
|
|
||||||
HttpLink,
|
|
||||||
InMemoryCache,
|
|
||||||
type ApolloError,
|
|
||||||
} from '@apollo/client';
|
|
||||||
import { type Assistant } from 'pages/[workspaceSlug]/[appSlug]/ai/assistants';
|
import { type Assistant } from 'pages/[workspaceSlug]/[appSlug]/ai/assistants';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export interface DeleteAssistantModalProps {
|
export interface DeleteAssistantModalProps {
|
||||||
@@ -32,29 +23,10 @@ export default function DeleteAssistantModal({
|
|||||||
const [remove, setRemove] = useState(false);
|
const [remove, setRemove] = useState(false);
|
||||||
const [loadingRemove, setLoadingRemove] = useState(false);
|
const [loadingRemove, setLoadingRemove] = useState(false);
|
||||||
|
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { adminClient } = useAdminApolloClient();
|
||||||
|
|
||||||
const serviceUrl = generateAppServiceUrl(
|
|
||||||
currentProject?.subdomain,
|
|
||||||
currentProject?.region,
|
|
||||||
'graphql',
|
|
||||||
);
|
|
||||||
|
|
||||||
const client = new ApolloClient({
|
|
||||||
cache: new InMemoryCache(),
|
|
||||||
link: new HttpLink({
|
|
||||||
uri: serviceUrl,
|
|
||||||
headers: {
|
|
||||||
'x-hasura-admin-secret':
|
|
||||||
process.env.NEXT_PUBLIC_ENV === 'dev'
|
|
||||||
? getHasuraAdminSecret()
|
|
||||||
: currentProject?.config?.hasura.adminSecret,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const [deleteAssistantMutation] = useDeleteAssistantMutation({
|
const [deleteAssistantMutation] = useDeleteAssistantMutation({
|
||||||
client,
|
client: adminClient,
|
||||||
});
|
});
|
||||||
|
|
||||||
const deleteAssistant = async () => {
|
const deleteAssistant = async () => {
|
||||||
@@ -70,27 +42,12 @@ export default function DeleteAssistantModal({
|
|||||||
async function handleClick() {
|
async function handleClick() {
|
||||||
setLoadingRemove(true);
|
setLoadingRemove(true);
|
||||||
|
|
||||||
await toast.promise(
|
await execPromiseWithErrorToast(deleteAssistant, {
|
||||||
deleteAssistant(),
|
loadingMessage: 'Deleting the assistant...',
|
||||||
{
|
successMessage: 'The Assistant has been deleted successfully.',
|
||||||
loading: 'Deleting the assistant...',
|
errorMessage:
|
||||||
success: `The Assistant has been deleted successfully.`,
|
'An error occurred while deleting the Assistant. Please try again.',
|
||||||
error: (arg: ApolloError) => {
|
});
|
||||||
// we need to get the internal error message from the GraphQL error
|
|
||||||
const { internal } = arg.graphQLErrors[0]?.extensions || {};
|
|
||||||
const { message } = (internal as Record<string, any>)?.error || {};
|
|
||||||
|
|
||||||
// we use the default Apollo error message if we can't find the
|
|
||||||
// internal error message
|
|
||||||
return (
|
|
||||||
message ||
|
|
||||||
arg.message ||
|
|
||||||
'An error occurred while deleting the Assistant. Please try again.'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,18 +4,12 @@ import { Checkbox } from '@/components/ui/v2/Checkbox';
|
|||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
|
||||||
import { getHasuraAdminSecret } from '@/utils/env';
|
import { getHasuraAdminSecret } from '@/utils/env';
|
||||||
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { useDeleteGraphiteAutoEmbeddingsConfigurationMutation } from '@/utils/__generated__/graphite.graphql';
|
import { useDeleteGraphiteAutoEmbeddingsConfigurationMutation } from '@/utils/__generated__/graphite.graphql';
|
||||||
import {
|
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
|
||||||
ApolloClient,
|
|
||||||
HttpLink,
|
|
||||||
InMemoryCache,
|
|
||||||
type ApolloError,
|
|
||||||
} from '@apollo/client';
|
|
||||||
import { type AutoEmbeddingsConfiguration } from 'pages/[workspaceSlug]/[appSlug]/ai/auto-embeddings';
|
import { type AutoEmbeddingsConfiguration } from 'pages/[workspaceSlug]/[appSlug]/ai/auto-embeddings';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export interface DeleteAutoEmbeddingsModalProps {
|
export interface DeleteAutoEmbeddingsModalProps {
|
||||||
@@ -71,27 +65,13 @@ export default function DeleteAutoEmbeddingsModal({
|
|||||||
async function handleClick() {
|
async function handleClick() {
|
||||||
setLoadingRemove(true);
|
setLoadingRemove(true);
|
||||||
|
|
||||||
await toast.promise(
|
await execPromiseWithErrorToast(deleteAutoEmbeddingsConfig, {
|
||||||
deleteAutoEmbeddingsConfig(),
|
loadingMessage: 'Deleting Auto-Embeddings Configuration...',
|
||||||
{
|
successMessage:
|
||||||
loading: 'Deleting Auto-Embeddings Configuration...',
|
'The Auto-Embeddings Configuration has been deleted successfully.',
|
||||||
success: `The Auto-Embeddings Configuration has been deleted successfully.`,
|
errorMessage:
|
||||||
error: (arg: ApolloError) => {
|
'An error occurred while deleting the Auto-Embeddings Configuration. Please try again.',
|
||||||
// we need to get the internal error message from the GraphQL error
|
});
|
||||||
const { internal } = arg.graphQLErrors[0]?.extensions || {};
|
|
||||||
const { message } = (internal as Record<string, any>)?.error || {};
|
|
||||||
|
|
||||||
// we use the default Apollo error message if we can't find the
|
|
||||||
// internal error message
|
|
||||||
return (
|
|
||||||
message ||
|
|
||||||
arg.message ||
|
|
||||||
'An error occurred while deleting the Auto-Embeddings Configuration. Please try again.'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { UpgradeToProBanner } from '@/components/common/UpgradeToProBanner';
|
|||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { Alert } from '@/components/ui/v2/Alert';
|
import { Alert } from '@/components/ui/v2/Alert';
|
||||||
import { Box } from '@/components/ui/v2/Box';
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
|
import { ErrorToast } from '@/components/ui/v2/ErrorToast';
|
||||||
import { IconButton } from '@/components/ui/v2/IconButton';
|
import { IconButton } from '@/components/ui/v2/IconButton';
|
||||||
import { ArrowUpIcon } from '@/components/ui/v2/icons/ArrowUpIcon';
|
import { ArrowUpIcon } from '@/components/ui/v2/icons/ArrowUpIcon';
|
||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
@@ -17,7 +18,6 @@ import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminA
|
|||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { useIsGraphiteEnabled } from '@/features/projects/common/hooks/useIsGraphiteEnabled';
|
import { useIsGraphiteEnabled } from '@/features/projects/common/hooks/useIsGraphiteEnabled';
|
||||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
|
||||||
import {
|
import {
|
||||||
useSendDevMessageMutation,
|
useSendDevMessageMutation,
|
||||||
useStartDevSessionMutation,
|
useStartDevSessionMutation,
|
||||||
@@ -122,11 +122,17 @@ export default function DevAssistant() {
|
|||||||
|
|
||||||
setMessages(thread);
|
setMessages(thread);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(
|
toast.custom(
|
||||||
'Failed to send the message to graphite. Please try again later.',
|
(t) => (
|
||||||
|
<ErrorToast
|
||||||
|
isVisible={t.visible}
|
||||||
|
errorMessage="Failed to send the message. Please try again later."
|
||||||
|
error={error}
|
||||||
|
close={() => toast.dismiss()}
|
||||||
|
/>
|
||||||
|
),
|
||||||
{
|
{
|
||||||
style: getToastStyleProps().style,
|
duration: Number.POSITIVE_INFINITY,
|
||||||
...getToastStyleProps().error,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function PreComponent(
|
|||||||
const { children } = props;
|
const { children } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group relative">
|
<div className="relative group">
|
||||||
<pre>{children}</pre>
|
<pre>{children}</pre>
|
||||||
<IconButton
|
<IconButton
|
||||||
sx={{
|
sx={{
|
||||||
@@ -34,13 +34,13 @@ function PreComponent(
|
|||||||
}}
|
}}
|
||||||
color="warning"
|
color="warning"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
className="absolute top-2 right-2 hidden group-hover:flex"
|
className="absolute hidden top-2 right-2 group-hover:flex"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
copy(onlyText(children), 'Snippet');
|
copy(onlyText(children), 'Snippet');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CopyIcon className="h-5 w-5" />
|
<CopyIcon className="w-5 h-5" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -53,7 +53,7 @@ export default function MessageBox({ message }: { message: Message }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
className="flex flex-col space-y-4 border-t p-4 first:border-t-0"
|
className="flex flex-col p-4 space-y-4 border-t first:border-t-0"
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: isUserMessage && 'background.default',
|
backgroundColor: isUserMessage && 'background.default',
|
||||||
}}
|
}}
|
||||||
@@ -67,7 +67,7 @@ export default function MessageBox({ message }: { message: Message }) {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Avatar
|
<Avatar
|
||||||
className="h-7 w-7 rounded-full"
|
className="rounded-full h-7 w-7"
|
||||||
alt={user?.displayName}
|
alt={user?.displayName}
|
||||||
src={user?.avatarUrl}
|
src={user?.avatarUrl}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
||||||
@@ -12,6 +13,7 @@ import { Switch } from '@/components/ui/v2/Switch';
|
|||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||||
import { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection';
|
import { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection';
|
||||||
import {
|
import {
|
||||||
@@ -20,9 +22,10 @@ import {
|
|||||||
useGetSoftwareVersionsQuery,
|
useGetSoftwareVersionsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
|
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
@@ -30,6 +33,8 @@ import { toast } from 'react-hot-toast';
|
|||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { DisableAIServiceConfirmationDialog } from './DisableAIServiceConfirmationDialog';
|
import { DisableAIServiceConfirmationDialog } from './DisableAIServiceConfirmationDialog';
|
||||||
|
|
||||||
|
const MIN_POSTGRES_VERSION_SUPPORTING_AI = '14.6-20231018-1';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
version: Yup.object({
|
version: Yup.object({
|
||||||
label: Yup.string().required(),
|
label: Yup.string().required(),
|
||||||
@@ -48,21 +53,28 @@ const validationSchema = Yup.object({
|
|||||||
export type AISettingsFormValues = Yup.InferType<typeof validationSchema>;
|
export type AISettingsFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function AISettings() {
|
export default function AISettings() {
|
||||||
const { maintenanceActive } = useUI();
|
const isPlatform = useIsPlatform();
|
||||||
const { openDialog } = useDialog();
|
const { openDialog } = useDialog();
|
||||||
const [updateConfig] = useUpdateConfigMutation();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
|
});
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
|
|
||||||
const [aiServiceEnabled, setAIServiceEnabled] = useState(true);
|
const [aiServiceEnabled, setAIServiceEnabled] = useState(true);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { config: { ai } = {} } = {},
|
data: {
|
||||||
|
config: { ai, postgres: { version: postgresVersion } = {} } = {},
|
||||||
|
} = {},
|
||||||
loading: loadingAiSettings,
|
loading: loadingAiSettings,
|
||||||
error: errorGettingAiSettings,
|
error: errorGettingAiSettings,
|
||||||
} = useGetAiSettingsQuery({
|
} = useGetAiSettingsQuery({
|
||||||
variables: {
|
variables: {
|
||||||
appId: currentProject.id,
|
appId: currentProject.id,
|
||||||
},
|
},
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: graphiteVersionsData, loading: loadingGraphiteVersionsData } =
|
const { data: graphiteVersionsData, loading: loadingGraphiteVersionsData } =
|
||||||
@@ -70,6 +82,7 @@ export default function AISettings() {
|
|||||||
variables: {
|
variables: {
|
||||||
software: Software_Type_Enum.Graphite,
|
software: Software_Type_Enum.Graphite,
|
||||||
},
|
},
|
||||||
|
skip: !isPlatform,
|
||||||
});
|
});
|
||||||
|
|
||||||
const graphiteVersions = graphiteVersionsData?.softwareVersions || [];
|
const graphiteVersions = graphiteVersionsData?.softwareVersions || [];
|
||||||
@@ -94,8 +107,8 @@ export default function AISettings() {
|
|||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
version: {
|
version: {
|
||||||
label: ai?.version ?? availableVersions?.at(0)?.label,
|
label: ai?.version || availableVersions?.at(0)?.label || '',
|
||||||
value: ai?.version ?? availableVersions?.at(0)?.value,
|
value: ai?.version || availableVersions?.at(0)?.value || '',
|
||||||
},
|
},
|
||||||
webhookSecret: '',
|
webhookSecret: '',
|
||||||
organization: '',
|
organization: '',
|
||||||
@@ -152,6 +165,17 @@ export default function AISettings() {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const toggleAIService = async (enabled: boolean) => {
|
const toggleAIService = async (enabled: boolean) => {
|
||||||
|
if (postgresVersion < MIN_POSTGRES_VERSION_SUPPORTING_AI) {
|
||||||
|
toast.error(
|
||||||
|
'In order to enable the AI service you need to update your database version to 14.6-20231018-1 or newer.',
|
||||||
|
{
|
||||||
|
style: getToastStyleProps().style,
|
||||||
|
...getToastStyleProps().error,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setAIServiceEnabled(enabled);
|
setAIServiceEnabled(enabled);
|
||||||
|
|
||||||
if (!enabled && ai) {
|
if (!enabled && ai) {
|
||||||
@@ -182,9 +206,9 @@ export default function AISettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleSubmit(formValues: AISettingsFormValues) {
|
async function handleSubmit(formValues: AISettingsFormValues) {
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfig({
|
await updateConfig({
|
||||||
variables: {
|
variables: {
|
||||||
appId: currentProject.id,
|
appId: currentProject.id,
|
||||||
config: {
|
config: {
|
||||||
@@ -207,21 +231,29 @@ export default function AISettings() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
});
|
||||||
{
|
|
||||||
loading: `AI settings are being updated...`,
|
|
||||||
success: `AI settings has been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the AI settings!`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
form.reset(formValues);
|
||||||
} catch {
|
|
||||||
// Note: The toast will handle the error.
|
if (!isPlatform) {
|
||||||
}
|
openDialog({
|
||||||
|
title: 'Apply your changes',
|
||||||
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'AI settings are being updated...',
|
||||||
|
successMessage: 'AI settings has been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while trying to update the AI settings!',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAIResourcesCost = () => {
|
const getAIResourcesCost = () => {
|
||||||
@@ -236,7 +268,7 @@ export default function AISettings() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="space-y-4" sx={{ backgroundColor: 'background.default' }}>
|
<Box className="space-y-4" sx={{ backgroundColor: 'background.default' }}>
|
||||||
<Box className="flex flex-row items-center justify-between rounded-lg border-1 p-4">
|
<Box className="flex flex-row items-center justify-between p-4 rounded-lg border-1">
|
||||||
<Text className="text-lg font-semibold">Enable AI service</Text>
|
<Text className="text-lg font-semibold">Enable AI service</Text>
|
||||||
<Switch
|
<Switch
|
||||||
checked={aiServiceEnabled}
|
checked={aiServiceEnabled}
|
||||||
@@ -259,54 +291,58 @@ export default function AISettings() {
|
|||||||
className="flex flex-col"
|
className="flex flex-col"
|
||||||
>
|
>
|
||||||
<Box className="space-y-4">
|
<Box className="space-y-4">
|
||||||
{availableVersions.length > 0 && (
|
<Box className="space-y-2">
|
||||||
<Box className="space-y-2">
|
<Box className="flex flex-row items-center space-x-2">
|
||||||
<Box className="flex flex-row items-center space-x-2">
|
<Text className="text-lg font-semibold">Version</Text>
|
||||||
<Text className="text-lg font-semibold">Version</Text>
|
<Tooltip title="Version of the service to use.">
|
||||||
<Tooltip title="Version of the service to use.">
|
<InfoIcon
|
||||||
<InfoIcon
|
aria-label="Info"
|
||||||
aria-label="Info"
|
className="w-4 h-4"
|
||||||
className="h-4 w-4"
|
color="primary"
|
||||||
color="primary"
|
/>
|
||||||
/>
|
</Tooltip>
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
|
||||||
<ControlledAutocomplete
|
|
||||||
id="version"
|
|
||||||
name="version"
|
|
||||||
autoHighlight
|
|
||||||
isOptionEqualToValue={() => false}
|
|
||||||
filterOptions={(options, { inputValue }) => {
|
|
||||||
const inputValueLower = inputValue.toLowerCase();
|
|
||||||
const matched = [];
|
|
||||||
const otherOptions = [];
|
|
||||||
|
|
||||||
options.forEach((option) => {
|
|
||||||
const optionLabelLower = option.label.toLowerCase();
|
|
||||||
|
|
||||||
if (optionLabelLower.startsWith(inputValueLower)) {
|
|
||||||
matched.push(option);
|
|
||||||
} else {
|
|
||||||
otherOptions.push(option);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = [...matched, ...otherOptions];
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}}
|
|
||||||
fullWidth
|
|
||||||
className="col-span-4"
|
|
||||||
options={availableVersions}
|
|
||||||
error={!!formState.errors?.version?.message}
|
|
||||||
helperText={formState.errors?.version?.message}
|
|
||||||
showCustomOption="auto"
|
|
||||||
customOptionLabel={(value) =>
|
|
||||||
`Use custom value: "${value}"`
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
<ControlledAutocomplete
|
||||||
|
id="version"
|
||||||
|
name="version"
|
||||||
|
autoHighlight
|
||||||
|
isOptionEqualToValue={() => false}
|
||||||
|
filterOptions={(options, { inputValue }) => {
|
||||||
|
const inputValueLower = inputValue.toLowerCase();
|
||||||
|
const matched = [];
|
||||||
|
const otherOptions = [];
|
||||||
|
|
||||||
|
options.forEach((option) => {
|
||||||
|
const optionLabelLower = option.label.toLowerCase();
|
||||||
|
|
||||||
|
if (optionLabelLower.startsWith(inputValueLower)) {
|
||||||
|
matched.push(option);
|
||||||
|
} else {
|
||||||
|
otherOptions.push(option);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = [...matched, ...otherOptions];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
className="col-span-4"
|
||||||
|
options={availableVersions}
|
||||||
|
error={
|
||||||
|
!!formState.errors?.version?.message ||
|
||||||
|
!!formState.errors?.version?.value?.message
|
||||||
|
}
|
||||||
|
helperText={
|
||||||
|
formState.errors?.version?.message ||
|
||||||
|
formState.errors?.version?.value?.message
|
||||||
|
}
|
||||||
|
showCustomOption="auto"
|
||||||
|
customOptionLabel={(value) =>
|
||||||
|
`Use custom value: "${value}"`
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box className="space-y-2">
|
<Box className="space-y-2">
|
||||||
<Box className="flex flex-row items-center space-x-2">
|
<Box className="flex flex-row items-center space-x-2">
|
||||||
@@ -316,7 +352,7 @@ export default function AISettings() {
|
|||||||
<Tooltip title="Used to validate requests between postgres and the AI service. The AI service will also include the header X-Graphite-Webhook-Secret with this value set when calling external webhooks so the source of the request can be validated.">
|
<Tooltip title="Used to validate requests between postgres and the AI service. The AI service will also include the header X-Graphite-Webhook-Secret with this value set when calling external webhooks so the source of the request can be validated.">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -340,26 +376,28 @@ export default function AISettings() {
|
|||||||
<Tooltip title="Dedicated resources allocated for the service.">
|
<Tooltip title="Dedicated resources allocated for the service.">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Alert
|
{isPlatform ? (
|
||||||
severity="info"
|
<Alert
|
||||||
className="flex items-center justify-between space-x-2"
|
severity="info"
|
||||||
>
|
className="flex items-center justify-between space-x-2"
|
||||||
<span>{getAIResourcesCost()}</span>
|
>
|
||||||
<b>
|
<span>{getAIResourcesCost()}</span>
|
||||||
$
|
<b>
|
||||||
{parseFloat(
|
$
|
||||||
(
|
{parseFloat(
|
||||||
aiSettingsFormValues.compute.cpu * COST_PER_VCPU
|
(
|
||||||
).toFixed(2),
|
aiSettingsFormValues.compute.cpu * COST_PER_VCPU
|
||||||
)}
|
).toFixed(2),
|
||||||
</b>
|
)}
|
||||||
</Alert>
|
</b>
|
||||||
|
</Alert>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<ComputeFormSection />
|
<ComputeFormSection />
|
||||||
</Box>
|
</Box>
|
||||||
@@ -378,7 +416,7 @@ export default function AISettings() {
|
|||||||
<Tooltip title="Key to use for authenticating API requests to OpenAI">
|
<Tooltip title="Key to use for authenticating API requests to OpenAI">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -401,7 +439,7 @@ export default function AISettings() {
|
|||||||
<Tooltip title="Optional. OpenAI organization to use.">
|
<Tooltip title="Optional. OpenAI organization to use.">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -429,7 +467,7 @@ export default function AISettings() {
|
|||||||
<Tooltip title="How often to run the job that keeps embeddings up to date.">
|
<Tooltip title="How often to run the job that keeps embeddings up to date.">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { Box } from '@/components/ui/v2/Box';
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { useUpdateConfigMutation } from '@/utils/__generated__/graphql';
|
import { useUpdateConfigMutation } from '@/utils/__generated__/graphql';
|
||||||
import type { ApolloError } from '@apollo/client';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export interface DisableAIServiceConfirmationDialogProps {
|
export interface DisableAIServiceConfirmationDialogProps {
|
||||||
@@ -25,46 +26,50 @@ export default function DisableAIServiceConfirmationDialog({
|
|||||||
onCancel,
|
onCancel,
|
||||||
onServiceDisabled,
|
onServiceDisabled,
|
||||||
}: DisableAIServiceConfirmationDialogProps) {
|
}: DisableAIServiceConfirmationDialogProps) {
|
||||||
const { closeDialog } = useDialog();
|
const isPlatform = useIsPlatform();
|
||||||
|
const { openDialog, closeDialog } = useDialog();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
const [updateConfig] = useUpdateConfigMutation();
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
async function handleClick() {
|
async function handleClick() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
await toast.promise(
|
await execPromiseWithErrorToast(
|
||||||
updateConfig({
|
async () => {
|
||||||
variables: {
|
await updateConfig({
|
||||||
appId: currentProject.id,
|
variables: {
|
||||||
config: {
|
appId: currentProject.id,
|
||||||
ai: null,
|
config: {
|
||||||
|
ai: null,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
}),
|
|
||||||
{
|
|
||||||
loading: 'Disabling the AI service...',
|
|
||||||
success: () => {
|
|
||||||
onServiceDisabled();
|
|
||||||
closeDialog();
|
|
||||||
return `The service has been disabled.`;
|
|
||||||
},
|
|
||||||
error: (arg: ApolloError) => {
|
|
||||||
// we need to get the internal error message from the GraphQL error
|
|
||||||
const { internal } = arg.graphQLErrors[0]?.extensions || {};
|
|
||||||
const { message } = (internal as Record<string, any>)?.error || {};
|
|
||||||
|
|
||||||
// we use the default Apollo error message if we can't find the
|
onServiceDisabled();
|
||||||
// internal error message
|
closeDialog();
|
||||||
return (
|
|
||||||
message ||
|
if (!isPlatform) {
|
||||||
arg.message ||
|
openDialog({
|
||||||
'An error occurred while disabling the AI service. Please try again later.'
|
title: 'Apply your changes',
|
||||||
);
|
component: <ApplyLocalSettingsDialog />,
|
||||||
},
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Disabling the AI service...',
|
||||||
|
successMessage: 'The service has been disabled.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while disabling the AI service. Please try again later.',
|
||||||
},
|
},
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
query GetAISettings($appId: uuid!) {
|
query GetAISettings($appId: uuid!) {
|
||||||
config(appID: $appId, resolve: false) {
|
config(appID: $appId, resolve: false) {
|
||||||
|
postgres {
|
||||||
|
version
|
||||||
|
}
|
||||||
ai {
|
ai {
|
||||||
version
|
version
|
||||||
webhookSecret
|
webhookSecret
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetAuthenticationSettingsDocument,
|
GetAuthenticationSettingsDocument,
|
||||||
useGetAuthenticationSettingsQuery,
|
useGetAuthenticationSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
@@ -28,15 +31,19 @@ export type AllowedEmailSettingsFormValues = Yup.InferType<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
export default function AllowedEmailDomainsSettings() {
|
export default function AllowedEmailDomainsSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetAuthenticationSettingsDocument],
|
refetchQueries: [GetAuthenticationSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { email, emailDomains } = data?.config?.auth?.user || {};
|
const { email, emailDomains } = data?.config?.auth?.user || {};
|
||||||
@@ -56,6 +63,17 @@ export default function AllowedEmailDomainsSettings() {
|
|||||||
|
|
||||||
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && email && emailDomains) {
|
||||||
|
form.reset({
|
||||||
|
enabled:
|
||||||
|
email?.allowed?.length > 0 || emailDomains?.allowed?.length > 0,
|
||||||
|
allowedEmails: email?.allowed?.join(', ') || '',
|
||||||
|
allowedEmailDomains: emailDomains?.allowed?.join(', ') || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, form, email, emailDomains]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -103,21 +121,31 @@ export default function AllowedEmailDomainsSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(values);
|
||||||
loading: `Allowed email settings are being updated...`,
|
|
||||||
success: `Allowed email settings have been updated successfully.`,
|
if (!isPlatform) {
|
||||||
error: getServerError(
|
openDialog({
|
||||||
`An error occurred while trying to update the project's allowed email settings.`,
|
title: 'Apply your changes',
|
||||||
),
|
component: <ApplyLocalSettingsDialog />,
|
||||||
},
|
props: {
|
||||||
getToastStyleProps(),
|
PaperProps: {
|
||||||
);
|
className: 'max-w-2xl',
|
||||||
} catch {
|
},
|
||||||
// Note: The toast will handle the error
|
},
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Allowed email settings are being updated...',
|
||||||
|
successMessage:
|
||||||
|
'Allowed email settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
"An error occurred while trying to update the project's allowed email settings.",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
form.reset(values);
|
form.reset(values);
|
||||||
};
|
};
|
||||||
@@ -134,7 +162,7 @@ export default function AllowedEmailDomainsSettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/authentication#allowed-emails-and-domains"
|
docsLink="https://docs.nhost.io/guides/auth/overview#allowed-emails-and-domains"
|
||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetAuthenticationSettingsDocument,
|
GetAuthenticationSettingsDocument,
|
||||||
useGetAuthenticationSettingsQuery,
|
useGetAuthenticationSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -25,15 +28,19 @@ export type AllowedRedirectURLFormValues = Yup.InferType<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
export default function AllowedRedirectURLsSettings() {
|
export default function AllowedRedirectURLsSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetAuthenticationSettingsDocument],
|
refetchQueries: [GetAuthenticationSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { allowedUrls } = data?.config?.auth?.redirections || {};
|
const { allowedUrls } = data?.config?.auth?.redirections || {};
|
||||||
@@ -46,6 +53,14 @@ export default function AllowedRedirectURLsSettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && allowedUrls) {
|
||||||
|
form.reset({
|
||||||
|
allowedUrls: allowedUrls?.join(', ') || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, allowedUrls, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -80,23 +95,31 @@ export default function AllowedRedirectURLsSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(values);
|
||||||
loading: `Allowed redirect URL settings are being updated...`,
|
|
||||||
success: `Allowed redirect URL settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's allowed redirect URL settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(values);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Allowed redirect URL settings are being updated...',
|
||||||
|
successMessage:
|
||||||
|
'Allowed redirect URL settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
"An error occurred while trying to update the project's allowed redirect URL settings.",
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -111,7 +134,7 @@ export default function AllowedRedirectURLsSettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/authentication#allowed-redirect-urls"
|
docsLink="https://docs.nhost.io/guides/auth/overview#allowed-redirect-urls"
|
||||||
className="grid grid-flow-row px-4 lg:grid-cols-5"
|
className="grid grid-flow-row px-4 lg:grid-cols-5"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -22,15 +25,19 @@ const validationSchema = Yup.object({
|
|||||||
export type AnonymousSignInFormValues = Yup.InferType<typeof validationSchema>;
|
export type AnonymousSignInFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function AnonymousSignInSettings() {
|
export default function AnonymousSignInSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetSignInMethodsDocument],
|
refetchQueries: [GetSignInMethodsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { enabled } = data?.config?.auth?.method?.anonymous || {};
|
const { enabled } = data?.config?.auth?.method?.anonymous || {};
|
||||||
@@ -43,6 +50,12 @@ export default function AnonymousSignInSettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({ enabled });
|
||||||
|
}
|
||||||
|
}, [loading, enabled, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -73,23 +86,31 @@ export default function AnonymousSignInSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(values);
|
||||||
loading: `Anonymous sign-in settings are being updated...`,
|
|
||||||
success: `Anonymous sign-in settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update Anonymous sign-in settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(values);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Anonymous sign-in settings are being updated...',
|
||||||
|
successMessage:
|
||||||
|
'Anonymous sign-in settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while trying to update Anonymous sign-in settings.',
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
@@ -7,19 +9,20 @@ import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
|
|||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
|
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { useTheme } from '@mui/material';
|
import { useTheme } from '@mui/material';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
@@ -55,15 +58,19 @@ export type AppleProviderFormValues = Yup.InferType<typeof validationSchema>;
|
|||||||
|
|
||||||
export default function AppleProviderSettings() {
|
export default function AppleProviderSettings() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetSignInMethodsDocument],
|
refetchQueries: [GetSignInMethodsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { clientId, enabled, keyId, privateKey, teamId } =
|
const { clientId, enabled, keyId, privateKey, teamId } =
|
||||||
@@ -81,6 +88,18 @@ export default function AppleProviderSettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
teamId: teamId || '',
|
||||||
|
keyId: keyId || '',
|
||||||
|
clientId: clientId || '',
|
||||||
|
privateKey: privateKey || '',
|
||||||
|
enabled: enabled || false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, teamId, keyId, clientId, privateKey, enabled, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -117,23 +136,30 @@ export default function AppleProviderSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Apple settings are being updated...`,
|
|
||||||
success: `Apple settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's Apple settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Apple settings are being updated...',
|
||||||
|
successMessage: 'Apple settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
"An error occurred while trying to update the project's Apple settings.",
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -148,7 +174,7 @@ export default function AppleProviderSettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/authentication/sign-in-with-apple"
|
docsLink="https://docs.nhost.io/guides/auth/social/sign-in-apple"
|
||||||
docsTitle="how to sign in users with Apple"
|
docsTitle="how to sign in users with Apple"
|
||||||
icon={
|
icon={
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
@@ -158,7 +184,7 @@ export default function AppleProviderSettings() {
|
|||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-y-4 gap-x-3 px-4 py-2',
|
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-x-3 gap-y-4 px-4 py-2',
|
||||||
!authEnabled && 'hidden',
|
!authEnabled && 'hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -243,7 +269,7 @@ export default function AppleProviderSettings() {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CopyIcon className="h-4 w-4" />
|
<CopyIcon className="w-4 h-4" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetAuthenticationSettingsDocument,
|
GetAuthenticationSettingsDocument,
|
||||||
Software_Type_Enum,
|
Software_Type_Enum,
|
||||||
@@ -11,11 +14,11 @@ import {
|
|||||||
useGetSoftwareVersionsQuery,
|
useGetSoftwareVersionsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -30,21 +33,26 @@ export type AuthServiceVersionFormValues = Yup.InferType<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
export default function AuthServiceVersionSettings() {
|
export default function AuthServiceVersionSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetAuthenticationSettingsDocument],
|
refetchQueries: [GetAuthenticationSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: authVersionsData } = useGetSoftwareVersionsQuery({
|
const { data: authVersionsData } = useGetSoftwareVersionsQuery({
|
||||||
variables: {
|
variables: {
|
||||||
software: Software_Type_Enum.Auth,
|
software: Software_Type_Enum.Auth,
|
||||||
},
|
},
|
||||||
|
skip: !isPlatform,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { version } = data?.config?.auth || {};
|
const { version } = data?.config?.auth || {};
|
||||||
@@ -61,10 +69,21 @@ export default function AuthServiceVersionSettings() {
|
|||||||
|
|
||||||
const form = useForm<AuthServiceVersionFormValues>({
|
const form = useForm<AuthServiceVersionFormValues>({
|
||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
defaultValues: { version: { label: version, value: version } },
|
defaultValues: { version: { label: '', value: '' } },
|
||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && version) {
|
||||||
|
form.reset({
|
||||||
|
version: {
|
||||||
|
label: version,
|
||||||
|
value: version,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, version, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -95,23 +114,29 @@ export default function AuthServiceVersionSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Auth version is being updated...`,
|
|
||||||
success: `Auth version has been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update Auth version.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Auth version is being updated...',
|
||||||
|
successMessage: 'Auth version has been updated successfully.',
|
||||||
|
errorMessage: 'An error occurred while trying to update Auth version.',
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -128,12 +153,20 @@ export default function AuthServiceVersionSettings() {
|
|||||||
}}
|
}}
|
||||||
docsLink="https://github.com/nhost/hasura-auth/releases"
|
docsLink="https://github.com/nhost/hasura-auth/releases"
|
||||||
docsTitle="the latest releases"
|
docsTitle="the latest releases"
|
||||||
className="grid grid-flow-row gap-y-2 gap-x-4 px-4 lg:grid-cols-5"
|
className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
|
||||||
>
|
>
|
||||||
<ControlledAutocomplete
|
<ControlledAutocomplete
|
||||||
id="version"
|
id="version"
|
||||||
name="version"
|
name="version"
|
||||||
autoHighlight
|
autoHighlight
|
||||||
|
freeSolo
|
||||||
|
getOptionLabel={(option) => {
|
||||||
|
if (typeof option === 'string') {
|
||||||
|
return option || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return option.value;
|
||||||
|
}}
|
||||||
isOptionEqualToValue={() => false}
|
isOptionEqualToValue={() => false}
|
||||||
filterOptions={(options, { inputValue }) => {
|
filterOptions={(options, { inputValue }) => {
|
||||||
const inputValueLower = inputValue.toLowerCase();
|
const inputValueLower = inputValue.toLowerCase();
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
@@ -8,18 +10,19 @@ import { Input } from '@/components/ui/v2/Input';
|
|||||||
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
|
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
|
||||||
import { BaseProviderSettings } from '@/features/authentication/settings/components/BaseProviderSettings';
|
import { BaseProviderSettings } from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
@@ -48,15 +51,19 @@ const validationSchema = Yup.object({
|
|||||||
export type AzureADProviderFormValues = Yup.InferType<typeof validationSchema>;
|
export type AzureADProviderFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function AzureADProviderSettings() {
|
export default function AzureADProviderSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetSignInMethodsDocument],
|
refetchQueries: [GetSignInMethodsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { clientId, clientSecret, tenant, enabled } =
|
const { clientId, clientSecret, tenant, enabled } =
|
||||||
@@ -73,6 +80,17 @@ export default function AzureADProviderSettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
clientId: clientId || '',
|
||||||
|
clientSecret: clientSecret || '',
|
||||||
|
tenant: tenant || '',
|
||||||
|
enabled: enabled || false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, clientId, clientSecret, tenant, enabled, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -106,23 +124,30 @@ export default function AzureADProviderSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Azure AD settings are being updated...`,
|
|
||||||
success: `Azure AD settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's Azure AD settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Azure AD settings are being updated...',
|
||||||
|
successMessage: 'Azure AD settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
"An error occurred while trying to update the project's Azure AD settings.",
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -141,7 +166,7 @@ export default function AzureADProviderSettings() {
|
|||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'grid grid-flow-row grid-cols-2 gap-y-4 gap-x-3 px-4 py-2',
|
'grid grid-flow-row grid-cols-2 gap-x-3 gap-y-4 px-4 py-2',
|
||||||
!authEnabled && 'hidden',
|
!authEnabled && 'hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -189,7 +214,7 @@ export default function AzureADProviderSettings() {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CopyIcon className="h-4 w-4" />
|
<CopyIcon className="w-4 h-4" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,10 @@ import {
|
|||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export default function BitbucketProviderSettings() {
|
export default function BitbucketProviderSettings() {
|
||||||
@@ -84,23 +82,18 @@ export default function BitbucketProviderSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Bitbucket settings are being updated...`,
|
},
|
||||||
success: `Bitbucket settings have been updated successfully.`,
|
{
|
||||||
error: getServerError(
|
loadingMessage: 'Bitbucket settings are being updated...',
|
||||||
`An error occurred while trying to update the project's Bitbucket settings.`,
|
successMessage: 'Bitbucket settings have been updated successfully.',
|
||||||
),
|
errorMessage:
|
||||||
},
|
"An error occurred while trying to update the project's Bitbucket settings.",
|
||||||
getToastStyleProps(),
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
form.reset(formValues);
|
|
||||||
} catch {
|
|
||||||
// Note: The toast will handle the error.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -119,7 +112,7 @@ export default function BitbucketProviderSettings() {
|
|||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-y-4 gap-x-3 px-4 py-2',
|
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-x-3 gap-y-4 px-4 py-2',
|
||||||
!authEnabled && 'hidden',
|
!authEnabled && 'hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetAuthenticationSettingsDocument,
|
GetAuthenticationSettingsDocument,
|
||||||
useGetAuthenticationSettingsQuery,
|
useGetAuthenticationSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
@@ -26,15 +29,19 @@ const validationSchema = Yup.object({
|
|||||||
export type BlockedEmailFormValues = Yup.InferType<typeof validationSchema>;
|
export type BlockedEmailFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function BlockedEmailSettings() {
|
export default function BlockedEmailSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetAuthenticationSettingsDocument],
|
refetchQueries: [GetAuthenticationSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { email, emailDomains } = data?.config?.auth?.user || {};
|
const { email, emailDomains } = data?.config?.auth?.user || {};
|
||||||
@@ -53,6 +60,17 @@ export default function BlockedEmailSettings() {
|
|||||||
const enabled = watch('enabled');
|
const enabled = watch('enabled');
|
||||||
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && email && emailDomains) {
|
||||||
|
form.reset({
|
||||||
|
enabled:
|
||||||
|
email?.blocked?.length > 0 || emailDomains?.blocked?.length > 0,
|
||||||
|
blockedEmails: email?.blocked?.join(', ') || '',
|
||||||
|
blockedEmailDomains: emailDomains?.blocked?.join(', ') || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, email, emailDomains, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -110,23 +128,32 @@ export default function BlockedEmailSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(values);
|
||||||
loading: `Blocked email and domain settings are being updated...`,
|
|
||||||
success: `Blocked email and domain settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's blocked email and domain settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(values);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage:
|
||||||
|
'Blocked email and domain settings are being updated...',
|
||||||
|
successMessage:
|
||||||
|
'Blocked email and domain settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
"An error occurred while trying to update the project's blocked email and domain settings.",
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -141,7 +168,7 @@ export default function BlockedEmailSettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/authentication#blocked-emails-and-domains"
|
docsLink="https://docs.nhost.io/guides/auth/overview#allowed-emails-and-domains"
|
||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetAuthenticationSettingsDocument,
|
GetAuthenticationSettingsDocument,
|
||||||
useGetAuthenticationSettingsQuery,
|
useGetAuthenticationSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -23,15 +26,19 @@ const validationSchema = Yup.object({
|
|||||||
export type ClientURLFormValues = Yup.InferType<typeof validationSchema>;
|
export type ClientURLFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function ClientURLSettings() {
|
export default function ClientURLSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetAuthenticationSettingsDocument],
|
refetchQueries: [GetAuthenticationSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { clientUrl, allowedUrls } = data?.config?.auth?.redirections || {};
|
const { clientUrl, allowedUrls } = data?.config?.auth?.redirections || {};
|
||||||
@@ -44,6 +51,12 @@ export default function ClientURLSettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && clientUrl) {
|
||||||
|
form.reset({ clientUrl });
|
||||||
|
}
|
||||||
|
}, [loading, clientUrl, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -75,23 +88,30 @@ export default function ClientURLSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(values);
|
||||||
loading: `Client URL is being updated...`,
|
|
||||||
success: `Client URL has been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's Client URL.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(values);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Client URL is being updated...',
|
||||||
|
successMessage: 'Client URL has been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
"An error occurred while trying to update the project's Client URL.",
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -106,7 +126,7 @@ export default function ClientURLSettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/authentication#client-url"
|
docsLink="https://docs.nhost.io/guides/auth/overview#client-url"
|
||||||
className="grid grid-flow-row lg:grid-cols-5"
|
className="grid grid-flow-row lg:grid-cols-5"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
|
import {
|
||||||
|
GetSmtpSettingsDocument,
|
||||||
|
useUpdateConfigMutation,
|
||||||
|
} from '@/utils/__generated__/graphql';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
function ConfirmDeleteSMTPSettingsModal({
|
||||||
|
close,
|
||||||
|
onDelete,
|
||||||
|
}: {
|
||||||
|
onDelete?: () => Promise<any>;
|
||||||
|
close: () => void;
|
||||||
|
}) {
|
||||||
|
const onClickDelete = async () => {
|
||||||
|
await onDelete();
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className={twMerge('w-full rounded-lg p-6 text-left')}>
|
||||||
|
<div className="grid grid-flow-row gap-4">
|
||||||
|
<Text variant="h3" component="h2">
|
||||||
|
Delete SMTP Settings?
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text>This will reset all your SMTP and Postmark settings.</Text>
|
||||||
|
|
||||||
|
<div className="grid grid-flow-row gap-2">
|
||||||
|
<Button color="error" onClick={onClickDelete}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button variant="outlined" color="secondary" onClick={close}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DeleteSMTPSettings() {
|
||||||
|
const { openDialog, closeDialog } = useDialog();
|
||||||
|
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
|
const { maintenanceActive } = useUI();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
|
|
||||||
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
|
refetchQueries: [GetSmtpSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteSMTPSettings = async () => {
|
||||||
|
const updateConfigPromise = updateConfig({
|
||||||
|
variables: {
|
||||||
|
appId: currentProject.id,
|
||||||
|
config: {
|
||||||
|
provider: {
|
||||||
|
smtp: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
await execPromiseWithErrorToast(
|
||||||
|
async () => {
|
||||||
|
await updateConfigPromise;
|
||||||
|
|
||||||
|
if (!isPlatform) {
|
||||||
|
openDialog({
|
||||||
|
title: 'Apply your changes',
|
||||||
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'SMTP settings are being deleted...',
|
||||||
|
successMessage: 'SMTP settings have been deleted successfully.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while trying to delete the SMTP settings.',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmDeleteSMTPSettings = async () => {
|
||||||
|
openDialog({
|
||||||
|
component: (
|
||||||
|
<ConfirmDeleteSMTPSettingsModal
|
||||||
|
close={closeDialog}
|
||||||
|
onDelete={deleteSMTPSettings}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsContainer
|
||||||
|
title="Delete SMTP Settings"
|
||||||
|
description="Delete SMTP settings and revert to default values"
|
||||||
|
className="px-0"
|
||||||
|
slotProps={{
|
||||||
|
submitButton: { className: 'hidden' },
|
||||||
|
footer: { className: 'hidden' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box className="grid grid-flow-row border-t-1">
|
||||||
|
<Button
|
||||||
|
color="error"
|
||||||
|
className="mx-4 mt-4 justify-self-end"
|
||||||
|
onClick={confirmDeleteSMTPSettings}
|
||||||
|
disabled={loading || maintenanceActive}
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</SettingsContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as DeleteSTMPSettings } from './DeleteSMTPSettings';
|
||||||
@@ -1,17 +1,20 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import {
|
import {
|
||||||
GetAuthenticationSettingsDocument,
|
GetAuthenticationSettingsDocument,
|
||||||
useGetAuthenticationSettingsQuery,
|
useGetAuthenticationSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/utils/__generated__/graphql';
|
} from '@/utils/__generated__/graphql';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -21,15 +24,19 @@ const validationSchema = Yup.object({
|
|||||||
export type DisableNewUsersFormValues = Yup.InferType<typeof validationSchema>;
|
export type DisableNewUsersFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function DisableNewUsersSettings() {
|
export default function DisableNewUsersSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetAuthenticationSettingsDocument],
|
refetchQueries: [GetAuthenticationSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const form = useForm<DisableNewUsersFormValues>({
|
const form = useForm<DisableNewUsersFormValues>({
|
||||||
@@ -39,6 +46,14 @@ export default function DisableNewUsersSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
disabled: !data?.config?.auth?.signUp?.enabled,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, data, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -71,23 +86,30 @@ export default function DisableNewUsersSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(values);
|
||||||
loading: `Disabling new user sign ups...`,
|
|
||||||
success: `New user sign ups have been disabled successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to disable new user sign ups.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(values);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Disabling new user sign ups...',
|
||||||
|
successMessage: 'New user sign ups have been disabled successfully.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while trying to disable new user sign ups.',
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -96,7 +118,7 @@ export default function DisableNewUsersSettings() {
|
|||||||
<SettingsContainer
|
<SettingsContainer
|
||||||
title="Disable New Users"
|
title="Disable New Users"
|
||||||
description="If set, newly registered users are disabled and won't be able to sign in."
|
description="If set, newly registered users are disabled and won't be able to sign in."
|
||||||
docsLink="https://docs.nhost.io/authentication#disable-new-users"
|
docsLink="https://docs.nhost.io/guides/auth/overview#disable-new-users"
|
||||||
switchId="disabled"
|
switchId="disabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
slotProps={{
|
slotProps={{
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
@@ -12,30 +14,35 @@ import {
|
|||||||
baseProviderValidationSchema,
|
baseProviderValidationSchema,
|
||||||
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export default function DiscordProviderSettings() {
|
export default function DiscordProviderSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetSignInMethodsDocument],
|
refetchQueries: [GetSignInMethodsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { clientId, clientSecret, enabled } =
|
const { clientId, clientSecret, enabled } =
|
||||||
@@ -51,6 +58,16 @@ export default function DiscordProviderSettings() {
|
|||||||
resolver: yupResolver(baseProviderValidationSchema),
|
resolver: yupResolver(baseProviderValidationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
clientId: clientId || '',
|
||||||
|
clientSecret: clientSecret || '',
|
||||||
|
enabled: enabled || false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, clientId, clientSecret, enabled, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -87,23 +104,30 @@ export default function DiscordProviderSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Discord settings are being updated...`,
|
|
||||||
success: `Discord settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's Discrod settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Discord settings are being updated...',
|
||||||
|
successMessage: 'Discord settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
"An error occurred while trying to update the project's Discrod settings.",
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -118,13 +142,13 @@ export default function DiscordProviderSettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/platform/authentication/sign-in-with-discord"
|
docsLink="https://docs.nhost.io/guides/auth/social/sign-in-discord"
|
||||||
docsTitle="how to sign in users with Discord"
|
docsTitle="how to sign in users with Discord"
|
||||||
icon="/assets/brands/discord.svg"
|
icon="/assets/brands/discord.svg"
|
||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-y-4 gap-x-3 px-4 py-2',
|
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-x-3 gap-y-4 px-4 py-2',
|
||||||
!authEnabled && 'hidden',
|
!authEnabled && 'hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -160,7 +184,7 @@ export default function DiscordProviderSettings() {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CopyIcon className="h-4 w-4" />
|
<CopyIcon className="w-4 h-4" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { ControlledCheckbox } from '@/components/form/ControlledCheckbox';
|
import { ControlledCheckbox } from '@/components/form/ControlledCheckbox';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
@@ -6,16 +8,17 @@ import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
|||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -31,15 +34,19 @@ const validationSchema = Yup.object({
|
|||||||
export type EmailAndPasswordFormValues = Yup.InferType<typeof validationSchema>;
|
export type EmailAndPasswordFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function EmailAndPasswordSettings() {
|
export default function EmailAndPasswordSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetSignInMethodsDocument],
|
refetchQueries: [GetSignInMethodsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, error, loading } = useGetSignInMethodsQuery({
|
const { data, error, loading } = useGetSignInMethodsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { hibpEnabled, emailVerificationRequired, passwordMinLength } =
|
const { hibpEnabled, emailVerificationRequired, passwordMinLength } =
|
||||||
@@ -55,6 +62,22 @@ export default function EmailAndPasswordSettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
hibpEnabled,
|
||||||
|
emailVerificationRequired,
|
||||||
|
passwordMinLength,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
loading,
|
||||||
|
hibpEnabled,
|
||||||
|
emailVerificationRequired,
|
||||||
|
passwordMinLength,
|
||||||
|
form,
|
||||||
|
]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -85,23 +108,31 @@ export default function EmailAndPasswordSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Email and password sign-in settings are being updated...`,
|
|
||||||
success: `Email and password sign-in settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update email sign-in settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: `Email and password sign-in settings are being updated...`,
|
||||||
|
successMessage:
|
||||||
|
'Email and password sign-in settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while trying to update email sign-in settings.',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -110,7 +141,7 @@ export default function EmailAndPasswordSettings() {
|
|||||||
<SettingsContainer
|
<SettingsContainer
|
||||||
title="Email and Password"
|
title="Email and Password"
|
||||||
description="Allow users to sign in with 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"
|
docsLink="https://docs.nhost.io/guides/auth/sign-in-email-password"
|
||||||
docsTitle="how to sign in users with email and password"
|
docsTitle="how to sign in users with email and password"
|
||||||
className="grid grid-flow-row"
|
className="grid grid-flow-row"
|
||||||
showSwitch
|
showSwitch
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
@@ -12,30 +14,35 @@ import {
|
|||||||
baseProviderValidationSchema,
|
baseProviderValidationSchema,
|
||||||
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export default function FacebookProviderSettings() {
|
export default function FacebookProviderSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetSignInMethodsDocument],
|
refetchQueries: [GetSignInMethodsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { clientId, clientSecret, enabled } =
|
const { clientId, clientSecret, enabled } =
|
||||||
@@ -51,6 +58,16 @@ export default function FacebookProviderSettings() {
|
|||||||
resolver: yupResolver(baseProviderValidationSchema),
|
resolver: yupResolver(baseProviderValidationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
clientId: clientId || '',
|
||||||
|
clientSecret: clientSecret || '',
|
||||||
|
enabled: enabled || false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, clientId, clientSecret, enabled, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -87,23 +104,30 @@ export default function FacebookProviderSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Facebook settings are being updated...`,
|
|
||||||
success: `Facebook settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's Facebook settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Facebook settings are being updated...',
|
||||||
|
successMessage: 'Facebook settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
"An error occurred while trying to update the project's Facebook settings.",
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -118,13 +142,13 @@ export default function FacebookProviderSettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/platform/authentication/sign-in-with-facebook"
|
docsLink="https://docs.nhost.io/guides/auth/social/sign-in-facebook"
|
||||||
docsTitle="how to sign in users with Facebook"
|
docsTitle="how to sign in users with Facebook"
|
||||||
icon="/assets/brands/facebook.svg"
|
icon="/assets/brands/facebook.svg"
|
||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-y-4 gap-x-3 px-4 py-2',
|
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-x-3 gap-y-4 px-4 py-2',
|
||||||
!authEnabled && 'hidden',
|
!authEnabled && 'hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -160,7 +184,7 @@ export default function FacebookProviderSettings() {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CopyIcon className="h-4 w-4" />
|
<CopyIcon className="w-4 h-4" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
@@ -12,32 +14,37 @@ import {
|
|||||||
baseProviderValidationSchema,
|
baseProviderValidationSchema,
|
||||||
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { useTheme } from '@mui/material';
|
import { useTheme } from '@mui/material';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export default function GitHubProviderSettings() {
|
export default function GitHubProviderSettings() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetSignInMethodsDocument],
|
refetchQueries: [GetSignInMethodsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { clientId, clientSecret, enabled } =
|
const { clientId, clientSecret, enabled } =
|
||||||
@@ -53,6 +60,16 @@ export default function GitHubProviderSettings() {
|
|||||||
resolver: yupResolver(baseProviderValidationSchema),
|
resolver: yupResolver(baseProviderValidationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
clientId: clientId || '',
|
||||||
|
clientSecret: clientSecret || '',
|
||||||
|
enabled: enabled || false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, clientId, clientSecret, enabled, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -89,23 +106,30 @@ export default function GitHubProviderSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `GitHub settings are being updated...`,
|
|
||||||
success: `GitHub settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's GitHub settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'GitHub settings are being updated...',
|
||||||
|
successMessage: 'GitHub settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
"An error occurred while trying to update the project's GitHub settings.",
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -120,7 +144,7 @@ export default function GitHubProviderSettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/platform/authentication/sign-in-with-github"
|
docsLink="https://docs.nhost.io/guides/auth/social/sign-in-github"
|
||||||
docsTitle="how to sign in users with GitHub"
|
docsTitle="how to sign in users with GitHub"
|
||||||
icon={
|
icon={
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
@@ -130,7 +154,7 @@ export default function GitHubProviderSettings() {
|
|||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-y-4 gap-x-3 px-4 py-2',
|
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-x-3 gap-y-4 px-4 py-2',
|
||||||
!authEnabled && 'hidden',
|
!authEnabled && 'hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -166,7 +190,7 @@ export default function GitHubProviderSettings() {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CopyIcon className="h-4 w-4" />
|
<CopyIcon className="w-4 h-4" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,10 @@ import {
|
|||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export default function GitLabProviderSettings() {
|
export default function GitLabProviderSettings() {
|
||||||
@@ -87,23 +85,18 @@ export default function GitLabProviderSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(values);
|
||||||
loading: `GitLab settings are being updated...`,
|
},
|
||||||
success: `GitLab settings have been updated successfully.`,
|
{
|
||||||
error: getServerError(
|
loadingMessage: 'GitLab settings are being updated...',
|
||||||
`An error occurred while trying to update the project's GitLab settings.`,
|
successMessage: 'GitLab settings have been updated successfully.',
|
||||||
),
|
errorMessage:
|
||||||
},
|
"An error occurred while trying to update the project's GitLab settings.",
|
||||||
getToastStyleProps(),
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
form.reset(values);
|
|
||||||
} catch {
|
|
||||||
// Note: The toast will handle the error.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -122,7 +115,7 @@ export default function GitLabProviderSettings() {
|
|||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-y-4 gap-x-3 px-4 py-2',
|
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-x-3 gap-y-4 px-4 py-2',
|
||||||
!authEnabled && 'hidden',
|
!authEnabled && 'hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
@@ -12,30 +14,35 @@ import {
|
|||||||
baseProviderValidationSchema,
|
baseProviderValidationSchema,
|
||||||
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export default function GoogleProviderSettings() {
|
export default function GoogleProviderSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetSignInMethodsDocument],
|
refetchQueries: [GetSignInMethodsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { clientId, clientSecret, enabled } =
|
const { clientId, clientSecret, enabled } =
|
||||||
@@ -51,6 +58,16 @@ export default function GoogleProviderSettings() {
|
|||||||
resolver: yupResolver(baseProviderValidationSchema),
|
resolver: yupResolver(baseProviderValidationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
clientId: clientId || '',
|
||||||
|
clientSecret: clientSecret || '',
|
||||||
|
enabled: enabled || false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, clientId, clientSecret, enabled, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -87,23 +104,30 @@ export default function GoogleProviderSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Google settings are being updated...`,
|
|
||||||
success: `Google settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's Google settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Google settings are being updated...',
|
||||||
|
successMessage: 'Google settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
"An error occurred while trying to update the project's Google settings.",
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -118,13 +142,13 @@ export default function GoogleProviderSettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/platform/authentication/sign-in-with-google"
|
docsLink="https://docs.nhost.io/guides/auth/social/sign-in-google"
|
||||||
docsTitle="how to sign in users with Google"
|
docsTitle="how to sign in users with Google"
|
||||||
icon="/assets/brands/google.svg"
|
icon="/assets/brands/google.svg"
|
||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-y-4 gap-x-3 px-4 py-2',
|
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-x-3 gap-y-4 px-4 py-2',
|
||||||
!authEnabled && 'hidden',
|
!authEnabled && 'hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -160,7 +184,7 @@ export default function GoogleProviderSettings() {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CopyIcon className="h-4 w-4" />
|
<CopyIcon className="w-4 h-4" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { ControlledSelect } from '@/components/form/ControlledSelect';
|
import { ControlledSelect } from '@/components/form/ControlledSelect';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
@@ -5,20 +7,21 @@ import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
|||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { Option } from '@/components/ui/v2/Option';
|
import { Option } from '@/components/ui/v2/Option';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetAuthenticationSettingsDocument,
|
GetAuthenticationSettingsDocument,
|
||||||
useGetAuthenticationSettingsQuery,
|
useGetAuthenticationSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import {
|
import {
|
||||||
AUTH_GRAVATAR_DEFAULT,
|
AUTH_GRAVATAR_DEFAULT,
|
||||||
AUTH_GRAVATAR_RATING,
|
AUTH_GRAVATAR_RATING,
|
||||||
getToastStyleProps,
|
|
||||||
} from '@/utils/constants/settings';
|
} from '@/utils/constants/settings';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
@@ -31,15 +34,19 @@ const validationSchema = Yup.object({
|
|||||||
export type GravatarFormValues = Yup.InferType<typeof validationSchema>;
|
export type GravatarFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function GravatarSettings() {
|
export default function GravatarSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetAuthenticationSettingsDocument],
|
refetchQueries: [GetAuthenticationSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -58,6 +65,16 @@ export default function GravatarSettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
default: defaultGravatar || '',
|
||||||
|
rating: rating || '',
|
||||||
|
enabled: enabled || false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, defaultGravatar, rating, enabled, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -89,23 +106,30 @@ export default function GravatarSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(values);
|
||||||
loading: `Gravatar settings are being updated...`,
|
|
||||||
success: `Gravatar settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's Gravatar settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(values);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Gravatar settings are being updated...',
|
||||||
|
successMessage: 'Gravatar settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
"An error occurred while trying to update the project's Gravatar settings.",
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -120,7 +144,7 @@ export default function GravatarSettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/authentication#gravatar"
|
docsLink="https://docs.nhost.io/guides/auth/overview#gravatar"
|
||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
@@ -12,30 +14,35 @@ import {
|
|||||||
baseProviderValidationSchema,
|
baseProviderValidationSchema,
|
||||||
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export default function LinkedInProviderSettings() {
|
export default function LinkedInProviderSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetSignInMethodsDocument],
|
refetchQueries: [GetSignInMethodsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { clientId, clientSecret, enabled } =
|
const { clientId, clientSecret, enabled } =
|
||||||
@@ -51,6 +58,16 @@ export default function LinkedInProviderSettings() {
|
|||||||
resolver: yupResolver(baseProviderValidationSchema),
|
resolver: yupResolver(baseProviderValidationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
clientId: clientId || '',
|
||||||
|
clientSecret: clientSecret || '',
|
||||||
|
enabled: enabled || false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, clientId, clientSecret, enabled, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -87,23 +104,30 @@ export default function LinkedInProviderSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `LinkedIn settings are being updated...`,
|
|
||||||
success: `LinkedIn settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's LinkedIn settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'LinkedIn settings are being updated...',
|
||||||
|
successMessage: 'LinkedIn settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
"An error occurred while trying to update the project's LinkedIn settings.",
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -118,13 +142,13 @@ export default function LinkedInProviderSettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/platform/authentication/sign-in-with-linkedin"
|
docsLink="https://docs.nhost.io/guides/auth/social/sign-in-linkedin"
|
||||||
docsTitle="how to sign in users with LinkedIn"
|
docsTitle="how to sign in users with LinkedIn"
|
||||||
icon="/assets/brands/linkedin.svg"
|
icon="/assets/brands/linkedin.svg"
|
||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-y-4 gap-x-3 px-4 py-2',
|
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-x-3 gap-y-4 px-4 py-2',
|
||||||
!authEnabled && 'hidden',
|
!authEnabled && 'hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -160,7 +184,7 @@ export default function LinkedInProviderSettings() {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CopyIcon className="h-4 w-4" />
|
<CopyIcon className="w-4 h-4" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetAuthenticationSettingsDocument,
|
GetAuthenticationSettingsDocument,
|
||||||
useGetAuthenticationSettingsQuery,
|
useGetAuthenticationSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
@@ -25,15 +28,19 @@ const validationSchema = Yup.object({
|
|||||||
export type MFASettingsFormValues = Yup.InferType<typeof validationSchema>;
|
export type MFASettingsFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function MFASettings() {
|
export default function MFASettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetAuthenticationSettingsDocument],
|
refetchQueries: [GetAuthenticationSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { enabled, issuer } = data?.config?.auth?.totp || {};
|
const { enabled, issuer } = data?.config?.auth?.totp || {};
|
||||||
@@ -47,6 +54,15 @@ export default function MFASettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && issuer && enabled) {
|
||||||
|
form.reset({
|
||||||
|
issuer,
|
||||||
|
enabled,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, issuer, enabled, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -76,23 +92,32 @@ export default function MFASettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(values);
|
||||||
loading: `Multi-factor authentication settings are being updated...`,
|
|
||||||
success: `Multi-factor authentication settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's multi-factor authentication settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(values);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage:
|
||||||
|
'Multi-factor authentication settings are being updated...',
|
||||||
|
successMessage:
|
||||||
|
'Multi-factor authentication settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
"An error occurred while trying to update the project's multi-factor authentication settings.",
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -107,7 +132,7 @@ export default function MFASettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/authentication#multi-factor-authentication"
|
docsLink="https://docs.nhost.io/guides/auth/overview#multi-factor-authentication"
|
||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -22,15 +25,19 @@ const validationSchema = Yup.object({
|
|||||||
export type MagicLinkFormValues = Yup.InferType<typeof validationSchema>;
|
export type MagicLinkFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function MagicLinkSettings() {
|
export default function MagicLinkSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetSignInMethodsDocument],
|
refetchQueries: [GetSignInMethodsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { enabled } = data?.config?.auth?.method?.emailPasswordless || {};
|
const { enabled } = data?.config?.auth?.method?.emailPasswordless || {};
|
||||||
@@ -43,6 +50,12 @@ export default function MagicLinkSettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({ enabled });
|
||||||
|
}
|
||||||
|
}, [loading, enabled, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -73,23 +86,30 @@ export default function MagicLinkSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(values);
|
||||||
loading: `Magic Link settings are being updated...`,
|
|
||||||
success: `Magic Link settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's Magic Link settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(values);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Magic Link settings are being updated...',
|
||||||
|
successMessage: 'Magic Link settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
"An error occurred while trying to update the project's Magic Link settings.",
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -104,7 +124,7 @@ export default function MagicLinkSettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/authentication/sign-in-with-magic-link"
|
docsLink="https://docs.nhost.io/guides/auth/sign-in-magic-link"
|
||||||
docsTitle="how to sign in users with Magic Link"
|
docsTitle="how to sign in users with Magic Link"
|
||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
|
import { Form } from '@/components/form/Form';
|
||||||
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
|
import {
|
||||||
|
GetSmtpSettingsDocument,
|
||||||
|
useGetSmtpSettingsQuery,
|
||||||
|
useUpdateConfigMutation,
|
||||||
|
} from '@/utils/__generated__/graphql';
|
||||||
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
import * as yup from 'yup';
|
||||||
|
|
||||||
|
const validationSchema = yup
|
||||||
|
.object({
|
||||||
|
sender: yup.string().label('SMTP Sender').email().required(),
|
||||||
|
password: yup.string().label('Password').required(),
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export type PostmarkFormValues = yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
|
export default function PostmarkSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
|
|
||||||
|
const { data } = useGetSmtpSettingsQuery({
|
||||||
|
variables: { appId: currentProject?.id },
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { sender, password } = data?.config?.provider?.smtp || {};
|
||||||
|
|
||||||
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
|
refetchQueries: [GetSmtpSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const form = useForm<PostmarkFormValues>({
|
||||||
|
reValidateMode: 'onSubmit',
|
||||||
|
resolver: yupResolver(validationSchema),
|
||||||
|
defaultValues: {
|
||||||
|
password: '',
|
||||||
|
sender: '',
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
password: password || '',
|
||||||
|
sender: sender || '',
|
||||||
|
},
|
||||||
|
mode: 'onSubmit',
|
||||||
|
criteriaMode: 'all',
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
formState: { errors, isDirty, isSubmitting },
|
||||||
|
} = form;
|
||||||
|
|
||||||
|
const handleEditPostmarkSettings = async (values: PostmarkFormValues) => {
|
||||||
|
const updateConfigPromise = updateConfig({
|
||||||
|
variables: {
|
||||||
|
appId: currentProject.id,
|
||||||
|
config: {
|
||||||
|
provider: {
|
||||||
|
smtp: { method: 'LOGIN', host: 'postmark', ...values },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await execPromiseWithErrorToast(
|
||||||
|
async () => {
|
||||||
|
await updateConfigPromise;
|
||||||
|
|
||||||
|
if (!isPlatform) {
|
||||||
|
openDialog({
|
||||||
|
title: 'Apply your changes',
|
||||||
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Postmark settings are being updated...',
|
||||||
|
successMessage: 'Postmark settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while trying to update your Postmark settings.',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<Form onSubmit={handleEditPostmarkSettings}>
|
||||||
|
<SettingsContainer
|
||||||
|
title="Postmark Settings"
|
||||||
|
description="Configure postmark's native integration to send emails from your email domain."
|
||||||
|
submitButtonText="Save"
|
||||||
|
className="grid grid-cols-9 gap-4"
|
||||||
|
slotProps={{
|
||||||
|
submitButton: {
|
||||||
|
disabled: !isDirty || maintenanceActive,
|
||||||
|
loading: isSubmitting,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...register('sender')}
|
||||||
|
id="sender"
|
||||||
|
name="sender"
|
||||||
|
label="From Email"
|
||||||
|
placeholder="noreply@nhost.app"
|
||||||
|
className="lg:col-span-4"
|
||||||
|
hideEmptyHelperText
|
||||||
|
fullWidth
|
||||||
|
error={Boolean(errors.sender)}
|
||||||
|
helperText={errors.sender?.message}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
{...register('password')}
|
||||||
|
id="password"
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
placeholder="Enter password"
|
||||||
|
className="lg:col-span-5"
|
||||||
|
hideEmptyHelperText
|
||||||
|
fullWidth
|
||||||
|
error={Boolean(errors.password)}
|
||||||
|
helperText={errors.password?.message}
|
||||||
|
/>
|
||||||
|
</SettingsContainer>
|
||||||
|
</Form>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './PostmarkSettings';
|
||||||
|
export { default as PostmarkSettings } from './PostmarkSettings';
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
@@ -7,17 +9,18 @@ import { Option } from '@/components/ui/v2/Option';
|
|||||||
import { Select } from '@/components/ui/v2/Select';
|
import { Select } from '@/components/ui/v2/Select';
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
@@ -46,15 +49,19 @@ const validationSchema = Yup.object({
|
|||||||
export type SMSSettingsFormValues = Yup.InferType<typeof validationSchema>;
|
export type SMSSettingsFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function SMSSettings() {
|
export default function SMSSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetSignInMethodsDocument],
|
refetchQueries: [GetSignInMethodsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, error, loading } = useGetSignInMethodsQuery({
|
const { data, error, loading } = useGetSignInMethodsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { accountSid, authToken, messagingServiceId } =
|
const { accountSid, authToken, messagingServiceId } =
|
||||||
@@ -72,6 +79,17 @@ export default function SMSSettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
accountSid: accountSid || '',
|
||||||
|
authToken: authToken || '',
|
||||||
|
messagingServiceId: messagingServiceId || '',
|
||||||
|
enabled: enabled || false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, accountSid, authToken, messagingServiceId, enabled, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -112,23 +130,29 @@ export default function SMSSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(values);
|
||||||
loading: `SMS settings are being updated...`,
|
|
||||||
success: `SMS settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update SMS settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(values);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'SMS settings are being updated...',
|
||||||
|
successMessage: 'SMS settings have been updated successfully.',
|
||||||
|
errorMessage: 'An error occurred while trying to update SMS settings.',
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -145,10 +169,10 @@ export default function SMSSettings() {
|
|||||||
}}
|
}}
|
||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
docsLink="https://docs.nhost.io/authentication/sign-in-with-phone-number-sms"
|
docsLink="https://docs.nhost.io/guides/auth/sign-in-phone-number"
|
||||||
docsTitle="how to sign in users with a phone number (SMS)"
|
docsTitle="how to sign in users with a phone number (SMS)"
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'grid grid-flow-col grid-cols-2 grid-rows-4 gap-y-4 gap-x-3 px-4 py-2',
|
'grid grid-flow-col grid-cols-2 grid-rows-4 gap-x-3 gap-y-4 px-4 py-2',
|
||||||
!authSmsPasswordlessEnabled && 'hidden',
|
!authSmsPasswordlessEnabled && 'hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -0,0 +1,238 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
|
import { ControlledCheckbox } from '@/components/form/ControlledCheckbox';
|
||||||
|
import { Form } from '@/components/form/Form';
|
||||||
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
|
import {
|
||||||
|
GetSmtpSettingsDocument,
|
||||||
|
useGetSmtpSettingsQuery,
|
||||||
|
useUpdateConfigMutation,
|
||||||
|
} from '@/utils/__generated__/graphql';
|
||||||
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
import { type Optional } from 'utility-types';
|
||||||
|
import * as yup from 'yup';
|
||||||
|
|
||||||
|
const smtpValidationSchema = yup
|
||||||
|
.object({
|
||||||
|
secure: yup.bool().label('SMTP Secure'),
|
||||||
|
host: yup
|
||||||
|
.string()
|
||||||
|
.label('SMTP Host')
|
||||||
|
.matches(
|
||||||
|
/((https?):\/\/)?(www\.)?[a-z0-9]+(\.[a-z]{2,}){1,3}(#[a-zA-Z0-9#]+?)*\/?(\?[a-zA-Z0-9-_]+=[a-zA-Z0-9-%]+&?)?$/,
|
||||||
|
'SMTP Host must be a valid URL',
|
||||||
|
)
|
||||||
|
.required(),
|
||||||
|
port: yup
|
||||||
|
.number()
|
||||||
|
.typeError('The SMTP port should contain only numbers.')
|
||||||
|
.required(),
|
||||||
|
user: yup.string().label('Username').required(),
|
||||||
|
password: yup.string().label('Password'),
|
||||||
|
method: yup.string().required(),
|
||||||
|
sender: yup.string().label('SMTP Sender').email().required(),
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export type SmtpFormValues = yup.InferType<typeof smtpValidationSchema>;
|
||||||
|
|
||||||
|
export default function SMTPSettings() {
|
||||||
|
const { maintenanceActive } = useUI();
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
|
|
||||||
|
const { data } = useGetSmtpSettingsQuery({
|
||||||
|
variables: { appId: currentProject?.id },
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { secure, host, port, user, method, sender, password } =
|
||||||
|
data?.config?.provider?.smtp || {};
|
||||||
|
|
||||||
|
const form = useForm<Optional<SmtpFormValues, 'password'>>({
|
||||||
|
reValidateMode: 'onSubmit',
|
||||||
|
resolver: yupResolver(smtpValidationSchema),
|
||||||
|
defaultValues: {
|
||||||
|
secure: false,
|
||||||
|
host: '',
|
||||||
|
port: undefined,
|
||||||
|
user: '',
|
||||||
|
password: '',
|
||||||
|
method: '',
|
||||||
|
sender: '',
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
secure: secure || false,
|
||||||
|
host: host || '',
|
||||||
|
port,
|
||||||
|
user: user || '',
|
||||||
|
password: password || '',
|
||||||
|
method: method || '',
|
||||||
|
sender: sender || '',
|
||||||
|
},
|
||||||
|
mode: 'onSubmit',
|
||||||
|
criteriaMode: 'all',
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
register: registerSmtp,
|
||||||
|
formState: { errors, isDirty, isSubmitting },
|
||||||
|
} = form;
|
||||||
|
|
||||||
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
|
refetchQueries: [GetSmtpSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleEditSMTPSettings = async (values: SmtpFormValues) => {
|
||||||
|
const { password: newPassword, ...valuesWithoutPassword } = values;
|
||||||
|
|
||||||
|
const updateConfigPromise = updateConfig({
|
||||||
|
variables: {
|
||||||
|
appId: currentProject.id,
|
||||||
|
config: {
|
||||||
|
provider: {
|
||||||
|
smtp: newPassword ? values : valuesWithoutPassword,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await execPromiseWithErrorToast(
|
||||||
|
async () => {
|
||||||
|
await updateConfigPromise;
|
||||||
|
|
||||||
|
if (!isPlatform) {
|
||||||
|
openDialog({
|
||||||
|
title: 'Apply your changes',
|
||||||
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'SMTP settings are being updated...',
|
||||||
|
successMessage: 'SMTP settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while trying to update the SMTP settings.',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<Form onSubmit={handleEditSMTPSettings}>
|
||||||
|
<SettingsContainer
|
||||||
|
title="SMTP Settings"
|
||||||
|
description="Configure your SMTP settings to send emails from your email domain."
|
||||||
|
submitButtonText="Save"
|
||||||
|
className="grid grid-cols-9 gap-4"
|
||||||
|
slotProps={{
|
||||||
|
submitButton: {
|
||||||
|
disabled: !isDirty || maintenanceActive,
|
||||||
|
loading: isSubmitting,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...registerSmtp('sender')}
|
||||||
|
id="sender"
|
||||||
|
name="sender"
|
||||||
|
label="From Email"
|
||||||
|
placeholder="noreply@nhost.app"
|
||||||
|
className="lg:col-span-4"
|
||||||
|
hideEmptyHelperText
|
||||||
|
fullWidth
|
||||||
|
error={Boolean(errors.sender)}
|
||||||
|
helperText={errors.sender?.message}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
{...registerSmtp('host')}
|
||||||
|
id="host"
|
||||||
|
name="host"
|
||||||
|
label="SMTP Host"
|
||||||
|
className="lg:col-span-4"
|
||||||
|
placeholder="e.g. smtp.sendgrid.net"
|
||||||
|
hideEmptyHelperText
|
||||||
|
fullWidth
|
||||||
|
error={Boolean(errors.host)}
|
||||||
|
helperText={errors.host?.message}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
{...registerSmtp('port')}
|
||||||
|
id="port"
|
||||||
|
name="port"
|
||||||
|
label="Port"
|
||||||
|
type="number"
|
||||||
|
placeholder="587"
|
||||||
|
className="lg:col-span-1"
|
||||||
|
hideEmptyHelperText
|
||||||
|
fullWidth
|
||||||
|
error={Boolean(errors.port)}
|
||||||
|
helperText={errors.port?.message}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
{...registerSmtp('user')}
|
||||||
|
id="user"
|
||||||
|
label="SMTP Username"
|
||||||
|
placeholder="SMTP Username"
|
||||||
|
className="lg:col-span-4"
|
||||||
|
hideEmptyHelperText
|
||||||
|
fullWidth
|
||||||
|
error={Boolean(errors.user)}
|
||||||
|
helperText={errors.user?.message}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
{...registerSmtp('password')}
|
||||||
|
id="password"
|
||||||
|
label="SMTP Password"
|
||||||
|
type="password"
|
||||||
|
placeholder="Enter SMTP password"
|
||||||
|
className="lg:col-span-5"
|
||||||
|
hideEmptyHelperText
|
||||||
|
fullWidth
|
||||||
|
error={Boolean(errors.password)}
|
||||||
|
helperText={errors.password?.message}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
{...registerSmtp('method')}
|
||||||
|
id="method"
|
||||||
|
name="method"
|
||||||
|
label="SMTP Auth Method"
|
||||||
|
placeholder="LOGIN"
|
||||||
|
hideEmptyHelperText
|
||||||
|
className="lg:col-span-4"
|
||||||
|
fullWidth
|
||||||
|
error={Boolean(errors.method)}
|
||||||
|
helperText={errors.method?.message}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ControlledCheckbox
|
||||||
|
name="secure"
|
||||||
|
id="secure"
|
||||||
|
label="Use SSL"
|
||||||
|
className="lg:col-span-9"
|
||||||
|
/>
|
||||||
|
</SettingsContainer>
|
||||||
|
</Form>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './SMTPSettings';
|
||||||
|
export { default as SMTPSettings } from './SMTPSettings';
|
||||||
@@ -1,19 +1,22 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetAuthenticationSettingsDocument,
|
GetAuthenticationSettingsDocument,
|
||||||
useGetAuthenticationSettingsQuery,
|
useGetAuthenticationSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -30,15 +33,19 @@ const validationSchema = Yup.object({
|
|||||||
export type SessionFormValues = Yup.InferType<typeof validationSchema>;
|
export type SessionFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function SessionSettings() {
|
export default function SessionSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetAuthenticationSettingsDocument],
|
refetchQueries: [GetAuthenticationSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { accessToken, refreshToken } = data?.config?.auth?.session || {};
|
const { accessToken, refreshToken } = data?.config?.auth?.session || {};
|
||||||
@@ -52,6 +59,15 @@ export default function SessionSettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && accessToken && refreshToken) {
|
||||||
|
form.reset({
|
||||||
|
accessTokenExpiresIn: accessToken?.expiresIn || 900,
|
||||||
|
refreshTokenExpiresIn: refreshToken?.expiresIn || 43200,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, accessToken, refreshToken, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -84,23 +100,30 @@ export default function SessionSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Session settings are being updated...`,
|
|
||||||
success: `Session settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's session settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Session settings are being updated...',
|
||||||
|
successMessage: 'Session settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
"An error occurred while trying to update the project's session settings.",
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
@@ -12,30 +14,35 @@ import {
|
|||||||
baseProviderValidationSchema,
|
baseProviderValidationSchema,
|
||||||
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export default function SpotifyProviderSettings() {
|
export default function SpotifyProviderSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetSignInMethodsDocument],
|
refetchQueries: [GetSignInMethodsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { clientId, clientSecret, enabled } =
|
const { clientId, clientSecret, enabled } =
|
||||||
@@ -51,6 +58,16 @@ export default function SpotifyProviderSettings() {
|
|||||||
resolver: yupResolver(baseProviderValidationSchema),
|
resolver: yupResolver(baseProviderValidationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
clientId: clientId || '',
|
||||||
|
clientSecret: clientSecret || '',
|
||||||
|
enabled: enabled || false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, clientId, clientSecret, enabled, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -87,23 +104,30 @@ export default function SpotifyProviderSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Spotify settings are being updated...`,
|
|
||||||
success: `Spotify settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's Spotify settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Spotify settings are being updated...',
|
||||||
|
successMessage: 'Spotify settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
"An error occurred while trying to update the project's Spotify settings.",
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -118,13 +142,13 @@ export default function SpotifyProviderSettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/platform/authentication/sign-in-with-spotify"
|
docsLink="https://docs.nhost.io/guides/auth/social/sign-in-spotify"
|
||||||
docsTitle="how to sign in users with Spotify"
|
docsTitle="how to sign in users with Spotify"
|
||||||
icon="/assets/brands/spotify.svg"
|
icon="/assets/brands/spotify.svg"
|
||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-y-4 gap-x-3 px-4 py-2',
|
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-x-3 gap-y-4 px-4 py-2',
|
||||||
!authEnabled && 'hidden',
|
!authEnabled && 'hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -160,7 +184,7 @@ export default function SpotifyProviderSettings() {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CopyIcon className="h-4 w-4" />
|
<CopyIcon className="w-4 h-4" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,10 @@ import {
|
|||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export default function StravaProviderSettings() {
|
export default function StravaProviderSettings() {
|
||||||
@@ -87,23 +85,18 @@ export default function StravaProviderSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(values);
|
||||||
loading: `Strava settings are being updated...`,
|
},
|
||||||
success: `Strava settings have been updated successfully.`,
|
{
|
||||||
error: getServerError(
|
loadingMessage: 'Strava settings are being updated...',
|
||||||
`An error occurred while trying to update the project's Strava settings.`,
|
successMessage: 'Strava settings have been updated successfully.',
|
||||||
),
|
errorMessage:
|
||||||
},
|
"An error occurred while trying to update the project's Strava settings.",
|
||||||
getToastStyleProps(),
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
form.reset(values);
|
|
||||||
} catch {
|
|
||||||
// Note: The toast will handle the error.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -122,7 +115,7 @@ export default function StravaProviderSettings() {
|
|||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-y-4 gap-x-3 px-4 py-2',
|
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-x-3 gap-y-4 px-4 py-2',
|
||||||
!authEnabled && 'hidden',
|
!authEnabled && 'hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
@@ -12,32 +14,37 @@ import {
|
|||||||
baseProviderValidationSchema,
|
baseProviderValidationSchema,
|
||||||
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { useTheme } from '@mui/material';
|
import { useTheme } from '@mui/material';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export default function TwitchProviderSettings() {
|
export default function TwitchProviderSettings() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetSignInMethodsDocument],
|
refetchQueries: [GetSignInMethodsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { clientId, clientSecret, enabled } =
|
const { clientId, clientSecret, enabled } =
|
||||||
@@ -53,6 +60,16 @@ export default function TwitchProviderSettings() {
|
|||||||
resolver: yupResolver(baseProviderValidationSchema),
|
resolver: yupResolver(baseProviderValidationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
clientId: clientId || '',
|
||||||
|
clientSecret: clientSecret || '',
|
||||||
|
enabled: enabled || false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, clientId, clientSecret, enabled, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -89,23 +106,30 @@ export default function TwitchProviderSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Twitch settings are being updated...`,
|
|
||||||
success: `Twitch settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's Twitch settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Twitch settings are being updated...',
|
||||||
|
successMessage: 'Twitch settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
"An error occurred while trying to update the project's Twitch settings.",
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -120,7 +144,7 @@ export default function TwitchProviderSettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/platform/authentication/sign-in-with-twitch"
|
docsLink="https://docs.nhost.io/guides/auth/social/sign-in-twitch"
|
||||||
docsTitle="how to sign in users with Twitch"
|
docsTitle="how to sign in users with Twitch"
|
||||||
icon={
|
icon={
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
@@ -130,7 +154,7 @@ export default function TwitchProviderSettings() {
|
|||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-y-4 gap-x-3 px-4 py-2',
|
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-x-3 gap-y-4 px-4 py-2',
|
||||||
!authEnabled && 'hidden',
|
!authEnabled && 'hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -166,7 +190,7 @@ export default function TwitchProviderSettings() {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CopyIcon className="h-4 w-4" />
|
<CopyIcon className="w-4 h-4" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
@@ -7,18 +9,19 @@ import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
|
|||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
|
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
@@ -41,15 +44,19 @@ const validationSchema = Yup.object({
|
|||||||
export type TwitterProviderFormValues = Yup.InferType<typeof validationSchema>;
|
export type TwitterProviderFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function TwitterProviderSettings() {
|
export default function TwitterProviderSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetSignInMethodsDocument],
|
refetchQueries: [GetSignInMethodsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { consumerKey, consumerSecret, enabled } =
|
const { consumerKey, consumerSecret, enabled } =
|
||||||
@@ -65,6 +72,16 @@ export default function TwitterProviderSettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
consumerSecret: consumerSecret || '',
|
||||||
|
consumerKey: consumerKey || '',
|
||||||
|
enabled: enabled || false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, consumerKey, consumerSecret, enabled, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -98,23 +115,29 @@ export default function TwitterProviderSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Twitter settings are being updated...`,
|
|
||||||
success: `Twitter settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's Twitter settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Twitter settings are being updated...',
|
||||||
|
successMessage: 'Twitter settings have been updated successfully.',
|
||||||
|
errorMessage: `An error occurred while trying to update the project's Twitter settings.`,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -134,7 +157,7 @@ export default function TwitterProviderSettings() {
|
|||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-y-4 gap-x-3 px-4 py-2',
|
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-x-3 gap-y-4 px-4 py-2',
|
||||||
!authEnabled && 'hidden',
|
!authEnabled && 'hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -193,7 +216,7 @@ export default function TwitterProviderSettings() {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CopyIcon className="h-4 w-4" />
|
<CopyIcon className="w-4 h-4" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -22,15 +25,19 @@ const validationSchema = Yup.object({
|
|||||||
export type WebAuthnFormValues = Yup.InferType<typeof validationSchema>;
|
export type WebAuthnFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function WebAuthnSettings() {
|
export default function WebAuthnSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetSignInMethodsDocument],
|
refetchQueries: [GetSignInMethodsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { enabled } = data?.config?.auth?.method?.webauthn || {};
|
const { enabled } = data?.config?.auth?.method?.webauthn || {};
|
||||||
@@ -43,6 +50,12 @@ export default function WebAuthnSettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({ enabled });
|
||||||
|
}
|
||||||
|
}, [loading, enabled, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -66,33 +79,41 @@ export default function WebAuthnSettings() {
|
|||||||
config: {
|
config: {
|
||||||
auth: {
|
auth: {
|
||||||
method: {
|
method: {
|
||||||
webauthn: {...values,
|
webauthn: {
|
||||||
relyingParty: {
|
...values,
|
||||||
name: currentProject.name,
|
relyingParty: {
|
||||||
}},
|
name: currentProject.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(values);
|
||||||
loading: `WebAuthn settings are being updated...`,
|
|
||||||
success: `WebAuthn settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's WebAuthn settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(values);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'WebAuthn settings are being updated...',
|
||||||
|
successMessage: 'WebAuthn settings have been updated successfully.',
|
||||||
|
errorMessage: `An error occurred while trying to update the project's WebAuthn settings.`,
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -107,7 +128,7 @@ export default function WebAuthnSettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/authentication/sign-in-with-security-keys"
|
docsLink="https://docs.nhost.io/guides/auth/sign-in-webauthn"
|
||||||
docsTitle="how to sign in users with security keys"
|
docsTitle="how to sign in users with security keys"
|
||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
@@ -12,30 +14,35 @@ import {
|
|||||||
baseProviderValidationSchema,
|
baseProviderValidationSchema,
|
||||||
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export default function WindowsLiveProviderSettings() {
|
export default function WindowsLiveProviderSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetSignInMethodsDocument],
|
refetchQueries: [GetSignInMethodsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { clientId, clientSecret, enabled } =
|
const { clientId, clientSecret, enabled } =
|
||||||
@@ -51,6 +58,16 @@ export default function WindowsLiveProviderSettings() {
|
|||||||
resolver: yupResolver(baseProviderValidationSchema),
|
resolver: yupResolver(baseProviderValidationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
clientId: clientId || '',
|
||||||
|
clientSecret: clientSecret || '',
|
||||||
|
enabled: enabled || false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, clientId, clientSecret, enabled, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -87,23 +104,29 @@ export default function WindowsLiveProviderSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Windows Live settings are being updated...`,
|
|
||||||
success: `Windows Live settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's Windows Live settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Windows Live settings are being updated...',
|
||||||
|
successMessage: 'Windows Live settings have been updated successfully.',
|
||||||
|
errorMessage: `An error occurred while trying to update the project's Windows Live settings.`,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -123,7 +146,7 @@ export default function WindowsLiveProviderSettings() {
|
|||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-y-4 gap-x-3 px-4 py-2',
|
'grid-flow-rows grid grid-cols-2 grid-rows-2 gap-x-3 gap-y-4 px-4 py-2',
|
||||||
!authEnabled && 'hidden',
|
!authEnabled && 'hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -159,7 +182,7 @@ export default function WindowsLiveProviderSettings() {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CopyIcon className="h-4 w-4" />
|
<CopyIcon className="w-4 h-4" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
@@ -8,18 +10,19 @@ import { Input } from '@/components/ui/v2/Input';
|
|||||||
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
|
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
|
||||||
import { BaseProviderSettings } from '@/features/authentication/settings/components/BaseProviderSettings';
|
import { BaseProviderSettings } from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
@@ -54,15 +57,19 @@ const validationSchema = Yup.object({
|
|||||||
export type WorkOsProviderFormValues = Yup.InferType<typeof validationSchema>;
|
export type WorkOsProviderFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function WorkOsProviderSettings() {
|
export default function WorkOsProviderSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetSignInMethodsDocument],
|
refetchQueries: [GetSignInMethodsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { clientId, clientSecret, organization, connection, enabled } =
|
const { clientId, clientSecret, organization, connection, enabled } =
|
||||||
@@ -80,6 +87,26 @@ export default function WorkOsProviderSettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
clientId: clientId || '',
|
||||||
|
clientSecret: clientSecret || '',
|
||||||
|
organization: organization || '',
|
||||||
|
connection: connection || '',
|
||||||
|
enabled: enabled || false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
loading,
|
||||||
|
clientId,
|
||||||
|
clientSecret,
|
||||||
|
organization,
|
||||||
|
connection,
|
||||||
|
enabled,
|
||||||
|
form,
|
||||||
|
]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -113,23 +140,30 @@ export default function WorkOsProviderSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `WorkOS settings are being updated...`,
|
|
||||||
success: `WorkOS settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's WorkOS settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'WorkOS settings are being updated...',
|
||||||
|
successMessage: 'WorkOS settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
"An error occurred while trying to update the project's WorkOS settings.",
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -144,13 +178,13 @@ export default function WorkOsProviderSettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/authentication/sign-in-with-workos"
|
docsLink="https://docs.nhost.io/guides/auth/social/sign-in-workos"
|
||||||
docsTitle="how to sign in users with WorkOS"
|
docsTitle="how to sign in users with WorkOS"
|
||||||
icon="/assets/brands/workos.svg"
|
icon="/assets/brands/workos.svg"
|
||||||
switchId="enabled"
|
switchId="enabled"
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'grid grid-flow-row grid-cols-2 gap-y-4 gap-x-3 px-4 py-2',
|
'grid grid-flow-row grid-cols-2 gap-x-3 gap-y-4 px-4 py-2',
|
||||||
!authEnabled && 'hidden',
|
!authEnabled && 'hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -210,7 +244,7 @@ export default function WorkOsProviderSettings() {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CopyIcon className="h-4 w-4" />
|
<CopyIcon className="w-4 h-4" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,10 @@ import { Input } from '@/components/ui/v2/Input';
|
|||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||||
import type { DialogFormProps } from '@/types/common';
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
export interface CreateUserFormProps extends DialogFormProps {
|
export interface CreateUserFormProps extends DialogFormProps {
|
||||||
@@ -77,9 +75,9 @@ export default function CreateUserForm({
|
|||||||
async function handleCreateUser({ email, password }: CreateUserFormValues) {
|
async function handleCreateUser({ email, password }: CreateUserFormValues) {
|
||||||
setCreateUserFormError(null);
|
setCreateUserFormError(null);
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
fetch(signUpUrl, {
|
await fetch(signUpUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ email, password }),
|
body: JSON.stringify({ email, password }),
|
||||||
@@ -95,21 +93,16 @@ export default function CreateUserForm({
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(data?.message || 'Something went wrong.');
|
throw new Error(data?.message || 'Something went wrong.');
|
||||||
}),
|
});
|
||||||
{
|
|
||||||
loading: 'Creating user...',
|
|
||||||
success: 'User has been created successfully.',
|
|
||||||
error: getServerError(
|
|
||||||
'An error occurred while trying to create the user.',
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
onSubmit?.();
|
onSubmit?.();
|
||||||
} catch (error) {
|
},
|
||||||
// Note: The error is already handled by the toast promise.
|
{
|
||||||
}
|
loadingMessage: 'Creating user...',
|
||||||
|
successMessage: 'User has been created successfully.',
|
||||||
|
errorMessage: 'An error occurred while trying to create the user.',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -19,9 +19,8 @@ import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/
|
|||||||
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
|
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
|
||||||
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
||||||
import type { DialogFormProps } from '@/types/common';
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import {
|
import {
|
||||||
RemoteAppGetUsersDocument,
|
RemoteAppGetUsersDocument,
|
||||||
useGetProjectLocalesQuery,
|
useGetProjectLocalesQuery,
|
||||||
@@ -36,7 +35,6 @@ import Image from 'next/image';
|
|||||||
import type { RemoteAppUser } from 'pages/[workspaceSlug]/[appSlug]/users';
|
import type { RemoteAppUser } from 'pages/[workspaceSlug]/[appSlug]/users';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
export interface EditUserFormProps extends DialogFormProps {
|
export interface EditUserFormProps extends DialogFormProps {
|
||||||
@@ -173,21 +171,15 @@ export default function EditUserForm({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await toast.promise(
|
await execPromiseWithErrorToast(() => banUser, {
|
||||||
banUser,
|
loadingMessage: shouldBan ? 'Banning user...' : 'Unbanning user...',
|
||||||
{
|
successMessage: shouldBan
|
||||||
loading: shouldBan ? 'Banning user...' : 'Unbanning user...',
|
? 'User has been banned successfully.'
|
||||||
success: shouldBan
|
: 'User has been unbanned successfully.',
|
||||||
? 'User has been banned successfully.'
|
errorMessage: shouldBan
|
||||||
: 'User has been unbanned successfully.',
|
? 'An error occurred while trying to ban the user.'
|
||||||
error: getServerError(
|
: 'An error occurred while trying to unban the user.',
|
||||||
shouldBan
|
});
|
||||||
? 'An error occurred while trying to ban the user.'
|
|
||||||
: 'An error occurred while trying to unban the user.',
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -196,14 +188,14 @@ export default function EditUserForm({
|
|||||||
className="flex flex-col overflow-hidden border-t-1 lg:flex-auto lg:content-between"
|
className="flex flex-col overflow-hidden border-t-1 lg:flex-auto lg:content-between"
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
>
|
>
|
||||||
<Box className="flex-auto overflow-y-auto divide-y">
|
<Box className="flex-auto divide-y overflow-y-auto">
|
||||||
<Box
|
<Box
|
||||||
component="section"
|
component="section"
|
||||||
className="grid grid-flow-col p-6 lg:grid-cols-7"
|
className="grid grid-flow-col p-6 lg:grid-cols-7"
|
||||||
>
|
>
|
||||||
<div className="grid items-center grid-flow-col col-span-6 gap-4 place-content-start">
|
<div className="col-span-6 grid grid-flow-col place-content-start items-center gap-4">
|
||||||
<Avatar className="w-12 h-12" src={user.avatarUrl} />
|
<Avatar className="h-12 w-12" src={user.avatarUrl} />
|
||||||
<div className="grid items-center grid-flow-row">
|
<div className="grid grid-flow-row items-center">
|
||||||
<Text className="text-lg font-medium">{user.displayName}</Text>
|
<Text className="text-lg font-medium">{user.displayName}</Text>
|
||||||
<Text className="text-sm+ font-normal" color="secondary">
|
<Text className="text-sm+ font-normal" color="secondary">
|
||||||
{user.email}
|
{user.email}
|
||||||
@@ -225,7 +217,7 @@ export default function EditUserForm({
|
|||||||
Actions
|
Actions
|
||||||
</Button>
|
</Button>
|
||||||
</Dropdown.Trigger>
|
</Dropdown.Trigger>
|
||||||
<Dropdown.Content menu className="w-full h-full">
|
<Dropdown.Content menu className="h-full w-full">
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
className="font-medium"
|
className="font-medium"
|
||||||
sx={{ color: 'error.main' }}
|
sx={{ color: 'error.main' }}
|
||||||
@@ -253,11 +245,11 @@ export default function EditUserForm({
|
|||||||
component="section"
|
component="section"
|
||||||
className="grid grid-flow-row grid-cols-4 gap-8 p-6"
|
className="grid grid-flow-row grid-cols-4 gap-8 p-6"
|
||||||
>
|
>
|
||||||
<InputLabel as="h3" className="self-center col-span-1">
|
<InputLabel as="h3" className="col-span-1 self-center">
|
||||||
User ID
|
User ID
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<div className="grid items-center justify-start grid-flow-col col-span-3 gap-2">
|
<div className="col-span-3 grid grid-flow-col items-center justify-start gap-2">
|
||||||
<Text className="font-medium truncate">{user.id}</Text>
|
<Text className="truncate font-medium">{user.id}</Text>
|
||||||
<IconButton
|
<IconButton
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
@@ -267,18 +259,18 @@ export default function EditUserForm({
|
|||||||
copy(user.id, 'User ID');
|
copy(user.id, 'User ID');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CopyIcon className="w-4 h-4" />
|
<CopyIcon className="h-4 w-4" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<InputLabel as="h3" className="self-center col-span-1 ">
|
<InputLabel as="h3" className="col-span-1 self-center ">
|
||||||
Created At
|
Created At
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Text className="col-span-3 font-medium">
|
<Text className="col-span-3 font-medium">
|
||||||
{format(new Date(user.createdAt), 'yyyy-MM-dd HH:mm:ss')}
|
{format(new Date(user.createdAt), 'yyyy-MM-dd HH:mm:ss')}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<InputLabel as="h3" className="self-center col-span-1 ">
|
<InputLabel as="h3" className="col-span-1 self-center ">
|
||||||
Last Seen
|
Last Seen
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Text className="col-span-3 font-medium">
|
<Text className="col-span-3 font-medium">
|
||||||
@@ -336,14 +328,14 @@ export default function EditUserForm({
|
|||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="grid items-center grid-flow-col grid-cols-8 col-span-1 my-1">
|
<div className="col-span-1 my-1 grid grid-flow-col grid-cols-8 items-center">
|
||||||
<div className="col-span-2 ">
|
<div className="col-span-2 ">
|
||||||
<InputLabel as="h3">Password</InputLabel>
|
<InputLabel as="h3">Password</InputLabel>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
className="col-span-6 px-2 place-self-start"
|
className="col-span-6 place-self-start px-2"
|
||||||
onClick={handleChangeUserPassword}
|
onClick={handleChangeUserPassword}
|
||||||
>
|
>
|
||||||
Change
|
Change
|
||||||
@@ -392,12 +384,12 @@ export default function EditUserForm({
|
|||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
component="section"
|
component="section"
|
||||||
className="grid gap-4 p-6 place-content-start lg:grid-cols-4"
|
className="grid place-content-start gap-4 p-6 lg:grid-cols-4"
|
||||||
>
|
>
|
||||||
<div className="items-center self-center col-span-1 align-middle">
|
<div className="col-span-1 items-center self-center align-middle">
|
||||||
<InputLabel as="h3">OAuth Providers</InputLabel>
|
<InputLabel as="h3">OAuth Providers</InputLabel>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid w-full grid-flow-row col-span-3 gap-y-6">
|
<div className="col-span-3 grid w-full grid-flow-row gap-y-6">
|
||||||
{user.userProviders.length === 0 && (
|
{user.userProviders.length === 0 && (
|
||||||
<div className="grid grid-flow-col place-content-between gap-x-1">
|
<div className="grid grid-flow-col place-content-between gap-x-1">
|
||||||
<Text className="font-normal" color="disabled">
|
<Text className="font-normal" color="disabled">
|
||||||
@@ -408,10 +400,10 @@ export default function EditUserForm({
|
|||||||
|
|
||||||
{user.userProviders.map((provider) => (
|
{user.userProviders.map((provider) => (
|
||||||
<div
|
<div
|
||||||
className="grid grid-flow-col gap-3 place-content-between"
|
className="grid grid-flow-col place-content-between gap-3"
|
||||||
key={provider.id}
|
key={provider.id}
|
||||||
>
|
>
|
||||||
<div className="grid grid-flow-col gap-2 span-cols-1">
|
<div className="span-cols-1 grid grid-flow-col gap-2">
|
||||||
<Image
|
<Image
|
||||||
src={
|
src={
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
@@ -424,7 +416,7 @@ export default function EditUserForm({
|
|||||||
}
|
}
|
||||||
width={25}
|
width={25}
|
||||||
height={25}
|
height={25}
|
||||||
alt='Oauth provider logo'
|
alt="Oauth provider logo"
|
||||||
/>
|
/>
|
||||||
<Text className="font-medium capitalize">
|
<Text className="font-medium capitalize">
|
||||||
{getReadableProviderName(provider.providerId)}
|
{getReadableProviderName(provider.providerId)}
|
||||||
@@ -437,7 +429,7 @@ export default function EditUserForm({
|
|||||||
{!isAnonymous && (
|
{!isAnonymous && (
|
||||||
<Box
|
<Box
|
||||||
component="section"
|
component="section"
|
||||||
className="grid grid-flow-row p-6 gap-y-10"
|
className="grid grid-flow-row gap-y-10 p-6"
|
||||||
>
|
>
|
||||||
<ControlledSelect
|
<ControlledSelect
|
||||||
{...register('defaultRole')}
|
{...register('defaultRole')}
|
||||||
@@ -457,11 +449,11 @@ export default function EditUserForm({
|
|||||||
</Option>
|
</Option>
|
||||||
))}
|
))}
|
||||||
</ControlledSelect>
|
</ControlledSelect>
|
||||||
<div className="grid grid-flow-row gap-6 place-content-start lg:grid-flow-col lg:grid-cols-8">
|
<div className="grid grid-flow-row place-content-start gap-6 lg:grid-flow-col lg:grid-cols-8">
|
||||||
<InputLabel as="h3" className="col-span-2">
|
<InputLabel as="h3" className="col-span-2">
|
||||||
Allowed Roles
|
Allowed Roles
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<div className="grid grid-flow-row col-span-3 gap-6">
|
<div className="col-span-3 grid grid-flow-row gap-6">
|
||||||
{roles.map((role, i) => (
|
{roles.map((role, i) => (
|
||||||
<ControlledCheckbox
|
<ControlledCheckbox
|
||||||
id={`roles.${i}`}
|
id={`roles.${i}`}
|
||||||
@@ -477,7 +469,7 @@ export default function EditUserForm({
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box className="grid justify-between flex-shrink-0 w-full grid-flow-col gap-3 p-2 snap-end place-self-end border-t-1">
|
<Box className="grid w-full flex-shrink-0 snap-end grid-flow-col justify-between gap-3 place-self-end border-t-1 p-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ import { Input } from '@/components/ui/v2/Input';
|
|||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
||||||
import type { DialogFormProps } from '@/types/common';
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
|
||||||
import type { RemoteAppGetUsersQuery } from '@/utils/__generated__/graphql';
|
import type { RemoteAppGetUsersQuery } from '@/utils/__generated__/graphql';
|
||||||
import {
|
import {
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
@@ -17,7 +16,6 @@ import { yupResolver } from '@hookform/resolvers/yup';
|
|||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
export interface EditUserPasswordFormProps extends DialogFormProps {
|
export interface EditUserPasswordFormProps extends DialogFormProps {
|
||||||
@@ -90,23 +88,23 @@ export default function EditUserPasswordForm({
|
|||||||
client: remoteProjectGQLClient,
|
client: remoteProjectGQLClient,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateUserPasswordPromise,
|
await updateUserPasswordPromise;
|
||||||
{
|
},
|
||||||
loading: 'Updating user password...',
|
{
|
||||||
success: 'User password updated successfully.',
|
loadingMessage: 'Updating user password...',
|
||||||
error: getServerError('Failed to update user password.'),
|
successMessage: 'User password updated successfully.',
|
||||||
|
errorMessage: 'Failed to update user password.',
|
||||||
|
onError: (error) => {
|
||||||
|
setEditUserPasswordFormError(
|
||||||
|
new Error(error.message || 'Something went wrong.'),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
getToastStyleProps(),
|
},
|
||||||
);
|
);
|
||||||
} catch (error) {
|
|
||||||
setEditUserPasswordFormError(
|
closeDialog();
|
||||||
new Error(error.message || 'Something went wrong.'),
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
closeDialog();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ import { getReadableProviderName } from '@/features/authentication/users/utils/g
|
|||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
|
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
|
||||||
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
|
||||||
import {
|
import {
|
||||||
useDeleteRemoteAppUserRolesMutation,
|
useDeleteRemoteAppUserRolesMutation,
|
||||||
useGetRolesPermissionsQuery,
|
useGetRolesPermissionsQuery,
|
||||||
@@ -33,7 +32,6 @@ import dynamic from 'next/dynamic';
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import type { RemoteAppUser } from 'pages/[workspaceSlug]/[appSlug]/users';
|
import type { RemoteAppUser } from 'pages/[workspaceSlug]/[appSlug]/users';
|
||||||
import { Fragment, useMemo } from 'react';
|
import { Fragment, useMemo } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
|
|
||||||
const EditUserForm = dynamic(
|
const EditUserForm = dynamic(
|
||||||
() =>
|
() =>
|
||||||
@@ -153,20 +151,18 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await toast.promise(
|
await execPromiseWithErrorToast(
|
||||||
updateUserMutationPromise,
|
async () => {
|
||||||
{
|
await updateUserMutationPromise;
|
||||||
loading: `Updating user's settings...`,
|
},
|
||||||
success: 'User settings have been updated successfully.',
|
{
|
||||||
error: getServerError(
|
loadingMessage: `Updating user's settings...`,
|
||||||
`An error occurred while trying to update this user's settings.`,
|
successMessage: 'User settings have been updated successfully.',
|
||||||
),
|
errorMessage: `An error occurred while trying to update this user's settings.`,
|
||||||
},
|
},
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await onSubmit?.();
|
await onSubmit?.();
|
||||||
|
|
||||||
closeDrawer();
|
closeDrawer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,20 +177,20 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
|||||||
),
|
),
|
||||||
props: {
|
props: {
|
||||||
onPrimaryAction: async () => {
|
onPrimaryAction: async () => {
|
||||||
await toast.promise(
|
await execPromiseWithErrorToast(
|
||||||
deleteUser({
|
async () => {
|
||||||
variables: {
|
await deleteUser({
|
||||||
id: user.id,
|
variables: {
|
||||||
},
|
id: user.id,
|
||||||
}),
|
},
|
||||||
{
|
});
|
||||||
loading: 'Deleting user...',
|
},
|
||||||
success: 'User deleted successfully.',
|
{
|
||||||
error: getServerError(
|
loadingMessage: 'Deleting user...',
|
||||||
'An error occurred while trying to delete this user.',
|
successMessage: 'User deleted successfully.',
|
||||||
),
|
errorMessage:
|
||||||
|
'An error occurred while trying to delete this user.',
|
||||||
},
|
},
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await onSubmit();
|
await onSubmit();
|
||||||
@@ -226,12 +222,12 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
|||||||
|
|
||||||
if (!users) {
|
if (!users) {
|
||||||
return (
|
return (
|
||||||
<div className="w-screen h-screen overflow-hidden">
|
<div className="h-screen w-screen overflow-hidden">
|
||||||
<div className="absolute top-0 left-0 z-50 block w-full h-full">
|
<div className="absolute left-0 top-0 z-50 block h-full w-full">
|
||||||
<span className="relative block mx-auto my-0 top50percent top-1/2">
|
<span className="top50percent relative top-1/2 mx-auto my-0 block">
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
label="Loading users..."
|
label="Loading users..."
|
||||||
className="flex items-center justify-center my-auto"
|
className="my-auto flex items-center justify-center"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -269,7 +265,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
|||||||
}}
|
}}
|
||||||
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
|
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
|
||||||
>
|
>
|
||||||
<UserIcon className="w-4 h-4" />
|
<UserIcon className="h-4 w-4" />
|
||||||
<Text className="font-medium">View User</Text>
|
<Text className="font-medium">View User</Text>
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
|
|
||||||
@@ -280,7 +276,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
|||||||
sx={{ color: 'error.main' }}
|
sx={{ color: 'error.main' }}
|
||||||
onClick={() => handleDeleteUser(user)}
|
onClick={() => handleDeleteUser(user)}
|
||||||
>
|
>
|
||||||
<TrashIcon className="w-4 h-4" />
|
<TrashIcon className="h-4 w-4" />
|
||||||
<Text className="font-medium" color="error">
|
<Text className="font-medium" color="error">
|
||||||
Delete User
|
Delete User
|
||||||
</Text>
|
</Text>
|
||||||
@@ -294,14 +290,14 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
|||||||
onClick={() => handleViewUser(user)}
|
onClick={() => handleViewUser(user)}
|
||||||
aria-label={`View ${user.displayName}`}
|
aria-label={`View ${user.displayName}`}
|
||||||
>
|
>
|
||||||
<div className="grid grid-flow-col col-span-2 gap-4 place-content-start">
|
<div className="col-span-2 grid grid-flow-col place-content-start gap-4">
|
||||||
<Avatar
|
<Avatar
|
||||||
src={user.avatarUrl}
|
src={user.avatarUrl}
|
||||||
alt={`Avatar of ${user.displayName}`}
|
alt={`Avatar of ${user.displayName}`}
|
||||||
/>
|
/>
|
||||||
<div className="grid items-center grid-flow-row">
|
<div className="grid grid-flow-row items-center">
|
||||||
<div className="grid items-center grid-flow-col gap-2">
|
<div className="grid grid-flow-col items-center gap-2">
|
||||||
<Text className="font-medium leading-5 truncate">
|
<Text className="truncate font-medium leading-5">
|
||||||
{user.displayName}
|
{user.displayName}
|
||||||
</Text>
|
</Text>
|
||||||
{user.disabled && (
|
{user.disabled && (
|
||||||
@@ -314,7 +310,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Text className="font-normal truncate" color="secondary">
|
<Text className="truncate font-normal" color="secondary">
|
||||||
{user.email}
|
{user.email}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
@@ -334,7 +330,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
|||||||
: '-'}
|
: '-'}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<div className="hidden grid-flow-col col-span-2 gap-3 px-4 place-content-start lg:grid">
|
<div className="col-span-2 hidden grid-flow-col place-content-start gap-3 px-4 lg:grid">
|
||||||
{user.userProviders.length === 0 && (
|
{user.userProviders.length === 0 && (
|
||||||
<Text className="col-span-3 font-medium">-</Text>
|
<Text className="col-span-3 font-medium">-</Text>
|
||||||
)}
|
)}
|
||||||
@@ -362,7 +358,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
|||||||
}
|
}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
alt='Oauth provider logo'
|
alt="Oauth provider logo"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ export default function DataBrowserGrid({
|
|||||||
() =>
|
() =>
|
||||||
columns
|
columns
|
||||||
.map((column) => ({
|
.map((column) => ({
|
||||||
...createDataGridColumn(column, isSchemaEditable),
|
...createDataGridColumn(column, true),
|
||||||
onCellEdit: async (variables: UpdateRecordVariables) => {
|
onCellEdit: async (variables: UpdateRecordVariables) => {
|
||||||
const result = await updateRow(variables);
|
const result = await updateRow(variables);
|
||||||
await queryClient.invalidateQueries([currentTablePath]);
|
await queryClient.invalidateQueries([currentTablePath]);
|
||||||
@@ -288,7 +288,6 @@ export default function DataBrowserGrid({
|
|||||||
[
|
[
|
||||||
columns,
|
columns,
|
||||||
currentTablePath,
|
currentTablePath,
|
||||||
isSchemaEditable,
|
|
||||||
optimisticlyRemovedColumnId,
|
optimisticlyRemovedColumnId,
|
||||||
queryClient,
|
queryClient,
|
||||||
removableColumnId,
|
removableColumnId,
|
||||||
@@ -422,7 +421,7 @@ export default function DataBrowserGrid({
|
|||||||
loading={status === 'loading'}
|
loading={status === 'loading'}
|
||||||
sortBy={sortBy}
|
sortBy={sortBy}
|
||||||
className="pb-17 sm:pb-0"
|
className="pb-17 sm:pb-0"
|
||||||
onInsertRow={isSchemaEditable ? handleInsertRowClick : undefined}
|
onInsertRow={handleInsertRowClick}
|
||||||
onInsertColumn={isSchemaEditable ? handleInsertColumnClick : undefined}
|
onInsertColumn={isSchemaEditable ? handleInsertColumnClick : undefined}
|
||||||
onEditColumn={isSchemaEditable ? handleEditColumnClick : undefined}
|
onEditColumn={isSchemaEditable ? handleEditColumnClick : undefined}
|
||||||
onRemoveColumn={isSchemaEditable ? handleColumnRemoveClick : undefined}
|
onRemoveColumn={isSchemaEditable ? handleColumnRemoveClick : undefined}
|
||||||
@@ -445,7 +444,7 @@ export default function DataBrowserGrid({
|
|||||||
onInsertColumnClick={
|
onInsertColumnClick={
|
||||||
isSchemaEditable ? handleInsertColumnClick : undefined
|
isSchemaEditable ? handleInsertColumnClick : undefined
|
||||||
}
|
}
|
||||||
onInsertRowClick={isSchemaEditable ? handleInsertRowClick : undefined}
|
onInsertRowClick={handleInsertRowClick}
|
||||||
paginationProps={{
|
paginationProps={{
|
||||||
currentPage: Math.max(currentPage, 1),
|
currentPage: Math.max(currentPage, 1),
|
||||||
totalPages: Math.max(numberOfPages, 1),
|
totalPages: Math.max(numberOfPages, 1),
|
||||||
|
|||||||
@@ -12,11 +12,9 @@ import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
|||||||
import { RowIcon } from '@/components/ui/v2/icons/RowIcon';
|
import { RowIcon } from '@/components/ui/v2/icons/RowIcon';
|
||||||
import { useDeleteRecordMutation } from '@/features/database/dataGrid/hooks/useDeleteRecordMutation';
|
import { useDeleteRecordMutation } from '@/features/database/dataGrid/hooks/useDeleteRecordMutation';
|
||||||
import type { DataBrowserGridColumn } from '@/features/database/dataGrid/types/dataBrowser';
|
import type { DataBrowserGridColumn } from '@/features/database/dataGrid/types/dataBrowser';
|
||||||
import { isSchemaLocked } from '@/features/database/dataGrid/utils/schemaHelpers/isSchemaLocked';
|
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { triggerToast } from '@/utils/toast';
|
import { triggerToast } from '@/utils/toast';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { Row } from 'react-table';
|
import type { Row } from 'react-table';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
@@ -58,11 +56,6 @@ export default function DataBrowserGridControls({
|
|||||||
const { className: paginationClassName, ...restPaginationProps } =
|
const { className: paginationClassName, ...restPaginationProps } =
|
||||||
paginationProps || ({} as DataGridPaginationProps);
|
paginationProps || ({} as DataGridPaginationProps);
|
||||||
|
|
||||||
const {
|
|
||||||
query: { schemaSlug },
|
|
||||||
} = useRouter();
|
|
||||||
const isSchemaEditable = !isSchemaLocked(schemaSlug as string);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
selectedFlatRows: selectedRows,
|
selectedFlatRows: selectedRows,
|
||||||
columns,
|
columns,
|
||||||
@@ -126,7 +119,7 @@ export default function DataBrowserGridControls({
|
|||||||
numberOfSelectedRows > 0 ? 'justify-between' : 'justify-end',
|
numberOfSelectedRows > 0 ? 'justify-between' : 'justify-end',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isSchemaEditable && numberOfSelectedRows > 0 && (
|
{numberOfSelectedRows > 0 && (
|
||||||
<div className="grid grid-flow-col place-content-start items-center gap-2">
|
<div className="grid grid-flow-col place-content-start items-center gap-2">
|
||||||
<Chip
|
<Chip
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
@@ -331,7 +331,7 @@ export default function EditPermissionsForm({
|
|||||||
<NavLink
|
<NavLink
|
||||||
href={`/${currentWorkspace.slug}/${currentProject.slug}/settings/roles-and-permissions`}
|
href={`/${currentWorkspace.slug}/${currentProject.slug}/settings/roles-and-permissions`}
|
||||||
passHref
|
passHref
|
||||||
>
|
legacyBehavior>
|
||||||
<Link
|
<Link
|
||||||
href="settings/roles-and-permissions"
|
href="settings/roles-and-permissions"
|
||||||
underline="hover"
|
underline="hover"
|
||||||
|
|||||||
@@ -14,13 +14,11 @@ import type {
|
|||||||
import { convertToHasuraPermissions } from '@/features/database/dataGrid/utils/convertToHasuraPermissions';
|
import { convertToHasuraPermissions } from '@/features/database/dataGrid/utils/convertToHasuraPermissions';
|
||||||
import { convertToRuleGroup } from '@/features/database/dataGrid/utils/convertToRuleGroup';
|
import { convertToRuleGroup } from '@/features/database/dataGrid/utils/convertToRuleGroup';
|
||||||
import type { DialogFormProps } from '@/types/common';
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import AggregationQuerySection from './sections/AggregationQuerySection';
|
import AggregationQuerySection from './sections/AggregationQuerySection';
|
||||||
import BackendOnlySection from './sections/BackendOnlySection';
|
import BackendOnlySection from './sections/BackendOnlySection';
|
||||||
import ColumnPermissionsSection from './sections/ColumnPermissionsSection';
|
import ColumnPermissionsSection from './sections/ColumnPermissionsSection';
|
||||||
@@ -254,18 +252,18 @@ export default function RolePermissionEditorForm({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await toast.promise(
|
await execPromiseWithErrorToast(
|
||||||
managePermissionPromise,
|
async () => {
|
||||||
{
|
await managePermissionPromise;
|
||||||
loading: 'Saving permission...',
|
onDirtyStateChange(false, location);
|
||||||
success: 'Permission has been saved successfully.',
|
onSubmit?.();
|
||||||
error: getServerError('An error occurred while saving the permission.'),
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Saving permission...',
|
||||||
|
successMessage: 'Permission has been saved successfully.',
|
||||||
|
errorMessage: 'An error occurred while saving the permission.',
|
||||||
},
|
},
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
onDirtyStateChange(false, location);
|
|
||||||
onSubmit?.();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCancelClick() {
|
function handleCancelClick() {
|
||||||
@@ -293,20 +291,18 @@ export default function RolePermissionEditorForm({
|
|||||||
mode: 'delete',
|
mode: 'delete',
|
||||||
});
|
});
|
||||||
|
|
||||||
await toast.promise(
|
await execPromiseWithErrorToast(
|
||||||
deletePermissionPromise,
|
async () => {
|
||||||
{
|
await deletePermissionPromise;
|
||||||
loading: 'Deleting permission...',
|
onDirtyStateChange(false, location);
|
||||||
success: 'Permission has been deleted successfully.',
|
onSubmit?.();
|
||||||
error: getServerError(
|
},
|
||||||
'An error occurred while deleting the permission.',
|
{
|
||||||
),
|
loadingMessage: 'Deleting permission...',
|
||||||
|
successMessage: 'Permission has been deleted successfully.',
|
||||||
|
errorMessage: 'An error occurred while deleting the permission.',
|
||||||
},
|
},
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
onDirtyStateChange(false, location);
|
|
||||||
onSubmit?.();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDeleteClick() {
|
function handleDeleteClick() {
|
||||||
|
|||||||
@@ -43,7 +43,13 @@ export default async function deleteRecord({
|
|||||||
(row) =>
|
(row) =>
|
||||||
`(${primaryOrUniqueColumns
|
`(${primaryOrUniqueColumns
|
||||||
.map((primaryOrUniqueColumn) =>
|
.map((primaryOrUniqueColumn) =>
|
||||||
format('%I=%L', primaryOrUniqueColumn, row[primaryOrUniqueColumn]),
|
row[primaryOrUniqueColumn] === null
|
||||||
|
? format('%I IS NULL', primaryOrUniqueColumn)
|
||||||
|
: format(
|
||||||
|
'%I=%L',
|
||||||
|
primaryOrUniqueColumn,
|
||||||
|
row[primaryOrUniqueColumn],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.join(' AND ')})`,
|
.join(' AND ')})`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
import { useDatabaseQuery } from '@/features/database/dataGrid/hooks/useDatabaseQuery';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||||
import { getHasuraAdminSecret } from '@/utils/env';
|
import { getHasuraAdminSecret } from '@/utils/env';
|
||||||
import { parseIdentifiersFromSQL } from '@/utils/sql';
|
import { parseIdentifiersFromSQL } from '@/utils/sql';
|
||||||
import toast from 'react-hot-toast';
|
import { useRouter } from 'next/router';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
export default function useRunSQL(
|
export default function useRunSQL(
|
||||||
sqlCode: string,
|
sqlCode: string,
|
||||||
@@ -15,6 +18,7 @@ export default function useRunSQL(
|
|||||||
migrationName: string,
|
migrationName: string,
|
||||||
) {
|
) {
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [commandOk, setCommandOk] = useState(false);
|
const [commandOk, setCommandOk] = useState(false);
|
||||||
@@ -22,6 +26,14 @@ export default function useRunSQL(
|
|||||||
const [columns, setColumns] = useState<string[]>([]);
|
const [columns, setColumns] = useState<string[]>([]);
|
||||||
const [rows, setRows] = useState<string[][]>([[]]);
|
const [rows, setRows] = useState<string[][]>([[]]);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
query: { dataSourceSlug },
|
||||||
|
} = router;
|
||||||
|
|
||||||
|
const { refetch } = useDatabaseQuery([dataSourceSlug as string]);
|
||||||
|
|
||||||
const appUrl = generateAppServiceUrl(
|
const appUrl = generateAppServiceUrl(
|
||||||
currentProject?.subdomain,
|
currentProject?.subdomain,
|
||||||
currentProject?.region,
|
currentProject?.region,
|
||||||
@@ -169,8 +181,34 @@ export default function useRunSQL(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const trackAll = async (objects: any[]): Promise<Response[]> => {
|
||||||
|
const apiPath = isPlatform ? '/v1/metadata' : '/apis/migrate';
|
||||||
|
const responses: Response[] = await Promise.all(
|
||||||
|
objects.map((object) =>
|
||||||
|
fetch(`${appUrl}${apiPath}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'x-hasura-admin-secret': adminSecret },
|
||||||
|
body: JSON.stringify(object),
|
||||||
|
}).then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error('failed to track:', response);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).catch((error) => {
|
||||||
|
console.error('Error in trackAll:', error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
|
return responses;
|
||||||
|
};
|
||||||
|
|
||||||
const updateMetadata = async (inputSQL: string) => {
|
const updateMetadata = async (inputSQL: string) => {
|
||||||
const entities = parseIdentifiersFromSQL(inputSQL);
|
const entities = parseIdentifiersFromSQL(inputSQL);
|
||||||
|
if (entities.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const tablesOrViewEntities = entities.filter(
|
const tablesOrViewEntities = entities.filter(
|
||||||
(entity) => entity.type !== 'function',
|
(entity) => entity.type !== 'function',
|
||||||
@@ -179,47 +217,75 @@ export default function useRunSQL(
|
|||||||
(entity) => entity.type === 'function',
|
(entity) => entity.type === 'function',
|
||||||
);
|
);
|
||||||
|
|
||||||
const trackTablesOrViews = tablesOrViewEntities.map(({ name, schema }) => ({
|
let trackTablesOrViews: any[] = [];
|
||||||
type: 'pg_track_table',
|
let trackFunctions: any[] = [];
|
||||||
args: {
|
if (isPlatform) {
|
||||||
source: 'default',
|
// use v2/query
|
||||||
table: {
|
trackTablesOrViews = tablesOrViewEntities.map(({ name, schema }) => ({
|
||||||
name,
|
type: 'pg_track_table',
|
||||||
schema,
|
args: {
|
||||||
|
source: 'default',
|
||||||
|
table: {
|
||||||
|
name,
|
||||||
|
schema,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
}));
|
||||||
}));
|
trackFunctions = functionEntities.map(({ name, schema }) => ({
|
||||||
|
type: 'pg_track_function',
|
||||||
const trackFunctions = functionEntities.map(({ name, schema }) => ({
|
args: {
|
||||||
type: 'pg_track_function',
|
source: 'default',
|
||||||
args: {
|
function: {
|
||||||
source: 'default',
|
name,
|
||||||
function: {
|
schema,
|
||||||
name,
|
configuration: {},
|
||||||
schema,
|
},
|
||||||
configuration: {},
|
|
||||||
},
|
},
|
||||||
},
|
}));
|
||||||
}));
|
} else {
|
||||||
|
// use apis/migrate
|
||||||
const metaDataPayload = {
|
trackTablesOrViews = tablesOrViewEntities.map(({ name, schema }) => ({
|
||||||
source: 'default',
|
name: `add_existing_table_or_view_${schema}_${name}`,
|
||||||
type: 'bulk',
|
datasource: 'default',
|
||||||
args: [...trackTablesOrViews, ...trackFunctions],
|
down: [],
|
||||||
};
|
skip_execution: false,
|
||||||
|
up: [
|
||||||
|
{
|
||||||
|
type: 'pg_track_table',
|
||||||
|
args: {
|
||||||
|
table: { name, schema },
|
||||||
|
source: 'default',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
trackFunctions = functionEntities.map(({ name, schema }) => ({
|
||||||
|
name: `add_existing_function_or_view_${schema}_${name}`,
|
||||||
|
datasource: 'default',
|
||||||
|
down: [],
|
||||||
|
skip_execution: false,
|
||||||
|
up: [
|
||||||
|
{
|
||||||
|
type: 'pg_track_function',
|
||||||
|
args: {
|
||||||
|
function: { name, schema },
|
||||||
|
source: 'default',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (entities.length > 0) {
|
await trackAll([...trackTablesOrViews, ...trackFunctions]).then(
|
||||||
const metadataApiResponse = await fetch(`${appUrl}/v1/metadata`, {
|
(responses) => {
|
||||||
method: 'POST',
|
responses.forEach((response) => {
|
||||||
headers: { 'x-hasura-admin-secret': adminSecret },
|
if (!response.ok) {
|
||||||
body: JSON.stringify(metaDataPayload),
|
console.error('Error tracking table or view:', response);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
if (!metadataApiResponse.ok) {
|
},
|
||||||
throw new Error('Metadata API call failed');
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('An error happened when calling the metadata API', {
|
toast.error('An error happened when calling the metadata API', {
|
||||||
style: toastStyle.style,
|
style: toastStyle.style,
|
||||||
@@ -269,6 +335,9 @@ export default function useRunSQL(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// refresh the table list after running the sql
|
||||||
|
await refetch();
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export function createDynamicValidationSchema(
|
|||||||
['time', 'timetz', 'interval'].includes(column.specificType)
|
['time', 'timetz', 'interval'].includes(column.specificType)
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
...schema,
|
...currentSchema,
|
||||||
[column.id]: createTextValidationSchema(details).matches(
|
[column.id]: createTextValidationSchema(details).matches(
|
||||||
/^\d{2}:\d{2}(:\d{2})?$/,
|
/^\d{2}:\d{2}(:\d{2})?$/,
|
||||||
'This is not a valid time (e.g: HH:MM:SS / HH:MM).',
|
'This is not a valid time (e.g: HH:MM:SS / HH:MM).',
|
||||||
@@ -124,14 +124,14 @@ export function createDynamicValidationSchema(
|
|||||||
|
|
||||||
if (column.type === 'date') {
|
if (column.type === 'date') {
|
||||||
return {
|
return {
|
||||||
...schema,
|
...currentSchema,
|
||||||
[column.id]: createDateValidationSchema(details),
|
[column.id]: createDateValidationSchema(details),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (column.type === 'boolean') {
|
if (column.type === 'boolean') {
|
||||||
return {
|
return {
|
||||||
...schema,
|
...currentSchema,
|
||||||
[column.id]: createBooleanValidationSchema(details),
|
[column.id]: createBooleanValidationSchema(details),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -141,13 +141,13 @@ export function createDynamicValidationSchema(
|
|||||||
(column.specificType === 'jsonb' || column.specificType === 'json')
|
(column.specificType === 'jsonb' || column.specificType === 'json')
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
...schema,
|
...currentSchema,
|
||||||
[column.id]: createJSONValidationSchema(details),
|
[column.id]: createJSONValidationSchema(details),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...schema,
|
...currentSchema,
|
||||||
[column.id]: createTextValidationSchema(details),
|
[column.id]: createTextValidationSchema(details),
|
||||||
};
|
};
|
||||||
}, {});
|
}, {});
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetPostgresSettingsDocument,
|
GetPostgresSettingsDocument,
|
||||||
Software_Type_Enum,
|
Software_Type_Enum,
|
||||||
@@ -11,11 +14,11 @@ import {
|
|||||||
useGetSoftwareVersionsQuery,
|
useGetSoftwareVersionsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -32,24 +35,30 @@ export type DatabaseServiceVersionFormValues = Yup.InferType<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
export default function DatabaseServiceVersionSettings() {
|
export default function DatabaseServiceVersionSettings() {
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
|
const { openDialog } = useDialog();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetPostgresSettingsDocument],
|
refetchQueries: [GetPostgresSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetPostgresSettingsQuery({
|
const { data, loading, error } = useGetPostgresSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: databaseVersionsData } = useGetSoftwareVersionsQuery({
|
const { data: databaseVersionsData } = useGetSoftwareVersionsQuery({
|
||||||
variables: {
|
variables: {
|
||||||
software: Software_Type_Enum.PostgreSql,
|
software: Software_Type_Enum.PostgreSql,
|
||||||
},
|
},
|
||||||
|
skip: !isPlatform,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { version } = data?.config?.postgres || {};
|
const { version } = data?.config?.postgres || {};
|
||||||
|
|
||||||
const databaseVersions = databaseVersionsData?.softwareVersions || [];
|
const databaseVersions = databaseVersionsData?.softwareVersions || [];
|
||||||
const availableVersions = Array.from(
|
const availableVersions = Array.from(
|
||||||
new Set(databaseVersions.map((el) => el.version)).add(version),
|
new Set(databaseVersions.map((el) => el.version)).add(version),
|
||||||
@@ -63,10 +72,21 @@ export default function DatabaseServiceVersionSettings() {
|
|||||||
|
|
||||||
const form = useForm<DatabaseServiceVersionFormValues>({
|
const form = useForm<DatabaseServiceVersionFormValues>({
|
||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
defaultValues: { version: { label: version, value: version } },
|
defaultValues: { version: { label: '', value: '' } },
|
||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && version) {
|
||||||
|
form.reset({
|
||||||
|
version: {
|
||||||
|
label: version,
|
||||||
|
value: version,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, version, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -97,23 +117,30 @@ export default function DatabaseServiceVersionSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Postgres version is being updated...`,
|
|
||||||
success: `Postgres version has been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update Postgres version.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
} catch {
|
openDialog({
|
||||||
// Note: The toast will handle the error.
|
title: 'Apply your changes',
|
||||||
}
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Postgres version is being updated...',
|
||||||
|
successMessage: 'Postgres version has been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while trying to update Postgres version.',
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -130,12 +157,20 @@ export default function DatabaseServiceVersionSettings() {
|
|||||||
}}
|
}}
|
||||||
docsLink="https://hub.docker.com/r/nhost/postgres/tags"
|
docsLink="https://hub.docker.com/r/nhost/postgres/tags"
|
||||||
docsTitle="the latest releases"
|
docsTitle="the latest releases"
|
||||||
className="grid grid-flow-row gap-y-2 gap-x-4 px-4 lg:grid-cols-5"
|
className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
|
||||||
>
|
>
|
||||||
<ControlledAutocomplete
|
<ControlledAutocomplete
|
||||||
id="version"
|
id="version"
|
||||||
name="version"
|
name="version"
|
||||||
autoHighlight
|
autoHighlight
|
||||||
|
freeSolo
|
||||||
|
getOptionLabel={(option) => {
|
||||||
|
if (typeof option === 'string') {
|
||||||
|
return option || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return option.value;
|
||||||
|
}}
|
||||||
isOptionEqualToValue={() => false}
|
isOptionEqualToValue={() => false}
|
||||||
filterOptions={(options, { inputValue }) => {
|
filterOptions={(options, { inputValue }) => {
|
||||||
const inputValueLower = inputValue.toLowerCase();
|
const inputValueLower = inputValue.toLowerCase();
|
||||||
|
|||||||
@@ -7,26 +7,28 @@ import { Box } from '@/components/ui/v2/Box';
|
|||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
import { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification';
|
import { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
useGetPostgresSettingsQuery,
|
useGetPostgresSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
capacity: Yup.number().required(),
|
capacity: Yup.number().required().min(10),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>;
|
export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function AuthDomain() {
|
export default function AuthDomain() {
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -36,14 +38,17 @@ export default function AuthDomain() {
|
|||||||
refetch: refetchPostgresSettings,
|
refetch: refetchPostgresSettings,
|
||||||
} = useGetPostgresSettingsQuery({
|
} = useGetPostgresSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const capacity =
|
const capacity =
|
||||||
data?.config?.postgres?.resources?.storage?.capacity ??
|
(data?.config?.postgres?.resources?.storage?.capacity ??
|
||||||
currentProject.plan.featureMaxDbSize;
|
currentProject?.plan?.featureMaxDbSize) ||
|
||||||
|
0;
|
||||||
|
|
||||||
const [updateConfig] = useUpdateConfigMutation();
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
const form = useForm<Yup.InferType<typeof validationSchema>>({
|
const form = useForm<Yup.InferType<typeof validationSchema>>({
|
||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
@@ -75,9 +80,9 @@ export default function AuthDomain() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleSubmit(formValues: AuthDomainFormValues) {
|
async function handleSubmit(formValues: AuthDomainFormValues) {
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfig({
|
await updateConfig({
|
||||||
variables: {
|
variables: {
|
||||||
appId: currentProject.id,
|
appId: currentProject.id,
|
||||||
config: {
|
config: {
|
||||||
@@ -90,22 +95,19 @@ export default function AuthDomain() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
});
|
||||||
{
|
|
||||||
loading: `Database storage capacity is being updated...`,
|
|
||||||
success: `Database storage capacity has been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the database storage capacity.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
form.reset(formValues);
|
||||||
await refetchPostgresSettings();
|
await refetchPostgresSettings();
|
||||||
} catch {
|
},
|
||||||
// Note: The toast will handle the error.
|
{
|
||||||
}
|
loadingMessage: 'Database storage capacity is being updated...',
|
||||||
|
successMessage:
|
||||||
|
'Database storage capacity has been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while trying to update the database storage capacity.',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -122,7 +124,7 @@ export default function AuthDomain() {
|
|||||||
}}
|
}}
|
||||||
className="flex flex-col"
|
className="flex flex-col"
|
||||||
>
|
>
|
||||||
{currentProject.plan.isFree && (
|
{currentProject.plan?.isFree && (
|
||||||
<UpgradeNotification message="Unlock by upgrading your project to the Pro plan." />
|
<UpgradeNotification message="Unlock by upgrading your project to the Pro plan." />
|
||||||
)}
|
)}
|
||||||
<Box className="grid grid-flow-row lg:grid-cols-5">
|
<Box className="grid grid-flow-row lg:grid-cols-5">
|
||||||
@@ -132,7 +134,7 @@ export default function AuthDomain() {
|
|||||||
name="capacity"
|
name="capacity"
|
||||||
type="number"
|
type="number"
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={currentProject.plan.isFree}
|
disabled={currentProject.plan?.isFree}
|
||||||
className="lg:col-span-2"
|
className="lg:col-span-2"
|
||||||
error={Boolean(formState.errors.capacity?.message)}
|
error={Boolean(formState.errors.capacity?.message)}
|
||||||
helperText={formState.errors.capacity?.message}
|
helperText={formState.errors.capacity?.message}
|
||||||
@@ -143,7 +145,7 @@ export default function AuthDomain() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
{!currentProject.plan.isFree && (
|
{!currentProject.plan?.isFree && (
|
||||||
<Alert severity="info" className="col-span-6 text-left">
|
<Alert severity="info" className="col-span-6 text-left">
|
||||||
Note that volumes can only be increased (not decreased). Also, due
|
Note that volumes can only be increased (not decreased). Also, due
|
||||||
to an AWS limitation, the same volume can only be increased once
|
to an AWS limitation, the same volume can only be increased once
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetHasuraSettingsDocument,
|
GetHasuraSettingsDocument,
|
||||||
useGetHasuraSettingsQuery,
|
useGetHasuraSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -22,16 +25,20 @@ const validationSchema = Yup.object({
|
|||||||
export type HasuraAllowListFormValues = Yup.InferType<typeof validationSchema>;
|
export type HasuraAllowListFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function HasuraAllowListSettings() {
|
export default function HasuraAllowListSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||||
useCurrentWorkspaceAndProject();
|
useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetHasuraSettingsDocument],
|
refetchQueries: [GetHasuraSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-first',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { enableAllowList } = data?.config?.hasura.settings || {};
|
const { enableAllowList } = data?.config?.hasura.settings || {};
|
||||||
@@ -44,6 +51,14 @@ export default function HasuraAllowListSettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
enabled: enableAllowList,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, enableAllowList, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -72,24 +87,31 @@ export default function HasuraAllowListSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Allow list settings are being updated...`,
|
await refetchWorkspaceAndProject();
|
||||||
success: `Allow list settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update allow list settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
await refetchWorkspaceAndProject();
|
openDialog({
|
||||||
} catch {
|
title: 'Apply your changes',
|
||||||
// Note: The toast will handle the error.
|
component: <ApplyLocalSettingsDialog />,
|
||||||
}
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Allow list settings are being updated...',
|
||||||
|
successMessage: 'Allow list settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while trying to update allow list settings.',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetHasuraSettingsDocument,
|
GetHasuraSettingsDocument,
|
||||||
useGetHasuraSettingsQuery,
|
useGetHasuraSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -22,16 +25,20 @@ const validationSchema = Yup.object({
|
|||||||
export type HasuraConsoleFormValues = Yup.InferType<typeof validationSchema>;
|
export type HasuraConsoleFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function HasuraConsoleSettings() {
|
export default function HasuraConsoleSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||||
useCurrentWorkspaceAndProject();
|
useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetHasuraSettingsDocument],
|
refetchQueries: [GetHasuraSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-first',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { enableConsole } = data?.config?.hasura.settings || {};
|
const { enableConsole } = data?.config?.hasura.settings || {};
|
||||||
@@ -44,6 +51,14 @@ export default function HasuraConsoleSettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
enabled: enableConsole,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, enableConsole, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -72,24 +87,32 @@ export default function HasuraConsoleSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Hasura Console settings are being updated...`,
|
await refetchWorkspaceAndProject();
|
||||||
success: `Hasura Console settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update Hasura Console settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
await refetchWorkspaceAndProject();
|
openDialog({
|
||||||
} catch {
|
title: 'Apply your changes',
|
||||||
// Note: The toast will handle the error.
|
component: <ApplyLocalSettingsDialog />,
|
||||||
}
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Hasura Console settings are being updated...',
|
||||||
|
successMessage:
|
||||||
|
'Hasura Console settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while trying to update Hasura Console settings.',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetHasuraSettingsDocument,
|
GetHasuraSettingsDocument,
|
||||||
useGetHasuraSettingsQuery,
|
useGetHasuraSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
@@ -30,16 +32,20 @@ const validationSchema = Yup.object({
|
|||||||
export type HasuraCorsDomainFormValues = Yup.InferType<typeof validationSchema>;
|
export type HasuraCorsDomainFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function HasuraCorsDomainSettings() {
|
export default function HasuraCorsDomainSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||||
useCurrentWorkspaceAndProject();
|
useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetHasuraSettingsDocument],
|
refetchQueries: [GetHasuraSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-first',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { corsDomain } = data?.config?.hasura.settings || {};
|
const { corsDomain } = data?.config?.hasura.settings || {};
|
||||||
@@ -95,24 +101,30 @@ export default function HasuraCorsDomainSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `CORS domain settings are being updated...`,
|
await refetchWorkspaceAndProject();
|
||||||
success: `CORS domain settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the project's CORS domain settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
await refetchWorkspaceAndProject();
|
openDialog({
|
||||||
} catch {
|
title: 'Apply your changes',
|
||||||
// Note: The toast will handle the error.
|
component: <ApplyLocalSettingsDialog />,
|
||||||
}
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'CORS domain settings are being updated...',
|
||||||
|
successMessage: 'CORS domain settings have been updated successfully.',
|
||||||
|
errorMessage: `An error occurred while trying to update the project's CORS domain settings.`,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetHasuraSettingsDocument,
|
GetHasuraSettingsDocument,
|
||||||
useGetHasuraSettingsQuery,
|
useGetHasuraSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -22,16 +25,20 @@ const validationSchema = Yup.object({
|
|||||||
export type HasuraDevModeFormValues = Yup.InferType<typeof validationSchema>;
|
export type HasuraDevModeFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function HasuraDevModeSettings() {
|
export default function HasuraDevModeSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||||
useCurrentWorkspaceAndProject();
|
useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetHasuraSettingsDocument],
|
refetchQueries: [GetHasuraSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-first',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { devMode } = data?.config?.hasura.settings || {};
|
const { devMode } = data?.config?.hasura.settings || {};
|
||||||
@@ -44,6 +51,14 @@ export default function HasuraDevModeSettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
enabled: devMode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, devMode, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -65,31 +80,38 @@ export default function HasuraDevModeSettings() {
|
|||||||
config: {
|
config: {
|
||||||
hasura: {
|
hasura: {
|
||||||
settings: {
|
settings: {
|
||||||
enableConsole: formValues.enabled,
|
devMode: formValues.enabled,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Dev Mode settings are being updated...`,
|
await refetchWorkspaceAndProject();
|
||||||
success: `Dev Mode settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update Dev Mode settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
await refetchWorkspaceAndProject();
|
openDialog({
|
||||||
} catch {
|
title: 'Apply your changes',
|
||||||
// Note: The toast will handle the error.
|
component: <ApplyLocalSettingsDialog />,
|
||||||
}
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Dev Mode settings are being updated...',
|
||||||
|
successMessage: 'Dev Mode settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while trying to update Dev Mode settings.',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetHasuraSettingsDocument,
|
GetHasuraSettingsDocument,
|
||||||
useGetHasuraSettingsQuery,
|
useGetHasuraSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -32,31 +35,40 @@ export type HasuraEnabledAPIFormValues = Yup.InferType<typeof validationSchema>;
|
|||||||
const AVAILABLE_HASURA_APIS = ['metadata', 'graphql', 'pgdump', 'config'];
|
const AVAILABLE_HASURA_APIS = ['metadata', 'graphql', 'pgdump', 'config'];
|
||||||
|
|
||||||
export default function HasuraEnabledAPISettings() {
|
export default function HasuraEnabledAPISettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||||
useCurrentWorkspaceAndProject();
|
useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetHasuraSettingsDocument],
|
refetchQueries: [GetHasuraSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { enabledAPIs } = data?.config?.hasura.settings || {};
|
const { enabledAPIs = [] } = data?.config?.hasura?.settings || {};
|
||||||
|
|
||||||
const form = useForm<HasuraEnabledAPIFormValues>({
|
const form = useForm<HasuraEnabledAPIFormValues>({
|
||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
enabledAPIs: enabledAPIs.map((api) => ({
|
enabledAPIs: [],
|
||||||
label: api,
|
|
||||||
value: api,
|
|
||||||
})),
|
|
||||||
},
|
},
|
||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (enabledAPIs && !loading) {
|
||||||
|
form.reset({
|
||||||
|
enabledAPIs: enabledAPIs.map((api) => ({ label: api, value: api })),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [form, enabledAPIs, loading]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -93,24 +105,30 @@ export default function HasuraEnabledAPISettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Enabled APIs are being updated...`,
|
await refetchWorkspaceAndProject();
|
||||||
success: `Enabled APIs have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update enabled APIs.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
await refetchWorkspaceAndProject();
|
openDialog({
|
||||||
} catch {
|
title: 'Apply your changes',
|
||||||
// Note: The toast will handle the error.
|
component: <ApplyLocalSettingsDialog />,
|
||||||
}
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Enabled APIs are being updated...',
|
||||||
|
successMessage: 'Enabled APIs have been updated successfully.',
|
||||||
|
errorMessage: 'An error occurred while trying to update enabled APIs.',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -125,7 +143,7 @@ export default function HasuraEnabledAPISettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
className="grid grid-flow-row gap-y-2 gap-x-4 px-4 lg:grid-cols-6"
|
className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-6"
|
||||||
>
|
>
|
||||||
<ControlledAutocomplete
|
<ControlledAutocomplete
|
||||||
id="enabledAPIs"
|
id="enabledAPIs"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
@@ -5,16 +7,17 @@ import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
|||||||
import { HighlightedText } from '@/components/presentational/HighlightedText';
|
import { HighlightedText } from '@/components/presentational/HighlightedText';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetHasuraSettingsDocument,
|
GetHasuraSettingsDocument,
|
||||||
useGetHasuraSettingsQuery,
|
useGetHasuraSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -31,16 +34,20 @@ export type HasuraLogLevelFormValues = Yup.InferType<typeof validationSchema>;
|
|||||||
const AVAILABLE_HASURA_LOG_LEVELS = ['debug', 'info', 'warn', 'error'];
|
const AVAILABLE_HASURA_LOG_LEVELS = ['debug', 'info', 'warn', 'error'];
|
||||||
|
|
||||||
export default function HasuraLogLevelSettings() {
|
export default function HasuraLogLevelSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||||
useCurrentWorkspaceAndProject();
|
useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetHasuraSettingsDocument],
|
refetchQueries: [GetHasuraSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-first',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { level } = data?.config?.hasura.logs || {};
|
const { level } = data?.config?.hasura.logs || {};
|
||||||
@@ -58,6 +65,17 @@ export default function HasuraLogLevelSettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && level) {
|
||||||
|
form.reset({
|
||||||
|
logLevel: {
|
||||||
|
label: level,
|
||||||
|
value: level,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [form, loading, level]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -94,24 +112,30 @@ export default function HasuraLogLevelSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Log level is being updated...`,
|
await refetchWorkspaceAndProject();
|
||||||
success: `Log level has been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update log level.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
await refetchWorkspaceAndProject();
|
openDialog({
|
||||||
} catch {
|
title: 'Apply your changes',
|
||||||
// Note: The toast will handle the error.
|
component: <ApplyLocalSettingsDialog />,
|
||||||
}
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Log level is being updated...',
|
||||||
|
successMessage: 'Log level has been updated successfully.',
|
||||||
|
errorMessage: 'An error occurred while trying to update log level.',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -136,7 +160,7 @@ export default function HasuraLogLevelSettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
className="grid grid-flow-row gap-y-2 gap-x-4 px-4 lg:grid-cols-5"
|
className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
|
||||||
>
|
>
|
||||||
<ControlledAutocomplete
|
<ControlledAutocomplete
|
||||||
id="logLevel"
|
id="logLevel"
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetHasuraSettingsDocument,
|
GetHasuraSettingsDocument,
|
||||||
useGetHasuraSettingsQuery,
|
useGetHasuraSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -28,16 +30,20 @@ const validationSchema = Yup.object({
|
|||||||
export type HasuraPoolSizeFormValues = Yup.InferType<typeof validationSchema>;
|
export type HasuraPoolSizeFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function HasuraPoolSizeSettings() {
|
export default function HasuraPoolSizeSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||||
useCurrentWorkspaceAndProject();
|
useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetHasuraSettingsDocument],
|
refetchQueries: [GetHasuraSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-first',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { httpPoolSize } = data?.config?.hasura.events || {};
|
const { httpPoolSize } = data?.config?.hasura.events || {};
|
||||||
@@ -79,24 +85,30 @@ export default function HasuraPoolSizeSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Pool size is being updated...`,
|
await refetchWorkspaceAndProject();
|
||||||
success: `Pool size has been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update the pool size.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
await refetchWorkspaceAndProject();
|
openDialog({
|
||||||
} catch {
|
title: 'Apply your changes',
|
||||||
// Note: The toast will handle the error.
|
component: <ApplyLocalSettingsDialog />,
|
||||||
}
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Pool size is being updated...',
|
||||||
|
successMessage: 'Pool size has been updated successfully.',
|
||||||
|
errorMessage: 'An error occurred while trying to update the pool size.',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -112,7 +124,7 @@ export default function HasuraPoolSizeSettings() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
className="grid grid-flow-row gap-y-2 gap-x-4 px-4 lg:grid-cols-5"
|
className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
{...register('httpPoolSize')}
|
{...register('httpPoolSize')}
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetHasuraSettingsDocument,
|
GetHasuraSettingsDocument,
|
||||||
useGetHasuraSettingsQuery,
|
useGetHasuraSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -24,16 +27,20 @@ export type HasuraRemoteSchemaPermissionsFormValues = Yup.InferType<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
export default function HasuraRemoteSchemaPermissionsSettings() {
|
export default function HasuraRemoteSchemaPermissionsSettings() {
|
||||||
|
const { openDialog } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||||
useCurrentWorkspaceAndProject();
|
useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetHasuraSettingsDocument],
|
refetchQueries: [GetHasuraSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-first',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { enableRemoteSchemaPermissions } = data?.config?.hasura.settings || {};
|
const { enableRemoteSchemaPermissions } = data?.config?.hasura.settings || {};
|
||||||
@@ -46,6 +53,14 @@ export default function HasuraRemoteSchemaPermissionsSettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
enabled: enableRemoteSchemaPermissions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, enableRemoteSchemaPermissions, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -69,31 +84,40 @@ export default function HasuraRemoteSchemaPermissionsSettings() {
|
|||||||
config: {
|
config: {
|
||||||
hasura: {
|
hasura: {
|
||||||
settings: {
|
settings: {
|
||||||
enableConsole: formValues.enabled,
|
enableRemoteSchemaPermissions: formValues.enabled,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Remote schema permission settings are being updated...`,
|
await refetchWorkspaceAndProject();
|
||||||
success: `Remote schema permission settings have been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update remote schema permission settings.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
await refetchWorkspaceAndProject();
|
openDialog({
|
||||||
} catch {
|
title: 'Apply your changes',
|
||||||
// Note: The toast will handle the error.
|
component: <ApplyLocalSettingsDialog />,
|
||||||
}
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage:
|
||||||
|
'Remote schema permission settings are being updated...',
|
||||||
|
successMessage:
|
||||||
|
'Remote schema permission settings have been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while trying to update remote schema permission settings.',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
GetHasuraSettingsDocument,
|
GetHasuraSettingsDocument,
|
||||||
Software_Type_Enum,
|
Software_Type_Enum,
|
||||||
@@ -11,11 +14,11 @@ import {
|
|||||||
useGetSoftwareVersionsQuery,
|
useGetSoftwareVersionsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
@@ -32,22 +35,27 @@ export type HasuraServiceVersionFormValues = Yup.InferType<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
export default function HasuraServiceVersionSettings() {
|
export default function HasuraServiceVersionSettings() {
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
|
const { openDialog } = useDialog();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||||
useCurrentWorkspaceAndProject();
|
useCurrentWorkspaceAndProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetHasuraSettingsDocument],
|
refetchQueries: [GetHasuraSettingsDocument],
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||||
variables: { appId: currentProject?.id },
|
variables: { appId: currentProject?.id },
|
||||||
fetchPolicy: 'cache-only',
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: hasuraVersionsData } = useGetSoftwareVersionsQuery({
|
const { data: hasuraVersionsData } = useGetSoftwareVersionsQuery({
|
||||||
variables: {
|
variables: {
|
||||||
software: Software_Type_Enum.Hasura,
|
software: Software_Type_Enum.Hasura,
|
||||||
},
|
},
|
||||||
|
skip: !isPlatform,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { version } = data?.config?.hasura || {};
|
const { version } = data?.config?.hasura || {};
|
||||||
@@ -55,6 +63,7 @@ export default function HasuraServiceVersionSettings() {
|
|||||||
const availableVersions = Array.from(
|
const availableVersions = Array.from(
|
||||||
new Set(versions.map((el) => el.version)).add(version),
|
new Set(versions.map((el) => el.version)).add(version),
|
||||||
)
|
)
|
||||||
|
.filter((v) => !!v)
|
||||||
.sort()
|
.sort()
|
||||||
.reverse()
|
.reverse()
|
||||||
.map((availableVersion) => ({
|
.map((availableVersion) => ({
|
||||||
@@ -62,12 +71,25 @@ export default function HasuraServiceVersionSettings() {
|
|||||||
value: availableVersion,
|
value: availableVersion,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// TODO make sure the network request is made in the parent component
|
||||||
|
// also this request should be made against cache only and the data should be available right away
|
||||||
const form = useForm<HasuraServiceVersionFormValues>({
|
const form = useForm<HasuraServiceVersionFormValues>({
|
||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
defaultValues: { version: { label: version, value: version } },
|
defaultValues: { version: { label: '', value: '' } },
|
||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && version) {
|
||||||
|
form.reset({
|
||||||
|
version: {
|
||||||
|
label: version,
|
||||||
|
value: version,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, version, form]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -96,24 +118,31 @@ export default function HasuraServiceVersionSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
updateConfigPromise,
|
await updateConfigPromise;
|
||||||
{
|
form.reset(formValues);
|
||||||
loading: `Hasura version is being updated...`,
|
await refetchWorkspaceAndProject();
|
||||||
success: `Hasura version has been updated successfully.`,
|
|
||||||
error: getServerError(
|
|
||||||
`An error occurred while trying to update Hasura version.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
form.reset(formValues);
|
if (!isPlatform) {
|
||||||
await refetchWorkspaceAndProject();
|
openDialog({
|
||||||
} catch {
|
title: 'Apply your changes',
|
||||||
// Note: The toast will handle the error.
|
component: <ApplyLocalSettingsDialog />,
|
||||||
}
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Hasura version is being updated...',
|
||||||
|
successMessage: 'Hasura version has been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while trying to update Hasura version.',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -130,11 +159,19 @@ export default function HasuraServiceVersionSettings() {
|
|||||||
}}
|
}}
|
||||||
docsLink="https://hub.docker.com/r/nhost/graphql-engine/tags"
|
docsLink="https://hub.docker.com/r/nhost/graphql-engine/tags"
|
||||||
docsTitle="the latest releases"
|
docsTitle="the latest releases"
|
||||||
className="grid grid-flow-row gap-y-2 gap-x-4 px-4 lg:grid-cols-5"
|
className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
|
||||||
>
|
>
|
||||||
<ControlledAutocomplete
|
<ControlledAutocomplete
|
||||||
id="version"
|
id="version"
|
||||||
name="version"
|
name="version"
|
||||||
|
freeSolo
|
||||||
|
getOptionLabel={(option) => {
|
||||||
|
if (typeof option === 'string') {
|
||||||
|
return option || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return option.value;
|
||||||
|
}}
|
||||||
autoHighlight
|
autoHighlight
|
||||||
isOptionEqualToValue={() => false}
|
isOptionEqualToValue={() => false}
|
||||||
filterOptions={(options, { inputValue }) => {
|
filterOptions={(options, { inputValue }) => {
|
||||||
|
|||||||
@@ -7,13 +7,11 @@ import {
|
|||||||
GetAllWorkspacesAndProjectsDocument,
|
GetAllWorkspacesAndProjectsDocument,
|
||||||
useDeleteApplicationMutation,
|
useDeleteApplicationMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { getServerError } from '@/utils/getServerError';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { getApplicationStatusString } from '@/utils/helpers';
|
import { getApplicationStatusString } from '@/utils/helpers';
|
||||||
import { formatDistance } from 'date-fns';
|
import { formatDistance } from 'date-fns';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
|
|
||||||
export default function ApplicationInfo() {
|
export default function ApplicationInfo() {
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
@@ -23,23 +21,18 @@ export default function ApplicationInfo() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
async function handleClickRemove() {
|
async function handleClickRemove() {
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
deleteApplication({ variables: { appId: currentProject.id } }),
|
await deleteApplication({ variables: { appId: currentProject.id } });
|
||||||
{
|
await router.push('/');
|
||||||
loading: 'Deleting project...',
|
},
|
||||||
success: 'The project has been deleted successfully.',
|
{
|
||||||
error: getServerError(
|
loadingMessage: 'Deleting project...',
|
||||||
'An error occurred while deleting the project. Please try again.',
|
successMessage: 'The project has been deleted successfully.',
|
||||||
),
|
errorMessage:
|
||||||
},
|
'An error occurred while deleting the project. Please try again.',
|
||||||
getToastStyleProps(),
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
await router.push('/');
|
|
||||||
} catch {
|
|
||||||
// Note: The toast will handle the error.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentProject) {
|
if (!currentProject) {
|
||||||
|
|||||||
@@ -18,12 +18,10 @@ import {
|
|||||||
useUnpauseApplicationMutation,
|
useUnpauseApplicationMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { MAX_FREE_PROJECTS } from '@/utils/constants/common';
|
import { MAX_FREE_PROJECTS } from '@/utils/constants/common';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import type { ApolloError } from '@apollo/client';
|
|
||||||
import { useUserData } from '@nhost/nextjs';
|
import { useUserData } from '@nhost/nextjs';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
|
|
||||||
export default function ApplicationPaused() {
|
export default function ApplicationPaused() {
|
||||||
const { openDialog } = useDialog();
|
const { openDialog } = useDialog();
|
||||||
@@ -47,33 +45,18 @@ export default function ApplicationPaused() {
|
|||||||
const wakeUpDisabled = numberOfFreeAndLiveProjects >= MAX_FREE_PROJECTS;
|
const wakeUpDisabled = numberOfFreeAndLiveProjects >= MAX_FREE_PROJECTS;
|
||||||
|
|
||||||
async function handleTriggerUnpausing() {
|
async function handleTriggerUnpausing() {
|
||||||
try {
|
await execPromiseWithErrorToast(
|
||||||
await toast.promise(
|
async () => {
|
||||||
unpauseApplication({ variables: { appId: currentProject.id } }),
|
await unpauseApplication({ variables: { appId: currentProject.id } });
|
||||||
{
|
await refetchWorkspaceAndProject();
|
||||||
loading: 'Starting the project...',
|
},
|
||||||
success: `The project has been started successfully.`,
|
{
|
||||||
error: (arg: ApolloError) => {
|
loadingMessage: 'Starting the project...',
|
||||||
// we need to get the internal error message from the GraphQL error
|
successMessage: 'The project has been started successfully.',
|
||||||
const { internal } = arg.graphQLErrors[0]?.extensions || {};
|
errorMessage:
|
||||||
const { message } = (internal as Record<string, any>)?.error || {};
|
'An error occurred while waking up the project. Please try again.',
|
||||||
|
},
|
||||||
// we use the default Apollo error message if we can't find the
|
);
|
||||||
// internal error message
|
|
||||||
return (
|
|
||||||
message ||
|
|
||||||
arg.message ||
|
|
||||||
'An error occurred while waking up the project. Please try again.'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
await refetchWorkspaceAndProject();
|
|
||||||
} catch {
|
|
||||||
// Note: The toast will handle the error.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user