Compare commits
132 Commits
@nhost/das
...
@nhost/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3143d66a8e | ||
|
|
8512a7f181 | ||
|
|
e503b8fe8b | ||
|
|
304065ae22 | ||
|
|
68e0622eb0 | ||
|
|
70c6834636 | ||
|
|
a7bde37bba | ||
|
|
1bc615beca | ||
|
|
a58c5cfc96 | ||
|
|
c61228e45d | ||
|
|
6cec04bd6f | ||
|
|
a448d7d182 | ||
|
|
948048940e | ||
|
|
5e91221d5a | ||
|
|
7278991a59 | ||
|
|
5924bc3248 | ||
|
|
c5ad634799 | ||
|
|
426b93a19f | ||
|
|
026f84f466 | ||
|
|
384fac00b1 | ||
|
|
7e9a2ce136 | ||
|
|
076fd4a7c0 | ||
|
|
9525fd74b3 | ||
|
|
8a2bc98214 | ||
|
|
dd5d262062 | ||
|
|
09962bef37 | ||
|
|
9cdecb6b23 | ||
|
|
e7eb90318e | ||
|
|
f67f22d321 | ||
|
|
87ae23ba05 | ||
|
|
b2be3642aa | ||
|
|
1230081ce6 | ||
|
|
c195c517de | ||
|
|
6f419be2c1 | ||
|
|
93ebdf844f | ||
|
|
bcd889b53a | ||
|
|
992939cdcd | ||
|
|
3c31657c50 | ||
|
|
a654d536e0 | ||
|
|
91c2bb6f53 | ||
|
|
9f2bf9ec2b | ||
|
|
d62bd0fc9a | ||
|
|
768ca17494 | ||
|
|
943831fe2e | ||
|
|
f242e4b92f | ||
|
|
863b37d313 | ||
|
|
c8a8d4fca3 | ||
|
|
311374e3fb | ||
|
|
e40a4529b4 | ||
|
|
1623e9bd20 | ||
|
|
5c47e8e675 | ||
|
|
9f9f1c64f4 | ||
|
|
981404f0b9 | ||
|
|
4ad27e9d72 | ||
|
|
778946998a | ||
|
|
6c11b75807 | ||
|
|
2dc031d16c | ||
|
|
40bd3e4572 | ||
|
|
6cb2b6331a | ||
|
|
08a7dd9894 | ||
|
|
f0a994a26e | ||
|
|
4fbd6bd4fa | ||
|
|
67fc77486c | ||
|
|
4f3fb3446e | ||
|
|
49a80c22be | ||
|
|
28676f4cdc | ||
|
|
e03f14133c | ||
|
|
150c04a4f4 | ||
|
|
bccd67b1b1 | ||
|
|
b14fd2f14c | ||
|
|
68b3d23cd9 | ||
|
|
d86e5c9c16 | ||
|
|
b2cc1411d7 | ||
|
|
407feeac37 | ||
|
|
7b25c37c26 | ||
|
|
6df4f02e95 | ||
|
|
aaae98f019 | ||
|
|
dc23dc0f49 | ||
|
|
82728da100 | ||
|
|
2d68fee54c | ||
|
|
35010353c7 | ||
|
|
aff059ec71 | ||
|
|
713d53cfc0 | ||
|
|
e0ab6d9a37 | ||
|
|
7baee8a9cc | ||
|
|
3db2999f60 | ||
|
|
3c4dd55045 | ||
|
|
92b434e840 | ||
|
|
13d359602f | ||
|
|
0d8d0eb10f | ||
|
|
ed9df85778 | ||
|
|
41617b970a | ||
|
|
c5c904b716 | ||
|
|
7db095fe92 | ||
|
|
f33e07b191 | ||
|
|
017f1a6c7b | ||
|
|
93957c8af3 | ||
|
|
2505b2e26b | ||
|
|
5f4b4d2acc | ||
|
|
71a8ce4446 | ||
|
|
5ef5189898 | ||
|
|
791b7295fb | ||
|
|
25bc4b7fd6 | ||
|
|
da20159ec5 | ||
|
|
2ae5ea8bc1 | ||
|
|
3ba485e582 | ||
|
|
e5bab6a061 | ||
|
|
be64353145 | ||
|
|
2f5913c78d | ||
|
|
757ddd901c | ||
|
|
1a61c658a7 | ||
|
|
d3d14245c7 | ||
|
|
53d2f9d3e0 | ||
|
|
8c34c69e79 | ||
|
|
b19ffed273 | ||
|
|
859efa988a | ||
|
|
3202b6b897 | ||
|
|
ba73bb4003 | ||
|
|
d5337ff5bd | ||
|
|
511ab19755 | ||
|
|
7c2a1c29fd | ||
|
|
5c9b8f0a3f | ||
|
|
b3f1f5f6ea | ||
|
|
6b8aad5c84 | ||
|
|
c36132c9bb | ||
|
|
b18edc0532 | ||
|
|
1d55d3ea38 | ||
|
|
fdc50b32d8 | ||
|
|
3cdca8d4b3 | ||
|
|
c425c9f265 | ||
|
|
3b8473b168 | ||
|
|
8d91f7103f |
@@ -14,7 +14,7 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: pnpm/action-setup@v2.2.4
|
- uses: pnpm/action-setup@v2.2.4
|
||||||
with:
|
with:
|
||||||
version: 8.6.2
|
version: 8.10.5
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm cache directory
|
- name: Get pnpm cache directory
|
||||||
id: pnpm-cache-dir
|
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
|
# * Run every `lint` script in the workspace . Dependencies build is cached by Turborepo
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: pnpm run lint:all
|
run: pnpm run lint:all
|
||||||
|
- name: Audit for vulnerabilities
|
||||||
|
run: pnpx audit-ci --config ./audit-ci.jsonc
|
||||||
|
|
||||||
e2e:
|
e2e:
|
||||||
name: 'E2E (Package: ${{ matrix.package.path }})'
|
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/
|
coverage/
|
||||||
dist/
|
dist/
|
||||||
umd/
|
umd/
|
||||||
node_modules/
|
node_modules
|
||||||
tmp/
|
tmp/
|
||||||
.pnpm-store
|
.pnpm-store
|
||||||
.turbo
|
.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
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
[ -n "$CI" ] && exit 0
|
||||||
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
pnpm dlx lint-staged --config config/.lintstagedrc.js
|
pnpm dlx lint-staged --config config/.lintstagedrc.js
|
||||||
|
|||||||
@@ -1,5 +1,240 @@
|
|||||||
# @nhost/dashboard
|
# @nhost/dashboard
|
||||||
|
|
||||||
|
## 1.15.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@11.0.3
|
||||||
|
- @nhost/nextjs@2.1.12
|
||||||
|
|
||||||
|
## 1.15.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- a7bde37: feat: send metadata in the edit form
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 1bc615b: feat: improve error message handling in `ErrorToast` component
|
||||||
|
- @nhost/react-apollo@11.0.2
|
||||||
|
- @nhost/nextjs@2.1.11
|
||||||
|
|
||||||
|
## 1.14.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- a448d7d: feat: allow configuring postmark and delete SMTP settings
|
||||||
|
|
||||||
|
## 1.13.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 5924bc3: fix: include password in `GetSmtpSettings` query
|
||||||
|
- c5ad634: fix: resolved an issue where one-click install links were broken on Safari
|
||||||
|
- 7278991: fix: update graphql auto-embeddings configuration to use String type for model field
|
||||||
|
|
||||||
|
## 1.13.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 026f84f: fix: use configuration server URL from environment variable
|
||||||
|
|
||||||
|
## 1.13.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 7e9a2ce: fix: resolve issue where run services form fails to open
|
||||||
|
|
||||||
|
## 1.13.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- dd5d262: feat: add model field to the auto-embeddings form
|
||||||
|
- 09962be: feat: enable settings and run services when running the dashboard locally
|
||||||
|
- 9cdecb6: feat: enable users to update their email address from the account settings page
|
||||||
|
|
||||||
|
## 1.12.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- c195c51: fix: send email upon signin for unverified users
|
||||||
|
|
||||||
|
## 1.12.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 93ebdf8: fix: use service urls when initilizaing NhostClient running local dashboard
|
||||||
|
- @nhost/react-apollo@11.0.1
|
||||||
|
- @nhost/nextjs@2.1.10
|
||||||
|
|
||||||
|
## 1.12.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- f242e4b: feat: add connect with github to the user's account settings
|
||||||
|
- 768ca17: chore: update dependencies
|
||||||
|
- d62bd0f: fix: "Track this" option within the SQL editor now correctly updates the metadata
|
||||||
|
- 91c2bb6: feat: refactor sign-in and sign-up pages to enforce email verification
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 943831f: fix: resolve an error toast issue when unpausing a project
|
||||||
|
- Updated dependencies [768ca17]
|
||||||
|
- @nhost/react-apollo@11.0.0
|
||||||
|
- @nhost/nextjs@2.1.9
|
||||||
|
|
||||||
|
## 1.11.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@10.0.2
|
||||||
|
- @nhost/nextjs@2.1.8
|
||||||
|
|
||||||
|
## 1.11.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 981404f: fix: set default value for healthCheck field validation
|
||||||
|
|
||||||
|
## 1.11.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 7789469: chore: upgrade dependency `@graphql-codegen/cli` to `5.0.2` to address vulnerability
|
||||||
|
- 6c11b75: feat: add update user displayName section in account settings
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@10.0.1
|
||||||
|
- @nhost/nextjs@2.1.7
|
||||||
|
|
||||||
|
## 1.10.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 49a80c2: chore: update dependencies
|
||||||
|
- 150c04a: feat: add healthcheck config to run services
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- e03f141: fix: allow insert, update and delete on tables in `auth` and `storage` schemas
|
||||||
|
- 28676f4: feat: add min postgres version check to enable the ai service
|
||||||
|
- Updated dependencies [49a80c2]
|
||||||
|
- @nhost/react-apollo@10.0.0
|
||||||
|
- @nhost/nextjs@2.1.6
|
||||||
|
|
||||||
|
## 1.9.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- d86e5c9: feat: add support for filtering the logs using a RegExp
|
||||||
|
|
||||||
|
## 1.8.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@9.0.3
|
||||||
|
- @nhost/nextjs@2.1.5
|
||||||
|
|
||||||
|
## 1.8.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 6df4f02: fix: use custom error toast and show correct message when sending an invite
|
||||||
|
|
||||||
|
## 1.8.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@9.0.2
|
||||||
|
- @nhost/nextjs@2.1.4
|
||||||
|
|
||||||
|
## 1.8.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 713d53c: feat: add catch-all route for workspace/project - useful for documentation
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 3db2999: fix: refresh table list after running SQL using the editor
|
||||||
|
- 3c4dd55: fix: handle `Error` objects properly in the `ErrorToast` component
|
||||||
|
- 92b434e: fix: resolve an issue where the checkbox in the data-grid header did not select all rows
|
||||||
|
- @nhost/react-apollo@9.0.1
|
||||||
|
- @nhost/nextjs@2.1.3
|
||||||
|
|
||||||
|
## 1.7.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 0d8d0eb: Update docs and dashboard references
|
||||||
|
|
||||||
|
## 1.6.9
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@9.0.0
|
||||||
|
- @nhost/nextjs@2.1.2
|
||||||
|
|
||||||
|
## 1.6.8
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@8.0.1
|
||||||
|
- @nhost/nextjs@2.1.1
|
||||||
|
|
||||||
|
## 1.6.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 5ef5189: fix: update `@apollo/client` to `3.9.4` to fix a cache bug
|
||||||
|
|
||||||
|
## 1.6.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 3ba485e: fix: added discord.com to connect-src
|
||||||
|
- e5bab6a: chore: update dependencies
|
||||||
|
- Updated dependencies [b19ffed]
|
||||||
|
- Updated dependencies [e5bab6a]
|
||||||
|
- @nhost/nextjs@2.1.0
|
||||||
|
- @nhost/react-apollo@8.0.0
|
||||||
|
|
||||||
|
## 1.6.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- ba73bb4: fix: update ErrorToast component to show the internal graphql error
|
||||||
|
- d5337ff: fix: utilize accumulator in the creation of validation schema within data grid utils
|
||||||
|
|
||||||
|
## 1.6.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 7c2a1c2: feat: show error and debug info in the error toast
|
||||||
|
|
||||||
|
## 1.6.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 6b8aad5: fix: add bare nhost.run to CSP
|
||||||
|
|
||||||
|
## 1.6.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- b18edc0: feat: added CSP and X-Frame-Options
|
||||||
|
|
||||||
|
## 1.6.1
|
||||||
|
|
||||||
|
### 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
|
## 1.6.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ RUN apk add --no-cache libc6-compat
|
|||||||
RUN apk update
|
RUN apk update
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN yarn global add turbo@1.10.11
|
RUN yarn global add turbo@1.11.3
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN turbo prune --scope="@nhost/dashboard" --docker
|
RUN turbo prune --scope="@nhost/dashboard" --docker
|
||||||
|
|
||||||
@@ -28,8 +28,9 @@ ENV NEXT_PUBLIC_NHOST_STORAGE_URL __NEXT_PUBLIC_NHOST_STORAGE_URL__
|
|||||||
ENV NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL __NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__
|
ENV NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL __NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__
|
||||||
ENV NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL __NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__
|
ENV NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL __NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__
|
||||||
ENV NEXT_PUBLIC_NHOST_HASURA_API_URL __NEXT_PUBLIC_NHOST_HASURA_API_URL__
|
ENV NEXT_PUBLIC_NHOST_HASURA_API_URL __NEXT_PUBLIC_NHOST_HASURA_API_URL__
|
||||||
|
ENV NEXT_PUBLIC_NHOST_CONFIGSERVER_URL __NEXT_PUBLIC_NHOST_CONFIGSERVER_URL__
|
||||||
|
|
||||||
RUN yarn global add pnpm@8.6.2
|
RUN yarn global add pnpm@8.10.5
|
||||||
COPY .gitignore .gitignore
|
COPY .gitignore .gitignore
|
||||||
COPY --from=pruner /app/out/json/ .
|
COPY --from=pruner /app/out/json/ .
|
||||||
COPY --from=pruner /app/out/pnpm-*.yaml .
|
COPY --from=pruner /app/out/pnpm-*.yaml .
|
||||||
|
|||||||
@@ -21,5 +21,6 @@ find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_STORAGE_URL__~${NEXT_
|
|||||||
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__~${NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL}~g" {} +
|
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__~${NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL}~g" {} +
|
||||||
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__~${NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL}~g" {} +
|
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__~${NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL}~g" {} +
|
||||||
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_API_URL__~${NEXT_PUBLIC_NHOST_HASURA_API_URL}~g" {} +
|
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_API_URL__~${NEXT_PUBLIC_NHOST_HASURA_API_URL}~g" {} +
|
||||||
|
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_CONFIGSERVER_URL__~${NEXT_PUBLIC_NHOST_CONFIGSERVER_URL}~g" {} +
|
||||||
|
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ test('should be able to create then delete a personal access token', async () =>
|
|||||||
const patName = faker.lorem.slug(3);
|
const patName = faker.lorem.slug(3);
|
||||||
|
|
||||||
await page.getByRole('textbox', { name: /name/i }).fill(patName);
|
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('option', { name: /7 days/i }).click();
|
||||||
await page.getByRole('button', { name: /create/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();
|
await page.getByRole('option', { name: /id/i }).click();
|
||||||
|
|
||||||
// create table
|
// 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();
|
await page.getByRole('button', { name: /add foreign key/i }).click();
|
||||||
|
|
||||||
// select column in current table
|
await page.locator('#columnName').click();
|
||||||
await page
|
|
||||||
.getByRole('button', { name: /column/i })
|
|
||||||
.first()
|
|
||||||
.click();
|
|
||||||
await page.getByRole('option', { name: /author_id/i }).click();
|
await page.getByRole('option', { name: /author_id/i }).click();
|
||||||
|
|
||||||
// select reference schema
|
// select reference schema
|
||||||
await page.getByRole('button', { name: /schema/i }).click();
|
await page.getByLabel('Schema').click();
|
||||||
await page.getByRole('option', { name: /public/i }).click();
|
await page.getByRole('option', { name: /public/i }).click();
|
||||||
|
|
||||||
// select reference table
|
// 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();
|
await page.getByRole('option', { name: firstTableName, exact: true }).click();
|
||||||
|
|
||||||
// select reference column
|
await page.locator('#referencedColumn').click();
|
||||||
await page
|
|
||||||
.getByRole('button', { name: /column/i })
|
|
||||||
.nth(1)
|
|
||||||
.click();
|
|
||||||
await page.getByRole('option', { name: /id/i }).click();
|
await page.getByRole('option', { name: /id/i }).click();
|
||||||
|
|
||||||
await page.getByRole('button', { name: /add/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();
|
await page.getByRole('button', { name: /add foreign key/i }).click();
|
||||||
|
|
||||||
// select column in current table
|
// select column in current table
|
||||||
await page
|
await page.locator('#columnName').click();
|
||||||
.getByRole('button', { name: /column/i })
|
|
||||||
.first()
|
|
||||||
.click();
|
|
||||||
await page.getByRole('option', { name: /author_id/i }).click();
|
await page.getByRole('option', { name: /author_id/i }).click();
|
||||||
|
|
||||||
// select reference schema
|
// select reference schema
|
||||||
await page.getByRole('button', { name: /schema/i }).click();
|
await page.getByLabel('Schema').click();
|
||||||
await page.getByRole('option', { name: /public/i }).click();
|
await page.getByRole('option', { name: /public/i }).click();
|
||||||
|
|
||||||
// select reference table
|
// 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();
|
await page.getByRole('option', { name: firstTableName, exact: true }).click();
|
||||||
|
|
||||||
// select reference column
|
// select reference column
|
||||||
await page
|
await page.locator('#referencedColumn').click();
|
||||||
.getByRole('button', { name: /column/i })
|
|
||||||
.nth(1)
|
|
||||||
.click();
|
|
||||||
await page.getByRole('option', { name: /id/i }).click();
|
await page.getByRole('option', { name: /id/i }).click();
|
||||||
|
|
||||||
await page.getByRole('button', { name: /add/i }).click();
|
await page.getByRole('button', { name: /add/i }).click();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
|
|||||||
2
dashboard/e2e/e2e-tests-project/.gitignore
vendored
Normal file
2
dashboard/e2e/e2e-tests-project/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.secrets
|
||||||
|
.nhost
|
||||||
1
dashboard/e2e/e2e-tests-project/nhost/config.yaml
Normal file
1
dashboard/e2e/e2e-tests-project/nhost/config.yaml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
version: 3
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Потвърдете смяната на вашия имейл</h2>
|
||||||
|
<p>Използвайте посочения линк, за да повърдите смяната на имейл:</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Смени имейл
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Потвърждение за смяна на имейл
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Потвърдете вашия имейл</h2>
|
||||||
|
<p>Използвайте посочения линк, за да потвърдите вашия имейл:</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Потвърдете имейл
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Потвърждаване на имейл
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Смяна на парола</h2>
|
||||||
|
<p>Използвайте посочения линк, за да смените вашата парола:</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Смяна на парола
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Смяна на парола
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Вашият код е ${code}.
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Магически линк за вход</h2>
|
||||||
|
<p>Използвайте посочения линк за защитен и бърз вход:</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Вход
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Магически линк за вход
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Potvrzení změny emailové adresy</h2>
|
||||||
|
<p>Použijte tento odkaz k potvrzení změny emailové adresy:</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Změnit email
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Změna vaší emailové adresy
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Ověření emailové adresy</h2>
|
||||||
|
<p>Použijte tento odkaz k ověření vaší emailové adresy:</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Ověřit emailovou adresu
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Ověření vaší emailové adresy
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Obnova hesla</h2>
|
||||||
|
<p>Použijte tento odkaz k obnovení vašeho hesla:</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Obnova hesla
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Obnova hesla
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Váš kód je ${code}.
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Magický odkaz</h2>
|
||||||
|
<p>Použijte tento odkaz k bezpečnému přihlášení:</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Přihlášení
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Bezpečný odkaz k přihlášení
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Confirm Email Change</h2>
|
||||||
|
<p>Use this link to confirm changing email:</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Change email
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Change your email address
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Verify Email</h2>
|
||||||
|
<p>Use this link to verify your email:</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Verify Email
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Verify your email
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Reset Password</h2>
|
||||||
|
<p>Use this link to reset your password:</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Reset password
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Reset your password
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Your code is ${code}.
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Magic Link</h2>
|
||||||
|
<p>Use this link to securely sign in:</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Sign In
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Secure sign-in link
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Confirmar cambio de correo electrónico</h2>
|
||||||
|
<p>Utiliza el siguiente enlace para confirmar el cambio de correo:</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Cambiar correo electrónico
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Cambiar dirección de correo electrónico
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Verificar correo electrónico</h2>
|
||||||
|
<p>Utilza el siguiente enlace para verificar tu correo:</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Verificar correo electrónico
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Verifica tu correo electrónico
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Recuperar contraseña</h2>
|
||||||
|
<p>Utiliza el siguiente enlace para recuperar tu contraseña:</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Recuperar contraseña
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Recuperar contraseña
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Tu código es ${code}.
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Enlace mágico</h2>
|
||||||
|
<p>Utiliza este enlace para iniciar sesión de forma segura:</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Iniciar sesión
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Enlace de acceso seguro
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Confirmer changement de courriel</h2>
|
||||||
|
<p>Utilisez ce lien pour confirmer le changement de courriel :</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Changer courriel
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Changez votre adresse courriel
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Vérifiez votre courriel</h2>
|
||||||
|
<p>Utilisez ce lien pour vérifier votre courriel :</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Vérifier courriel
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Vérifier votre courriel
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Réinitialiser votre mot de passe</h2>
|
||||||
|
<p>Utilisez ce lien pour réinitialiser votre mot de passe :</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Réinitialiser mot de passe
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Réinitialiser votre mot de passe
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Votre code est ${code}.
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Lien magique</h2>
|
||||||
|
<p>Utilisez ce lien pour vous connecter de façon sécurisée :</p>
|
||||||
|
<p>
|
||||||
|
<a href="${link}">
|
||||||
|
Connexion
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Lien de connexion sécurisé
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
${link},
|
||||||
|
${displayName},
|
||||||
|
${email},
|
||||||
|
${ticket},
|
||||||
|
${redirectTo},
|
||||||
|
${serverUrl},
|
||||||
|
${clientUrl},
|
||||||
|
${locale},
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
${link}, ${displayName}, ${email}, ${ticket}, ${redirectTo}, ${serverUrl}, ${clientUrl}, ${locale}
|
||||||
151
dashboard/e2e/e2e-tests-project/nhost/nhost.toml
Normal file
151
dashboard/e2e/e2e-tests-project/nhost/nhost.toml
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
[global]
|
||||||
|
|
||||||
|
[hasura]
|
||||||
|
version = 'v2.33.4-ce'
|
||||||
|
adminSecret = '{{ secrets.HASURA_GRAPHQL_ADMIN_SECRET }}'
|
||||||
|
webhookSecret = '{{ secrets.NHOST_WEBHOOK_SECRET }}'
|
||||||
|
|
||||||
|
[[hasura.jwtSecrets]]
|
||||||
|
type = 'HS256'
|
||||||
|
key = '{{ secrets.HASURA_GRAPHQL_JWT_SECRET }}'
|
||||||
|
|
||||||
|
[hasura.settings]
|
||||||
|
corsDomain = ['*']
|
||||||
|
devMode = true
|
||||||
|
enableAllowList = false
|
||||||
|
enableConsole = true
|
||||||
|
enableRemoteSchemaPermissions = false
|
||||||
|
enabledAPIs = ['metadata', 'graphql', 'pgdump', 'config']
|
||||||
|
liveQueriesMultiplexedRefetchInterval = 1000
|
||||||
|
stringifyNumericTypes = false
|
||||||
|
|
||||||
|
[hasura.logs]
|
||||||
|
level = 'warn'
|
||||||
|
|
||||||
|
[hasura.events]
|
||||||
|
httpPoolSize = 100
|
||||||
|
|
||||||
|
[functions]
|
||||||
|
[functions.node]
|
||||||
|
version = 18
|
||||||
|
|
||||||
|
[auth]
|
||||||
|
version = '0.24.1'
|
||||||
|
|
||||||
|
[auth.elevatedPrivileges]
|
||||||
|
mode = 'disabled'
|
||||||
|
|
||||||
|
[auth.redirections]
|
||||||
|
clientUrl = 'http://localhost:3000'
|
||||||
|
|
||||||
|
[auth.signUp]
|
||||||
|
enabled = true
|
||||||
|
disableNewUsers = false
|
||||||
|
|
||||||
|
[auth.user]
|
||||||
|
[auth.user.roles]
|
||||||
|
default = 'user'
|
||||||
|
allowed = ['user', 'me']
|
||||||
|
|
||||||
|
[auth.user.locale]
|
||||||
|
default = 'en'
|
||||||
|
allowed = ['en']
|
||||||
|
|
||||||
|
[auth.user.gravatar]
|
||||||
|
enabled = true
|
||||||
|
default = 'blank'
|
||||||
|
rating = 'g'
|
||||||
|
|
||||||
|
[auth.user.email]
|
||||||
|
|
||||||
|
[auth.user.emailDomains]
|
||||||
|
|
||||||
|
[auth.session]
|
||||||
|
[auth.session.accessToken]
|
||||||
|
expiresIn = 900
|
||||||
|
|
||||||
|
[auth.session.refreshToken]
|
||||||
|
expiresIn = 2592000
|
||||||
|
|
||||||
|
[auth.method]
|
||||||
|
[auth.method.anonymous]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.emailPasswordless]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.emailPassword]
|
||||||
|
hibpEnabled = false
|
||||||
|
emailVerificationRequired = true
|
||||||
|
passwordMinLength = 9
|
||||||
|
|
||||||
|
[auth.method.smsPasswordless]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.oauth]
|
||||||
|
[auth.method.oauth.apple]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.oauth.azuread]
|
||||||
|
tenant = 'common'
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.oauth.bitbucket]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.oauth.discord]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.oauth.facebook]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.oauth.github]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.oauth.gitlab]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.oauth.google]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.oauth.linkedin]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.oauth.spotify]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.oauth.strava]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.oauth.twitch]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.oauth.twitter]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.oauth.windowslive]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.oauth.workos]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.webauthn]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[auth.method.webauthn.attestation]
|
||||||
|
timeout = 60000
|
||||||
|
|
||||||
|
[auth.totp]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[postgres]
|
||||||
|
version = '14.6-20240129-1'
|
||||||
|
|
||||||
|
[provider]
|
||||||
|
|
||||||
|
[storage]
|
||||||
|
version = '0.6.0'
|
||||||
|
|
||||||
|
[observability]
|
||||||
|
[observability.grafana]
|
||||||
|
adminPassword = '{{ secrets.GRAFANA_ADMIN_PASSWORD }}'
|
||||||
@@ -93,7 +93,7 @@ test("should show the project's region and subdomain", async () => {
|
|||||||
|
|
||||||
test('should not have a GitHub repository connected', async () => {
|
test('should not have a GitHub repository connected', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: /connect to github/i }),
|
page.getByRole('button', { name: /connect to github/i }).first(),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,8 @@ export async function prepareTable({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// select the first column as primary key
|
// 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();
|
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 { version } = require('./package.json');
|
||||||
|
|
||||||
|
const cspHeader = `
|
||||||
|
default-src 'self' *.nhost.run ws://*.nhost.run nhost.run ws://nhost.run;
|
||||||
|
script-src 'self' 'unsafe-eval' 'unsafe-inline' cdn.segment.com js.stripe.com;
|
||||||
|
connect-src 'self' *.nhost.run ws://*.nhost.run nhost.run ws://nhost.run discord.com;
|
||||||
|
style-src 'self' 'unsafe-inline';
|
||||||
|
img-src 'self' blob: data: avatars.githubusercontent.com s.gravatar.com *.nhost.run nhost.run;
|
||||||
|
font-src 'self' data:;
|
||||||
|
object-src 'none';
|
||||||
|
base-uri 'self';
|
||||||
|
form-action 'self';
|
||||||
|
frame-ancestors 'none';
|
||||||
|
frame-src 'self' js.stripe.com;
|
||||||
|
`;
|
||||||
|
|
||||||
module.exports = withBundleAnalyzer({
|
module.exports = withBundleAnalyzer({
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
swcMinify: false,
|
swcMinify: false,
|
||||||
@@ -17,6 +31,19 @@ module.exports = withBundleAnalyzer({
|
|||||||
eslint: {
|
eslint: {
|
||||||
dirs: ['src'],
|
dirs: ['src'],
|
||||||
},
|
},
|
||||||
|
async headers() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: '/(.*)',
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: 'X-Frame-Options',
|
||||||
|
value: 'SAMEORIGIN',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
async redirects() {
|
async redirects() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/dashboard",
|
"name": "@nhost/dashboard",
|
||||||
"version": "1.6.0",
|
"version": "1.15.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
@@ -15,62 +15,62 @@
|
|||||||
"format": "prettier --write \"src/**/*.{js,ts,tsx,jsx,json,md}\" --plugin-search-dir=.",
|
"format": "prettier --write \"src/**/*.{js,ts,tsx,jsx,json,md}\" --plugin-search-dir=.",
|
||||||
"storybook": "start-storybook -p 6006 -s public",
|
"storybook": "start-storybook -p 6006 -s public",
|
||||||
"build-storybook": "build-storybook",
|
"build-storybook": "build-storybook",
|
||||||
"install-browsers": "pnpm dlx playwright@1.31.0 install --with-deps",
|
"install-browsers": "pnpm playwright install && pnpm playwright install-deps",
|
||||||
"e2e": "pnpm install-browsers && pnpm dlx playwright@1.31.0 test"
|
"e2e": "pnpm install-browsers && pnpm playwright test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.7.10",
|
"@apollo/client": "^3.9.9",
|
||||||
"@codemirror/lang-sql": "^6.5.4",
|
"@codemirror/lang-sql": "^6.6.2",
|
||||||
"@emotion/cache": "^11.10.5",
|
"@emotion/cache": "^11.11.0",
|
||||||
"@emotion/react": "^11.10.5",
|
"@emotion/react": "^11.11.4",
|
||||||
"@emotion/server": "^11.4.0",
|
"@emotion/server": "^11.11.0",
|
||||||
"@emotion/styled": "^11.10.5",
|
"@emotion/styled": "^11.11.5",
|
||||||
"@fontsource/inter": "^5.0.0",
|
"@fontsource/inter": "^5.0.17",
|
||||||
"@fontsource/roboto-mono": "^5.0.0",
|
"@fontsource/roboto-mono": "^5.0.17",
|
||||||
"@graphiql/react": "^0.18.0",
|
"@graphiql/react": "^0.20.3",
|
||||||
"@graphiql/toolkit": "^0.8.2",
|
"@graphiql/toolkit": "^0.9.1",
|
||||||
"@headlessui/react": "^1.6.5",
|
"@headlessui/react": "^1.7.18",
|
||||||
"@heroicons/react": "^1.0.6",
|
"@heroicons/react": "^1.0.6",
|
||||||
"@hookform/resolvers": "^3.0.0",
|
"@hookform/resolvers": "^3.3.4",
|
||||||
"@mui/base": "^5.0.0-alpha.106",
|
"@mui/base": "5.0.0-beta.31",
|
||||||
"@mui/material": "^5.10.14",
|
"@mui/material": "^5.15.14",
|
||||||
"@mui/system": "^5.10.14",
|
"@mui/system": "^5.15.14",
|
||||||
"@mui/x-date-pickers": "^5.0.8",
|
"@mui/x-date-pickers": "^5.0.20",
|
||||||
"@nhost/nextjs": "workspace:*",
|
"@nhost/nextjs": "workspace:*",
|
||||||
"@nhost/react-apollo": "workspace:*",
|
"@nhost/react-apollo": "workspace:*",
|
||||||
"@segment/snippet": "^4.15.3",
|
"@segment/snippet": "^4.16.2",
|
||||||
"@stripe/react-stripe-js": "^2.0.0",
|
"@stripe/react-stripe-js": "^2.6.2",
|
||||||
"@stripe/stripe-js": "^1.35.0",
|
"@stripe/stripe-js": "^1.54.2",
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
"@tanstack/react-query": "^4.16.1",
|
"@tanstack/react-query": "^4.36.1",
|
||||||
"@tanstack/react-table": "^8.5.30",
|
"@tanstack/react-table": "^8.15.3",
|
||||||
"@tanstack/react-virtual": "^3.0.0-beta.23",
|
"@tanstack/react-virtual": "^3.2.0",
|
||||||
"@uiw/codemirror-theme-github": "^4.21.20",
|
"@uiw/codemirror-theme-github": "^4.21.25",
|
||||||
"@uiw/react-codemirror": "^4.21.20",
|
"@uiw/react-codemirror": "^4.21.25",
|
||||||
"analytics-node": "^6.2.0",
|
"analytics-node": "^6.2.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.30.0",
|
||||||
"generate-password": "^1.7.0",
|
"framer-motion": "^10.18.0",
|
||||||
"graphiql": "^3.0.0",
|
"generate-password": "^1.7.1",
|
||||||
"graphql": "^16.6.0",
|
"graphiql": "^3.1.1",
|
||||||
"graphql-request": "^6.0.0",
|
"graphql": "16.8.1",
|
||||||
|
"graphql-request": "^6.1.0",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
"graphql-ws": "^5.11.2",
|
"graphql-ws": "^5.16.0",
|
||||||
"hypertune": "^1.4.4",
|
"just-kebab-case": "^4.2.0",
|
||||||
"just-kebab-case": "^4.1.1",
|
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"next": "^12.3.1",
|
"next": "^14.1.4",
|
||||||
"next-seo": "^6.0.0",
|
"next-seo": "^6.5.0",
|
||||||
"node-pg-format": "^1.3.5",
|
"node-pg-format": "^1.3.5",
|
||||||
"pluralize": "^8.0.0",
|
"pluralize": "^8.0.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-children-utilities": "^2.9.0",
|
"react-children-utilities": "^2.10.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-error-boundary": "^4.0.0",
|
"react-error-boundary": "^4.0.13",
|
||||||
"react-hook-form": "^7.42.1",
|
"react-hook-form": "^7.51.2",
|
||||||
"react-hot-toast": "^2.4.0",
|
"react-hot-toast": "^2.4.1",
|
||||||
"react-intersection-observer": "^9.5.2",
|
"react-intersection-observer": "^9.8.1",
|
||||||
"react-is": "18.2.0",
|
"react-is": "18.2.0",
|
||||||
"react-loading-skeleton": "^2.2.0",
|
"react-loading-skeleton": "^2.2.0",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
@@ -82,87 +82,87 @@
|
|||||||
"rehype-highlight": "^7.0.0",
|
"rehype-highlight": "^7.0.0",
|
||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
"shell-quote": "^1.8.1",
|
"shell-quote": "^1.8.1",
|
||||||
"slugify": "^1.6.5",
|
"slugify": "^1.6.6",
|
||||||
"stripe": "^10.17.0",
|
"stripe": "^10.17.0",
|
||||||
"tailwind-merge": "^1.8.0",
|
"tailwind-merge": "^1.14.0",
|
||||||
"utility-types": "^3.10.0",
|
"utility-types": "^3.11.0",
|
||||||
"validator": "^13.7.0",
|
"validator": "^13.11.0",
|
||||||
"yup": "^1.0.2",
|
"yup": "^1.4.0",
|
||||||
"yup-password": "^0.2.2"
|
"yup-password": "^0.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.20.2",
|
"@babel/core": "^7.24.3",
|
||||||
"@faker-js/faker": "^7.6.0",
|
"@faker-js/faker": "^7.6.0",
|
||||||
"@graphql-codegen/cli": "^3.0.0",
|
"@graphql-codegen/cli": "^5.0.2",
|
||||||
"@graphql-codegen/typescript": "^3.0.0",
|
"@graphql-codegen/typescript": "^3.0.4",
|
||||||
"@graphql-codegen/typescript-operations": "^3.0.0",
|
"@graphql-codegen/typescript-operations": "^3.0.4",
|
||||||
"@graphql-codegen/typescript-react-apollo": "^3.3.1",
|
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
|
||||||
"@next/bundle-analyzer": "^12.3.1",
|
"@next/bundle-analyzer": "^12.3.4",
|
||||||
"@playwright/test": "1.31.0",
|
"@playwright/test": "1.41.0",
|
||||||
"@storybook/addon-actions": "^6.5.14",
|
"@storybook/addon-actions": "^6.5.16",
|
||||||
"@storybook/addon-essentials": "^6.5.14",
|
"@storybook/addon-essentials": "^6.5.16",
|
||||||
"@storybook/addon-interactions": "^6.5.14",
|
"@storybook/addon-interactions": "^6.5.16",
|
||||||
"@storybook/addon-links": "^6.5.14",
|
"@storybook/addon-links": "^6.5.16",
|
||||||
"@storybook/addon-postcss": "^2.0.0",
|
"@storybook/addon-postcss": "^2.0.0",
|
||||||
"@storybook/builder-webpack5": "^6.5.14",
|
"@storybook/builder-webpack5": "^6.5.16",
|
||||||
"@storybook/manager-webpack5": "^6.5.14",
|
"@storybook/manager-webpack5": "^6.5.16",
|
||||||
"@storybook/react": "^6.5.14",
|
"@storybook/react": "^7.6.17",
|
||||||
"@storybook/testing-library": "^0.2.0",
|
"@storybook/testing-library": "^0.2.2",
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.12",
|
||||||
"@testing-library/dom": "^9.0.0",
|
"@testing-library/dom": "^9.3.4",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^14.2.2",
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"@types/ace": "^0.0.48",
|
"@types/ace": "^0.0.48",
|
||||||
"@types/bcryptjs": "^2.4.2",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"@types/jest": "^29.5.3",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/lodash.debounce": "^4.0.7",
|
"@types/lodash.debounce": "^4.0.9",
|
||||||
"@types/node": "^16.11.7",
|
"@types/node": "^16.18.93",
|
||||||
"@types/pluralize": "^0.0.30",
|
"@types/pluralize": "^0.0.30",
|
||||||
"@types/react": "^18.2.14",
|
"@types/react": "^18.2.73",
|
||||||
"@types/react-dom": "^18.2.6",
|
"@types/react-dom": "^18.2.23",
|
||||||
"@types/react-table": "^7.7.12",
|
"@types/react-table": "^7.7.20",
|
||||||
"@types/shell-quote": "^1.7.1",
|
"@types/shell-quote": "^1.7.5",
|
||||||
"@types/testing-library__jest-dom": "^5.14.5",
|
"@types/testing-library__jest-dom": "^5.14.9",
|
||||||
"@types/validator": "^13.7.10",
|
"@types/validator": "^13.11.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||||
"@typescript-eslint/parser": "^6.15.0",
|
"@typescript-eslint/parser": "^6.21.0",
|
||||||
"@vitejs/plugin-react": "^4.0.0",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"@vitest/coverage-v8": "^0.32.0",
|
"@vitest/coverage-v8": "^0.32.4",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.19",
|
||||||
"babel-loader": "^8.3.0",
|
"babel-loader": "^8.3.0",
|
||||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||||
"csstype": "^3.0.10",
|
"csstype": "^3.1.3",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.4.5",
|
||||||
"encoding": "^0.1.13",
|
"encoding": "^0.1.13",
|
||||||
"eslint": "^8.28.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-airbnb": "19.0.4",
|
"eslint-config-airbnb": "19.0.4",
|
||||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||||
"eslint-config-next": "^13.0.2",
|
"eslint-config-next": "^13.5.6",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.10.0",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-jsx-a11y": "^6.6.1",
|
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||||
"eslint-plugin-react": "^7.31.11",
|
"eslint-plugin-react": "^7.34.1",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"jsdom": "^22.0.0",
|
"jsdom": "^22.1.0",
|
||||||
"lint-staged": ">=13",
|
"lint-staged": "^15.2.2",
|
||||||
"msw": "^1.0.1",
|
"msw": "^1.3.3",
|
||||||
"msw-storybook-addon": "^1.6.3",
|
"msw-storybook-addon": "^1.10.0",
|
||||||
"node-fetch": "^3.3.0",
|
"node-fetch": "^3.3.2",
|
||||||
"postcss": "^8.4.19",
|
"postcss": "^8.4.38",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.8.8",
|
||||||
"prettier-plugin-organize-imports": "^3.2.0",
|
"prettier-plugin-organize-imports": "^3.2.4",
|
||||||
"prettier-plugin-tailwindcss": "^0.4.0",
|
"prettier-plugin-tailwindcss": "^0.4.1",
|
||||||
"react-date-fns-hooks": "^0.9.4",
|
"react-date-fns-hooks": "^0.9.4",
|
||||||
"require-from-string": "^2.0.2",
|
"require-from-string": "^2.0.2",
|
||||||
"snake-case": "^3.0.4",
|
"snake-case": "^3.0.4",
|
||||||
"storybook-addon-next-router": "^4.0.1",
|
"storybook-addon-next-router": "^4.0.2",
|
||||||
"tailwindcss": "^3.1.2",
|
"tailwindcss": "^3.4.3",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.2",
|
||||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
"tsconfig-paths-webpack-plugin": "^4.1.0",
|
||||||
"vite": "^4.0.2",
|
"vite": "^5.2.7",
|
||||||
"vite-tsconfig-paths": "^4.0.3",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
"vitest": "^0.32.0"
|
"vitest": "^0.32.4"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
|
|
||||||
|
export default function ApplyLocalSettingsDialog() {
|
||||||
|
const { closeDialog } = useDialog();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4 px-6 pb-6">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Text color="secondary">
|
||||||
|
Run{' '}
|
||||||
|
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
|
||||||
|
$ nhost up
|
||||||
|
</code>{' '}
|
||||||
|
using the cli to apply your changes
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="w-full"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => closeDialog()}
|
||||||
|
autoFocus
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as ApplyLocalSettingsDialog } from './ApplyLocalSettingsDialog';
|
||||||
@@ -17,7 +17,7 @@ function NavLink(
|
|||||||
ref: ForwardedRef<HTMLAnchorElement>,
|
ref: ForwardedRef<HTMLAnchorElement>,
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<NextLink href={href} passHref>
|
<NextLink href={href} passHref legacyBehavior>
|
||||||
<Link className={twMerge('font-display', className)} ref={ref} {...props}>
|
<Link className={twMerge('font-display', className)} ref={ref} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default function ThemeSwitcher({
|
|||||||
listbox: { className: 'min-w-0 w-full' },
|
listbox: { className: 'min-w-0 w-full' },
|
||||||
popper: {
|
popper: {
|
||||||
disablePortal: false,
|
disablePortal: false,
|
||||||
className: 'z-[10000] w-[270px] w-full',
|
className: 'z-[10000] w-[270px]',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default function UpgradeToProBanner({
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{ backgroundColor: 'primary.light' }}
|
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="flex flex-col justify-between space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -81,13 +81,13 @@ export default function UpgradeToProBanner({
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
underline="hover"
|
underline="hover"
|
||||||
className="text-center font-medium"
|
className="font-medium text-center"
|
||||||
sx={{
|
sx={{
|
||||||
color: 'text.secondary',
|
color: 'text.secondary',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
See all features
|
See all features
|
||||||
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
|
<ArrowSquareOutIcon className="w-4 h-4 ml-1" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -97,6 +97,7 @@ export default function UpgradeToProBanner({
|
|||||||
width={300}
|
width={300}
|
||||||
height={140}
|
height={140}
|
||||||
objectFit="contain"
|
objectFit="contain"
|
||||||
|
alt='Upgrade to Pro illustration'
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ function InsertPlaceholderTableRow({
|
|||||||
...props
|
...props
|
||||||
}: InsertPlaceholderTableRowProps) {
|
}: InsertPlaceholderTableRowProps) {
|
||||||
return (
|
return (
|
||||||
<Box className="h-12 border-r-1 border-b-1" {...props}>
|
<Box className="h-12 border-b-1 border-r-1" {...props}>
|
||||||
<Button
|
<Button
|
||||||
onClick={onInsertRow}
|
onClick={onInsertRow}
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
@@ -209,7 +209,7 @@ export default function DataGridBody<T extends object>({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Box
|
<Box
|
||||||
className="inline-flex h-12 items-center border-b-1 border-r-1 py-1.5 px-2 text-xs"
|
className="inline-flex h-12 items-center border-b-1 border-r-1 px-2 py-1.5 text-xs"
|
||||||
sx={{ color: 'text.secondary' }}
|
sx={{ color: 'text.secondary' }}
|
||||||
style={{
|
style={{
|
||||||
width: allowInsertColumn
|
width: allowInsertColumn
|
||||||
@@ -281,8 +281,8 @@ export default function DataGridBody<T extends object>({
|
|||||||
}}
|
}}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'h-12 font-display text-xs motion-safe:transition-colors',
|
'h-12 font-display text-xs motion-safe:transition-colors',
|
||||||
'border-r-1 border-b-1',
|
'border-b-1 border-r-1',
|
||||||
'scroll-mt-[57px] scroll-ml-8',
|
'scroll-ml-8 scroll-mt-[57px]',
|
||||||
column.id === 'selection' &&
|
column.id === 'selection' &&
|
||||||
'sticky left-0 z-20 justify-center px-0',
|
'sticky left-0 z-20 justify-center px-0',
|
||||||
)}
|
)}
|
||||||
@@ -296,7 +296,7 @@ export default function DataGridBody<T extends object>({
|
|||||||
})}
|
})}
|
||||||
|
|
||||||
{allowInsertColumn && (
|
{allowInsertColumn && (
|
||||||
<Box className="h-12 w-25 border-r-1 border-b-1" />
|
<Box className="h-12 w-25 border-b-1 border-r-1" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,15 @@ import type {
|
|||||||
DataBrowserGridCellProps,
|
DataBrowserGridCellProps,
|
||||||
} from '@/features/database/dataGrid/types/dataBrowser';
|
} from '@/features/database/dataGrid/types/dataBrowser';
|
||||||
import { triggerToast } from '@/utils/toast';
|
import { triggerToast } from '@/utils/toast';
|
||||||
import type { FocusEvent, KeyboardEvent, MouseEvent } from 'react';
|
import type {
|
||||||
|
FocusEvent,
|
||||||
|
JSXElementConstructor,
|
||||||
|
KeyboardEvent,
|
||||||
|
MouseEvent,
|
||||||
|
ReactElement,
|
||||||
|
ReactNode,
|
||||||
|
ReactPortal,
|
||||||
|
} from 'react';
|
||||||
import {
|
import {
|
||||||
Children,
|
Children,
|
||||||
cloneElement,
|
cloneElement,
|
||||||
@@ -308,7 +316,7 @@ function DataGridCellContent<TData extends object = {}, TValue = unknown>({
|
|||||||
isEditable &&
|
isEditable &&
|
||||||
'focus-within:outline-none focus-within:ring-0 focus:ring-0',
|
'focus-within:outline-none focus-within:ring-0 focus:ring-0',
|
||||||
isSelected && 'shadow-outline',
|
isSelected && 'shadow-outline',
|
||||||
isEditing ? 'p-0.5 shadow-outline-dark' : 'py-1.5 px-2',
|
isEditing ? 'p-0.5 shadow-outline-dark' : 'px-2 py-1.5',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
@@ -320,20 +328,28 @@ function DataGridCellContent<TData extends object = {}, TValue = unknown>({
|
|||||||
sx={{ backgroundColor: 'transparent' }}
|
sx={{ backgroundColor: 'transparent' }}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{Children.map(children, (child) => {
|
{Children.map(
|
||||||
if (!isValidElement(child)) {
|
children,
|
||||||
return null;
|
(
|
||||||
}
|
child:
|
||||||
|
| ReactNode
|
||||||
|
| ReactPortal
|
||||||
|
| ReactElement<unknown, string | JSXElementConstructor<any>>,
|
||||||
|
) => {
|
||||||
|
if (!isValidElement(child)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return cloneElement(child, {
|
return cloneElement(child, {
|
||||||
...child.props,
|
...child.props,
|
||||||
onSave: handleSave,
|
onSave: handleSave,
|
||||||
optimisticValue,
|
optimisticValue,
|
||||||
onOptimisticValueChange: setOptimisticValue,
|
onOptimisticValueChange: setOptimisticValue,
|
||||||
temporaryValue,
|
temporaryValue,
|
||||||
onTemporaryValueChange: setTemporaryValue,
|
onTemporaryValueChange: setTemporaryValue,
|
||||||
});
|
});
|
||||||
})}
|
},
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -96,45 +96,52 @@ export default function DataGridHeader<T extends object>({
|
|||||||
}}
|
}}
|
||||||
key={column.id}
|
key={column.id}
|
||||||
>
|
>
|
||||||
<Dropdown.Trigger
|
{column.id === 'selection' ? (
|
||||||
className={twMerge(
|
|
||||||
'focus:outline-none motion-safe:transition-colors',
|
|
||||||
)}
|
|
||||||
disabled={
|
|
||||||
column.isDisabled ||
|
|
||||||
column.id === 'selection' ||
|
|
||||||
(column.disableSortBy && !onRemoveColumn)
|
|
||||||
}
|
|
||||||
hideChevron
|
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
{...headerProps}
|
{...headerProps}
|
||||||
className="relative grid w-full grid-flow-col items-center justify-between p-2"
|
className="relative grid w-full grid-flow-col items-center justify-between p-2"
|
||||||
>
|
>
|
||||||
{column.render('Header')}
|
{column.render('Header')}
|
||||||
|
|
||||||
{allowSort && (
|
|
||||||
<Box component="span" sx={{ color: 'text.primary' }}>
|
|
||||||
{column.isSorted && !column.isSortedDesc && (
|
|
||||||
<ArrowUpIcon className="h-3 w-3" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{column.isSorted && column.isSortedDesc && (
|
|
||||||
<ArrowDownIcon className="h-3 w-3" />
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
|
) : (
|
||||||
{allowResize && !column.disableResizing && (
|
<Dropdown.Trigger
|
||||||
|
className={twMerge(
|
||||||
|
'focus:outline-none motion-safe:transition-colors',
|
||||||
|
)}
|
||||||
|
disabled={
|
||||||
|
column.isDisabled || (column.disableSortBy && !onRemoveColumn)
|
||||||
|
}
|
||||||
|
hideChevron
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
{...column.getResizerProps({
|
{...headerProps}
|
||||||
onClick: (event: Event) => event.stopPropagation(),
|
className="relative grid w-full grid-flow-col items-center justify-between p-2"
|
||||||
})}
|
>
|
||||||
className="absolute top-0 bottom-0 -right-0.5 z-10 h-full w-1.5 group-hover:bg-slate-900 group-hover:bg-opacity-20 group-active:bg-slate-900 group-active:bg-opacity-20 motion-safe:transition-colors"
|
{column.render('Header')}
|
||||||
/>
|
|
||||||
)}
|
{allowSort && (
|
||||||
</Dropdown.Trigger>
|
<Box component="span" sx={{ color: 'text.primary' }}>
|
||||||
|
{column.isSorted && !column.isSortedDesc && (
|
||||||
|
<ArrowUpIcon className="h-3 w-3" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{column.isSorted && column.isSortedDesc && (
|
||||||
|
<ArrowDownIcon className="h-3 w-3" />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{allowResize && !column.disableResizing && (
|
||||||
|
<span
|
||||||
|
{...column.getResizerProps({
|
||||||
|
onClick: (event: Event) => event.stopPropagation(),
|
||||||
|
})}
|
||||||
|
className="absolute -right-0.5 bottom-0 top-0 z-10 h-full w-1.5 group-hover:bg-slate-900 group-hover:bg-opacity-20 group-active:bg-slate-900 group-active:bg-opacity-20 motion-safe:transition-colors"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Dropdown.Trigger>
|
||||||
|
)}
|
||||||
|
|
||||||
<Dropdown.Content
|
<Dropdown.Content
|
||||||
menu
|
menu
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { useNotFoundRedirect } from '@/features/projects/common/hooks/useNotFoun
|
|||||||
import { useProjectRoutes } from '@/features/projects/common/hooks/useProjectRoutes';
|
import { useProjectRoutes } from '@/features/projects/common/hooks/useProjectRoutes';
|
||||||
import { NextSeo } from 'next-seo';
|
import { NextSeo } from 'next-seo';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export interface ProjectLayoutProps extends AuthenticatedLayoutProps {
|
export interface ProjectLayoutProps extends AuthenticatedLayoutProps {
|
||||||
@@ -48,15 +47,16 @@ function ProjectLayoutContent({
|
|||||||
|
|
||||||
useNotFoundRedirect();
|
useNotFoundRedirect();
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if (isPlatform || !router.isReady) {
|
// if (isPlatform || !router.isReady) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (isRestrictedPath) {
|
// TODO // Double check what restricted path means here
|
||||||
router.push('/local/local');
|
// if (isRestrictedPath) {
|
||||||
}
|
// router.push('/local/local');
|
||||||
}, [isPlatform, isRestrictedPath, router]);
|
// }
|
||||||
|
// }, [isPlatform, isRestrictedPath, router]);
|
||||||
|
|
||||||
if (isRestrictedPath || loading) {
|
if (isRestrictedPath || loading) {
|
||||||
return <LoadingScreen />;
|
return <LoadingScreen />;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { List } from '@/components/ui/v2/List';
|
|||||||
import type { ListItemButtonProps } from '@/components/ui/v2/ListItem';
|
import type { ListItemButtonProps } from '@/components/ui/v2/ListItem';
|
||||||
import { ListItem } from '@/components/ui/v2/ListItem';
|
import { ListItem } from '@/components/ui/v2/ListItem';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -61,6 +62,7 @@ export default function SettingsSidebar({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: SettingsSidebarProps) {
|
}: SettingsSidebarProps) {
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
|
|
||||||
@@ -95,7 +97,7 @@ export default function SettingsSidebar({
|
|||||||
<>
|
<>
|
||||||
<Backdrop
|
<Backdrop
|
||||||
open={expanded}
|
open={expanded}
|
||||||
className="absolute top-0 left-0 bottom-0 right-0 z-[34] md:hidden"
|
className="absolute bottom-0 left-0 right-0 top-0 z-[34] md:hidden"
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
onClick={() => setExpanded(false)}
|
onClick={() => setExpanded(false)}
|
||||||
@@ -112,7 +114,7 @@ export default function SettingsSidebar({
|
|||||||
<Box
|
<Box
|
||||||
component="aside"
|
component="aside"
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'absolute top-0 z-[35] h-full w-full overflow-auto border-r-1 px-2 pt-2 pb-17 motion-safe:transition-transform md:relative md:z-0 md:h-full md:py-2.5 md:transition-none',
|
'absolute top-0 z-[35] h-full w-full overflow-auto border-r-1 px-2 pb-17 pt-2 motion-safe:transition-transform md:relative md:z-0 md:h-full md:py-2.5 md:transition-none',
|
||||||
expanded ? 'translate-x-0' : '-translate-x-full md:translate-x-0',
|
expanded ? 'translate-x-0' : '-translate-x-full md:translate-x-0',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
@@ -181,7 +183,12 @@ export default function SettingsSidebar({
|
|||||||
SMTP
|
SMTP
|
||||||
</SettingsNavLink>
|
</SettingsNavLink>
|
||||||
|
|
||||||
<SettingsNavLink href="/git" exact={false} onClick={handleSelect}>
|
<SettingsNavLink
|
||||||
|
href="/git"
|
||||||
|
exact={false}
|
||||||
|
onClick={handleSelect}
|
||||||
|
disabled={!isPlatform}
|
||||||
|
>
|
||||||
Git
|
Git
|
||||||
</SettingsNavLink>
|
</SettingsNavLink>
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import { Input, inputClasses } from '@/components/ui/v2/Input';
|
|||||||
import { OptionBase } from '@/components/ui/v2/Option';
|
import { OptionBase } from '@/components/ui/v2/Option';
|
||||||
import { OptionGroupBase } from '@/components/ui/v2/OptionGroup';
|
import { OptionGroupBase } from '@/components/ui/v2/OptionGroup';
|
||||||
import type { StyledComponent } from '@emotion/styled';
|
import type { StyledComponent } from '@emotion/styled';
|
||||||
import type { UseAutocompleteProps } from '@mui/base/AutocompleteUnstyled';
|
import type { UseAutocompleteProps } from '@mui/base/useAutocomplete';
|
||||||
import { createFilterOptions } from '@mui/base/AutocompleteUnstyled';
|
import { createFilterOptions } from '@mui/base/useAutocomplete';
|
||||||
import PopperUnstyled from '@mui/base/PopperUnstyled';
|
import { Popper } from '@mui/base'
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import type { AutocompleteProps as MaterialAutocompleteProps } from '@mui/material/Autocomplete';
|
import type { AutocompleteProps as MaterialAutocompleteProps } from '@mui/material/Autocomplete';
|
||||||
import MaterialAutocomplete, {
|
import MaterialAutocomplete, {
|
||||||
@@ -142,7 +142,7 @@ const StyledOptionBase = styled(OptionBase)(({ theme }) => ({
|
|||||||
gap: theme.spacing(0.5),
|
gap: theme.spacing(0.5),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const AutocompletePopper = styled(PopperUnstyled)(({ theme }) => ({
|
export const AutocompletePopper = styled(Popper)(({ theme }) => ({
|
||||||
zIndex: theme.zIndex.modal + 1,
|
zIndex: theme.zIndex.modal + 1,
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
minWidth: 320,
|
minWidth: 320,
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import type {
|
import type { BoxProps as MaterialBoxProps } from '@mui/material/Box';
|
||||||
BoxProps as MaterialBoxProps,
|
|
||||||
BoxTypeMap,
|
|
||||||
} from '@mui/material/Box';
|
|
||||||
import MaterialBox from '@mui/material/Box';
|
import MaterialBox from '@mui/material/Box';
|
||||||
|
import { type BoxTypeMap } from '@mui/system';
|
||||||
import type { ForwardedRef, PropsWithoutRef } from 'react';
|
import type { ForwardedRef, PropsWithoutRef } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { PlusCircleIcon } from '@/components/ui/v2/icons/PlusCircleIcon';
|
import { PlusCircleIcon } from '@/components/ui/v2/icons/PlusCircleIcon';
|
||||||
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
||||||
import type { ComponentMeta, ComponentStory } from '@storybook/react';
|
import type { Meta, StoryFn } from '@storybook/react';
|
||||||
import type { ButtonProps } from './Button';
|
import type { ButtonProps } from './Button';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
|
|
||||||
@@ -24,9 +24,9 @@ export default {
|
|||||||
control: { type: 'radio' },
|
control: { type: 'radio' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as ComponentMeta<typeof Button>;
|
} as Meta<typeof Button>;
|
||||||
|
|
||||||
const Template: ComponentStory<typeof Button> = function Template(
|
const Template: StoryFn<ButtonProps> = function TemplateFunction(
|
||||||
args: ButtonProps,
|
args: ButtonProps,
|
||||||
) {
|
) {
|
||||||
return <Button {...args} />;
|
return <Button {...args} />;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export interface ChipProps extends MaterialChipProps {
|
|||||||
/**
|
/**
|
||||||
* Custom component for the root node.
|
* Custom component for the root node.
|
||||||
*/
|
*/
|
||||||
component?: string | ElementType;
|
component?: ElementType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Chip = styled(MaterialChip)<ChipProps>(({ theme }) => ({
|
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 graphqlError = error.graphQLErrors?.[0];
|
||||||
|
const graphqlExtensionsError = graphqlError?.extensions?.internal
|
||||||
|
?.error as { message: string };
|
||||||
|
return graphqlError.message || graphqlExtensionsError?.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.
|
* Custom component for the root node.
|
||||||
*/
|
*/
|
||||||
component?: string | ElementType;
|
component?: ElementType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HelperText = styled(MaterialFormHelperText)<HelperTextProps>({
|
const HelperText = styled(MaterialFormHelperText)<HelperTextProps>({
|
||||||
|
|||||||
@@ -1,41 +1,42 @@
|
|||||||
import type { OptionUnstyledProps } from '@mui/base/OptionUnstyled';
|
import {
|
||||||
import OptionUnstyled, {
|
Option as BaseOption,
|
||||||
optionUnstyledClasses,
|
optionClasses as baseOptionClasses,
|
||||||
} from '@mui/base/OptionUnstyled';
|
type OptionProps as BaseOptionProps,
|
||||||
|
} from '@mui/base';
|
||||||
import { darken, styled } from '@mui/material';
|
import { darken, styled } from '@mui/material';
|
||||||
import type { ForwardedRef } from 'react';
|
import type { ForwardedRef } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import OptionBase from './OptionBase';
|
import OptionBase from './OptionBase';
|
||||||
|
|
||||||
export interface OptionProps<TValue extends {}>
|
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']),
|
transition: theme.transitions.create(['background-color']),
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
[`&.${optionUnstyledClasses.selected}`]: {
|
[`&.${baseOptionClasses.selected}`]: {
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
? `${darken(theme.palette.action.hover, 0.1)} !important`
|
? `${darken(theme.palette.action.hover, 0.1)} !important`
|
||||||
: `${darken(theme.palette.action.hover, 0.05)} !important`,
|
: `${darken(theme.palette.action.hover, 0.05)} !important`,
|
||||||
},
|
},
|
||||||
[`&.${optionUnstyledClasses.selected}:hover, &.${optionUnstyledClasses.selected}.${optionUnstyledClasses.highlighted}`]:
|
[`&.${baseOptionClasses.selected}:hover, &.${baseOptionClasses.selected}.${baseOptionClasses.highlighted}`]:
|
||||||
{
|
{
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
? `${darken(theme.palette.action.hover, 0.25)} !important`
|
? `${darken(theme.palette.action.hover, 0.25)} !important`
|
||||||
: `${darken(theme.palette.action.hover, 0.075)} !important`,
|
: `${darken(theme.palette.action.hover, 0.075)} !important`,
|
||||||
},
|
},
|
||||||
[`&.${optionUnstyledClasses.highlighted}, &:hover`]: {
|
[`&.${baseOptionClasses.highlighted}, &:hover`]: {
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
? `${darken(theme.palette.action.hover, 0.15)} !important`
|
? `${darken(theme.palette.action.hover, 0.15)} !important`
|
||||||
: `${theme.palette.action.hover} !important`,
|
: `${theme.palette.action.hover} !important`,
|
||||||
},
|
},
|
||||||
[`&.${optionUnstyledClasses.disabled}`]: {
|
[`&.${baseOptionClasses.disabled}`]: {
|
||||||
color: theme.palette.text.disabled,
|
color: theme.palette.text.disabled,
|
||||||
},
|
},
|
||||||
[`&.${optionUnstyledClasses.disabled}:hover`]: {
|
[`&.${baseOptionClasses.disabled}:hover`]: {
|
||||||
backgroundColor: 'transparent !important',
|
backgroundColor: 'transparent !important',
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import type { OptionGroupUnstyledProps } from '@mui/base/OptionGroupUnstyled';
|
import {
|
||||||
import OptionGroupUnstyled from '@mui/base/OptionGroupUnstyled';
|
OptionGroup as BaseOptionGroup,
|
||||||
|
type OptionGroupProps as BaseOptionGroupProps,
|
||||||
|
} from '@mui/base';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import type { ForwardedRef } from 'react';
|
import type { ForwardedRef } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import OptionGroupBase from './OptionGroupBase';
|
import OptionGroupBase from './OptionGroupBase';
|
||||||
|
|
||||||
export interface OptionGroupProps extends OptionGroupUnstyledProps {}
|
export interface OptionGroupProps extends BaseOptionGroupProps {}
|
||||||
|
|
||||||
const StyledGroupRoot = styled('li')(({ theme }) => ({
|
const StyledGroupRoot = styled('li')(({ theme }) => ({
|
||||||
listStyle: 'none',
|
listStyle: 'none',
|
||||||
@@ -25,7 +27,7 @@ function OptionGroup(
|
|||||||
...externalSlots,
|
...externalSlots,
|
||||||
};
|
};
|
||||||
|
|
||||||
return <OptionGroupUnstyled {...props} ref={ref} slots={slots} />;
|
return <BaseOptionGroup {...props} ref={ref} slots={slots} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
OptionGroup.displayName = 'NhostOptionGroup';
|
OptionGroup.displayName = 'NhostOptionGroup';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Option } from '@/components/ui/v2/Option';
|
import { Option } from '@/components/ui/v2/Option';
|
||||||
import type { ComponentMeta, ComponentStory } from '@storybook/react';
|
import type { Meta, StoryFn } from '@storybook/react';
|
||||||
import type { SelectProps } from './Select';
|
import type { SelectProps } from './Select';
|
||||||
import Select from './Select';
|
import Select from './Select';
|
||||||
|
|
||||||
@@ -7,11 +7,9 @@ export default {
|
|||||||
title: 'UI Library / Select',
|
title: 'UI Library / Select',
|
||||||
component: Select,
|
component: Select,
|
||||||
argTypes: {},
|
argTypes: {},
|
||||||
} as ComponentMeta<typeof Select>;
|
} as Meta<typeof Select>;
|
||||||
|
|
||||||
const Template: ComponentStory<typeof Select> = function Template(
|
const Template: StoryFn<SelectProps<any>> = function TemplateFunction(args) {
|
||||||
args: SelectProps<any>,
|
|
||||||
) {
|
|
||||||
return (
|
return (
|
||||||
<Select className="w-64" {...args}>
|
<Select className="w-64" {...args}>
|
||||||
<Option value="value1">Value 1</Option>
|
<Option value="value1">Value 1</Option>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { FormControlProps } from '@/components/ui/v2/FormControl';
|
import type { FormControlProps } from '@/components/ui/v2/FormControl';
|
||||||
import { FormControl } from '@/components/ui/v2/FormControl';
|
import { FormControl } from '@/components/ui/v2/FormControl';
|
||||||
import PopperUnstyled from '@mui/base/PopperUnstyled';
|
import { Popper as BasePopper } from '@mui/base/Popper';
|
||||||
import type { SelectUnstyledProps } from '@mui/base/SelectUnstyled';
|
import type { SelectProps as BaseSelectProps } from '@mui/base/Select';
|
||||||
import SelectUnstyled from '@mui/base/SelectUnstyled';
|
import { Select as BaseSelect } from '@mui/base/Select';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/system';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type { ForwardedRef, PropsWithoutRef } from 'react';
|
import type { ForwardedRef, PropsWithoutRef } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
@@ -11,7 +11,7 @@ import type { ToggleButtonProps } from './ToggleButton';
|
|||||||
import ToggleButton from './ToggleButton';
|
import ToggleButton from './ToggleButton';
|
||||||
|
|
||||||
export interface SelectProps<TValue extends {}>
|
export interface SelectProps<TValue extends {}>
|
||||||
extends SelectUnstyledProps<TValue>,
|
extends BaseSelectProps<TValue, false>,
|
||||||
Pick<
|
Pick<
|
||||||
FormControlProps,
|
FormControlProps,
|
||||||
| 'fullWidth'
|
| 'fullWidth'
|
||||||
@@ -25,7 +25,7 @@ export interface SelectProps<TValue extends {}>
|
|||||||
/**
|
/**
|
||||||
* Props for component slots.
|
* Props for component slots.
|
||||||
*/
|
*/
|
||||||
slotProps?: SelectUnstyledProps<TValue>['slotProps'] & {
|
slotProps?: BaseSelectProps<TValue, false>['slotProps'] & {
|
||||||
root?: Partial<PropsWithoutRef<ToggleButtonProps>>;
|
root?: Partial<PropsWithoutRef<ToggleButtonProps>>;
|
||||||
label?: Partial<FormControlProps['labelProps']>;
|
label?: Partial<FormControlProps['labelProps']>;
|
||||||
formControl?: Partial<FormControlProps>;
|
formControl?: Partial<FormControlProps>;
|
||||||
@@ -59,8 +59,8 @@ const StyledListbox = styled('ul')(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledPopper = styled(PopperUnstyled)`
|
const StyledPopper = styled(BasePopper)`
|
||||||
z-index: 10;
|
z-index: 9999;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function Select<TValue>(
|
function Select<TValue>(
|
||||||
@@ -80,7 +80,7 @@ function Select<TValue>(
|
|||||||
}: SelectProps<TValue>,
|
}: SelectProps<TValue>,
|
||||||
ref: ForwardedRef<HTMLButtonElement>,
|
ref: ForwardedRef<HTMLButtonElement>,
|
||||||
) {
|
) {
|
||||||
const slots: SelectUnstyledProps<TValue>['slots'] = {
|
const slots: BaseSelectProps<TValue, false>['slots'] = {
|
||||||
root: ToggleButton,
|
root: ToggleButton,
|
||||||
popper: StyledPopper,
|
popper: StyledPopper,
|
||||||
listbox: StyledListbox,
|
listbox: StyledListbox,
|
||||||
@@ -107,7 +107,7 @@ function Select<TValue>(
|
|||||||
htmlFor: props.id,
|
htmlFor: props.id,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectUnstyled
|
<BaseSelect
|
||||||
aria-label={typeof label === 'string' ? label : undefined}
|
aria-label={typeof label === 'string' ? label : undefined}
|
||||||
{...props}
|
{...props}
|
||||||
className={clsx(error && 'error')}
|
className={clsx(error && 'error')}
|
||||||
@@ -117,7 +117,6 @@ function Select<TValue>(
|
|||||||
...slotProps,
|
...slotProps,
|
||||||
root: {
|
root: {
|
||||||
...slotProps?.root,
|
...slotProps?.root,
|
||||||
placeholder,
|
|
||||||
},
|
},
|
||||||
listbox: {
|
listbox: {
|
||||||
...slotProps?.listbox,
|
...slotProps?.listbox,
|
||||||
@@ -132,7 +131,7 @@ function Select<TValue>(
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</SelectUnstyled>
|
</BaseSelect>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { ChevronDownIcon } from '@/components/ui/v2/icons/ChevronDownIcon';
|
import { ChevronDownIcon } from '@/components/ui/v2/icons/ChevronDownIcon';
|
||||||
import { ChevronUpIcon } from '@/components/ui/v2/icons/ChevronUpIcon';
|
import { ChevronUpIcon } from '@/components/ui/v2/icons/ChevronUpIcon';
|
||||||
import type { ButtonUnstyledProps } from '@mui/base/ButtonUnstyled';
|
import {
|
||||||
import ButtonUnstyled from '@mui/base/ButtonUnstyled';
|
Button as ButtonUnstyled,
|
||||||
import { selectUnstyledClasses } from '@mui/base/SelectUnstyled';
|
type ButtonProps as ButtonUnstyledProps,
|
||||||
|
} from '@mui/base';
|
||||||
|
import { selectClasses as selectUnstyledClasses } from '@mui/base/Select';
|
||||||
import type { SxProps } from '@mui/material';
|
import type { SxProps } from '@mui/material';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import type { Theme } from '@mui/system';
|
import type { Theme } from '@mui/system';
|
||||||
@@ -24,6 +26,7 @@ export interface ToggleButtonProps
|
|||||||
Omit<DetailedHTMLProps<HTMLProps<HTMLSpanElement>, HTMLSpanElement>, 'as'>
|
Omit<DetailedHTMLProps<HTMLProps<HTMLSpanElement>, HTMLSpanElement>, 'as'>
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
placeholder?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledButton = styled(ButtonUnstyled)(({ theme }) => ({
|
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 type { SwitchProps } from './Switch';
|
||||||
import Switch from './Switch';
|
import Switch from './Switch';
|
||||||
|
|
||||||
@@ -6,9 +6,9 @@ export default {
|
|||||||
title: 'UI Library / Switch',
|
title: 'UI Library / Switch',
|
||||||
component: Switch,
|
component: Switch,
|
||||||
argTypes: {},
|
argTypes: {},
|
||||||
} as ComponentMeta<typeof Switch>;
|
} as Meta<typeof Switch>;
|
||||||
|
|
||||||
const Template: ComponentStory<typeof Switch> = function Template(
|
const Template: StoryFn<SwitchProps> = function TemplateFunction(
|
||||||
args: SwitchProps,
|
args: SwitchProps,
|
||||||
) {
|
) {
|
||||||
return <Switch label="Accept Rules" {...args} />;
|
return <Switch label="Accept Rules" {...args} />;
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import type { FormControlLabelProps } from '@/components/ui/v2/FormControlLabel';
|
import type { FormControlLabelProps } from '@/components/ui/v2/FormControlLabel';
|
||||||
import { FormControlLabel } from '@/components/ui/v2/FormControlLabel';
|
import { FormControlLabel } from '@/components/ui/v2/FormControlLabel';
|
||||||
import SwitchUnstyled, {
|
import {
|
||||||
switchUnstyledClasses,
|
Switch as BaseSwitch,
|
||||||
} from '@mui/base/SwitchUnstyled';
|
switchClasses as baseSwitchClasses,
|
||||||
import type { SwitchUnstyledProps } from '@mui/base/SwitchUnstyled/SwitchUnstyled.types';
|
} from '@mui/base';
|
||||||
|
import type { SwitchProps as BaseSwitchProps } from '@mui/base/Switch';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import type { ForwardedRef, PropsWithoutRef } from 'react';
|
import type { ForwardedRef, PropsWithoutRef } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
export interface SwitchProps extends SwitchUnstyledProps {
|
export interface SwitchProps extends BaseSwitchProps {
|
||||||
/**
|
/**
|
||||||
* Label to be displayed next to the checkbox.
|
* 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.
|
* Props to be passed to the internal components.
|
||||||
*/
|
*/
|
||||||
slotProps?: SwitchUnstyledProps['slotProps'] & {
|
slotProps?: BaseSwitchProps['slotProps'] & {
|
||||||
/**
|
/**
|
||||||
* Props to be passed to the `Switch` component.
|
* Props to be passed to the `Switch` component.
|
||||||
*/
|
*/
|
||||||
root?: Partial<SwitchUnstyledProps>;
|
root?: Partial<BaseSwitchProps>;
|
||||||
/**
|
/**
|
||||||
* Props to be passed to the `FormControlLabel` component.
|
* Props to be passed to the `FormControlLabel` component.
|
||||||
*/
|
*/
|
||||||
@@ -35,23 +36,23 @@ const StyledFormControlLabel = styled(FormControlLabel)(({ theme }) => ({
|
|||||||
justifyContent: 'start',
|
justifyContent: 'start',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledSwitch = styled(SwitchUnstyled)(({ theme }) => ({
|
const StyledSwitch = styled(BaseSwitch)(({ theme }) => ({
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
width: '40px',
|
width: '40px',
|
||||||
height: '24px',
|
height: '24px',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
|
||||||
[`&.${switchUnstyledClasses.disabled}`]: {
|
[`&.${baseSwitchClasses.disabled}`]: {
|
||||||
cursor: 'not-allowed',
|
cursor: 'not-allowed',
|
||||||
|
|
||||||
[`& .${switchUnstyledClasses.track}`]: {
|
[`& .${baseSwitchClasses.track}`]: {
|
||||||
backgroundColor: theme.palette.grey[200],
|
backgroundColor: theme.palette.grey[200],
|
||||||
color: theme.palette.grey[200],
|
color: theme.palette.grey[200],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
[`& .${switchUnstyledClasses.track}`]: {
|
[`& .${baseSwitchClasses.track}`]: {
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
? theme.palette.grey[500]
|
? theme.palette.grey[500]
|
||||||
@@ -63,7 +64,7 @@ const StyledSwitch = styled(SwitchUnstyled)(({ theme }) => ({
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
},
|
},
|
||||||
|
|
||||||
[` & .${switchUnstyledClasses.thumb}`]: {
|
[` & .${baseSwitchClasses.thumb}`]: {
|
||||||
display: 'block',
|
display: 'block',
|
||||||
width: '18px',
|
width: '18px',
|
||||||
height: '18px',
|
height: '18px',
|
||||||
@@ -77,24 +78,24 @@ const StyledSwitch = styled(SwitchUnstyled)(({ theme }) => ({
|
|||||||
transitionDuration: '120ms',
|
transitionDuration: '120ms',
|
||||||
},
|
},
|
||||||
|
|
||||||
[`&.${switchUnstyledClasses.focusVisible} .${switchUnstyledClasses.thumb}`]: {
|
[`&.${baseSwitchClasses.focusVisible} .${baseSwitchClasses.thumb}`]: {
|
||||||
backgroundColor: theme.palette.action.focus,
|
backgroundColor: theme.palette.action.focus,
|
||||||
boxShadow: '0 0 1px 8px rgba(0, 0, 0, 0.25)',
|
boxShadow: '0 0 1px 8px rgba(0, 0, 0, 0.25)',
|
||||||
},
|
},
|
||||||
|
|
||||||
[`&.${switchUnstyledClasses.checked}`]: {
|
[`&.${baseSwitchClasses.checked}`]: {
|
||||||
[`.${switchUnstyledClasses.thumb}`]: {
|
[`.${baseSwitchClasses.thumb}`]: {
|
||||||
left: '19px',
|
left: '19px',
|
||||||
top: '3px',
|
top: '3px',
|
||||||
backgroundColor: theme.palette.common.white,
|
backgroundColor: theme.palette.common.white,
|
||||||
},
|
},
|
||||||
|
|
||||||
[`.${switchUnstyledClasses.track}`]: {
|
[`.${baseSwitchClasses.track}`]: {
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: theme.palette.primary.main,
|
||||||
},
|
},
|
||||||
|
|
||||||
[`&.${switchUnstyledClasses.disabled}`]: {
|
[`&.${baseSwitchClasses.disabled}`]: {
|
||||||
[`.${switchUnstyledClasses.track}`]: {
|
[`.${baseSwitchClasses.track}`]: {
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
@@ -104,7 +105,7 @@ const StyledSwitch = styled(SwitchUnstyled)(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
[`& .${switchUnstyledClasses.input}`]: {
|
[`& .${baseSwitchClasses.input}`]: {
|
||||||
cursor: 'inherit',
|
cursor: 'inherit',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
|||||||
@@ -4,16 +4,14 @@ import { Box } from '@/components/ui/v2/Box';
|
|||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
import { Checkbox } from '@/components/ui/v2/Checkbox';
|
import { Checkbox } from '@/components/ui/v2/Checkbox';
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import {
|
import {
|
||||||
useDeleteUserAccountMutation,
|
useDeleteUserAccountMutation,
|
||||||
useGetAllWorkspacesAndProjectsQuery,
|
useGetAllWorkspacesAndProjectsQuery,
|
||||||
} from '@/utils/__generated__/graphql';
|
} from '@/utils/__generated__/graphql';
|
||||||
import { type ApolloError } from '@apollo/client';
|
|
||||||
import { useSignOut, useUserData } from '@nhost/nextjs';
|
import { useSignOut, useUserData } from '@nhost/nextjs';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
function ConfirmDeleteAccountModal({
|
function ConfirmDeleteAccountModal({
|
||||||
@@ -44,30 +42,19 @@ function ConfirmDeleteAccountModal({
|
|||||||
const onClickConfirm = async () => {
|
const onClickConfirm = async () => {
|
||||||
setLoadingRemove(true);
|
setLoadingRemove(true);
|
||||||
|
|
||||||
await toast.promise(
|
await execPromiseWithErrorToast(
|
||||||
deleteUserAccount(),
|
async () => {
|
||||||
{
|
await deleteUserAccount();
|
||||||
loading: 'Deleting your account...',
|
onDelete?.();
|
||||||
success: `The account has been deleted successfully.`,
|
close();
|
||||||
error: (arg: ApolloError) => {
|
},
|
||||||
// we need to get the internal error message from the GraphQL error
|
{
|
||||||
const { internal } = arg.graphQLErrors[0]?.extensions || {};
|
loadingMessage: 'Deleting your account...',
|
||||||
const { message } = (internal as Record<string, any>)?.error || {};
|
successMessage: 'The account has been deleted successfully.',
|
||||||
|
errorMessage:
|
||||||
// we use the default Apollo error message if we can't find the
|
'An error occurred while deleting your account. Please try again.',
|
||||||
// internal error message
|
|
||||||
return (
|
|
||||||
message ||
|
|
||||||
arg.message ||
|
|
||||||
'An error occurred while deleting your account. Please try again.'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
onDelete?.();
|
|
||||||
close();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { Form } from '@/components/form/Form';
|
||||||
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
|
import { useUpdateUserDisplayNameMutation } from '@/utils/__generated__/graphql';
|
||||||
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useUserData } from '@nhost/nextjs';
|
||||||
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
|
const validationSchema = Yup.object({
|
||||||
|
displayName: Yup.string()
|
||||||
|
.label('Display Name')
|
||||||
|
.required('This field is required.'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type DisplayNameSettingFormValues = Yup.InferType<
|
||||||
|
typeof validationSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export default function DisplayNameSetting() {
|
||||||
|
const { id: userID, displayName } = useUserData();
|
||||||
|
|
||||||
|
const [updateUserDisplayName] = useUpdateUserDisplayNameMutation();
|
||||||
|
|
||||||
|
const form = useForm<DisplayNameSettingFormValues>({
|
||||||
|
reValidateMode: 'onSubmit',
|
||||||
|
defaultValues: {
|
||||||
|
displayName,
|
||||||
|
},
|
||||||
|
resolver: yupResolver(validationSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { register, formState } = form;
|
||||||
|
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
||||||
|
|
||||||
|
async function handleSubmit(formValues: DisplayNameSettingFormValues) {
|
||||||
|
await execPromiseWithErrorToast(
|
||||||
|
async () => {
|
||||||
|
await updateUserDisplayName({
|
||||||
|
variables: {
|
||||||
|
id: userID,
|
||||||
|
displayName: formValues.displayName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
form.reset({ displayName: formValues.displayName });
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Updating your display name...',
|
||||||
|
successMessage: 'Your display name has been updated successfully.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while trying to update your display name. Please try again.',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<SettingsContainer
|
||||||
|
title="Update your display name"
|
||||||
|
slotProps={{
|
||||||
|
submitButton: {
|
||||||
|
disabled: !isDirty,
|
||||||
|
loading: formState.isSubmitting,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
className="grid grid-flow-row lg:grid-cols-5"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...register('displayName')}
|
||||||
|
className="col-span-2"
|
||||||
|
type="text"
|
||||||
|
id="display-name"
|
||||||
|
label="Display Name"
|
||||||
|
fullWidth
|
||||||
|
helperText={formState.errors.displayName?.message}
|
||||||
|
error={Boolean(formState.errors.displayName)}
|
||||||
|
/>
|
||||||
|
</SettingsContainer>
|
||||||
|
</Form>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './DisplayNameSetting';
|
||||||
|
export { default as DisplayNameSetting } from './DisplayNameSetting';
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { Form } from '@/components/form/Form';
|
||||||
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { useNhostClient, useUserData } from '@nhost/nextjs';
|
||||||
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
|
const validationSchema = Yup.object({
|
||||||
|
email: Yup.string().label('Email').email().required(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type EmailSettingFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
|
export default function EmailSetting() {
|
||||||
|
const nhost = useNhostClient();
|
||||||
|
const { email } = useUserData();
|
||||||
|
|
||||||
|
const form = useForm<EmailSettingFormValues>({
|
||||||
|
reValidateMode: 'onSubmit',
|
||||||
|
defaultValues: { email },
|
||||||
|
resolver: yupResolver(validationSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { register, formState } = form;
|
||||||
|
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
||||||
|
|
||||||
|
async function handleSubmit(formValues: EmailSettingFormValues) {
|
||||||
|
await execPromiseWithErrorToast(
|
||||||
|
async () => {
|
||||||
|
await nhost.auth.changeEmail({
|
||||||
|
newEmail: formValues.email,
|
||||||
|
options: {
|
||||||
|
redirectTo: `${window.location.origin}/account`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
form.reset({ email: formValues.email });
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadingMessage: 'Updating your email...',
|
||||||
|
successMessage:
|
||||||
|
'Please check your inbox. Follow the link to finalize changing your email.',
|
||||||
|
errorMessage:
|
||||||
|
'An error occurred while trying to update your email. Please try again.',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<SettingsContainer
|
||||||
|
title="Update your email"
|
||||||
|
slotProps={{
|
||||||
|
submitButton: {
|
||||||
|
disabled: !isDirty,
|
||||||
|
loading: formState.isSubmitting,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
className="grid grid-flow-row lg:grid-cols-5"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...register('email')}
|
||||||
|
className="col-span-2"
|
||||||
|
id="email"
|
||||||
|
spellCheck="false"
|
||||||
|
autoCapitalize="none"
|
||||||
|
type="email"
|
||||||
|
label="Email"
|
||||||
|
hideEmptyHelperText
|
||||||
|
fullWidth
|
||||||
|
helperText={formState.errors.email?.message}
|
||||||
|
error={Boolean(formState.errors.email)}
|
||||||
|
/>
|
||||||
|
</SettingsContainer>
|
||||||
|
</Form>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './EmailSetting';
|
||||||
|
export { default as EmailSetting } from './EmailSetting';
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user