Compare commits
97 Commits
@nhost/rea
...
@nhost/nex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60bcd8f949 | ||
|
|
e28975d6a5 | ||
|
|
33284d3cf0 | ||
|
|
1dbd65eb0e | ||
|
|
6eec78f9c5 | ||
|
|
e3f0732108 | ||
|
|
807b8c049a | ||
|
|
998c0376bf | ||
|
|
cf5423dac6 | ||
|
|
a2efeed36f | ||
|
|
533b74d82d | ||
|
|
42cf86c8f1 | ||
|
|
70e74f2f3d | ||
|
|
a01985466e | ||
|
|
8ea4210582 | ||
|
|
58919ba763 | ||
|
|
86f3f8d505 | ||
|
|
201abb89fd | ||
|
|
cf6b712b20 | ||
|
|
b51986289d | ||
|
|
c640c50c70 | ||
|
|
b3ff6adcc2 | ||
|
|
dbadf59092 | ||
|
|
bedbb82cd7 | ||
|
|
79ce7cae2f | ||
|
|
9c9137f813 | ||
|
|
502abadbae | ||
|
|
b6b67773d1 | ||
|
|
33ce95536d | ||
|
|
11f9ed7507 | ||
|
|
ac6d1b6e01 | ||
|
|
77fba27d12 | ||
|
|
7163854767 | ||
|
|
66bd4504d7 | ||
|
|
a03fb2cf82 | ||
|
|
87a37cfc08 | ||
|
|
6fb0cc27aa | ||
|
|
2c33051f83 | ||
|
|
a9413af6e0 | ||
|
|
f4f0353f2e | ||
|
|
defffd8bc4 | ||
|
|
614c20cbbf | ||
|
|
aef4a0a4fc | ||
|
|
d0c9f4cd17 | ||
|
|
e2646cab55 | ||
|
|
d5077c7ca4 | ||
|
|
c6d5c5cc8c | ||
|
|
f1d9b472d1 | ||
|
|
c6dc7f44df | ||
|
|
3cea460c36 | ||
|
|
4c351714f5 | ||
|
|
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 |
@@ -12,7 +12,7 @@ inputs:
|
|||||||
runs:
|
runs:
|
||||||
using: 'composite'
|
using: 'composite'
|
||||||
steps:
|
steps:
|
||||||
- uses: pnpm/action-setup@v2.2.4
|
- uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 8.10.5
|
version: 8.10.5
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|||||||
@@ -17,5 +17,10 @@ NEXT_PUBLIC_GITHUB_APP_INSTALL_URL=<github_app_install_url>
|
|||||||
NEXT_PUBLIC_ANALYTICS_WRITE_KEY=<analytics_write_key>
|
NEXT_PUBLIC_ANALYTICS_WRITE_KEY=<analytics_write_key>
|
||||||
NEXT_PUBLIC_NHOST_BRAGI_WEBSOCKET=<nhost_bragi_websocket>
|
NEXT_PUBLIC_NHOST_BRAGI_WEBSOCKET=<nhost_bragi_websocket>
|
||||||
|
|
||||||
|
NEXT_PUBLIC_ZENDESK_URL=
|
||||||
|
NEXT_PUBLIC_ZENDESK_API_KEY=
|
||||||
|
NEXT_PUBLIC_ZENDESK_USER_EMAIL=
|
||||||
|
|
||||||
|
|
||||||
CODEGEN_GRAPHQL_URL=https://local.graphql.nhost.run/v1
|
CODEGEN_GRAPHQL_URL=https://local.graphql.nhost.run/v1
|
||||||
CODEGEN_HASURA_ADMIN_SECRET=nhost-admin-secret
|
CODEGEN_HASURA_ADMIN_SECRET=nhost-admin-secret
|
||||||
|
|||||||
@@ -1,5 +1,176 @@
|
|||||||
# @nhost/dashboard
|
# @nhost/dashboard
|
||||||
|
|
||||||
|
## 1.23.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 33284d3: fix: don't show double scrollbar in configuration editor
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@12.0.3
|
||||||
|
- @nhost/nextjs@2.1.17
|
||||||
|
|
||||||
|
## 1.22.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 998c037: fix: align drop-down list in select component
|
||||||
|
- 807b8c0: fix: show city name in region selection for project creation
|
||||||
|
|
||||||
|
## 1.21.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- a2efeed: fix: improve project health error handling, add unknown state and polling interval for health state
|
||||||
|
|
||||||
|
## 1.20.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 8ea4210: fix: error toasts can be closed individually, instead of dismissing all toasts at once
|
||||||
|
- 58919ba: chore: add blink animation when project health service is updating
|
||||||
|
|
||||||
|
## 1.19.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- b519862: fix: get configuration in configuration editor using local development environment
|
||||||
|
|
||||||
|
## 1.18.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 502abad: feat: add services health checks indicators to the overview page
|
||||||
|
- b3ff6ad: chore: update title text on service status modal
|
||||||
|
- dbadf59: feat: add project configuration TOML editor to the settings page
|
||||||
|
|
||||||
|
## 1.17.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 77fba27: fix: postgres version validation when activating ai in ai settings page
|
||||||
|
- ac6d1b6: feat: use name instead of awsName
|
||||||
|
|
||||||
|
## 1.16.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 87a37cf: fix: remove unnecessary isPlatform check from verify button disable logic on custom domains
|
||||||
|
- @nhost/react-apollo@12.0.2
|
||||||
|
- @nhost/nextjs@2.1.16
|
||||||
|
|
||||||
|
## 1.16.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- a9413af: fix: update `GetAllWorkspacesAndProjects` query polling to use exponential backoff
|
||||||
|
- @nhost/react-apollo@12.0.1
|
||||||
|
- @nhost/nextjs@2.1.15
|
||||||
|
|
||||||
|
## 1.16.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@12.0.0
|
||||||
|
- @nhost/nextjs@2.1.14
|
||||||
|
|
||||||
|
## 1.16.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- c6d5c5c: feat: add toggle switch to enable/disable public access in the database settings
|
||||||
|
|
||||||
|
## 1.15.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@11.0.4
|
||||||
|
- @nhost/nextjs@2.1.13
|
||||||
|
|
||||||
|
## 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
|
## 1.11.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ ENV NEXT_PUBLIC_NHOST_STORAGE_URL __NEXT_PUBLIC_NHOST_STORAGE_URL__
|
|||||||
ENV NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL __NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__
|
ENV NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL __NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__
|
||||||
ENV NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL __NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__
|
ENV NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL __NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__
|
||||||
ENV NEXT_PUBLIC_NHOST_HASURA_API_URL __NEXT_PUBLIC_NHOST_HASURA_API_URL__
|
ENV NEXT_PUBLIC_NHOST_HASURA_API_URL __NEXT_PUBLIC_NHOST_HASURA_API_URL__
|
||||||
|
ENV NEXT_PUBLIC_NHOST_CONFIGSERVER_URL __NEXT_PUBLIC_NHOST_CONFIGSERVER_URL__
|
||||||
|
|
||||||
RUN yarn global add pnpm@8.10.5
|
RUN yarn global add pnpm@8.10.5
|
||||||
COPY .gitignore .gitignore
|
COPY .gitignore .gitignore
|
||||||
|
|||||||
@@ -21,5 +21,6 @@ find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_STORAGE_URL__~${NEXT_
|
|||||||
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__~${NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL}~g" {} +
|
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__~${NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL}~g" {} +
|
||||||
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__~${NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL}~g" {} +
|
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__~${NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL}~g" {} +
|
||||||
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_API_URL__~${NEXT_PUBLIC_NHOST_HASURA_API_URL}~g" {} +
|
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_API_URL__~${NEXT_PUBLIC_NHOST_HASURA_API_URL}~g" {} +
|
||||||
|
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_CONFIGSERVER_URL__~${NEXT_PUBLIC_NHOST_CONFIGSERVER_URL}~g" {} +
|
||||||
|
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|||||||
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 }}'
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/dashboard",
|
"name": "@nhost/dashboard",
|
||||||
"version": "1.11.2",
|
"version": "1.23.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
@@ -19,48 +19,53 @@
|
|||||||
"e2e": "pnpm install-browsers && pnpm playwright test"
|
"e2e": "pnpm install-browsers && pnpm playwright test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.9.5",
|
"@apollo/client": "^3.9.9",
|
||||||
"@codemirror/lang-sql": "^6.6.0",
|
"@codemirror/lang-sql": "^6.6.2",
|
||||||
|
"@codemirror/language": "^6.10.1",
|
||||||
|
"@codemirror/legacy-modes": "^6.4.0",
|
||||||
"@emotion/cache": "^11.11.0",
|
"@emotion/cache": "^11.11.0",
|
||||||
"@emotion/react": "^11.11.4",
|
"@emotion/react": "^11.11.4",
|
||||||
"@emotion/server": "^11.11.0",
|
"@emotion/server": "^11.11.0",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.5",
|
||||||
"@fontsource/inter": "^5.0.16",
|
"@fontsource/inter": "^5.0.17",
|
||||||
"@fontsource/roboto-mono": "^5.0.16",
|
"@fontsource/roboto-mono": "^5.0.17",
|
||||||
"@graphiql/react": "^0.20.3",
|
"@graphiql/react": "^0.22.3",
|
||||||
"@graphiql/toolkit": "^0.9.1",
|
"@graphiql/toolkit": "^0.9.1",
|
||||||
"@headlessui/react": "^1.7.18",
|
"@headlessui/react": "^1.7.18",
|
||||||
"@heroicons/react": "^1.0.6",
|
"@heroicons/react": "^1.0.6",
|
||||||
"@hookform/resolvers": "^3.3.4",
|
"@hookform/resolvers": "^3.3.4",
|
||||||
|
"@iarna/toml": "^2.2.5",
|
||||||
"@mui/base": "5.0.0-beta.31",
|
"@mui/base": "5.0.0-beta.31",
|
||||||
"@mui/material": "^5.15.11",
|
"@mui/material": "^5.15.14",
|
||||||
"@mui/system": "^5.15.11",
|
"@mui/system": "^5.15.14",
|
||||||
"@mui/x-date-pickers": "^5.0.20",
|
"@mui/x-date-pickers": "^5.0.20",
|
||||||
"@nhost/nextjs": "workspace:*",
|
"@nhost/nextjs": "workspace:*",
|
||||||
"@nhost/react-apollo": "workspace:*",
|
"@nhost/react-apollo": "workspace:*",
|
||||||
"@segment/snippet": "^4.16.2",
|
"@segment/snippet": "^4.16.2",
|
||||||
"@stripe/react-stripe-js": "^2.5.1",
|
"@stripe/react-stripe-js": "^2.6.2",
|
||||||
"@stripe/stripe-js": "^1.54.2",
|
"@stripe/stripe-js": "^1.54.2",
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
"@tanstack/react-query": "^4.36.1",
|
"@tanstack/react-query": "^4.36.1",
|
||||||
"@tanstack/react-table": "^8.13.2",
|
"@tanstack/react-table": "^8.15.3",
|
||||||
"@tanstack/react-virtual": "^3.1.3",
|
"@tanstack/react-virtual": "^3.2.0",
|
||||||
"@uiw/codemirror-theme-github": "^4.21.24",
|
"@uidotdev/usehooks": "^2.4.1",
|
||||||
"@uiw/react-codemirror": "^4.21.24",
|
"@uiw/codemirror-theme-bbedit": "^4.22.2",
|
||||||
|
"@uiw/codemirror-theme-github": "^4.21.25",
|
||||||
|
"@uiw/react-codemirror": "^4.21.25",
|
||||||
"analytics-node": "^6.2.0",
|
"analytics-node": "^6.2.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
"framer-motion": "^10.18.0",
|
"framer-motion": "^10.18.0",
|
||||||
"generate-password": "^1.7.1",
|
"generate-password": "^1.7.1",
|
||||||
"graphiql": "^3.1.1",
|
"graphiql": "^3.3.1",
|
||||||
"graphql": "16.8.1",
|
"graphql": "16.8.1",
|
||||||
"graphql-request": "^6.1.0",
|
"graphql-request": "^6.1.0",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
"graphql-ws": "^5.15.0",
|
"graphql-ws": "^5.16.0",
|
||||||
"just-kebab-case": "^4.2.0",
|
"just-kebab-case": "^4.2.0",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"next": "^14.1.0",
|
"next": "^14.1.4",
|
||||||
"next-seo": "^6.5.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",
|
||||||
@@ -68,7 +73,7 @@
|
|||||||
"react-children-utilities": "^2.10.0",
|
"react-children-utilities": "^2.10.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-error-boundary": "^4.0.13",
|
"react-error-boundary": "^4.0.13",
|
||||||
"react-hook-form": "^7.50.1",
|
"react-hook-form": "^7.51.2",
|
||||||
"react-hot-toast": "^2.4.1",
|
"react-hot-toast": "^2.4.1",
|
||||||
"react-intersection-observer": "^9.8.1",
|
"react-intersection-observer": "^9.8.1",
|
||||||
"react-is": "18.2.0",
|
"react-is": "18.2.0",
|
||||||
@@ -87,11 +92,11 @@
|
|||||||
"tailwind-merge": "^1.14.0",
|
"tailwind-merge": "^1.14.0",
|
||||||
"utility-types": "^3.11.0",
|
"utility-types": "^3.11.0",
|
||||||
"validator": "^13.11.0",
|
"validator": "^13.11.0",
|
||||||
"yup": "^1.3.3",
|
"yup": "^1.4.0",
|
||||||
"yup-password": "^0.2.2"
|
"yup-password": "^0.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.24.0",
|
"@babel/core": "^7.24.3",
|
||||||
"@faker-js/faker": "^7.6.0",
|
"@faker-js/faker": "^7.6.0",
|
||||||
"@graphql-codegen/cli": "^5.0.2",
|
"@graphql-codegen/cli": "^5.0.2",
|
||||||
"@graphql-codegen/typescript": "^3.0.4",
|
"@graphql-codegen/typescript": "^3.0.4",
|
||||||
@@ -108,20 +113,20 @@
|
|||||||
"@storybook/manager-webpack5": "^6.5.16",
|
"@storybook/manager-webpack5": "^6.5.16",
|
||||||
"@storybook/react": "^7.6.17",
|
"@storybook/react": "^7.6.17",
|
||||||
"@storybook/testing-library": "^0.2.2",
|
"@storybook/testing-library": "^0.2.2",
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.12",
|
||||||
"@testing-library/dom": "^9.3.4",
|
"@testing-library/dom": "^9.3.4",
|
||||||
"@testing-library/jest-dom": "^5.17.0",
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
"@testing-library/react": "^14.2.1",
|
"@testing-library/react": "^14.2.2",
|
||||||
"@testing-library/user-event": "^14.5.2",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"@types/ace": "^0.0.48",
|
"@types/ace": "^0.0.48",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/lodash.debounce": "^4.0.9",
|
"@types/lodash.debounce": "^4.0.9",
|
||||||
"@types/node": "^16.18.86",
|
"@types/node": "^16.18.93",
|
||||||
"@types/pluralize": "^0.0.30",
|
"@types/pluralize": "^0.0.30",
|
||||||
"@types/react": "^18.2.61",
|
"@types/react": "^18.2.73",
|
||||||
"@types/react-dom": "^18.2.19",
|
"@types/react-dom": "^18.2.23",
|
||||||
"@types/react-table": "^7.7.19",
|
"@types/react-table": "^7.7.20",
|
||||||
"@types/shell-quote": "^1.7.5",
|
"@types/shell-quote": "^1.7.5",
|
||||||
"@types/testing-library__jest-dom": "^5.14.9",
|
"@types/testing-library__jest-dom": "^5.14.9",
|
||||||
"@types/validator": "^13.11.9",
|
"@types/validator": "^13.11.9",
|
||||||
@@ -129,7 +134,7 @@
|
|||||||
"@typescript-eslint/parser": "^6.21.0",
|
"@typescript-eslint/parser": "^6.21.0",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"@vitest/coverage-v8": "^0.32.4",
|
"@vitest/coverage-v8": "^0.32.4",
|
||||||
"autoprefixer": "^10.4.17",
|
"autoprefixer": "^10.4.19",
|
||||||
"babel-loader": "^8.3.0",
|
"babel-loader": "^8.3.0",
|
||||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||||
"csstype": "^3.1.3",
|
"csstype": "^3.1.3",
|
||||||
@@ -142,14 +147,14 @@
|
|||||||
"eslint-config-prettier": "^8.10.0",
|
"eslint-config-prettier": "^8.10.0",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint-plugin-react": "^7.34.1",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"jsdom": "^22.1.0",
|
"jsdom": "^22.1.0",
|
||||||
"lint-staged": "^15.2.2",
|
"lint-staged": "^15.2.2",
|
||||||
"msw": "^1.3.2",
|
"msw": "^1.3.3",
|
||||||
"msw-storybook-addon": "^1.10.0",
|
"msw-storybook-addon": "^1.10.0",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"postcss": "^8.4.35",
|
"postcss": "^8.4.38",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"prettier-plugin-organize-imports": "^3.2.4",
|
"prettier-plugin-organize-imports": "^3.2.4",
|
||||||
"prettier-plugin-tailwindcss": "^0.4.1",
|
"prettier-plugin-tailwindcss": "^0.4.1",
|
||||||
@@ -157,11 +162,11 @@
|
|||||||
"require-from-string": "^2.0.2",
|
"require-from-string": "^2.0.2",
|
||||||
"snake-case": "^3.0.4",
|
"snake-case": "^3.0.4",
|
||||||
"storybook-addon-next-router": "^4.0.2",
|
"storybook-addon-next-router": "^4.0.2",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.3",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsconfig-paths-webpack-plugin": "^4.1.0",
|
"tsconfig-paths-webpack-plugin": "^4.1.0",
|
||||||
"vite": "^5.1.4",
|
"vite": "^5.2.7",
|
||||||
"vite-tsconfig-paths": "^4.3.1",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
"vitest": "^0.32.4"
|
"vitest": "^0.32.4"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
|
|
||||||
|
export default function ApplyLocalSettingsDialog() {
|
||||||
|
const { closeDialog } = useDialog();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4 px-6 pb-6">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Text color="secondary">
|
||||||
|
Run{' '}
|
||||||
|
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
|
||||||
|
$ nhost up
|
||||||
|
</code>{' '}
|
||||||
|
using the cli to apply your changes
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="w-full"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => closeDialog()}
|
||||||
|
autoFocus
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as ApplyLocalSettingsDialog } from './ApplyLocalSettingsDialog';
|
||||||
@@ -29,7 +29,7 @@ export default function CountrySelector({
|
|||||||
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]',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { ContactUs } from '@/components/common/ContactUs';
|
|
||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { NavLink } from '@/components/common/NavLink';
|
import { NavLink } from '@/components/common/NavLink';
|
||||||
import { AccountMenu } from '@/components/layout/AccountMenu';
|
import { AccountMenu } from '@/components/layout/AccountMenu';
|
||||||
@@ -9,11 +8,9 @@ import { Logo } from '@/components/presentational/Logo';
|
|||||||
import { Box } from '@/components/ui/v2/Box';
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
import { Chip } from '@/components/ui/v2/Chip';
|
import { Chip } from '@/components/ui/v2/Chip';
|
||||||
import { Dropdown } from '@/components/ui/v2/Dropdown';
|
|
||||||
import { GraphiteIcon } from '@/components/ui/v2/icons/GraphiteIcon';
|
import { GraphiteIcon } from '@/components/ui/v2/icons/GraphiteIcon';
|
||||||
import { DevAssistant } from '@/features/ai/DevAssistant';
|
import { DevAssistant } from '@/features/ai/DevAssistant';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCurrentUserOwner';
|
|
||||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { ApplicationStatus } from '@/types/application';
|
import { ApplicationStatus } from '@/types/application';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||||
@@ -38,8 +35,6 @@ export default function Header({ className, ...props }: HeaderProps) {
|
|||||||
const { currentProject, refetch: refetchProject } =
|
const { currentProject, refetch: refetchProject } =
|
||||||
useCurrentWorkspaceAndProject();
|
useCurrentWorkspaceAndProject();
|
||||||
|
|
||||||
const isOwner = useIsCurrentUserOwner();
|
|
||||||
|
|
||||||
const isProjectUpdating =
|
const isProjectUpdating =
|
||||||
currentProject?.appStates[0]?.stateId === ApplicationStatus.Updating;
|
currentProject?.appStates[0]?.stateId === ApplicationStatus.Updating;
|
||||||
|
|
||||||
@@ -105,25 +100,19 @@ export default function Header({ className, ...props }: HeaderProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{isPlatform && (
|
{isPlatform && (
|
||||||
<Dropdown.Root>
|
<NavLink
|
||||||
<Dropdown.Trigger
|
underline="none"
|
||||||
hideChevron
|
href="/support"
|
||||||
className="rounded-md px-2.5 py-1.5 text-sm motion-safe:transition-colors"
|
className="mr-2 rounded-md px-2.5 py-1.5 text-sm motion-safe:transition-colors"
|
||||||
>
|
sx={{
|
||||||
Contact us
|
color: 'text.primary',
|
||||||
</Dropdown.Trigger>
|
'&:hover': { backgroundColor: 'grey.200' },
|
||||||
|
}}
|
||||||
<Dropdown.Content
|
target="_blank"
|
||||||
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
rel="noopener noreferrer"
|
||||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
>
|
||||||
>
|
Support
|
||||||
<ContactUs
|
</NavLink>
|
||||||
className="max-w-md"
|
|
||||||
isTeam={currentProject?.plan?.name === 'Team'}
|
|
||||||
isOwner={isOwner}
|
|
||||||
/>
|
|
||||||
</Dropdown.Content>
|
|
||||||
</Dropdown.Root>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<NavLink
|
<NavLink
|
||||||
|
|||||||
@@ -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 />;
|
||||||
|
|||||||
@@ -45,48 +45,53 @@ export default function SettingsLayout({
|
|||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{ backgroundColor: 'background.default' }}
|
sx={{ backgroundColor: 'background.default' }}
|
||||||
className="flex w-full flex-auto flex-col overflow-scroll overflow-x-hidden"
|
className="flex w-full flex-auto flex-col overflow-y-auto overflow-x-hidden"
|
||||||
>
|
>
|
||||||
<RetryableErrorBoundary>
|
<Box
|
||||||
<div className="flex flex-col space-y-2">
|
sx={{ backgroundColor: 'background.default' }}
|
||||||
{hasGitRepo && (
|
className="flex h-full flex-col"
|
||||||
<Alert
|
>
|
||||||
severity="warning"
|
<RetryableErrorBoundary>
|
||||||
className="grid grid-flow-row place-content-center gap-2"
|
<div className="flex flex-col space-y-2">
|
||||||
>
|
{hasGitRepo && (
|
||||||
<Text color="warning" className="text-sm ">
|
<Alert
|
||||||
As you have a connected repository, make sure to synchronize
|
severity="warning"
|
||||||
your changes with{' '}
|
className="grid grid-flow-row place-content-center gap-2"
|
||||||
<code
|
>
|
||||||
className={twMerge(
|
<Text color="warning" className="text-sm ">
|
||||||
'rounded-md px-2 py-px',
|
As you have a connected repository, make sure to synchronize
|
||||||
theme.palette.mode === 'dark'
|
your changes with{' '}
|
||||||
? 'bg-brown text-copper'
|
<code
|
||||||
: 'bg-slate-200 text-slate-700',
|
className={twMerge(
|
||||||
)}
|
'rounded-md px-2 py-px',
|
||||||
>
|
theme.palette.mode === 'dark'
|
||||||
nhost config pull
|
? 'bg-brown text-copper'
|
||||||
</code>{' '}
|
: 'bg-slate-200 text-slate-700',
|
||||||
or they may be reverted with the next push.
|
)}
|
||||||
<br />
|
>
|
||||||
If there are multiple projects linked to the same repository
|
nhost config pull
|
||||||
and you only want these changes to apply to a subset of them,
|
</code>{' '}
|
||||||
please check out{' '}
|
or they may be reverted with the next push.
|
||||||
<a
|
<br />
|
||||||
target="_blank"
|
If there are multiple projects linked to the same repository
|
||||||
rel="noopener noreferrer"
|
and you only want these changes to apply to a subset of
|
||||||
className="underline"
|
them, please check out{' '}
|
||||||
href="https://docs.nhost.io/cli/overlays"
|
<a
|
||||||
>
|
target="_blank"
|
||||||
docs.nhost.io/cli/overlays
|
rel="noopener noreferrer"
|
||||||
</a>{' '}
|
className="underline"
|
||||||
for guidance.
|
href="https://docs.nhost.io/cli/overlays"
|
||||||
</Text>
|
>
|
||||||
</Alert>
|
docs.nhost.io/cli/overlays
|
||||||
)}
|
</a>{' '}
|
||||||
</div>
|
for guidance.
|
||||||
{children}
|
</Text>
|
||||||
</RetryableErrorBoundary>
|
</Alert>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</RetryableErrorBoundary>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</ProjectLayout>
|
</ProjectLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import { Backdrop } from '@/components/ui/v2/Backdrop';
|
|||||||
import type { BoxProps } from '@/components/ui/v2/Box';
|
import type { BoxProps } from '@/components/ui/v2/Box';
|
||||||
import { Box } from '@/components/ui/v2/Box';
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
import { IconButton } from '@/components/ui/v2/IconButton';
|
import { IconButton } from '@/components/ui/v2/IconButton';
|
||||||
|
import { SlidersIcon } from '@/components/ui/v2/icons/SlidersIcon';
|
||||||
import { List } from '@/components/ui/v2/List';
|
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';
|
||||||
@@ -26,12 +28,17 @@ interface SettingsNavLinkProps extends ListItemButtonProps {
|
|||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
exact?: boolean;
|
exact?: boolean;
|
||||||
|
/**
|
||||||
|
* Class name passed to the text element.
|
||||||
|
*/
|
||||||
|
textClassName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SettingsNavLink({
|
function SettingsNavLink({
|
||||||
exact = true,
|
exact = true,
|
||||||
href,
|
href,
|
||||||
children,
|
children,
|
||||||
|
textClassName,
|
||||||
...props
|
...props
|
||||||
}: SettingsNavLinkProps) {
|
}: SettingsNavLinkProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -51,7 +58,7 @@ function SettingsNavLink({
|
|||||||
selected={active}
|
selected={active}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ListItem.Text>{children}</ListItem.Text>
|
<ListItem.Text className={textClassName}>{children}</ListItem.Text>
|
||||||
</ListItem.Button>
|
</ListItem.Button>
|
||||||
</ListItem.Root>
|
</ListItem.Root>
|
||||||
);
|
);
|
||||||
@@ -61,6 +68,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 +103,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,13 +120,13 @@ 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] flex h-full w-full flex-col justify-between overflow-auto border-r-1 pb-17 pt-2 motion-safe:transition-transform md:relative md:z-0 md:h-full md:pb-0 md:pt-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,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<nav aria-label="Settings navigation">
|
<nav aria-label="Settings navigation" className="px-2">
|
||||||
<List className="grid gap-2">
|
<List className="grid gap-2">
|
||||||
<SettingsNavLink
|
<SettingsNavLink
|
||||||
href="/general"
|
href="/general"
|
||||||
@@ -181,7 +189,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>
|
||||||
|
|
||||||
@@ -213,6 +226,20 @@ export default function SettingsSidebar({
|
|||||||
</SettingsNavLink>
|
</SettingsNavLink>
|
||||||
</List>
|
</List>
|
||||||
</nav>
|
</nav>
|
||||||
|
<Box className="border-t">
|
||||||
|
<SettingsNavLink
|
||||||
|
href="/editor"
|
||||||
|
exact={false}
|
||||||
|
onClick={handleSelect}
|
||||||
|
className="flex w-full border group-focus-within:pr-9 group-hover:pr-9 group-active:pr-9"
|
||||||
|
textClassName="flex w-full justify-center"
|
||||||
|
>
|
||||||
|
<div className="flex w-full flex-row items-center justify-center space-x-4 py-2.5">
|
||||||
|
<SlidersIcon />
|
||||||
|
<span className="flex">Configuration Editor</span>
|
||||||
|
</div>
|
||||||
|
</SettingsNavLink>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|||||||
132
dashboard/src/components/presentational/CodeBlock/CodeBlock.tsx
Normal file
132
dashboard/src/components/presentational/CodeBlock/CodeBlock.tsx
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
import { clsx } from 'clsx';
|
||||||
|
import {
|
||||||
|
forwardRef,
|
||||||
|
type ComponentPropsWithoutRef,
|
||||||
|
type ForwardedRef,
|
||||||
|
type ReactElement,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
|
import { CopyToClipboardButton as CopyToClipboardButtonOriginal } from './CopyToClipboardButton';
|
||||||
|
import { getNodeText } from './getNodeText';
|
||||||
|
|
||||||
|
export interface CodeBlockPropsBase {
|
||||||
|
filename?: string;
|
||||||
|
/**
|
||||||
|
* Color of the filename text and the border underneath it when content is being shown.
|
||||||
|
*/
|
||||||
|
filenameColor?: string;
|
||||||
|
/**
|
||||||
|
* Text of the toast that appears when the code is copied to the clipboard.
|
||||||
|
*/
|
||||||
|
copyToClipboardToastTitle?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CodeBlockProps = CodeBlockPropsBase &
|
||||||
|
Omit<ComponentPropsWithoutRef<'div'>, keyof CodeBlockPropsBase>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Different from CodeGroup because we cannot use Headless UI's Tab component outside a Tab.Group
|
||||||
|
* Styling should look the same though.
|
||||||
|
*/
|
||||||
|
function CodeTabBar({
|
||||||
|
filename,
|
||||||
|
filenameColor,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
filename: string;
|
||||||
|
filenameColor?: string;
|
||||||
|
children?: ReactElement;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="flex text-xs leading-6 text-slate-400">
|
||||||
|
<div
|
||||||
|
className="flex flex-none items-center border-b border-t border-t-transparent px-4 py-1"
|
||||||
|
style={{ color: filenameColor, borderBottomColor: filenameColor }}
|
||||||
|
>
|
||||||
|
{filename}
|
||||||
|
</div>
|
||||||
|
<div className="bg-codeblock-tabs flex flex-auto items-center rounded-t border border-slate-500/30">
|
||||||
|
{children && (
|
||||||
|
<div className="flex flex-auto items-center justify-end space-x-4 px-4">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CopyToClipboardButtonProps
|
||||||
|
extends Partial<
|
||||||
|
ComponentPropsWithoutRef<typeof CopyToClipboardButtonOriginal>
|
||||||
|
> {
|
||||||
|
filenameColor?: string;
|
||||||
|
tooltipColor?: string;
|
||||||
|
toastTitle?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CopyToClipboardButton({
|
||||||
|
tooltipColor,
|
||||||
|
filenameColor,
|
||||||
|
textToCopy,
|
||||||
|
toastTitle,
|
||||||
|
...props
|
||||||
|
}: CopyToClipboardButtonProps) {
|
||||||
|
return (
|
||||||
|
<CopyToClipboardButtonOriginal
|
||||||
|
textToCopy={textToCopy}
|
||||||
|
title={toastTitle}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CodeBlock = forwardRef(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
filename,
|
||||||
|
filenameColor,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
copyToClipboardToastTitle,
|
||||||
|
...props
|
||||||
|
}: CodeBlockProps,
|
||||||
|
ref: ForwardedRef<HTMLDivElement>,
|
||||||
|
) => (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundColor: (theme) =>
|
||||||
|
theme.palette.mode === 'dark' ? 'grey.200' : 'grey.200',
|
||||||
|
}}
|
||||||
|
className={clsx(
|
||||||
|
'not-prose relative mt-5 px-2',
|
||||||
|
filename && 'pt-2',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{filename ? (
|
||||||
|
<CodeTabBar filename={filename} filenameColor={filenameColor}>
|
||||||
|
<CopyToClipboardButton
|
||||||
|
filenameColor={filenameColor}
|
||||||
|
textToCopy={getNodeText(children)}
|
||||||
|
toastTitle={copyToClipboardToastTitle}
|
||||||
|
className="relative"
|
||||||
|
/>
|
||||||
|
</CodeTabBar>
|
||||||
|
) : (
|
||||||
|
<CopyToClipboardButton
|
||||||
|
filenameColor={filenameColor}
|
||||||
|
textToCopy={getNodeText(children)}
|
||||||
|
toastTitle={copyToClipboardToastTitle}
|
||||||
|
className="absolute right-3 top-0"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<pre className="overflow-x-auto">
|
||||||
|
<code className="font-mono">{children}</code>
|
||||||
|
</pre>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
);
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { clsx } from 'clsx';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IconButton,
|
||||||
|
type IconButtonProps,
|
||||||
|
} from '@/components/ui/v2/IconButton';
|
||||||
|
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
|
||||||
|
import { copy } from '@/utils/copy';
|
||||||
|
|
||||||
|
export function CopyToClipboardButton({
|
||||||
|
textToCopy,
|
||||||
|
className,
|
||||||
|
title,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
textToCopy: string;
|
||||||
|
title: string;
|
||||||
|
} & IconButtonProps) {
|
||||||
|
const [disabled, setDisabled] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Hide copy button if the browser does not support it
|
||||||
|
if (typeof window !== 'undefined' && !navigator?.clipboard) {
|
||||||
|
console.error(
|
||||||
|
"The browser's Clipboard API is unavailable. The Clipboard API is only available on HTTPS.",
|
||||||
|
);
|
||||||
|
setDisabled(true);
|
||||||
|
} else {
|
||||||
|
setDisabled(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Hide copy button if you would copy an empty string
|
||||||
|
if (!textToCopy || disabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
variant="borderless"
|
||||||
|
color="secondary"
|
||||||
|
className={clsx('group', className)}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
copy(textToCopy, title);
|
||||||
|
}}
|
||||||
|
aria-label={textToCopy}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<CopyIcon className="top-5 h-4 w-4" />
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// Gets the text from a component as if you selected it with a mouse and copied it.
|
||||||
|
export const getNodeText = (node: any): string => {
|
||||||
|
if (['string', 'number'].includes(typeof node)) {
|
||||||
|
// Convert number into string
|
||||||
|
return node.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node instanceof Array) {
|
||||||
|
return node.map(getNodeText).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof node === 'object' && node?.props?.children) {
|
||||||
|
return getNodeText(node.props.children);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import {
|
||||||
|
CodeBlock,
|
||||||
|
type CodeBlockProps,
|
||||||
|
type CodeBlockPropsBase,
|
||||||
|
} from './CodeBlock';
|
||||||
|
|
||||||
|
export type { CodeBlockPropsBase, CodeBlockProps };
|
||||||
|
export { CodeBlock };
|
||||||
28
dashboard/src/components/ui/v2/Accordion/Accordion.tsx
Normal file
28
dashboard/src/components/ui/v2/Accordion/Accordion.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { styled } from '@mui/material';
|
||||||
|
import type { AccordionProps as MaterialAccordionProps } from '@mui/material/Accordion';
|
||||||
|
|
||||||
|
import MaterialAccordion, { accordionClasses } from '@mui/material/Accordion';
|
||||||
|
|
||||||
|
export interface AccordionProps extends MaterialAccordionProps {}
|
||||||
|
|
||||||
|
const Accordion = styled(MaterialAccordion)<AccordionProps>(({ theme }) => ({
|
||||||
|
fontFamily: theme.typography.fontFamily,
|
||||||
|
fontSize: theme.typography.pxToRem(12),
|
||||||
|
lineHeight: theme.typography.pxToRem(16),
|
||||||
|
borderBottom: `transparent solid 0px`,
|
||||||
|
boxShadow: `none`,
|
||||||
|
[`&.${accordionClasses.disabled}`]: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
[`&.${accordionClasses.root}`]: {
|
||||||
|
overflowX: 'hidden',
|
||||||
|
},
|
||||||
|
[`&.${accordionClasses.expanded}`]: {
|
||||||
|
marginTop: 0,
|
||||||
|
marginBottom: 0,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
Accordion.displayName = 'NhostAccordion';
|
||||||
|
|
||||||
|
export default Accordion;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { styled } from '@mui/material';
|
||||||
|
import type { AccordionActionsProps as MaterialAccordionActionsProps } from '@mui/material/AccordionActions';
|
||||||
|
|
||||||
|
import MaterialAccordionActions from '@mui/material/AccordionActions';
|
||||||
|
|
||||||
|
export interface AccordionActionsProps extends MaterialAccordionActionsProps {}
|
||||||
|
|
||||||
|
const AccordionActions = styled(
|
||||||
|
MaterialAccordionActions,
|
||||||
|
)<AccordionActionsProps>(({ theme }) => ({
|
||||||
|
fontFamily: theme.typography.fontFamily,
|
||||||
|
fontSize: theme.typography.pxToRem(12),
|
||||||
|
lineHeight: theme.typography.pxToRem(16),
|
||||||
|
}));
|
||||||
|
|
||||||
|
AccordionActions.displayName = 'NhostAccordionActions';
|
||||||
|
|
||||||
|
export default AccordionActions;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { styled } from '@mui/material';
|
||||||
|
import type { AccordionDetailsProps as MaterialAccordionDetailsProps } from '@mui/material/AccordionDetails';
|
||||||
|
|
||||||
|
import MaterialAccordionDetails from '@mui/material/AccordionDetails';
|
||||||
|
|
||||||
|
export interface AccordionDetailsProps extends MaterialAccordionDetailsProps {}
|
||||||
|
|
||||||
|
const AccordionDetails = styled(
|
||||||
|
MaterialAccordionDetails,
|
||||||
|
)<AccordionDetailsProps>(({ theme }) => ({
|
||||||
|
fontFamily: theme.typography.fontFamily,
|
||||||
|
fontSize: theme.typography.pxToRem(12),
|
||||||
|
lineHeight: theme.typography.pxToRem(16),
|
||||||
|
backgroundColor: theme.palette.grey[200],
|
||||||
|
marginTop: 0,
|
||||||
|
marginBottom: 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
AccordionDetails.displayName = 'NhostAccordionDetails';
|
||||||
|
|
||||||
|
export default AccordionDetails;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { styled } from '@mui/material';
|
||||||
|
import type { AccordionSummaryProps as MaterialAccordionSummaryProps } from '@mui/material/AccordionSummary';
|
||||||
|
|
||||||
|
import MaterialAccordionSummary, {
|
||||||
|
accordionSummaryClasses,
|
||||||
|
} from '@mui/material/AccordionSummary';
|
||||||
|
|
||||||
|
export interface AccordionSummaryProps extends MaterialAccordionSummaryProps {}
|
||||||
|
|
||||||
|
const AccordionSummary = styled(
|
||||||
|
MaterialAccordionSummary,
|
||||||
|
)<AccordionSummaryProps>(({ theme }) => ({
|
||||||
|
fontFamily: theme.typography.fontFamily,
|
||||||
|
fontSize: theme.typography.pxToRem(12),
|
||||||
|
lineHeight: theme.typography.pxToRem(16),
|
||||||
|
[`&.${accordionSummaryClasses.disabled}`]: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
AccordionSummary.displayName = 'NhostAccordionSummary';
|
||||||
|
|
||||||
|
export default AccordionSummary;
|
||||||
19
dashboard/src/components/ui/v2/Accordion/index.ts
Normal file
19
dashboard/src/components/ui/v2/Accordion/index.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import AccordionRoot from './Accordion';
|
||||||
|
import AccordionActions from './AccordionActions';
|
||||||
|
import AccordionDetails from './AccordionDetails';
|
||||||
|
import AccordionSummary from './AccordionSummary';
|
||||||
|
|
||||||
|
export { default as BaseAccordion } from './Accordion';
|
||||||
|
export * from './AccordionActions';
|
||||||
|
export { default as AccordionActions } from './AccordionActions';
|
||||||
|
export * from './AccordionDetails';
|
||||||
|
export { default as AccordionDetails } from './AccordionDetails';
|
||||||
|
export * from './AccordionSummary';
|
||||||
|
export { default as AccordionSummary } from './AccordionSummary';
|
||||||
|
|
||||||
|
export const Accordion = {
|
||||||
|
Root: AccordionRoot,
|
||||||
|
Details: AccordionDetails,
|
||||||
|
Summary: AccordionSummary,
|
||||||
|
Actions: AccordionActions,
|
||||||
|
};
|
||||||
47
dashboard/src/components/ui/v2/Badge/Badge.tsx
Normal file
47
dashboard/src/components/ui/v2/Badge/Badge.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { styled } from '@mui/material';
|
||||||
|
import type { BadgeProps as MaterialBadgeProps } from '@mui/material/Badge';
|
||||||
|
import MaterialBadge from '@mui/material/Badge';
|
||||||
|
import type { ElementType } from 'react';
|
||||||
|
|
||||||
|
export interface BadgeProps extends MaterialBadgeProps {
|
||||||
|
/**
|
||||||
|
* Custom component for the root node.
|
||||||
|
*/
|
||||||
|
component?: ElementType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Badge = styled(MaterialBadge)<BadgeProps>(({ theme }) => ({
|
||||||
|
fontFamily: theme.typography.fontFamily,
|
||||||
|
fontSize: theme.typography.pxToRem(12),
|
||||||
|
lineHeight: theme.typography.pxToRem(16),
|
||||||
|
fontWeight: 500,
|
||||||
|
padding: 0,
|
||||||
|
'& .MuiBadge-dot': {
|
||||||
|
minWidth: '0.625rem',
|
||||||
|
minHeight: '0.625rem',
|
||||||
|
borderRadius: '50%',
|
||||||
|
},
|
||||||
|
'& .MuiBadge-standard': {
|
||||||
|
padding: 0,
|
||||||
|
margin: 0,
|
||||||
|
minWidth: '0.625rem',
|
||||||
|
height: '0.625rem',
|
||||||
|
borderRadius: '50%',
|
||||||
|
},
|
||||||
|
'& .MuiBadge-colorError': {
|
||||||
|
backgroundColor: theme.palette.error.main,
|
||||||
|
},
|
||||||
|
'& .MuiBadge-colorWarning': {
|
||||||
|
backgroundColor: theme.palette.warning.main,
|
||||||
|
},
|
||||||
|
'& .MuiBadge-colorSuccess': {
|
||||||
|
backgroundColor: theme.palette.success.dark,
|
||||||
|
},
|
||||||
|
'& .MuiBadge-colorSecondary': {
|
||||||
|
backgroundColor: theme.palette.grey[500],
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
Badge.displayName = 'NhostBadge';
|
||||||
|
|
||||||
|
export default Badge;
|
||||||
2
dashboard/src/components/ui/v2/Badge/index.ts
Normal file
2
dashboard/src/components/ui/v2/Badge/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './Badge';
|
||||||
|
export { default as Badge } from './Badge';
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Backdrop } from '@/components/ui/v2/Backdrop';
|
import { Backdrop } from '@/components/ui/v2/Backdrop';
|
||||||
import type { ButtonProps } from '@/components/ui/v2/Button';
|
import type { ButtonProps } from '@/components/ui/v2/Button';
|
||||||
|
import type { DialogTitleProps } from '@/components/ui/v2/Dialog';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import type { DialogProps as MaterialDialogProps } from '@mui/material/Dialog';
|
import type { DialogProps as MaterialDialogProps } from '@mui/material/Dialog';
|
||||||
import MaterialDialog from '@mui/material/Dialog';
|
import MaterialDialog from '@mui/material/Dialog';
|
||||||
import type { DialogActionsProps } from '@mui/material/DialogActions';
|
import type { DialogActionsProps } from '@mui/material/DialogActions';
|
||||||
import type { DialogContentProps } from '@mui/material/DialogContent';
|
import type { DialogContentProps } from '@mui/material/DialogContent';
|
||||||
import type { DialogContentTextProps } from '@mui/material/DialogContentText';
|
import type { DialogContentTextProps } from '@mui/material/DialogContentText';
|
||||||
import type { DialogTitleProps } from '@mui/material/DialogTitle';
|
|
||||||
import Paper from '@mui/material/Paper';
|
import Paper from '@mui/material/Paper';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { render, screen } from '@/tests/testUtils';
|
||||||
|
import { test } from 'vitest';
|
||||||
|
import ErrorToast from './ErrorToast';
|
||||||
|
|
||||||
|
const oneMemberByWorkspaceError = {
|
||||||
|
name: 'ApolloError',
|
||||||
|
graphQLErrors: [
|
||||||
|
{
|
||||||
|
message: 'database query error',
|
||||||
|
extensions: {
|
||||||
|
path: '$.selectionSet.insertApp.args.object',
|
||||||
|
code: 'unexpected',
|
||||||
|
internal: {
|
||||||
|
arguments: [],
|
||||||
|
error: {
|
||||||
|
description: null,
|
||||||
|
exec_status: 'FatalError',
|
||||||
|
hint: null,
|
||||||
|
message:
|
||||||
|
'Only one workspace member is allowed for individual plans',
|
||||||
|
status_code: 'P0001',
|
||||||
|
},
|
||||||
|
prepared: false,
|
||||||
|
statement: '.....',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
protocolErrors: [],
|
||||||
|
clientErrors: [],
|
||||||
|
networkError: null,
|
||||||
|
message: 'database query error',
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeNodeInvalidVersionError = {
|
||||||
|
name: 'ApolloError',
|
||||||
|
graphQLErrors: [
|
||||||
|
{
|
||||||
|
message:
|
||||||
|
'failed to resolve config: failed to validate config: config is not valid: #Config.functions.node.version: 2 errors in empty disjunction: (and 2 more errors)',
|
||||||
|
path: ['replaceConfigRawJSON'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
protocolErrors: [],
|
||||||
|
clientErrors: [],
|
||||||
|
networkError: null,
|
||||||
|
message:
|
||||||
|
'failed to resolve config: failed to validate config: config is not valid: #Config.functions.node.version: 2 errors in empty disjunction: (and 2 more errors)',
|
||||||
|
};
|
||||||
|
|
||||||
|
test('should render the error message when creating a project with an individual plan in a workspace with multiple users', () => {
|
||||||
|
const errorMessage =
|
||||||
|
'An error occurred while creating the project. Please try again.';
|
||||||
|
render(
|
||||||
|
<ErrorToast
|
||||||
|
isVisible
|
||||||
|
errorMessage={errorMessage}
|
||||||
|
error={oneMemberByWorkspaceError}
|
||||||
|
close={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
/Only one workspace member is allowed for individual plans/i,
|
||||||
|
),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render the error message when changing the node version to an invalid value in configuration editor', () => {
|
||||||
|
const errorMessage =
|
||||||
|
'An error occurred while saving configuration. Please try again.';
|
||||||
|
render(
|
||||||
|
<ErrorToast
|
||||||
|
isVisible
|
||||||
|
errorMessage={errorMessage}
|
||||||
|
error={changeNodeInvalidVersionError}
|
||||||
|
close={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
const regex =
|
||||||
|
/failed to resolve config: failed to validate config: config is not valid: #Config\.functions\.node\.version: 2 errors in empty disjunction: \(and 2 more errors\)/i;
|
||||||
|
|
||||||
|
expect(screen.getByText(regex)).toBeInTheDocument();
|
||||||
|
});
|
||||||
@@ -29,10 +29,10 @@ const getInternalErrorMessage = (
|
|||||||
|
|
||||||
if (error.name === 'ApolloError') {
|
if (error.name === 'ApolloError') {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const internalError = error.graphQLErrors?.[0]?.extensions?.internal as {
|
const graphqlError = error.graphQLErrors?.[0];
|
||||||
error: { message: string };
|
const graphqlExtensionsError = graphqlError?.extensions?.internal
|
||||||
};
|
?.error as { message: string };
|
||||||
return internalError?.error?.message || null;
|
return graphqlExtensionsError?.message || graphqlError.message || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
export default function CommunityIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M5.5 10C7.29493 10 8.75 8.54493 8.75 6.75C8.75 4.95507 7.29493 3.5 5.5 3.5C3.70507 3.5 2.25 4.95507 2.25 6.75C2.25 8.54493 3.70507 10 5.5 10Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeMiterlimit="10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M9.71338 3.62107C10.1604 3.49513 10.6292 3.46644 11.0882 3.53693C11.5473 3.60743 11.9859 3.77547 12.3745 4.02975C12.7631 4.28403 13.0927 4.61863 13.3411 5.01102C13.5896 5.40342 13.751 5.84449 13.8146 6.30453C13.8782 6.76457 13.8425 7.2329 13.7098 7.67797C13.5772 8.12304 13.3507 8.53452 13.0457 8.8847C12.7406 9.23487 12.364 9.51561 11.9413 9.70799C11.5187 9.90038 11.0596 9.99996 10.5952 10"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M1 12.3373C1.50758 11.6153 2.18143 11.026 2.96466 10.6192C3.74788 10.2124 4.61748 10 5.50005 10C6.38262 9.99997 7.25224 10.2123 8.0355 10.619C8.81875 11.0258 9.49264 11.615 10.0003 12.337"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M10.5952 10C11.4779 9.99936 12.3477 10.2114 13.131 10.6182C13.9143 11.025 14.5881 11.6146 15.0952 12.337"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as CommunityIcon } from './CommunityIcon';
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
export default function DiscordIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width={14}
|
||||||
|
height={14}
|
||||||
|
viewBox="0 0 14 14"
|
||||||
|
fill="currentColor"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M3.40571 1.5H12.5943C13.3691 1.5 14 2.13086 14 2.91257V15.2143L12.5257 13.9114L11.696 13.1434L10.8183 12.3274L11.1817 13.596H3.40571C2.63086 13.596 2 12.9651 2 12.1834V2.91257C2 2.13086 2.63086 1.5 3.40571 1.5ZM9.49486 9.9C9.70057 10.1606 9.94743 10.4554 9.94743 10.4554C11.4629 10.4074 12.0457 9.41314 12.0457 9.41314C12.0457 7.20514 11.0583 5.41543 11.0583 5.41543C10.0709 4.67486 9.13143 4.69543 9.13143 4.69543L9.03543 4.80514C10.2011 5.16171 10.7429 5.676 10.7429 5.676C10.0297 5.28514 9.33029 5.09314 8.67886 5.01771C8.18514 4.96286 7.712 4.97657 7.29371 5.03143C7.2578 5.03143 7.2271 5.03665 7.19251 5.04254C7.18748 5.0434 7.18237 5.04427 7.17714 5.04514C6.93714 5.06571 6.35429 5.15486 5.62057 5.47714C5.36686 5.59371 5.216 5.676 5.216 5.676C5.216 5.676 5.78514 5.13429 7.01943 4.77771L6.95086 4.69543C6.95086 4.69543 6.01143 4.67486 5.024 5.41543C5.024 5.41543 4.03657 7.20514 4.03657 9.41314C4.03657 9.41314 4.61257 10.4074 6.128 10.4554C6.128 10.4554 6.38171 10.1469 6.58743 9.88628C5.71657 9.62571 5.38743 9.07714 5.38743 9.07714C5.38743 9.07714 5.456 9.12514 5.57943 9.19371C5.58629 9.20057 5.59314 9.20743 5.60686 9.21428C5.61714 9.22114 5.62743 9.22628 5.63771 9.23143C5.648 9.23657 5.65829 9.24171 5.66857 9.24857C5.84 9.34457 6.01143 9.42 6.16914 9.48171C6.45029 9.59143 6.78629 9.70114 7.17714 9.77657C7.69143 9.87257 8.29486 9.90686 8.95314 9.78343C9.27543 9.72857 9.60457 9.63257 9.94743 9.48857C10.1874 9.39943 10.4549 9.26914 10.736 9.084C10.736 9.084 10.3931 9.64628 9.49486 9.9ZM6.05942 8.01421C6.05942 7.59593 6.36799 7.25307 6.75885 7.25307C7.1497 7.25307 7.46513 7.59593 7.45827 8.01421C7.45827 8.4325 7.1497 8.77536 6.75885 8.77536C6.37485 8.77536 6.05942 8.4325 6.05942 8.01421ZM8.56227 8.01421C8.56227 7.59593 8.87084 7.25307 9.2617 7.25307C9.65256 7.25307 9.96113 7.59593 9.96113 8.01421C9.96113 8.4325 9.65256 8.77536 9.2617 8.77536C8.8777 8.77536 8.56227 8.4325 8.56227 8.01421Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as DiscordIcon } from './DiscordIcon';
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
export default function EnvelopeIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width={14}
|
||||||
|
height={14}
|
||||||
|
viewBox="0 0 14 14"
|
||||||
|
fill="currentColor"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M2 3.5H14V12C14 12.1326 13.9473 12.2598 13.8536 12.3536C13.7598 12.4473 13.6326 12.5 13.5 12.5H2.5C2.36739 12.5 2.24021 12.4473 2.14645 12.3536C2.05268 12.2598 2 12.1326 2 12V3.5Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M14 3.5L8 9L2 3.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as EnvelopeIcon } from './EnvelopeIcon';
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import type { IconProps } from '@/components/ui/v2/icons';
|
||||||
|
import { SvgIcon } from '@/components/ui/v2/icons/SvgIcon';
|
||||||
|
import type { ForwardedRef } from 'react';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
|
function ExclamationFilledIcon(
|
||||||
|
props: IconProps,
|
||||||
|
ref: ForwardedRef<SVGSVGElement>,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<SvgIcon
|
||||||
|
width="7"
|
||||||
|
height="7"
|
||||||
|
viewBox="0 0 7 7"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-label="Exclamation mark"
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M7 3.5C7 5.433 5.433 7 3.5 7C1.567 7 0 5.433 0 3.5C0 1.567 1.567 0 3.5 0C5.433 0 7 1.567 7 3.5ZM3.96667 5.36667C3.96667 5.6244 3.75773 5.83333 3.5 5.83333C3.24227 5.83333 3.03333 5.6244 3.03333 5.36667C3.03333 5.10893 3.24227 4.9 3.5 4.9C3.75773 4.9 3.96667 5.10893 3.96667 5.36667ZM3.5 1.16667C3.20564 1.16667 2.97296 1.41615 2.99345 1.70979L3.16724 4.20075C3.17943 4.37554 3.32478 4.51111 3.5 4.51111C3.67522 4.51111 3.82057 4.37554 3.83276 4.20075L4.00655 1.70979C4.02704 1.41615 3.79436 1.16667 3.5 1.16667Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExclamationFilledIcon.displayName = 'NhostExclamationFilledIcon';
|
||||||
|
|
||||||
|
export default forwardRef(ExclamationFilledIcon);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as ExclamationFilledIcon } from './ExclamationFilledIcon';
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import type { IconProps } from '@/components/ui/v2/icons';
|
||||||
|
import { SvgIcon } from '@/components/ui/v2/icons/SvgIcon';
|
||||||
|
|
||||||
|
function QuestionMarkIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<SvgIcon
|
||||||
|
width="320"
|
||||||
|
height="512"
|
||||||
|
aria-label="Question mark"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 320 512"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M80 160c0-35.3 28.7-64 64-64h32c35.3 0 64 28.7 64 64v3.6c0 21.8-11.1 42.1-29.4 53.8l-42.2 27.1c-25.2 16.2-40.4 44.1-40.4 74V320c0 17.7 14.3 32 32 32s32-14.3 32-32v-1.4c0-8.2 4.2-15.8 11-20.2l42.2-27.1c36.6-23.6 58.8-64.1 58.8-107.7V160c0-70.7-57.3-128-128-128H144C73.3 32 16 89.3 16 160c0 17.7 14.3 32 32 32s32-14.3 32-32zm80 320a40 40 0 1 0 0-80 40 40 0 1 0 0 80z" />
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
QuestionMarkIcon.displayName = 'NhostQuestionMarkIcon';
|
||||||
|
|
||||||
|
export default QuestionMarkIcon;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as QuestionMarkIcon } from './QuestionMarkIcon';
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import type { IconProps } from '@/components/ui/v2/icons';
|
||||||
|
import { SvgIcon } from '@/components/ui/v2/icons/SvgIcon';
|
||||||
|
|
||||||
|
function ServicesOutlinedIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<SvgIcon
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-label="Services"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M15.8521 7.06294C15.8143 7.03192 15.485 6.77435 14.7986 6.73709C14.7878 6.7365 14.7769 6.73597 14.7659 6.73549C14.7223 6.7336 14.6772 6.7326 14.6307 6.7326C14.458 6.73338 14.2857 6.74604 14.1147 6.77049C14.0763 6.77598 14.0379 6.78208 13.9996 6.78876C13.9966 6.76748 13.9934 6.74642 13.9899 6.72556C13.9367 6.41145 13.8247 6.14509 13.6926 5.92522C13.5869 5.74941 13.4683 5.60332 13.3565 5.48631C13.1377 5.25722 12.9451 5.13955 12.9269 5.12843L12.7118 5L12.5703 5.21132C12.4532 5.39906 12.3569 5.59952 12.2832 5.80884C12.2455 5.916 12.2137 6.02549 12.188 6.13679C12.1785 6.17826 12.17 6.21957 12.1626 6.26069C12.0784 6.72436 12.1218 7.16514 12.2887 7.56346C12.3311 7.66471 12.3815 7.76323 12.4399 7.85868C12.0678 8.07332 11.471 8.12616 11.3503 8.13083H1.46954C1.21146 8.1312 1.00204 8.34712 1.00063 8.6143C0.989105 9.51047 1.13576 10.4013 1.43337 11.2429C1.77374 12.1671 2.28012 12.8478 2.93893 13.2644C3.67717 13.7325 4.87658 14 6.23616 14C6.85036 14.0019 7.4634 13.9444 8.06725 13.8281C8.90666 13.6685 9.71439 13.3648 10.457 12.9294C11.0689 12.5625 11.6196 12.0958 12.0879 11.5472C12.8644 10.6371 13.3296 9.62426 13.6755 8.72167C13.6783 8.7144 13.6811 8.70714 13.6839 8.69989H13.8221C14.6792 8.69989 15.2062 8.3448 15.4969 8.04724C15.69 7.85746 15.8408 7.62628 15.9386 7.36986L16 7.18397L15.8521 7.06294ZM11.9029 9.4592C11.6843 9.49512 11.4987 9.51062 11.3978 9.51453L11.374 9.51544H2.35702C2.41226 9.93468 2.51073 10.3477 2.65136 10.7474C2.90985 11.445 3.24967 11.8492 3.60646 12.0748L3.60778 12.0757C4.05853 12.3615 4.98755 12.6153 6.23616 12.6153H6.24016C6.77497 12.6171 7.30873 12.567 7.83441 12.4657L7.83733 12.4652C8.53499 12.3325 9.20535 12.0806 9.82092 11.7205C10.31 11.4265 10.7488 11.0538 11.1211 10.6177M11.9029 9.4592C11.6864 9.86254 11.4322 10.2531 11.1211 10.6177L11.9029 9.4592Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M2.38514 7.54505H3.7092C3.77306 7.54505 3.8248 7.49328 3.8248 7.42944V6.25005C3.82516 6.18619 3.77369 6.13415 3.70985 6.13379C3.70964 6.13379 3.70941 6.13379 3.7092 6.13379H2.38514C2.32128 6.13379 2.26953 6.18556 2.26953 6.24939V6.25005V7.42942C2.26953 7.49328 2.32128 7.54505 2.38514 7.54505Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M4.21003 7.54505H5.53409C5.59794 7.54505 5.64969 7.49328 5.64969 7.42944V6.25005C5.65005 6.18619 5.59857 6.13415 5.53472 6.13379C5.53451 6.13379 5.53427 6.13379 5.53407 6.13379H4.21001C4.14579 6.13379 4.09375 6.18583 4.09375 6.25005V7.42942C4.09413 7.49339 4.14606 7.54505 4.21003 7.54505Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M6.06192 7.54505H7.38597C7.44983 7.54505 7.50157 7.49328 7.50157 7.42944V6.25005C7.50193 6.18619 7.45046 6.13415 7.3866 6.13379C7.38639 6.13379 7.38616 6.13379 7.38595 6.13379H6.06189C5.99804 6.13379 5.94629 6.18556 5.94629 6.24939V6.25005V7.42942C5.94631 7.49328 5.99808 7.54505 6.06192 7.54505Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M7.89295 7.54505H9.21701C9.28097 7.54505 9.33291 7.49341 9.33326 7.42944V6.25005C9.33326 6.18583 9.28122 6.13379 9.21701 6.13379H7.89295C7.82909 6.13379 7.77734 6.18556 7.77734 6.24939V6.25005V7.42942C7.77734 7.49328 7.82911 7.54505 7.89295 7.54505Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M4.21001 5.84874H5.53406C5.59801 5.84839 5.64967 5.79643 5.64967 5.73249V4.55311C5.64967 4.48925 5.5979 4.4375 5.53406 4.4375H4.21001C4.14606 4.4375 4.09411 4.48914 4.09375 4.55311V5.73249C4.09411 5.79656 4.14594 5.84839 4.21001 5.84874Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M6.06189 5.84874H7.38595C7.4499 5.84839 7.50156 5.79643 7.50156 5.73249V4.55311C7.50156 4.48925 7.44979 4.4375 7.38595 4.4375H6.06189C5.99804 4.4375 5.94629 4.48927 5.94629 4.55311V5.73249C5.94629 5.79643 5.99795 5.84839 6.06189 5.84874Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M7.89295 5.84874H9.21701C9.28108 5.84839 9.33291 5.79656 9.33326 5.73249V4.55311C9.33291 4.48914 9.28097 4.4375 9.21701 4.4375H7.89295C7.82909 4.4375 7.77734 4.48927 7.77734 4.55311V5.73249C7.77734 5.79643 7.82901 5.84839 7.89295 5.84874Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M7.89295 4.1515H9.21701C9.28097 4.1515 9.33291 4.09983 9.33326 4.03589V2.85584C9.33291 2.79188 9.28097 2.74023 9.21701 2.74023H7.89295C7.82909 2.74023 7.77734 2.79198 7.77734 2.85584V4.03587C7.77734 4.09973 7.82911 4.1515 7.89295 4.1515Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M9.73963 7.54505H11.0637C11.1277 7.54505 11.1796 7.49341 11.1799 7.42944V6.25005C11.1799 6.18583 11.1279 6.13379 11.0637 6.13379H9.73963C9.67579 6.13379 9.62402 6.18556 9.62402 6.24939V6.25005V7.42942C9.62402 7.49328 9.67579 7.54505 9.73963 7.54505Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ServicesOutlinedIcon.displayName = 'NhostServicesIcon';
|
||||||
|
|
||||||
|
export default ServicesOutlinedIcon;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as ServicesOutlinedIcon } from './ServicesOutlinedIcon';
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import type { IconProps } from '@/components/ui/v2/icons';
|
||||||
|
import { SvgIcon } from '@/components/ui/v2/icons/SvgIcon';
|
||||||
|
|
||||||
|
function SlidersIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<SvgIcon
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-label="Sliders"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<line x1="21" x2="14" y1="4" y2="4" />
|
||||||
|
<line x1="10" x2="3" y1="4" y2="4" />
|
||||||
|
<line x1="21" x2="12" y1="12" y2="12" />
|
||||||
|
<line x1="8" x2="3" y1="12" y2="12" />
|
||||||
|
<line x1="21" x2="16" y1="20" y2="20" />
|
||||||
|
<line x1="12" x2="3" y1="20" y2="20" />
|
||||||
|
<line x1="14" x2="14" y1="2" y2="6" />
|
||||||
|
<line x1="8" x2="8" y1="10" y2="14" />
|
||||||
|
<line x1="16" x2="16" y1="18" y2="22" />
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SlidersIcon.displayName = 'NhostSlidersIcon';
|
||||||
|
|
||||||
|
export default SlidersIcon;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as SlidersIcon } from './SlidersIcon';
|
||||||
@@ -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';
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
|
import { GitHubIcon } from '@/components/ui/v2/icons/GitHubIcon';
|
||||||
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
|
import { useGetAuthUserProvidersQuery } from '@/utils/__generated__/graphql';
|
||||||
|
import { useProviderLink } from '@nhost/nextjs';
|
||||||
|
import NavLink from 'next/link';
|
||||||
|
|
||||||
|
export default function SocialProvidersSettings() {
|
||||||
|
const { data, loading, error } = useGetAuthUserProvidersQuery();
|
||||||
|
const isGithubConnected = data?.authUserProviders?.some(
|
||||||
|
(item) => item.providerId === 'github',
|
||||||
|
);
|
||||||
|
|
||||||
|
const { github } = useProviderLink({
|
||||||
|
connect: true,
|
||||||
|
redirectTo: `${window.location.origin}/account`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data && loading) {
|
||||||
|
return (
|
||||||
|
<ActivityIndicator
|
||||||
|
delay={1000}
|
||||||
|
label="Loading personal access tokens..."
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsContainer
|
||||||
|
title="Authentication providers"
|
||||||
|
description=""
|
||||||
|
rootClassName="gap-0 flex flex-col items-start"
|
||||||
|
className="my-2"
|
||||||
|
slotProps={{
|
||||||
|
submitButton: { className: 'hidden' },
|
||||||
|
footer: { className: 'hidden' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isGithubConnected ? (
|
||||||
|
<Box
|
||||||
|
sx={{ backgroundColor: 'grey.200' }}
|
||||||
|
className="flex flex-row items-center justify-start space-x-2 rounded-md p-2"
|
||||||
|
>
|
||||||
|
<GitHubIcon />
|
||||||
|
<Text className="font-medium ">Connected</Text>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box>
|
||||||
|
<NavLink
|
||||||
|
href={github}
|
||||||
|
passHref
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
legacyBehavior
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className=""
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
startIcon={<GitHubIcon />}
|
||||||
|
>
|
||||||
|
Connect with GitHub
|
||||||
|
</Button>
|
||||||
|
</NavLink>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</SettingsContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as SocialProvidersSettings } from './SocialProvidersSettings';
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
query getAuthUserProviders {
|
||||||
|
authUserProviders {
|
||||||
|
id
|
||||||
|
providerId
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -117,7 +117,7 @@ export default function ArgumentsFormSection({
|
|||||||
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]',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import { ControlledSelect } from '@/components/form/ControlledSelect';
|
||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { Box } from '@/components/ui/v2/Box';
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
@@ -6,6 +7,7 @@ import { ArrowsClockwise } from '@/components/ui/v2/icons/ArrowsClockwise';
|
|||||||
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
|
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
|
||||||
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
|
import { Option } from '@/components/ui/v2/Option';
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||||
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
|
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
|
||||||
@@ -20,11 +22,18 @@ import { useEffect } from 'react';
|
|||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
|
const AUTO_EMBEDDINGS_MODELS = [
|
||||||
|
'text-embedding-ada-002',
|
||||||
|
'text-embedding-3-small',
|
||||||
|
'text-embedding-3-large',
|
||||||
|
];
|
||||||
|
|
||||||
export const validationSchema = Yup.object({
|
export const validationSchema = Yup.object({
|
||||||
name: Yup.string().required('The name is required.'),
|
name: Yup.string().required('The name field is required.'),
|
||||||
schemaName: Yup.string().required('The schema is required'),
|
model: Yup.string().oneOf(AUTO_EMBEDDINGS_MODELS),
|
||||||
tableName: Yup.string().required('The table is required'),
|
schemaName: Yup.string().required('The schema field is required'),
|
||||||
columnName: Yup.string().required('The column is required'),
|
tableName: Yup.string().required('The table field is required'),
|
||||||
|
columnName: Yup.string().required('The column field is required'),
|
||||||
query: Yup.string(),
|
query: Yup.string(),
|
||||||
mutation: Yup.string(),
|
mutation: Yup.string(),
|
||||||
});
|
});
|
||||||
@@ -40,7 +49,7 @@ export interface AutoEmbeddingsFormProps extends DialogFormProps {
|
|||||||
/**
|
/**
|
||||||
* if there is initialData then it's an update operation
|
* if there is initialData then it's an update operation
|
||||||
*/
|
*/
|
||||||
initialData?: AutoEmbeddingsFormValues;
|
initialData?: AutoEmbeddingsFormValues & { model: string };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to be called when the operation is cancelled.
|
* Function to be called when the operation is cancelled.
|
||||||
@@ -74,7 +83,10 @@ export default function AutoEmbeddingsForm({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const form = useForm<AutoEmbeddingsFormValues>({
|
const form = useForm<AutoEmbeddingsFormValues>({
|
||||||
defaultValues: initialData,
|
defaultValues: {
|
||||||
|
...initialData,
|
||||||
|
model: initialData?.model ?? 'text-embedding-ada-002',
|
||||||
|
},
|
||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
@@ -106,7 +118,9 @@ export default function AutoEmbeddingsForm({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await insertGraphiteAutoEmbeddingsConfiguration({
|
await insertGraphiteAutoEmbeddingsConfiguration({
|
||||||
variables: values,
|
variables: {
|
||||||
|
...values,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -129,9 +143,9 @@ export default function AutoEmbeddingsForm({
|
|||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<Form
|
<Form
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
className="flex h-full flex-col gap-4 overflow-hidden"
|
className="flex flex-col h-full gap-4 overflow-hidden"
|
||||||
>
|
>
|
||||||
<div className="flex flex-1 flex-col space-y-6 overflow-auto px-6">
|
<div className="flex flex-col flex-1 px-6 space-y-6 overflow-auto">
|
||||||
<Input
|
<Input
|
||||||
{...register('name')}
|
{...register('name')}
|
||||||
id="name"
|
id="name"
|
||||||
@@ -141,7 +155,7 @@ export default function AutoEmbeddingsForm({
|
|||||||
<Tooltip title="Name of the Auto-Embeddings">
|
<Tooltip title="Name of the Auto-Embeddings">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -155,6 +169,36 @@ export default function AutoEmbeddingsForm({
|
|||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ControlledSelect
|
||||||
|
slotProps={{
|
||||||
|
popper: { disablePortal: false, className: 'z-[10000]' },
|
||||||
|
}}
|
||||||
|
id="model"
|
||||||
|
name="model"
|
||||||
|
label={
|
||||||
|
<Box className="flex flex-row items-center space-x-2">
|
||||||
|
<Text>Model</Text>
|
||||||
|
<Tooltip title="Auto-Embeddings Model">
|
||||||
|
<InfoIcon
|
||||||
|
aria-label="Info"
|
||||||
|
className="w-4 h-4"
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
fullWidth
|
||||||
|
error={!!errors?.model?.message}
|
||||||
|
helperText={errors?.model?.message}
|
||||||
|
>
|
||||||
|
{AUTO_EMBEDDINGS_MODELS.map((model) => (
|
||||||
|
<Option key={model} value={model}>
|
||||||
|
{model}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</ControlledSelect>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
{...register('schemaName')}
|
{...register('schemaName')}
|
||||||
id="schemaName"
|
id="schemaName"
|
||||||
@@ -164,7 +208,7 @@ export default function AutoEmbeddingsForm({
|
|||||||
<Tooltip title={<span>Schema where the table belongs to</span>}>
|
<Tooltip title={<span>Schema where the table belongs to</span>}>
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -186,7 +230,7 @@ export default function AutoEmbeddingsForm({
|
|||||||
<Tooltip title="Table Name">
|
<Tooltip title="Table Name">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -208,7 +252,7 @@ export default function AutoEmbeddingsForm({
|
|||||||
<Tooltip title="Column name">
|
<Tooltip title="Column name">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -230,7 +274,7 @@ export default function AutoEmbeddingsForm({
|
|||||||
<Tooltip title="Query">
|
<Tooltip title="Query">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -254,7 +298,7 @@ export default function AutoEmbeddingsForm({
|
|||||||
<Tooltip title="Mutation">
|
<Tooltip title="Mutation">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -271,7 +315,7 @@ export default function AutoEmbeddingsForm({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Box className="flex w-full flex-row justify-between rounded border-t px-6 py-4">
|
<Box className="flex flex-row justify-between w-full px-6 py-4 border-t rounded">
|
||||||
<Button variant="outlined" color="secondary" onClick={onCancel}>
|
<Button variant="outlined" color="secondary" onClick={onCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/components/common/UIProvider';
|
import { useUI } from '@/components/common/UIProvider';
|
||||||
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
||||||
@@ -12,6 +13,7 @@ import { Switch } from '@/components/ui/v2/Switch';
|
|||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||||
import { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection';
|
import { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection';
|
||||||
import {
|
import {
|
||||||
@@ -20,6 +22,7 @@ import {
|
|||||||
useGetSoftwareVersionsQuery,
|
useGetSoftwareVersionsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
|
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
@@ -29,8 +32,7 @@ import { FormProvider, useForm } from 'react-hook-form';
|
|||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { DisableAIServiceConfirmationDialog } from './DisableAIServiceConfirmationDialog';
|
import { DisableAIServiceConfirmationDialog } from './DisableAIServiceConfirmationDialog';
|
||||||
|
import { isPostgresVersionValidForAI } from '@/features/ai/settings/utils/isPostgresVersionValidForAI';
|
||||||
const MIN_POSTGRES_VERSION_SUPPORTING_AI = '14.6-20231018-1';
|
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
version: Yup.object({
|
version: Yup.object({
|
||||||
@@ -50,9 +52,13 @@ const validationSchema = Yup.object({
|
|||||||
export type AISettingsFormValues = Yup.InferType<typeof validationSchema>;
|
export type AISettingsFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function AISettings() {
|
export default function AISettings() {
|
||||||
const { maintenanceActive } = useUI();
|
const isPlatform = useIsPlatform();
|
||||||
const { openDialog } = useDialog();
|
const { openDialog } = useDialog();
|
||||||
const [updateConfig] = useUpdateConfigMutation();
|
const { maintenanceActive } = useUI();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
|
});
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
|
|
||||||
const [aiServiceEnabled, setAIServiceEnabled] = useState(true);
|
const [aiServiceEnabled, setAIServiceEnabled] = useState(true);
|
||||||
@@ -67,6 +73,7 @@ export default function AISettings() {
|
|||||||
variables: {
|
variables: {
|
||||||
appId: currentProject.id,
|
appId: currentProject.id,
|
||||||
},
|
},
|
||||||
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: graphiteVersionsData, loading: loadingGraphiteVersionsData } =
|
const { data: graphiteVersionsData, loading: loadingGraphiteVersionsData } =
|
||||||
@@ -74,6 +81,7 @@ export default function AISettings() {
|
|||||||
variables: {
|
variables: {
|
||||||
software: Software_Type_Enum.Graphite,
|
software: Software_Type_Enum.Graphite,
|
||||||
},
|
},
|
||||||
|
skip: !isPlatform,
|
||||||
});
|
});
|
||||||
|
|
||||||
const graphiteVersions = graphiteVersionsData?.softwareVersions || [];
|
const graphiteVersions = graphiteVersionsData?.softwareVersions || [];
|
||||||
@@ -98,8 +106,8 @@ export default function AISettings() {
|
|||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
version: {
|
version: {
|
||||||
label: ai?.version ?? availableVersions?.at(0)?.label,
|
label: ai?.version || availableVersions?.at(0)?.label || '',
|
||||||
value: ai?.version ?? availableVersions?.at(0)?.value,
|
value: ai?.version || availableVersions?.at(0)?.value || '',
|
||||||
},
|
},
|
||||||
webhookSecret: '',
|
webhookSecret: '',
|
||||||
organization: '',
|
organization: '',
|
||||||
@@ -156,7 +164,7 @@ export default function AISettings() {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const toggleAIService = async (enabled: boolean) => {
|
const toggleAIService = async (enabled: boolean) => {
|
||||||
if (postgresVersion < MIN_POSTGRES_VERSION_SUPPORTING_AI) {
|
if (!isPostgresVersionValidForAI(postgresVersion)) {
|
||||||
toast.error(
|
toast.error(
|
||||||
'In order to enable the AI service you need to update your database version to 14.6-20231018-1 or newer.',
|
'In order to enable the AI service you need to update your database version to 14.6-20231018-1 or newer.',
|
||||||
{
|
{
|
||||||
@@ -225,6 +233,18 @@ export default function AISettings() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
form.reset(formValues);
|
form.reset(formValues);
|
||||||
|
|
||||||
|
if (!isPlatform) {
|
||||||
|
openDialog({
|
||||||
|
title: 'Apply your changes',
|
||||||
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
loadingMessage: 'AI settings are being updated...',
|
loadingMessage: 'AI settings are being updated...',
|
||||||
@@ -247,7 +267,7 @@ export default function AISettings() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="space-y-4" sx={{ backgroundColor: 'background.default' }}>
|
<Box className="space-y-4" sx={{ backgroundColor: 'background.default' }}>
|
||||||
<Box className="flex flex-row items-center justify-between rounded-lg border-1 p-4">
|
<Box className="flex flex-row items-center justify-between p-4 rounded-lg border-1">
|
||||||
<Text className="text-lg font-semibold">Enable AI service</Text>
|
<Text className="text-lg font-semibold">Enable AI service</Text>
|
||||||
<Switch
|
<Switch
|
||||||
checked={aiServiceEnabled}
|
checked={aiServiceEnabled}
|
||||||
@@ -270,54 +290,58 @@ export default function AISettings() {
|
|||||||
className="flex flex-col"
|
className="flex flex-col"
|
||||||
>
|
>
|
||||||
<Box className="space-y-4">
|
<Box className="space-y-4">
|
||||||
{availableVersions.length > 0 && (
|
<Box className="space-y-2">
|
||||||
<Box className="space-y-2">
|
<Box className="flex flex-row items-center space-x-2">
|
||||||
<Box className="flex flex-row items-center space-x-2">
|
<Text className="text-lg font-semibold">Version</Text>
|
||||||
<Text className="text-lg font-semibold">Version</Text>
|
<Tooltip title="Version of the service to use.">
|
||||||
<Tooltip title="Version of the service to use.">
|
<InfoIcon
|
||||||
<InfoIcon
|
aria-label="Info"
|
||||||
aria-label="Info"
|
className="w-4 h-4"
|
||||||
className="h-4 w-4"
|
color="primary"
|
||||||
color="primary"
|
/>
|
||||||
/>
|
</Tooltip>
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
|
||||||
<ControlledAutocomplete
|
|
||||||
id="version"
|
|
||||||
name="version"
|
|
||||||
autoHighlight
|
|
||||||
isOptionEqualToValue={() => false}
|
|
||||||
filterOptions={(options, { inputValue }) => {
|
|
||||||
const inputValueLower = inputValue.toLowerCase();
|
|
||||||
const matched = [];
|
|
||||||
const otherOptions = [];
|
|
||||||
|
|
||||||
options.forEach((option) => {
|
|
||||||
const optionLabelLower = option.label.toLowerCase();
|
|
||||||
|
|
||||||
if (optionLabelLower.startsWith(inputValueLower)) {
|
|
||||||
matched.push(option);
|
|
||||||
} else {
|
|
||||||
otherOptions.push(option);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = [...matched, ...otherOptions];
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}}
|
|
||||||
fullWidth
|
|
||||||
className="col-span-4"
|
|
||||||
options={availableVersions}
|
|
||||||
error={!!formState.errors?.version?.message}
|
|
||||||
helperText={formState.errors?.version?.message}
|
|
||||||
showCustomOption="auto"
|
|
||||||
customOptionLabel={(value) =>
|
|
||||||
`Use custom value: "${value}"`
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
<ControlledAutocomplete
|
||||||
|
id="version"
|
||||||
|
name="version"
|
||||||
|
autoHighlight
|
||||||
|
isOptionEqualToValue={() => false}
|
||||||
|
filterOptions={(options, { inputValue }) => {
|
||||||
|
const inputValueLower = inputValue.toLowerCase();
|
||||||
|
const matched = [];
|
||||||
|
const otherOptions = [];
|
||||||
|
|
||||||
|
options.forEach((option) => {
|
||||||
|
const optionLabelLower = option.label.toLowerCase();
|
||||||
|
|
||||||
|
if (optionLabelLower.startsWith(inputValueLower)) {
|
||||||
|
matched.push(option);
|
||||||
|
} else {
|
||||||
|
otherOptions.push(option);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = [...matched, ...otherOptions];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
className="col-span-4"
|
||||||
|
options={availableVersions}
|
||||||
|
error={
|
||||||
|
!!formState.errors?.version?.message ||
|
||||||
|
!!formState.errors?.version?.value?.message
|
||||||
|
}
|
||||||
|
helperText={
|
||||||
|
formState.errors?.version?.message ||
|
||||||
|
formState.errors?.version?.value?.message
|
||||||
|
}
|
||||||
|
showCustomOption="auto"
|
||||||
|
customOptionLabel={(value) =>
|
||||||
|
`Use custom value: "${value}"`
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box className="space-y-2">
|
<Box className="space-y-2">
|
||||||
<Box className="flex flex-row items-center space-x-2">
|
<Box className="flex flex-row items-center space-x-2">
|
||||||
@@ -327,7 +351,7 @@ export default function AISettings() {
|
|||||||
<Tooltip title="Used to validate requests between postgres and the AI service. The AI service will also include the header X-Graphite-Webhook-Secret with this value set when calling external webhooks so the source of the request can be validated.">
|
<Tooltip title="Used to validate requests between postgres and the AI service. The AI service will also include the header X-Graphite-Webhook-Secret with this value set when calling external webhooks so the source of the request can be validated.">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -351,26 +375,28 @@ export default function AISettings() {
|
|||||||
<Tooltip title="Dedicated resources allocated for the service.">
|
<Tooltip title="Dedicated resources allocated for the service.">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Alert
|
{isPlatform ? (
|
||||||
severity="info"
|
<Alert
|
||||||
className="flex items-center justify-between space-x-2"
|
severity="info"
|
||||||
>
|
className="flex items-center justify-between space-x-2"
|
||||||
<span>{getAIResourcesCost()}</span>
|
>
|
||||||
<b>
|
<span>{getAIResourcesCost()}</span>
|
||||||
$
|
<b>
|
||||||
{parseFloat(
|
$
|
||||||
(
|
{parseFloat(
|
||||||
aiSettingsFormValues.compute.cpu * COST_PER_VCPU
|
(
|
||||||
).toFixed(2),
|
aiSettingsFormValues.compute.cpu * COST_PER_VCPU
|
||||||
)}
|
).toFixed(2),
|
||||||
</b>
|
)}
|
||||||
</Alert>
|
</b>
|
||||||
|
</Alert>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<ComputeFormSection />
|
<ComputeFormSection />
|
||||||
</Box>
|
</Box>
|
||||||
@@ -389,7 +415,7 @@ export default function AISettings() {
|
|||||||
<Tooltip title="Key to use for authenticating API requests to OpenAI">
|
<Tooltip title="Key to use for authenticating API requests to OpenAI">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -412,7 +438,7 @@ export default function AISettings() {
|
|||||||
<Tooltip title="Optional. OpenAI organization to use.">
|
<Tooltip title="Optional. OpenAI organization to use.">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -440,7 +466,7 @@ export default function AISettings() {
|
|||||||
<Tooltip title="How often to run the job that keeps embeddings up to date.">
|
<Tooltip title="How often to run the job that keeps embeddings up to date.">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -468,3 +494,4 @@ export default function AISettings() {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { Box } from '@/components/ui/v2/Box';
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
|
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||||
import { useUpdateConfigMutation } from '@/utils/__generated__/graphql';
|
import { useUpdateConfigMutation } from '@/utils/__generated__/graphql';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -23,11 +26,14 @@ export default function DisableAIServiceConfirmationDialog({
|
|||||||
onCancel,
|
onCancel,
|
||||||
onServiceDisabled,
|
onServiceDisabled,
|
||||||
}: DisableAIServiceConfirmationDialogProps) {
|
}: DisableAIServiceConfirmationDialogProps) {
|
||||||
const { closeDialog } = useDialog();
|
const isPlatform = useIsPlatform();
|
||||||
|
const { openDialog, closeDialog } = useDialog();
|
||||||
|
const localMimirClient = useLocalMimirClient();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
const [updateConfig] = useUpdateConfigMutation();
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
async function handleClick() {
|
async function handleClick() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -45,6 +51,18 @@ export default function DisableAIServiceConfirmationDialog({
|
|||||||
|
|
||||||
onServiceDisabled();
|
onServiceDisabled();
|
||||||
closeDialog();
|
closeDialog();
|
||||||
|
|
||||||
|
if (!isPlatform) {
|
||||||
|
openDialog({
|
||||||
|
title: 'Apply your changes',
|
||||||
|
component: <ApplyLocalSettingsDialog />,
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
loadingMessage: 'Disabling the AI service...',
|
loadingMessage: 'Disabling the AI service...',
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user