Compare commits
93 Commits
@nhost/das
...
@nhost/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
fdc50b32d8 | ||
|
|
3cdca8d4b3 | ||
|
|
c425c9f265 | ||
|
|
3b8473b168 | ||
|
|
8d91f7103f | ||
|
|
11ce93d64b | ||
|
|
3ff1c2b531 | ||
|
|
e4341c3706 | ||
|
|
c2ef17c0a0 | ||
|
|
1045ea0a46 | ||
|
|
5faaf36e26 | ||
|
|
65b6a48d51 |
@@ -14,7 +14,7 @@ runs:
|
||||
steps:
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 8.6.2
|
||||
version: 8.10.5
|
||||
run_install: false
|
||||
- name: Get pnpm cache directory
|
||||
id: pnpm-cache-dir
|
||||
|
||||
23
.github/renovate.json
vendored
23
.github/renovate.json
vendored
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"docker-compose": {
|
||||
"enabled": true
|
||||
},
|
||||
"ignoreDeps": [
|
||||
"pnpm",
|
||||
"node",
|
||||
"@types/node"
|
||||
],
|
||||
"labels": [
|
||||
"dependencies"
|
||||
],
|
||||
"enabledManagers": [
|
||||
"npm",
|
||||
"dockerfile",
|
||||
"docker-compose",
|
||||
"github-actions"
|
||||
]
|
||||
}
|
||||
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -106,6 +106,8 @@ jobs:
|
||||
# * Run every `lint` script in the workspace . Dependencies build is cached by Turborepo
|
||||
- name: Lint
|
||||
run: pnpm run lint:all
|
||||
- name: Audit for vulnerabilities
|
||||
run: pnpx audit-ci --config ./audit-ci.jsonc
|
||||
|
||||
e2e:
|
||||
name: 'E2E (Package: ${{ matrix.package.path }})'
|
||||
|
||||
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/
|
||||
dist/
|
||||
umd/
|
||||
node_modules/
|
||||
node_modules
|
||||
tmp/
|
||||
.pnpm-store
|
||||
.turbo
|
||||
|
||||
3
.npmrc
3
.npmrc
@@ -1 +1,2 @@
|
||||
prefer-workspace-packages = true
|
||||
prefer-workspace-packages = true
|
||||
auto-install-peers = false
|
||||
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.
|
||||
6
audit-ci.jsonc
Normal file
6
audit-ci.jsonc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
// $schema provides code completion hints to IDEs.
|
||||
"$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json",
|
||||
"moderate": true,
|
||||
"allowlist": ["trim-newlines"]
|
||||
}
|
||||
3
config/.husky/pre-commit
Executable file → Normal file
3
config/.husky/pre-commit
Executable file → Normal file
@@ -1,4 +1,7 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
[ -n "$CI" ] && exit 0
|
||||
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
pnpm dlx lint-staged --config config/.lintstagedrc.js
|
||||
|
||||
@@ -1,5 +1,169 @@
|
||||
# @nhost/dashboard
|
||||
|
||||
## 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
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 8d91f71: chore: update deps and enable pnpm audit
|
||||
- 3b8473b: chore: update turbo to `1.11.3` and pnpm to `8.10.5` in Dockerfile
|
||||
- Updated dependencies [8d91f71]
|
||||
- @nhost/react-apollo@7.0.2
|
||||
- @nhost/nextjs@2.0.2
|
||||
|
||||
## 1.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 3ff1c2b53: fix: show upgrade option for pro projects
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- c2ef17c0a: feat: add support for new Team plan
|
||||
|
||||
## 1.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -3,7 +3,7 @@ RUN apk add --no-cache libc6-compat
|
||||
RUN apk update
|
||||
WORKDIR /app
|
||||
|
||||
RUN yarn global add turbo@1.10.11
|
||||
RUN yarn global add turbo@1.11.3
|
||||
COPY . .
|
||||
RUN turbo prune --scope="@nhost/dashboard" --docker
|
||||
|
||||
@@ -29,7 +29,7 @@ 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_API_URL __NEXT_PUBLIC_NHOST_HASURA_API_URL__
|
||||
|
||||
RUN yarn global add pnpm@8.6.2
|
||||
RUN yarn global add pnpm@8.10.5
|
||||
COPY .gitignore .gitignore
|
||||
COPY --from=pruner /app/out/json/ .
|
||||
COPY --from=pruner /app/out/pnpm-*.yaml .
|
||||
|
||||
@@ -27,7 +27,7 @@ test('should be able to create then delete a personal access token', async () =>
|
||||
const patName = faker.lorem.slug(3);
|
||||
|
||||
await page.getByRole('textbox', { name: /name/i }).fill(patName);
|
||||
await page.getByRole('button', { name: /expiration/i }).click();
|
||||
await page.getByLabel('Expiration').click();
|
||||
await page.getByRole('option', { name: /7 days/i }).click();
|
||||
await page.getByRole('button', { name: /create/i }).click();
|
||||
|
||||
|
||||
@@ -138,7 +138,8 @@ test('should create a table with an identity column', async () => {
|
||||
],
|
||||
});
|
||||
|
||||
await page.getByRole('button', { name: /identity/i }).click();
|
||||
// await page.getByRole('button', { name: /identity/i }).click();
|
||||
await page.getByLabel('Identity').click();
|
||||
await page.getByRole('option', { name: /id/i }).click();
|
||||
|
||||
// create table
|
||||
@@ -194,26 +195,18 @@ test('should create table with foreign key constraint', async () => {
|
||||
|
||||
await page.getByRole('button', { name: /add foreign key/i }).click();
|
||||
|
||||
// select column in current table
|
||||
await page
|
||||
.getByRole('button', { name: /column/i })
|
||||
.first()
|
||||
.click();
|
||||
await page.locator('#columnName').click();
|
||||
await page.getByRole('option', { name: /author_id/i }).click();
|
||||
|
||||
// select reference schema
|
||||
await page.getByRole('button', { name: /schema/i }).click();
|
||||
await page.getByLabel('Schema').click();
|
||||
await page.getByRole('option', { name: /public/i }).click();
|
||||
|
||||
// select reference table
|
||||
await page.getByRole('button', { name: /table/i }).click();
|
||||
await page.getByLabel('Table').click();
|
||||
await page.getByRole('option', { name: firstTableName, exact: true }).click();
|
||||
|
||||
// select reference column
|
||||
await page
|
||||
.getByRole('button', { name: /column/i })
|
||||
.nth(1)
|
||||
.click();
|
||||
await page.locator('#referencedColumn').click();
|
||||
await page.getByRole('option', { name: /id/i }).click();
|
||||
|
||||
await page.getByRole('button', { name: /add/i }).click();
|
||||
|
||||
@@ -113,27 +113,21 @@ test('should not be able to delete a table if other tables have foreign keys ref
|
||||
await page.getByRole('button', { name: /add foreign key/i }).click();
|
||||
|
||||
// select column in current table
|
||||
await page
|
||||
.getByRole('button', { name: /column/i })
|
||||
.first()
|
||||
.click();
|
||||
await page.locator('#columnName').click();
|
||||
|
||||
await page.getByRole('option', { name: /author_id/i }).click();
|
||||
|
||||
// select reference schema
|
||||
await page.getByRole('button', { name: /schema/i }).click();
|
||||
await page.getByLabel('Schema').click();
|
||||
await page.getByRole('option', { name: /public/i }).click();
|
||||
|
||||
// select reference table
|
||||
await page.getByRole('button', { name: /table/i }).click();
|
||||
await page.getByLabel('Table').click();
|
||||
await page.getByRole('option', { name: firstTableName, exact: true }).click();
|
||||
|
||||
// select reference column
|
||||
await page
|
||||
.getByRole('button', { name: /column/i })
|
||||
.nth(1)
|
||||
.click();
|
||||
await page.locator('#referencedColumn').click();
|
||||
await page.getByRole('option', { name: /id/i }).click();
|
||||
|
||||
await page.getByRole('button', { name: /add/i }).click();
|
||||
|
||||
await expect(
|
||||
|
||||
@@ -93,7 +93,7 @@ test("should show the project's region and subdomain", async () => {
|
||||
|
||||
test('should not have a GitHub repository connected', async () => {
|
||||
await expect(
|
||||
page.getByRole('button', { name: /connect to github/i }),
|
||||
page.getByRole('button', { name: /connect to github/i }).first(),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
|
||||
@@ -116,7 +116,8 @@ export async function prepareTable({
|
||||
);
|
||||
|
||||
// select the first column as primary key
|
||||
await page.getByRole('button', { name: /primary key/i }).click();
|
||||
// await page.getByRole('button', { name: /primary key/i }).click();
|
||||
await page.getByLabel('Primary Key').click();
|
||||
await page.getByRole('option', { name: primaryKey, exact: true }).click();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
query InitQuery {
|
||||
root {
|
||||
enableServices
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"projectId": 2596,
|
||||
"token": "U2FsdGVkX19+V8BJnVR0xLEC+42OW5qZl/A0i6beAaRmJoIhFh5Yf6eIKBzLbV9h",
|
||||
"outputDirectoryPath": "src/hypertune"
|
||||
}
|
||||
@@ -4,6 +4,20 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
||||
});
|
||||
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({
|
||||
reactStrictMode: true,
|
||||
swcMinify: false,
|
||||
@@ -17,6 +31,19 @@ module.exports = withBundleAnalyzer({
|
||||
eslint: {
|
||||
dirs: ['src'],
|
||||
},
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: '/(.*)',
|
||||
headers: [
|
||||
{
|
||||
key: 'X-Frame-Options',
|
||||
value: 'SAMEORIGIN',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/dashboard",
|
||||
"version": "1.4.0",
|
||||
"version": "1.11.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
@@ -15,62 +15,62 @@
|
||||
"format": "prettier --write \"src/**/*.{js,ts,tsx,jsx,json,md}\" --plugin-search-dir=.",
|
||||
"storybook": "start-storybook -p 6006 -s public",
|
||||
"build-storybook": "build-storybook",
|
||||
"install-browsers": "pnpm dlx playwright@1.31.0 install --with-deps",
|
||||
"e2e": "pnpm install-browsers && pnpm dlx playwright@1.31.0 test"
|
||||
"install-browsers": "pnpm playwright install && pnpm playwright install-deps",
|
||||
"e2e": "pnpm install-browsers && pnpm playwright test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.7.10",
|
||||
"@codemirror/lang-sql": "^6.5.4",
|
||||
"@emotion/cache": "^11.10.5",
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/server": "^11.4.0",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@fontsource/inter": "^5.0.0",
|
||||
"@fontsource/roboto-mono": "^5.0.0",
|
||||
"@graphiql/react": "^0.18.0",
|
||||
"@graphiql/toolkit": "^0.8.2",
|
||||
"@headlessui/react": "^1.6.5",
|
||||
"@apollo/client": "^3.9.5",
|
||||
"@codemirror/lang-sql": "^6.6.0",
|
||||
"@emotion/cache": "^11.11.0",
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/server": "^11.11.0",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@fontsource/inter": "^5.0.16",
|
||||
"@fontsource/roboto-mono": "^5.0.16",
|
||||
"@graphiql/react": "^0.20.3",
|
||||
"@graphiql/toolkit": "^0.9.1",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@hookform/resolvers": "^3.0.0",
|
||||
"@mui/base": "^5.0.0-alpha.106",
|
||||
"@mui/material": "^5.10.14",
|
||||
"@mui/system": "^5.10.14",
|
||||
"@mui/x-date-pickers": "^5.0.8",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@mui/base": "5.0.0-beta.31",
|
||||
"@mui/material": "^5.15.11",
|
||||
"@mui/system": "^5.15.11",
|
||||
"@mui/x-date-pickers": "^5.0.20",
|
||||
"@nhost/nextjs": "workspace:*",
|
||||
"@nhost/react-apollo": "workspace:*",
|
||||
"@segment/snippet": "^4.15.3",
|
||||
"@stripe/react-stripe-js": "^2.0.0",
|
||||
"@stripe/stripe-js": "^1.35.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tanstack/react-query": "^4.16.1",
|
||||
"@tanstack/react-table": "^8.5.30",
|
||||
"@tanstack/react-virtual": "^3.0.0-beta.23",
|
||||
"@uiw/codemirror-theme-github": "^4.21.20",
|
||||
"@uiw/react-codemirror": "^4.21.20",
|
||||
"@segment/snippet": "^4.16.2",
|
||||
"@stripe/react-stripe-js": "^2.5.1",
|
||||
"@stripe/stripe-js": "^1.54.2",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
"@tanstack/react-table": "^8.13.2",
|
||||
"@tanstack/react-virtual": "^3.1.3",
|
||||
"@uiw/codemirror-theme-github": "^4.21.24",
|
||||
"@uiw/react-codemirror": "^4.21.24",
|
||||
"analytics-node": "^6.2.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"clsx": "^1.2.1",
|
||||
"date-fns": "^2.29.3",
|
||||
"generate-password": "^1.7.0",
|
||||
"graphiql": "^3.0.0",
|
||||
"graphql": "^16.6.0",
|
||||
"graphql-request": "^6.0.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"framer-motion": "^10.18.0",
|
||||
"generate-password": "^1.7.1",
|
||||
"graphiql": "^3.1.1",
|
||||
"graphql": "16.8.1",
|
||||
"graphql-request": "^6.1.0",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"graphql-ws": "^5.11.2",
|
||||
"hypertune": "^1.4.4",
|
||||
"just-kebab-case": "^4.1.1",
|
||||
"graphql-ws": "^5.15.0",
|
||||
"just-kebab-case": "^4.2.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"next": "^12.3.1",
|
||||
"next-seo": "^6.0.0",
|
||||
"next": "^14.1.0",
|
||||
"next-seo": "^6.5.0",
|
||||
"node-pg-format": "^1.3.5",
|
||||
"pluralize": "^8.0.0",
|
||||
"react": "18.2.0",
|
||||
"react-children-utilities": "^2.9.0",
|
||||
"react-children-utilities": "^2.10.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-error-boundary": "^4.0.0",
|
||||
"react-hook-form": "^7.42.1",
|
||||
"react-hot-toast": "^2.4.0",
|
||||
"react-intersection-observer": "^9.5.2",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-hook-form": "^7.50.1",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-intersection-observer": "^9.8.1",
|
||||
"react-is": "18.2.0",
|
||||
"react-loading-skeleton": "^2.2.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
@@ -82,87 +82,87 @@
|
||||
"rehype-highlight": "^7.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"shell-quote": "^1.8.1",
|
||||
"slugify": "^1.6.5",
|
||||
"slugify": "^1.6.6",
|
||||
"stripe": "^10.17.0",
|
||||
"tailwind-merge": "^1.8.0",
|
||||
"utility-types": "^3.10.0",
|
||||
"validator": "^13.7.0",
|
||||
"yup": "^1.0.2",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"utility-types": "^3.11.0",
|
||||
"validator": "^13.11.0",
|
||||
"yup": "^1.3.3",
|
||||
"yup-password": "^0.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.2",
|
||||
"@babel/core": "^7.24.0",
|
||||
"@faker-js/faker": "^7.6.0",
|
||||
"@graphql-codegen/cli": "^3.0.0",
|
||||
"@graphql-codegen/typescript": "^3.0.0",
|
||||
"@graphql-codegen/typescript-operations": "^3.0.0",
|
||||
"@graphql-codegen/typescript-react-apollo": "^3.3.1",
|
||||
"@next/bundle-analyzer": "^12.3.1",
|
||||
"@playwright/test": "1.31.0",
|
||||
"@storybook/addon-actions": "^6.5.14",
|
||||
"@storybook/addon-essentials": "^6.5.14",
|
||||
"@storybook/addon-interactions": "^6.5.14",
|
||||
"@storybook/addon-links": "^6.5.14",
|
||||
"@graphql-codegen/cli": "^5.0.2",
|
||||
"@graphql-codegen/typescript": "^3.0.4",
|
||||
"@graphql-codegen/typescript-operations": "^3.0.4",
|
||||
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
|
||||
"@next/bundle-analyzer": "^12.3.4",
|
||||
"@playwright/test": "1.41.0",
|
||||
"@storybook/addon-actions": "^6.5.16",
|
||||
"@storybook/addon-essentials": "^6.5.16",
|
||||
"@storybook/addon-interactions": "^6.5.16",
|
||||
"@storybook/addon-links": "^6.5.16",
|
||||
"@storybook/addon-postcss": "^2.0.0",
|
||||
"@storybook/builder-webpack5": "^6.5.14",
|
||||
"@storybook/manager-webpack5": "^6.5.14",
|
||||
"@storybook/react": "^6.5.14",
|
||||
"@storybook/testing-library": "^0.2.0",
|
||||
"@storybook/builder-webpack5": "^6.5.16",
|
||||
"@storybook/manager-webpack5": "^6.5.16",
|
||||
"@storybook/react": "^7.6.17",
|
||||
"@storybook/testing-library": "^0.2.2",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"@testing-library/dom": "^9.0.0",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^14.2.1",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/ace": "^0.0.48",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
"@types/node": "^16.11.7",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/lodash.debounce": "^4.0.9",
|
||||
"@types/node": "^16.18.86",
|
||||
"@types/pluralize": "^0.0.30",
|
||||
"@types/react": "^18.2.14",
|
||||
"@types/react-dom": "^18.2.6",
|
||||
"@types/react-table": "^7.7.12",
|
||||
"@types/shell-quote": "^1.7.1",
|
||||
"@types/testing-library__jest-dom": "^5.14.5",
|
||||
"@types/validator": "^13.7.10",
|
||||
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
||||
"@typescript-eslint/parser": "^6.15.0",
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"@vitest/coverage-v8": "^0.32.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"@types/react": "^18.2.61",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@types/react-table": "^7.7.19",
|
||||
"@types/shell-quote": "^1.7.5",
|
||||
"@types/testing-library__jest-dom": "^5.14.9",
|
||||
"@types/validator": "^13.11.9",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@vitest/coverage-v8": "^0.32.4",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"babel-loader": "^8.3.0",
|
||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
"csstype": "^3.0.10",
|
||||
"dotenv": "^16.0.3",
|
||||
"csstype": "^3.1.3",
|
||||
"dotenv": "^16.4.5",
|
||||
"encoding": "^0.1.13",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb": "19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||
"eslint-config-next": "^13.0.2",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.6.1",
|
||||
"eslint-plugin-react": "^7.31.11",
|
||||
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||
"eslint-config-next": "^13.5.6",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"jsdom": "^22.0.0",
|
||||
"lint-staged": ">=13",
|
||||
"msw": "^1.0.1",
|
||||
"msw-storybook-addon": "^1.6.3",
|
||||
"node-fetch": "^3.3.0",
|
||||
"postcss": "^8.4.19",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-organize-imports": "^3.2.0",
|
||||
"prettier-plugin-tailwindcss": "^0.4.0",
|
||||
"jsdom": "^22.1.0",
|
||||
"lint-staged": "^15.2.2",
|
||||
"msw": "^1.3.2",
|
||||
"msw-storybook-addon": "^1.10.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"postcss": "^8.4.35",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"prettier-plugin-tailwindcss": "^0.4.1",
|
||||
"react-date-fns-hooks": "^0.9.4",
|
||||
"require-from-string": "^2.0.2",
|
||||
"snake-case": "^3.0.4",
|
||||
"storybook-addon-next-router": "^4.0.1",
|
||||
"tailwindcss": "^3.1.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
||||
"vite": "^4.0.2",
|
||||
"vite-tsconfig-paths": "^4.0.3",
|
||||
"vitest": "^0.32.0"
|
||||
"storybook-addon-next-router": "^4.0.2",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths-webpack-plugin": "^4.1.0",
|
||||
"vite": "^5.1.4",
|
||||
"vite-tsconfig-paths": "^4.3.1",
|
||||
"vitest": "^0.32.4"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
||||
@@ -4,9 +4,17 @@ import type { DetailedHTMLProps, HTMLProps } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export interface ContactUsProps
|
||||
extends DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> {}
|
||||
extends DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> {
|
||||
isTeam?: boolean;
|
||||
isOwner?: boolean;
|
||||
}
|
||||
|
||||
export default function FeedbackForm({ className, ...props }: ContactUsProps) {
|
||||
export default function FeedbackForm({
|
||||
className,
|
||||
isTeam,
|
||||
isOwner,
|
||||
...props
|
||||
}: ContactUsProps) {
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
@@ -19,6 +27,30 @@ export default function FeedbackForm({ className, ...props }: ContactUsProps) {
|
||||
Contact us
|
||||
</Text>
|
||||
|
||||
{isTeam && isOwner && (
|
||||
<Text>
|
||||
If this is a new Team project, or you need to manage members, reach
|
||||
out to us on discord or via email at{' '}
|
||||
<Link
|
||||
href="mailto:support@nhost.io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
underline="hover"
|
||||
>
|
||||
support@nhost.io
|
||||
</Link>{' '}
|
||||
so we can have your dedicated channel set up.
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{isTeam && !isOwner && (
|
||||
<Text>
|
||||
As part of a team plan you can reach out to us on the private channel
|
||||
for this workspace. If you haven't been added to the channel, ask
|
||||
the workspace owner to add you.
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Text>
|
||||
To report issues with Nhost, please open a GitHub issue in the{' '}
|
||||
<Link
|
||||
|
||||
@@ -17,7 +17,7 @@ function NavLink(
|
||||
ref: ForwardedRef<HTMLAnchorElement>,
|
||||
) {
|
||||
return (
|
||||
<NextLink href={href} passHref>
|
||||
<NextLink href={href} passHref legacyBehavior>
|
||||
<Link className={twMerge('font-display', className)} ref={ref} {...props}>
|
||||
{children}
|
||||
</Link>
|
||||
|
||||
@@ -26,7 +26,7 @@ export default function ThemeSwitcher({
|
||||
listbox: { className: 'min-w-0 w-full' },
|
||||
popper: {
|
||||
disablePortal: false,
|
||||
className: 'z-[10000] w-[270px] w-full',
|
||||
className: 'z-[10000] w-[270px]',
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -26,7 +26,7 @@ export default function UpgradeToProBanner({
|
||||
return (
|
||||
<Box
|
||||
sx={{ backgroundColor: 'primary.light' }}
|
||||
className="flex flex-col justify-between space-y-4 rounded-md p-4 lg:flex-row lg:items-center lg:space-y-0"
|
||||
className="flex flex-col justify-between p-4 space-y-4 rounded-md lg:flex-row lg:items-center lg:space-y-0"
|
||||
>
|
||||
<div className="flex flex-col justify-between space-y-4">
|
||||
<div className="space-y-2">
|
||||
@@ -81,13 +81,13 @@ export default function UpgradeToProBanner({
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
underline="hover"
|
||||
className="text-center font-medium"
|
||||
className="font-medium text-center"
|
||||
sx={{
|
||||
color: 'text.secondary',
|
||||
}}
|
||||
>
|
||||
See all features
|
||||
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
|
||||
<ArrowSquareOutIcon className="w-4 h-4 ml-1" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
@@ -97,6 +97,7 @@ export default function UpgradeToProBanner({
|
||||
width={300}
|
||||
height={140}
|
||||
objectFit="contain"
|
||||
alt='Upgrade to Pro illustration'
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -35,7 +35,7 @@ function InsertPlaceholderTableRow({
|
||||
...props
|
||||
}: InsertPlaceholderTableRowProps) {
|
||||
return (
|
||||
<Box className="h-12 border-r-1 border-b-1" {...props}>
|
||||
<Box className="h-12 border-b-1 border-r-1" {...props}>
|
||||
<Button
|
||||
onClick={onInsertRow}
|
||||
variant="borderless"
|
||||
@@ -209,7 +209,7 @@ export default function DataGridBody<T extends object>({
|
||||
/>
|
||||
) : (
|
||||
<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' }}
|
||||
style={{
|
||||
width: allowInsertColumn
|
||||
@@ -281,8 +281,8 @@ export default function DataGridBody<T extends object>({
|
||||
}}
|
||||
className={twMerge(
|
||||
'h-12 font-display text-xs motion-safe:transition-colors',
|
||||
'border-r-1 border-b-1',
|
||||
'scroll-mt-[57px] scroll-ml-8',
|
||||
'border-b-1 border-r-1',
|
||||
'scroll-ml-8 scroll-mt-[57px]',
|
||||
column.id === 'selection' &&
|
||||
'sticky left-0 z-20 justify-center px-0',
|
||||
)}
|
||||
@@ -296,7 +296,7 @@ export default function DataGridBody<T extends object>({
|
||||
})}
|
||||
|
||||
{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>
|
||||
|
||||
|
||||
@@ -8,7 +8,15 @@ import type {
|
||||
DataBrowserGridCellProps,
|
||||
} from '@/features/database/dataGrid/types/dataBrowser';
|
||||
import { triggerToast } from '@/utils/toast';
|
||||
import type { FocusEvent, KeyboardEvent, MouseEvent } from 'react';
|
||||
import type {
|
||||
FocusEvent,
|
||||
JSXElementConstructor,
|
||||
KeyboardEvent,
|
||||
MouseEvent,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
ReactPortal,
|
||||
} from 'react';
|
||||
import {
|
||||
Children,
|
||||
cloneElement,
|
||||
@@ -308,7 +316,7 @@ function DataGridCellContent<TData extends object = {}, TValue = unknown>({
|
||||
isEditable &&
|
||||
'focus-within:outline-none focus-within:ring-0 focus:ring-0',
|
||||
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,
|
||||
)}
|
||||
onFocus={handleFocus}
|
||||
@@ -320,20 +328,28 @@ function DataGridCellContent<TData extends object = {}, TValue = unknown>({
|
||||
sx={{ backgroundColor: 'transparent' }}
|
||||
{...props}
|
||||
>
|
||||
{Children.map(children, (child) => {
|
||||
if (!isValidElement(child)) {
|
||||
return null;
|
||||
}
|
||||
{Children.map(
|
||||
children,
|
||||
(
|
||||
child:
|
||||
| ReactNode
|
||||
| ReactPortal
|
||||
| ReactElement<unknown, string | JSXElementConstructor<any>>,
|
||||
) => {
|
||||
if (!isValidElement(child)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return cloneElement(child, {
|
||||
...child.props,
|
||||
onSave: handleSave,
|
||||
optimisticValue,
|
||||
onOptimisticValueChange: setOptimisticValue,
|
||||
temporaryValue,
|
||||
onTemporaryValueChange: setTemporaryValue,
|
||||
});
|
||||
})}
|
||||
return cloneElement(child, {
|
||||
...child.props,
|
||||
onSave: handleSave,
|
||||
optimisticValue,
|
||||
onOptimisticValueChange: setOptimisticValue,
|
||||
temporaryValue,
|
||||
onTemporaryValueChange: setTemporaryValue,
|
||||
});
|
||||
},
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
|
||||
@@ -96,45 +96,52 @@ export default function DataGridHeader<T extends object>({
|
||||
}}
|
||||
key={column.id}
|
||||
>
|
||||
<Dropdown.Trigger
|
||||
className={twMerge(
|
||||
'focus:outline-none motion-safe:transition-colors',
|
||||
)}
|
||||
disabled={
|
||||
column.isDisabled ||
|
||||
column.id === 'selection' ||
|
||||
(column.disableSortBy && !onRemoveColumn)
|
||||
}
|
||||
hideChevron
|
||||
>
|
||||
{column.id === 'selection' ? (
|
||||
<span
|
||||
{...headerProps}
|
||||
className="relative grid w-full grid-flow-col items-center justify-between p-2"
|
||||
>
|
||||
{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>
|
||||
|
||||
{allowResize && !column.disableResizing && (
|
||||
) : (
|
||||
<Dropdown.Trigger
|
||||
className={twMerge(
|
||||
'focus:outline-none motion-safe:transition-colors',
|
||||
)}
|
||||
disabled={
|
||||
column.isDisabled || (column.disableSortBy && !onRemoveColumn)
|
||||
}
|
||||
hideChevron
|
||||
>
|
||||
<span
|
||||
{...column.getResizerProps({
|
||||
onClick: (event: Event) => event.stopPropagation(),
|
||||
})}
|
||||
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"
|
||||
/>
|
||||
)}
|
||||
</Dropdown.Trigger>
|
||||
{...headerProps}
|
||||
className="relative grid w-full grid-flow-col items-center justify-between p-2"
|
||||
>
|
||||
{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>
|
||||
|
||||
{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
|
||||
menu
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Dropdown } from '@/components/ui/v2/Dropdown';
|
||||
import { GraphiteIcon } from '@/components/ui/v2/icons/GraphiteIcon';
|
||||
import { DevAssistant } from '@/features/ai/DevAssistant';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCurrentUserOwner';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { ApplicationStatus } from '@/types/application';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
@@ -37,6 +38,8 @@ export default function Header({ className, ...props }: HeaderProps) {
|
||||
const { currentProject, refetch: refetchProject } =
|
||||
useCurrentWorkspaceAndProject();
|
||||
|
||||
const isOwner = useIsCurrentUserOwner();
|
||||
|
||||
const isProjectUpdating =
|
||||
currentProject?.appStates[0]?.stateId === ApplicationStatus.Updating;
|
||||
|
||||
@@ -114,7 +117,11 @@ export default function Header({ className, ...props }: HeaderProps) {
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||
>
|
||||
<ContactUs className="max-w-md" />
|
||||
<ContactUs
|
||||
className="max-w-md"
|
||||
isTeam={currentProject?.plan?.name === 'Team'}
|
||||
isOwner={isOwner}
|
||||
/>
|
||||
</Dropdown.Content>
|
||||
</Dropdown.Root>
|
||||
)}
|
||||
|
||||
@@ -8,9 +8,9 @@ import { Input, inputClasses } from '@/components/ui/v2/Input';
|
||||
import { OptionBase } from '@/components/ui/v2/Option';
|
||||
import { OptionGroupBase } from '@/components/ui/v2/OptionGroup';
|
||||
import type { StyledComponent } from '@emotion/styled';
|
||||
import type { UseAutocompleteProps } from '@mui/base/AutocompleteUnstyled';
|
||||
import { createFilterOptions } from '@mui/base/AutocompleteUnstyled';
|
||||
import PopperUnstyled from '@mui/base/PopperUnstyled';
|
||||
import type { UseAutocompleteProps } from '@mui/base/useAutocomplete';
|
||||
import { createFilterOptions } from '@mui/base/useAutocomplete';
|
||||
import { Popper } from '@mui/base'
|
||||
import { styled } from '@mui/material';
|
||||
import type { AutocompleteProps as MaterialAutocompleteProps } from '@mui/material/Autocomplete';
|
||||
import MaterialAutocomplete, {
|
||||
@@ -142,7 +142,7 @@ const StyledOptionBase = styled(OptionBase)(({ theme }) => ({
|
||||
gap: theme.spacing(0.5),
|
||||
}));
|
||||
|
||||
export const AutocompletePopper = styled(PopperUnstyled)(({ theme }) => ({
|
||||
export const AutocompletePopper = styled(Popper)(({ theme }) => ({
|
||||
zIndex: theme.zIndex.modal + 1,
|
||||
boxShadow: 'none',
|
||||
minWidth: 320,
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { styled } from '@mui/material';
|
||||
import type {
|
||||
BoxProps as MaterialBoxProps,
|
||||
BoxTypeMap,
|
||||
} from '@mui/material/Box';
|
||||
import type { BoxProps as MaterialBoxProps } from '@mui/material/Box';
|
||||
import MaterialBox from '@mui/material/Box';
|
||||
import { type BoxTypeMap } from '@mui/system';
|
||||
import type { ForwardedRef, PropsWithoutRef } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PlusCircleIcon } from '@/components/ui/v2/icons/PlusCircleIcon';
|
||||
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 Button from './Button';
|
||||
|
||||
@@ -24,9 +24,9 @@ export default {
|
||||
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,
|
||||
) {
|
||||
return <Button {...args} />;
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface ChipProps extends MaterialChipProps {
|
||||
/**
|
||||
* Custom component for the root node.
|
||||
*/
|
||||
component?: string | ElementType;
|
||||
component?: ElementType;
|
||||
}
|
||||
|
||||
const Chip = styled(MaterialChip)<ChipProps>(({ theme }) => ({
|
||||
|
||||
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';
|
||||
@@ -7,7 +7,7 @@ export interface HelperTextProps extends MaterialFormHelperTextProps {
|
||||
/**
|
||||
* Custom component for the root node.
|
||||
*/
|
||||
component?: string | ElementType;
|
||||
component?: ElementType;
|
||||
}
|
||||
|
||||
const HelperText = styled(MaterialFormHelperText)<HelperTextProps>({
|
||||
|
||||
@@ -1,41 +1,42 @@
|
||||
import type { OptionUnstyledProps } from '@mui/base/OptionUnstyled';
|
||||
import OptionUnstyled, {
|
||||
optionUnstyledClasses,
|
||||
} from '@mui/base/OptionUnstyled';
|
||||
import {
|
||||
Option as BaseOption,
|
||||
optionClasses as baseOptionClasses,
|
||||
type OptionProps as BaseOptionProps,
|
||||
} from '@mui/base';
|
||||
import { darken, styled } from '@mui/material';
|
||||
import type { ForwardedRef } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import OptionBase from './OptionBase';
|
||||
|
||||
export interface OptionProps<TValue extends {}>
|
||||
extends OptionUnstyledProps<TValue> {}
|
||||
extends BaseOptionProps<TValue> {}
|
||||
|
||||
const StyledOption = styled(OptionUnstyled)(({ theme }) => ({
|
||||
const StyledOption = styled(BaseOption)(({ theme }) => ({
|
||||
transition: theme.transitions.create(['background-color']),
|
||||
color: theme.palette.text.primary,
|
||||
[`&.${optionUnstyledClasses.selected}`]: {
|
||||
[`&.${baseOptionClasses.selected}`]: {
|
||||
backgroundColor:
|
||||
theme.palette.mode === 'dark'
|
||||
? `${darken(theme.palette.action.hover, 0.1)} !important`
|
||||
: `${darken(theme.palette.action.hover, 0.05)} !important`,
|
||||
},
|
||||
[`&.${optionUnstyledClasses.selected}:hover, &.${optionUnstyledClasses.selected}.${optionUnstyledClasses.highlighted}`]:
|
||||
[`&.${baseOptionClasses.selected}:hover, &.${baseOptionClasses.selected}.${baseOptionClasses.highlighted}`]:
|
||||
{
|
||||
backgroundColor:
|
||||
theme.palette.mode === 'dark'
|
||||
? `${darken(theme.palette.action.hover, 0.25)} !important`
|
||||
: `${darken(theme.palette.action.hover, 0.075)} !important`,
|
||||
},
|
||||
[`&.${optionUnstyledClasses.highlighted}, &:hover`]: {
|
||||
[`&.${baseOptionClasses.highlighted}, &:hover`]: {
|
||||
backgroundColor:
|
||||
theme.palette.mode === 'dark'
|
||||
? `${darken(theme.palette.action.hover, 0.15)} !important`
|
||||
: `${theme.palette.action.hover} !important`,
|
||||
},
|
||||
[`&.${optionUnstyledClasses.disabled}`]: {
|
||||
[`&.${baseOptionClasses.disabled}`]: {
|
||||
color: theme.palette.text.disabled,
|
||||
},
|
||||
[`&.${optionUnstyledClasses.disabled}:hover`]: {
|
||||
[`&.${baseOptionClasses.disabled}:hover`]: {
|
||||
backgroundColor: 'transparent !important',
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import type { OptionGroupUnstyledProps } from '@mui/base/OptionGroupUnstyled';
|
||||
import OptionGroupUnstyled from '@mui/base/OptionGroupUnstyled';
|
||||
import {
|
||||
OptionGroup as BaseOptionGroup,
|
||||
type OptionGroupProps as BaseOptionGroupProps,
|
||||
} from '@mui/base';
|
||||
import { styled } from '@mui/material';
|
||||
import type { ForwardedRef } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import OptionGroupBase from './OptionGroupBase';
|
||||
|
||||
export interface OptionGroupProps extends OptionGroupUnstyledProps {}
|
||||
export interface OptionGroupProps extends BaseOptionGroupProps {}
|
||||
|
||||
const StyledGroupRoot = styled('li')(({ theme }) => ({
|
||||
listStyle: 'none',
|
||||
@@ -25,7 +27,7 @@ function OptionGroup(
|
||||
...externalSlots,
|
||||
};
|
||||
|
||||
return <OptionGroupUnstyled {...props} ref={ref} slots={slots} />;
|
||||
return <BaseOptionGroup {...props} ref={ref} slots={slots} />;
|
||||
}
|
||||
|
||||
OptionGroup.displayName = 'NhostOptionGroup';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 Select from './Select';
|
||||
|
||||
@@ -7,11 +7,9 @@ export default {
|
||||
title: 'UI Library / Select',
|
||||
component: Select,
|
||||
argTypes: {},
|
||||
} as ComponentMeta<typeof Select>;
|
||||
} as Meta<typeof Select>;
|
||||
|
||||
const Template: ComponentStory<typeof Select> = function Template(
|
||||
args: SelectProps<any>,
|
||||
) {
|
||||
const Template: StoryFn<SelectProps<any>> = function TemplateFunction(args) {
|
||||
return (
|
||||
<Select className="w-64" {...args}>
|
||||
<Option value="value1">Value 1</Option>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { FormControlProps } from '@/components/ui/v2/FormControl';
|
||||
import { FormControl } from '@/components/ui/v2/FormControl';
|
||||
import PopperUnstyled from '@mui/base/PopperUnstyled';
|
||||
import type { SelectUnstyledProps } from '@mui/base/SelectUnstyled';
|
||||
import SelectUnstyled from '@mui/base/SelectUnstyled';
|
||||
import { styled } from '@mui/material';
|
||||
import { Popper as BasePopper } from '@mui/base/Popper';
|
||||
import type { SelectProps as BaseSelectProps } from '@mui/base/Select';
|
||||
import { Select as BaseSelect } from '@mui/base/Select';
|
||||
import { styled } from '@mui/system';
|
||||
import clsx from 'clsx';
|
||||
import type { ForwardedRef, PropsWithoutRef } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
@@ -11,7 +11,7 @@ import type { ToggleButtonProps } from './ToggleButton';
|
||||
import ToggleButton from './ToggleButton';
|
||||
|
||||
export interface SelectProps<TValue extends {}>
|
||||
extends SelectUnstyledProps<TValue>,
|
||||
extends BaseSelectProps<TValue, false>,
|
||||
Pick<
|
||||
FormControlProps,
|
||||
| 'fullWidth'
|
||||
@@ -25,7 +25,7 @@ export interface SelectProps<TValue extends {}>
|
||||
/**
|
||||
* Props for component slots.
|
||||
*/
|
||||
slotProps?: SelectUnstyledProps<TValue>['slotProps'] & {
|
||||
slotProps?: BaseSelectProps<TValue, false>['slotProps'] & {
|
||||
root?: Partial<PropsWithoutRef<ToggleButtonProps>>;
|
||||
label?: Partial<FormControlProps['labelProps']>;
|
||||
formControl?: Partial<FormControlProps>;
|
||||
@@ -59,8 +59,8 @@ const StyledListbox = styled('ul')(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledPopper = styled(PopperUnstyled)`
|
||||
z-index: 10;
|
||||
const StyledPopper = styled(BasePopper)`
|
||||
z-index: 9999;
|
||||
`;
|
||||
|
||||
function Select<TValue>(
|
||||
@@ -80,7 +80,7 @@ function Select<TValue>(
|
||||
}: SelectProps<TValue>,
|
||||
ref: ForwardedRef<HTMLButtonElement>,
|
||||
) {
|
||||
const slots: SelectUnstyledProps<TValue>['slots'] = {
|
||||
const slots: BaseSelectProps<TValue, false>['slots'] = {
|
||||
root: ToggleButton,
|
||||
popper: StyledPopper,
|
||||
listbox: StyledListbox,
|
||||
@@ -107,7 +107,7 @@ function Select<TValue>(
|
||||
htmlFor: props.id,
|
||||
}}
|
||||
>
|
||||
<SelectUnstyled
|
||||
<BaseSelect
|
||||
aria-label={typeof label === 'string' ? label : undefined}
|
||||
{...props}
|
||||
className={clsx(error && 'error')}
|
||||
@@ -117,7 +117,6 @@ function Select<TValue>(
|
||||
...slotProps,
|
||||
root: {
|
||||
...slotProps?.root,
|
||||
placeholder,
|
||||
},
|
||||
listbox: {
|
||||
...slotProps?.listbox,
|
||||
@@ -132,7 +131,7 @@ function Select<TValue>(
|
||||
placeholder={placeholder}
|
||||
>
|
||||
{children}
|
||||
</SelectUnstyled>
|
||||
</BaseSelect>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { ChevronDownIcon } from '@/components/ui/v2/icons/ChevronDownIcon';
|
||||
import { ChevronUpIcon } from '@/components/ui/v2/icons/ChevronUpIcon';
|
||||
import type { ButtonUnstyledProps } from '@mui/base/ButtonUnstyled';
|
||||
import ButtonUnstyled from '@mui/base/ButtonUnstyled';
|
||||
import { selectUnstyledClasses } from '@mui/base/SelectUnstyled';
|
||||
import {
|
||||
Button as ButtonUnstyled,
|
||||
type ButtonProps as ButtonUnstyledProps,
|
||||
} from '@mui/base';
|
||||
import { selectClasses as selectUnstyledClasses } from '@mui/base/Select';
|
||||
import type { SxProps } from '@mui/material';
|
||||
import { styled } from '@mui/material';
|
||||
import type { Theme } from '@mui/system';
|
||||
@@ -24,6 +26,7 @@ export interface ToggleButtonProps
|
||||
Omit<DetailedHTMLProps<HTMLProps<HTMLSpanElement>, HTMLSpanElement>, 'as'>
|
||||
>;
|
||||
};
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
const StyledButton = styled(ButtonUnstyled)(({ theme }) => ({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
import type { SwitchProps } from './Switch';
|
||||
import Switch from './Switch';
|
||||
|
||||
@@ -6,9 +6,9 @@ export default {
|
||||
title: 'UI Library / Switch',
|
||||
component: Switch,
|
||||
argTypes: {},
|
||||
} as ComponentMeta<typeof Switch>;
|
||||
} as Meta<typeof Switch>;
|
||||
|
||||
const Template: ComponentStory<typeof Switch> = function Template(
|
||||
const Template: StoryFn<SwitchProps> = function TemplateFunction(
|
||||
args: SwitchProps,
|
||||
) {
|
||||
return <Switch label="Accept Rules" {...args} />;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import type { FormControlLabelProps } from '@/components/ui/v2/FormControlLabel';
|
||||
import { FormControlLabel } from '@/components/ui/v2/FormControlLabel';
|
||||
import SwitchUnstyled, {
|
||||
switchUnstyledClasses,
|
||||
} from '@mui/base/SwitchUnstyled';
|
||||
import type { SwitchUnstyledProps } from '@mui/base/SwitchUnstyled/SwitchUnstyled.types';
|
||||
import {
|
||||
Switch as BaseSwitch,
|
||||
switchClasses as baseSwitchClasses,
|
||||
} from '@mui/base';
|
||||
import type { SwitchProps as BaseSwitchProps } from '@mui/base/Switch';
|
||||
import { styled } from '@mui/material';
|
||||
import type { ForwardedRef, PropsWithoutRef } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
export interface SwitchProps extends SwitchUnstyledProps {
|
||||
export interface SwitchProps extends BaseSwitchProps {
|
||||
/**
|
||||
* Label to be displayed next to the checkbox.
|
||||
*/
|
||||
@@ -16,11 +17,11 @@ export interface SwitchProps extends SwitchUnstyledProps {
|
||||
/**
|
||||
* Props to be passed to the internal components.
|
||||
*/
|
||||
slotProps?: SwitchUnstyledProps['slotProps'] & {
|
||||
slotProps?: BaseSwitchProps['slotProps'] & {
|
||||
/**
|
||||
* Props to be passed to the `Switch` component.
|
||||
*/
|
||||
root?: Partial<SwitchUnstyledProps>;
|
||||
root?: Partial<BaseSwitchProps>;
|
||||
/**
|
||||
* Props to be passed to the `FormControlLabel` component.
|
||||
*/
|
||||
@@ -35,23 +36,23 @@ const StyledFormControlLabel = styled(FormControlLabel)(({ theme }) => ({
|
||||
justifyContent: 'start',
|
||||
}));
|
||||
|
||||
const StyledSwitch = styled(SwitchUnstyled)(({ theme }) => ({
|
||||
const StyledSwitch = styled(BaseSwitch)(({ theme }) => ({
|
||||
position: 'relative',
|
||||
display: 'inline-block',
|
||||
width: '40px',
|
||||
height: '24px',
|
||||
cursor: 'pointer',
|
||||
|
||||
[`&.${switchUnstyledClasses.disabled}`]: {
|
||||
[`&.${baseSwitchClasses.disabled}`]: {
|
||||
cursor: 'not-allowed',
|
||||
|
||||
[`& .${switchUnstyledClasses.track}`]: {
|
||||
[`& .${baseSwitchClasses.track}`]: {
|
||||
backgroundColor: theme.palette.grey[200],
|
||||
color: theme.palette.grey[200],
|
||||
},
|
||||
},
|
||||
|
||||
[`& .${switchUnstyledClasses.track}`]: {
|
||||
[`& .${baseSwitchClasses.track}`]: {
|
||||
backgroundColor:
|
||||
theme.palette.mode === 'dark'
|
||||
? theme.palette.grey[500]
|
||||
@@ -63,7 +64,7 @@ const StyledSwitch = styled(SwitchUnstyled)(({ theme }) => ({
|
||||
position: 'absolute',
|
||||
},
|
||||
|
||||
[` & .${switchUnstyledClasses.thumb}`]: {
|
||||
[` & .${baseSwitchClasses.thumb}`]: {
|
||||
display: 'block',
|
||||
width: '18px',
|
||||
height: '18px',
|
||||
@@ -77,24 +78,24 @@ const StyledSwitch = styled(SwitchUnstyled)(({ theme }) => ({
|
||||
transitionDuration: '120ms',
|
||||
},
|
||||
|
||||
[`&.${switchUnstyledClasses.focusVisible} .${switchUnstyledClasses.thumb}`]: {
|
||||
[`&.${baseSwitchClasses.focusVisible} .${baseSwitchClasses.thumb}`]: {
|
||||
backgroundColor: theme.palette.action.focus,
|
||||
boxShadow: '0 0 1px 8px rgba(0, 0, 0, 0.25)',
|
||||
},
|
||||
|
||||
[`&.${switchUnstyledClasses.checked}`]: {
|
||||
[`.${switchUnstyledClasses.thumb}`]: {
|
||||
[`&.${baseSwitchClasses.checked}`]: {
|
||||
[`.${baseSwitchClasses.thumb}`]: {
|
||||
left: '19px',
|
||||
top: '3px',
|
||||
backgroundColor: theme.palette.common.white,
|
||||
},
|
||||
|
||||
[`.${switchUnstyledClasses.track}`]: {
|
||||
[`.${baseSwitchClasses.track}`]: {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
|
||||
[`&.${switchUnstyledClasses.disabled}`]: {
|
||||
[`.${switchUnstyledClasses.track}`]: {
|
||||
[`&.${baseSwitchClasses.disabled}`]: {
|
||||
[`.${baseSwitchClasses.track}`]: {
|
||||
opacity: 0.5,
|
||||
backgroundColor:
|
||||
theme.palette.mode === 'dark'
|
||||
@@ -104,7 +105,7 @@ const StyledSwitch = styled(SwitchUnstyled)(({ theme }) => ({
|
||||
},
|
||||
},
|
||||
|
||||
[`& .${switchUnstyledClasses.input}`]: {
|
||||
[`& .${baseSwitchClasses.input}`]: {
|
||||
cursor: 'inherit',
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
|
||||
@@ -4,16 +4,14 @@ import { Box } from '@/components/ui/v2/Box';
|
||||
import { Button } from '@/components/ui/v2/Button';
|
||||
import { Checkbox } from '@/components/ui/v2/Checkbox';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
useDeleteUserAccountMutation,
|
||||
useGetAllWorkspacesAndProjectsQuery,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { type ApolloError } from '@apollo/client';
|
||||
import { useSignOut, useUserData } from '@nhost/nextjs';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
function ConfirmDeleteAccountModal({
|
||||
@@ -44,30 +42,19 @@ function ConfirmDeleteAccountModal({
|
||||
const onClickConfirm = async () => {
|
||||
setLoadingRemove(true);
|
||||
|
||||
await toast.promise(
|
||||
deleteUserAccount(),
|
||||
{
|
||||
loading: 'Deleting your account...',
|
||||
success: `The account has been deleted successfully.`,
|
||||
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 your account. Please try again.'
|
||||
);
|
||||
},
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await deleteUserAccount();
|
||||
onDelete?.();
|
||||
close();
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Deleting your account...',
|
||||
successMessage: 'The account has been deleted successfully.',
|
||||
errorMessage:
|
||||
'An error occurred while deleting your account. Please try again.',
|
||||
},
|
||||
getToastStyleProps(),
|
||||
);
|
||||
|
||||
onDelete?.();
|
||||
close();
|
||||
};
|
||||
|
||||
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';
|
||||
@@ -15,15 +15,13 @@ import { ListItem } from '@/components/ui/v2/ListItem';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||
import { CreatePATForm } from '@/features/account/settings/components/CreatePATForm';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
GetPersonalAccessTokensDocument,
|
||||
useDeletePersonalAccessTokenMutation,
|
||||
useGetPersonalAccessTokensQuery,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { Fragment } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function PATSettings() {
|
||||
@@ -59,28 +57,20 @@ export default function PATSettings() {
|
||||
|
||||
async function handleDeletePAT({
|
||||
id,
|
||||
}: typeof availablePersonalAccessTokens[0]) {
|
||||
const deletePATPromise = deletePAT({ variables: { patId: id } });
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
deletePATPromise,
|
||||
{
|
||||
loading: 'Deleting 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.
|
||||
}
|
||||
}: (typeof availablePersonalAccessTokens)[0]) {
|
||||
await execPromiseWithErrorToast(
|
||||
() => deletePAT({ variables: { patId: id } }),
|
||||
{
|
||||
loadingMessage: 'Deleting personal access token...',
|
||||
successMessage: 'Personal access token has been deleted successfully.',
|
||||
errorMessage:
|
||||
'An error occurred while deleting the personal access token.',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function handleConfirmDelete(
|
||||
originalPAT: typeof availablePersonalAccessTokens[0],
|
||||
originalPAT: (typeof availablePersonalAccessTokens)[0],
|
||||
) {
|
||||
openAlertDialog({
|
||||
title: 'Delete Personal Access Token',
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useChangePassword } from '@nhost/nextjs';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
@@ -38,25 +36,19 @@ export default function PasswordSettings() {
|
||||
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
||||
|
||||
async function handleSubmit(formValues: PasswordSettingsFormValues) {
|
||||
try {
|
||||
const changePasswordPromise = changePassword(formValues.newPassword);
|
||||
|
||||
await toast.promise(
|
||||
changePasswordPromise,
|
||||
{
|
||||
loading: 'Changing password...',
|
||||
success: 'The password has been changed successfully.',
|
||||
error: getServerError(
|
||||
'An error occurred while trying to update the password. Please try again.',
|
||||
),
|
||||
},
|
||||
getToastStyleProps(),
|
||||
);
|
||||
|
||||
form.reset();
|
||||
} catch {
|
||||
// Note: The error is handled by the toast.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
// TODO fix changePassword should throw an error if something happens
|
||||
await changePassword(formValues.newPassword);
|
||||
form.reset();
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Changing password...',
|
||||
successMessage: 'The password has been changed successfully.',
|
||||
errorMessage:
|
||||
'An error occurred while trying to update the password. Please try again.',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -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 { GraphqlDataSourcesFormSection } from '@/features/ai/AssistantForm/components/GraphqlDataSourcesFormSection';
|
||||
import { WebhooksDataSourcesFormSection } from '@/features/ai/AssistantForm/components/WebhooksDataSourcesFormSection';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
|
||||
import type { DialogFormProps } from '@/types/common';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getHasuraAdminSecret } from '@/utils/env';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { removeTypename, type DeepRequired } from '@/utils/helpers';
|
||||
import {
|
||||
useInsertAssistantMutation,
|
||||
useUpdateAssistantMutation,
|
||||
} from '@/utils/__generated__/graphite.graphql';
|
||||
import {
|
||||
ApolloClient,
|
||||
HttpLink,
|
||||
InMemoryCache,
|
||||
type ApolloError,
|
||||
} from '@apollo/client';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
export const validationSchema = Yup.object({
|
||||
@@ -101,32 +92,15 @@ export default function AssistantForm({
|
||||
}: AssistantFormProps) {
|
||||
const { onDirtyStateChange } = useDialog();
|
||||
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
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 { adminClient } = useAdminApolloClient();
|
||||
|
||||
const [insertAssistantMutation] = useInsertAssistantMutation({
|
||||
client,
|
||||
client: adminClient,
|
||||
});
|
||||
|
||||
const [updateAssistantMutation] = useUpdateAssistantMutation({ client });
|
||||
const [updateAssistantMutation] = useUpdateAssistantMutation({
|
||||
client: adminClient,
|
||||
});
|
||||
|
||||
const form = useForm<AssistantFormValues>({
|
||||
defaultValues: initialData,
|
||||
@@ -186,33 +160,18 @@ export default function AssistantForm({
|
||||
const handleSubmit = async (
|
||||
values: DeepRequired<AssistantFormValues> & { assistantID: string },
|
||||
) => {
|
||||
try {
|
||||
await toast.promise(
|
||||
createOrUpdateAutoEmbeddings(values),
|
||||
{
|
||||
loading: 'Configuring the Assistant...',
|
||||
success: `The Assistant has been configured successfully.`,
|
||||
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 configuring the Assistant. Please try again.'
|
||||
);
|
||||
},
|
||||
},
|
||||
getToastStyleProps(),
|
||||
);
|
||||
|
||||
onSubmit?.();
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await createOrUpdateAutoEmbeddings(values);
|
||||
onSubmit?.();
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Configuring the Assistant...',
|
||||
successMessage: 'The Assistant has been configured successfully.',
|
||||
errorMessage:
|
||||
'An error occurred while configuring the Assistant. Please try again.',
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -8,25 +8,16 @@ import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
|
||||
import type { DialogFormProps } from '@/types/common';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getHasuraAdminSecret } from '@/utils/env';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
useInsertGraphiteAutoEmbeddingsConfigurationMutation,
|
||||
useUpdateGraphiteAutoEmbeddingsConfigurationMutation,
|
||||
} from '@/utils/__generated__/graphite.graphql';
|
||||
import {
|
||||
ApolloClient,
|
||||
HttpLink,
|
||||
InMemoryCache,
|
||||
type ApolloError,
|
||||
} from '@apollo/client';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
export const validationSchema = Yup.object({
|
||||
@@ -70,34 +61,17 @@ export default function AutoEmbeddingsForm({
|
||||
}: AutoEmbeddingsFormProps) {
|
||||
const { onDirtyStateChange } = useDialog();
|
||||
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
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 { adminClient } = useAdminApolloClient();
|
||||
|
||||
const [insertGraphiteAutoEmbeddingsConfiguration] =
|
||||
useInsertGraphiteAutoEmbeddingsConfigurationMutation({
|
||||
client,
|
||||
client: adminClient,
|
||||
});
|
||||
|
||||
const [updateGraphiteAutoEmbeddingsConfiguration] =
|
||||
useUpdateGraphiteAutoEmbeddingsConfigurationMutation({ client });
|
||||
useUpdateGraphiteAutoEmbeddingsConfigurationMutation({
|
||||
client: adminClient,
|
||||
});
|
||||
|
||||
const form = useForm<AutoEmbeddingsFormValues>({
|
||||
defaultValues: initialData,
|
||||
@@ -137,33 +111,18 @@ export default function AutoEmbeddingsForm({
|
||||
};
|
||||
|
||||
const handleSubmit = async (values: AutoEmbeddingsFormValues) => {
|
||||
try {
|
||||
await toast.promise(
|
||||
createOrUpdateAutoEmbeddings(values),
|
||||
{
|
||||
loading: 'Configuring the Auto-Embeddings...',
|
||||
success: `The Auto-Embeddings has been configured successfully.`,
|
||||
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 configuring the Auto-Embeddings. Please try again.'
|
||||
);
|
||||
},
|
||||
},
|
||||
getToastStyleProps(),
|
||||
);
|
||||
|
||||
onSubmit?.();
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await createOrUpdateAutoEmbeddings(values);
|
||||
onSubmit?.();
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Configuring the Auto-Embeddings...',
|
||||
successMessage: 'The Auto-Embeddings has been configured successfully.',
|
||||
errorMessage:
|
||||
'An error occurred while configuring the Auto-Embeddings. Please try again.',
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,20 +2,11 @@ import { Box } from '@/components/ui/v2/Box';
|
||||
import { Button } from '@/components/ui/v2/Button';
|
||||
import { Checkbox } from '@/components/ui/v2/Checkbox';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getHasuraAdminSecret } from '@/utils/env';
|
||||
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
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 { useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export interface DeleteAssistantModalProps {
|
||||
@@ -32,29 +23,10 @@ export default function DeleteAssistantModal({
|
||||
const [remove, setRemove] = useState(false);
|
||||
const [loadingRemove, setLoadingRemove] = useState(false);
|
||||
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
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 { adminClient } = useAdminApolloClient();
|
||||
|
||||
const [deleteAssistantMutation] = useDeleteAssistantMutation({
|
||||
client,
|
||||
client: adminClient,
|
||||
});
|
||||
|
||||
const deleteAssistant = async () => {
|
||||
@@ -70,27 +42,12 @@ export default function DeleteAssistantModal({
|
||||
async function handleClick() {
|
||||
setLoadingRemove(true);
|
||||
|
||||
await toast.promise(
|
||||
deleteAssistant(),
|
||||
{
|
||||
loading: 'Deleting the assistant...',
|
||||
success: `The Assistant has been deleted successfully.`,
|
||||
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(),
|
||||
);
|
||||
await execPromiseWithErrorToast(deleteAssistant, {
|
||||
loadingMessage: 'Deleting the assistant...',
|
||||
successMessage: 'The Assistant has been deleted successfully.',
|
||||
errorMessage:
|
||||
'An error occurred while deleting the Assistant. Please try again.',
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,18 +4,12 @@ import { Checkbox } from '@/components/ui/v2/Checkbox';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getHasuraAdminSecret } from '@/utils/env';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { useDeleteGraphiteAutoEmbeddingsConfigurationMutation } from '@/utils/__generated__/graphite.graphql';
|
||||
import {
|
||||
ApolloClient,
|
||||
HttpLink,
|
||||
InMemoryCache,
|
||||
type ApolloError,
|
||||
} from '@apollo/client';
|
||||
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
|
||||
import { type AutoEmbeddingsConfiguration } from 'pages/[workspaceSlug]/[appSlug]/ai/auto-embeddings';
|
||||
import { useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export interface DeleteAutoEmbeddingsModalProps {
|
||||
@@ -71,27 +65,13 @@ export default function DeleteAutoEmbeddingsModal({
|
||||
async function handleClick() {
|
||||
setLoadingRemove(true);
|
||||
|
||||
await toast.promise(
|
||||
deleteAutoEmbeddingsConfig(),
|
||||
{
|
||||
loading: 'Deleting Auto-Embeddings Configuration...',
|
||||
success: `The Auto-Embeddings Configuration has been deleted successfully.`,
|
||||
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 Auto-Embeddings Configuration. Please try again.'
|
||||
);
|
||||
},
|
||||
},
|
||||
getToastStyleProps(),
|
||||
);
|
||||
await execPromiseWithErrorToast(deleteAutoEmbeddingsConfig, {
|
||||
loadingMessage: 'Deleting Auto-Embeddings Configuration...',
|
||||
successMessage:
|
||||
'The Auto-Embeddings Configuration has been deleted successfully.',
|
||||
errorMessage:
|
||||
'An error occurred while deleting the Auto-Embeddings Configuration. Please try again.',
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,6 +2,7 @@ import { UpgradeToProBanner } from '@/components/common/UpgradeToProBanner';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Alert } from '@/components/ui/v2/Alert';
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { ErrorToast } from '@/components/ui/v2/ErrorToast';
|
||||
import { IconButton } from '@/components/ui/v2/IconButton';
|
||||
import { ArrowUpIcon } from '@/components/ui/v2/icons/ArrowUpIcon';
|
||||
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 { useIsGraphiteEnabled } from '@/features/projects/common/hooks/useIsGraphiteEnabled';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import {
|
||||
useSendDevMessageMutation,
|
||||
useStartDevSessionMutation,
|
||||
@@ -122,11 +122,17 @@ export default function DevAssistant() {
|
||||
|
||||
setMessages(thread);
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
'Failed to send the message to graphite. Please try again later.',
|
||||
toast.custom(
|
||||
(t) => (
|
||||
<ErrorToast
|
||||
isVisible={t.visible}
|
||||
errorMessage="Failed to send the message. Please try again later."
|
||||
error={error}
|
||||
close={() => toast.dismiss()}
|
||||
/>
|
||||
),
|
||||
{
|
||||
style: getToastStyleProps().style,
|
||||
...getToastStyleProps().error,
|
||||
duration: Number.POSITIVE_INFINITY,
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
|
||||
@@ -24,7 +24,7 @@ function PreComponent(
|
||||
const { children } = props;
|
||||
|
||||
return (
|
||||
<div className="group relative">
|
||||
<div className="relative group">
|
||||
<pre>{children}</pre>
|
||||
<IconButton
|
||||
sx={{
|
||||
@@ -34,13 +34,13 @@ function PreComponent(
|
||||
}}
|
||||
color="warning"
|
||||
variant="contained"
|
||||
className="absolute top-2 right-2 hidden group-hover:flex"
|
||||
className="absolute hidden top-2 right-2 group-hover:flex"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
copy(onlyText(children), 'Snippet');
|
||||
}}
|
||||
>
|
||||
<CopyIcon className="h-5 w-5" />
|
||||
<CopyIcon className="w-5 h-5" />
|
||||
</IconButton>
|
||||
</div>
|
||||
);
|
||||
@@ -53,7 +53,7 @@ export default function MessageBox({ message }: { message: Message }) {
|
||||
|
||||
return (
|
||||
<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={{
|
||||
backgroundColor: isUserMessage && 'background.default',
|
||||
}}
|
||||
@@ -67,7 +67,7 @@ export default function MessageBox({ message }: { message: Message }) {
|
||||
) : (
|
||||
<>
|
||||
<Avatar
|
||||
className="h-7 w-7 rounded-full"
|
||||
className="rounded-full h-7 w-7"
|
||||
alt={user?.displayName}
|
||||
src={user?.avatarUrl}
|
||||
>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
messagesState,
|
||||
import messagesState, {
|
||||
type ProjectMessage,
|
||||
} from '@/features/ai/DevAssistant/state';
|
||||
} from '@/features/ai/DevAssistant/state/messages';
|
||||
import { selectorFamily } from 'recoil';
|
||||
|
||||
const projectMessagesState = selectorFamily<ProjectMessage[], string>({
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
} from '@/generated/graphql';
|
||||
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
@@ -30,6 +30,8 @@ import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
import { DisableAIServiceConfirmationDialog } from './DisableAIServiceConfirmationDialog';
|
||||
|
||||
const MIN_POSTGRES_VERSION_SUPPORTING_AI = '14.6-20231018-1';
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
version: Yup.object({
|
||||
label: Yup.string().required(),
|
||||
@@ -56,7 +58,9 @@ export default function AISettings() {
|
||||
const [aiServiceEnabled, setAIServiceEnabled] = useState(true);
|
||||
|
||||
const {
|
||||
data: { config: { ai } = {} } = {},
|
||||
data: {
|
||||
config: { ai, postgres: { version: postgresVersion } = {} } = {},
|
||||
} = {},
|
||||
loading: loadingAiSettings,
|
||||
error: errorGettingAiSettings,
|
||||
} = useGetAiSettingsQuery({
|
||||
@@ -152,6 +156,17 @@ export default function AISettings() {
|
||||
]);
|
||||
|
||||
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);
|
||||
|
||||
if (!enabled && ai) {
|
||||
@@ -182,9 +197,9 @@ export default function AISettings() {
|
||||
}
|
||||
|
||||
async function handleSubmit(formValues: AISettingsFormValues) {
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfig({
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfig({
|
||||
variables: {
|
||||
appId: currentProject.id,
|
||||
config: {
|
||||
@@ -207,21 +222,17 @@ 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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
form.reset(formValues);
|
||||
},
|
||||
{
|
||||
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 = () => {
|
||||
|
||||
@@ -3,11 +3,9 @@ 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 { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { useUpdateConfigMutation } from '@/utils/__generated__/graphql';
|
||||
import type { ApolloError } from '@apollo/client';
|
||||
import { useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export interface DisableAIServiceConfirmationDialogProps {
|
||||
@@ -34,37 +32,26 @@ export default function DisableAIServiceConfirmationDialog({
|
||||
async function handleClick() {
|
||||
setLoading(true);
|
||||
|
||||
await toast.promise(
|
||||
updateConfig({
|
||||
variables: {
|
||||
appId: currentProject.id,
|
||||
config: {
|
||||
ai: null,
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfig({
|
||||
variables: {
|
||||
appId: currentProject.id,
|
||||
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
|
||||
// internal error message
|
||||
return (
|
||||
message ||
|
||||
arg.message ||
|
||||
'An error occurred while disabling the AI service. Please try again later.'
|
||||
);
|
||||
},
|
||||
onServiceDisabled();
|
||||
closeDialog();
|
||||
},
|
||||
{
|
||||
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!) {
|
||||
config(appID: $appId, resolve: false) {
|
||||
postgres {
|
||||
version
|
||||
}
|
||||
ai {
|
||||
version
|
||||
webhookSecret
|
||||
|
||||
@@ -9,11 +9,9 @@ import {
|
||||
useGetAuthenticationSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -103,21 +101,19 @@ export default function AllowedEmailDomainsSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
loading: `Allowed email settings are being updated...`,
|
||||
success: `Allowed email settings have been updated successfully.`,
|
||||
error: getServerError(
|
||||
`An error occurred while trying to update the project's allowed email settings.`,
|
||||
),
|
||||
},
|
||||
getToastStyleProps(),
|
||||
);
|
||||
} catch {
|
||||
// Note: The toast will handle the error
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
},
|
||||
{
|
||||
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);
|
||||
};
|
||||
@@ -134,7 +130,7 @@ export default function AllowedEmailDomainsSettings() {
|
||||
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"
|
||||
showSwitch
|
||||
className={twMerge(
|
||||
|
||||
@@ -9,11 +9,9 @@ import {
|
||||
useGetAuthenticationSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
@@ -80,23 +78,19 @@ export default function AllowedRedirectURLsSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -111,7 +105,7 @@ export default function AllowedRedirectURLsSettings() {
|
||||
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"
|
||||
>
|
||||
<Input
|
||||
|
||||
@@ -8,11 +8,9 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
@@ -73,23 +71,19 @@ export default function AnonymousSignInSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
|
||||
@@ -13,13 +13,11 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useTheme } from '@mui/material';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -117,23 +115,18 @@ export default function AppleProviderSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -148,7 +141,7 @@ export default function AppleProviderSettings() {
|
||||
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"
|
||||
icon={
|
||||
theme.palette.mode === 'dark'
|
||||
@@ -158,7 +151,7 @@ export default function AppleProviderSettings() {
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -11,11 +11,9 @@ import {
|
||||
useGetSoftwareVersionsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
@@ -95,23 +93,17 @@ export default function AuthServiceVersionSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Auth version is being updated...',
|
||||
successMessage: 'Auth version has been updated successfully.',
|
||||
errorMessage: 'An error occurred while trying to update Auth version.',
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -128,7 +120,7 @@ export default function AuthServiceVersionSettings() {
|
||||
}}
|
||||
docsLink="https://github.com/nhost/hasura-auth/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 gap-x-4 gap-y-2 px-4 lg:grid-cols-5"
|
||||
>
|
||||
<ControlledAutocomplete
|
||||
id="version"
|
||||
|
||||
@@ -14,12 +14,10 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -106,23 +104,18 @@ export default function AzureADProviderSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -141,7 +134,7 @@ export default function AzureADProviderSettings() {
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -18,12 +18,10 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function BitbucketProviderSettings() {
|
||||
@@ -84,23 +82,18 @@ export default function BitbucketProviderSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
loading: `Bitbucket settings are being updated...`,
|
||||
success: `Bitbucket settings have been updated successfully.`,
|
||||
error: getServerError(
|
||||
`An error occurred while trying to update the project's Bitbucket settings.`,
|
||||
),
|
||||
},
|
||||
getToastStyleProps(),
|
||||
);
|
||||
|
||||
form.reset(formValues);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Bitbucket settings are being updated...',
|
||||
successMessage: 'Bitbucket settings have been updated successfully.',
|
||||
errorMessage:
|
||||
"An error occurred while trying to update the project's Bitbucket settings.",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -119,7 +112,7 @@ export default function BitbucketProviderSettings() {
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -9,11 +9,9 @@ import {
|
||||
useGetAuthenticationSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -110,23 +108,20 @@ export default function BlockedEmailSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -141,7 +136,7 @@ export default function BlockedEmailSettings() {
|
||||
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"
|
||||
showSwitch
|
||||
className={twMerge(
|
||||
|
||||
@@ -9,11 +9,9 @@ import {
|
||||
useGetAuthenticationSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
@@ -75,23 +73,18 @@ export default function ClientURLSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -106,7 +99,7 @@ export default function ClientURLSettings() {
|
||||
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"
|
||||
>
|
||||
<Input
|
||||
|
||||
@@ -3,15 +3,13 @@ import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
GetAuthenticationSettingsDocument,
|
||||
useGetAuthenticationSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
@@ -71,23 +69,18 @@ export default function DisableNewUsersSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -96,7 +89,7 @@ export default function DisableNewUsersSettings() {
|
||||
<SettingsContainer
|
||||
title="Disable New Users"
|
||||
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"
|
||||
showSwitch
|
||||
slotProps={{
|
||||
|
||||
@@ -18,12 +18,10 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function DiscordProviderSettings() {
|
||||
@@ -87,23 +85,18 @@ export default function DiscordProviderSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -118,13 +111,13 @@ export default function DiscordProviderSettings() {
|
||||
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"
|
||||
icon="/assets/brands/discord.svg"
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -11,11 +11,9 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
@@ -85,23 +83,19 @@ export default function EmailAndPasswordSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -110,7 +104,7 @@ export default function EmailAndPasswordSettings() {
|
||||
<SettingsContainer
|
||||
title="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"
|
||||
className="grid grid-flow-row"
|
||||
showSwitch
|
||||
|
||||
@@ -18,12 +18,10 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function FacebookProviderSettings() {
|
||||
@@ -87,23 +85,18 @@ export default function FacebookProviderSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -118,13 +111,13 @@ export default function FacebookProviderSettings() {
|
||||
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"
|
||||
icon="/assets/brands/facebook.svg"
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -18,13 +18,11 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useTheme } from '@mui/material';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function GitHubProviderSettings() {
|
||||
@@ -89,23 +87,18 @@ export default function GitHubProviderSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -120,7 +113,7 @@ export default function GitHubProviderSettings() {
|
||||
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"
|
||||
icon={
|
||||
theme.palette.mode === 'dark'
|
||||
@@ -130,7 +123,7 @@ export default function GitHubProviderSettings() {
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -18,12 +18,10 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function GitLabProviderSettings() {
|
||||
@@ -87,23 +85,18 @@ export default function GitLabProviderSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
loading: `GitLab settings are being updated...`,
|
||||
success: `GitLab settings have been updated successfully.`,
|
||||
error: getServerError(
|
||||
`An error occurred while trying to update the project's GitLab settings.`,
|
||||
),
|
||||
},
|
||||
getToastStyleProps(),
|
||||
);
|
||||
|
||||
form.reset(values);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
},
|
||||
{
|
||||
loadingMessage: 'GitLab settings are being updated...',
|
||||
successMessage: 'GitLab settings have been updated successfully.',
|
||||
errorMessage:
|
||||
"An error occurred while trying to update the project's GitLab settings.",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -122,7 +115,7 @@ export default function GitLabProviderSettings() {
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -18,12 +18,10 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function GoogleProviderSettings() {
|
||||
@@ -87,23 +85,18 @@ export default function GoogleProviderSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -118,13 +111,13 @@ export default function GoogleProviderSettings() {
|
||||
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"
|
||||
icon="/assets/brands/google.svg"
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -13,12 +13,10 @@ import {
|
||||
import {
|
||||
AUTH_GRAVATAR_DEFAULT,
|
||||
AUTH_GRAVATAR_RATING,
|
||||
getToastStyleProps,
|
||||
} from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -89,23 +87,18 @@ export default function GravatarSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -120,7 +113,7 @@ export default function GravatarSettings() {
|
||||
loading: formState.isSubmitting,
|
||||
},
|
||||
}}
|
||||
docsLink="https://docs.nhost.io/authentication#gravatar"
|
||||
docsLink="https://docs.nhost.io/guides/auth/overview#gravatar"
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
className={twMerge(
|
||||
|
||||
@@ -18,12 +18,10 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function LinkedInProviderSettings() {
|
||||
@@ -87,23 +85,18 @@ export default function LinkedInProviderSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -118,13 +111,13 @@ export default function LinkedInProviderSettings() {
|
||||
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"
|
||||
icon="/assets/brands/linkedin.svg"
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -9,11 +9,9 @@ import {
|
||||
useGetAuthenticationSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -76,23 +74,20 @@ export default function MFASettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -107,7 +102,7 @@ export default function MFASettings() {
|
||||
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"
|
||||
showSwitch
|
||||
className={twMerge(
|
||||
|
||||
@@ -8,11 +8,9 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
@@ -73,23 +71,18 @@ export default function MagicLinkSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -104,7 +97,7 @@ export default function MagicLinkSettings() {
|
||||
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"
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
|
||||
@@ -12,12 +12,10 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import Image from 'next/image';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -112,23 +110,17 @@ export default function SMSSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
},
|
||||
{
|
||||
loadingMessage: 'SMS settings are being updated...',
|
||||
successMessage: 'SMS settings have been updated successfully.',
|
||||
errorMessage: 'An error occurred while trying to update SMS settings.',
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -145,10 +137,10 @@ export default function SMSSettings() {
|
||||
}}
|
||||
switchId="enabled"
|
||||
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)"
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -9,11 +9,9 @@ import {
|
||||
useGetAuthenticationSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
@@ -84,23 +82,18 @@ export default function SessionSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
|
||||
@@ -18,12 +18,10 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function SpotifyProviderSettings() {
|
||||
@@ -87,23 +85,18 @@ export default function SpotifyProviderSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -118,13 +111,13 @@ export default function SpotifyProviderSettings() {
|
||||
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"
|
||||
icon="/assets/brands/spotify.svg"
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -18,12 +18,10 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function StravaProviderSettings() {
|
||||
@@ -87,23 +85,18 @@ export default function StravaProviderSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
loading: `Strava settings are being updated...`,
|
||||
success: `Strava settings have been updated successfully.`,
|
||||
error: getServerError(
|
||||
`An error occurred while trying to update the project's Strava settings.`,
|
||||
),
|
||||
},
|
||||
getToastStyleProps(),
|
||||
);
|
||||
|
||||
form.reset(values);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Strava settings are being updated...',
|
||||
successMessage: 'Strava settings have been updated successfully.',
|
||||
errorMessage:
|
||||
"An error occurred while trying to update the project's Strava settings.",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -122,7 +115,7 @@ export default function StravaProviderSettings() {
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -18,13 +18,11 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useTheme } from '@mui/material';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function TwitchProviderSettings() {
|
||||
@@ -89,23 +87,18 @@ export default function TwitchProviderSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -120,7 +113,7 @@ export default function TwitchProviderSettings() {
|
||||
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"
|
||||
icon={
|
||||
theme.palette.mode === 'dark'
|
||||
@@ -130,7 +123,7 @@ export default function TwitchProviderSettings() {
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -13,12 +13,10 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -98,23 +96,17 @@ export default function TwitterProviderSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -134,7 +126,7 @@ export default function TwitterProviderSettings() {
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -8,11 +8,9 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
@@ -66,33 +64,29 @@ export default function WebAuthnSettings() {
|
||||
config: {
|
||||
auth: {
|
||||
method: {
|
||||
webauthn: {...values,
|
||||
relyingParty: {
|
||||
name: currentProject.name,
|
||||
}},
|
||||
webauthn: {
|
||||
...values,
|
||||
relyingParty: {
|
||||
name: currentProject.name,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -107,7 +101,7 @@ export default function WebAuthnSettings() {
|
||||
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"
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
|
||||
@@ -18,12 +18,10 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function WindowsLiveProviderSettings() {
|
||||
@@ -87,23 +85,17 @@ export default function WindowsLiveProviderSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -123,7 +115,7 @@ export default function WindowsLiveProviderSettings() {
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -14,12 +14,10 @@ import {
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -113,23 +111,18 @@ export default function WorkOsProviderSettings() {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateConfigPromise,
|
||||
{
|
||||
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);
|
||||
} catch {
|
||||
// Note: The toast will handle the error.
|
||||
}
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
},
|
||||
{
|
||||
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 (
|
||||
@@ -144,13 +137,13 @@ export default function WorkOsProviderSettings() {
|
||||
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"
|
||||
icon="/assets/brands/workos.svg"
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -6,12 +6,10 @@ import { Input } from '@/components/ui/v2/Input';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||
import type { DialogFormProps } from '@/types/common';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
export interface CreateUserFormProps extends DialogFormProps {
|
||||
@@ -77,9 +75,9 @@ export default function CreateUserForm({
|
||||
async function handleCreateUser({ email, password }: CreateUserFormValues) {
|
||||
setCreateUserFormError(null);
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
fetch(signUpUrl, {
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await fetch(signUpUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password }),
|
||||
@@ -95,21 +93,16 @@ export default function CreateUserForm({
|
||||
}
|
||||
|
||||
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?.();
|
||||
} catch (error) {
|
||||
// Note: The error is already handled by the toast promise.
|
||||
}
|
||||
onSubmit?.();
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Creating user...',
|
||||
successMessage: 'User has been created successfully.',
|
||||
errorMessage: 'An error occurred while trying to create the user.',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -19,9 +19,8 @@ import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/
|
||||
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
|
||||
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
||||
import type { DialogFormProps } from '@/types/common';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
RemoteAppGetUsersDocument,
|
||||
useGetProjectLocalesQuery,
|
||||
@@ -36,7 +35,6 @@ import Image from 'next/image';
|
||||
import type { RemoteAppUser } from 'pages/[workspaceSlug]/[appSlug]/users';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
export interface EditUserFormProps extends DialogFormProps {
|
||||
@@ -173,21 +171,15 @@ export default function EditUserForm({
|
||||
},
|
||||
});
|
||||
|
||||
await toast.promise(
|
||||
banUser,
|
||||
{
|
||||
loading: shouldBan ? 'Banning user...' : 'Unbanning user...',
|
||||
success: shouldBan
|
||||
? 'User has been banned successfully.'
|
||||
: 'User has been unbanned successfully.',
|
||||
error: getServerError(
|
||||
shouldBan
|
||||
? 'An error occurred while trying to ban the user.'
|
||||
: 'An error occurred while trying to unban the user.',
|
||||
),
|
||||
},
|
||||
getToastStyleProps(),
|
||||
);
|
||||
await execPromiseWithErrorToast(() => banUser, {
|
||||
loadingMessage: shouldBan ? 'Banning user...' : 'Unbanning user...',
|
||||
successMessage: shouldBan
|
||||
? 'User has been banned successfully.'
|
||||
: 'User has been unbanned successfully.',
|
||||
errorMessage: shouldBan
|
||||
? 'An error occurred while trying to ban the user.'
|
||||
: 'An error occurred while trying to unban the user.',
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -424,6 +416,7 @@ export default function EditUserForm({
|
||||
}
|
||||
width={25}
|
||||
height={25}
|
||||
alt="Oauth provider logo"
|
||||
/>
|
||||
<Text className="font-medium capitalize">
|
||||
{getReadableProviderName(provider.providerId)}
|
||||
|
||||
@@ -6,8 +6,7 @@ import { Input } from '@/components/ui/v2/Input';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
||||
import type { DialogFormProps } from '@/types/common';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import type { RemoteAppGetUsersQuery } from '@/utils/__generated__/graphql';
|
||||
import {
|
||||
useGetSignInMethodsQuery,
|
||||
@@ -17,7 +16,6 @@ import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
export interface EditUserPasswordFormProps extends DialogFormProps {
|
||||
@@ -90,23 +88,23 @@ export default function EditUserPasswordForm({
|
||||
client: remoteProjectGQLClient,
|
||||
});
|
||||
|
||||
try {
|
||||
await toast.promise(
|
||||
updateUserPasswordPromise,
|
||||
{
|
||||
loading: 'Updating user password...',
|
||||
success: 'User password updated successfully.',
|
||||
error: getServerError('Failed to update user password.'),
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateUserPasswordPromise;
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Updating 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(
|
||||
new Error(error.message || 'Something went wrong.'),
|
||||
);
|
||||
} finally {
|
||||
closeDialog();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const {
|
||||
|
||||
@@ -17,8 +17,7 @@ import { getReadableProviderName } from '@/features/authentication/users/utils/g
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
|
||||
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { getServerError } from '@/utils/getServerError';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
useDeleteRemoteAppUserRolesMutation,
|
||||
useGetRolesPermissionsQuery,
|
||||
@@ -33,7 +32,6 @@ import dynamic from 'next/dynamic';
|
||||
import Image from 'next/image';
|
||||
import type { RemoteAppUser } from 'pages/[workspaceSlug]/[appSlug]/users';
|
||||
import { Fragment, useMemo } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
const EditUserForm = dynamic(
|
||||
() =>
|
||||
@@ -153,20 +151,18 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
||||
});
|
||||
}
|
||||
|
||||
await toast.promise(
|
||||
updateUserMutationPromise,
|
||||
{
|
||||
loading: `Updating user's settings...`,
|
||||
success: 'User settings have been updated successfully.',
|
||||
error: getServerError(
|
||||
`An error occurred while trying to update this user's settings.`,
|
||||
),
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateUserMutationPromise;
|
||||
},
|
||||
{
|
||||
loadingMessage: `Updating 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?.();
|
||||
|
||||
closeDrawer();
|
||||
}
|
||||
|
||||
@@ -181,20 +177,20 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
||||
),
|
||||
props: {
|
||||
onPrimaryAction: async () => {
|
||||
await toast.promise(
|
||||
deleteUser({
|
||||
variables: {
|
||||
id: user.id,
|
||||
},
|
||||
}),
|
||||
{
|
||||
loading: 'Deleting user...',
|
||||
success: 'User deleted successfully.',
|
||||
error: getServerError(
|
||||
'An error occurred while trying to delete this user.',
|
||||
),
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await deleteUser({
|
||||
variables: {
|
||||
id: user.id,
|
||||
},
|
||||
});
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Deleting user...',
|
||||
successMessage: 'User deleted successfully.',
|
||||
errorMessage:
|
||||
'An error occurred while trying to delete this user.',
|
||||
},
|
||||
getToastStyleProps(),
|
||||
);
|
||||
|
||||
await onSubmit();
|
||||
@@ -227,7 +223,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
||||
if (!users) {
|
||||
return (
|
||||
<div className="h-screen w-screen overflow-hidden">
|
||||
<div className="absolute top-0 left-0 z-50 block h-full w-full">
|
||||
<div className="absolute left-0 top-0 z-50 block h-full w-full">
|
||||
<span className="top50percent relative top-1/2 mx-auto my-0 block">
|
||||
<ActivityIndicator
|
||||
label="Loading users..."
|
||||
@@ -362,6 +358,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
||||
}
|
||||
width={16}
|
||||
height={16}
|
||||
alt="Oauth provider logo"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -102,10 +102,10 @@ export default function BaseColumnForm({
|
||||
return (
|
||||
<Form
|
||||
onSubmit={handleExternalSubmit}
|
||||
className="flex flex-auto flex-col content-between overflow-hidden border-t-1"
|
||||
className="flex flex-col content-between flex-auto overflow-hidden border-t-1"
|
||||
>
|
||||
<div className="flex-auto overflow-y-auto">
|
||||
<section className="grid grid-cols-8 py-3 px-6">
|
||||
<section className="grid grid-cols-8 px-6 py-3">
|
||||
<Input
|
||||
{...register('name', {
|
||||
onChange: (event) => {
|
||||
@@ -184,7 +184,7 @@ export default function BaseColumnForm({
|
||||
</Text>
|
||||
</span>
|
||||
}
|
||||
className="col-span-8 m-0 w-full py-3 sm:col-span-6 sm:col-start-3 sm:ml-1"
|
||||
className="w-full col-span-8 py-3 m-0 sm:col-span-6 sm:col-start-3 sm:ml-1"
|
||||
onChange={(_event, checked) => {
|
||||
if (checked) {
|
||||
setDefaultValueInputText('');
|
||||
@@ -197,7 +197,7 @@ export default function BaseColumnForm({
|
||||
|
||||
<Box
|
||||
component="section"
|
||||
className="grid grid-cols-8 border-t-1 py-3 px-6"
|
||||
className="grid grid-cols-8 px-6 py-3 border-t-1"
|
||||
>
|
||||
<ControlledAutocomplete
|
||||
id="defaultValue"
|
||||
@@ -249,7 +249,7 @@ export default function BaseColumnForm({
|
||||
/>
|
||||
|
||||
<ControlledCheckbox
|
||||
className="col-span-8 m-0 w-full py-3 sm:col-span-6 sm:col-start-3 sm:ml-1"
|
||||
className="w-full col-span-8 py-3 m-0 sm:col-span-6 sm:col-start-3 sm:ml-1"
|
||||
name="isNullable"
|
||||
label={
|
||||
<span className="inline-grid grid-flow-row">
|
||||
@@ -269,7 +269,7 @@ export default function BaseColumnForm({
|
||||
/>
|
||||
|
||||
<ControlledCheckbox
|
||||
className="col-span-8 m-0 w-full py-3 sm:col-span-6 sm:col-start-3 sm:ml-1"
|
||||
className="w-full col-span-8 py-3 m-0 sm:col-span-6 sm:col-start-3 sm:ml-1"
|
||||
name="isUnique"
|
||||
label={
|
||||
<span className="inline-grid grid-flow-row">
|
||||
@@ -306,7 +306,7 @@ export default function BaseColumnForm({
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
<Box className="grid flex-shrink-0 grid-flow-col justify-between gap-3 border-t-1 p-2">
|
||||
<Box className="grid justify-between flex-shrink-0 grid-flow-col gap-3 p-2 border-t-1">
|
||||
<Button
|
||||
variant="borderless"
|
||||
color="secondary"
|
||||
|
||||
@@ -107,9 +107,9 @@ export default function BaseForeignKeyForm({
|
||||
selectedColumn?.isPrimary || selectedColumn?.isUnique || false,
|
||||
});
|
||||
}}
|
||||
className="flex flex-auto flex-col content-between overflow-hidden pb-4"
|
||||
className="flex flex-col content-between flex-auto pb-4 overflow-hidden"
|
||||
>
|
||||
<Box className="grid flex-auto grid-flow-row gap-4 overflow-y-auto border-t-1 py-4">
|
||||
<Box className="grid flex-auto grid-flow-row gap-4 py-4 overflow-y-auto border-t-1">
|
||||
<Box component="section" className="grid grid-flow-row gap-4 px-6">
|
||||
<Text variant="h3">From</Text>
|
||||
|
||||
@@ -185,7 +185,7 @@ export default function BaseForeignKeyForm({
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box className="grid flex-shrink-0 grid-flow-row gap-2 border-t-1 px-6 pt-4">
|
||||
<Box className="grid flex-shrink-0 grid-flow-row gap-2 px-6 pt-4 border-t-1">
|
||||
<Button loading={isSubmitting} disabled={isSubmitting} type="submit">
|
||||
{submitButtonText}
|
||||
</Button>
|
||||
|
||||
@@ -14,8 +14,8 @@ import { Text } from '@/components/ui/v2/Text';
|
||||
import { useMetadataQuery } from '@/features/database/dataGrid/hooks/useMetadataQuery';
|
||||
import { useTableQuery } from '@/features/database/dataGrid/hooks/useTableQuery';
|
||||
import { getTruncatedText } from '@/utils/getTruncatedText';
|
||||
import type { AutocompleteGroupedOption } from '@mui/base/AutocompleteUnstyled';
|
||||
import { useAutocomplete } from '@mui/base/AutocompleteUnstyled';
|
||||
import type { AutocompleteGroupedOption } from '@mui/base/useAutocomplete';
|
||||
import { useAutocomplete } from '@mui/base/useAutocomplete';
|
||||
import type { AutocompleteRenderGroupParams } from '@mui/material/Autocomplete';
|
||||
import { autocompleteClasses } from '@mui/material/Autocomplete';
|
||||
import type {
|
||||
@@ -366,11 +366,11 @@ function ColumnAutocomplete(
|
||||
);
|
||||
}}
|
||||
>
|
||||
<ArrowLeftIcon className="h-4 w-4" />
|
||||
<ArrowLeftIcon className="w-4 h-4" />
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
<Text className="direction-rtl truncate text-left">
|
||||
<Text className="text-left truncate direction-rtl">
|
||||
<Text component="span" color="disabled">
|
||||
{defaultTable}
|
||||
</Text>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { triggerToast } from '@/utils/toast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useRouter } from 'next/router';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import type * as Yup from 'yup';
|
||||
|
||||
export interface CreateColumnFormProps
|
||||
extends Pick<BaseColumnFormProps, 'onCancel' | 'location'> {
|
||||
@@ -50,7 +51,9 @@ export default function CreateColumnForm({
|
||||
resetForeignKeyError();
|
||||
}
|
||||
|
||||
const form = useForm<BaseColumnFormValues>({
|
||||
const form = useForm<
|
||||
BaseColumnFormValues | Yup.InferType<typeof baseColumnValidationSchema>
|
||||
>({
|
||||
defaultValues: {
|
||||
name: '',
|
||||
type: null,
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import type * as Yup from 'yup';
|
||||
|
||||
export interface CreateForeignKeyFormProps
|
||||
extends Pick<
|
||||
@@ -34,7 +35,10 @@ export default function CreateForeignKeyForm({
|
||||
}: CreateForeignKeyFormProps) {
|
||||
const [error, setError] = useState<Error>(null);
|
||||
|
||||
const form = useForm<BaseForeignKeyFormValues>({
|
||||
const form = useForm<
|
||||
| BaseForeignKeyFormValues
|
||||
| Yup.InferType<typeof baseForeignKeyValidationSchema>
|
||||
>({
|
||||
defaultValues: {
|
||||
id: null,
|
||||
name: '',
|
||||
|
||||
@@ -16,6 +16,7 @@ import { triggerToast } from '@/utils/toast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useRouter } from 'next/router';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import type * as Yup from 'yup';
|
||||
|
||||
export interface CreateTableFormProps
|
||||
extends Pick<BaseTableFormProps, 'onCancel' | 'location'> {
|
||||
@@ -53,7 +54,9 @@ export default function CreateTableForm({
|
||||
|
||||
const error = createTableError || trackTableError || foreignKeyError;
|
||||
|
||||
const form = useForm<BaseTableFormValues>({
|
||||
const form = useForm<
|
||||
BaseTableFormValues | Yup.InferType<typeof baseTableValidationSchema>
|
||||
>({
|
||||
defaultValues: {
|
||||
columns: [
|
||||
{
|
||||
|
||||
@@ -275,7 +275,7 @@ export default function DataBrowserGrid({
|
||||
() =>
|
||||
columns
|
||||
.map((column) => ({
|
||||
...createDataGridColumn(column, isSchemaEditable),
|
||||
...createDataGridColumn(column, true),
|
||||
onCellEdit: async (variables: UpdateRecordVariables) => {
|
||||
const result = await updateRow(variables);
|
||||
await queryClient.invalidateQueries([currentTablePath]);
|
||||
@@ -288,7 +288,6 @@ export default function DataBrowserGrid({
|
||||
[
|
||||
columns,
|
||||
currentTablePath,
|
||||
isSchemaEditable,
|
||||
optimisticlyRemovedColumnId,
|
||||
queryClient,
|
||||
removableColumnId,
|
||||
@@ -422,7 +421,7 @@ export default function DataBrowserGrid({
|
||||
loading={status === 'loading'}
|
||||
sortBy={sortBy}
|
||||
className="pb-17 sm:pb-0"
|
||||
onInsertRow={isSchemaEditable ? handleInsertRowClick : undefined}
|
||||
onInsertRow={handleInsertRowClick}
|
||||
onInsertColumn={isSchemaEditable ? handleInsertColumnClick : undefined}
|
||||
onEditColumn={isSchemaEditable ? handleEditColumnClick : undefined}
|
||||
onRemoveColumn={isSchemaEditable ? handleColumnRemoveClick : undefined}
|
||||
@@ -445,7 +444,7 @@ export default function DataBrowserGrid({
|
||||
onInsertColumnClick={
|
||||
isSchemaEditable ? handleInsertColumnClick : undefined
|
||||
}
|
||||
onInsertRowClick={isSchemaEditable ? handleInsertRowClick : undefined}
|
||||
onInsertRowClick={handleInsertRowClick}
|
||||
paginationProps={{
|
||||
currentPage: Math.max(currentPage, 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 { useDeleteRecordMutation } from '@/features/database/dataGrid/hooks/useDeleteRecordMutation';
|
||||
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 { triggerToast } from '@/utils/toast';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useState } from 'react';
|
||||
import type { Row } from 'react-table';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
@@ -58,11 +56,6 @@ export default function DataBrowserGridControls({
|
||||
const { className: paginationClassName, ...restPaginationProps } =
|
||||
paginationProps || ({} as DataGridPaginationProps);
|
||||
|
||||
const {
|
||||
query: { schemaSlug },
|
||||
} = useRouter();
|
||||
const isSchemaEditable = !isSchemaLocked(schemaSlug as string);
|
||||
|
||||
const {
|
||||
selectedFlatRows: selectedRows,
|
||||
columns,
|
||||
@@ -126,7 +119,7 @@ export default function DataBrowserGridControls({
|
||||
numberOfSelectedRows > 0 ? 'justify-between' : 'justify-end',
|
||||
)}
|
||||
>
|
||||
{isSchemaEditable && numberOfSelectedRows > 0 && (
|
||||
{numberOfSelectedRows > 0 && (
|
||||
<div className="grid grid-flow-col place-content-start items-center gap-2">
|
||||
<Chip
|
||||
size="small"
|
||||
|
||||
@@ -241,7 +241,7 @@ function DataBrowserSidebarContent({
|
||||
) {
|
||||
openDrawer({
|
||||
title: (
|
||||
<span className="inline-grid grid-flow-col items-center gap-2">
|
||||
<span className="inline-grid items-center grid-flow-col gap-2">
|
||||
Permissions
|
||||
<InlineCode className="!text-sm+ font-normal">{table}</InlineCode>
|
||||
<Chip label="Preview" size="small" color="info" component="span" />
|
||||
@@ -263,12 +263,12 @@ function DataBrowserSidebarContent({
|
||||
}
|
||||
|
||||
return (
|
||||
<Box className="flex h-full flex-col justify-between">
|
||||
<Box className="flex flex-col justify-between h-full">
|
||||
<Box className="flex flex-col px-2">
|
||||
{schemas && schemas.length > 0 && (
|
||||
<Select
|
||||
renderValue={(option) => (
|
||||
<span className="grid grid-flow-col items-center gap-1">
|
||||
<span className="grid items-center grid-flow-col gap-1">
|
||||
{option?.label}
|
||||
</span>
|
||||
)}
|
||||
@@ -281,7 +281,7 @@ function DataBrowserSidebarContent({
|
||||
>
|
||||
{schemas.map((schema) => (
|
||||
<Option
|
||||
className="grid grid-flow-col items-center gap-1"
|
||||
className="grid items-center grid-flow-col gap-1"
|
||||
value={schema.schema_name}
|
||||
key={schema.schema_name}
|
||||
>
|
||||
@@ -295,7 +295,7 @@ function DataBrowserSidebarContent({
|
||||
</Text>
|
||||
{(isSchemaLocked(schema.schema_name) || isGitHubConnected) && (
|
||||
<LockIcon
|
||||
className="h-3 w-3"
|
||||
className="w-3 h-3"
|
||||
sx={{ color: 'text.secondary' }}
|
||||
/>
|
||||
)}
|
||||
@@ -317,7 +317,7 @@ function DataBrowserSidebarContent({
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
underline="hover"
|
||||
className="grid grid-flow-col items-center justify-start gap-1"
|
||||
className="grid items-center justify-start grid-flow-col gap-1"
|
||||
>
|
||||
Learn More <ArrowRightIcon />
|
||||
</Link>
|
||||
@@ -327,7 +327,7 @@ function DataBrowserSidebarContent({
|
||||
<Button
|
||||
variant="borderless"
|
||||
endIcon={<PlusIcon />}
|
||||
className="mt-1 w-full justify-between px-2"
|
||||
className="justify-between w-full px-2 mt-1"
|
||||
onClick={() => {
|
||||
openDrawer({
|
||||
title: 'Create a New Table',
|
||||
@@ -396,7 +396,7 @@ function DataBrowserSidebarContent({
|
||||
}
|
||||
>
|
||||
<UsersIcon
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
sx={{ color: 'text.secondary' }}
|
||||
/>
|
||||
<span>View Permissions</span>
|
||||
@@ -426,7 +426,7 @@ function DataBrowserSidebarContent({
|
||||
}
|
||||
>
|
||||
<PencilIcon
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
sx={{ color: 'text.secondary' }}
|
||||
/>
|
||||
<span>Edit Table</span>
|
||||
@@ -449,7 +449,7 @@ function DataBrowserSidebarContent({
|
||||
}
|
||||
>
|
||||
<UsersIcon
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
sx={{ color: 'text.secondary' }}
|
||||
/>
|
||||
<span>Edit Permissions</span>
|
||||
@@ -473,7 +473,7 @@ function DataBrowserSidebarContent({
|
||||
}
|
||||
>
|
||||
<TrashIcon
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
sx={{ color: 'error.main' }}
|
||||
/>
|
||||
<span>Delete Table</span>
|
||||
@@ -521,7 +521,7 @@ function DataBrowserSidebarContent({
|
||||
component={NavLink}
|
||||
href={sqlEditorHref}
|
||||
>
|
||||
<div className="flex w-full flex-row items-center justify-center space-x-4">
|
||||
<div className="flex flex-row items-center justify-center w-full space-x-4">
|
||||
<TerminalIcon />
|
||||
<span className="flex">SQL Editor</span>
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user