Compare commits

..

52 Commits

Author SHA1 Message Date
github-actions[bot]
7163854767 chore: update versions (#2724)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/apollo@7.1.2

### Patch Changes

-   @nhost/nhost-js@3.1.5

## @nhost/react-apollo@12.0.2

### Patch Changes

-   @nhost/apollo@7.1.2
-   @nhost/react@3.5.2

## @nhost/react-urql@9.0.2

### Patch Changes

-   @nhost/react@3.5.2

## @nhost/hasura-auth-js@2.5.2

### Patch Changes

- a03fb2c: fix: deep clone machine context to prevent mutations in
nested objects during initial session setup

## @nhost/nextjs@2.1.16

### Patch Changes

-   @nhost/react@3.5.2

## @nhost/nhost-js@3.1.5

### Patch Changes

-   Updated dependencies [a03fb2c]
    -   @nhost/hasura-auth-js@2.5.2

## @nhost/react@3.5.2

### Patch Changes

-   @nhost/nhost-js@3.1.5

## @nhost/vue@2.6.2

### Patch Changes

-   @nhost/nhost-js@3.1.5

## @nhost/docs@2.13.0

### Minor Changes

-   6fb0cc2: fix: minor improvements to compute resources' docs
-   66bd450: chore: various improvements

## @nhost/dashboard@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

## @nhost-examples/cli@0.3.7

### Patch Changes

-   @nhost/nhost-js@3.1.5

## @nhost-examples/codegen-react-apollo@0.4.7

### Patch Changes

-   @nhost/react@3.5.2
-   @nhost/react-apollo@12.0.2

## @nhost-examples/codegen-react-query@0.4.7

### Patch Changes

-   @nhost/react@3.5.2

## @nhost-examples/codegen-react-urql@0.3.7

### Patch Changes

-   @nhost/react@3.5.2
-   @nhost/react-urql@9.0.2

## @nhost-examples/multi-tenant-one-to-many@2.2.7

### Patch Changes

-   @nhost/nhost-js@3.1.5

## @nhost-examples/nextjs@0.3.7

### Patch Changes

-   @nhost/react@3.5.2
-   @nhost/react-apollo@12.0.2
-   @nhost/nextjs@2.1.16

## @nhost-examples/node-storage@0.2.7

### Patch Changes

-   @nhost/nhost-js@3.1.5

## @nhost-examples/nextjs-server-components@0.4.7

### Patch Changes

-   @nhost/nhost-js@3.1.5

## @nhost-examples/react-apollo@0.8.7

### Patch Changes

-   @nhost/react@3.5.2
-   @nhost/react-apollo@12.0.2

## @nhost-examples/react-gqty@1.2.7

### Patch Changes

-   @nhost/react@3.5.2

## @nhost-examples/vue-apollo@0.6.7

### Patch Changes

-   @nhost/nhost-js@3.1.5
-   @nhost/apollo@7.1.2
-   @nhost/vue@2.6.2

## @nhost-examples/vue-quickstart@0.2.7

### Patch Changes

-   @nhost/apollo@7.1.2
-   @nhost/vue@2.6.2

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-05-31 10:25:11 +01:00
David Barroso
66bd4504d7 chore (docs): various improvements (#2738)
- Adds references to custom domains in social signin methods
- a short note to remind users to update the SDK instantiation when
using custom domains
- added a sample function to generate custom jwts
2024-05-31 11:22:11 +02:00
Hassan Ben Jobrane
a03fb2cf82 fix(hasura-auth-js): deep clone machine context to avoid mutating nested objects (#2736)
fixes https://github.com/nhost/nhost/issues/2732
2024-05-31 10:20:18 +01:00
Hassan Ben Jobrane
87a37cfc08 fix(dashboard): remove isPlatform check from verify button disable logic (#2737) 2024-05-31 09:54:55 +01:00
David Barroso
6fb0cc27aa fix (docs): improvements to compute resources (#2723) 2024-05-23 16:19:36 +02:00
github-actions[bot]
2c33051f83 chore: update versions (#2719)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/apollo@7.1.1

### Patch Changes

-   @nhost/nhost-js@3.1.4

## @nhost/react-apollo@12.0.1

### Patch Changes

-   @nhost/apollo@7.1.1
-   @nhost/react@3.5.1

## @nhost/react-urql@9.0.1

### Patch Changes

-   @nhost/react@3.5.1

## @nhost/hasura-auth-js@2.5.1

### Patch Changes

- f4f0353: fix: improve environment/browser detection to support React
Native
- defffd8: fix: resolve issue where `/token` endpoint is called with an
empty token during sign-in

## @nhost/nextjs@2.1.15

### Patch Changes

-   @nhost/react@3.5.1

## @nhost/nhost-js@3.1.4

### Patch Changes

-   Updated dependencies [f4f0353]
-   Updated dependencies [defffd8]
    -   @nhost/hasura-auth-js@2.5.1

## @nhost/react@3.5.1

### Patch Changes

-   @nhost/nhost-js@3.1.4

## @nhost/vue@2.6.1

### Patch Changes

-   @nhost/nhost-js@3.1.4

## @nhost/dashboard@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

## @nhost-examples/cli@0.3.6

### Patch Changes

-   @nhost/nhost-js@3.1.4

## @nhost-examples/codegen-react-apollo@0.4.6

### Patch Changes

-   @nhost/react@3.5.1
-   @nhost/react-apollo@12.0.1

## @nhost-examples/codegen-react-query@0.4.6

### Patch Changes

-   @nhost/react@3.5.1

## @nhost-examples/codegen-react-urql@0.3.6

### Patch Changes

-   @nhost/react@3.5.1
-   @nhost/react-urql@9.0.1

## @nhost-examples/multi-tenant-one-to-many@2.2.6

### Patch Changes

-   @nhost/nhost-js@3.1.4

## @nhost-examples/nextjs@0.3.6

### Patch Changes

-   @nhost/react@3.5.1
-   @nhost/react-apollo@12.0.1
-   @nhost/nextjs@2.1.15

## @nhost-examples/node-storage@0.2.6

### Patch Changes

-   @nhost/nhost-js@3.1.4

## @nhost-examples/nextjs-server-components@0.4.6

### Patch Changes

-   @nhost/nhost-js@3.1.4

## @nhost-examples/react-apollo@0.8.6

### Patch Changes

-   @nhost/react@3.5.1
-   @nhost/react-apollo@12.0.1

## @nhost-examples/react-gqty@1.2.6

### Patch Changes

-   @nhost/react@3.5.1

## @nhost-examples/vue-apollo@0.6.6

### Patch Changes

-   @nhost/nhost-js@3.1.4
-   @nhost/apollo@7.1.1
-   @nhost/vue@2.6.1

## @nhost-examples/vue-quickstart@0.2.6

### Patch Changes

-   @nhost/apollo@7.1.1
-   @nhost/vue@2.6.1

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-05-21 15:23:32 +01:00
Hassan Ben Jobrane
a9413af6e0 fix: update GetAllWorkspacesAndProjects query polling to use exp backoff (#2718) 2024-05-21 13:44:38 +01:00
Hassan Ben Jobrane
f4f0353f2e fix(hasura-auth-js): resolve social sign-in issue in React Native (#2716)
fix https://github.com/nhost/nhost/issues/2216
2024-05-20 19:13:13 +01:00
Hassan Ben Jobrane
defffd8bc4 fix: update internal-client to use payload.token instead of payload.token.data (#2717) 2024-05-20 19:05:21 +01:00
github-actions[bot]
614c20cbbf chore: update versions (#2712)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/apollo@7.1.0

### Minor Changes

-   d0c9f4c: fix: replace `jose` with `jwt-decode` version 4.0.0

### Patch Changes

-   @nhost/nhost-js@3.1.3

## @nhost/graphql-js@0.3.0

### Minor Changes

-   d0c9f4c: fix: replace `jose` with `jwt-decode` version 4.0.0

## @nhost/hasura-auth-js@2.5.0

### Minor Changes

-   d0c9f4c: fix: replace `jose` with `jwt-decode` version 4.0.0

## @nhost/react@3.5.0

### Minor Changes

-   d0c9f4c: fix: replace `jose` with `jwt-decode` version 4.0.0

### Patch Changes

-   @nhost/nhost-js@3.1.3

## @nhost/vue@2.6.0

### Minor Changes

-   d0c9f4c: fix: replace `jose` with `jwt-decode` version 4.0.0

### Patch Changes

-   @nhost/nhost-js@3.1.3

## @nhost/react-apollo@12.0.0

### Patch Changes

-   Updated dependencies [d0c9f4c]
    -   @nhost/apollo@7.1.0
    -   @nhost/react@3.5.0

## @nhost/react-urql@9.0.0

### Patch Changes

-   Updated dependencies [d0c9f4c]
    -   @nhost/react@3.5.0

## @nhost/nextjs@2.1.14

### Patch Changes

-   Updated dependencies [d0c9f4c]
    -   @nhost/react@3.5.0

## @nhost/nhost-js@3.1.3

### Patch Changes

-   Updated dependencies [d0c9f4c]
    -   @nhost/hasura-auth-js@2.5.0
    -   @nhost/graphql-js@0.3.0

## @nhost/dashboard@1.16.1

### Patch Changes

-   @nhost/react-apollo@12.0.0
-   @nhost/nextjs@2.1.14

## @nhost-examples/cli@0.3.5

### Patch Changes

-   @nhost/nhost-js@3.1.3

## @nhost-examples/codegen-react-apollo@0.4.5

### Patch Changes

-   Updated dependencies [d0c9f4c]
    -   @nhost/react@3.5.0
    -   @nhost/react-apollo@12.0.0

## @nhost-examples/codegen-react-query@0.4.5

### Patch Changes

-   Updated dependencies [d0c9f4c]
    -   @nhost/react@3.5.0

## @nhost-examples/codegen-react-urql@0.3.5

### Patch Changes

-   Updated dependencies [d0c9f4c]
    -   @nhost/react@3.5.0
    -   @nhost/react-urql@9.0.0

## @nhost-examples/multi-tenant-one-to-many@2.2.5

### Patch Changes

-   @nhost/nhost-js@3.1.3

## @nhost-examples/nextjs@0.3.5

### Patch Changes

-   Updated dependencies [d0c9f4c]
    -   @nhost/react@3.5.0
    -   @nhost/react-apollo@12.0.0
    -   @nhost/nextjs@2.1.14

## @nhost-examples/node-storage@0.2.5

### Patch Changes

-   @nhost/nhost-js@3.1.3

## @nhost-examples/nextjs-server-components@0.4.5

### Patch Changes

-   @nhost/nhost-js@3.1.3

## @nhost-examples/react-apollo@0.8.5

### Patch Changes

-   aef4a0a: fix: resolve e2e test issues
-   Updated dependencies [d0c9f4c]
    -   @nhost/react@3.5.0
    -   @nhost/react-apollo@12.0.0

## @nhost-examples/react-gqty@1.2.5

### Patch Changes

-   Updated dependencies [d0c9f4c]
    -   @nhost/react@3.5.0

## @nhost-examples/vue-apollo@0.6.5

### Patch Changes

-   Updated dependencies [d0c9f4c]
    -   @nhost/apollo@7.1.0
    -   @nhost/vue@2.6.0
    -   @nhost/nhost-js@3.1.3

## @nhost-examples/vue-quickstart@0.2.5

### Patch Changes

-   Updated dependencies [d0c9f4c]
    -   @nhost/apollo@7.1.0
    -   @nhost/vue@2.6.0

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-05-15 19:24:01 +01:00
Hassan Ben Jobrane
aef4a0a4fc fix: examples: react-apollo: address e2e test issues (#2714)
fixes https://github.com/nhost/nhost/issues/2711
2024-05-15 19:07:42 +01:00
Hassan Ben Jobrane
d0c9f4cd17 fix: replace jose with jwt-decode version 4.0.0 (#2705)
fixes https://github.com/nhost/nhost/issues/2603
2024-05-15 14:35:16 +01:00
github-actions[bot]
e2646cab55 chore: update versions (#2709)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@1.16.0

### Minor Changes

- c6d5c5c: feat: add toggle switch to enable/disable public access in
the database settings

## @nhost/docs@2.12.0

### Minor Changes

-   d5077c7: feat: added docs about how to connect to postgres

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-05-15 09:36:54 +01:00
David Barroso
d5077c7ca4 feat (docs): added docs about how to connect to postgres (#2708) 2024-05-15 10:15:23 +02:00
Hassan Ben Jobrane
c6d5c5cc8c feat: dashboard: add toggle switch to enable/disable database public access (#2707) 2024-05-15 09:12:06 +01:00
github-actions[bot]
f1d9b472d1 chore: update versions (#2704)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/apollo@7.0.2

### Patch Changes

-   Updated dependencies [4c35171]
-   Updated dependencies [3cea460]
    -   @nhost/nhost-js@3.1.2

## @nhost/react-apollo@11.0.4

### Patch Changes

-   @nhost/apollo@7.0.2
-   @nhost/react@3.4.4

## @nhost/react-urql@8.0.4

### Patch Changes

-   @nhost/react@3.4.4

## @nhost/nextjs@2.1.13

### Patch Changes

-   @nhost/react@3.4.4

## @nhost/nhost-js@3.1.2

### Patch Changes

- 4c35171: fix: update docstring to correctly reflect usage of
nhost.unsetRole method
- 3cea460: chore: update docs links for nhost-js setRole and unsetRole
methods

## @nhost/react@3.4.4

### Patch Changes

-   Updated dependencies [4c35171]
-   Updated dependencies [3cea460]
    -   @nhost/nhost-js@3.1.2

## @nhost/vue@2.5.4

### Patch Changes

-   Updated dependencies [4c35171]
-   Updated dependencies [3cea460]
    -   @nhost/nhost-js@3.1.2

## @nhost/docs@2.11.0

### Minor Changes

-   c6dc7f4: chore: docs: add Nhost client reference

## @nhost/dashboard@1.15.2

### Patch Changes

-   @nhost/react-apollo@11.0.4
-   @nhost/nextjs@2.1.13

## @nhost-examples/cli@0.3.4

### Patch Changes

-   Updated dependencies [4c35171]
-   Updated dependencies [3cea460]
    -   @nhost/nhost-js@3.1.2

## @nhost-examples/codegen-react-apollo@0.4.4

### Patch Changes

-   @nhost/react@3.4.4
-   @nhost/react-apollo@11.0.4

## @nhost-examples/codegen-react-query@0.4.4

### Patch Changes

-   @nhost/react@3.4.4

## @nhost-examples/codegen-react-urql@0.3.4

### Patch Changes

-   @nhost/react@3.4.4
-   @nhost/react-urql@8.0.4

## @nhost-examples/multi-tenant-one-to-many@2.2.4

### Patch Changes

-   Updated dependencies [4c35171]
-   Updated dependencies [3cea460]
    -   @nhost/nhost-js@3.1.2

## @nhost-examples/nextjs@0.3.4

### Patch Changes

-   @nhost/react@3.4.4
-   @nhost/react-apollo@11.0.4
-   @nhost/nextjs@2.1.13

## @nhost-examples/node-storage@0.2.4

### Patch Changes

-   Updated dependencies [4c35171]
-   Updated dependencies [3cea460]
    -   @nhost/nhost-js@3.1.2

## @nhost-examples/nextjs-server-components@0.4.4

### Patch Changes

-   Updated dependencies [4c35171]
-   Updated dependencies [3cea460]
    -   @nhost/nhost-js@3.1.2

## @nhost-examples/react-apollo@0.8.4

### Patch Changes

-   @nhost/react@3.4.4
-   @nhost/react-apollo@11.0.4

## @nhost-examples/react-gqty@1.2.4

### Patch Changes

-   @nhost/react@3.4.4

## @nhost-examples/vue-apollo@0.6.4

### Patch Changes

-   Updated dependencies [4c35171]
-   Updated dependencies [3cea460]
    -   @nhost/nhost-js@3.1.2
    -   @nhost/apollo@7.0.2
    -   @nhost/vue@2.5.4

## @nhost-examples/vue-quickstart@0.2.4

### Patch Changes

-   @nhost/apollo@7.0.2
-   @nhost/vue@2.5.4

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-05-14 13:56:40 +01:00
Nuno Pato
c6dc7f44df chore: docs: add Nhost client reference (#2698) 2024-05-14 13:49:31 +01:00
Hassan Ben Jobrane
3cea460c36 chore: update docs links for nhost-js setRole and unsetRole methods (#2706) 2024-05-14 12:52:30 +01:00
Hassan Ben Jobrane
4c351714f5 fix(sdk): update docstring to accurately reflect usage of nhost.unsetRole method (#2703) 2024-05-14 10:54:41 +01:00
github-actions[bot]
3143d66a8e chore: update versions (#2701)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/apollo@7.0.1

### Patch Changes

-   @nhost/nhost-js@3.1.1

## @nhost/react-apollo@11.0.3

### Patch Changes

-   @nhost/apollo@7.0.1
-   @nhost/react@3.4.3

## @nhost/react-urql@8.0.3

### Patch Changes

-   @nhost/react@3.4.3

## @nhost/hasura-storage-js@2.5.1

### Patch Changes

- 8512a7f: fix: fix types StorageGetUrlParams and
StorageGetPresignedUrlParams to include missing StorageHeadersParam

## @nhost/nextjs@2.1.12

### Patch Changes

-   @nhost/react@3.4.3

## @nhost/nhost-js@3.1.1

### Patch Changes

-   Updated dependencies [8512a7f]
    -   @nhost/hasura-storage-js@2.5.1

## @nhost/react@3.4.3

### Patch Changes

-   @nhost/nhost-js@3.1.1

## @nhost/vue@2.5.3

### Patch Changes

-   @nhost/nhost-js@3.1.1

## @nhost/dashboard@1.15.1

### Patch Changes

-   @nhost/react-apollo@11.0.3
-   @nhost/nextjs@2.1.12

## @nhost-examples/cli@0.3.3

### Patch Changes

-   @nhost/nhost-js@3.1.1

## @nhost-examples/codegen-react-apollo@0.4.3

### Patch Changes

-   @nhost/react@3.4.3
-   @nhost/react-apollo@11.0.3

## @nhost-examples/codegen-react-query@0.4.3

### Patch Changes

-   @nhost/react@3.4.3

## @nhost-examples/codegen-react-urql@0.3.3

### Patch Changes

-   @nhost/react@3.4.3
-   @nhost/react-urql@8.0.3

## @nhost-examples/multi-tenant-one-to-many@2.2.3

### Patch Changes

-   @nhost/nhost-js@3.1.1

## @nhost-examples/nextjs@0.3.3

### Patch Changes

-   @nhost/react@3.4.3
-   @nhost/react-apollo@11.0.3
-   @nhost/nextjs@2.1.12

## @nhost-examples/node-storage@0.2.3

### Patch Changes

-   @nhost/nhost-js@3.1.1

## @nhost-examples/nextjs-server-components@0.4.3

### Patch Changes

-   @nhost/nhost-js@3.1.1

## @nhost-examples/react-apollo@0.8.3

### Patch Changes

-   @nhost/react@3.4.3
-   @nhost/react-apollo@11.0.3

## @nhost-examples/react-gqty@1.2.3

### Patch Changes

-   @nhost/react@3.4.3

## @nhost-examples/vue-apollo@0.6.3

### Patch Changes

-   @nhost/nhost-js@3.1.1
-   @nhost/apollo@7.0.1
-   @nhost/vue@2.5.3

## @nhost-examples/vue-quickstart@0.2.3

### Patch Changes

-   @nhost/apollo@7.0.1
-   @nhost/vue@2.5.3

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-05-13 21:58:21 +01:00
Hassan Ben Jobrane
8512a7f181 fix:(hasura-storage-js): add missing StorageHeadersParam to StorageGetUrlParams and StorageGetPresignedUrlParams (#2700) 2024-05-13 21:39:48 +01:00
github-actions[bot]
e503b8fe8b chore: update versions (#2691)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/graphql-js@0.2.0

### Minor Changes

- 304065a: feat: add `setHeaders` method enabling global configuration
of storage, graphql, and functions client headers, alongside added
support for passing specific headers with individual calls

## @nhost/hasura-storage-js@2.5.0

### Minor Changes

- 304065a: feat: add `setHeaders` method enabling global configuration
of storage, graphql, and functions client headers, alongside added
support for passing specific headers with individual calls

## @nhost/nhost-js@3.1.0

### Minor Changes

- 304065a: feat: add `setHeaders` method enabling global configuration
of storage, graphql, and functions client headers, alongside added
support for passing specific headers with individual calls

### Patch Changes

-   Updated dependencies [68e0622]
-   Updated dependencies [304065a]
    -   @nhost/hasura-auth-js@2.4.2
    -   @nhost/hasura-storage-js@2.5.0
    -   @nhost/graphql-js@0.2.0

## @nhost/apollo@7.0.0

### Patch Changes

-   Updated dependencies [304065a]
    -   @nhost/nhost-js@3.1.0

## @nhost/react-apollo@11.0.2

### Patch Changes

-   @nhost/apollo@7.0.0
-   @nhost/react@3.4.2

## @nhost/react-urql@8.0.2

### Patch Changes

-   @nhost/react@3.4.2

## @nhost/hasura-auth-js@2.4.2

### Patch Changes

- 68e0622: fix: resolved infinite loop occurring with requests to /token
when a user logs out in one tab while other tabs are open

## @nhost/nextjs@2.1.11

### Patch Changes

-   @nhost/react@3.4.2

## @nhost/react@3.4.2

### Patch Changes

-   Updated dependencies [304065a]
    -   @nhost/nhost-js@3.1.0

## @nhost/vue@2.5.2

### Patch Changes

-   Updated dependencies [304065a]
    -   @nhost/nhost-js@3.1.0

## @nhost/dashboard@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

## @nhost/docs@2.10.3

### Patch Changes

-   a58c5cf: fix: broken link

## @nhost-examples/cli@0.3.2

### Patch Changes

-   Updated dependencies [304065a]
    -   @nhost/nhost-js@3.1.0

## @nhost-examples/codegen-react-apollo@0.4.2

### Patch Changes

-   @nhost/react@3.4.2
-   @nhost/react-apollo@11.0.2

## @nhost-examples/codegen-react-query@0.4.2

### Patch Changes

-   @nhost/react@3.4.2

## @nhost-examples/codegen-react-urql@0.3.2

### Patch Changes

-   @nhost/react@3.4.2
-   @nhost/react-urql@8.0.2

## @nhost-examples/multi-tenant-one-to-many@2.2.2

### Patch Changes

-   Updated dependencies [304065a]
    -   @nhost/nhost-js@3.1.0

## @nhost-examples/nextjs@0.3.2

### Patch Changes

-   @nhost/react@3.4.2
-   @nhost/react-apollo@11.0.2
-   @nhost/nextjs@2.1.11

## @nhost-examples/node-storage@0.2.2

### Patch Changes

-   Updated dependencies [304065a]
    -   @nhost/nhost-js@3.1.0

## @nhost-examples/nextjs-server-components@0.4.2

### Patch Changes

-   Updated dependencies [304065a]
    -   @nhost/nhost-js@3.1.0

## @nhost-examples/react-apollo@0.8.2

### Patch Changes

-   @nhost/react@3.4.2
-   @nhost/react-apollo@11.0.2

## @nhost-examples/react-gqty@1.2.2

### Patch Changes

-   @nhost/react@3.4.2

## @nhost-examples/vue-apollo@0.6.2

### Patch Changes

-   Updated dependencies [304065a]
    -   @nhost/nhost-js@3.1.0
    -   @nhost/apollo@7.0.0
    -   @nhost/vue@2.5.2

## @nhost-examples/vue-quickstart@0.2.2

### Patch Changes

-   @nhost/apollo@7.0.0
-   @nhost/vue@2.5.2

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-05-13 13:52:42 +01:00
Hassan Ben Jobrane
304065ae22 feat: sdk: add setHeaders for setting global headers and make sure all client calls have a headers arg (#2697)
resolves https://github.com/nhost/nhost/issues/2696
2024-05-13 13:38:28 +01:00
Hassan Ben Jobrane
68e0622eb0 fix: broadcast signout event to all tabs (#2686)
fixes https://github.com/nhost/nhost/issues/2635
2024-05-09 11:20:39 +01:00
Hassan Ben Jobrane
70c6834636 chore: add e2e test project (#2688) 2024-05-08 12:52:11 +01:00
Johanna Blom
a7bde37bba feat (dashboard): send metadata in the edit form (#2689)
resolves #2684

---------

Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-05-08 13:36:03 +02:00
Hassan Ben Jobrane
1bc615beca fix(dashboard): improve error toast message handling (#2692) 2024-05-08 12:35:21 +01:00
David Barroso
a58c5cfc96 fix (docs): broken link (#2690) 2024-05-08 12:42:58 +02:00
Daniel Olofsson
c61228e45d Fixed typo in README.md (#2687) 2024-05-07 14:39:31 +02:00
github-actions[bot]
6cec04bd6f chore: update versions (#2680)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@1.14.0

### Minor Changes

-   a448d7d: feat: allow configuring postmark and delete SMTP settings

## @nhost/docs@2.10.2

### Patch Changes

-   9480489: fix: update docs performance info

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-25 13:13:32 +01:00
David Barroso
a448d7d182 feat (dashboard): allow configuring postmark and deleting SMTP settings (#2678)
Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-04-25 12:58:47 +01:00
David Barroso
948048940e fix (docs): update docs performance info (#2679)
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2024-04-25 13:50:45 +02:00
github-actions[bot]
5e91221d5a chore: update versions (#2672)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@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

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-18 15:49:01 +01:00
Hassan Ben Jobrane
7278991a59 fix: dashboard: update graphql auto-embeddings configuration to use String type for model field (#2674) 2024-04-18 15:22:32 +01:00
Hassan Ben Jobrane
5924bc3248 fix: dashboard: include password in GetSmtpSettings query (#2673) 2024-04-18 15:14:02 +01:00
Hassan Ben Jobrane
c5ad634799 fix: dashboard: check for undefined router query on Safari when accessing base64config (#2671) 2024-04-18 10:04:18 +01:00
github-actions[bot]
426b93a19f chore: update versions (#2670)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@1.13.2

### Patch Changes

-   026f84f: fix: use configuration server URL from environment variable

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-17 14:15:04 +01:00
Hassan Ben Jobrane
026f84f466 fix: dashboard: use config server url from env variable (#2669)
related to https://github.com/nhost/cli/issues/862
2024-04-17 14:02:11 +01:00
github-actions[bot]
384fac00b1 chore: update versions (#2664)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@1.13.1

### Patch Changes

-   7e9a2ce: fix: resolve issue where run services form fails to open

## @nhost/docs@2.10.1

### Patch Changes

-   9525fd7: fix: update AI docs for 0.5.0
-   076fd4a: fix: update permissions page to indicate we use jsonpath

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-16 10:53:36 +01:00
Hassan Ben Jobrane
7e9a2ce136 fix: dashboard: fix isPlatform check when opening run services form (#2666) 2024-04-16 10:38:28 +01:00
David Barroso
076fd4a7c0 fix (docs): update permissions page to indicate we use jsonpath (#2663) 2024-04-15 14:50:21 +02:00
David Barroso
9525fd74b3 fix (docs): update AI docs for 0.5.0 (#2662) 2024-04-15 14:33:59 +02:00
github-actions[bot]
8a2bc98214 chore: update versions (#2648)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@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

## @nhost/docs@2.10.0

### Minor Changes

-   87ae23b: feat: added "advanced graphql" documentation

### Patch Changes

-   b2be364: feat: added postmark native integration

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-15 13:12:24 +01:00
Hassan Ben Jobrane
dd5d262062 feat: dashboard: add model field to auto-embeddings form (#2661)
fixes https://github.com/nhost/nhost/issues/2660
2024-04-15 12:51:57 +01:00
Hassan Ben Jobrane
09962bef37 feat: dashboard: enable local settings (#2647)
fixes https://github.com/nhost/projects/issues/66
2024-04-15 12:49:20 +01:00
Hassan Ben Jobrane
9cdecb6b23 feat: dashboard: add email field to account settings (#2612)
fixes https://github.com/nhost/nhost/issues/2561
2024-04-11 10:47:03 +01:00
David Barroso
e7eb90318e fix: observability: amend export of graphql dashboard (#2655) 2024-04-11 11:44:56 +02:00
Hassan Ben Jobrane
f67f22d321 chore: update @google-cloud/translate to 8.2.0 (#2654) 2024-04-11 10:00:13 +01:00
David Barroso
87ae23ba05 feat (docs/observability): added docs and observability dashboard for "advanced graphql" (#2653)
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2024-04-11 10:22:54 +02:00
David Barroso
b2be3642aa feat (docs): added postmark native integration (#2636) 2024-04-09 09:36:48 +02:00
github-actions[bot]
1230081ce6 chore: update versions (#2640)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@1.12.2

### Patch Changes

-   c195c51: fix: send email upon signin for unverified users

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-05 12:48:55 +01:00
Hassan Ben Jobrane
c195c517de fix: send email upon signin for unverified users (#2639) 2024-04-05 11:48:14 +01:00
294 changed files with 7648 additions and 2044 deletions

View File

@@ -1,5 +1,100 @@
# @nhost/dashboard
## 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

View File

@@ -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_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_CONFIGSERVER_URL __NEXT_PUBLIC_NHOST_CONFIGSERVER_URL__
RUN yarn global add pnpm@8.10.5
COPY .gitignore .gitignore

View File

@@ -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_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_CONFIGSERVER_URL__~${NEXT_PUBLIC_NHOST_CONFIGSERVER_URL}~g" {} +
exec "$@"

View File

@@ -0,0 +1,2 @@
.secrets
.nhost

View File

@@ -0,0 +1 @@
version: 3

View File

@@ -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>

View File

@@ -0,0 +1 @@
Потвърждение за смяна на имейл

View File

@@ -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>

View File

@@ -0,0 +1 @@
Потвърждаване на имейл

View File

@@ -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>

View File

@@ -0,0 +1 @@
Смяна на парола

View File

@@ -0,0 +1 @@
Вашият код е ${code}.

View File

@@ -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>

View File

@@ -0,0 +1 @@
Магически линк за вход

View File

@@ -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>

View File

@@ -0,0 +1 @@
Změna vaší emailové adresy

View File

@@ -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>

View File

@@ -0,0 +1 @@
Ověření vaší emailové adresy

View File

@@ -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>

View File

@@ -0,0 +1 @@
Obnova hesla

View File

@@ -0,0 +1 @@
Váš kód je ${code}.

View File

@@ -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>

View File

@@ -0,0 +1 @@
Bezpečný odkaz k přihlášení

View File

@@ -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>

View File

@@ -0,0 +1 @@
Change your email address

View File

@@ -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>

View File

@@ -0,0 +1 @@
Verify your email

View File

@@ -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>

View File

@@ -0,0 +1 @@
Reset your password

View File

@@ -0,0 +1 @@
Your code is ${code}.

View File

@@ -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>

View File

@@ -0,0 +1 @@
Secure sign-in link

View File

@@ -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>

View File

@@ -0,0 +1 @@
Cambiar dirección de correo electrónico

View File

@@ -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>

View File

@@ -0,0 +1 @@
Verifica tu correo electrónico

View File

@@ -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>

View File

@@ -0,0 +1 @@
Recuperar contraseña

View File

@@ -0,0 +1 @@
Tu código es ${code}.

View File

@@ -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>

View File

@@ -0,0 +1 @@
Enlace de acceso seguro

View File

@@ -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>

View File

@@ -0,0 +1 @@
Changez votre adresse courriel

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h2>V&eacute;rifiez votre courriel</h2>
<p>Utilisez ce lien pour v&eacute;rifier votre courriel :</p>
<p>
<a href="${link}">
V&eacute;rifier courriel
</a>
</p>
</body>
</html>

View File

@@ -0,0 +1 @@
Vérifier votre courriel

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h2>R&eacute;initialiser votre mot de passe</h2>
<p>Utilisez ce lien pour r&eacute;initialiser votre mot de passe :</p>
<p>
<a href="${link}">
R&eacute;initialiser mot de passe
</a>
</p>
</body>
</html>

View File

@@ -0,0 +1 @@
Réinitialiser votre mot de passe

View File

@@ -0,0 +1 @@
Votre code est ${code}.

View File

@@ -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&ccedil;on s&eacute;curisée :</p>
<p>
<a href="${link}">
Connexion
</a>
</p>
</body>
</html>

View File

@@ -0,0 +1 @@
Lien de connexion sécurisé

View File

@@ -0,0 +1,8 @@
${link},
${displayName},
${email},
${ticket},
${redirectTo},
${serverUrl},
${clientUrl},
${locale},

View File

@@ -0,0 +1 @@
${link}, ${displayName}, ${email}, ${ticket}, ${redirectTo}, ${serverUrl}, ${clientUrl}, ${locale}

View 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 }}'

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "1.12.1",
"version": "1.16.3",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",

View File

@@ -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>
);
}

View File

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

View File

@@ -11,7 +11,6 @@ import { useNotFoundRedirect } from '@/features/projects/common/hooks/useNotFoun
import { useProjectRoutes } from '@/features/projects/common/hooks/useProjectRoutes';
import { NextSeo } from 'next-seo';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { twMerge } from 'tailwind-merge';
export interface ProjectLayoutProps extends AuthenticatedLayoutProps {
@@ -48,15 +47,16 @@ function ProjectLayoutContent({
useNotFoundRedirect();
useEffect(() => {
if (isPlatform || !router.isReady) {
return;
}
// useEffect(() => {
// if (isPlatform || !router.isReady) {
// return;
// }
if (isRestrictedPath) {
router.push('/local/local');
}
}, [isPlatform, isRestrictedPath, router]);
// TODO // Double check what restricted path means here
// if (isRestrictedPath) {
// router.push('/local/local');
// }
// }, [isPlatform, isRestrictedPath, router]);
if (isRestrictedPath || loading) {
return <LoadingScreen />;

View File

@@ -7,6 +7,7 @@ import { List } from '@/components/ui/v2/List';
import type { ListItemButtonProps } from '@/components/ui/v2/ListItem';
import { ListItem } from '@/components/ui/v2/ListItem';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
@@ -61,6 +62,7 @@ export default function SettingsSidebar({
className,
...props
}: SettingsSidebarProps) {
const isPlatform = useIsPlatform();
const [expanded, setExpanded] = useState(false);
const { currentProject } = useCurrentWorkspaceAndProject();
@@ -95,7 +97,7 @@ export default function SettingsSidebar({
<>
<Backdrop
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"
tabIndex={-1}
onClick={() => setExpanded(false)}
@@ -112,7 +114,7 @@ export default function SettingsSidebar({
<Box
component="aside"
className={twMerge(
'absolute top-0 z-[35] h-full w-full overflow-auto border-r-1 px-2 pt-2 pb-17 motion-safe:transition-transform md:relative md:z-0 md:h-full md:py-2.5 md:transition-none',
'absolute top-0 z-[35] h-full w-full overflow-auto border-r-1 px-2 pb-17 pt-2 motion-safe:transition-transform md:relative md:z-0 md:h-full md:py-2.5 md:transition-none',
expanded ? 'translate-x-0' : '-translate-x-full md:translate-x-0',
className,
)}
@@ -181,7 +183,12 @@ export default function SettingsSidebar({
SMTP
</SettingsNavLink>
<SettingsNavLink href="/git" exact={false} onClick={handleSelect}>
<SettingsNavLink
href="/git"
exact={false}
onClick={handleSelect}
disabled={!isPlatform}
>
Git
</SettingsNavLink>

View File

@@ -29,10 +29,10 @@ const getInternalErrorMessage = (
if (error.name === 'ApolloError') {
// @ts-ignore
const internalError = error.graphQLErrors?.[0]?.extensions?.internal as {
error: { message: string };
};
return internalError?.error?.message || null;
const graphqlError = error.graphQLErrors?.[0];
const graphqlExtensionsError = graphqlError?.extensions?.internal
?.error as { message: string };
return graphqlError.message || graphqlExtensionsError?.message || null;
}
if (error instanceof Error) {

View File

@@ -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>
);
}

View File

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

View File

@@ -1,4 +1,5 @@
import { useDialog } from '@/components/common/DialogProvider';
import { ControlledSelect } from '@/components/form/ControlledSelect';
import { Form } from '@/components/form/Form';
import { Box } from '@/components/ui/v2/Box';
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 { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { Input } from '@/components/ui/v2/Input';
import { Option } from '@/components/ui/v2/Option';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
@@ -20,11 +22,18 @@ import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
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({
name: Yup.string().required('The name is required.'),
schemaName: Yup.string().required('The schema is required'),
tableName: Yup.string().required('The table is required'),
columnName: Yup.string().required('The column is required'),
name: Yup.string().required('The name field is required.'),
model: Yup.string().oneOf(AUTO_EMBEDDINGS_MODELS),
schemaName: Yup.string().required('The schema field is required'),
tableName: Yup.string().required('The table field is required'),
columnName: Yup.string().required('The column field is required'),
query: Yup.string(),
mutation: Yup.string(),
});
@@ -40,7 +49,7 @@ export interface AutoEmbeddingsFormProps extends DialogFormProps {
/**
* 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.
@@ -74,7 +83,10 @@ export default function AutoEmbeddingsForm({
});
const form = useForm<AutoEmbeddingsFormValues>({
defaultValues: initialData,
defaultValues: {
...initialData,
model: initialData?.model ?? 'text-embedding-ada-002',
},
reValidateMode: 'onSubmit',
resolver: yupResolver(validationSchema),
});
@@ -106,7 +118,9 @@ export default function AutoEmbeddingsForm({
}
await insertGraphiteAutoEmbeddingsConfiguration({
variables: values,
variables: {
...values,
},
});
};
@@ -129,9 +143,9 @@ export default function AutoEmbeddingsForm({
<FormProvider {...form}>
<Form
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
{...register('name')}
id="name"
@@ -141,7 +155,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Name of the Auto-Embeddings">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -155,6 +169,36 @@ export default function AutoEmbeddingsForm({
autoComplete="off"
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
{...register('schemaName')}
id="schemaName"
@@ -164,7 +208,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title={<span>Schema where the table belongs to</span>}>
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -186,7 +230,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Table Name">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -208,7 +252,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Column name">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -230,7 +274,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Query">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -254,7 +298,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Mutation">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -271,7 +315,7 @@ export default function AutoEmbeddingsForm({
/>
</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}>
Cancel
</Button>

View File

@@ -1,3 +1,4 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
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 { Tooltip } from '@/components/ui/v2/Tooltip';
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 { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection';
import {
@@ -20,6 +22,7 @@ import {
useGetSoftwareVersionsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
import { getToastStyleProps } from '@/utils/constants/settings';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
@@ -50,9 +53,13 @@ const validationSchema = Yup.object({
export type AISettingsFormValues = Yup.InferType<typeof validationSchema>;
export default function AISettings() {
const { maintenanceActive } = useUI();
const isPlatform = useIsPlatform();
const { openDialog } = useDialog();
const [updateConfig] = useUpdateConfigMutation();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { currentProject } = useCurrentWorkspaceAndProject();
const [aiServiceEnabled, setAIServiceEnabled] = useState(true);
@@ -67,6 +74,7 @@ export default function AISettings() {
variables: {
appId: currentProject.id,
},
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data: graphiteVersionsData, loading: loadingGraphiteVersionsData } =
@@ -74,6 +82,7 @@ export default function AISettings() {
variables: {
software: Software_Type_Enum.Graphite,
},
skip: !isPlatform,
});
const graphiteVersions = graphiteVersionsData?.softwareVersions || [];
@@ -98,8 +107,8 @@ export default function AISettings() {
reValidateMode: 'onSubmit',
defaultValues: {
version: {
label: ai?.version ?? availableVersions?.at(0)?.label,
value: ai?.version ?? availableVersions?.at(0)?.value,
label: ai?.version || availableVersions?.at(0)?.label || '',
value: ai?.version || availableVersions?.at(0)?.value || '',
},
webhookSecret: '',
organization: '',
@@ -225,6 +234,18 @@ export default function AISettings() {
});
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'AI settings are being updated...',
@@ -247,7 +268,7 @@ export default function AISettings() {
return (
<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>
<Switch
checked={aiServiceEnabled}
@@ -270,54 +291,58 @@ export default function AISettings() {
className="flex flex-col"
>
<Box className="space-y-4">
{availableVersions.length > 0 && (
<Box className="space-y-2">
<Box className="flex flex-row items-center space-x-2">
<Text className="text-lg font-semibold">Version</Text>
<Tooltip title="Version of the service to use.">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
color="primary"
/>
</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 className="space-y-2">
<Box className="flex flex-row items-center space-x-2">
<Text className="text-lg font-semibold">Version</Text>
<Tooltip title="Version of the service to use.">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
color="primary"
/>
</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 ||
!!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="flex flex-row items-center space-x-2">
@@ -327,7 +352,7 @@ export default function AISettings() {
<Tooltip title="Used to validate requests between postgres and the AI service. The AI service will also include the header X-Graphite-Webhook-Secret with this value set when calling external webhooks so the source of the request can be validated.">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -351,26 +376,28 @@ export default function AISettings() {
<Tooltip title="Dedicated resources allocated for the service.">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
</Box>
<Alert
severity="info"
className="flex items-center justify-between space-x-2"
>
<span>{getAIResourcesCost()}</span>
<b>
$
{parseFloat(
(
aiSettingsFormValues.compute.cpu * COST_PER_VCPU
).toFixed(2),
)}
</b>
</Alert>
{isPlatform ? (
<Alert
severity="info"
className="flex items-center justify-between space-x-2"
>
<span>{getAIResourcesCost()}</span>
<b>
$
{parseFloat(
(
aiSettingsFormValues.compute.cpu * COST_PER_VCPU
).toFixed(2),
)}
</b>
</Alert>
) : null}
<ComputeFormSection />
</Box>
@@ -389,7 +416,7 @@ export default function AISettings() {
<Tooltip title="Key to use for authenticating API requests to OpenAI">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -412,7 +439,7 @@ export default function AISettings() {
<Tooltip title="Optional. OpenAI organization to use.">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -440,7 +467,7 @@ export default function AISettings() {
<Tooltip title="How often to run the job that keeps embeddings up to date.">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>

View File

@@ -1,8 +1,11 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { useUpdateConfigMutation } from '@/utils/__generated__/graphql';
import { useState } from 'react';
@@ -23,11 +26,14 @@ export default function DisableAIServiceConfirmationDialog({
onCancel,
onServiceDisabled,
}: DisableAIServiceConfirmationDialogProps) {
const { closeDialog } = useDialog();
const isPlatform = useIsPlatform();
const { openDialog, closeDialog } = useDialog();
const localMimirClient = useLocalMimirClient();
const [loading, setLoading] = useState(false);
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation();
const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
async function handleClick() {
setLoading(true);
@@ -45,6 +51,18 @@ export default function DisableAIServiceConfirmationDialog({
onServiceDisabled();
closeDialog();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Disabling the AI service...',

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup';
@@ -26,15 +31,19 @@ export type AllowedEmailSettingsFormValues = Yup.InferType<
>;
export default function AllowedEmailDomainsSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { email, emailDomains } = data?.config?.auth?.user || {};
@@ -54,6 +63,17 @@ export default function AllowedEmailDomainsSettings() {
const isDirty = Object.keys(formState.dirtyFields).length > 0;
useEffect(() => {
if (!loading && email && emailDomains) {
form.reset({
enabled:
email?.allowed?.length > 0 || emailDomains?.allowed?.length > 0,
allowedEmails: email?.allowed?.join(', ') || '',
allowedEmailDomains: emailDomains?.allowed?.join(', ') || '',
});
}
}, [loading, form, email, emailDomains]);
if (loading) {
return (
<ActivityIndicator
@@ -105,6 +125,18 @@ export default function AllowedEmailDomainsSettings() {
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Allowed email settings are being updated...',

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -23,15 +28,19 @@ export type AllowedRedirectURLFormValues = Yup.InferType<
>;
export default function AllowedRedirectURLsSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { allowedUrls } = data?.config?.auth?.redirections || {};
@@ -44,6 +53,14 @@ export default function AllowedRedirectURLsSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading && allowedUrls) {
form.reset({
allowedUrls: allowedUrls?.join(', ') || '',
});
}
}, [loading, allowedUrls, form]);
if (loading) {
return (
<ActivityIndicator
@@ -82,6 +99,18 @@ export default function AllowedRedirectURLsSettings() {
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Allowed redirect URL settings are being updated...',

View File

@@ -1,15 +1,20 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -20,15 +25,19 @@ const validationSchema = Yup.object({
export type AnonymousSignInFormValues = Yup.InferType<typeof validationSchema>;
export default function AnonymousSignInSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { enabled } = data?.config?.auth?.method?.anonymous || {};
@@ -41,6 +50,12 @@ export default function AnonymousSignInSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({ enabled });
}
}, [loading, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -75,6 +90,18 @@ export default function AnonymousSignInSettings() {
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Anonymous sign-in settings are being updated...',

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -7,16 +9,19 @@ import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
import { Input } from '@/components/ui/v2/Input';
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useTheme } from '@mui/material';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup';
@@ -53,15 +58,19 @@ export type AppleProviderFormValues = Yup.InferType<typeof validationSchema>;
export default function AppleProviderSettings() {
const theme = useTheme();
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, enabled, keyId, privateKey, teamId } =
@@ -79,6 +88,18 @@ export default function AppleProviderSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
teamId: teamId || '',
keyId: keyId || '',
clientId: clientId || '',
privateKey: privateKey || '',
enabled: enabled || false,
});
}
}, [loading, teamId, keyId, clientId, privateKey, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -119,6 +140,18 @@ export default function AppleProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Apple settings are being updated...',
@@ -236,7 +269,7 @@ export default function AppleProviderSettings() {
);
}}
>
<CopyIcon className="h-4 w-4" />
<CopyIcon className="w-4 h-4" />
</IconButton>
</InputAdornment>
}

View File

@@ -1,9 +1,12 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetAuthenticationSettingsDocument,
Software_Type_Enum,
@@ -11,8 +14,10 @@ import {
useGetSoftwareVersionsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -28,21 +33,26 @@ export type AuthServiceVersionFormValues = Yup.InferType<
>;
export default function AuthServiceVersionSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data: authVersionsData } = useGetSoftwareVersionsQuery({
variables: {
software: Software_Type_Enum.Auth,
},
skip: !isPlatform,
});
const { version } = data?.config?.auth || {};
@@ -59,10 +69,21 @@ export default function AuthServiceVersionSettings() {
const form = useForm<AuthServiceVersionFormValues>({
reValidateMode: 'onSubmit',
defaultValues: { version: { label: version, value: version } },
defaultValues: { version: { label: '', value: '' } },
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading && version) {
form.reset({
version: {
label: version,
value: version,
},
});
}
}, [loading, version, form]);
if (loading) {
return (
<ActivityIndicator
@@ -97,6 +118,18 @@ export default function AuthServiceVersionSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Auth version is being updated...',
@@ -120,12 +153,20 @@ export default function AuthServiceVersionSettings() {
}}
docsLink="https://github.com/nhost/hasura-auth/releases"
docsTitle="the latest releases"
className="grid grid-flow-row gap-x-4 gap-y-2 px-4 lg:grid-cols-5"
className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
>
<ControlledAutocomplete
id="version"
name="version"
autoHighlight
freeSolo
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option || '';
}
return option.value;
}}
isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => {
const inputValueLower = inputValue.toLowerCase();

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -8,15 +10,18 @@ import { Input } from '@/components/ui/v2/Input';
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
import { BaseProviderSettings } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup';
@@ -46,15 +51,19 @@ const validationSchema = Yup.object({
export type AzureADProviderFormValues = Yup.InferType<typeof validationSchema>;
export default function AzureADProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, tenant, enabled } =
@@ -71,6 +80,17 @@ export default function AzureADProviderSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
tenant: tenant || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, tenant, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -108,6 +128,18 @@ export default function AzureADProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Azure AD settings are being updated...',
@@ -182,7 +214,7 @@ export default function AzureADProviderSettings() {
);
}}
>
<CopyIcon className="h-4 w-4" />
<CopyIcon className="w-4 h-4" />
</IconButton>
</InputAdornment>
}

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup';
@@ -24,15 +29,19 @@ const validationSchema = Yup.object({
export type BlockedEmailFormValues = Yup.InferType<typeof validationSchema>;
export default function BlockedEmailSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { email, emailDomains } = data?.config?.auth?.user || {};
@@ -51,6 +60,17 @@ export default function BlockedEmailSettings() {
const enabled = watch('enabled');
const isDirty = Object.keys(formState.dirtyFields).length > 0;
useEffect(() => {
if (!loading && email && emailDomains) {
form.reset({
enabled:
email?.blocked?.length > 0 || emailDomains?.blocked?.length > 0,
blockedEmails: email?.blocked?.join(', ') || '',
blockedEmailDomains: emailDomains?.blocked?.join(', ') || '',
});
}
}, [loading, email, emailDomains, form]);
if (loading) {
return (
<ActivityIndicator
@@ -112,6 +132,18 @@ export default function BlockedEmailSettings() {
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage:

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -21,15 +26,19 @@ const validationSchema = Yup.object({
export type ClientURLFormValues = Yup.InferType<typeof validationSchema>;
export default function ClientURLSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientUrl, allowedUrls } = data?.config?.auth?.redirections || {};
@@ -42,6 +51,12 @@ export default function ClientURLSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading && clientUrl) {
form.reset({ clientUrl });
}
}, [loading, clientUrl, form]);
if (loading) {
return (
<ActivityIndicator
@@ -77,6 +92,18 @@ export default function ClientURLSettings() {
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Client URL is being updated...',

View File

@@ -0,0 +1,143 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
GetSmtpSettingsDocument,
useUpdateConfigMutation,
} from '@/utils/__generated__/graphql';
import { useState } from 'react';
import { twMerge } from 'tailwind-merge';
function ConfirmDeleteSMTPSettingsModal({
close,
onDelete,
}: {
onDelete?: () => Promise<any>;
close: () => void;
}) {
const onClickDelete = async () => {
await onDelete();
close();
};
return (
<Box className={twMerge('w-full rounded-lg p-6 text-left')}>
<div className="grid grid-flow-row gap-4">
<Text variant="h3" component="h2">
Delete SMTP Settings?
</Text>
<Text>This will reset all your SMTP and Postmark settings.</Text>
<div className="grid grid-flow-row gap-2">
<Button color="error" onClick={onClickDelete}>
Delete
</Button>
<Button variant="outlined" color="secondary" onClick={close}>
Cancel
</Button>
</div>
</div>
</Box>
);
}
export default function DeleteSMTPSettings() {
const { openDialog, closeDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI();
const [loading, setLoading] = useState(false);
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSmtpSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const deleteSMTPSettings = async () => {
const updateConfigPromise = updateConfig({
variables: {
appId: currentProject.id,
config: {
provider: {
smtp: null,
},
},
},
});
setLoading(true);
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'SMTP settings are being deleted...',
successMessage: 'SMTP settings have been deleted successfully.',
errorMessage:
'An error occurred while trying to delete the SMTP settings.',
},
);
setLoading(false);
};
const confirmDeleteSMTPSettings = async () => {
openDialog({
component: (
<ConfirmDeleteSMTPSettingsModal
close={closeDialog}
onDelete={deleteSMTPSettings}
/>
),
});
};
return (
<SettingsContainer
title="Delete SMTP Settings"
description="Delete SMTP settings and revert to default values"
className="px-0"
slotProps={{
submitButton: { className: 'hidden' },
footer: { className: 'hidden' },
}}
>
<Box className="grid grid-flow-row border-t-1">
<Button
color="error"
className="mx-4 mt-4 justify-self-end"
onClick={confirmDeleteSMTPSettings}
disabled={loading || maintenanceActive}
loading={loading}
>
Delete
</Button>
</Box>
</SettingsContainer>
);
}

View File

@@ -0,0 +1 @@
export { default as DeleteSTMPSettings } from './DeleteSMTPSettings';

View File

@@ -1,14 +1,19 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
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 {
GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
} from '@/utils/__generated__/graphql';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -19,15 +24,19 @@ const validationSchema = Yup.object({
export type DisableNewUsersFormValues = Yup.InferType<typeof validationSchema>;
export default function DisableNewUsersSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const form = useForm<DisableNewUsersFormValues>({
@@ -37,6 +46,14 @@ export default function DisableNewUsersSettings() {
},
});
useEffect(() => {
if (!loading) {
form.reset({
disabled: !data?.config?.auth?.signUp?.enabled,
});
}
}, [loading, data, form]);
if (loading) {
return (
<ActivityIndicator
@@ -73,6 +90,18 @@ export default function DisableNewUsersSettings() {
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Disabling new user sign ups...',

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
export default function DiscordProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function DiscordProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -89,6 +108,18 @@ export default function DiscordProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Discord settings are being updated...',
@@ -153,7 +184,7 @@ export default function DiscordProviderSettings() {
);
}}
>
<CopyIcon className="h-4 w-4" />
<CopyIcon className="w-4 h-4" />
</IconButton>
</InputAdornment>
}

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { ControlledCheckbox } from '@/components/form/ControlledCheckbox';
import { Form } from '@/components/form/Form';
@@ -6,13 +8,16 @@ import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -29,15 +34,19 @@ const validationSchema = Yup.object({
export type EmailAndPasswordFormValues = Yup.InferType<typeof validationSchema>;
export default function EmailAndPasswordSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, error, loading } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { hibpEnabled, emailVerificationRequired, passwordMinLength } =
@@ -53,6 +62,22 @@ export default function EmailAndPasswordSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
hibpEnabled,
emailVerificationRequired,
passwordMinLength,
});
}
}, [
loading,
hibpEnabled,
emailVerificationRequired,
passwordMinLength,
form,
]);
if (loading) {
return (
<ActivityIndicator
@@ -87,6 +112,18 @@ export default function EmailAndPasswordSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: `Email and password sign-in settings are being updated...`,

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
export default function FacebookProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function FacebookProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -89,6 +108,18 @@ export default function FacebookProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Facebook settings are being updated...',
@@ -153,7 +184,7 @@ export default function FacebookProviderSettings() {
);
}}
>
<CopyIcon className="h-4 w-4" />
<CopyIcon className="w-4 h-4" />
</IconButton>
</InputAdornment>
}

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,30 +14,37 @@ import {
baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useTheme } from '@mui/material';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
export default function GitHubProviderSettings() {
const theme = useTheme();
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, enabled } =
@@ -51,6 +60,16 @@ export default function GitHubProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -91,6 +110,18 @@ export default function GitHubProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'GitHub settings are being updated...',
@@ -159,7 +190,7 @@ export default function GitHubProviderSettings() {
);
}}
>
<CopyIcon className="h-4 w-4" />
<CopyIcon className="w-4 h-4" />
</IconButton>
</InputAdornment>
}

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
export default function GoogleProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function GoogleProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -89,6 +108,18 @@ export default function GoogleProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Google settings are being updated...',
@@ -153,7 +184,7 @@ export default function GoogleProviderSettings() {
);
}}
>
<CopyIcon className="h-4 w-4" />
<CopyIcon className="w-4 h-4" />
</IconButton>
</InputAdornment>
}

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { ControlledSelect } from '@/components/form/ControlledSelect';
import { Form } from '@/components/form/Form';
@@ -5,17 +7,20 @@ import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Option } from '@/components/ui/v2/Option';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import {
AUTH_GRAVATAR_DEFAULT,
AUTH_GRAVATAR_RATING,
} from '@/utils/constants/settings';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup';
@@ -29,15 +34,19 @@ const validationSchema = Yup.object({
export type GravatarFormValues = Yup.InferType<typeof validationSchema>;
export default function GravatarSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const {
@@ -56,6 +65,16 @@ export default function GravatarSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
default: defaultGravatar || '',
rating: rating || '',
enabled: enabled || false,
});
}
}, [loading, defaultGravatar, rating, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -91,6 +110,18 @@ export default function GravatarSettings() {
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Gravatar settings are being updated...',

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
export default function LinkedInProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function LinkedInProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -89,6 +108,18 @@ export default function LinkedInProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'LinkedIn settings are being updated...',
@@ -153,7 +184,7 @@ export default function LinkedInProviderSettings() {
);
}}
>
<CopyIcon className="h-4 w-4" />
<CopyIcon className="w-4 h-4" />
</IconButton>
</InputAdornment>
}

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup';
@@ -23,15 +28,19 @@ const validationSchema = Yup.object({
export type MFASettingsFormValues = Yup.InferType<typeof validationSchema>;
export default function MFASettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { enabled, issuer } = data?.config?.auth?.totp || {};
@@ -45,6 +54,15 @@ export default function MFASettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading && issuer && enabled) {
form.reset({
issuer,
enabled,
});
}
}, [loading, issuer, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -78,6 +96,18 @@ export default function MFASettings() {
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage:

View File

@@ -1,15 +1,20 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -20,15 +25,19 @@ const validationSchema = Yup.object({
export type MagicLinkFormValues = Yup.InferType<typeof validationSchema>;
export default function MagicLinkSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { enabled } = data?.config?.auth?.method?.emailPasswordless || {};
@@ -41,6 +50,12 @@ export default function MagicLinkSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({ enabled });
}
}, [loading, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -75,6 +90,18 @@ export default function MagicLinkSettings() {
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Magic Link settings are being updated...',

View File

@@ -0,0 +1,149 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
GetSmtpSettingsDocument,
useGetSmtpSettingsQuery,
useUpdateConfigMutation,
} from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form';
import * as yup from 'yup';
const validationSchema = yup
.object({
sender: yup.string().label('SMTP Sender').email().required(),
password: yup.string().label('Password').required(),
})
.required();
export type PostmarkFormValues = yup.InferType<typeof validationSchema>;
export default function PostmarkSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const { data } = useGetSmtpSettingsQuery({
variables: { appId: currentProject?.id },
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { sender, password } = data?.config?.provider?.smtp || {};
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSmtpSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const form = useForm<PostmarkFormValues>({
reValidateMode: 'onSubmit',
resolver: yupResolver(validationSchema),
defaultValues: {
password: '',
sender: '',
},
values: {
password: password || '',
sender: sender || '',
},
mode: 'onSubmit',
criteriaMode: 'all',
});
const {
register,
formState: { errors, isDirty, isSubmitting },
} = form;
const handleEditPostmarkSettings = async (values: PostmarkFormValues) => {
const updateConfigPromise = updateConfig({
variables: {
appId: currentProject.id,
config: {
provider: {
smtp: { method: 'LOGIN', host: 'postmark', ...values },
},
},
},
});
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Postmark settings are being updated...',
successMessage: 'Postmark settings have been updated successfully.',
errorMessage:
'An error occurred while trying to update your Postmark settings.',
},
);
};
return (
<FormProvider {...form}>
<Form onSubmit={handleEditPostmarkSettings}>
<SettingsContainer
title="Postmark Settings"
description="Configure postmark's native integration to send emails from your email domain."
submitButtonText="Save"
className="grid grid-cols-9 gap-4"
slotProps={{
submitButton: {
disabled: !isDirty || maintenanceActive,
loading: isSubmitting,
},
}}
>
<Input
{...register('sender')}
id="sender"
name="sender"
label="From Email"
placeholder="noreply@nhost.app"
className="lg:col-span-4"
hideEmptyHelperText
fullWidth
error={Boolean(errors.sender)}
helperText={errors.sender?.message}
/>
<Input
{...register('password')}
id="password"
label="Password"
type="password"
placeholder="Enter password"
className="lg:col-span-5"
hideEmptyHelperText
fullWidth
error={Boolean(errors.password)}
helperText={errors.password?.message}
/>
</SettingsContainer>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -7,14 +9,17 @@ import { Option } from '@/components/ui/v2/Option';
import { Select } from '@/components/ui/v2/Select';
import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import Image from 'next/image';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup';
@@ -44,15 +49,19 @@ const validationSchema = Yup.object({
export type SMSSettingsFormValues = Yup.InferType<typeof validationSchema>;
export default function SMSSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, error, loading } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { accountSid, authToken, messagingServiceId } =
@@ -70,6 +79,17 @@ export default function SMSSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
accountSid: accountSid || '',
authToken: authToken || '',
messagingServiceId: messagingServiceId || '',
enabled: enabled || false,
});
}
}, [loading, accountSid, authToken, messagingServiceId, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -114,6 +134,18 @@ export default function SMSSettings() {
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'SMS settings are being updated...',

View File

@@ -0,0 +1,238 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { ControlledCheckbox } from '@/components/form/ControlledCheckbox';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
GetSmtpSettingsDocument,
useGetSmtpSettingsQuery,
useUpdateConfigMutation,
} from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form';
import { type Optional } from 'utility-types';
import * as yup from 'yup';
const smtpValidationSchema = yup
.object({
secure: yup.bool().label('SMTP Secure'),
host: yup
.string()
.label('SMTP Host')
.matches(
/((https?):\/\/)?(www\.)?[a-z0-9]+(\.[a-z]{2,}){1,3}(#[a-zA-Z0-9#]+?)*\/?(\?[a-zA-Z0-9-_]+=[a-zA-Z0-9-%]+&?)?$/,
'SMTP Host must be a valid URL',
)
.required(),
port: yup
.number()
.typeError('The SMTP port should contain only numbers.')
.required(),
user: yup.string().label('Username').required(),
password: yup.string().label('Password'),
method: yup.string().required(),
sender: yup.string().label('SMTP Sender').email().required(),
})
.required();
export type SmtpFormValues = yup.InferType<typeof smtpValidationSchema>;
export default function SMTPSettings() {
const { maintenanceActive } = useUI();
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const { data } = useGetSmtpSettingsQuery({
variables: { appId: currentProject?.id },
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { secure, host, port, user, method, sender, password } =
data?.config?.provider?.smtp || {};
const form = useForm<Optional<SmtpFormValues, 'password'>>({
reValidateMode: 'onSubmit',
resolver: yupResolver(smtpValidationSchema),
defaultValues: {
secure: false,
host: '',
port: undefined,
user: '',
password: '',
method: '',
sender: '',
},
values: {
secure: secure || false,
host: host || '',
port,
user: user || '',
password: password || '',
method: method || '',
sender: sender || '',
},
mode: 'onSubmit',
criteriaMode: 'all',
});
const {
register: registerSmtp,
formState: { errors, isDirty, isSubmitting },
} = form;
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSmtpSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const handleEditSMTPSettings = async (values: SmtpFormValues) => {
const { password: newPassword, ...valuesWithoutPassword } = values;
const updateConfigPromise = updateConfig({
variables: {
appId: currentProject.id,
config: {
provider: {
smtp: newPassword ? values : valuesWithoutPassword,
},
},
},
});
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'SMTP settings are being updated...',
successMessage: 'SMTP settings have been updated successfully.',
errorMessage:
'An error occurred while trying to update the SMTP settings.',
},
);
};
return (
<FormProvider {...form}>
<Form onSubmit={handleEditSMTPSettings}>
<SettingsContainer
title="SMTP Settings"
description="Configure your SMTP settings to send emails from your email domain."
submitButtonText="Save"
className="grid grid-cols-9 gap-4"
slotProps={{
submitButton: {
disabled: !isDirty || maintenanceActive,
loading: isSubmitting,
},
}}
>
<Input
{...registerSmtp('sender')}
id="sender"
name="sender"
label="From Email"
placeholder="noreply@nhost.app"
className="lg:col-span-4"
hideEmptyHelperText
fullWidth
error={Boolean(errors.sender)}
helperText={errors.sender?.message}
/>
<Input
{...registerSmtp('host')}
id="host"
name="host"
label="SMTP Host"
className="lg:col-span-4"
placeholder="e.g. smtp.sendgrid.net"
hideEmptyHelperText
fullWidth
error={Boolean(errors.host)}
helperText={errors.host?.message}
/>
<Input
{...registerSmtp('port')}
id="port"
name="port"
label="Port"
type="number"
placeholder="587"
className="lg:col-span-1"
hideEmptyHelperText
fullWidth
error={Boolean(errors.port)}
helperText={errors.port?.message}
/>
<Input
{...registerSmtp('user')}
id="user"
label="SMTP Username"
placeholder="SMTP Username"
className="lg:col-span-4"
hideEmptyHelperText
fullWidth
error={Boolean(errors.user)}
helperText={errors.user?.message}
/>
<Input
{...registerSmtp('password')}
id="password"
label="SMTP Password"
type="password"
placeholder="Enter SMTP password"
className="lg:col-span-5"
hideEmptyHelperText
fullWidth
error={Boolean(errors.password)}
helperText={errors.password?.message}
/>
<Input
{...registerSmtp('method')}
id="method"
name="method"
label="SMTP Auth Method"
placeholder="LOGIN"
hideEmptyHelperText
className="lg:col-span-4"
fullWidth
error={Boolean(errors.method)}
helperText={errors.method?.message}
/>
<ControlledCheckbox
name="secure"
id="secure"
label="Use SSL"
className="lg:col-span-9"
/>
</SettingsContainer>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -28,15 +33,19 @@ const validationSchema = Yup.object({
export type SessionFormValues = Yup.InferType<typeof validationSchema>;
export default function SessionSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { accessToken, refreshToken } = data?.config?.auth?.session || {};
@@ -50,6 +59,15 @@ export default function SessionSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading && accessToken && refreshToken) {
form.reset({
accessTokenExpiresIn: accessToken?.expiresIn || 900,
refreshTokenExpiresIn: refreshToken?.expiresIn || 43200,
});
}
}, [loading, accessToken, refreshToken, form]);
if (loading) {
return (
<ActivityIndicator
@@ -86,6 +104,18 @@ export default function SessionSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Session settings are being updated...',

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
export default function SpotifyProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function SpotifyProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -89,6 +108,18 @@ export default function SpotifyProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Spotify settings are being updated...',
@@ -153,7 +184,7 @@ export default function SpotifyProviderSettings() {
);
}}
>
<CopyIcon className="h-4 w-4" />
<CopyIcon className="w-4 h-4" />
</IconButton>
</InputAdornment>
}

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,30 +14,37 @@ import {
baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useTheme } from '@mui/material';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
export default function TwitchProviderSettings() {
const theme = useTheme();
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, enabled } =
@@ -51,6 +60,16 @@ export default function TwitchProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -91,6 +110,18 @@ export default function TwitchProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Twitch settings are being updated...',
@@ -159,7 +190,7 @@ export default function TwitchProviderSettings() {
);
}}
>
<CopyIcon className="h-4 w-4" />
<CopyIcon className="w-4 h-4" />
</IconButton>
</InputAdornment>
}

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -7,15 +9,18 @@ import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
import { Input } from '@/components/ui/v2/Input';
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup';
@@ -39,15 +44,19 @@ const validationSchema = Yup.object({
export type TwitterProviderFormValues = Yup.InferType<typeof validationSchema>;
export default function TwitterProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { consumerKey, consumerSecret, enabled } =
@@ -63,6 +72,16 @@ export default function TwitterProviderSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
consumerSecret: consumerSecret || '',
consumerKey: consumerKey || '',
enabled: enabled || false,
});
}
}, [loading, consumerKey, consumerSecret, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -100,6 +119,18 @@ export default function TwitterProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Twitter settings are being updated...',
@@ -185,7 +216,7 @@ export default function TwitterProviderSettings() {
);
}}
>
<CopyIcon className="h-4 w-4" />
<CopyIcon className="w-4 h-4" />
</IconButton>
</InputAdornment>
}

View File

@@ -1,15 +1,20 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -20,15 +25,19 @@ const validationSchema = Yup.object({
export type WebAuthnFormValues = Yup.InferType<typeof validationSchema>;
export default function WebAuthnSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { enabled } = data?.config?.auth?.method?.webauthn || {};
@@ -41,6 +50,12 @@ export default function WebAuthnSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({ enabled });
}
}, [loading, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -80,6 +95,18 @@ export default function WebAuthnSettings() {
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'WebAuthn settings are being updated...',

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
export default function WindowsLiveProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function WindowsLiveProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -89,6 +108,18 @@ export default function WindowsLiveProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Windows Live settings are being updated...',
@@ -151,7 +182,7 @@ export default function WindowsLiveProviderSettings() {
);
}}
>
<CopyIcon className="h-4 w-4" />
<CopyIcon className="w-4 h-4" />
</IconButton>
</InputAdornment>
}

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -8,15 +10,18 @@ import { Input } from '@/components/ui/v2/Input';
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
import { BaseProviderSettings } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup';
@@ -52,15 +57,19 @@ const validationSchema = Yup.object({
export type WorkOsProviderFormValues = Yup.InferType<typeof validationSchema>;
export default function WorkOsProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, organization, connection, enabled } =
@@ -78,6 +87,26 @@ export default function WorkOsProviderSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
organization: organization || '',
connection: connection || '',
enabled: enabled || false,
});
}
}, [
loading,
clientId,
clientSecret,
organization,
connection,
enabled,
form,
]);
if (loading) {
return (
<ActivityIndicator
@@ -115,6 +144,18 @@ export default function WorkOsProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'WorkOS settings are being updated...',
@@ -203,7 +244,7 @@ export default function WorkOsProviderSettings() {
);
}}
>
<CopyIcon className="h-4 w-4" />
<CopyIcon className="w-4 h-4" />
</IconButton>
</InputAdornment>
}

View File

@@ -31,9 +31,10 @@ import { yupResolver } from '@hookform/resolvers/yup';
import { useTheme } from '@mui/material';
import { format } from 'date-fns';
import kebabCase from 'just-kebab-case';
import debounce from 'lodash.debounce';
import Image from 'next/image';
import type { RemoteAppUser } from 'pages/[workspaceSlug]/[appSlug]/users';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -76,6 +77,21 @@ export const EditUserFormValidationSchema = Yup.object({
locale: Yup.string(),
defaultRole: Yup.string(),
roles: Yup.array().of(Yup.boolean()),
metadata: Yup.string().test(
'is-valid-json',
'Metadata must be valid JSON or empty',
(value) => {
if (value === '') {
return true;
} // Allow empty string as valid input
try {
JSON.parse(value);
return true;
} catch (error) {
return false;
}
},
),
});
export type EditUserFormValues = Yup.InferType<
@@ -116,14 +132,55 @@ export default function EditUserForm({
locale: user.locale,
defaultRole: user.defaultRole,
roles: roles.map((role) => Object.values(role)[0]),
metadata: user?.metadata ? JSON.stringify(user.metadata, null, 2) : '',
},
});
const {
register,
setError,
clearErrors,
formState: { errors, dirtyFields, isSubmitting, isValidating },
} = form;
const handleMetadataError = useMemo(() => {
const debouncedSetError = debounce((value) => {
try {
JSON.parse(value);
// Only set an error if JSON parsing fails
} catch (error) {
setError('metadata', {
type: 'manual',
message: 'Invalid JSON format',
});
}
}, 500);
return {
call: debouncedSetError,
cancel: debouncedSetError.cancel, // lodash debounce provides a cancel method to stop the delayed function
};
}, [setError]);
const handleMetadataChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const { value } = event.target;
if (value === '') {
clearErrors('metadata'); // Clear errors when the input is explicitly cleared
handleMetadataError.cancel(); // Cancel any debounced error checks
} else {
try {
JSON.parse(value);
clearErrors('metadata'); // Clear errors when valid JSON is entered
handleMetadataError.cancel(); // Cancel pending debounced error checks
} catch (error) {
handleMetadataError.call(value); // Call the debounced error setter
}
}
},
[clearErrors, handleMetadataError],
);
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
@@ -467,6 +524,28 @@ export default function EditUserForm({
</div>
</Box>
)}
<Box component="section" className="grid grid-flow-row gap-8 p-6">
<Input
{...register('metadata', { onChange: handleMetadataChange })}
id="metadata"
label="Metadata"
variant="inline"
hideEmptyHelperText
error={!!errors.metadata}
fullWidth
multiline
inputProps={{
className: 'resize-y min-h-[130px]',
}}
helperText={
errors.metadata
? errors.metadata.message
: 'Enter valid JSON. This can be a number, boolean, array, or object.'
}
maxRows={100}
autoComplete="off"
/>
</Box>
</Box>
<Box className="grid w-full flex-shrink-0 snap-end grid-flow-col justify-between gap-3 place-self-end border-t-1 p-2">

View File

@@ -113,6 +113,9 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
phoneNumber: values.phoneNumber,
phoneNumberVerified: values.phoneNumberVerified,
locale: values.locale,
...(values?.metadata !== undefined && values.metadata !== ''
? { metadata: JSON.parse(values.metadata) }
: { metadata: null }),
},
},
});

View File

@@ -1,3 +1,7 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Alert } from '@/components/ui/v2/Alert';
@@ -7,11 +11,32 @@ import type { InputProps } from '@/components/ui/v2/Input';
import { Input } from '@/components/ui/v2/Input';
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { useGetPostgresSettingsQuery } from '@/utils/__generated__/graphql';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
useGetPostgresSettingsQuery,
useUpdateConfigMutation,
} from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
const databasePublicAccessValidationSchema = Yup.object({
enablePublicAccess: Yup.bool(),
});
type DatabasePublicAccessFormValues = Yup.InferType<
typeof databasePublicAccessValidationSchema
>;
export default function DatabaseConnectionInfo() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const { data, loading, error } = useGetPostgresSettingsQuery({
@@ -19,6 +44,61 @@ export default function DatabaseConnectionInfo() {
fetchPolicy: 'cache-only',
});
const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const enablePublicAccess =
!!data?.config?.postgres?.resources?.enablePublicAccess;
const form = useForm<DatabasePublicAccessFormValues>({
reValidateMode: 'onSubmit',
defaultValues: {
enablePublicAccess,
},
resolver: yupResolver(databasePublicAccessValidationSchema),
});
async function handleSubmit(formValues: DatabasePublicAccessFormValues) {
const updateConfigPromise = updateConfig({
variables: {
appId: currentProject.id,
config: {
postgres: {
resources: {
enablePublicAccess: formValues.enablePublicAccess,
},
},
},
},
});
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Database settings are being updated...',
successMessage: 'Database settings have been updated successfully.',
errorMessage:
"An error occurred while trying to update the project's database settings.",
},
);
}
if (loading) {
return (
<ActivityIndicator
@@ -76,49 +156,72 @@ export default function DatabaseConnectionInfo() {
},
];
return (
<SettingsContainer
title="Connection Info"
description="Connect directly to the Postgres database with this information."
slotProps={{ footer: { className: 'hidden' } }}
className="grid grid-cols-6 gap-4 pb-2"
>
{settingsDatabaseCustomInputs.map(
({ name, label, className, value: inputValue }) => (
<Input
key={name}
label={label}
required
disabled
value={inputValue}
className={className}
slotProps={{ inputRoot: { className: '!pr-8 truncate' } }}
fullWidth
hideEmptyHelperText
endAdornment={
<InputAdornment position="end" className="absolute right-2">
<Button
sx={{ minWidth: 0, padding: 0 }}
color="secondary"
variant="borderless"
onClick={(e) => {
e.stopPropagation();
copy(inputValue as string, `${label}`);
}}
>
<CopyIcon className="h-4 w-4" />
</Button>
</InputAdornment>
}
/>
),
)}
const { formState } = form;
<Alert severity="info" className="col-span-6 text-left">
To connect to the Postgres database directly, generate a new password,
securely save it, and then modify your connection string with the newly
created password.
</Alert>
</SettingsContainer>
return (
<FormProvider {...form}>
<Form onSubmit={handleSubmit}>
<SettingsContainer
title="Public access"
description={
enablePublicAccess
? 'Connect directly to the Postgres database with this information.'
: 'Enable public access to your Postgres database.'
}
slotProps={{
submitButton: {
disabled: !formState.isDirty || maintenanceActive,
loading: formState.isSubmitting,
},
}}
className="grid grid-cols-6 gap-4 pb-2"
switchId="enablePublicAccess"
showSwitch
>
{enablePublicAccess && (
<>
{settingsDatabaseCustomInputs.map(
({ name, label, className, value: inputValue }) => (
<Input
key={name}
label={label}
required
disabled
value={inputValue}
className={className}
slotProps={{ inputRoot: { className: '!pr-8 truncate' } }}
fullWidth
hideEmptyHelperText
endAdornment={
<InputAdornment
position="end"
className="absolute right-2"
>
<Button
sx={{ minWidth: 0, padding: 0 }}
color="secondary"
variant="borderless"
onClick={(e) => {
e.stopPropagation();
copy(inputValue as string, `${label}`);
}}
>
<CopyIcon className="w-4 h-4" />
</Button>
</InputAdornment>
}
/>
),
)}
<Alert severity="info" className="col-span-6 text-left">
To connect to the Postgres database directly, generate a new
password, securely save it, and then modify your connection
string with the newly created password.
</Alert>
</>
)}
</SettingsContainer>
</Form>
</FormProvider>
);
}

View File

@@ -1,9 +1,12 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetPostgresSettingsDocument,
Software_Type_Enum,
@@ -11,8 +14,10 @@ import {
useGetSoftwareVersionsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -30,24 +35,30 @@ export type DatabaseServiceVersionFormValues = Yup.InferType<
>;
export default function DatabaseServiceVersionSettings() {
const isPlatform = useIsPlatform();
const { openDialog } = useDialog();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetPostgresSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetPostgresSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data: databaseVersionsData } = useGetSoftwareVersionsQuery({
variables: {
software: Software_Type_Enum.PostgreSql,
},
skip: !isPlatform,
});
const { version } = data?.config?.postgres || {};
const databaseVersions = databaseVersionsData?.softwareVersions || [];
const availableVersions = Array.from(
new Set(databaseVersions.map((el) => el.version)).add(version),
@@ -61,10 +72,21 @@ export default function DatabaseServiceVersionSettings() {
const form = useForm<DatabaseServiceVersionFormValues>({
reValidateMode: 'onSubmit',
defaultValues: { version: { label: version, value: version } },
defaultValues: { version: { label: '', value: '' } },
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading && version) {
form.reset({
version: {
label: version,
value: version,
},
});
}
}, [loading, version, form]);
if (loading) {
return (
<ActivityIndicator
@@ -99,6 +121,18 @@ export default function DatabaseServiceVersionSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Postgres version is being updated...',
@@ -123,12 +157,20 @@ export default function DatabaseServiceVersionSettings() {
}}
docsLink="https://hub.docker.com/r/nhost/postgres/tags"
docsTitle="the latest releases"
className="grid grid-flow-row gap-x-4 gap-y-2 px-4 lg:grid-cols-5"
className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
>
<ControlledAutocomplete
id="version"
name="version"
autoHighlight
freeSolo
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option || '';
}
return option.value;
}}
isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => {
const inputValueLower = inputValue.toLowerCase();

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