Compare commits

..

40 Commits

Author SHA1 Message Date
github-actions[bot]
76e77da5de chore: update versions (#3124)
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/hasura-auth-js@2.10.0

### Minor Changes

-   04d2ce1: feat: add signin security key with user handle

### Patch Changes

-   44c1e17: chore: update `msw` to v1.3.5 to fix vulnerabilities

## @nhost/react@3.9.0

### Minor Changes

-   04d2ce1: feat: add signin security key with user handle

### Patch Changes

-   @nhost/nhost-js@3.2.3

## @nhost/vue@2.9.0

### Minor Changes

-   04d2ce1: feat: add signin security key with user handle

### Patch Changes

-   @nhost/nhost-js@3.2.3

## @nhost/apollo@8.0.3

### Patch Changes

-   @nhost/nhost-js@3.2.3

## @nhost/react-apollo@16.0.0

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0
    -   @nhost/apollo@8.0.3

## @nhost/react-urql@13.0.0

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0

## @nhost/nextjs@2.2.1

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0

## @nhost/nhost-js@3.2.3

### Patch Changes

-   Updated dependencies [44c1e17]
-   Updated dependencies [04d2ce1]
    -   @nhost/hasura-auth-js@2.10.0

## @nhost/dashboard@2.14.0

### Minor Changes

- d43931e: fix: invalid organization slug/project subdomain doesn't open
404 page
- 5df6fa2: feat: add unencrypted disk warning in storage capacity
settings

### Patch Changes

-   44c1e17: chore: update `msw` to v1.3.5 to fix vulnerabilities
    -   @nhost/react-apollo@16.0.0
    -   @nhost/nextjs@2.2.1

## @nhost/docs@2.26.0

### Minor Changes

-   04d2ce1: feat: add reference documentation for signin security key

### Patch Changes

-   1fa6cc4: chore: added docs for pg_jsonschema

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

### Minor Changes

- 04d2ce1: feat: update signin components to use `useSignInSecuritykey`
with user handle

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0
    -   @nhost/react-apollo@16.0.0

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

### Minor Changes

- 04d2ce1: feat: update signin components to use `useSignInSecuritykey`
with user handle

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/vue@2.9.0
    -   @nhost/nhost-js@3.2.3
    -   @nhost/apollo@8.0.3

## @nhost-examples/cli@0.3.16

### Patch Changes

-   @nhost/nhost-js@3.2.3

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

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0
    -   @nhost/react-apollo@16.0.0

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

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0

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

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0
    -   @nhost/react-urql@13.0.0

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

### Patch Changes

-   @nhost/nhost-js@3.2.3

## @nhost-examples/nextjs@0.4.1

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0
    -   @nhost/react-apollo@16.0.0
    -   @nhost/nextjs@2.2.1

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

### Patch Changes

-   @nhost/nhost-js@3.2.3

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

### Patch Changes

-   @nhost/nhost-js@3.2.3

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

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0

## @nhost-examples/react-native@0.1.2

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0
    -   @nhost/react-apollo@16.0.0

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

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/vue@2.9.0
    -   @nhost/apollo@8.0.3

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-10 17:48:29 +01:00
Hassan Ben Jobrane
04d2ce110a feat: add support for webauthn modern flow (#3097)
### **User description**
resolves https://github.com/nhost/nhost/issues/3031


___

### **PR Type**
Enhancement


___

### **Description**
- Implemented WebAuthn-based security key authentication flow
- Added new `useSignInSecurityKey` hook for React applications
- Updated authentication machine to support security key sign-in
- Simplified security key sign-in process by removing email input
requirement
- Added `useSignInEmailOTP` hook for email OTP authentication
- Updated auth version in example configuration



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>8
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>sign-in-security-key.tsx</strong><dd><code>Simplify
security key sign-in process</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


examples/react-apollo/src/components/routes/auth/sign-in/sign-in-security-key.tsx

<li>Removed email input and form-related imports and components<br> <li>
Replaced <code>useSignInEmailSecurityKey</code> with
<code>useSignInSecurityKey</code><br> <li> Simplified sign-in process to
use <code>signInSecurityKey</code> without email<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3097/files#diff-c53d13936f5d9c0cc31811f17c9721f9d7a2795d0cff4a1333fdb147846f5cd8">+12/-47</a>&nbsp;
</td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>events.ts</strong><dd><code>Add new security key
sign-in event</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

packages/hasura-auth-js/src/machines/authentication/events.ts

- Added new event type `SIGNIN_SECURITY_KEY`



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3097/files#diff-a1a1ecc9ad9d8ed8e460e0401007a8d479b4d9ba66bc909e1d1458947b5fdf85">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>machine.ts</strong><dd><code>Implement WebAuthn-based
security key authentication</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

packages/hasura-auth-js/src/machines/authentication/machine.ts

<li>Added <code>signInSecurityKey</code> service<br> <li> Implemented
new state for security key authentication<br> <li> Added WebAuthn-based
authentication logic<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3097/files#diff-a8fdfee087ad5a72ea0a64667e2a0c7f25baa84eaaf73ebfee3f5a5a1b7584d1">+78/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>index.ts</strong><dd><code>Export new security key
sign-in module</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

packages/hasura-auth-js/src/promises/index.ts

- Exported `signInSecurityKey` module



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3097/files#diff-154b9309bf25adbddd17bf34e6e831aadbac8f8eae8df83d22933566c2466694">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>signInSecurityKey.ts</strong><dd><code>Add security key
sign-in promise and types</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

packages/hasura-auth-js/src/promises/signInSecurityKey.ts

<li>Implemented <code>signInSecurityKeyPromise</code> function<br> <li>
Added types and interfaces for security key sign-in<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3097/files#diff-4bbb85594a3d42a1b0ae555ceb2cd468e4dba9904ba59b189bfc76bdd61a92b3">+70/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>index.ts</strong><dd><code>Export new security key
sign-in hook</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

packages/react/src/index.ts

- Exported `useSignInSecurityKey` hook



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3097/files#diff-1cee8646d2cfba37d6ce6a6e9a8d16f8caba0b99fc3a1ad0cb997ed8c7384d2e">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>useSignInEmailOTP.ts</strong><dd><code>Add email OTP
sign-in hook</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

packages/react/src/useSignInEmailOTP.ts

- Implemented `useSignInEmailOTP` hook for email OTP authentication



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3097/files#diff-ec99d0a935dcc1d5fb83ebe3509d69c8f449a2f592a586ca491875aa566a83bf">+84/-1</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>useSignInSecurityKey.ts</strong><dd><code>Implement
security key sign-in hook</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

packages/react/src/useSignInSecurityKey.ts

<li>Implemented <code>useSignInSecurityKey</code> hook for
WebAuthn-based <br>authentication<br> <li> Added types and interfaces
for the hook<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3097/files#diff-67332920be590dafee3e397f2134dcb174b61e23b65ce32a7cafcb38dd61e331">+94/-0</a>&nbsp;
&nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Configuration
changes</strong></td><td><details><summary>1 files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>nhost.toml</strong><dd><code>Update auth version in
configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/nhost/nhost.toml

- Updated auth version from '0.32.1' to '0.36.1'



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3097/files#diff-268d6c8dddd6990d60d62c1c923955c4e0e7549a80f0f5856192f889378416a0">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></details></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2025-01-10 16:59:29 +01:00
David Barroso
b2755045c9 chore: added overlay for nhost cli (#3126)
### **PR Type**
Enhancement


___

### **Description**
- Add Nhost CLI package to Nix configuration

- Define Nhost CLI version and platform-specific distributions

- Implement Nix derivation for Nhost CLI installation

- Update overlay to include Nhost CLI package


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>nhost-cli.nix</strong><dd><code>Implement Nix package
for Nhost CLI</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

nix/nhost-cli.nix

<li>Define Nhost CLI version and platform-specific distributions<br>
<li> Implement Nix derivation for Nhost CLI installation<br> <li> Set up
build and installation process<br> <li> Define package metadata and
maintainer information


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3126/files#diff-9ecb33bccf0f24a938368b6152474086a6520c7f62d1b366fe76bdcb6cc6cc4a">+63/-0</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Configuration
changes</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>overlay.nix</strong><dd><code>Update overlay to include
Nhost CLI package</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

nix/overlay.nix

- Add Nhost CLI package to the Nix overlay


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3126/files#diff-0f31c68216d617b465827b69d5b5cd4c2c0c4489008cd14ee0e88b9887d15295">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2025-01-09 15:36:17 +01:00
David BM
d43931e761 fix (dashboard): invalid organization slug/project subdomain opens 404 page (#3125)
### **User description**
Fixes #3119


___

### **PR Type**
Bug fix


___

### **Description**
- Fix 404 redirect for invalid org slug/project subdomain

- Improve conditional checks in useNotFoundRedirect hook

- Add project and org loading states

- Update URL parameter handling and comparisons


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Bug
fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>useNotFoundRedirect.ts</strong><dd><code>Enhance
useNotFoundRedirect hook for better 404 handling</code>&nbsp;
</dd></summary>
<hr>


dashboard/src/features/projects/common/hooks/useNotFoundRedirect/useNotFoundRedirect.ts

<li>Added imports for useCurrentOrg and useProject hooks<br> <li>
Updated URL parameter handling and comparisons<br> <li> Introduced
project and org loading states<br> <li> Improved conditional checks for
404 redirect


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3125/files#diff-837279cf43199053bca09913f62c4af019063a2e8dc7bfb7643ec54b7cecd29d">+31/-9</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>light-dryers-invite.md</strong><dd><code>Add changeset
for 404 page fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.changeset/light-dryers-invite.md

<li>Added changeset file for version bump<br> <li> Described fix for
invalid org slug/project subdomain 404 issue


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3125/files#diff-e0305e7650a5062b889ab6efdd71872be23e6e4c8b7792190478ef6702c1abd3">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2025-01-09 14:17:31 +01:00
Hassan Ben Jobrane
44c1e17fd5 chore: update dependencies with vulnerabilities (#3105)
### **User description**
resolves https://github.com/nhost/nhost/issues/3031


___

### **PR Type**
Enhancement


___

### **Description**
- Updated `audit-ci.jsonc` configuration:
- Removed 'micromatch' and 'path-to-regexp' from the allowlist,
potentially increasing security checks
- Modified `package.json`:
  - Added 'path-to-regexp' dependency (version ^8.2.0)
- These changes aim to address vulnerabilities and update dependencies



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Configuration
changes</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>audit-ci.jsonc</strong><dd><code>Update allowlist in
audit-ci configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

audit-ci.jsonc

- Removed 'micromatch' and 'path-to-regexp' from the allowlist


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3105/files#diff-4ede69da2a1704e53e08b8d647a315c202f037cc9277f16c94176d9622d261c6">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Dependencies</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Add path-to-regexp
dependency</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

package.json

- Added 'path-to-regexp' dependency with version '^8.2.0'


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3105/files#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519">+2/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2025-01-09 00:21:00 +01:00
David BM
5df6fa2d0b feat (dashboard): unencrypted disk warning (#3116)
### **User description**
Resolves #3050


___

### **PR Type**
Enhancement


___

### **Description**
- Add unencrypted disk warning in storage capacity settings

- Implement query for persistent volumes encryption status

- Display alert for enabling disk encryption

- Update GraphQL types and queries


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>DatabaseStorageCapacity.tsx</strong><dd><code>Add
unencrypted disk warning and encryption info</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/database/settings/components/DatabaseStorageCapacity/DatabaseStorageCapacity.tsx

<li>Import new UI components (Alert, Link, Text)<br> <li> Add query for
persistent volumes encryption status<br> <li> Implement conditional
rendering of encryption warning<br> <li> Display alert with instructions
to enable encryption


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3116/files#diff-097a59d13b44816051386182a444eadfe2dcacd69b88c121af6733d7eca3ee43">+34/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>graphql.ts</strong><dd><code>Update GraphQL types and
add encryption query</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/utils/__generated__/graphql.ts

<li>Add GetPersistentVolumesEncryptedQuery type and related
functions<br> <li> Update Organization_Member_Invites type


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3116/files#diff-fbd5db84b560b1c91675004448c6c7fa0dcbfb28b9eb05d53b03e6cb7b83ebac">+46/-1</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>

<summary><strong>getPersistentVolumesEncrypted.gql</strong><dd><code>Add
GraphQL query for persistent volumes encryption</code>&nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/database/settings/gql/getPersistentVolumesEncrypted.gql

- Add new GraphQL query for persistent volumes encryption status


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3116/files#diff-a0b0a1fd74b04a74ccd04f1f1f1a917729f603f678da3d0af9fc051ce96bb674">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>sour-bats-repair.md</strong><dd><code>Add changeset for
unencrypted disk warning feature</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/sour-bats-repair.md

<li>Add changeset for minor version bump<br> <li> Describe new feature:
unencrypted disk warning


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3116/files#diff-257a5bb608ef376c916b9c93ca8b72e260560f9bfff0c07f954862feb25d5ea7">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2025-01-08 17:35:18 +01:00
David Barroso
1fa6cc47ec chore (docs): added pg_jsonschema docs (#3123)
Fixes https://github.com/nhost/nhost/issues/3101
### **PR Type**
Enhancement, Documentation


___

### **Description**
- Added pg_jsonschema extension to available extensions list

- Included documentation for pg_jsonschema extension usage

- Updated extensions table with pg_jsonschema details

- Minor formatting changes in the document


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>loud-years-know.md</strong><dd><code>Add changeset for
pg_jsonschema documentation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/loud-years-know.md

<li>Added changeset file for @nhost/docs patch<br> <li> Described change
as adding docs for pg_jsonschema


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3123/files#diff-bc43a9bd6a512cf9ed7cfbb91eaeba7738490ea29fdc3c620f90aebfb7d3a4df">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>extensions.mdx</strong><dd><code>Add pg_jsonschema
extension documentation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

docs/guides/database/extensions.mdx

<li>Added pg_jsonschema to extensions table<br> <li> Included new
section with installation and usage instructions<br> <li> Provided
GitHub resource link for pg_jsonschema<br> <li> Minor formatting
adjustments


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3123/files#diff-7a41fa45d84db83a8c01a76ddb42ad614022ad94a4c3a6aa321f5b9a5300da8c">+25/-1</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2025-01-08 15:04:17 +01:00
github-actions[bot]
4854df4559 chore: update versions (#3099)
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/hasura-auth-js@2.9.0

### Minor Changes

- b944d05: feat: introduce `initWithSession` to initialize auth client
with an existing session

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

### Minor Changes

- 4148964: fix: stack overflow on storage client getHeaders method call

## @nhost/nextjs@2.2.0

### Minor Changes

- 46fc520: chore: add support to next.js 15, update quickstart template
commands in docs
-   29d27e1: chore: update `next` to v14.2.22 to fix vulnerabilities

### Patch Changes

-   @nhost/react@3.8.1

## @nhost/apollo@8.0.2

### Patch Changes

-   @nhost/nhost-js@3.2.2

## @nhost/react-apollo@15.0.1

### Patch Changes

-   @nhost/apollo@8.0.2
-   @nhost/react@3.8.1

## @nhost/react-urql@12.0.1

### Patch Changes

-   @nhost/react@3.8.1

## @nhost/nhost-js@3.2.2

### Patch Changes

-   Updated dependencies [b944d05]
-   Updated dependencies [4148964]
    -   @nhost/hasura-auth-js@2.9.0
    -   @nhost/hasura-storage-js@2.6.0

## @nhost/react@3.8.1

### Patch Changes

-   @nhost/nhost-js@3.2.2

## @nhost/vue@2.8.1

### Patch Changes

-   @nhost/nhost-js@3.2.2

## @nhost/dashboard@2.13.0

### Minor Changes

- 21e90da: chore: remove restrictions on SMTP sender so My Name
[name@acme.com](mailto:name@acme.com) can be added
- 865dd93: fix: duplicate Run placeholders when there is an error in the
backend
- 6902a36: fix: can remove resources if postgres capacity is higher than
10
-   a535aa3: fix: fetch user roles locally in auth section
-   0c50816: fix: allow decimal numbers in database row insert
- aea6d18: chore: add warning when pausing a project about losing Run
services persistent volume data
- d3b4fc3: feat: allow to change postgres settings if project is paused
-   29d27e1: chore: update `next` to v14.2.22 to fix vulnerabilities
-   c9dca09: feat: add reset password form
-   b3bcacb: fix: paused project banner cannot read null project name

### Patch Changes

-   Updated dependencies [46fc520]
-   Updated dependencies [29d27e1]
    -   @nhost/nextjs@2.2.0
    -   @nhost/react-apollo@15.0.1

## @nhost/docs@2.25.0

### Minor Changes

- 46fc520: chore: add support to next.js 15, update quickstart template
commands in docs
-   cdf6776: fix: update links to create new project in dashboard

## @nhost-examples/nextjs@0.4.0

### Minor Changes

-   29d27e1: chore: update `next` to v14.2.22 to fix vulnerabilities

### Patch Changes

-   Updated dependencies [46fc520]
-   Updated dependencies [29d27e1]
    -   @nhost/nextjs@2.2.0
    -   @nhost/react@3.8.1
    -   @nhost/react-apollo@15.0.1

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

### Minor Changes

- b944d05: chore: simplify Nhost client initialization with session and
remove xstate dependency
-   29d27e1: chore: update `next` to v14.2.22 to fix vulnerabilities

### Patch Changes

-   @nhost/nhost-js@3.2.2

## @nhost-examples/cli@0.3.15

### Patch Changes

-   @nhost/nhost-js@3.2.2

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

### Patch Changes

-   @nhost/react@3.8.1
-   @nhost/react-apollo@15.0.1

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

### Patch Changes

-   @nhost/react@3.8.1

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

### Patch Changes

-   @nhost/react@3.8.1
-   @nhost/react-urql@12.0.1

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

### Patch Changes

-   @nhost/nhost-js@3.2.2

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

### Patch Changes

-   @nhost/nhost-js@3.2.2

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

### Patch Changes

-   @nhost/react@3.8.1
-   @nhost/react-apollo@15.0.1

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

### Patch Changes

-   @nhost/react@3.8.1

## @nhost-examples/react-native@0.1.1

### Patch Changes

-   @nhost/react@3.8.1
-   @nhost/react-apollo@15.0.1

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

### Patch Changes

-   @nhost/nhost-js@3.2.2
-   @nhost/apollo@8.0.2
-   @nhost/vue@2.8.1

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

### Patch Changes

-   @nhost/apollo@8.0.2
-   @nhost/vue@2.8.1

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-08 13:47:10 +01:00
David BM
865dd93fbe fix (dashboard): duplicate Run placeholders when there is a backend error (#3114)
### **User description**
Resolves #2842


___

### **PR Type**
Bug fix, Enhancement


___

### **Description**
- Refactor Run service creation process

- Use single mutation for service config insertion

- Remove duplicate placeholder creation

- Update GraphQL schema and related types


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>ServiceForm.tsx</strong><dd><code>Refactor service
creation process in ServiceForm component</code></dd></summary>
<hr>


dashboard/src/features/orgs/projects/services/components/ServiceForm/ServiceForm.tsx

<li>Replace separate insertRunService and insertRunServiceConfig
mutations <br>with single insertRunServiceConfig<br> <li> Remove UUID
generation for new services<br> <li> Update error handling and form
submission logic<br> <li> Adjust image handling for private registries


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3114/files#diff-a02746694d45a84390d09b49a1b3eec85c25a8bd9a70b4834ee5af1ba82cb88e">+14/-34</a>&nbsp;
</td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>ServiceFormTypes.ts</strong><dd><code>Enhance image
field validation in ServiceFormTypes</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/services/components/ServiceForm/ServiceFormTypes.ts

<li>Add trim() to image validation<br> <li> Enforce minimum length of 1
character for image field


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3114/files#diff-e9e0545b8c213ce04a08f9b04aedc81f96a031429e2ac9ac9e19d47982c112dc">+5/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>graphql.ts</strong><dd><code>Update GraphQL schema
types for Run service changes</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

dashboard/src/utils/__generated__/graphql.ts

<li>Add InsertRunServiceConfigResponse type<br> <li> Update
Mutation_Root and related types<br> <li> Remove creatorUserId and
creator fields from Run_Service type<br> <li> Update Users type to
remove runServices field


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3114/files#diff-fbd5db84b560b1c91675004448c6c7fa0dcbfb28b9eb05d53b03e6cb7b83ebac">+16/-57</a>&nbsp;
</td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>insertRunService.graphql</strong><dd><code>Rename
insertRunService GraphQL mutation</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/gql/services/insertRunService.graphql

- Rename mutation from insertRunService to InsertRunService


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3114/files#diff-a74b9bbdda4bce6f90fe8e8438397c61c2f044b5956e3da856f934b084ec3dc6">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>

<summary><strong>insertRunServiceConfig.graphql</strong><dd><code>Refactor
insertRunServiceConfig GraphQL mutation</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/gql/services/insertRunServiceConfig.graphql

<li>Rename mutation to InsertRunServiceConfig<br> <li> Remove serviceID
parameter<br> <li> Update return type to include serviceID and config


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3114/files#diff-9f98ee0dd349db6404e3de3a3e487a4df6a4737acfd80ae8e5c0a8c6043ecb05">+6/-8</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Formatting</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>ReplicasFormSection.tsx</strong><dd><code>Minor styling
updates in ReplicasFormSection component</code>&nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>


dashboard/src/features/orgs/projects/services/components/ServiceForm/components/ReplicasFormSection/ReplicasFormSection.tsx

- Minor CSS class order adjustments
- Update InfoOutlinedIcon styling


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3114/files#diff-026fa3492e982d5c25430ea27282a81bbb372dcb7061274006d123d6f91a36f2">+3/-3</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>curly-hotels-hang.md</strong><dd><code>Add changeset
for Run placeholders fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/curly-hotels-hang.md

- Add changeset for fixing duplicate Run placeholders issue


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3114/files#diff-f2d1135a0f9ad0a75d2b50a42fb2c0ce1f17749af238f4acd06e5699f303d668">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2025-01-08 13:31:56 +01:00
David BM
0c50816717 fix (dashboard): allow decimal row insert (#3110)
### **User description**
Fixes #2923


___

### **PR Type**
Bug fix, Enhancement


___

### **Description**
- Allow decimal numbers in database row insert

- Separate integer and decimal cell components

- Update PostgreSQL type constants

- Refactor DataBrowserGrid component imports


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>7
files</summary><table>
<tr>
<td><strong>DataBrowserGrid.tsx</strong><dd><code>Refactor imports and
add decimal cell support</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3110/files#diff-5910fd8730fbe65c60aa5f54031989a7868e944d5958f69535e5684b72ca1396">+22/-11</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>postgresqlConstants.ts</strong><dd><code>Separate integer
and decimal PostgreSQL types</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3110/files#diff-b497da90feca5bff94b0d38b69e519d171d43acc292098054d672a73a89b4717">+8/-5</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>DataGridDecimalCell.tsx</strong><dd><code>Add new
DataGridDecimalCell component</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3110/files#diff-9ad38d4c8a67f8daf6020b9782cb1d7a4933e2901b4937a597a2c19c2367d7d0">+108/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>index.ts</strong><dd><code>Add index file for
DataGridDecimalCell</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3110/files#diff-796c86f4c7526c140e70830072876324b6809204eb0e59da9931f048bb00c3ed">+2/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>DataGridIntegerCell.tsx</strong><dd><code>Rename
DataGridNumericCell to DataGridIntegerCell</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3110/files#diff-9db68b16a44a34c57b847023c1dd2f74e486b0a028f84fcc0cc1f29e0ff38f0d">+3/-3</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>index.ts</strong><dd><code>Add index file for
DataGridIntegerCell</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3110/files#diff-2354d98927d0c0bf7165211cbe9f478727bb889793716cfe39083c200d625c40">+2/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>index.ts</strong><dd><code>Remove DataGridNumericCell index
file</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3110/files#diff-0b88c218b31ef402892e055abae0b5a05b96ec1550881d69f0fd73bad93e159e">+0/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Bug
fix</strong></td><td><details><summary>1 files</summary><table>
<tr>
<td><strong>DatabaseRecordInputGroup.tsx</strong><dd><code>Remove step
property from input</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3110/files#diff-52b5499e9afc3c5e4929046b487de649d421dda3250a4131462ec710575abc12">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

</table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>1
files</summary><table>
<tr>
<td><strong>nice-mangos-act.md</strong><dd><code>Add changeset for
decimal number fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3110/files#diff-7207f060172dcdd7fd5c3c4078140fb57564714e1f95c96d428a22d9e7a3e670">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></details></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2025-01-07 22:18:04 +01:00
David BM
d3b4fc358e feat (dashboard): allow to change postgres settings if project is paused (#3106) 2025-01-07 18:09:12 +01:00
David BM
b3bcacb300 fix (dashboard): paused application cannot read null project name (#3117)
### **User description**
Fixes the occasional error `Cannot read properties of null (reading
'name') when you open a page inside a paused project

![image](https://github.com/user-attachments/assets/bfd57bf6-5679-48d3-ac8f-b69f6d72ee3d)


___

### **PR Type**
Bug fix


___

### **Description**
- Fix error when reading project name in paused application

- Update ApplicationPaused component to handle null project

- Add optional chaining to prevent null reference errors

- Improve error handling for paused projects in dashboard


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Bug
fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>ApplicationPaused.tsx</strong><dd><code>Add null checks
for project name in ApplicationPaused component</code></dd></summary>
<hr>


dashboard/src/features/orgs/projects/common/components/ApplicationPaused/ApplicationPaused.tsx

<li>Added optional chaining (<code>?.</code>) to
<code>project.name</code> references<br> <li> Updated modal title and
description to handle potential null project


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3117/files#diff-14afdf5ac20f058c26563a6992a3751f11cf173eec27206001262b5d1b3b979f">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>wise-chefs-drum.md</strong><dd><code>Add changeset for
paused project banner fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.changeset/wise-chefs-drum.md

<li>Added changeset file for version bump and change description<br>
<li> Described fix for paused project banner issue


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3117/files#diff-dddc3d3dd31a1cb69106c6acad798c2201016cffb11cb8447e0696563838300c">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2025-01-07 15:51:47 +01:00
David Barroso
aa7ecdb38f chore: update pr-agent (#3121)
### **PR Type**
Enhancement


___

### **Description**
- Update PR Agent action to version 0.26


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>gen_ai_review.yaml</strong><dd><code>Upgrade PR Agent
action version</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.github/workflows/gen_ai_review.yaml

- Updated PR Agent action from version 0.24 to 0.26


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3121/files#diff-d1e4c772e0acb5ce4891df2dd94ba58ffaf6393e8f75493ec7e10cbce1c4992c">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2025-01-07 15:00:13 +01:00
David Barroso
20672c7a9b chore: update actions/cache to v4 (#3120)
### **PR Type**
Enhancement


___

### **Description**
- Update actions/cache from v3 to v4


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Dependencies</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>action.yaml</strong><dd><code>Update actions/cache
version</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

.github/actions/install-dependencies/action.yaml

- Upgraded actions/cache from v3 to v4


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3120/files#diff-342d59190b4737ee45e2062eb625ada477bcea5b4a843b25900ad55d7982f200">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2025-01-07 14:59:00 +01:00
David BM
29d27e19b4 chore: update dependencies with vulnerabilities, fix ci (#3118)
### **PR Type**
Enhancement, Bug fix


___

### **Description**
- Update Next.js to v14.2.22 for vulnerability fixes

- Upgrade @nhost/react and @nhost/react-apollo packages

- Update dependencies across multiple projects

- Add changeset for version bumps


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>smart-penguins-love.md</strong><dd><code>Add changeset
for Next.js and Nhost package updates</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

.changeset/smart-penguins-love.md

<li>Add new changeset file for version bumps<br> <li> Specify minor
version updates for multiple packages<br> <li> Note Next.js update to
v14.2.22 for vulnerability fixes


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3118/files#diff-e7c2756594d0b5180066d581205200665f8f3f6104259550e080c3916dca065a">+8/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Dependencies</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Upgrade Next.js to
v14.2.22 in dashboard</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

dashboard/package.json

- Update `next` dependency from ^14.2.10 to ^14.2.22


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3118/files#diff-2d8d55c799cd71f1b35e831f075f8178ed1734c4820a2ad548b4dd24d6938d7c">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Upgrade Next.js to
v14.2.22 in NextJS example</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/nextjs/package.json

- Update `next` dependency from ^14.2.10 to ^14.2.22


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3118/files#diff-23044c563f1173db6464d127497c342c8f7f90722764a37749681bf455a515e0">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Upgrade Next.js to
v14.2.22 in server components example</code>&nbsp; </dd></summary>
<hr>

examples/quickstarts/nextjs-server-components/package.json

- Update `next` dependency from ^14.2.10 to ^14.2.22


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3118/files#diff-04889f3402d5191034459febd340282af1c718175c3b0b14ff03fb2ab46cf9b3">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Upgrade Next.js dev
dependency in NextJS package</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

packages/nextjs/package.json

- Update `next` devDependency from ^14.2.10 to ^14.2.22


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3118/files#diff-e5237f683dda3354b835c7c7c94b9759db2c743d4ba94d47d7f8b8e0b2bfb442">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Upgrade Nhost React and
Apollo packages in CRA template</code>&nbsp; &nbsp; </dd></summary>
<hr>

templates/cra-template-nhost-react-apollo-template/template/package.json

<li>Update @nhost/react from ^3.5.4 to ^3.8.0<br> <li> Update
@nhost/react-apollo from ^12.0.4 to ^12.0.5


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3118/files#diff-27f71682a447c654ff4a94d33944ebb70e10d07a4279107c230bd8ec7dce7391">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2025-01-07 14:17:32 +01:00
David BM
46fc520707 chore (nextjs): fix nextjs tutorial quickstart commands in docs, add support to next.js 15 (#3109)
### **User description**
Resolves #3103, resolves #3102


___

### **PR Type**
Enhancement, Bug fix


___

### **Description**
- Update Next.js quickstart and tutorial for version 14

- Add support for Next.js 15 and React 19

- Fix typos in documentation

- Update Nhost package versions in template


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>nextjs.mdx</strong><dd><code>Update Next.js quickstart
guide for version 14</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/quickstarts/nextjs.mdx

<li>Updated command to create Next.js 14 app instead of latest<br> <li>
Fixed typo: 'Navidate' to 'Navigate'


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3109/files#diff-d39f09ffc6c1ab86d648e6bf0e612463f51f5588a9f674e43454b86ce86bc174">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>vue.mdx</strong><dd><code>Fix typo in Vue quickstart
guide</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/quickstarts/vue.mdx

- Fixed typo: 'Navidate' to 'Navigate'


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3109/files#diff-1038d117c49e6af817cc5cb861d8e0af9df274612c9acbf1dc24bb7ee76aa6f4">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>nextjs.mdx</strong><dd><code>Update Next.js tutorial
for version 14</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/tutorials/nextjs.mdx

- Updated command to create Next.js 14 app instead of latest


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3109/files#diff-f6388bd90e4e34be1cdfd7c81a5ce7bc21a51949553a29a16bdc32e6875aba6f">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Update Next.js and React
version support</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

packages/nextjs/package.json

<li>Added support for Next.js 15 and React 19 in peerDependencies<br>
<li> Updated devDependencies to Next.js 15 and React 19


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3109/files#diff-e5237f683dda3354b835c7c7c94b9759db2c743d4ba94d47d7f8b8e0b2bfb442">+7/-7</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Dependencies</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Update Nhost package
versions in template</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

templates/cra-template-nhost-react-apollo-template/template/package.json

- Updated @nhost/react and @nhost/react-apollo versions


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3109/files#diff-27f71682a447c654ff4a94d33944ebb70e10d07a4279107c230bd8ec7dce7391">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2025-01-02 17:24:01 +01:00
David BM
21e90da476 chore (dashboard): remove restrictions on SMTP sender so My Name <name@acme.com> can be added (#3111) 2025-01-02 11:38:48 +01:00
Hassan Ben Jobrane
b944d053d0 feat(hasura-auth-js): feat: addinitWithSession to initialize auth client with existing session (#3108)
### **User description**
resolves https://github.com/nhost/nhost/issues/2319


___

### **PR Type**
Enhancement


___

### **Description**
- Added new `initWithSession()` method to `@nhost/hasura-auth-js`
package, allowing initialization of auth client with an existing session
- Simplified Nhost client initialization in Next.js server components
example by using the new `initWithSession()` method
- Removed xstate dependency from Next.js server components example
- Updated changesets to document the new feature and changes in the
Next.js example
- Improved code organization and reduced complexity in the Next.js
example



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>nhost.ts</strong><dd><code>Simplify Nhost client
initialization in Next.js</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/quickstarts/nextjs-server-components/src/utils/nhost.ts

<li>Removed xstate dependency imports<br> <li> Replaced
<code>nhost.auth.client.start()</code> and <code>waitFor()</code> with
new <br><code>nhost.auth.initWithSession()</code> method<br> <li>
Simplified initialization of Nhost client with existing session


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3108/files#diff-e13ecdf248c9041902e5e8a79555ccefc225eb7df3d717cc1b61ce0d5da092db">+1/-7</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>hasura-auth-client.ts</strong><dd><code>Add
initWithSession method to HasuraAuthClient</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

packages/hasura-auth-js/src/hasura-auth-client.ts

<li>Added <code>NhostSession</code> import<br> <li> Implemented new
<code>initWithSession()</code> method in <code>HasuraAuthClient</code>
class<br> <li> Added JSDoc comments for the new method


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3108/files#diff-0dbc30932ed723b7fd458066893f29f2f77658436c84adf42613813ea042c992">+18/-0</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>flat-apes-shake.md</strong><dd><code>Add changeset for
Next.js example updates</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.changeset/flat-apes-shake.md

<li>Added changeset for Next.js server components example<br> <li>
Describes simplification of Nhost client initialization and removal of
<br>xstate dependency


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3108/files#diff-ee9361d5d0dad7378328ba9e63937d85fbe91156790a50764aaa285fab6b4db1">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>long-guests-sparkle.md</strong><dd><code>Add changeset
for new initWithSession feature</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/long-guests-sparkle.md

<li>Added changeset for @nhost/hasura-auth-js package<br> <li> Describes
the addition of <code>initWithSession</code> method


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3108/files#diff-e7c748ca311b610d130750f4d24256383ef064c1ee2f1a9981060fd274335a1c">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Dependencies</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Remove xstate dependency
from Next.js example</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/quickstarts/nextjs-server-components/package.json

- Removed "xstate" dependency from the project


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3108/files#diff-04889f3402d5191034459febd340282af1c718175c3b0b14ff03fb2ab46cf9b3">+1/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-31 12:58:10 +01:00
David BM
6902a36512 fix (dashboard): remove compute resources erases postgres capacity (#3107)
### **User description**
Fixes #3075


___

### **PR Type**
Bug fix, Enhancement


___

### **Description**
- Fixed a bug that prevented removing compute resources when PostgreSQL
capacity was higher than 10
- Refactored resource configuration handling to preserve non-compute
settings when disabling resources
- Updated GraphQL types and queries to include additional fields for
networking and storage configurations
- Implemented UI tweaks for better alignment and dark theme
compatibility in the resource settings form
- Added a changeset file to document the bug fix and minor version bump



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>ResourcesForm.tsx</strong><dd><code>Refactor resource
configuration handling</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/resources/settings/components/ResourcesForm/ResourcesForm.tsx

<li>Added <code>getFormattedConfig</code> function to handle resource
configuration<br> <li> Modified <code>handleSubmit</code> to use
<code>getFormattedConfig</code><br> <li> Updated initial resource
retrieval to include <code>rest</code> properties<br> <li> Implemented
logic to preserve non-compute resource settings when <br>disabling
resources


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3107/files#diff-0a7e99e6ee09c17eec103656a9aa088b379c7927a182098538b793488a1f9337">+118/-59</a></td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>graphql.ts</strong><dd><code>Update GraphQL types for
extended resource configurations</code></dd></summary>
<hr>

dashboard/src/utils/__generated__/graphql.ts

<li>Updated GraphQL types to include additional fields for resources<br>
<li> Added networking and storage-related fields to various resource
<br>configurations


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3107/files#diff-fbd5db84b560b1c91675004448c6c7fa0dcbfb28b9eb05d53b03e6cb7b83ebac">+16/-2</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>getResources.gql</strong><dd><code>Extend GraphQL query
for resource configurations</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/resources/settings/gql/getResources.gql

<li>Updated GraphQL query to include additional fields for resources<br>
<li> Added networking and storage-related fields to the query


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3107/files#diff-45c2f030236a2836bd4ba61e46a20bc0b40f2ab08874c056c49b285a9c2c80eb">+14/-0</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Formatting</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>ServiceResourcesFormFragment.tsx</strong><dd><code>UI
tweaks for resource settings form</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/resources/settings/components/ServiceResourcesFormFragment/ServiceResourcesFormFragment.tsx

<li>Minor CSS class adjustments for better alignment<br> <li> Updated
icon color handling for dark theme compatibility


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3107/files#diff-9b9bf7e4f4e4dd34502e1b636c9f9aabbb20defe43595a79aa7e3f7d89750029">+6/-6</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>eighty-candles-tell.md</strong><dd><code>Add changeset
for bug fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/eighty-candles-tell.md

- Added a changeset file to document the bug fix


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3107/files#diff-872c88455eb33ca58fddfd06a1cb56559dddfbdc4fd4aea7b44c2984ef9785a1">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-31 12:21:20 +01:00
David BM
aea6d186c2 chore (dashboard): warn run volumes losing data on project pause (#3104)
### **User description**
Resolves #3048


___

### **PR Type**
Enhancement


___

### **Description**
- Added a warning message when pausing a project with Run services that
have persistent volume data.
- The warning is displayed in an Alert component within the pause
confirmation dialog.
- Implemented logic to determine when to show the warning based on the
project's plan and Run services configuration.
- Updated the pause confirmation dialog to include more detailed
information and styling.
- Added a changeset file to document the minor version bump for this
feature.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>index.tsx</strong><dd><code>Add warning for Run service
data loss on project pause</code>&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/pages/orgs/[orgSlug]/projects/[appSubdomain]/settings/index.tsx

<li>Added imports for new UI components (Alert, Link, Text)<br> <li>
Implemented <code>useRunServices</code> hook and
<code>showWarning</code> logic<br> <li> Enhanced the pause project
dialog with a warning about data loss for <br>Run services<br> <li>
Added conditional rendering of the warning alert


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3104/files#diff-b4185be97a505e25badcdefe31ea86fa9d69f72264c4bb35eae17fba936a3d47">+61/-3</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>purple-trains-itch.md</strong><dd><code>Add changeset
for Run services warning feature</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/purple-trains-itch.md

<li>Added a changeset file to document the minor version bump<br> <li>
Described the new feature of warning about data loss when pausing a
<br>project


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3104/files#diff-e810895b65f5a519690acf26c92d7f534ddab07a3baf619556674561d965b026">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-30 16:19:41 +01:00
David BM
a535aa3834 fix (dashboard): fetch user roles locally in auth section (#3096)
### **User description**
Fixes #2472


___

### **PR Type**
Bug fix


___

### **Description**
- Fixed issue with user roles not appearing locally in the Nhost
dashboard
- Implemented support for fetching user roles and locales using a local
Mimir client when not running on the Nhost platform
- Modified queries in EditUserForm and UsersBody components to use the
local client when appropriate
- Corrected image source paths for provider logos in both components
- Added a changeset file to document the minor version bump and fix
description
- Improved code formatting and consistency



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>EditUserForm.tsx</strong><dd><code>Add local client
support for user roles and locales</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/authentication/users/components/EditUserForm/EditUserForm.tsx

<li>Added <code>useIsPlatform</code> and
<code>useLocalMimirClient</code> hooks<br> <li> Modified
<code>useGetRolesPermissionsQuery</code> and
<code>useGetProjectLocalesQuery</code> to <br>use local Mimir client
when not on platform<br> <li> Fixed image source paths for provider
logos<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3096/files#diff-6867937d55b269352d4e146ff21b36ca939f6a838ee70b1b29efa9eabad88c2e">+10/-4</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>UsersBody.tsx</strong><dd><code>Implement local client
for user roles in UsersBody</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/authentication/users/components/UsersBody/UsersBody.tsx

<li>Added <code>useIsPlatform</code> and
<code>useLocalMimirClient</code> hooks<br> <li> Modified
<code>useGetRolesPermissionsQuery</code> to use local Mimir client when
<br>not on platform<br> <li> Fixed formatting for date display and image
source paths<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3096/files#diff-33b33017f46d5cb8e4652c183619f3dc86c5377125ed3a612888739e0da22484">+12/-7</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>forty-knives-check.md</strong><dd><code>Add changeset
for user roles fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.changeset/forty-knives-check.md

<li>Added a changeset file to document the minor version bump and fix
<br>description<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3096/files#diff-1eb27af3bf6c2c3562da23f6e52944ae10861d75351b5df022fdf0c50b1ca245">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-30 14:31:23 +01:00
Hassan Ben Jobrane
c9dca09478 feat(dashboard): add change password form (#3089)
### **User description**
resolves https://github.com/nhost/nhost/issues/3058


___

### **PR Type**
Enhancement, Bug fix


___

### **Description**
- Renamed the existing reset password page to `NewPasswordPage` and
updated its form handling.
- Added a new `ResetPasswordPage` with a form to change the password,
including validation and success/error handling.
- Updated the "Forgot password?" link in the sign-in page to direct
users to the new password page.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>new.tsx</strong><dd><code>Rename and update reset
password page</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/pages/password/new.tsx

<li>Renamed <code>ResetPasswordPage</code> to
<code>NewPasswordPage</code>.<br> <li> Updated form type from
<code>ResetPasswordFormValues</code> to
<br><code>NewPasswordFormValues</code>.<br> <li> Changed layout title to
"Request Password Reset".<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3089/files#diff-153045bbcb44ce952fbd9ee585c63109891973ab4d1ecc1e1b5edf8f981b1259">+8/-6</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>reset.tsx</strong><dd><code>Add new reset password page
with form</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/pages/password/reset.tsx

<li>Introduced a new <code>ResetPasswordPage</code> component.<br> <li>
Implemented form for changing password with validation.<br> <li> Added
navigation to sign-in page upon successful password change.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3089/files#diff-0b60d94b63e36ce54a4dafb098322e11b9a130defb0f48984f8b3e71461e8011">+144/-0</a>&nbsp;
</td>

</tr>
</table></td></tr><tr><td><strong>Bug fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>email.tsx</strong><dd><code>Update forgot password link
and formatting</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/pages/signin/email.tsx

<li>Updated "Forgot password?" link to point to the new password
page.<br> <li> Minor formatting adjustments in the component.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3089/files#diff-b5d7db4460066bc114cb766771612d6f908bd6e440f40de98e4ac311a26b50cd">+4/-4</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-30 12:51:38 +01:00
David BM
414896491f fix (hasura-storage-js): stack overflow on storage client getHeaders (#3100)
### **User description**
Fixes #2964


___

### **PR Type**
Bug fix


___

### **Description**
- Fixed a critical bug in the `HasuraStorageClient` class where the
`getHeaders` method was causing a stack overflow due to recursive
self-calling.
- Updated the `getHeaders` method to correctly call
`this.api.getHeaders()` instead of `this.getHeaders()`.
- Added a changeset file to document the bug fix and specify a minor
version bump for the '@nhost/hasura-storage-js' package.
- This fix resolves the issue reported in ticket #2964, where calling
`nhostClient.storage.getHeaders()` was leading to a stack overflow
error.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Bug
fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>hasura-storage-client.ts</strong><dd><code>Fix stack
overflow in storage client getHeaders method</code>&nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

packages/hasura-storage-js/src/hasura-storage-client.ts

<li>Fixed a recursive call in the <code>getHeaders</code> method that
was causing a <br>stack overflow<br> <li> Changed
<code>this.getHeaders()</code> to <code>this.api.getHeaders()</code><br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3100/files#diff-f994829d5b30e7a7d47629651e1a013110a71ed2c8cddced340fb3ac05603956">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>smooth-wombats-begin.md</strong><dd><code>Add changeset
for storage client bug fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

.changeset/smooth-wombats-begin.md

<li>Added a changeset file to document the bug fix<br> <li> Specified a
minor version bump for '@nhost/hasura-storage-js' package<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3100/files#diff-9174b11cea1aad4b7308eac7c7fc2d78a44e81b7040b38e542974549ef15b047">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-27 16:46:00 +01:00
David BM
cdf6776523 fix (docs): broken links to create new project in dashboard (#3098)
### **PR Type**
Bug fix, Documentation


___

### **Description**
- Fixed broken links to create a new project in the Nhost Dashboard
across multiple documentation files
- Updated the URL from `https://app.nhost.io/new` to
`https://app.nhost.io` in all affected files
- Modified quickstart guides for Next.js, React Native, React, and Vue
- Updated tutorial guides for Next.js, React, and Vue
- Added a changeset file to document the fix and specify the version
change for `@nhost/docs`



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>wicked-colts-fetch.md</strong><dd><code>Add changeset
for documentation update</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/wicked-colts-fetch.md

<li>Added a new changeset file for documenting the fix<br> <li>
Specified the package and version change<br> <li> Brief description of
the fix<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3098/files#diff-fd20b463cfa62ac545411e62d62a7443334fbc24050651eec02a9a0fb21cc02c">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Bug fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>nextjs.mdx</strong><dd><code>Update Nhost Dashboard
link in Next.js quickstart</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/quickstarts/nextjs.mdx

- Updated the link to create a new project in the Nhost Dashboard



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3098/files#diff-d39f09ffc6c1ab86d648e6bf0e612463f51f5588a9f674e43454b86ce86bc174">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>react-native.mdx</strong><dd><code>Update Nhost
Dashboard link in React Native quickstart</code>&nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

docs/guides/quickstarts/react-native.mdx

- Updated the link to create a new project in the Nhost Dashboard



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3098/files#diff-7f0904dabc13ea9bd8e8b39e5b870ed6926791172f22a1df922302dfc43dac9e">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>react.mdx</strong><dd><code>Update Nhost Dashboard link
in React quickstart</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/quickstarts/react.mdx

- Updated the link to create a new project in the Nhost Dashboard



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3098/files#diff-b954a7a56bea978a7b33eedf5a250f4599f285bc0a8556a3c85e1a416bc89e61">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>vue.mdx</strong><dd><code>Update Nhost Dashboard link
in Vue quickstart</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/quickstarts/vue.mdx

- Updated the link to create a new project in the Nhost Dashboard



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3098/files#diff-1038d117c49e6af817cc5cb861d8e0af9df274612c9acbf1dc24bb7ee76aa6f4">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>nextjs.mdx</strong><dd><code>Update Nhost Dashboard
link in Next.js tutorial</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/tutorials/nextjs.mdx

- Updated the link to create a new project in the Nhost Dashboard



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3098/files#diff-f6388bd90e4e34be1cdfd7c81a5ce7bc21a51949553a29a16bdc32e6875aba6f">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>react.mdx</strong><dd><code>Update Nhost Dashboard link
in React tutorial</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/tutorials/react.mdx

- Updated the link to create a new project in the Nhost Dashboard



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3098/files#diff-22eb5f72de09dd8d979ef3fb5ae8a321000db2b29defac5dd6459703972b7e9a">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>vue.mdx</strong><dd><code>Update Nhost Dashboard link
in Vue tutorial</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/tutorials/vue.mdx

- Updated the link to create a new project in the Nhost Dashboard



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3098/files#diff-861872b2c9bf786c73e9ded726a1d4b27f57918cc351dc06ba2bfaf51fda6d09">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-27 13:55:53 +01:00
github-actions[bot]
1b40e99530 chore: update versions (#3091)
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@2.12.0

### Minor Changes

- eb95562: fix: show all available permission variables in permission
dropdown select

### Patch Changes

- 8b5c4a0: chore: cleanup layout and add disable duplicate atom key
checking in development mode

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-25 14:41:34 +01:00
Hassan Ben Jobrane
8b5c4a0951 chore(dashboard): cleanup layout and add disable duplicate atom key checking in development mode (#3093)
### **PR Type**
enhancement, bug fix


___

### **Description**
- Removed unused `contentContainerProps` from various layout components
to simplify the code.
- Refactored class names across multiple files for better readability
and consistency.
- Disabled duplicate atom key checking in development mode to improve
performance.
- Added a changeset for the layout cleanup and configuration changes.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>AuthenticatedLayout.tsx</strong><dd><code>Remove unused
props and clean imports in AuthenticatedLayout</code></dd></summary>
<hr>


dashboard/src/components/layout/AuthenticatedLayout/AuthenticatedLayout.tsx

<li>Removed unused <code>contentContainerProps</code> from
<code>AuthenticatedLayoutProps</code>.<br> <li> Cleaned up imports by
removing unnecessary type imports.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3093/files#diff-2d69ccffd267658f76d77a864cdece93fc222e08f6025955795fc6f4697f60e7">+3/-16</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>index.tsx</strong><dd><code>Simplify ProjectLayout
usage in UsersPage</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

dashboard/src/pages/[workspaceSlug]/[appSlug]/users/index.tsx

<li>Removed <code>contentContainerProps</code> from
<code>ProjectLayout</code> in <code>UsersPage</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3093/files#diff-65a8a029ab52eaeffa315d4ec53a0733fb07b5ee605c1305d4805d3baa7723a8">+1/-5</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>index.tsx</strong><dd><code>Simplify
AuthenticatedLayout usage in IndexPage</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/pages/index.tsx

<li>Removed <code>contentContainerProps</code> from
<code>AuthenticatedLayout</code> in <code>IndexPage</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3093/files#diff-4eefa54204aa396da4d4d2f1d633d42d1b8ef86987f6e8c9b63d81df1ea6a273">+1/-4</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>users.tsx</strong><dd><code>Refactor class names for
consistency in UsersPage</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/pages/orgs/[orgSlug]/projects/[appSubdomain]/users.tsx

- Adjusted class names for better readability and consistency.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3093/files#diff-bac9a82a4d6cfabd076edfa73a9e6dbd637c58c003f4c90eca28995ae0426690">+20/-24</a>&nbsp;
</td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>ticket.tsx</strong><dd><code>Refactor class names and
simplify layout in TicketPage</code>&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/pages/support/ticket.tsx

<li>Adjusted class names for better readability and consistency.<br>
<li> Removed <code>contentContainerProps</code> from
<code>AuthenticatedLayout</code> in <code>TicketPage</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3093/files#diff-a66cba186d2014b03f1a0e005147ae7b48e88933700fe065d235cd819a949a97">+11/-16</a>&nbsp;
</td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>silver-goats-stare.md</strong><dd><code>Add changeset
for layout cleanup and atom key check</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

.changeset/silver-goats-stare.md

<li>Added a changeset for layout cleanup and disabling duplicate atom
key <br>checking.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3093/files#diff-13329d1e408c653a824517c11f5da1d215f05541a1e56fed13a67d9cfe8459ba">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Bug fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Disable duplicate atom
key checking in dev mode</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/package.json

- Disabled duplicate atom key checking in development mode.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3093/files#diff-2d8d55c799cd71f1b35e831f075f8178ed1734c4820a2ad548b4dd24d6938d7c">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-25 14:18:53 +01:00
Hassan Ben Jobrane
f5594ef991 fix(ci): use node20 in Dockerfile and upgrade turbo from 1.11.3 to 2.3.3 (#3092)
### **PR Type**
enhancement, configuration changes


___

### **Description**
- Upgraded Node.js version from 18 to 20 in the Dockerfile to ensure
compatibility with newer features and improvements.
- Updated Turbo version from 1.11.3 to 2.3.3 in both Dockerfile and
package.json to leverage new features and optimizations.
- Adjusted environment variable syntax in Dockerfile for consistency.
- Modified build and test scripts in package.json to remove
`--include-dependencies` for streamlined operations.
- Changed `pipeline` key to `tasks` in turbo.json to align with updated
configuration standards.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Configuration
changes</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>Dockerfile</strong><dd><code>Upgrade Node.js and Turbo
versions in Dockerfile</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/Dockerfile

<li>Upgraded Node.js version from 18 to 20.<br> <li> Updated Turbo
version from 1.11.3 to 2.2.3.<br> <li> Adjusted environment variable
syntax for consistency.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3092/files#diff-e4409471758b4d6438b1bf954190cf0659eb6c4b30efafe877d20e4e485c383f">+17/-17</a>&nbsp;
</td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>turbo.json</strong><dd><code>Modify Turbo configuration
structure</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

turbo.json

- Changed `pipeline` key to `tasks`.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3092/files#diff-f8de965273949793edc0fbfe249bb458c0becde39b2e141db087bcbf5d4ad5e3">+1/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Update Turbo version and
modify scripts in package.json</code>&nbsp; &nbsp; </dd></summary>
<hr>

package.json

<li>Updated Turbo version from 1.11.3 to 2.3.3.<br> <li> Modified build
and test scripts to remove <code>--include-dependencies</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3092/files#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519">+4/-4</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-25 13:57:37 +01:00
David BM
eb9556280c fix (dashboard): retrieve all permission variables in permission dropdown select (#3012)
### **User description**
Fixes #2387


___

### **PR Type**
Bug fix, Enhancement


___

### **Description**
- Replaced `useCurrentWorkspaceAndProject` with `useProject` in
`RuleValueInput` component to streamline project data retrieval.
- Updated GraphQL query variables in `RuleValueInput` to use
`project?.id` for better consistency.
- Added a `convertOperator` function to handle `_in_hasura` and
`_nin_hasura` operators, ensuring they are converted to valid Hasura
operators.
- Modified `createNestedObjectFromRule` to utilize the new
`convertOperator` function for accurate rule conversion.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>RuleValueInput.tsx</strong><dd><code>Update project
hook and GraphQL query variables</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/database/dataGrid/components/RuleGroupEditor/RuleValueInput.tsx

<li>Replaced <code>useCurrentWorkspaceAndProject</code> with
<code>useProject</code>.<br> <li> Updated GraphQL query variables to use
<code>project?.id</code> instead of
<br><code>currentProject?.id</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3012/files#diff-e3198b245b5963e81e4566758b7d60c8d2784a7ca0ad0b17b354b33074ef1bb0">+4/-4</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Bug fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>convertToHasuraPermissions.ts</strong><dd><code>Add
operator conversion for Hasura permissions</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/database/dataGrid/utils/convertToHasuraPermissions/convertToHasuraPermissions.ts

<li>Added <code>convertOperator</code> function to handle
<code>_in_hasura</code> and <code>_nin_hasura</code>.<br> <li> Updated
<code>createNestedObjectFromRule</code> to use
<code>convertOperator</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3012/files#diff-046bb93fc9fd9abd712719cd01982ebe633596af1e3ca488403d22a32c2c067e">+26/-4</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-23 08:33:37 -05:00
github-actions[bot]
c87736eeeb chore: update versions (#3088)
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@2.11.3

### Patch Changes

- 714dffa: fix: improve project polling logic and unify usage across
components

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-19 20:10:54 +01:00
Hassan Ben Jobrane
714dffa5ec feat: split get project query to improve performance while polling for the project state (#3086)
### **PR Type**
Enhancement, Other


___

### **Description**
- Introduced a new hook `useProjectWithState` to improve project state
polling, replacing the previous `useProject` hook.
- Updated components and hooks to use `useProjectWithState` for better
performance and state management.
- Enhanced GraphQL schema with new queries and types, including virus
management capabilities.
- Removed deprecated fields and functions from the GraphQL schema.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>ProjectLayout.tsx</strong><dd><code>Update project hook
to improve state polling</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/features/orgs/layout/ProjectLayout/ProjectLayout.tsx

<li>Replaced <code>useProject</code> with
<code>useProjectWithState</code> to improve project state
<br>polling.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3086/files#diff-c5aa135e650744742b6195a88f0dc2b63518bd713c8bf7d31310d62ab95a56ad">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>useAppState.ts</strong><dd><code>Use updated project
hook for app state</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/common/hooks/useAppState/useAppState.ts

<li>Replaced <code>useProject</code> with
<code>useProjectWithState</code> for application state
<br>retrieval.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3086/files#diff-3d41e9731972061d2f25f1b71cda250cd8e38454c608564c6ddc847508bf150f">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>useProject.ts</strong><dd><code>Simplify useProject
hook by removing polling</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/features/orgs/projects/hooks/useProject/useProject.ts

<li>Removed polling options from <code>useProject</code>.<br> <li>
Simplified the hook to not include polling logic.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3086/files#diff-ef96f340af7a87a1fc60c42d8f4de846a2a54fde830a9461c64cfbc99dc11128">+1/-12</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>index.ts</strong><dd><code>Export useProjectWithState
hook</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/features/orgs/projects/hooks/useProjectWithState/index.ts

- Added export for `useProjectWithState`.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3086/files#diff-82f3d2d843a1f457832aa3d15683f6a1c30da94c0e3c01a60f9bbfccd49c5c43">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>useProjectWithState.ts</strong><dd><code>Implement
useProjectWithState hook with polling</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/hooks/useProjectWithState/useProjectWithState.ts

<li>Implemented <code>useProjectWithState</code> hook with polling
logic.<br> <li> Utilizes <code>useQuery</code> for fetching project
state.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3086/files#diff-4fa0e580d9f12e35ff5d2751597bf443bd055cd1c854cf6b356110724d424188">+77/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>graphql.ts</strong><dd><code>Update GraphQL types and
queries for project state and virus
</code><br><code>management</code></dd></summary>
<hr>

dashboard/src/utils/__generated__/graphql.ts

<li>Added <code>GetProjectStateQuery</code> and related types.<br> <li>
Removed unused fields and functions.<br> <li> Added new fields and types
for virus management.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3086/files#diff-fbd5db84b560b1c91675004448c6c7fa0dcbfb28b9eb05d53b03e6cb7b83ebac">+501/-39</a></td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>getProjectState.gql</strong><dd><code>Add GraphQL query
for project state</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/gql/organizations/getProjectState.gql

- Added new GraphQL query for fetching project state by subdomain.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3086/files#diff-88f84673d467d0b44d14b789a6beed90050c7898bb3fb95847ad892b116a3b6d">+16/-0</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-19 19:54:00 +01:00
github-actions[bot]
760835d80f chore: update versions (#3087)
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@2.11.2

### Patch Changes

- 6a34f89: fix: improve project polling logic and unify usage across
components

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-19 12:16:31 +01:00
Hassan Ben Jobrane
6a34f891a5 fix: improveuseProject hook to use proper caching and refetching (#3085)
### **PR Type**
Bug fix, Enhancement


___

### **Description**
- Removed the `target` option from multiple `useProject` hook calls
across various components to simplify usage.
- Enhanced the `useProject` hook by improving project fetching logic
with `useMemo` and adjusting refetching and caching strategies.
- Made minor formatting adjustments in several files for consistency.
- Added a changeset documenting the improvements in project polling
logic.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>9
files</summary><table>
<tr>
  <td>
    <details>

<summary><strong>useRemoteApplicationGQLClient.tsx</strong><dd><code>Simplified
`useProject` hook usage by removing `target`
option</code></dd></summary>
<hr>


dashboard/src/features/orgs/hooks/useRemoteApplicationGQLClient/useRemoteApplicationGQLClient.tsx

- Removed the `target` option from the `useProject` hook call.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3085/files#diff-3323409c3168ed8475247d08c5b868de300441b3363ae647fa298a90c4e3c973">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>useGetAppUsers.ts</strong><dd><code>Simplified
`useProject` hook usage by removing `target`
option</code></dd></summary>
<hr>


dashboard/src/features/orgs/projects/graphql/common/hooks/useGetAppUsers.ts

- Removed the `target` option from the `useProject` hook call.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3085/files#diff-8408473ab849d4b41515ef0387ce66851205c28244bd1c962f72f14e7f74d27a">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>useAppClient.ts</strong><dd><code>Simplified
`useProject` hook usage by removing `target`
option</code></dd></summary>
<hr>

dashboard/src/features/orgs/projects/hooks/useAppClient/useAppClient.ts

- Removed the `target` option from the `useProject` hook call.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3085/files#diff-0aa83222c0e0eac6f0058070de2b199e5e78514cbba405eb98d3693329a93e65">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>useProject.ts</strong><dd><code>Enhanced project
fetching logic and caching in `useProject` hook</code></dd></summary>
<hr>

dashboard/src/features/orgs/projects/hooks/useProject/useProject.ts

<li>Removed <code>target</code> option from <code>useProject</code>
hook.<br> <li> Improved project fetching logic with
<code>useMemo</code>.<br> <li> Adjusted refetching and caching
strategies.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3085/files#diff-ef96f340af7a87a1fc60c42d8f4de846a2a54fde830a9461c64cfbc99dc11128">+30/-27</a>&nbsp;
</td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>DataGridPreviewCell.tsx</strong><dd><code>Simplified
`useProject` hook usage by removing `target`
option</code></dd></summary>
<hr>


dashboard/src/features/orgs/projects/storage/dataGrid/components/DataGridPreviewCell/DataGridPreviewCell.tsx

- Removed the `target` option from the `useProject` hook call.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3085/files#diff-d7bffe5896d2c9bac505fa9675790c59549d4fb35a2ad0cce903cc0aa31a8321">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>FilesDataGrid.tsx</strong><dd><code>Simplified
`useProject` hook usage and formatting adjustments</code></dd></summary>
<hr>


dashboard/src/features/orgs/projects/storage/dataGrid/components/FilesDataGrid/FilesDataGrid.tsx

<li>Removed the <code>target</code> option from the
<code>useProject</code> hook call.<br> <li> Minor formatting
adjustments.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3085/files#diff-18c8df727e1a4fc6a94d03bd4a3a7a8cb3ad44d754803c4c7988c1c00a4b7caf">+4/-4</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>FilesDataGridControls.tsx</strong><dd><code>Simplified
`useProject` hook usage and formatting adjustments</code></dd></summary>
<hr>


dashboard/src/features/orgs/projects/storage/dataGrid/components/FilesDataGridControls/FilesDataGridControls.tsx

<li>Removed the <code>target</code> option from the
<code>useProject</code> hook call.<br> <li> Minor formatting
adjustments.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3085/files#diff-b85b40168e9c149331a68cb1a0cbec570c75233fa34385945e094b8f4c032974">+4/-4</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>useFiles.ts</strong><dd><code>Simplified `useProject`
hook usage by removing `target` option</code></dd></summary>
<hr>


dashboard/src/features/orgs/projects/storage/dataGrid/hooks/useFiles/useFiles.ts

- Removed the `target` option from the `useProject` hook call.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3085/files#diff-ed71bc1eff3e4515937f01dec41686108466c8272d974628483103ea4dd0b1ed">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>graphql.tsx</strong><dd><code>Simplified `useProject`
hook usage and formatting adjustments</code></dd></summary>
<hr>

dashboard/src/pages/orgs/[orgSlug]/projects/[appSubdomain]/graphql.tsx

<li>Removed the <code>target</code> option from the
<code>useProject</code> hook call.<br> <li> Minor formatting
adjustments.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3085/files#diff-6e3410ca11e10761fa7e9fbac46fa88089ed697b58aae7a2c58245d24208fbb1">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

</table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>1
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>stale-avocados-shake.md</strong><dd><code>Documented
changeset for project polling improvements</code>&nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

.changeset/stale-avocados-shake.md

- Added changeset for project polling logic improvements.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3085/files#diff-d70925481a44dd2deb12d1b3af17bebf4d25becb0d133e82a1410a8070f42471">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></details></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-19 18:17:44 +08:00
github-actions[bot]
037bd74764 chore: update versions (#3084)
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@2.11.1

### Patch Changes

-   0f6ce52: fix: consolidate useProject hook and fix jwt expired error

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-17 12:44:21 +01:00
Hassan Ben Jobrane
0f6ce52c4e fix(dashboard): resolve JWT expired error (#3083)
### **User description**
fixes https://github.com/nhost/projects/issues/124


___

### **PR Type**
Bug fix, Enhancement


___

### **Description**
- Refactored the `useProject` hook to consolidate query logic and
simplify error handling and loading state management.
- Removed redundant query logic for the 'console-next' target, improving
code clarity and maintainability.
- Updated pnpm version in the Dockerfile from 8.10.5 to 9.15.0 to ensure
compatibility with the latest features and fixes.
- Added a changeset documenting the fix for the JWT expired error.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>useProject.ts</strong><dd><code>Refactor and simplify
`useProject` hook logic</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/features/orgs/projects/hooks/useProject/useProject.ts

<li>Consolidated query logic in <code>useProject</code> hook.<br> <li>
Removed redundant query logic for 'console-next' target.<br> <li>
Simplified error handling and loading state management.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3083/files#diff-ef96f340af7a87a1fc60c42d8f4de846a2a54fde830a9461c64cfbc99dc11128">+15/-44</a>&nbsp;
</td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>shaggy-rivers-rescue.md</strong><dd><code>Add changeset
for JWT expired error fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/shaggy-rivers-rescue.md

<li>Added changeset for patch release.<br> <li> Documented fix for JWT
expired error.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3083/files#diff-4b792f2b2f308ef98dc4d91b0a710530f2f0635c1d6f8ba8d0eabd93ebaa049d">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Dependencies</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>Dockerfile</strong><dd><code>Update pnpm version in
Dockerfile</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/Dockerfile

- Updated pnpm version from 8.10.5 to 9.15.0.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3083/files#diff-e4409471758b4d6438b1bf954190cf0659eb6c4b30efafe877d20e4e485c383f">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-17 12:23:52 +01:00
Hassan Ben Jobrane
6696172bcb chore: upgrade to pnpm 9.15.0 (#3081)
### **PR Type**
enhancement, configuration changes


___

### **Description**
- Upgraded `pnpm` to version 9.15.0 across the project, including GitHub
Actions and root package configuration.
- Updated Node.js version to 20 in GitHub Actions and root package
configuration.
- Moved `@nhost/nhost-js` from `devDependencies` to `dependencies` in
the SvelteKit example.
- Removed `pnpm` from `devDependencies` in the Vue quickstart example.
- Added `compilerOptions` with an empty `types` array in the
`tsconfig.json` for `hasura-auth-js`.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Configuration
changes</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>action.yaml</strong><dd><code>Upgrade pnpm and Node.js
versions in GitHub Actions</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

.github/actions/install-dependencies/action.yaml

<li>Upgraded <code>pnpm</code> version from 8.10.5 to 9.15.0.<br> <li>
Updated Node.js version from 18 to 20.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3081/files#diff-342d59190b4737ee45e2062eb625ada477bcea5b4a843b25900ad55d7982f200">+3/-3</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Update pnpm and Node.js
engine in root package.json</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

package.json

<li>Updated <code>pnpm</code> version in <code>packageManager</code>
field to 9.15.0.<br> <li> Changed Node.js engine requirement to version
20 or higher.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3081/files#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519">+2/-3</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Adjust dependencies in
SvelteKit example</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

examples/quickstarts/sveltekit/package.json

- Moved `@nhost/nhost-js` from `devDependencies` to `dependencies`.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3081/files#diff-6288951fff74ec246c9cc023b7b7e3e9aad31423891bc4ea25b5d84a5f5b061f">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Remove pnpm from Vue
quickstart devDependencies</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/vue-quickstart/package.json

- Removed `pnpm` from `devDependencies`.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3081/files#diff-85166d1137e29a5275f991e1e94a0c9d5b83ac7504463ba76f9187b2b750c895">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>tsconfig.json</strong><dd><code>Add compiler options to
TypeScript configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

packages/hasura-auth-js/tsconfig.json

- Added `compilerOptions` with empty `types` array.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3081/files#diff-f4ce71cf32b93f403010fe5002bdc1081000207e40a6fea24b82346d6ea0dac7">+4/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-16 19:48:12 +01:00
github-actions[bot]
b0e848d353 chore: update versions (#3066)
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@2.11.0

### Minor Changes

-   cea3ef5: Feat: add org and project placeholders

## @nhost/docs@2.24.0

### Minor Changes

-   a99f034: chore: fix function name

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-11 12:57:46 +01:00
Nuno Pato
cea3ef5c8a feat: dashboard: add org and project static placeholder routes (#3069)
### **PR Type**
enhancement


___

### **Description**
- Introduced new components `SelectOrg` and `SelectOrgAndProject` for
selecting organizations and projects, respectively.
- Implemented filtering functionality for both organizations and
projects.
- Integrated loading indicators and error boundaries for better user
experience.
- Added navigation logic to handle routing to selected organization and
project pages.
- Updated redirect logic to accommodate new routes for organizations and
projects.
- Added new pages and index for organization and project selection.
- Documented changes in a changeset file.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>8
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>SelectOrg.tsx</strong><dd><code>Add
SelectOrganizationAndProject component with filtering and
</code><br><code>navigation</code></dd></summary>
<hr>

dashboard/src/components/common/SelectOrg/SelectOrg.tsx

<li>Added a new component <code>SelectOrganizationAndProject</code> for
selecting <br>organizations.<br> <li> Implemented filtering
functionality for organizations.<br> <li> Integrated loading indicator
and error boundary.<br> <li> Added navigation logic to organization
pages.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3069/files#diff-3d9046053de6cf89a71b2c8843435afbade4eacff8f38f57bd9dd40e81fc5ba0">+144/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>index.ts</strong><dd><code>Export SelectOrg
component</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/components/common/SelectOrg/index.ts

- Exported `SelectOrg` component from `SelectOrg.tsx`.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3069/files#diff-13c3f09c9b9992210b030c77a795c895b7b5672a603fd6577547272f1b4292c3">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>SelectOrgAndProject.tsx</strong><dd><code>Add
SelectOrganizationAndProject component with filtering and
</code><br><code>navigation</code></dd></summary>
<hr>


dashboard/src/components/common/SelectOrgAndProject/SelectOrgAndProject.tsx

<li>Added a new component <code>SelectOrganizationAndProject</code> for
selecting <br>projects.<br> <li> Implemented filtering functionality for
projects.<br> <li> Integrated loading indicator and error boundary.<br>
<li> Added navigation logic to project pages.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3069/files#diff-7d86c6e5bc51696bf1aa421c920e01a1447699456c37b025bdc407050c7b5613">+146/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>index.ts</strong><dd><code>Export SelectOrgAndProject
component</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/components/common/SelectOrgAndProject/index.ts

<li>Exported <code>SelectOrgAndProject</code> component from
<code>SelectOrgAndProject.tsx</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3069/files#diff-8b07aa6bcfe4996a7c46923856e713ecf3156fe6c2720b28efcadebd7fb1496f">+2/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>useNotFoundRedirect.ts</strong><dd><code>Update
redirect logic for new routes</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/projects/common/hooks/useNotFoundRedirect/useNotFoundRedirect.ts

<li>Updated redirect logic to include new organization and project
routes.<br> <br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3069/files#diff-837279cf43199053bca09913f62c4af019063a2e8dc7bfb7643ec54b7cecd29d">+2/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>[...slug].tsx</strong><dd><code>Add organization
selection page</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

dashboard/src/pages/orgs/_/[...slug].tsx

- Added a new page for selecting organizations using `SelectOrg`.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3069/files#diff-3993ec4184ca06532310b26dcf40fb3fb5b79c78621fbb8c83b15b145331b3e6">+15/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>[...slug].tsx</strong><dd><code>Add project selection
page</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/pages/orgs/_/projects/_/[...slug].tsx

<li>Added a new page for selecting projects using
<code>SelectOrgAndProject</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3069/files#diff-e4e0c0ae58b0bb766af6983e44171470d085d9b15079450d788ffe0ab34440ae">+15/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>index.tsx</strong><dd><code>Add index page for project
selection</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/pages/orgs/_/projects/_/index.tsx

<li>Added a new index page for project selection using
<br><code>SelectOrgAndProject</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3069/files#diff-f1903b9c41e81033add23bed91df48b3e2c485802187b160a87d2d6e2caef507">+14/-0</a>&nbsp;
&nbsp; </td>

</tr>

</table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>1
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>thin-pants-battle.md</strong><dd><code>Document org and
project placeholder feature</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/thin-pants-battle.md

- Documented the addition of organization and project placeholders.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3069/files#diff-74b67093a68ccc2b180504af0ce16b3404f16de81bd5200d15e066bda7345038">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></details></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-11 19:41:38 +08:00
Hassan Ben Jobrane
a05db74bb6 chore: remove refs toNEXT_PUBLIC_NHOST_BACKEND_URL env var (#3064)
### **PR Type**
enhancement, documentation


___

### **Description**
- Removed the `NEXT_PUBLIC_NHOST_BACKEND_URL` environment variable from
multiple configuration files, including Storybook, CI, and dashboard
workflows.
- Updated the README file to remove the reference to the
`NEXT_PUBLIC_NHOST_BACKEND_URL` environment variable.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Configuration
changes</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>main.js</strong><dd><code>Remove unused environment
variable from Storybook configuration</code></dd></summary>
<hr>

dashboard/.storybook/main.js

<li>Removed <code>NEXT_PUBLIC_NHOST_BACKEND_URL</code> from environment
configuration.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3064/files#diff-0fca1613b8f07aaeab7e635b990a910ff90eddb8a4a84f79f16d9dce3ab90aeb">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>ci.yaml</strong><dd><code>Remove unused environment
variable from CI workflow</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

.github/workflows/ci.yaml

<li>Removed <code>NEXT_PUBLIC_NHOST_BACKEND_URL</code> from CI
environment variables.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3064/files#diff-944291df2c9c06359d37cc8833d182d705c9e8c3108e7cfe132d61a06e9133dd">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>dashboard.yaml</strong><dd><code>Remove unused
environment variable from dashboard workflow</code></dd></summary>
<hr>

.github/workflows/dashboard.yaml

<li>Removed <code>NEXT_PUBLIC_NHOST_BACKEND_URL</code> from dashboard
workflow <br>environment variables.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3064/files#diff-1b7fed7873af85166ff8fc5089ed9507b4ae6b40cccd993849feca4b42a37c4a">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>README.md</strong><dd><code>Update README to remove
unused environment variable</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

dashboard/README.md

<li>Removed documentation reference to
<code>NEXT_PUBLIC_NHOST_BACKEND_URL</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3064/files#diff-c15729e6c35a283a4b0eda60a991303b6c36c03903ba42dbf832bb8d0daa1a1a">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-09 14:14:57 +01:00
David Barroso
73f3d69776 chore (ci): remove unnecessary/unsafe permissions to gen_ai_review (#3067)
### **PR Type**
enhancement


___

### **Description**
- Removed the 'contents: write' permission from the GitHub Actions
workflow to enhance security by limiting permissions to only those
necessary for the job.
- The change ensures that the workflow runs with minimal permissions,
reducing potential security risks.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>gen_ai_review.yaml</strong><dd><code>Remove unnecessary
permissions from GitHub Actions workflow</code></dd></summary>
<hr>

.github/workflows/gen_ai_review.yaml

<li>Removed 'contents: write' permission from the workflow.<br> <li>
Ensured only necessary permissions are retained for the job.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3067/files#diff-d1e4c772e0acb5ce4891df2dd94ba58ffaf6393e8f75493ec7e10cbce1c4992c">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-09 14:13:30 +01:00
Nuno Pato
a99f034bd4 chore: fix docs (#3063)
### **PR Type**
documentation, enhancement


___

### **Description**
- Corrected the function name from `refreshToken` to `refreshSession` in
both TypeScript code and documentation.
- Updated code examples and documentation to reflect the correct
function usage.
- Added a changeset to document the changes made to the function name.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>hasura-auth-client.ts</strong><dd><code>Correct
function name in TypeScript code examples</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

packages/hasura-auth-js/src/hasura-auth-client.ts

<li>Corrected function name from <code>refreshToken</code> to
<code>refreshSession</code> in code <br>examples.<br> <li> Updated
documentation comments to reflect the function name change.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3063/files#diff-0dbc30932ed723b7fd458066893f29f2f77658436c84adf42613813ea042c992">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>late-shrimps-taste.md</strong><dd><code>Add changeset
for function name correction</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.changeset/late-shrimps-taste.md

<li>Added a changeset file for documenting the function name
correction.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3063/files#diff-9405aff0ebd3f8ea80c4142f72c9fdfede73f701869ccaf6d05e27c10f2f5eee">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>refresh-session.mdx</strong><dd><code>Update function
name in documentation examples</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/reference/javascript/auth/refresh-session.mdx

<li>Updated function name from <code>refreshToken</code> to
<code>refreshSession</code> in <br>documentation examples.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3063/files#diff-a738ad2b44e508034c9df030d5538c17596a205d75dae3f03cb76690ca8d0d50">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-12-09 20:30:46 +08:00
183 changed files with 17332 additions and 30445 deletions

View File

@@ -14,22 +14,22 @@ runs:
steps:
- uses: pnpm/action-setup@v4
with:
version: 8.10.5
version: 9.15.0
run_install: false
- name: Get pnpm cache directory
id: pnpm-cache-dir
shell: bash
run: echo "dir=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
- uses: actions/cache@v4
id: pnpm-cache
with:
path: ${{ steps.pnpm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: ${{ runner.os }}-node-
- name: Use Node.js v18
- name: Use Node.js v20
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20
- shell: bash
name: Install packages
run: pnpm install --frozen-lockfile

View File

@@ -18,7 +18,6 @@ env:
TURBO_TEAM: nhost
NEXT_PUBLIC_ENV: dev
NEXT_TELEMETRY_DISABLED: 1
NEXT_PUBLIC_NHOST_BACKEND_URL: http://localhost:1337
NHOST_TEST_DASHBOARD_URL: ${{ vars.NHOST_TEST_DASHBOARD_URL }}
NHOST_TEST_WORKSPACE_NAME: ${{ vars.NHOST_TEST_WORKSPACE_NAME }}
NHOST_TEST_PROJECT_NAME: ${{ vars.NHOST_TEST_PROJECT_NAME }}

View File

@@ -8,7 +8,6 @@ env:
TURBO_TEAM: nhost
NEXT_PUBLIC_ENV: dev
NEXT_TELEMETRY_DISABLED: 1
NEXT_PUBLIC_NHOST_BACKEND_URL: http://localhost:1337
jobs:
build:

View File

@@ -12,12 +12,11 @@ jobs:
permissions:
issues: write
pull-requests: write
contents: write
name: Run pr agent on every pull request, respond to user comments
steps:
- name: PR Agent action step
id: pragent
uses: Codium-ai/pr-agent@v0.24
uses: Codium-ai/pr-agent@v0.26
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENAI_KEY: ${{ secrets.OPENAI_API_KEY }}

View File

@@ -2,5 +2,5 @@
// $schema provides code completion hints to IDEs.
"$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json",
"moderate": true,
"allowlist": ["vue-template-compiler", "micromatch", "path-to-regexp"]
"allowlist": ["vue-template-compiler"]
}

View File

@@ -42,7 +42,6 @@ module.exports = {
env: (config) => ({
...config,
NEXT_PUBLIC_ENV: 'dev',
NEXT_PUBLIC_NHOST_BACKEND_URL: 'http://localhost:1337',
NEXT_PUBLIC_NHOST_PLATFORM: 'false',
}),
};

View File

@@ -1,5 +1,74 @@
# @nhost/dashboard
## 2.14.0
### Minor Changes
- d43931e: fix: invalid organization slug/project subdomain doesn't open 404 page
- 5df6fa2: feat: add unencrypted disk warning in storage capacity settings
### Patch Changes
- 44c1e17: chore: update `msw` to v1.3.5 to fix vulnerabilities
- @nhost/react-apollo@16.0.0
- @nhost/nextjs@2.2.1
## 2.13.0
### Minor Changes
- 21e90da: chore: remove restrictions on SMTP sender so My Name <name@acme.com> can be added
- 865dd93: fix: duplicate Run placeholders when there is an error in the backend
- 6902a36: fix: can remove resources if postgres capacity is higher than 10
- a535aa3: fix: fetch user roles locally in auth section
- 0c50816: fix: allow decimal numbers in database row insert
- aea6d18: chore: add warning when pausing a project about losing Run services persistent volume data
- d3b4fc3: feat: allow to change postgres settings if project is paused
- 29d27e1: chore: update `next` to v14.2.22 to fix vulnerabilities
- c9dca09: feat: add reset password form
- b3bcacb: fix: paused project banner cannot read null project name
### Patch Changes
- Updated dependencies [46fc520]
- Updated dependencies [29d27e1]
- @nhost/nextjs@2.2.0
- @nhost/react-apollo@15.0.1
## 2.12.0
### Minor Changes
- eb95562: fix: show all available permission variables in permission dropdown select
### Patch Changes
- 8b5c4a0: chore: cleanup layout and add disable duplicate atom key checking in development mode
## 2.11.3
### Patch Changes
- 714dffa: fix: improve project polling logic and unify usage across components
## 2.11.2
### Patch Changes
- 6a34f89: fix: improve project polling logic and unify usage across components
## 2.11.1
### Patch Changes
- 0f6ce52: fix: consolidate useProject hook and fix jwt expired error
## 2.11.0
### Minor Changes
- cea3ef5: Feat: add org and project placeholders
## 2.10.0
### Minor Changes

View File

@@ -1,13 +1,13 @@
FROM node:18-alpine AS pruner
FROM node:20-alpine AS pruner
RUN apk add --no-cache libc6-compat
RUN apk update
WORKDIR /app
RUN yarn global add turbo@1.11.3
RUN yarn global add turbo@2.2.3
COPY . .
RUN turbo prune --scope="@nhost/dashboard" --docker
FROM node:18-alpine AS builder
FROM node:20-alpine AS builder
ARG TURBO_TOKEN
ARG TURBO_TEAM
@@ -15,22 +15,22 @@ RUN apk add --no-cache libc6-compat python3 make g++
RUN apk update
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED 1
ENV NEXT_PUBLIC_ENV dev
ENV NEXT_PUBLIC_NHOST_PLATFORM false
ENV NEXT_TELEMETRY_DISABLED=1
ENV NEXT_PUBLIC_ENV=dev
ENV NEXT_PUBLIC_NHOST_PLATFORM=false
# placeholders for URLs, will be replaced on runtime by entrypoint script
ENV NEXT_PUBLIC_NHOST_ADMIN_SECRET __NEXT_PUBLIC_NHOST_ADMIN_SECRET__
ENV NEXT_PUBLIC_NHOST_AUTH_URL __NEXT_PUBLIC_NHOST_AUTH_URL__
ENV NEXT_PUBLIC_NHOST_FUNCTIONS_URL __NEXT_PUBLIC_NHOST_FUNCTIONS_URL__
ENV NEXT_PUBLIC_NHOST_GRAPHQL_URL __NEXT_PUBLIC_NHOST_GRAPHQL_URL__
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__
ENV NEXT_PUBLIC_NHOST_ADMIN_SECRET=__NEXT_PUBLIC_NHOST_ADMIN_SECRET__
ENV NEXT_PUBLIC_NHOST_AUTH_URL=__NEXT_PUBLIC_NHOST_AUTH_URL__
ENV NEXT_PUBLIC_NHOST_FUNCTIONS_URL=__NEXT_PUBLIC_NHOST_FUNCTIONS_URL__
ENV NEXT_PUBLIC_NHOST_GRAPHQL_URL=__NEXT_PUBLIC_NHOST_GRAPHQL_URL__
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
RUN yarn global add pnpm@9.15.0
COPY .gitignore .gitignore
COPY --from=pruner /app/out/json/ .
COPY --from=pruner /app/out/pnpm-*.yaml .
@@ -41,7 +41,7 @@ COPY turbo.json turbo.json
COPY config/ config/
RUN pnpm build:dashboard
FROM node:18-alpine AS runner
FROM node:20-alpine AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs
@@ -58,4 +58,4 @@ COPY --from=builder --chown=nextjs:nodejs /app/dashboard/.next/standalone/app ./
COPY --from=builder --chown=nextjs:nodejs /app/dashboard/.next/static ./dashboard/.next/static
ENTRYPOINT ["./docker-entrypoint.sh"]
CMD ["node", "dashboard/server.js"]
CMD ["node", "dashboard/server.js"]

View File

@@ -100,7 +100,6 @@ pnpm storybook --port 6007
| Name | Description |
| --------------------------------------- | ------------------------------------------------------------------------------------------- |
| `NEXT_PUBLIC_NHOST_BACKEND_URL` | Backend URL. This is only used if `NEXT_PUBLIC_NHOST_PLATFORM` is `true`. |
| `NEXT_PUBLIC_STRIPE_PK` | Stripe public key. This is only used if `NEXT_PUBLIC_NHOST_PLATFORM` is `true`. |
| `NEXT_PUBLIC_GITHUB_APP_INSTALL_URL` | URL of the GitHub application. This is only used if `NEXT_PUBLIC_NHOST_PLATFORM` is `true`. |
| `NEXT_PUBLIC_ANALYTICS_WRITE_KEY` | Analytics key. This is only used if `NEXT_PUBLIC_NHOST_PLATFORM` is `true`. |

View File

@@ -1,10 +1,10 @@
{
"name": "@nhost/dashboard",
"version": "2.10.0",
"version": "2.14.0",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
"dev": "next dev",
"dev": "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false next dev",
"build": "next build --no-lint",
"analyze": "ANALYZE=true pnpm build --no-lint",
"start": "next start",
@@ -84,7 +84,7 @@
"just-kebab-case": "^4.2.0",
"lodash.debounce": "^4.0.8",
"lucide-react": "^0.416.0",
"next": "^14.2.10",
"next": "^14.2.22",
"next-nprogress-bar": "^2.3.13",
"next-seo": "^6.5.0",
"next-themes": "^0.3.0",
@@ -177,7 +177,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"jsdom": "^22.1.0",
"lint-staged": "^15.2.2",
"msw": "^1.3.3",
"msw": "^1.3.5",
"msw-storybook-addon": "^1.10.0",
"node-fetch": "^3.3.2",
"postcss": "^8.4.38",

View File

@@ -0,0 +1,137 @@
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Input } from '@/components/ui/v2/Input';
import { List } from '@/components/ui/v2/List';
import { ListItem } from '@/components/ui/v2/ListItem';
import { Text } from '@/components/ui/v2/Text';
import { useOrgs } from '@/features/orgs/projects/hooks/useOrgs';
import { } from '@/utils/__generated__/graphql';
import { Divider } from '@mui/material';
import debounce from 'lodash.debounce';
import Image from 'next/image';
import { useRouter } from 'next/router';
import type { ChangeEvent } from 'react';
import { Fragment, useEffect, useMemo, useState } from 'react';
export default function SelectOrganizationAndProject() {
const { orgs, loading } = useOrgs();
const router = useRouter();
const organizations = orgs.map((org) => ({
name: org.name,
value: `/orgs/${org.slug}`,
}));
const [filter, setFilter] = useState('');
const handleFilterChange = useMemo(
() =>
debounce((event: ChangeEvent<HTMLInputElement>) => {
setFilter(event.target.value);
}, 200),
[],
);
useEffect(() => () => handleFilterChange.cancel(), [handleFilterChange]);
const goToOrgPage = async (org: {
name: string;
value: string;
}) => {
const { slug } = router.query;
await router.push({
pathname: `${org.value}/${
(() => {
if (!slug) {
return '';
}
return Array.isArray(slug) ? slug.join('/') : slug;
})()
}`,
});
};
const orgsToDisplay = filter
? organizations.filter((org) =>
org.name.toLowerCase().includes(filter.toLowerCase()),
)
: organizations;
if (loading) {
return (
<div className="flex w-full justify-center">
<ActivityIndicator
delay={500}
label="Loading organizations..."
/>
</div>
);
}
return (
<div className="flex flex-col items-start w-full h-full px-5 py-4 mx-auto bg-background">
<div className="mx-auto flex h-full w-full max-w-[760px] flex-col gap-4 py-6 sm:py-14">
<Text variant="h2" component="h1" className="">
Select an Organization
</Text>
<div>
<div className="mb-2 flex w-full">
<Input
placeholder="Search..."
onChange={handleFilterChange}
fullWidth
autoFocus
/>
</div>
<RetryableErrorBoundary>
{orgsToDisplay.length === 0 ? (
<Box className="h-import py-2">
<Text variant="subtitle2">No results found.</Text>
</Box>
) : (
<List className="h-import overflow-y-auto">
{orgsToDisplay.map((org, index) => (
<Fragment key={org.value}>
<ListItem.Root
className="grid grid-flow-col justify-start gap-2 py-2.5"
secondaryAction={
<Button
variant="borderless"
color="primary"
onClick={() => goToOrgPage(org)}
>
Select
</Button>
}
>
<ListItem.Avatar>
<span className="inline-block h-6 w-6 overflow-hidden rounded-md">
<Image
src="/logos/new.svg"
alt="Nhost Logo"
width={24}
height={24}
/>
</span>
</ListItem.Avatar>
<ListItem.Text
primary={org.name}
secondary={`${org.name} / ${org.name}`}
/>
</ListItem.Root>
{index < orgs.length - 1 && <Divider component="li" />}
</Fragment>
))}
</List>
)}
</RetryableErrorBoundary>
</div>
</div>
</div>
);
}

View File

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

View File

@@ -0,0 +1,141 @@
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Input } from '@/components/ui/v2/Input';
import { List } from '@/components/ui/v2/List';
import { ListItem } from '@/components/ui/v2/ListItem';
import { Text } from '@/components/ui/v2/Text';
import { useOrgs } from '@/features/orgs/projects/hooks/useOrgs';
import { } from '@/utils/__generated__/graphql';
import { Divider } from '@mui/material';
import debounce from 'lodash.debounce';
import Image from 'next/image';
import { useRouter } from 'next/router';
import type { ChangeEvent } from 'react';
import { Fragment, useEffect, useMemo, useState } from 'react';
export default function SelectOrganizationAndProject() {
const { orgs, loading } = useOrgs();
const router = useRouter();
const projects = orgs.flatMap((org) =>
org.apps.map((app) => ({
organizationName: org.name,
projectName: app.name,
value: `/orgs/${org.slug}/projects/${app.subdomain}`,
})),
);
const [filter, setFilter] = useState('');
const handleFilterChange = useMemo(
() =>
debounce((event: ChangeEvent<HTMLInputElement>) => {
setFilter(event.target.value);
}, 200),
[],
);
useEffect(() => () => handleFilterChange.cancel(), [handleFilterChange]);
const goToProjectPage = async (project: {
organizationName: string;
projectName: string;
value: string;
}) => {
const { slug } = router.query;
await router.push({
pathname: `${project.value}/${
(() => {
if (!slug) {
return '';
}
return Array.isArray(slug) ? slug.join('/') : slug;
})()
}`,
});
};
const projectsToDisplay = filter
? projects.filter((project) =>
project.projectName.toLowerCase().includes(filter.toLowerCase()),
)
: projects;
if (loading) {
return (
<div className="flex w-full justify-center">
<ActivityIndicator
delay={500}
label="Loading organizations and projects..."
/>
</div>
);
}
return (
<div className="flex flex-col items-start w-full h-full px-5 py-4 mx-auto bg-background">
<div className="mx-auto flex h-full w-full max-w-[760px] flex-col gap-4 py-6 sm:py-14">
<Text variant="h2" component="h1" className="">
Select a Project
</Text>
<div>
<div className="mb-2 flex w-full">
<Input
placeholder="Search..."
onChange={handleFilterChange}
fullWidth
autoFocus
/>
</div>
<RetryableErrorBoundary>
{projectsToDisplay.length === 0 ? (
<Box className="h-import py-2">
<Text variant="subtitle2">No results found.</Text>
</Box>
) : (
<List className="h-import overflow-y-auto">
{projectsToDisplay.map((project, index) => (
<Fragment key={project.value}>
<ListItem.Root
className="grid grid-flow-col justify-start gap-2 py-2.5"
secondaryAction={
<Button
variant="borderless"
color="primary"
onClick={() => goToProjectPage(project)}
>
Select
</Button>
}
>
<ListItem.Avatar>
<span className="inline-block h-6 w-6 overflow-hidden rounded-md">
<Image
src="/logos/new.svg"
alt="Nhost Logo"
width={24}
height={24}
/>
</span>
</ListItem.Avatar>
<ListItem.Text
primary={project.projectName}
secondary={`${project.organizationName} / ${project.projectName}`}
/>
</ListItem.Root>
{index < projects.length - 1 && <Divider component="li" />}
</Fragment>
))}
</List>
)}
</RetryableErrorBoundary>
</div>
</div>
</div>
);
}

View File

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

View File

@@ -21,22 +21,9 @@ import { useNotFoundRedirect } from '@/features/projects/common/hooks/useNotFoun
import { cn } from '@/lib/utils';
import Image from 'next/image';
import { useRouter } from 'next/router';
import {
useEffect,
useState,
type DetailedHTMLProps,
type HTMLProps,
} from 'react';
import { useEffect, useState } from 'react';
export interface AuthenticatedLayoutProps extends BaseLayoutProps {
/**
* Props passed to the internal content container.
*/
contentContainerProps?: DetailedHTMLProps<
HTMLProps<HTMLDivElement>,
HTMLDivElement
>;
}
export interface AuthenticatedLayoutProps extends BaseLayoutProps {}
export default function AuthenticatedLayout({
children,

View File

@@ -21,23 +21,22 @@ export default function UnauthenticatedLayout({
const router = useRouter();
const isPlatform = useIsPlatform();
const { isAuthenticated, isLoading } = useAuthenticationStatus();
const isOnResetPassword = router.route === '/password/reset';
useEffect(() => {
if (!isPlatform || (!isLoading && isAuthenticated)) {
router.push('/');
// we do not want to redirect if the user tries to reset their password
if (!isOnResetPassword) {
router.push('/');
}
}
}, [isLoading, isAuthenticated, router, isPlatform]);
}, [isLoading, isAuthenticated, router, isPlatform, isOnResetPassword]);
if (!isPlatform || isLoading || isAuthenticated) {
if ((!isPlatform || isLoading || isAuthenticated) && !isOnResetPassword) {
return (
<BaseLayout {...props}>
<LoadingScreen
sx={{ backgroundColor: (theme) => theme.palette.background.default }}
slotProps={{
activityIndicator: {
className: 'text-white',
},
}}
/>
</BaseLayout>
);
@@ -59,19 +58,19 @@ export default function UnauthenticatedLayout({
<RetryableErrorBoundary>
<Box
className="flex items-center min-h-screen"
className="flex min-h-screen items-center"
sx={{ backgroundColor: (theme) => theme.palette.common.black }}
>
<Container
rootClassName="bg-transparent h-full"
className="grid items-center w-full h-full gap-12 pt-8 pb-12 bg-transparent justify-items-center lg:grid-cols-2 lg:gap-4 lg:pb-0 lg:pt-0"
className="grid h-full w-full items-center justify-items-center gap-12 bg-transparent pb-12 pt-8 lg:grid-cols-2 lg:gap-4 lg:pb-0 lg:pt-0"
>
<div className="relative z-10 order-2 grid w-full max-w-[544px] grid-flow-row gap-12 lg:order-1">
{children}
</div>
<div className="relative z-0 order-1 flex h-full w-full items-center justify-center md:min-h-[150px] lg:order-2 lg:min-h-[none]">
<div className="absolute top-0 bottom-0 left-0 right-0 flex items-center justify-center w-full h-full max-w-xl mx-auto overflow-hidden opacity-70">
<div className="absolute bottom-0 left-0 right-0 top-0 mx-auto flex h-full w-full max-w-xl items-center justify-center overflow-hidden opacity-70">
<Image
priority
src="/assets/line-grid.svg"

View File

@@ -1,6 +1,6 @@
import { type DialogProps } from '@radix-ui/react-dialog';
import { Command as CommandPrimitive } from 'cmdk';
import { Search } from 'lucide-react';
import { Command as CommandPrimitive, useCommandState } from 'cmdk';
import { PlusIcon, Search } from 'lucide-react';
import * as React from 'react';
import { Dialog, DialogContent } from '@/components/ui/v3/dialog';
@@ -26,7 +26,7 @@ interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="p-0 overflow-hidden shadow-lg">
<DialogContent className="overflow-hidden p-0 shadow-lg">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
@@ -37,14 +37,22 @@ const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center px-3 border-b" cmdk-input-wrapper="">
<Search className="w-4 h-4 mr-2 opacity-50 shrink-0" />
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input> & {
prefix?: React.ReactNode;
}
>(({ className, prefix, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
{prefix && (
<span className="pointer-events-none flex items-center text-muted-foreground">
{prefix}
</span>
)}
<CommandPrimitive.Input
ref={ref}
className={cn(
'flex h-11 w-full rounded-md border-none bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground focus:ring-0 disabled:cursor-not-allowed disabled:opacity-50',
prefix && 'pl-0',
className,
)}
{...props}
@@ -73,7 +81,7 @@ const CommandEmpty = React.forwardRef<
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-sm text-center"
className="py-6 text-center text-sm"
{...props}
/>
));
@@ -140,6 +148,25 @@ const CommandShortcut = ({
};
CommandShortcut.displayName = 'CommandShortcut';
const CommandCreateItem = ({
onCreate,
}: {
onCreate: (value: string) => void;
}) => {
const query = useCommandState((state) => state.search);
if (!query || !onCreate) {
return null;
}
return (
<CommandItem forceMount value="create" onSelect={() => onCreate(query)}>
<PlusIcon className="mr-2" /> {query}
</CommandItem>
);
};
CommandCreateItem.displayName = 'CommandCreateItem';
export {
Command,
CommandDialog,
@@ -150,4 +177,5 @@ export {
CommandItem,
CommandShortcut,
CommandSeparator,
CommandCreateItem,
};

View File

@@ -0,0 +1,183 @@
'use client';
import { X } from 'lucide-react';
import { Badge } from '@/components/ui/v3/badge';
import {
Command,
CommandGroup,
CommandItem,
CommandList,
} from '@/components/ui/v3/command';
import { cn } from '@/lib/utils';
import { Command as CommandPrimitive } from 'cmdk';
import {
useCallback,
useMemo,
useRef,
useState,
type KeyboardEvent,
} from 'react';
type Option = Record<'value' | 'label', string>;
interface FancyMultiSelectProps {
defaultValue?: Option[];
options?: Option[];
creatable?: boolean;
className?: string;
onChange?: (selected: Option[]) => void;
}
export function FancyMultiSelect({
defaultValue = [],
options = [],
creatable = false,
className,
onChange,
}: FancyMultiSelectProps) {
const inputRef = useRef<HTMLInputElement>(null);
const [open, setOpen] = useState(false);
const [selected, setSelected] = useState<Option[]>(defaultValue);
const [inputValue, setInputValue] = useState('');
const handleUnselect = useCallback((option: Option) => {
setSelected((prev) => prev.filter((s) => s.value !== option.value));
}, []);
const handleKeyDown = useCallback((e: KeyboardEvent<HTMLDivElement>) => {
const input = inputRef.current;
if (input) {
if (e.key === 'Delete' || e.key === 'Backspace') {
if (input.value === '') {
setSelected((prev) => {
const newSelected = [...prev];
newSelected.pop();
return newSelected;
});
}
}
// This is not a default behaviour of the <input /> field
if (e.key === 'Escape') {
input.blur();
}
}
}, []);
const handleSelect = useCallback(
(option: Option) => {
setInputValue('');
setSelected((prev) => {
const newSelected = [...prev, option];
onChange?.(newSelected);
return newSelected;
});
},
[onChange],
);
const selectables = useMemo(() => {
const filtered = options.filter(
(option) =>
!selected.map((s) => s.value).includes(option.value) &&
option.label.toLowerCase().includes(inputValue.toLowerCase()),
);
if (creatable && inputValue) {
return [
...filtered,
{
value: inputValue.toLowerCase(),
label: inputValue,
},
];
}
return filtered;
}, [options, selected, inputValue, creatable]);
return (
<Command
onKeyDown={handleKeyDown}
className="relative overflow-visible bg-transparent"
>
<div
className={cn(
'group flex min-h-10 flex-1 rounded-md border bg-background px-4 py-0 text-sm ring-offset-background hover:bg-accent',
className,
)}
>
<div className="flex flex-1 flex-wrap items-center gap-1 overflow-x-hidden py-1">
{selected.map((option) => {
return (
<Badge
className="h-7 overflow-x-hidden text-[12px] font-normal"
key={option.value}
variant="outline"
>
<span className="overflow-x-hidden text-ellipsis whitespace-nowrap break-words font-medium">
{option.label}
</span>
<button
type="button"
aria-label={`Remove ${option.label}`}
className="ml-1 rounded-full outline-none"
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleUnselect(option);
}
}}
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onClick={() => handleUnselect(option)}
>
<X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
</button>
</Badge>
);
})}
{/* Avoid having the "Search" Icon */}
<CommandPrimitive.Input
ref={inputRef}
value={inputValue}
onValueChange={setInputValue}
onBlur={() => setOpen(false)}
onFocus={() => setOpen(true)}
placeholder="Select options..."
className="flex flex-1 border-none bg-transparent px-0 py-1 text-sm font-medium outline-none !ring-0 !ring-offset-0 placeholder:text-sm placeholder:text-muted-foreground group-hover:text-accent-foreground"
/>
</div>
</div>
<div className="relative">
<CommandList>
{open && selectables.length > 0 ? (
<div className="absolute top-2 z-10 w-full rounded-md border bg-popover text-popover-foreground shadow-md outline-none animate-in">
<CommandGroup className="h-full overflow-auto">
{selectables.map((option) => {
return (
<CommandItem
key={option.value}
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onSelect={() => handleSelect(option)}
className="cursor-pointer"
>
{creatable &&
!options.find((opt) => opt.value === option.value)
? `Create "${option.label}"`
: option.label}
</CommandItem>
);
})}
</CommandGroup>
</div>
) : null}
</CommandList>
</div>
</Command>
);
}

View File

@@ -9,7 +9,7 @@ import { useMemo } from 'react';
* @returns A function that returns a new ApolloClient instance.
*/
export default function useRemoteApplicationGQLClient() {
const { project, loading } = useProject({ target: 'user-project' });
const { project, loading } = useProject();
const serviceUrl = generateAppServiceUrl(
project?.subdomain,
project?.region,

View File

@@ -12,7 +12,7 @@ import { ApplicationUnknown } from '@/features/orgs/projects/common/components/A
import { ApplicationUnpausing } from '@/features/orgs/projects/common/components/ApplicationUnpausing';
import { useAppState } from '@/features/orgs/projects/common/hooks/useAppState';
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useProjectWithState } from '@/features/orgs/projects/hooks/useProjectWithState';
import { ApplicationStatus } from '@/types/application';
import { NextSeo } from 'next-seo';
import { useRouter } from 'next/router';
@@ -37,7 +37,7 @@ function ProjectLayoutContent({
const { state } = useAppState();
const isPlatform = useIsPlatform();
const { project, loading, error } = useProject({ poll: true });
const { project, loading, error } = useProjectWithState();
const isOnOverviewPage = route === '/orgs/[orgSlug]/projects/[appSubdomain]';

View File

@@ -19,7 +19,7 @@ import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWith
const validationSchema = yup
.object({
sender: yup.string().label('SMTP Sender').email().required(),
sender: yup.string().label('SMTP Sender').required(),
password: yup.string().label('Password').required(),
})
.required();

View File

@@ -30,7 +30,7 @@ const smtpValidationSchema = yup
user: yup.string().label('Username').required(),
password: yup.string().label('Password'),
method: yup.string().required(),
sender: yup.string().label('SMTP Sender').email().required(),
sender: yup.string().label('SMTP Sender').required(),
})
.required();

View File

@@ -16,18 +16,20 @@ import { Text } from '@/components/ui/v2/Text';
import { useRemoteApplicationGQLClient } from '@/features/orgs/hooks/useRemoteApplicationGQLClient';
import { EditUserPasswordForm } from '@/features/orgs/projects/authentication/users/components/EditUserPasswordForm';
import { getReadableProviderName } from '@/features/orgs/projects/authentication/users/utils/getReadableProviderName';
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimirClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
import { type RemoteAppUser } from '@/pages/orgs/[orgSlug]/projects/[appSubdomain]/users';
import type { DialogFormProps } from '@/types/common';
import { copy } from '@/utils/copy';
import {
RemoteAppGetUsersDocument,
useGetProjectLocalesQuery,
useGetRolesPermissionsQuery,
useUpdateRemoteAppUserMutation,
} from '@/utils/__generated__/graphql';
import { copy } from '@/utils/copy';
import { yupResolver } from '@hookform/resolvers/yup';
import { useTheme } from '@mui/material';
import { format } from 'date-fns';
@@ -106,6 +108,8 @@ export default function EditUserForm({
onDeleteUser,
roles,
}: EditUserFormProps) {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const theme = useTheme();
const { onDirtyStateChange, openDialog } = useDialog();
const { project } = useProject();
@@ -196,6 +200,7 @@ export default function EditUserForm({
const { data: dataRoles } = useGetRolesPermissionsQuery({
variables: { appId: project?.id },
...(!isPlatform ? { client: localMimirClient } : {}),
});
const allAvailableProjectRoles = getUserRoles(
@@ -206,6 +211,7 @@ export default function EditUserForm({
variables: {
appId: project?.id,
},
...(!isPlatform ? { client: localMimirClient } : {}),
});
const allowedLocales = data?.config?.auth?.user?.locale?.allowed || [];

View File

@@ -15,6 +15,8 @@ import { Text } from '@/components/ui/v2/Text';
import { useRemoteApplicationGQLClient } from '@/features/orgs/hooks/useRemoteApplicationGQLClient';
import type { EditUserFormValues } from '@/features/orgs/projects/authentication/users/components/EditUserForm';
import { getReadableProviderName } from '@/features/orgs/projects/authentication/users/utils/getReadableProviderName';
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimirClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
@@ -61,6 +63,8 @@ export interface UsersBodyProps {
export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
const theme = useTheme();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { openAlertDialog, openDrawer, closeDrawer } = useDialog();
const { project } = useProject();
const remoteProjectGQLClient = useRemoteApplicationGQLClient();
@@ -88,6 +92,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
*/
const { data: dataRoles } = useGetRolesPermissionsQuery({
variables: { appId: project?.id },
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { allowed: allowedRoles } = dataRoles?.config?.auth?.user?.roles || {};

View File

@@ -36,8 +36,8 @@ export default function ApplicationPaused() {
>
<RemoveApplicationModal
close={() => setShowDeletingModal(false)}
title={`Remove project ${project.name}?`}
description={`The project ${project.name} will be removed. All data will be lost and there will be no way to
title={`Remove project ${project?.name}?`}
description={`The project ${project?.name} will be removed. All data will be lost and there will be no way to
recover the app once it has been deleted.`}
className="z-50"
/>

View File

@@ -1,4 +1,4 @@
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useProjectWithState } from '@/features/orgs/projects/hooks/useProjectWithState';
import { ApplicationStatus } from '@/types/application';
/**
@@ -9,7 +9,7 @@ export default function useAppState(): {
state: ApplicationStatus;
message?: string;
} {
const { project } = useProject({ poll: true });
const { project } = useProjectWithState();
const noApplication = !project;
if (noApplication) {

View File

@@ -24,7 +24,7 @@ export default function useProjectRedirectWhenReady(
const { data, client, startPolling, ...rest } = useGetApplicationStateQuery({
...options,
variables: { ...options.variables, appId: project?.id },
skip: !project.id,
skip: !project?.id,
});
useEffect(() => {

View File

@@ -65,8 +65,7 @@ const Template: ComponentStory<typeof ColumnAutocomplete> = function Template(
<ColumnAutocomplete
{...args}
name="firstReference"
label="First Reference"
onChange={(_event, newValue) =>
onChange={(newValue) =>
form.setValue('firstReference', newValue.value, {
shouldDirty: true,
})
@@ -80,8 +79,7 @@ const Template: ComponentStory<typeof ColumnAutocomplete> = function Template(
<ColumnAutocomplete
{...args}
name="secondReference"
label="Second Reference"
onChange={(_event, newValue) =>
onChange={(newValue) =>
form.setValue('secondReference', newValue.value, {
shouldDirty: true,
})

View File

@@ -7,6 +7,10 @@ import { setupServer } from 'msw/node';
import { afterAll, afterEach, beforeAll, test, vi } from 'vitest';
import ColumnAutocomplete from './ColumnAutocomplete';
vi.mock('@/lib/utils', () => ({
cn: (...classes: (string | undefined)[]) => classes.filter(Boolean).join(' '),
}));
const server = setupServer(
tableQuery,
hasuraMetadataQuery,
@@ -21,17 +25,9 @@ afterAll(() => {
});
test('should render a combobox', () => {
render(
<ColumnAutocomplete
schema="public"
table="books"
label="Column Autocomplete"
/>,
);
render(<ColumnAutocomplete schema="public" table="books" />);
expect(
screen.getByRole('combobox', { name: /column autocomplete/i }),
).toBeInTheDocument();
expect(screen.getByRole('combobox')).toBeInTheDocument();
});
// Note: Network requests don't go through in tests, so we can't test the

View File

@@ -1,39 +1,33 @@
import { InlineCode } from '@/components/presentational/InlineCode';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import type { AutocompleteOption } from '@/components/ui/v2/Autocomplete';
import { AutocompletePopper } from '@/components/ui/v2/Autocomplete';
import { Box } from '@/components/ui/v2/Box';
import { IconButton } from '@/components/ui/v2/IconButton';
import { ArrowLeftIcon } from '@/components/ui/v2/icons/ArrowLeftIcon';
import type { InputProps } from '@/components/ui/v2/Input';
import { Input, inputClasses } from '@/components/ui/v2/Input';
import { List } from '@/components/ui/v2/List';
import { OptionBase } from '@/components/ui/v2/Option';
import { OptionGroupBase } from '@/components/ui/v2/OptionGroup';
import { Text } from '@/components/ui/v2/Text';
import { Button, type ButtonProps } from '@/components/ui/v3/button';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/v3/command';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/v3/popover';
import { useMetadataQuery } from '@/features/orgs/projects/database/dataGrid/hooks/useMetadataQuery';
import { useTableQuery } from '@/features/orgs/projects/database/dataGrid/hooks/useTableQuery';
import { getTruncatedText } from '@/utils/getTruncatedText';
import type { AutocompleteGroupedOption } from '@mui/base/useAutocomplete';
import { useAutocomplete } from '@mui/base/useAutocomplete';
import type { AutocompleteRenderGroupParams } from '@mui/material/Autocomplete';
import { autocompleteClasses } from '@mui/material/Autocomplete';
import type {
ChangeEvent,
ForwardedRef,
HTMLAttributes,
PropsWithoutRef,
SyntheticEvent,
} from 'react';
import { cn } from '@/lib/utils';
import { Check, ChevronLeft, ChevronsUpDown } from 'lucide-react';
import useRuleGroupEditor from '@/features/orgs/projects/database/dataGrid/components/RuleGroupEditor/useRuleGroupEditor';
import { CommandLoading } from 'cmdk';
import type { ForwardedRef } from 'react';
import { forwardRef, useEffect, useState } from 'react';
import { twMerge } from 'tailwind-merge';
import type { UseAsyncValueOptions } from './useAsyncValue';
import useAsyncValue from './useAsyncValue';
import type { UseColumnGroupsOptions } from './useColumnGroups';
import useColumnGroups from './useColumnGroups';
export interface ColumnAutocompleteProps
extends Omit<PropsWithoutRef<InputProps>, 'onChange'> {
export interface ColumnAutocompleteProps extends Omit<ButtonProps, 'onChange'> {
value?: string;
/**
* Schema where the `table` is located.
*/
@@ -45,70 +39,39 @@ export interface ColumnAutocompleteProps
/**
* Function to be called when the value changes.
*/
onChange?: (
event: SyntheticEvent,
value: {
value: string;
columnMetadata?: Record<string, any>;
disableReset?: boolean;
},
) => void;
onChange?: (value: {
value: string;
columnMetadata?: Record<string, any>;
disableReset?: boolean;
}) => void;
/**
* Function to be called when the input is asynchronously initialized.
*/
onInitialized?: UseAsyncValueOptions['onInitialized'];
/**
* Class name to be applied to the root element.
*/
rootClassName?: string;
/**
* Determines if the autocomplete should allow relationships.
*/
disableRelationships?: UseColumnGroupsOptions['disableRelationships'];
}
function renderGroup(params: AutocompleteRenderGroupParams) {
return (
<li key={params.key}>
<OptionGroupBase>{params.group}</OptionGroupBase>
<List>{params.children}</List>
</li>
);
}
function renderOption(
option: AutocompleteOption<string>,
optionProps: HTMLAttributes<HTMLLIElement>,
) {
return (
<OptionBase
{...optionProps}
className="grid grid-flow-col items-baseline justify-start justify-items-start gap-1.5"
>
<Text component="span">{option.label}</Text>
{option.group === 'columns' && (
<InlineCode>{option.metadata?.udt_name || option.value}</InlineCode>
)}
</OptionBase>
);
}
function ColumnAutocomplete(
{
rootClassName,
schema: defaultSchema,
table: defaultTable,
value: externalValue,
disableRelationships,
onChange,
onInitialized,
...props
}: ColumnAutocompleteProps,
ref: ForwardedRef<HTMLInputElement>,
ref: ForwardedRef<HTMLButtonElement>,
) {
const [open, setOpen] = useState(false);
const [value, setValue] = useState('');
const { disabled } = useRuleGroupEditor();
const [search, setSearch] = useState('');
const [activeRelationship, setActiveRelationship] = useState<{
schema: string;
table: string;
@@ -120,7 +83,6 @@ function ColumnAutocomplete(
const {
data: tableData,
status: tableStatus,
error: tableError,
isFetching: isTableFetching,
} = useTableQuery([`default.${selectedSchema}.${selectedTable}`], {
schema: selectedSchema,
@@ -132,7 +94,6 @@ function ColumnAutocomplete(
const {
data: metadata,
status: metadataStatus,
error: metadataError,
isFetching: isMetadataFetching,
} = useMetadataQuery([`default.metadata`], {
queryOptions: { refetchOnWindowFocus: false },
@@ -140,8 +101,6 @@ function ColumnAutocomplete(
const {
initialized,
inputValue,
setInputValue,
selectedColumn,
setSelectedColumn,
selectedRelationships,
@@ -159,57 +118,20 @@ function ColumnAutocomplete(
onInitialized,
});
const [pages, setPages] = useState<string[]>([]);
useEffect(() => {
setPages(
relationshipDotNotation ? [relationshipDotNotation?.split('.')[0]] : [],
);
}, [relationshipDotNotation]);
const activePage = pages[pages.length - 1];
useEffect(() => {
setActiveRelationship(asyncActiveRelationship);
}, [asyncActiveRelationship]);
function isOptionEqualToValue(
option: AutocompleteOption,
value: NonNullable<string | AutocompleteOption>,
) {
if (!value) {
return false;
}
if (typeof value === 'string') {
return option.value === value;
}
return option.value === value.value && option.custom === value.custom;
}
function handleChange(
event: SyntheticEvent,
value: NonNullable<string | AutocompleteOption>,
) {
if (typeof value === 'string' || Array.isArray(value) || !value) {
return;
}
if ('group' in value && value.group === 'columns') {
setSelectedColumn(value);
setOpen(false);
setInputValue(value.value);
onChange?.(event, {
value:
selectedRelationships.length > 0
? [relationshipDotNotation, value.value].join('.')
: value.value,
columnMetadata: value.metadata,
});
return;
}
setInputValue('');
setSelectedColumn(null);
setSelectedRelationships((currentRelationships) => [
...currentRelationships,
value.metadata?.target,
]);
}
const options = useColumnGroups({
selectedSchema,
selectedTable,
@@ -218,246 +140,214 @@ function ColumnAutocomplete(
disableRelationships,
});
const {
popupOpen,
anchorEl,
setAnchorEl,
getRootProps,
getInputLabelProps,
getInputProps,
getListboxProps,
getOptionProps,
groupedOptions,
} = useAutocomplete({
open,
inputValue,
options,
id: props?.name,
openOnFocus: !props.disabled,
disableCloseOnSelect: true,
value: selectedColumn,
onClose: () => setOpen(false),
groupBy: (option) => option.group,
isOptionEqualToValue,
onChange: handleChange,
});
const handleChange = (newValue: string) => {
const selectedOption = options.find((option) => option.value === newValue);
function handleInputValueChange(
event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) {
const { value } = event.target;
setInputValue(value);
if (!selectedOption) {
return;
}
setSelectedColumn({
value,
label: value,
metadata: selectedColumn?.metadata || {
table_schema: selectedSchema,
table_name: selectedTable,
},
});
setSelectedColumn(selectedOption);
setOpen(false);
setValue(newValue === value ? '' : newValue);
onChange?.(event, {
const valueObj = {
value:
selectedRelationships.length > 0
? [relationshipDotNotation, value].join('.')
: value,
columnMetadata: {
table_schema: selectedSchema,
table_name: selectedTable,
},
});
}
? [relationshipDotNotation, newValue].join('.')
: newValue,
columnMetadata: selectedOption.metadata,
};
onChange?.(valueObj);
};
const handleRelationshipChange = (newValue: string) => {
const selectedOption = options.find((option) => option.value === newValue);
if (!selectedOption) {
return;
}
setPages((p) => [...p, newValue]);
setSelectedColumn(null);
setSearch('');
setSelectedRelationships((currentRelationships) => [
...currentRelationships,
selectedOption.metadata?.target,
]);
};
const columns = options.filter((option) => option.group === 'columns');
const relationships = options.filter(
(option) => option.group === 'relationships',
);
const handleBackRelationship = () => {
setPages((p) => p.slice(0, -1));
setSelectedColumn(null);
setSelectedRelationships((activeRelationships) =>
activeRelationships.slice(0, -1),
);
setSearch('');
};
const buttonPrefix = relationshipDotNotation
? `${selectedTable}.${relationshipDotNotation}`
: '';
return (
<>
<div {...getRootProps()} className={rootClassName}>
<Input
{...props}
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
ref={ref}
fullWidth
slotProps={{
...(props.slotProps || {}),
label: getInputLabelProps(),
input: {
...(props.slotProps?.input || {}),
ref: setAnchorEl,
sx: [
...(Array.isArray(props.slotProps?.input?.sx)
? props.slotProps.input.sx
: [props.slotProps?.input?.sx || {}]),
{
[`& .${inputClasses.input}`]: {
backgroundColor: 'transparent',
},
},
],
},
inputRoot: {
...getInputProps(),
className: twMerge(
Boolean(selectedColumn) || Boolean(relationshipDotNotation)
? '!pl-0'
: null,
props.slotProps?.inputRoot?.className,
),
},
}}
onFocus={() => {
if (props.disabled) {
return;
}
setOpen(true);
}}
onClick={() => {
if (props.disabled) {
return;
}
setOpen(true);
}}
error={Boolean(tableError || metadataError) || props.error}
helperText={
String(tableError || metadataError || '') || props.helperText
}
onChange={handleInputValueChange}
value={inputValue}
startAdornment={
selectedColumn || relationshipDotNotation ? (
<Text
component="span"
sx={{
color: props.disabled ? 'text.disabled' : 'text.primary',
}}
className="!ml-2 flex-shrink-0 truncate lg:max-w-[200px]"
>
<Text component="span" color="disabled">
{selectedTable}
</Text>
.
{relationshipDotNotation && (
<>
<span className="hidden lg:inline">
{getTruncatedText(relationshipDotNotation, 15, 'end')}.
</span>
<span className="inline lg:hidden">
{getTruncatedText(relationshipDotNotation, 35, 'end')}.
</span>
</>
)}
</Text>
) : null
}
endAdornment={
tableStatus === 'loading' ||
metadataStatus === 'loading' ||
!initialized ? (
<ActivityIndicator className="mr-2" delay={500} />
) : null
}
/>
</div>
<AutocompletePopper
onMouseDown={(event) => event.preventDefault()}
modifiers={[{ name: 'offset', options: { offset: [0, 10] } }]}
placement="bottom-start"
open={popupOpen}
anchorEl={anchorEl}
style={{ width: anchorEl?.parentElement?.clientWidth }}
disabled={disabled}
variant="outline"
role="combobox"
aria-expanded={open}
className="justify-between"
>
{buttonPrefix ? (
<div className="flex flex-shrink-0 gap-0 truncate">
<span className="flex-shrink-0 truncate text-sm text-muted-foreground lg:max-w-[200px]">
{buttonPrefix}.
</span>
{selectedColumn?.label}
</div>
) : (
selectedColumn?.label || 'Select a column'
)}
<ChevronsUpDown className="ml-2 h-5 w-5 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent
side="bottom"
align="start"
className="max-h-[var(--radix-popover-content-available-height)] w-[var(--radix-popover-trigger-width)] p-0"
>
<Box
className={autocompleteClasses.paper}
sx={{
borderWidth: (theme) => (theme.palette.mode === 'dark' ? 1 : 0),
borderColor: (theme) =>
theme.palette.mode === 'dark' ? 'grey.400' : 'none',
<Command
onKeyDown={(e) => {
if (e.key === 'Escape' || (e.key === 'Backspace' && !search)) {
e.preventDefault();
setPages((p) => p.slice(0, -1));
setSelectedColumn(null);
setSelectedRelationships((activeRelationships) =>
activeRelationships.slice(0, -1),
);
}
}}
>
<Box
className="grid grid-flow-col items-center justify-start gap-2 border-b-1 px-3 py-2.5"
sx={{ backgroundColor: 'transparent' }}
>
{selectedRelationships.length > 0 && (
<IconButton
variant="borderless"
color="secondary"
onClick={(event) => {
event.stopPropagation();
setInputValue('');
setSelectedColumn(null);
setSelectedRelationships((activeRelationships) =>
activeRelationships.slice(0, -1),
);
}}
<CommandInput
value={search}
onValueChange={setSearch}
autoFocus
placeholder=""
prefix={
relationshipDotNotation
? `
${selectedTable}.${relationshipDotNotation}.`
: ``
}
/>
{pages?.length > 0 ? (
<div className="flex flex-row items-center gap-2 px-2 py-1.5">
<Button
variant="outline"
size="icon"
className="h-8 w-8"
onClick={handleBackRelationship}
>
<ArrowLeftIcon className="h-4 w-4" />
</IconButton>
)}
<Text className="direction-rtl truncate text-left">
<Text component="span" color="disabled">
{defaultTable}
</Text>
{relationshipDotNotation && (
<>
<span className="hidden lg:inline">
.{getTruncatedText(relationshipDotNotation, 20, 'start')}
</span>
<span className="inline lg:hidden">
.{relationshipDotNotation}
</span>
</>
)}
</Text>
</Box>
{(tableStatus === 'loading' ||
metadataStatus === 'loading' ||
!initialized) && (
<div className="p-2">
<ActivityIndicator label="Loading..." />
<ChevronLeft className="h-5 w-5" />
</Button>
<span className="py-1.5 text-sm text-muted-foreground">
{defaultTable}.{pages.join('.')}
</span>
</div>
)}
{groupedOptions.length > 0 && (
<List
{...getListboxProps()}
className={autocompleteClasses.listbox}
>
{(
groupedOptions as AutocompleteGroupedOption<
(typeof options)[number]
>[]
).map((optionGroup) =>
renderGroup({
key: `${optionGroup.key}`,
group: optionGroup.group,
children: optionGroup.options.map((option, index) =>
renderOption(
option,
getOptionProps({
option,
index: optionGroup.index + index,
}),
),
),
}),
)}
</List>
)}
{groupedOptions.length === 0 && Boolean(anchorEl) && (
<Text className={autocompleteClasses.noOptions}>No options</Text>
)}
</Box>
</AutocompletePopper>
</>
) : null}
<CommandList>
{!activePage && (
<>
<CommandEmpty>No options found.</CommandEmpty>
{tableStatus === 'loading' ||
metadataStatus === 'loading' ||
(!initialized && <CommandLoading>Loading...</CommandLoading>)}
<CommandGroup heading="columns">
{columns.map((option) => (
<CommandItem
key={option.value}
value={option.value}
onSelect={handleChange}
className="overflow-x-hidden"
>
<Check
className={cn(
'mr-2 h-4 w-4',
value === option.value ? 'opacity-100' : 'opacity-0',
)}
/>
<div className="flex gap-3">
<span className="line-clamp-2 break-all">
{option.label}
</span>
<div className="flex items-center">
<code className="relative rounded bg-primary px-[0.2rem] font-mono text-white">
{option.metadata?.udt_name || option.value}
</code>
</div>
</div>
</CommandItem>
))}
</CommandGroup>
{relationships.length > 0 && !disableRelationships && (
<CommandGroup heading="relationships">
{relationships.map((option) => (
<CommandItem
key={option.value}
value={option.value}
onSelect={handleRelationshipChange}
>
{option.label}
</CommandItem>
))}
</CommandGroup>
)}
</>
)}
{activePage && (
<>
<CommandEmpty>No options found.</CommandEmpty>
<CommandGroup heading="columns">
{columns.map((option) => (
<CommandItem
key={option.value}
value={option.value}
onSelect={handleChange}
>
<Check
className={cn(
'mr-2 h-4 w-4',
value === option.value ? 'opacity-100' : 'opacity-0',
)}
/>
<div className="flex gap-3">
<span className="line-clamp-2 break-all">
{option.label}
</span>
<div className="flex items-center">
<code className="relative rounded bg-primary px-[0.2rem] font-mono text-white">
{option.metadata?.udt_name || option.value}
</code>
</div>
</div>
</CommandItem>
))}
</CommandGroup>
</>
)}
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View File

@@ -53,7 +53,6 @@ export default function useAsyncValue({
onInitialized,
}: UseAsyncValueOptions) {
const currentTablePath = `${selectedSchema}.${selectedTable}`;
const [inputValue, setInputValue] = useState('');
const [initialized, setInitialized] = useState(false);
// We might not going to have the most up-to-date table data because the
// relationship is loaded asynchronously, so we need to make sure we don't
@@ -131,7 +130,6 @@ export default function useAsyncValue({
),
});
setRemainingColumnPath((columnPath) => columnPath.slice(1));
setInputValue(activeColumn);
}, [
remainingColumnPath,
isTableLoading,
@@ -287,8 +285,6 @@ export default function useAsyncValue({
return {
initialized,
inputValue,
setInputValue,
activeRelationship,
selectedRelationships: initialized ? selectedRelationships : [],
selectedColumn: initialized ? selectedColumn : null,

View File

@@ -1,10 +1,4 @@
import { useDialog } from '@/components/common/DialogProvider';
import type { DataGridProps } from '@/components/dataGrid/DataGrid';
import { DataGrid } from '@/components/dataGrid/DataGrid';
import { DataGridBooleanCell } from '@/components/dataGrid/DataGridBooleanCell';
import { DataGridDateCell } from '@/components/dataGrid/DataGridDateCell';
import { DataGridNumericCell } from '@/components/dataGrid/DataGridNumericCell';
import { DataGridTextCell } from '@/components/dataGrid/DataGridTextCell';
import { FormActivityIndicator } from '@/components/form/FormActivityIndicator';
import { InlineCode } from '@/components/presentational/InlineCode';
import { KeyIcon } from '@/components/ui/v2/icons/KeyIcon';
@@ -23,11 +17,19 @@ import { normalizeDefaultValue } from '@/features/orgs/projects/database/dataGri
import {
POSTGRESQL_CHARACTER_TYPES,
POSTGRESQL_DATE_TIME_TYPES,
POSTGRESQL_DECIMAL_TYPES,
POSTGRESQL_INTEGER_TYPES,
POSTGRESQL_JSON_TYPES,
POSTGRESQL_NUMERIC_TYPES,
} from '@/features/orgs/projects/database/dataGrid/utils/postgresqlConstants';
import { isSchemaLocked } from '@/features/orgs/projects/database/dataGrid/utils/schemaHelpers';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import type { DataGridProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid';
import { DataGrid } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid';
import { DataGridBooleanCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridBooleanCell';
import { DataGridDateCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridDateCell';
import { DataGridDecimalCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridDecimalCell';
import { DataGridIntegerCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridIntegerCell';
import { DataGridTextCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridTextCell';
import { useQueryClient } from '@tanstack/react-query';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/router';
@@ -68,10 +70,10 @@ export function createDataGridColumn(
const defaultColumnConfiguration = {
Header: () => (
<div className="grid items-center justify-start grid-flow-col gap-1 font-normal">
<div className="grid grid-flow-col items-center justify-start gap-1 font-normal">
{column.is_primary && <KeyIcon className="text-sm" />}
<span className="font-bold truncate" title={column.column_name}>
<span className="truncate font-bold" title={column.column_name}>
{column.column_name}
</span>
@@ -104,12 +106,21 @@ export function createDataGridColumn(
foreignKeyRelation: column.foreign_key_relation,
};
if (POSTGRESQL_NUMERIC_TYPES.includes(column.data_type)) {
if (POSTGRESQL_INTEGER_TYPES.includes(column.data_type)) {
return {
...defaultColumnConfiguration,
type: 'number',
width: 200,
Cell: DataGridNumericCell,
Cell: DataGridIntegerCell,
};
}
if (POSTGRESQL_DECIMAL_TYPES.includes(column.data_type)) {
return {
...defaultColumnConfiguration,
type: 'text',
width: 200,
Cell: DataGridDecimalCell,
};
}

View File

@@ -209,7 +209,6 @@ export default function DatabaseRecordInputGroup({
autoFocus={index === 0 && autoFocusFirstInput}
slotProps={{
label: commonLabelProps,
inputRoot: { step: 1 },
}}
/>
);

View File

@@ -326,10 +326,10 @@ export default function RolePermissionEditorForm({
return (
<FormProvider {...form}>
{error && error instanceof Error && (
<div className="px-6 mb-4 -mt-3">
<div className="-mt-3 mb-4 px-6">
<Alert
severity="error"
className="grid items-center justify-between grid-flow-col px-4 py-3"
className="grid grid-flow-col items-center justify-between px-4 py-3"
>
<span className="text-left">
<strong>Error:</strong> {error.message}
@@ -349,13 +349,13 @@ export default function RolePermissionEditorForm({
<Form
onSubmit={handleSubmit}
className="flex flex-col content-between flex-auto overflow-hidden border-t-1"
className="flex flex-auto flex-col content-between overflow-hidden border-t-1"
sx={{ backgroundColor: 'background.default' }}
>
<div className="grid content-start flex-auto grid-flow-row gap-6 py-4 overflow-auto">
<div className="grid flex-auto grid-flow-row content-start gap-6 overflow-auto py-4">
<PermissionSettingsSection
title="Selected role & action"
className="justify-between grid-flow-col"
className="grid-flow-col justify-between"
>
<div className="grid grid-flow-col gap-4">
<Text>
@@ -408,7 +408,7 @@ export default function RolePermissionEditorForm({
{action !== 'select' && <BackendOnlySection disabled={disabled} />}
</div>
<Box className="grid flex-shrink-0 gap-2 p-2 border-t-1 sm:grid-flow-col sm:justify-between">
<Box className="grid flex-shrink-0 gap-2 border-t-1 p-2 sm:grid-flow-col sm:justify-between">
<Button
variant="borderless"
color="secondary"

View File

@@ -0,0 +1,135 @@
import { Check, ChevronsUpDown } from 'lucide-react';
import { Button } from '@/components/ui/v3/button';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/v3/command';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/v3/popover';
import type { HasuraOperator } from '@/features/database/dataGrid/types/dataBrowser';
import { cn } from '@/lib/utils';
import { useState } from 'react';
import { useFormContext } from 'react-hook-form';
const commonOperators: {
value: HasuraOperator;
label?: string;
helperText?: string;
}[] = [
{ value: '_eq', helperText: 'equal' },
{ value: '_neq', helperText: 'not equal' },
{ value: '_in', helperText: 'in (array)' },
{ value: '_nin', helperText: 'not in (array)' },
{ value: '_gt', helperText: 'greater than' },
{ value: '_lt', helperText: 'lower than' },
{ value: '_gte', helperText: 'greater than or equal' },
{ value: '_lte', helperText: 'lower than or equal' },
{ value: '_ceq', helperText: 'equal to column' },
{ value: '_cne', helperText: 'not equal to column' },
{ value: '_cgt', helperText: 'greater than column' },
{ value: '_clt', helperText: 'lower than column' },
{ value: '_cgte', helperText: 'greater than or equal to column' },
{ value: '_clte', helperText: 'lower than or equal to column' },
{ value: '_is_null', helperText: 'null' },
];
const textSpecificOperators: typeof commonOperators = [
{ value: '_like', helperText: 'like' },
{ value: '_nlike', helperText: 'not like' },
{ value: '_ilike', helperText: 'like (case-insensitive)' },
{ value: '_nilike', helperText: 'not like (case-insensitive)' },
{ value: '_similar', helperText: 'similar' },
{ value: '_nsimilar', helperText: 'not similar' },
{ value: '_regex', helperText: 'matches regex' },
{ value: '_nregex', helperText: `doesn't match regex` },
{ value: '_iregex', helperText: 'matches case-insensitive regex' },
{ value: '_niregex', helperText: `doesn't match case-insensitive regex` },
];
interface OperatorComboBoxProps {
name: string;
disabled?: boolean;
selectedColumnType?: string;
}
export default function OperatorComboBox({
name,
disabled,
selectedColumnType,
}: OperatorComboBoxProps) {
const [open, setOpen] = useState(false);
const { watch, setValue } = useFormContext();
const operator = watch(`${name}.operator`);
const availableOperators = [
...commonOperators,
...(selectedColumnType === 'text' ? textSpecificOperators : []),
];
const handleSelect = (value: string) => {
if (['_in', '_nin'].includes(value)) {
setValue(`${name}.value`, [], { shouldDirty: true });
}
setValue(`${name}.operator`, value, { shouldDirty: true });
setOpen(false);
};
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
disabled={disabled}
variant="outline"
role="combobox"
aria-expanded={open}
className="justify-between"
>
{operator ?? 'Select operator...'}
<ChevronsUpDown className="h-5 w-5 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent side="bottom" align="start" className="p-0">
<Command>
<CommandInput placeholder="Search operator..." />
<CommandList>
<CommandEmpty>No operator found.</CommandEmpty>
<CommandGroup>
{availableOperators.map((op) => (
<CommandItem
key={op.value}
keywords={[op.helperText]}
value={op.value}
onSelect={handleSelect}
className="flex flex-row justify-between"
>
<div className="flex flex-row gap-2">
<span className="min-w-[9ch]">{op.value}</span>
<span className="text-muted-foreground">
{op.helperText}
</span>
</div>
<Check
className={cn(
'ml-auto',
op.value === operator ? 'opacity-100' : 'opacity-0',
)}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View File

@@ -1,12 +1,9 @@
import { ControlledSelect } from '@/components/form/ControlledSelect';
import { Option } from '@/components/ui/v2/Option';
import { Text } from '@/components/ui/v2/Text';
import { ColumnAutocomplete } from '@/features/orgs/projects/database/dataGrid/components/ColumnAutocomplete';
import type { HasuraOperator } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
import type { DetailedHTMLProps, HTMLProps } from 'react';
import { useState } from 'react';
import { useController, useFormContext } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import OperatorComboBox from './OperatorComboBox';
import RuleRemoveButton from './RuleRemoveButton';
import RuleValueInput from './RuleValueInput';
import useRuleGroupEditor from './useRuleGroupEditor';
@@ -25,69 +22,6 @@ export interface RuleEditorRowProps
* Function to be called when the remove button is clicked.
*/
onRemove?: VoidFunction;
/**
* List of operators to be disabled for the rule editor.
*
* @default []
*/
disabledOperators?: HasuraOperator[];
}
const commonOperators: {
value: HasuraOperator;
label?: string;
helperText?: string;
}[] = [
{ value: '_eq', helperText: 'equal' },
{ value: '_neq', helperText: 'not equal' },
{ value: '_in_hasura', label: '_in', helperText: 'in (X-Hasura-)' },
{ value: '_in', helperText: 'in (array)' },
{ value: '_nin_hasura', label: '_nin', helperText: 'not in (X-Hasura-)' },
{ value: '_nin', helperText: 'not in (array)' },
{ value: '_gt', helperText: 'greater than' },
{ value: '_lt', helperText: 'lower than' },
{ value: '_gte', helperText: 'greater than or equal' },
{ value: '_lte', helperText: 'lower than or equal' },
{ value: '_ceq', helperText: 'equal to column' },
{ value: '_cne', helperText: 'not equal to column' },
{ value: '_cgt', helperText: 'greater than column' },
{ value: '_clt', helperText: 'lower than column' },
{ value: '_cgte', helperText: 'greater than or equal to column' },
{ value: '_clte', helperText: 'lower than or equal to column' },
{ value: '_is_null', helperText: 'null' },
];
const textSpecificOperators: typeof commonOperators = [
{ value: '_like', helperText: 'like' },
{ value: '_nlike', helperText: 'not like' },
{ value: '_ilike', helperText: 'like (case-insensitive)' },
{ value: '_nilike', helperText: 'not like (case-insensitive)' },
{ value: '_similar', helperText: 'similar' },
{ value: '_nsimilar', helperText: 'not similar' },
{ value: '_regex', helperText: 'matches regex' },
{ value: '_nregex', helperText: `doesn't match regex` },
{ value: '_iregex', helperText: 'matches case-insensitive regex' },
{ value: '_niregex', helperText: `doesn't match case-insensitive regex` },
];
function renderOption({
value,
label,
helperText,
}: (typeof commonOperators)[number]) {
return (
<Option key={value} value={value} className="grid grid-flow-col gap-2">
<Text component="span" className="inline-block w-16">
{label || value}
</Text>
{helperText && (
<Text component="span" color="disabled">
{helperText}
</Text>
)}
</Option>
);
}
export default function RuleEditorRow({
@@ -95,17 +29,12 @@ export default function RuleEditorRow({
index,
onRemove,
className,
disabledOperators = [],
...props
}: RuleEditorRowProps) {
const { schema, table, disabled } = useRuleGroupEditor();
const { control, setValue, getFieldState } = useFormContext();
const { schema, table } = useRuleGroupEditor();
const { control, setValue } = useFormContext();
const rowName = `${name}.rules.${index}`;
const columnState = getFieldState(`${rowName}.column`);
const operatorState = getFieldState(`${rowName}.operator`);
const valueState = getFieldState(`${rowName}.value`);
const [selectedTablePath, setSelectedTablePath] = useState<string>('');
const [selectedColumnType, setSelectedColumnType] = useState<string>('');
const { field: autocompleteField } = useController({
@@ -113,48 +42,19 @@ export default function RuleEditorRow({
control,
});
const disabledOperatorMap = disabledOperators.reduce(
(map, currentOperator) => map.set(currentOperator, true),
new Map<string, boolean>(),
);
const availableOperators = [
...commonOperators.filter(({ value }) => !disabledOperatorMap.has(value)),
...(selectedColumnType === 'text'
? textSpecificOperators.filter(
({ value }) => !disabledOperatorMap.get(value),
)
: []),
];
return (
<div
className={twMerge(
'grid grid-flow-row space-y-1 lg:max-h-10 lg:grid-cols-[320px_140px_minmax(100px,_1fr)_40px] lg:space-y-0',
'flex flex-col gap-1 space-y-1 overflow-x-hidden pb-4 xl:grid xl:grid-flow-row xl:grid-cols-[320px_140px_minmax(100px,_1fr)_40px] xl:space-y-0 xl:overflow-x-visible',
className,
)}
{...props}
>
<ColumnAutocomplete
{...autocompleteField}
disabled={disabled}
schema={schema}
table={table}
rootClassName="h-10"
slotProps={{
input: {
className: 'lg:!rounded-r-none',
sx: !disabled
? {
backgroundColor: (theme) =>
theme.palette.mode === 'dark' ? 'grey.300' : 'common.white',
}
: undefined,
},
}}
fullWidth
error={Boolean(columnState?.error?.message)}
onChange={(_event, { value, columnMetadata, disableReset }) => {
onChange={({ value, columnMetadata, disableReset }) => {
setSelectedTablePath(
`${columnMetadata.table_schema}.${columnMetadata.table_name}`,
);
@@ -182,69 +82,21 @@ export default function RuleEditorRow({
});
}}
/>
<ControlledSelect
disabled={disabled}
name={`${rowName}.operator`}
className="h-10"
slotProps={{
root: {
className: 'lg:!rounded-none',
sx: !disabled
? {
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? `${theme.palette.grey[300]} !important`
: `${theme.palette.common.white} !important`,
}
: {},
},
listbox: { className: 'max-h-[300px]' },
popper: { disablePortal: false, className: 'z-[10000]' },
}}
fullWidth
error={Boolean(operatorState?.error?.message)}
onChange={(_event, value: HasuraOperator) => {
if (!['_in', '_nin', '_in_hasura', '_nin_hasura'].includes(value)) {
return;
}
if (value === '_in_hasura' || value === '_nin_hasura') {
setValue(`${rowName}.value`, null, {
shouldDirty: true,
});
return;
}
setValue(`${rowName}.value`, [], { shouldDirty: true });
}}
renderValue={(option) => {
if (!option?.value) {
return <span />;
}
if (option.value === '_in_hasura') {
return <span>_in</span>;
}
if (option.value === '_nin_hasura') {
return <span>_nin</span>;
}
return <span>{option.value}</span>;
}}
>
{availableOperators.map(renderOption)}
</ControlledSelect>
<OperatorComboBox
name={rowName}
selectedColumnType={selectedColumnType}
/>
<RuleValueInput
selectedTablePath={selectedTablePath}
name={rowName}
error={Boolean(valueState?.error?.message)}
className="min-h-10"
/>
<RuleRemoveButton onRemove={onRemove} name={name} disabled={disabled} />
<RuleRemoveButton
className="w-full xl:w-auto"
onRemove={onRemove}
name={name}
/>
</div>
);
}

View File

@@ -1,9 +1,14 @@
import { ControlledSelect } from '@/components/form/ControlledSelect';
import { Option } from '@/components/ui/v2/Option';
import { Text } from '@/components/ui/v2/Text';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/v3/select';
import type { RuleGroup } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
import type { DetailedHTMLProps, HTMLProps } from 'react';
import { useWatch } from 'react-hook-form';
import { useFormContext, useWatch } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import useRuleGroupEditor from './useRuleGroupEditor';
@@ -32,9 +37,11 @@ export default function RuleGroupControls({
...props
}: RuleGroupControlsProps) {
const { disabled } = useRuleGroupEditor();
const inputName = `${name}.operator`;
const currentOperator: RuleGroup['operator'] = useWatch({
name: `${name}.operator`,
name: inputName,
});
const { setValue } = useFormContext();
return (
<div
@@ -42,24 +49,26 @@ export default function RuleGroupControls({
{...props}
>
{showSelect ? (
<ControlledSelect
<Select
disabled={disabled}
name={`${name}.operator`}
slotProps={{
root: {
sx: {
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? `${theme.palette.grey[300]} !important`
: `${theme.palette.common.white} !important`,
},
},
name={inputName}
onValueChange={(newValue: string) => {
setValue(inputName, newValue, { shouldDirty: true });
}}
fullWidth
defaultValue={currentOperator}
>
<Option value="_and">and</Option>
<Option value="_or">or</Option>
</ControlledSelect>
<SelectTrigger className="border hover:bg-accent hover:text-accent-foreground focus:ring-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="_and">
<span className="font-medium">and</span>
</SelectItem>
<SelectItem value="_or">
<span className="font-medium">or</span>
</SelectItem>
</SelectContent>
</Select>
) : (
<Text className="p-2 !font-medium">
{operatorDictionary[currentOperator]}

View File

@@ -89,9 +89,3 @@ const Template: ComponentStory<typeof RuleGroupEditor> = function Template(
export const Default = Template.bind({});
Default.args = {};
Default.parameters = defaultParameters;
export const DisabledOperators = Template.bind({});
DisabledOperators.args = {
disabledOperators: ['_in_hasura', '_nin_hasura', '_is_null'],
};
DisabledOperators.parameters = defaultParameters;

View File

@@ -14,14 +14,11 @@ import { generateAppServiceUrl } from '@/features/projects/common/utils/generate
import { useMemo } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import type { RuleEditorRowProps } from './RuleEditorRow';
import RuleEditorRow from './RuleEditorRow';
import RuleGroupControls from './RuleGroupControls';
import { RuleGroupEditorContext } from './useRuleGroupEditor';
export interface RuleGroupEditorProps
extends BoxProps,
Pick<RuleEditorRowProps, 'disabledOperators'> {
export interface RuleGroupEditorProps extends BoxProps {
/**
* Determines whether or not the rule group editor is disabled.
*/
@@ -63,7 +60,6 @@ export default function RuleGroupEditor({
name,
className,
disableRemove,
disabledOperators = [],
depth = 0,
maxDepth,
schema,
@@ -115,7 +111,7 @@ export default function RuleGroupEditor({
<Box
{...props}
className={twMerge(
'rounded-lg border border-r-8 border-transparent pl-2',
'flex min-h-44 flex-col justify-between rounded-lg border border-r-8 border-transparent pl-2',
className,
)}
sx={[
@@ -147,7 +143,6 @@ export default function RuleGroupEditor({
name={name}
index={ruleIndex}
onRemove={() => removeRule(ruleIndex)}
disabledOperators={disabledOperators}
/>
</div>
))}
@@ -177,7 +172,6 @@ export default function RuleGroupEditor({
table={table}
onRemove={() => removeGroup(ruleGroupIndex)}
disableRemove={rules.length === 0 && groups.length === 1}
disabledOperators={disabledOperators}
name={`${name}.groups.${ruleGroupIndex}`}
depth={depth + 1}
disabled={disabled}
@@ -247,7 +241,7 @@ export default function RuleGroupEditor({
{onRemove && (
<Button
variant="borderless"
color="secondary"
color="error"
onClick={onRemove}
disabled={disableRemove}
>

View File

@@ -1,10 +1,9 @@
import type { ButtonProps } from '@/components/ui/v2/Button';
import { Button } from '@/components/ui/v2/Button';
import { XIcon } from '@/components/ui/v2/icons/XIcon';
import { Button, type ButtonProps } from '@/components/ui/v3/button';
import type {
Rule,
RuleGroup,
} from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
import { X } from 'lucide-react';
import { useWatch } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
@@ -34,9 +33,9 @@ function RuleRemoveButton({
return (
<Button
variant="outlined"
color="secondary"
className={twMerge('h-10 !min-w-0 lg:!rounded-l-none', className)}
variant="outline"
size="icon"
className={twMerge('h-10 !min-w-0', className)}
disabled={
disabled ||
(rules.length === 1 && !groups?.length && !unsupported?.length)
@@ -44,18 +43,8 @@ function RuleRemoveButton({
{...props}
aria-label="Remove Rule"
onClick={onRemove}
sx={
!disabled
? {
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? `${theme.palette.grey[300]} !important`
: `${theme.palette.common.white} !important`,
}
: undefined
}
>
<XIcon className="!h-4 !w-4" />
<X className="h-4 w-4" />
</Button>
);
}

View File

@@ -1,17 +1,41 @@
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { ControlledSelect } from '@/components/form/ControlledSelect';
import { ReadOnlyToggle } from '@/components/presentational/ReadOnlyToggle';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import type { AutocompleteOption } from '@/components/ui/v2/Autocomplete';
import type { InputProps } from '@/components/ui/v2/Input';
import { inputClasses } from '@/components/ui/v2/Input';
import { Option } from '@/components/ui/v2/Option';
import type { ColumnAutocompleteProps } from '@/features/orgs/projects/database/dataGrid/components/ColumnAutocomplete';
import { ColumnAutocomplete } from '@/features/orgs/projects/database/dataGrid/components/ColumnAutocomplete';
import { Check, ChevronsUpDown } from 'lucide-react';
import { Button } from '@/components/ui/v3/button';
import {
Command,
CommandCreateItem,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/v3/command';
import { FancyMultiSelect } from '@/components/ui/v3/fancy-multi-select';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/v3/popover';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/v3/select';
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import {
ColumnAutocomplete,
type ColumnAutocompleteProps,
} from '@/features/orgs/projects/database/dataGrid/components/ColumnAutocomplete';
import type { HasuraOperator } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimirClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables';
import { cn } from '@/lib/utils';
import { useGetRolesPermissionsQuery } from '@/utils/__generated__/graphql';
import { CommandLoading } from 'cmdk';
import { useState } from 'react';
import { useController, useFormContext, useWatch } from 'react-hook-form';
import useRuleGroupEditor from './useRuleGroupEditor';
@@ -41,23 +65,7 @@ function ColumnSelectorInput({
schema={schema}
table={table}
disableRelationships
slotProps={{
input: {
className: 'lg:!rounded-none !z-10',
sx: !disabled
? {
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? theme.palette.grey[300]
: theme.palette.common.white,
[`& .${inputClasses.input}`]: {
backgroundColor: 'transparent',
},
}
: undefined,
},
}}
onChange={(_event, { value }) => {
onChange={({ value }) => {
if (selectedTablePath === `${schema}.${table}`) {
setValue(name, [value], { shouldDirty: true });
return;
@@ -75,113 +83,92 @@ export interface RuleValueInputProps {
* Name of the parent group editor.
*/
name: string;
/**
* Class name to apply to the input wrapper.
*/
className?: string;
/**
* Path of the table selected through the column input.
*/
selectedTablePath?: string;
/**
* Whether the input should be marked as invalid.
*/
error?: InputProps['error'];
/**
* Helper text to display below the input.
*/
helperText?: InputProps['helperText'];
}
export default function RuleValueInput({
name,
selectedTablePath,
error,
helperText,
className,
}: RuleValueInputProps) {
const { schema, table, disabled } = useRuleGroupEditor();
const { currentProject } = useCurrentWorkspaceAndProject();
const { setValue } = useFormContext();
const { project } = useProject();
const { setValue, control } = useFormContext();
const inputName = `${name}.value`;
const operator: HasuraOperator = useWatch({ name: `${name}.operator` });
const isHasuraInput = operator === '_in_hasura' || operator === '_nin_hasura';
const sharedInputSx: InputProps['sx'] = !disabled
? {
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? theme.palette.grey[300]
: theme.palette.common.white,
[`& .${inputClasses.input}`]: {
backgroundColor: 'transparent',
},
}
: undefined;
const { field } = useController({
name: inputName,
control,
});
const {
data,
loading,
error: customClaimsError,
} = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id },
skip: !isHasuraInput || !currentProject?.id,
const [open, setOpen] = useState(false);
const comboboxValue = useWatch({ name: inputName });
const operator: HasuraOperator = useWatch({ name: `${name}.operator` });
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { data, loading } = useGetRolesPermissionsQuery({
variables: { appId: project?.id },
skip: !project?.id,
...(!isPlatform ? { client: localMimirClient } : {}),
});
if (operator === '_is_null') {
const defaultValue = !Array.isArray(comboboxValue) ? comboboxValue : null;
return (
<ControlledSelect
<Select
disabled={disabled}
name={inputName}
fullWidth
slotProps={{
root: {
className: 'lg:!rounded-none h-10',
sx: !disabled
? {
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? `${theme.palette.grey[300]} !important`
: `${theme.palette.common.white} !important`,
}
: null,
},
popper: { disablePortal: false, className: 'z-[10000]' },
onValueChange={(newValue: string) => {
setValue(inputName, newValue, { shouldDirty: true });
}}
error={error}
helperText={helperText}
defaultValue={defaultValue}
>
<Option value="true">
<ReadOnlyToggle
checked
slotProps={{ label: { className: '!text-sm' } }}
/>
</Option>
<Option value="false">
<ReadOnlyToggle
checked={false}
slotProps={{ label: { className: '!text-sm' } }}
/>
</Option>
</ControlledSelect>
<SelectTrigger className="border hover:bg-accent hover:text-accent-foreground focus:ring-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
<SelectValue placeholder="Is null?" />
</SelectTrigger>
<SelectContent>
<SelectItem value="true">
<span className="font-medium">true</span>
</SelectItem>
<SelectItem value="false">
<span className="font-medium">false</span>
</SelectItem>
</SelectContent>
</Select>
);
}
const availableHasuraPermissionVariables = getAllPermissionVariables(
data?.config?.auth?.session?.accessToken?.customClaims,
).map(({ key }) => ({
value: `X-Hasura-${key}`,
label: `X-Hasura-${key}`,
group: 'Frequently used',
}));
if (operator === '_in' || operator === '_nin') {
const defaultValue = Array.isArray(field.value) ? field.value : [];
return (
<ControlledAutocomplete
disabled={disabled}
name={inputName}
multiple
freeSolo
limitTags={3}
slotProps={{
input: {
className: 'lg:!rounded-none !z-10',
sx: sharedInputSx,
},
paper: { className: 'hidden' },
<FancyMultiSelect
className={className}
options={availableHasuraPermissionVariables}
creatable
defaultValue={defaultValue.map((v) => ({ value: v, label: v }))}
onChange={(value) => {
setValue(
inputName,
value.map((v) => v.value),
{ shouldDirty: true },
);
}}
options={[]}
fullWidth
filterSelectedOptions
error={error}
helperText={helperText}
/>
);
}
@@ -194,71 +181,70 @@ export default function RuleValueInput({
schema={schema}
table={table}
name={inputName}
error={error}
helperText={helperText}
/>
);
}
const availableHasuraPermissionVariables = getAllPermissionVariables(
data?.config?.auth?.session?.accessToken?.customClaims,
).map(({ key }) => ({
value: `X-Hasura-${key}`,
label: `X-Hasura-${key}`,
group: 'Frequently used',
}));
const selectedVariable = availableHasuraPermissionVariables.find(
(variable) => variable.value === comboboxValue,
);
const comboboxLabel =
selectedVariable?.label || comboboxValue || 'Select variable...';
return (
<ControlledAutocomplete
disabled={disabled}
freeSolo={!isHasuraInput}
autoHighlight={isHasuraInput}
isOptionEqualToValue={(
option,
value: string | number | AutocompleteOption<string>,
) => {
if (typeof value !== 'object') {
return option.value.toLowerCase() === value?.toString().toLowerCase();
}
return option.value.toLowerCase() === value.value.toLowerCase();
}}
name={inputName}
groupBy={(option) => option.group}
slotProps={{
input: {
className: 'lg:!rounded-none',
sx: sharedInputSx,
},
formControl: { className: '!bg-transparent' },
paper: { className: 'empty:border-transparent' },
}}
fullWidth
loading={loading}
loadingText={<ActivityIndicator label="Loading..." />}
error={Boolean(customClaimsError) || error}
helperText={customClaimsError?.message || helperText}
options={
isHasuraInput
? availableHasuraPermissionVariables
: [
{
value: 'X-Hasura-User-Id',
label: 'X-Hasura-User-Id',
group: 'Frequently used',
},
]
}
onChange={(_event, _value, reason, details) => {
if (
reason !== 'selectOption' &&
details.option.value !== 'X-Hasura-User-Id'
) {
return;
}
setValue(inputName, details.option.value, { shouldDirty: true });
}}
/>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="justify-between"
>
<span className="truncate">{comboboxLabel}</span>
<ChevronsUpDown className="h-5 min-h-5 w-5 min-w-5 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent
side="bottom"
align="start"
className="max-h-[var(--radix-popover-content-available-height)] w-[var(--radix-popover-trigger-width)] p-0"
>
<Command>
<CommandInput placeholder="Choose variable..." />
<CommandList>
<CommandEmpty>No variable found.</CommandEmpty>
{loading && <CommandLoading>Loading...</CommandLoading>}
<CommandGroup>
{availableHasuraPermissionVariables.map((variable) => (
<CommandItem
key={variable.value}
value={variable.value}
onSelect={(currentValue) => {
setValue(inputName, currentValue, { shouldDirty: true });
setOpen(false);
}}
>
{variable.label}
<Check
className={cn(
'ml-auto',
comboboxValue === variable.value
? 'opacity-100'
: 'opacity-0',
)}
/>
</CommandItem>
))}
</CommandGroup>
<CommandCreateItem
onCreate={(currentValue) => {
setValue(inputName, currentValue, { shouldDirty: true });
setOpen(false);
}}
/>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View File

@@ -544,9 +544,7 @@ export type HasuraOperator =
| '_eq'
| '_neq'
| '_in'
| '_in_hasura'
| '_nin'
| '_nin_hasura'
| '_gt'
| '_lt'
| '_gte'

View File

@@ -202,36 +202,6 @@ test('should convert a complex permission to a rule group', () => {
});
});
test(`should convert an _in or _nin value that do not have an array as value to _in_hasura or _nin_hasura`, () => {
expect(
convertToRuleGroup({ title: { _in: ['X-Hasura-Allowed-Ids'] } }),
).toMatchObject({
operator: '_and',
rules: [
{
column: 'title',
operator: '_in',
value: ['X-Hasura-Allowed-Ids'],
},
],
groups: [],
});
expect(
convertToRuleGroup({ title: { _in: 'X-Hasura-Allowed-Ids' } }),
).toMatchObject({
operator: '_and',
rules: [
{
column: 'title',
operator: '_in_hasura',
value: 'X-Hasura-Allowed-Ids',
},
],
groups: [],
});
});
test('should transform operators and relations if the _not operator is being used', () => {
expect(
convertToRuleGroup({ _not: { title: { _eq: 'test' } } }),

View File

@@ -52,8 +52,6 @@ const negatedValueOperatorPairs: Record<HasuraOperator, HasuraOperator> = {
_cgte: '_clt',
_clte: '_cgt',
_is_null: '_is_null',
_in_hasura: '_nin_hasura',
_nin_hasura: '_in_hasura',
};
export default function convertToRuleGroup(
@@ -151,16 +149,14 @@ export default function convertToRuleGroup(
(currentKey === '_in' || currentKey === '_nin') &&
typeof value === 'string'
) {
const operator = currentKey === '_in' ? '_in_hasura' : '_nin_hasura';
return {
operator: '_and',
rules: [
{
column: previousKey,
operator: shouldNegate
? negatedValueOperatorPairs[operator]
: operator,
? negatedValueOperatorPairs[currentKey]
: currentKey,
value,
},
],

View File

@@ -19,20 +19,23 @@ export const POSTGRESQL_ERROR_CODES = {
*
* @docs https://www.postgresql.org/docs/current/datatype-numeric.html
*/
export const POSTGRESQL_NUMERIC_TYPES = [
export const POSTGRESQL_INTEGER_TYPES = [
'smallint',
'integer',
'bigint',
'decimal',
'numeric',
'real',
'double precision',
'smallserial',
'serial',
'bigserial',
'oid',
];
export const POSTGRESQL_DECIMAL_TYPES = [
'decimal',
'numeric',
'real',
'double precision',
];
/**
* Character data types in PostgreSQL.
*

View File

@@ -118,8 +118,8 @@ export default function DatabaseConnectionInfo() {
}
const postgresHost = generateAppServiceUrl(
project.subdomain,
project.region,
project?.subdomain,
project?.region,
'db',
).replace('https://', '');

View File

@@ -43,7 +43,8 @@ const validationSchema = Yup.object({
value: Yup.string().required('Major version is a required field'),
})
.label('Postgres major version')
.required(),
.required()
.test('not-zero', 'Invalid major version', (value) => value?.value !== '0'),
minorVersion: Yup.object({
label: Yup.string().required(),
value: Yup.string().required('Minor version is a required field'),
@@ -186,18 +187,29 @@ export default function DatabaseServiceVersionSettings() {
shouldPoll: true,
});
const showMigrateWarning =
Number(selectedMajor) > Number(currentPostgresMajor);
const { state } = useAppState();
const applicationUpdating =
state === ApplicationStatus.Updating ||
state === ApplicationStatus.Migrating;
const applicationLive = state === ApplicationStatus.Live;
const applicationPaused = state === ApplicationStatus.Paused;
const applicationPausing = state === ApplicationStatus.Pausing;
const showMigrateWarning =
!applicationPaused &&
!applicationPausing &&
Number(selectedMajor) > Number(currentPostgresMajor);
const applicationUnhealthy =
state !== ApplicationStatus.Live && !applicationUpdating;
!applicationLive &&
!applicationPaused &&
!applicationPausing &&
!applicationUpdating;
const isMajorVersionDirty = formState?.dirtyFields?.majorVersion;
const isMinorVersionDirty = formState?.dirtyFields?.minorVersion;
const isDirty = isMajorVersionDirty || isMinorVersionDirty;
const versionFieldsDisabled =
applicationUpdating || applicationUnhealthy || maintenanceActive;
const saveDisabled = versionFieldsDisabled || !isDirty;
@@ -208,7 +220,7 @@ export default function DatabaseServiceVersionSettings() {
const newVersion = `${formValues.majorVersion.value}.${formValues.minorVersion.value}`;
// Major version change
if (isMajorVersionDirty) {
if (isMajorVersionDirty && applicationLive) {
openDialog({
title: 'Update Postgres MAJOR version',
component: (
@@ -228,7 +240,7 @@ export default function DatabaseServiceVersionSettings() {
return;
}
// Minor version change
// Only minor version change or project is paused/pausing
const updateConfigPromise = updateConfig({
variables: {
appId: project.id,
@@ -338,7 +350,6 @@ export default function DatabaseServiceVersionSettings() {
return option.value;
}}
showCustomOption="auto"
isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => {
const inputValueLower = inputValue.toLowerCase();
const matched = [];
@@ -383,12 +394,13 @@ export default function DatabaseServiceVersionSettings() {
form.setValue('majorVersion', value);
}
}}
clearOnBlur
fullWidth
className="lg:col-span-1"
label="MAJOR"
options={availableMajorVersions}
error={!!formState.errors?.majorVersion?.value?.message}
helperText={formState.errors?.majorVersion?.value?.message}
error={!!formState.errors?.majorVersion?.message}
helperText={formState.errors?.majorVersion?.message}
customOptionLabel={(value) => `Use custom value: "${value}"`}
/>
<ControlledAutocomplete
@@ -424,12 +436,13 @@ export default function DatabaseServiceVersionSettings() {
return result;
}}
clearOnBlur
fullWidth
className="lg:col-span-2"
label="MINOR"
options={availableMinorVersions}
error={!!formState.errors?.minorVersion?.value?.message}
helperText={formState.errors?.minorVersion?.value?.message}
error={!!formState.errors?.minorVersion?.message}
helperText={formState.errors?.minorVersion?.message}
showCustomOption="auto"
customOptionLabel={(value) => `Use custom value: "${value}"`}
/>

View File

@@ -5,28 +5,41 @@ import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Alert } from '@/components/ui/v2/Alert';
import { Box } from '@/components/ui/v2/Box';
import { Input } from '@/components/ui/v2/Input';
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text';
import { UpgradeNotification } from '@/features/orgs/projects/common/components/UpgradeNotification';
import { useAppState } from '@/features/orgs/projects/common/hooks/useAppState';
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import { DatabaseStorageCapacityWarning } from '@/features/orgs/projects/database/settings/components/DatabaseStorageCapacityWarning';
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimirClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
import {
useGetPersistentVolumesEncryptedQuery,
useGetPostgresSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { ApplicationStatus } from '@/types/application';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { useEffect, useMemo } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
const validationSchema = Yup.object({
capacity: Yup.number().required().min(10),
capacity: Yup.number()
.integer('Capacity must be an integer')
.typeError('You must specify a number')
.min(1, 'Capacity must be greater than 0')
.required('Capacity is required'),
});
export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>;
export type DatabaseStorageCapacityFormValues = Yup.InferType<
typeof validationSchema
>;
export default function AuthDomain() {
export default function DatabaseStorageCapacity() {
const isPlatform = useIsPlatform();
const { org } = useCurrentOrg();
const { maintenanceActive } = useUI();
@@ -48,6 +61,15 @@ export default function AuthDomain() {
org?.plan?.featureMaxDbSize) ||
0;
const { data: encryptedVolumesData } = useGetPersistentVolumesEncryptedQuery({
variables: { appId: project?.id },
skip: !isPlatform,
});
const showEncryptionWarning = encryptedVolumesData
? !encryptedVolumesData?.systemConfig?.persistentVolumesEncrypted
: false;
const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
@@ -58,8 +80,32 @@ export default function AuthDomain() {
resolver: yupResolver(validationSchema),
});
const { formState, register, reset } = form;
const { state } = useAppState();
const applicationPause =
state === ApplicationStatus.Paused || state === ApplicationStatus.Pausing;
const { formState, register, reset, watch } = form;
const isDirty = Object.keys(formState.dirtyFields).length > 0;
const newCapacity = watch('capacity');
const decreasingSize = newCapacity < capacity;
const submitDisabled = useMemo(() => {
if (!isDirty) {
return true;
}
if (maintenanceActive) {
return true;
}
if (decreasingSize && !applicationPause) {
return true;
}
return false;
}, [isDirty, maintenanceActive, decreasingSize, applicationPause]);
useEffect(() => {
if (data && !loading) {
@@ -81,7 +127,7 @@ export default function AuthDomain() {
throw error;
}
async function handleSubmit(formValues: AuthDomainFormValues) {
async function handleSubmit(formValues: DatabaseStorageCapacityFormValues) {
await execPromiseWithErrorToast(
async () => {
await updateConfig({
@@ -120,7 +166,7 @@ export default function AuthDomain() {
description="Specify the storage capacity for your PostgreSQL database."
slotProps={{
submitButton: {
disabled: !isDirty || maintenanceActive,
disabled: submitDisabled,
loading: formState.isSubmitting,
},
}}
@@ -134,26 +180,48 @@ export default function AuthDomain() {
{...register('capacity')}
id="capacity"
name="capacity"
type="number"
type="text"
endAdornment={
<InputAdornment className="absolute right-2" position="end">
GB
</InputAdornment>
}
fullWidth
disabled={project.legacyPlan?.isFree}
className="lg:col-span-2"
error={Boolean(formState.errors.capacity?.message)}
helperText={formState.errors.capacity?.message}
slotProps={{
inputRoot: {
min: capacity,
},
}}
/>
</Box>
{!project.legacyPlan?.isFree && (
<Alert severity="info" className="col-span-6 text-left">
Note that volumes can only be increased (not decreased). Also, due
to an AWS limitation, the same volume can only be increased once
every 6 hours.
</Alert>
<DatabaseStorageCapacityWarning
state={state}
decreasingSize={decreasingSize}
isDirty={isDirty}
/>
)}
{showEncryptionWarning ? (
<Alert severity="warning" className="flex flex-col gap-3 text-left">
<div className="flex flex-col gap-2 lg:flex-row lg:justify-between">
<Text className="flex items-start gap-1 font-semibold">
Disk encryption is now available!
</Text>
</div>
<div>
<Text>
To enable encryption in this project all you have to do is
pause & unpause it in{' '}
<Link
href={`/orgs/${org?.slug}/projects/${project?.subdomain}/settings`}
underline="hover"
>
General Settings
</Link>
.
</Text>
</div>
</Alert>
) : null}
</SettingsContainer>
</Form>
</FormProvider>

View File

@@ -0,0 +1,73 @@
import { Alert } from '@/components/ui/v2/Alert';
import { Text } from '@/components/ui/v2/Text';
import { ApplicationStatus } from '@/types/application';
interface DatabaseStorageCapacityWarningProps {
state: ApplicationStatus;
decreasingSize: boolean;
isDirty: boolean;
}
export default function DatabaseStorageCapacityWarning({
state,
decreasingSize,
isDirty,
}: DatabaseStorageCapacityWarningProps) {
const applicationPause =
state === ApplicationStatus.Paused || state === ApplicationStatus.Pausing;
if (!isDirty) {
return null;
}
if (state === ApplicationStatus.Live && !decreasingSize) {
return (
<Alert severity="warning" className="flex flex-col gap-3 text-left">
<div className="flex flex-col gap-2 lg:flex-row lg:justify-between">
<Text className="flex items-start gap-1 font-semibold">
<span></span> Warning: Increasing disk size
</Text>
</div>
<div>
<Text>
Due to AWS limitations, disk size can only be modified once every 6
hours. Please ensure you increase capacity sufficiently to cover
your needs during this period.
</Text>
</div>
</Alert>
);
}
if (state === ApplicationStatus.Live && decreasingSize) {
return (
<Alert severity="warning" className="flex flex-col gap-3 text-left">
<div className="flex flex-col gap-2 lg:flex-row lg:justify-between">
<Text className="flex items-start gap-1 font-semibold">
<span></span> Warning: Decreasing disk size requires project to be
paused first.
</Text>
</div>
</Alert>
);
}
if (applicationPause && decreasingSize) {
return (
<Alert severity="warning" className="flex flex-col gap-3 text-left">
<div className="flex flex-col gap-2 lg:flex-row lg:justify-between">
<Text className="flex items-start gap-1 font-semibold">
<span></span> Warning: Ensure enough space before downsizing.
</Text>
</div>
<div>
<Text>
Before downsizing, ensure enough space for your database, WAL files,
and other supporting data to prevent issues when unpausing your
project.
</Text>
</div>
</Alert>
);
}
return null;
}

View File

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

View File

@@ -0,0 +1,5 @@
query GetPersistentVolumesEncrypted($appId: uuid!) {
systemConfig(appID: $appId) {
persistentVolumesEncrypted
}
}

View File

@@ -26,7 +26,7 @@ export default function useGetAppUsers({
offset = 0,
options = {},
}: UseFilesOptions) {
const { project } = useProject({ target: 'user-project' });
const { project } = useProject();
const userApplicationClient = useRemoteApplicationGQLClient();
const { data, error, loading } = useRemoteAppGetUsersCustomQuery({
...options,

View File

@@ -24,7 +24,7 @@ export default function useAppClient(
options?: UseAppClientOptions,
): UseAppClientReturn {
const isPlatform = useIsPlatform();
const { project } = useProject({ target: 'user-project' });
const { project } = useProject();
if (!isPlatform) {
return new NhostClient({

View File

@@ -2,21 +2,16 @@ import { localApplication } from '@/features/orgs/utils/local-dashboard';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetProjectDocument,
useGetProjectQuery,
type GetProjectQuery,
type ProjectFragment,
} from '@/utils/__generated__/graphql';
import { useAuthenticationStatus, useNhostClient } from '@nhost/nextjs';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import { useMemo } from 'react';
type Project = GetProjectQuery['apps'][0];
interface UseProjectOptions {
poll?: boolean;
target?: 'console-next' | 'user-project';
}
export interface UseProjectReturnType {
project: Project;
loading?: boolean;
@@ -24,10 +19,7 @@ export interface UseProjectReturnType {
refetch: (variables?: any) => Promise<any>;
}
export default function useProject({
poll = false,
target = 'console-next',
}: UseProjectOptions = {}): UseProjectReturnType {
export default function useProject(): UseProjectReturnType {
const {
query: { appSubdomain },
isReady: isRouterReady,
@@ -37,65 +29,36 @@ export default function useProject({
const { isAuthenticated, isLoading: isAuthLoading } =
useAuthenticationStatus();
const shouldFetchProject =
isPlatform &&
isAuthenticated &&
!isAuthLoading &&
!!appSubdomain &&
isRouterReady;
// Fetch project data for 'console-next' target
const {
data: consoleData,
loading: consoleLoading,
error: consoleError,
refetch: refetchConsole,
} = useGetProjectQuery({
variables: { subdomain: appSubdomain as string },
skip: !shouldFetchProject && target === 'console-next',
fetchPolicy: 'cache-and-network',
pollInterval: poll ? 5000 * 2 : 0, // every 10s
});
// Fetch project data for 'user-project' target using client.graphql
const {
data: userProjectData,
isFetching: userProjectFetching,
refetch: refetchUserProject,
} = useQuery(
['currentProject', appSubdomain],
const shouldFetchProject = useMemo(
() =>
client.graphql.request<{ apps: ProjectFragment[] }>(GetProjectDocument, {
isPlatform &&
isAuthenticated &&
!isAuthLoading &&
!!appSubdomain &&
isRouterReady,
[isPlatform, isAuthenticated, isAuthLoading, appSubdomain, isRouterReady],
);
const { data, isLoading, refetch, error } = useQuery(
['project', appSubdomain as string],
async () => {
const response = await client.graphql.request<{
apps: ProjectFragment[];
}>(GetProjectDocument, {
subdomain: (appSubdomain as string) || '',
}),
});
return response;
},
{
keepPreviousData: true,
enabled: shouldFetchProject && target === 'user-project',
staleTime: poll ? 5000 : Infinity, // Adjust staleTime for better performance
enabled: shouldFetchProject,
},
);
const project =
target === 'console-next'
? consoleData?.apps?.[0] || null
: userProjectData?.data?.apps?.[0] || null;
const loading =
target === 'console-next'
? consoleLoading || isAuthLoading
: userProjectFetching || isAuthLoading;
const error = consoleError
? new Error(consoleError.message || 'Unknown error occurred.')
: null;
const refetch =
target === 'console-next' ? refetchConsole : refetchUserProject;
if (isPlatform) {
return {
project,
loading,
error,
project: data?.data?.apps?.[0] || null,
loading: isLoading && shouldFetchProject,
error: Array.isArray(error || {}) ? error[0] : error,
refetch,
};
}

View File

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

View File

@@ -0,0 +1,77 @@
import { localApplication } from '@/features/orgs/utils/local-dashboard';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetProjectStateDocument,
type GetProjectQuery,
type ProjectFragment,
} from '@/utils/__generated__/graphql';
import { useAuthenticationStatus, useNhostClient } from '@nhost/nextjs';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import { useMemo } from 'react';
type Project = GetProjectQuery['apps'][0];
export interface UseProjectWithStateReturnType {
project: Project;
loading?: boolean;
error?: Error;
refetch: (variables?: any) => Promise<any>;
}
export default function useProjectWithState(): UseProjectWithStateReturnType {
const {
query: { appSubdomain },
isReady: isRouterReady,
} = useRouter();
const client = useNhostClient();
const isPlatform = useIsPlatform();
const { isAuthenticated, isLoading: isAuthLoading } =
useAuthenticationStatus();
const shouldFetchProject = useMemo(
() =>
isPlatform &&
isAuthenticated &&
!isAuthLoading &&
!!appSubdomain &&
isRouterReady,
[isPlatform, isAuthenticated, isAuthLoading, appSubdomain, isRouterReady],
);
const { data, isLoading, refetch, error } = useQuery(
['projectWithState', appSubdomain as string],
async () => {
const response = await client.graphql.request<{
apps: ProjectFragment[];
}>(GetProjectStateDocument, {
subdomain: (appSubdomain as string) || '',
});
return response;
},
{
enabled: shouldFetchProject,
keepPreviousData: true,
refetchOnWindowFocus: true,
refetchInterval: 10000, // poll every 10s
staleTime: 1000 * 60 * 5, // 1 minutes
cacheTime: 1000 * 60 * 6, //
},
);
if (isPlatform) {
return {
project: data?.data?.apps?.[0] || null,
loading: isLoading && shouldFetchProject,
error: Array.isArray(error || {}) ? error[0] : error,
refetch,
};
}
return {
project: localApplication,
loading: false,
error: null,
refetch: () => Promise.resolve(),
};
}

View File

@@ -28,7 +28,7 @@ const smtpValidationSchema = yup
.required(),
user: yup.string().label('Username').required(),
password: yup.string().label('Password'),
sender: yup.string().label('SMTP Sender').email().required(),
sender: yup.string().label('SMTP Sender').required(),
})
.required();

View File

@@ -18,15 +18,19 @@ import { calculateBillableResources } from '@/features/orgs/projects/resources/s
import type { ResourceSettingsFormValues } from '@/features/orgs/projects/resources/settings/utils/resourceSettingsValidationSchema';
import { resourceSettingsValidationSchema } from '@/features/orgs/projects/resources/settings/utils/resourceSettingsValidationSchema';
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
import {
RESOURCE_VCPU_MULTIPLIER,
RESOURCE_VCPU_PRICE,
} from '@/utils/constants/common';
import type { GetResourcesQuery } from '@/utils/__generated__/graphql';
import type {
ConfigConfigUpdateInput,
GetResourcesQuery,
} from '@/utils/__generated__/graphql';
import {
useGetResourcesQuery,
useUpdateConfigMutation,
} from '@/utils/__generated__/graphql';
import {
RESOURCE_VCPU_MULTIPLIER,
RESOURCE_VCPU_PRICE,
} from '@/utils/constants/common';
import { removeTypename } from '@/utils/helpers';
import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
@@ -36,7 +40,7 @@ function getInitialServiceResources(
data: GetResourcesQuery,
service: Exclude<keyof GetResourcesQuery['config'], '__typename'>,
) {
const { compute, replicas, autoscaler } =
const { compute, replicas, autoscaler, ...rest } =
data?.config?.[service]?.resources || {};
return {
@@ -44,6 +48,7 @@ function getInitialServiceResources(
vcpu: compute?.cpu || 0,
memory: compute?.memory || 0,
autoscale: autoscaler || null,
rest,
};
}
@@ -176,76 +181,130 @@ export default function ResourcesForm() {
? (billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) * RESOURCE_VCPU_PRICE
: 0;
const getFormattedConfig = (
values: ResourceSettingsFormValues,
): ConfigConfigUpdateInput => {
const sanitizedValues = removeTypename(
values,
) as ResourceSettingsFormValues;
const sanitizedInitialDatabaseResources = removeTypename(
initialDatabaseResources,
);
const sanitizedInitialHasuraResources = removeTypename(
initialHasuraResources,
);
const sanitizedInitialAuthResources = removeTypename(initialAuthResources);
const sanitizedInitialStorageResources = removeTypename(
initialStorageResources,
);
if (sanitizedValues.enabled) {
return {
postgres: {
resources: {
compute: {
cpu: sanitizedValues.database.vcpu,
memory: sanitizedValues.database.memory,
},
replicas: sanitizedValues.database.replicas,
autoscaler: sanitizedValues.database.autoscale
? {
maxReplicas: sanitizedValues.database.maxReplicas,
}
: null,
...sanitizedInitialDatabaseResources.rest,
},
},
hasura: {
resources: {
compute: {
cpu: sanitizedValues.hasura.vcpu,
memory: sanitizedValues.hasura.memory,
},
replicas: sanitizedValues.hasura.replicas,
autoscaler: sanitizedValues.hasura.autoscale
? {
maxReplicas: sanitizedValues.hasura.maxReplicas,
}
: null,
...sanitizedInitialHasuraResources.rest,
},
},
auth: {
resources: {
compute: {
cpu: sanitizedValues.auth.vcpu,
memory: sanitizedValues.auth.memory,
},
replicas: sanitizedValues.auth.replicas,
autoscaler: sanitizedValues.auth.autoscale
? {
maxReplicas: sanitizedValues.auth.maxReplicas,
}
: null,
...sanitizedInitialAuthResources.rest,
},
},
storage: {
resources: {
compute: {
cpu: sanitizedValues.storage.vcpu,
memory: sanitizedValues.storage.memory,
},
replicas: sanitizedValues.storage.replicas,
autoscaler: sanitizedValues.storage.autoscale
? {
maxReplicas: sanitizedValues.storage.maxReplicas,
}
: null,
...sanitizedInitialStorageResources.rest,
},
},
};
}
return {
postgres: {
resources: {
compute: null,
replicas: null,
autoscaler: null,
...sanitizedInitialDatabaseResources.rest,
},
},
hasura: {
resources: {
compute: null,
replicas: null,
autoscaler: null,
...sanitizedInitialHasuraResources.rest,
},
},
auth: {
resources: {
compute: null,
replicas: null,
autoscaler: null,
...sanitizedInitialAuthResources.rest,
},
},
storage: {
resources: {
compute: null,
replicas: null,
autoscaler: null,
...sanitizedInitialStorageResources.rest,
},
},
};
};
async function handleSubmit(formValues: ResourceSettingsFormValues) {
const updateConfigPromise = updateConfig({
variables: {
appId: project?.id,
config: {
postgres: {
resources: formValues.enabled
? {
compute: {
cpu: formValues.database.vcpu,
memory: formValues.database.memory,
},
replicas: formValues.database.replicas,
autoscaler: formValues.database.autoscale
? {
maxReplicas: formValues.database.maxReplicas,
}
: null,
}
: null,
},
hasura: {
resources: formValues.enabled
? {
compute: {
cpu: formValues.hasura.vcpu,
memory: formValues.hasura.memory,
},
replicas: formValues.hasura.replicas,
autoscaler: formValues.hasura.autoscale
? {
maxReplicas: formValues.hasura.maxReplicas,
}
: null,
}
: null,
},
auth: {
resources: formValues.enabled
? {
compute: {
cpu: formValues.auth.vcpu,
memory: formValues.auth.memory,
},
replicas: formValues.auth.replicas,
autoscaler: formValues.auth.autoscale
? {
maxReplicas: formValues.auth.maxReplicas,
}
: null,
}
: null,
},
storage: {
resources: formValues.enabled
? {
compute: {
cpu: formValues.storage.vcpu,
memory: formValues.storage.memory,
},
replicas: formValues.storage.replicas,
autoscaler: formValues.storage.autoscale
? {
maxReplicas: formValues.storage.maxReplicas,
}
: null,
}
: null,
},
},
config: getFormattedConfig(formValues),
},
});

View File

@@ -171,7 +171,7 @@ export default function ServiceResourcesFormFragment({
</Box>
<Box className="grid grid-flow-row gap-2">
<Box className="grid items-center justify-between grid-flow-col gap-2">
<Box className="grid grid-flow-col items-center justify-between gap-2">
<Text>
Allocated vCPUs:{' '}
<span className="font-medium">
@@ -201,7 +201,7 @@ export default function ServiceResourcesFormFragment({
</Box>
<Box className="grid grid-flow-row gap-2">
<Box className="grid items-center justify-between grid-flow-col gap-2">
<Box className="grid grid-flow-col items-center justify-between gap-2">
<Text>
Allocated Memory:{' '}
<span className="font-medium">
@@ -246,7 +246,7 @@ export default function ServiceResourcesFormFragment({
>
<ExclamationIcon
color="error"
className="w-4 h-4"
className="h-4 w-4"
aria-hidden="false"
/>
</Tooltip>
@@ -274,7 +274,7 @@ export default function ServiceResourcesFormFragment({
>
<ExclamationIcon
color="error"
className="w-4 h-4"
className="h-4 w-4"
aria-hidden="false"
/>
</Tooltip>
@@ -306,7 +306,7 @@ export default function ServiceResourcesFormFragment({
<Tooltip
title={`Enable autoscaler to automatically provision extra ${title} replicas when needed.`}
>
<InfoOutlinedIcon className="w-4 h-4 text-black" />
<InfoOutlinedIcon className="h-4 w-4" />
</Tooltip>
</Box>
</Box>
@@ -323,7 +323,7 @@ export default function ServiceResourcesFormFragment({
className="font-medium"
>
Service Replicas
<ArrowSquareOutIcon className="w-4 h-4 ml-1" />
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
</Link>
</Text>
)}

View File

@@ -9,6 +9,11 @@ fragment ServiceResources on ConfigConfig {
autoscaler {
maxReplicas
}
networking {
ingresses {
fqdn
}
}
}
}
hasura {
@@ -21,10 +26,19 @@ fragment ServiceResources on ConfigConfig {
autoscaler {
maxReplicas
}
networking {
ingresses {
fqdn
}
}
}
}
postgres {
resources {
storage {
capacity
}
enablePublicAccess
compute {
cpu
memory

View File

@@ -19,7 +19,6 @@ import { StorageFormSection } from '@/features/orgs/projects/services/components
import { useHostName } from '@/features/projects/common/hooks/useHostName';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import { v4 as uuidv4 } from 'uuid';
import {
validationSchema,
@@ -29,16 +28,15 @@ import {
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import {
useInsertRunServiceConfigMutation,
useReplaceRunServiceConfigMutation,
type ConfigRunServiceConfigInsertInput,
} from '@/utils/__generated__/graphql';
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { removeTypename } from '@/utils/helpers';
import {
useInsertRunServiceConfigMutation,
useInsertRunServiceMutation,
useReplaceRunServiceConfigMutation,
type ConfigRunServiceConfigInsertInput,
} from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useMemo, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
@@ -58,9 +56,10 @@ export default function ServiceForm({
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { onDirtyStateChange, openDialog, closeDialog } = useDialog();
const [insertRunService] = useInsertRunServiceMutation();
const { project } = useProject();
const [insertRunServiceConfig] = useInsertRunServiceConfigMutation();
const [insertRunServiceConfig] = useInsertRunServiceConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const [replaceRunServiceConfig] = useReplaceRunServiceConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
@@ -96,14 +95,14 @@ export default function ServiceForm({
if (serviceID) {
return serviceID;
}
return uuidv4();
return '<uuid-to-be-generated-on-creation>';
}, [serviceID]);
const privateRegistryImage = `registry.${project?.region.name}.${project?.region.domain}/${newServiceID}`;
let initialImageType: 'public' | 'private' | 'nhost' = 'public';
if (initialData?.image?.startsWith(privateRegistryImage)) {
if (initialData?.image?.startsWith(privateRegistryImage.split('/')[0])) {
initialImageType = 'nhost';
}
@@ -225,33 +224,14 @@ export default function ServiceForm({
});
}
} else {
// Insert service config
const {
data: {
insertRunService: { id },
},
} = await insertRunService({
variables: {
object: {
appID: project.id,
id: newServiceID,
},
},
});
// Create service
await insertRunServiceConfig({
variables: {
appID: project.id,
serviceID: id,
config: {
...config,
image: {
// If the image field left empty then we auto-populate following this format
// registry.<region>.<nhost_domain>/<service_id>
image:
values.image.length > 0
? values.image
: `registry.${project.region.name}.${project.region.domain}/${newServiceID}`,
image: values.image,
pullCredentials:
values.pullCredentials?.length > 0
? values.pullCredentials
@@ -335,7 +315,7 @@ export default function ServiceForm({
<Tooltip title="Name of the service, must be unique per project.">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
@@ -359,7 +339,7 @@ export default function ServiceForm({
<Tooltip title="Command to run when to start the service. This is optional as the image may already have a baked-in command.">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
@@ -414,7 +394,7 @@ export default function ServiceForm({
{createServiceFormError && (
<Alert
severity="error"
className="grid items-center justify-between grid-flow-col px-4 py-3"
className="grid grid-flow-col items-center justify-between px-4 py-3"
>
<span className="text-left">
<strong>Error:</strong> {createServiceFormError.message}

View File

@@ -12,7 +12,11 @@ import {
export const validationSchema = Yup.object({
name: Yup.string().required('The name is required.'),
image: Yup.string().label('Image to run').required('The image is required.'),
image: Yup.string()
.trim()
.label('Image to run')
.required('The image is required.')
.min(1, 'Image must be at least 1 character long'),
pullCredentials: Yup.string().label('Pull credentials').nullable(),
command: Yup.string(),
environment: Yup.array().of(

View File

@@ -45,7 +45,7 @@ export default function ReplicasFormSection() {
};
return (
<Box className="p-4 space-y-4 rounded border-1">
<Box className="space-y-4 rounded border-1 p-4">
<Box className="flex flex-row items-center space-x-2">
<Text variant="h4" className="font-semibold">
Replicas ({replicas})
@@ -65,7 +65,7 @@ export default function ReplicasFormSection() {
</Text>
}
>
<InfoIcon aria-label="Info" className="w-4 h-4" color="primary" />
<InfoIcon aria-label="Info" className="h-4 w-4" color="primary" />
</Tooltip>
</Box>
@@ -121,7 +121,7 @@ export default function ReplicasFormSection() {
/>
<Text>Autoscaler</Text>
<Tooltip title="Enable autoscaler to automatically provision extra run service replicas when needed.">
<InfoOutlinedIcon className="w-4 h-4 text-black" />
<InfoOutlinedIcon className="h-4 w-4" />
</Tooltip>
</Box>
</Box>

View File

@@ -0,0 +1,108 @@
import type { CommonDataGridCellProps } from '@/components/dataGrid/DataGridCell';
import { useDataGridCell } from '@/components/dataGrid/DataGridCell';
import { Input, inputClasses } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import type { ChangeEvent, KeyboardEvent } from 'react';
export type DataGridDecimalCellProps<TData extends object> =
CommonDataGridCellProps<TData, number | string>;
export default function DataGridDecimalCell<TData extends object>({
onSave,
optimisticValue,
temporaryValue,
onTemporaryValueChange,
}: DataGridDecimalCellProps<TData>) {
const { inputRef, focusCell, isEditing, cancelEditCell } =
useDataGridCell<HTMLInputElement>();
async function handleSave() {
if (onSave) {
if (typeof temporaryValue === 'string') {
await onSave(parseFloat(temporaryValue));
} else if (typeof temporaryValue === 'number') {
await onSave(temporaryValue);
} else {
await onSave(null);
}
}
}
async function handleKeyDown(event: KeyboardEvent<HTMLInputElement>) {
if (
event.key === 'ArrowLeft' ||
event.key === 'ArrowRight' ||
event.key === 'ArrowUp' ||
event.key === 'ArrowDown' ||
event.key === 'Backspace'
) {
event.stopPropagation();
}
if (event.key === 'Tab') {
await handleSave();
}
if (event.key === 'Enter') {
await handleSave();
await focusCell();
cancelEditCell();
}
}
function handleChange(event: ChangeEvent<HTMLInputElement>) {
if (onTemporaryValueChange) {
onTemporaryValueChange(event.target.value ?? null);
}
}
if (isEditing) {
return (
<Input
type="text"
ref={inputRef}
value={
temporaryValue !== null && typeof temporaryValue !== 'undefined'
? temporaryValue
: ''
}
onKeyDown={handleKeyDown}
onChange={handleChange}
fullWidth
className="absolute top-0 z-10 -mx-0.5 h-full place-content-stretch"
sx={{
[`&.${inputClasses.focused}`]: {
boxShadow: `inset 0 0 0 1.5px rgba(0, 82, 205, 1)`,
borderColor: 'transparent !important',
borderRadius: 0,
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? `${theme.palette.secondary[100]} !important`
: `${theme.palette.common.white} !important`,
},
[`& .${inputClasses.input}`]: {
backgroundColor: 'transparent',
},
}}
slotProps={{
inputWrapper: { className: 'h-full' },
input: { className: 'h-full' },
inputRoot: {
className:
'resize-none outline-none focus:outline-none !text-xs focus:ring-0',
},
}}
/>
);
}
if (optimisticValue === null || typeof optimisticValue === 'undefined') {
return (
<Text className="truncate !text-xs" color="disabled">
null
</Text>
);
}
return <Text className="truncate !text-xs">{optimisticValue}</Text>;
}

View File

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

View File

@@ -4,15 +4,15 @@ import { Input, inputClasses } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import type { ChangeEvent, KeyboardEvent } from 'react';
export type DataGridNumericCellProps<TData extends object> =
export type DataGridIntegerCellProps<TData extends object> =
CommonDataGridCellProps<TData, number>;
export default function DataGridNumericCell<TData extends object>({
export default function DataGridIntegerCell<TData extends object>({
onSave,
optimisticValue,
temporaryValue,
onTemporaryValueChange,
}: DataGridNumericCellProps<TData>) {
}: DataGridIntegerCellProps<TData>) {
const { inputRef, focusCell, isEditing, cancelEditCell } =
useDataGridCell<HTMLInputElement>();

View File

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

View File

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

View File

@@ -166,7 +166,7 @@ export default function DataGridPreviewCell<TData extends object>({
value: { fetchBlob, id, mimeType, alt, blob },
fallbackPreview = null,
}: DataGridPreviewCellProps<TData>) {
const { project } = useProject({ target: 'user-project' });
const { project } = useProject();
const appClient = useAppClient();
const { objectUrl, loading, error } = useBlob({ fetchBlob, blob, mimeType });
const [showModal, setShowModal] = useState(false);

View File

@@ -13,10 +13,10 @@ import { FilesDataGridControls } from '@/features/orgs/projects/storage/dataGrid
import { useBuckets } from '@/features/orgs/projects/storage/dataGrid/hooks/useBuckets';
import { useFiles } from '@/features/orgs/projects/storage/dataGrid/hooks/useFiles';
import { useFilesAggregate } from '@/features/orgs/projects/storage/dataGrid/hooks/useFilesAggregate';
import { getHasuraAdminSecret } from '@/utils/env';
import { showLoadingToast, triggerToast } from '@/utils/toast';
import type { Files } from '@/utils/__generated__/graphql';
import { Order_By as OrderBy } from '@/utils/__generated__/graphql';
import { getHasuraAdminSecret } from '@/utils/env';
import { showLoadingToast, triggerToast } from '@/utils/toast';
import debounce from 'lodash.debounce';
import { useRouter } from 'next/router';
import type { ChangeEvent } from 'react';
@@ -32,7 +32,7 @@ export type FilesDataGridProps = Partial<DataGridProps<StoredFile>>;
export default function FilesDataGrid(props: FilesDataGridProps) {
const router = useRouter();
const { project } = useProject({ target: 'user-project' });
const { project } = useProject();
const appClient = useAppClient();
const [searchString, setSearchString] = useState<string | null>(null);
const [currentOffset, setCurrentOffset] = useState<number | null>(
@@ -118,7 +118,7 @@ export default function FilesDataGrid(props: FilesDataGridProps) {
DataGridPreviewCell({
...cellProps,
fallbackPreview: (
<FilePreviewIcon className="w-5 h-5 fill-current" />
<FilePreviewIcon className="h-5 w-5 fill-current" />
),
}),
minWidth: 80,

View File

@@ -12,9 +12,9 @@ import { useAppClient } from '@/features/orgs/projects/hooks/useAppClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import type { FileUploadButtonProps } from '@/features/orgs/projects/storage/dataGrid/components/FileUploadButton';
import { FileUploadButton } from '@/features/orgs/projects/storage/dataGrid/components/FileUploadButton';
import type { Files } from '@/utils/__generated__/graphql';
import { getHasuraAdminSecret } from '@/utils/env';
import { triggerToast } from '@/utils/toast';
import type { Files } from '@/utils/__generated__/graphql';
import type { PropsWithoutRef } from 'react';
import { useState } from 'react';
import type { Row } from 'react-table';
@@ -38,7 +38,7 @@ export default function FilesDataGridControls({
...props
}: FilesDataGridControlsProps) {
const { openAlertDialog } = useDialog();
const { project } = useProject({ target: 'user-project' });
const { project } = useProject();
const appClient = useAppClient();
const [deleteLoading, setDeleteLoading] = useState(false);
@@ -160,7 +160,7 @@ export default function FilesDataGridControls({
</Button>
</div>
) : (
<div className="grid w-full grid-cols-12 gap-2 mx-auto">
<div className="mx-auto grid w-full grid-cols-12 gap-2">
<Input
className={twMerge(
'col-span-12 xs+:col-span-12 md:col-span-9 xl:col-span-10',
@@ -170,7 +170,7 @@ export default function FilesDataGridControls({
{...restFilterProps}
/>
<div className="grid grid-flow-col col-span-12 gap-2 md:col-span-3 xl:col-span-2">
<div className="col-span-12 grid grid-flow-col gap-2 md:col-span-3 xl:col-span-2">
<DataGridPagination
className={twMerge('col-span-6', paginationClassName)}
{...restPaginationProps}

View File

@@ -1,11 +1,11 @@
import { generateAppServiceUrl } from '@/features/orgs/projects/common/utils/generateAppServiceUrl';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { getHasuraAdminSecret } from '@/utils/env';
import type {
Files_Order_By as FilesOrderBy,
GetFilesQuery,
} from '@/utils/__generated__/graphql';
import { useGetFilesQuery } from '@/utils/__generated__/graphql';
import { getHasuraAdminSecret } from '@/utils/env';
import type { QueryHookOptions } from '@apollo/client';
export type UseFilesOptions = {
@@ -38,7 +38,7 @@ export default function useFiles({
orderBy,
options = {},
}: UseFilesOptions) {
const { project } = useProject({ target: 'user-project' });
const { project } = useProject();
const { data, previousData, ...rest } = useGetFilesQuery({
variables: {
where: searchString

View File

@@ -1,3 +1,5 @@
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
@@ -8,10 +10,22 @@ import { useEffect } from 'react';
export default function useNotFoundRedirect() {
const router = useRouter();
const {
query: { orgSlug, workspaceSlug, appSubdomain, updating, appSlug },
query: {
orgSlug: urlOrgSlug,
workspaceSlug: urlWorkspaceSlug,
appSubdomain: urlAppSubdomain,
updating,
appSlug: urlAppSlug,
},
isReady,
} = router;
const { project, loading: projectLoading } = useProject();
const { org, loading: orgLoading } = useCurrentOrg();
const { subdomain: projectSubdomain } = project || {};
const { slug: currentOrgSlug } = org || {};
const { currentProject, currentWorkspace, loading } =
useCurrentWorkspaceAndProject();
@@ -23,18 +37,24 @@ export default function useNotFoundRedirect() {
!isReady ||
// If the current workspace and project are not loaded, we don't want to redirect to 404
loading ||
// If the project is loading, we don't want to redirect to 404
projectLoading ||
// If the org is loading, we don't want to redirect to 404
orgLoading ||
// If we're already on the 404 page, we don't want to redirect to 404
router.pathname === '/404' ||
router.pathname === '/' ||
router.pathname === '/account' ||
router.pathname === '/support/ticket' ||
router.pathname === '/run-one-click-install' ||
orgSlug ||
(orgSlug && appSubdomain) ||
router.pathname.includes('/orgs/_') ||
router.pathname.includes('/orgs/_/projects/_') ||
(urlOrgSlug === currentOrgSlug && !urlAppSubdomain) ||
(urlOrgSlug === currentOrgSlug && urlAppSubdomain === projectSubdomain) ||
// If we are on a valid workspace and project, we don't want to redirect to 404
(workspaceSlug && currentWorkspace && appSlug && currentProject) ||
(urlWorkspaceSlug && currentWorkspace && urlAppSlug && currentProject) ||
// If we are on a valid workspace and no project is selected, we don't want to redirect to 404
(workspaceSlug && currentWorkspace && !appSlug && !currentProject)
(urlWorkspaceSlug && currentWorkspace && !urlAppSlug && !currentProject)
) {
return;
}
@@ -45,11 +65,15 @@ export default function useNotFoundRedirect() {
currentWorkspace,
isReady,
loading,
appSubdomain,
appSlug,
urlAppSubdomain,
urlAppSlug,
router,
updating,
workspaceSlug,
orgSlug,
projectLoading,
orgLoading,
currentOrgSlug,
projectSubdomain,
urlWorkspaceSlug,
urlOrgSlug,
]);
}

View File

@@ -28,16 +28,15 @@ import {
type ServiceFormValues,
} from '@/features/services/components/ServiceForm/ServiceFormTypes';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import {
useInsertRunServiceConfigMutation,
useReplaceRunServiceConfigMutation,
type ConfigRunServiceConfigInsertInput,
} from '@/utils/__generated__/graphql';
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { removeTypename } from '@/utils/helpers';
import {
useInsertRunServiceConfigMutation,
useInsertRunServiceMutation,
useReplaceRunServiceConfigMutation,
type ConfigRunServiceConfigInsertInput,
} from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
@@ -57,7 +56,6 @@ export default function ServiceForm({
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { onDirtyStateChange, openDialog, closeDialog } = useDialog();
const [insertRunService] = useInsertRunServiceMutation();
const { currentProject } = useCurrentWorkspaceAndProject();
const [insertRunServiceConfig] = useInsertRunServiceConfigMutation();
const [replaceRunServiceConfig] = useReplaceRunServiceConfigMutation({
@@ -187,20 +185,11 @@ export default function ServiceForm({
// Insert service config
const {
data: {
insertRunService: { id: newServiceID, subdomain },
insertRunServiceConfig: { serviceID: newServiceID },
},
} = await insertRunService({
variables: {
object: {
appID: currentProject.id,
},
},
});
await insertRunServiceConfig({
} = await insertRunServiceConfig({
variables: {
appID: currentProject.id,
serviceID: newServiceID,
config: {
...config,
image: {
@@ -209,14 +198,14 @@ export default function ServiceForm({
image:
values.image.length > 0
? values.image
: `registry.${currentProject.region.name}.${currentProject.region.domain}/${newServiceID}`,
: `registry.${currentProject.region.name}.${currentProject.region.domain}/<uuid-to-be-generated-on-creation>`,
},
},
},
});
setDetailsServiceId(newServiceID);
setDetailsServiceSubdomain(subdomain);
setDetailsServiceSubdomain('');
}
};
@@ -322,7 +311,7 @@ export default function ServiceForm({
<Tooltip title="Name of the service, must be unique per project.">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
@@ -362,7 +351,7 @@ export default function ServiceForm({
>
<InfoIcon
aria-label="Info"
className="w-4 h-4"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
@@ -393,7 +382,7 @@ export default function ServiceForm({
<Tooltip title="Command to run when to start the service. This is optional as the image may already have a baked-in command.">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
@@ -441,7 +430,7 @@ export default function ServiceForm({
{createServiceFormError && (
<Alert
severity="error"
className="grid items-center justify-between grid-flow-col px-4 py-3"
className="grid grid-flow-col items-center justify-between px-4 py-3"
>
<span className="text-left">
<strong>Error:</strong> {createServiceFormError.message}

View File

@@ -0,0 +1,23 @@
query getProjectState($subdomain: String!) {
apps(where: { subdomain: { _eq: $subdomain } }) {
id
name
subdomain
region {
id
countryCode
name
domain
city
}
createdAt
desiredState
appStates(order_by: { createdAt: desc }, limit: 1) {
id
appId
message
stateId
createdAt
}
}
}

View File

@@ -1,6 +0,0 @@
mutation insertRunService($object: run_service_insert_input!) {
insertRunService(object: $object) {
id
subdomain
}
}

View File

@@ -1,13 +1,11 @@
mutation insertRunServiceConfig(
mutation InsertRunServiceConfig(
$appID: uuid!
$serviceID: uuid!
$config: ConfigRunServiceConfigInsertInput!
) {
insertRunServiceConfig(
appID: $appID
serviceID: $serviceID
config: $config
) {
name
insertRunServiceConfig(appID: $appID, config: $config) {
serviceID
config {
name
}
}
}

View File

@@ -388,9 +388,5 @@ export default function UsersPage() {
}
UsersPage.getLayout = function getLayout(page: ReactElement) {
return (
<ProjectLayout contentContainerProps={{ className: 'h-full' }}>
{page}
</ProjectLayout>
);
return <ProjectLayout>{page}</ProjectLayout>;
};

View File

@@ -65,10 +65,7 @@ export default function IndexPage() {
IndexPage.getLayout = function getLayout(page: ReactElement) {
return (
<AuthenticatedLayout
title="Dashboard"
contentContainerProps={{ className: 'flex w-full flex-col' }}
>
<AuthenticatedLayout title="Dashboard">
<Container className="py-0">
<MaintenanceAlert />
</Container>

View File

@@ -102,7 +102,7 @@ export default function AutoEmbeddingsPage() {
}
if (
(isPlatform && !org?.plan?.isFree && !project.config?.ai) ||
(isPlatform && !org?.plan?.isFree && !project?.config?.ai) ||
!isGraphiteEnabled
) {
return (

View File

@@ -117,7 +117,7 @@ function GraphiQLHeader({ onUserChange, onRoleChange }: GraphiQLHeaderProps) {
}
return (
<header className="grid items-end grid-flow-row gap-2 p-2 md:grid-flow-col md:justify-between">
<header className="grid grid-flow-row items-end gap-2 p-2 md:grid-flow-col md:justify-between">
<div className="grid grid-flow-row gap-2 md:grid-flow-col md:items-end">
<div className="grid grid-cols-2 gap-2 md:grid-flow-col md:grid-cols-[initial]">
<UserSelect
@@ -250,7 +250,7 @@ function GraphiQLEditor({ onHeaderChange }: GraphiQLEditorProps) {
}
export default function GraphQLPage() {
const { project } = useProject({ target: 'user-project' });
const { project } = useProject();
const [userHeaders, setUserHeaders] = useState<Record<string, any>>({});
if (!project?.subdomain || !project?.config?.hasura.adminSecret) {

View File

@@ -4,7 +4,10 @@ import { Form } from '@/components/form/Form';
import { Container } from '@/components/layout/Container';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Alert } from '@/components/ui/v2/Alert';
import { Input } from '@/components/ui/v2/Input';
import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text';
import { TransferProject } from '@/features/orgs/components/TransferProject';
import { ProjectLayout } from '@/features/orgs/layout/ProjectLayout';
import { SettingsLayout } from '@/features/orgs/layout/SettingsLayout';
@@ -12,6 +15,7 @@ import { RemoveApplicationModal } from '@/features/orgs/projects/common/componen
import { useAppState } from '@/features/orgs/projects/common/hooks/useAppState';
import { useIsCurrentUserOwner } from '@/features/orgs/projects/common/hooks/useIsCurrentUserOwner';
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import { useRunServices } from '@/features/orgs/projects/common/hooks/useRunServices';
import { useOrgs } from '@/features/orgs/projects/hooks/useOrgs';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
@@ -25,7 +29,7 @@ import { ApplicationStatus } from '@/types/application';
import { slugifyString } from '@/utils/helpers';
import { yupResolver } from '@hookform/resolvers/yup';
import { useRouter } from 'next/router';
import type { ReactElement } from 'react';
import { useMemo, type ReactElement } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -51,6 +55,20 @@ export default function SettingsGeneralPage() {
const { project, loading, refetch: refetchProject } = useProject();
const { state } = useAppState();
const { services } = useRunServices();
const showWarning = useMemo(() => {
const isPlanFree = org?.plan?.isFree;
if (isPlanFree) {
return false;
}
return services?.some(
(service) => service?.config?.resources?.storage?.length > 0,
);
}, [org?.plan?.isFree, services]);
const [updateApp] = useUpdateApplicationMutation();
const [deleteApplication] = useBillingDeleteAppMutation();
const [pauseApplication, { loading: pauseApplicationLoading }] =
@@ -242,9 +260,49 @@ export default function SettingsGeneralPage() {
onClick: () => {
openAlertDialog({
title: 'Pause Project?',
payload:
'Are you sure you want to pause this project? It will not be accessible until you unpause it.',
payload: (
<div className="flex flex-col gap-2">
{showWarning ? (
<Alert
severity="warning"
className="flex flex-col gap-3 text-left"
>
<div className="flex flex-col gap-2 lg:flex-row lg:justify-between">
<Text className="flex items-start gap-1 font-semibold">
<span></span> Warning: This action will delete
all volume data for your Run services.
</Text>
</div>
<div className="flex flex-col gap-4">
<Text>
Pausing this project will delete all persistent
volume data for your Run services. No automatic
backups are made. Please backup your data
manually to prevent loss. Contact{' '}
<Link
href="/support"
target="_blank"
className="underline"
sx={{
color: 'text.primary',
}}
rel="noopener noreferrer"
>
support
</Link>{' '}
with any questions.
</Text>
</div>
</Alert>
) : null}
<p className="text-pretty">
Are you sure you want to pause this project? It will
not be accessible until you unpause it.
</p>
</div>
),
props: {
maxWidth: 'sm',
onPrimaryAction: handlePauseApplication,
},
});

View File

@@ -224,16 +224,16 @@ export default function UsersPage() {
if (loadingRemoteAppUsersQuery) {
return (
<Container
className="flex flex-col h-full max-w-9xl"
className="flex h-full max-w-9xl flex-col"
rootClassName="h-full"
>
<div className="flex flex-row shrink-0 grow-0 place-content-between">
<div className="flex shrink-0 grow-0 flex-row place-content-between">
<Input
className="rounded-sm"
placeholder="Search users"
startAdornment={
<SearchIcon
className="w-4 h-4 ml-2 -mr-1 shrink-0"
className="-mr-1 ml-2 h-4 w-4 shrink-0"
sx={{ color: 'text.disabled' }}
/>
}
@@ -241,14 +241,14 @@ export default function UsersPage() {
/>
<Button
onClick={openCreateUserDialog}
startIcon={<PlusIcon className="w-4 h-4" />}
startIcon={<PlusIcon className="h-4 w-4" />}
size="small"
>
Create User
</Button>
</div>
<div className="flex items-center justify-center flex-auto overflow-hidden">
<div className="flex flex-auto items-center justify-center overflow-hidden">
<ActivityIndicator label="Loading users..." />
</div>
</Container>
@@ -256,14 +256,14 @@ export default function UsersPage() {
}
return (
<Container className="mx-auto space-y-5 overflow-x-hidden max-w-9xl">
<Container className="mx-auto max-w-9xl space-y-5 overflow-x-hidden">
<div className="flex flex-row place-content-between">
<Input
className="rounded-sm"
placeholder="Search users"
startAdornment={
<SearchIcon
className="w-4 h-4 ml-2 -mr-1 shrink-0"
className="-mr-1 ml-2 h-4 w-4 shrink-0"
sx={{ color: 'text.disabled' }}
/>
}
@@ -271,21 +271,21 @@ export default function UsersPage() {
/>
<Button
onClick={openCreateUserDialog}
startIcon={<PlusIcon className="w-4 h-4" />}
startIcon={<PlusIcon className="h-4 w-4" />}
size="small"
>
Create User
</Button>
</div>
{usersCount === 0 ? (
<Box className="flex flex-col items-center justify-center px-48 py-12 space-y-5 border rounded-lg shadow-sm">
<Box className="flex flex-col items-center justify-center space-y-5 rounded-lg border px-48 py-12 shadow-sm">
<UserIcon
strokeWidth={1}
className="w-10 h-10"
className="h-10 w-10"
sx={{ color: 'text.disabled' }}
/>
<div className="flex flex-col space-y-1">
<Text className="font-medium text-center" variant="h3">
<Text className="text-center font-medium" variant="h3">
There are no users yet
</Text>
<Text variant="subtitle1" className="text-center">
@@ -298,34 +298,34 @@ export default function UsersPage() {
color="primary"
className="w-full"
onClick={openCreateUserDialog}
startIcon={<PlusIcon className="w-4 h-4" />}
startIcon={<PlusIcon className="h-4 w-4" />}
>
Create User
</Button>
</div>
</Box>
) : (
<div className="grid grid-flow-row gap-2 lg:w-9xl">
<div className="grid w-full h-full grid-flow-row pb-4 overflow-hidden">
<Box className="grid w-full p-2 border-b md:grid-cols-6">
<div className="lg:w-9xl grid grid-flow-row gap-2">
<div className="grid h-full w-full grid-flow-row overflow-hidden pb-4">
<Box className="grid w-full border-b p-2 md:grid-cols-6">
<Text className="font-medium md:col-span-2">Name</Text>
<Text className="hidden font-medium md:block">Signed up at</Text>
<Text className="hidden font-medium md:block">Last Seen</Text>
<Text className="hidden col-span-2 font-medium md:block">
<Text className="col-span-2 hidden font-medium md:block">
OAuth Providers
</Text>
</Box>
{dataRemoteAppUsers?.filteredUsersAggreggate.aggregate.count ===
0 &&
usersCount !== 0 && (
<Box className="flex flex-col items-center justify-center px-48 py-12 space-y-5 border-b border-x">
<Box className="flex flex-col items-center justify-center space-y-5 border-x border-b px-48 py-12">
<UserIcon
strokeWidth={1}
className="w-10 h-10"
className="h-10 w-10"
sx={{ color: 'text.disabled' }}
/>
<div className="flex flex-col space-y-1">
<Text className="font-medium text-center" variant="h3">
<Text className="text-center font-medium" variant="h3">
No results for &quot;{searchString}&quot;
</Text>
<Text variant="subtitle1" className="text-center">
@@ -388,9 +388,5 @@ export default function UsersPage() {
}
UsersPage.getLayout = function getLayout(page: ReactElement) {
return (
<ProjectLayout contentContainerProps={{ className: 'h-full' }}>
{page}
</ProjectLayout>
);
return <ProjectLayout>{page}</ProjectLayout>;
};

View File

@@ -0,0 +1,15 @@
import { SelectOrg } from '@/components/common/SelectOrg';
import { AuthenticatedLayout } from '@/components/layout/AuthenticatedLayout';
import { } from '@/utils/__generated__/graphql';
import type { ReactElement } from 'react';
export default function SelectOrganization() {
return <SelectOrg />
}
SelectOrganization.getLayout = function getLayout(page: ReactElement) {
return (
<AuthenticatedLayout title="Select an Organization">{page}</AuthenticatedLayout>
);
};

View File

@@ -0,0 +1,15 @@
import { AuthenticatedLayout } from '@/components/layout/AuthenticatedLayout';
import { } from '@/utils/__generated__/graphql';
import type { ReactElement } from 'react';
import { SelectOrgAndProject } from '@/components/common/SelectOrgAndProject';
export default function OrganizationAndProject() {
return <SelectOrgAndProject />
}
OrganizationAndProject.getLayout = function getLayout(page: ReactElement) {
return (
<AuthenticatedLayout title="Select a Project">{page}</AuthenticatedLayout>
);
};

View File

@@ -0,0 +1,14 @@
import { SelectOrgAndProject } from '@/components/common/SelectOrgAndProject';
import { AuthenticatedLayout } from '@/components/layout/AuthenticatedLayout';
import { } from '@/utils/__generated__/graphql';
import type { ReactElement } from 'react';
export default function SelectOrganizationAndProject() {
return <SelectOrgAndProject />
}
SelectOrganizationAndProject.getLayout = function getLayout(page: ReactElement) {
return (
<AuthenticatedLayout title="Select a Project">{page}</AuthenticatedLayout>
);
};

View File

@@ -19,7 +19,7 @@ const validationSchema = Yup.object({
email: Yup.string().label('Email').email().required(),
});
export type ResetPasswordFormValues = Yup.InferType<typeof validationSchema>;
export type NewPasswordFormValues = Yup.InferType<typeof validationSchema>;
const StyledInput = styled(Input)({
backgroundColor: 'transparent',
@@ -28,10 +28,10 @@ const StyledInput = styled(Input)({
},
});
export default function ResetPasswordPage() {
export default function NewPasswordPage() {
const { resetPassword, error, isSent } = useResetPassword();
const form = useForm<ResetPasswordFormValues>({
const form = useForm<NewPasswordFormValues>({
reValidateMode: 'onSubmit',
defaultValues: {
email: '',
@@ -52,9 +52,11 @@ export default function ResetPasswordPage() {
);
}, [error]);
async function handleSubmit({ email }: ResetPasswordFormValues) {
async function handleSubmit({ email }: NewPasswordFormValues) {
try {
await resetPassword(email);
await resetPassword(email, {
redirectTo: '/password/reset',
});
} catch {
toast.error(
'An error occurred while signing up. Please try again.',
@@ -124,8 +126,10 @@ export default function ResetPasswordPage() {
);
}
ResetPasswordPage.getLayout = function getLayout(page: ReactElement) {
NewPasswordPage.getLayout = function getLayout(page: ReactElement) {
return (
<UnauthenticatedLayout title="Reset Password">{page}</UnauthenticatedLayout>
<UnauthenticatedLayout title="Request Password Reset">
{page}
</UnauthenticatedLayout>
);
};

View File

@@ -0,0 +1,144 @@
import { NavLink } from '@/components/common/NavLink';
import { Form } from '@/components/form/Form';
import { UnauthenticatedLayout } from '@/components/layout/UnauthenticatedLayout';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Input, inputClasses } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import { getToastStyleProps } from '@/utils/constants/settings';
import { yupResolver } from '@hookform/resolvers/yup';
import { styled } from '@mui/material';
import { useChangePassword } from '@nhost/nextjs';
import { useRouter } from 'next/router';
import type { ReactElement } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import * as Yup from 'yup';
const validationSchema = Yup.object({
newPassword: Yup.string()
.label('New Password')
.required('New Password is required'),
confirmNewPassword: Yup.string()
.label('Confirm New Password')
.required('Confirm New Password is required')
.oneOf([Yup.ref('newPassword')], 'Passwords must match'),
});
export type ResetPasswordFormValues = Yup.InferType<typeof validationSchema>;
const StyledInput = styled(Input)({
backgroundColor: 'transparent',
[`& .${inputClasses.input}`]: {
backgroundColor: 'transparent !important',
},
});
export default function ResetPasswordPage() {
const router = useRouter();
const { changePassword } = useChangePassword();
const form = useForm<ResetPasswordFormValues>({
reValidateMode: 'onSubmit',
defaultValues: {
newPassword: '',
confirmNewPassword: '',
},
resolver: yupResolver(validationSchema),
});
const { register, formState } = form;
async function handleSubmit({ newPassword }: ResetPasswordFormValues) {
try {
const password = newPassword;
const { isError, error } = await changePassword(password);
if (isError) {
toast.error(
`An error occurred while changing your password: ${error.message}`,
getToastStyleProps(),
);
return;
}
toast.success('Password was updated successfully.');
router.push('/');
} catch {
toast.error(
'An error occurred while updating your password. Please try again.',
getToastStyleProps(),
);
}
}
return (
<>
<Text
variant="h2"
component="h1"
className="text-center text-3.5xl font-semibold lg:text-4.5xl"
>
Change password
</Text>
<Box className="grid grid-flow-row gap-4 rounded-md border bg-transparent p-6 lg:p-12">
<FormProvider {...form}>
<Form
onSubmit={handleSubmit}
className="grid grid-flow-row gap-4 bg-transparent"
>
<StyledInput
{...register('newPassword')}
type="password"
id="newPassword"
label="New Password"
fullWidth
inputProps={{ min: 2, max: 128 }}
error={!!formState.errors.newPassword}
helperText={formState.errors.newPassword?.message}
/>
<StyledInput
{...register('confirmNewPassword')}
type="password"
id="confirmNewPassword"
label="Confirm New Password"
fullWidth
inputProps={{ min: 2, max: 128 }}
error={!!formState.errors.confirmNewPassword}
helperText={formState.errors.confirmNewPassword?.message}
/>
<Button
className="!bg-white !text-black disabled:!text-black disabled:!text-opacity-60"
size="large"
type="submit"
disabled={formState.isSubmitting}
loading={formState.isSubmitting}
>
Change password
</Button>
</Form>
</FormProvider>
</Box>
<Text color="secondary" className="text-center text-base lg:text-lg">
Go back to{' '}
<NavLink href="/signin/email" color="white" className="font-medium">
Sign In
</NavLink>
</Text>
</>
);
}
ResetPasswordPage.getLayout = function getLayout(page: ReactElement) {
return (
<UnauthenticatedLayout title="Request Password Reset">
{page}
</UnauthenticatedLayout>
);
};

View File

@@ -85,7 +85,7 @@ export default function EmailSignUpPage() {
Sign In
</Text>
<Box className="grid grid-flow-row gap-4 p-6 bg-transparent border rounded-md lg:p-12">
<Box className="grid grid-flow-row gap-4 rounded-md border bg-transparent p-6 lg:p-12">
<FormProvider {...form}>
<Form
onSubmit={handleSubmit}
@@ -123,9 +123,9 @@ export default function EmailSignUpPage() {
/>
<NavLink
href="/reset-password"
href="/password/new"
color="white"
className="font-semibold justify-self-start"
className="justify-self-start font-semibold"
>
Forgot password?
</NavLink>
@@ -150,7 +150,7 @@ export default function EmailSignUpPage() {
</FormProvider>
</Box>
<Text color="secondary" className="text-base text-center lg:text-lg">
<Text color="secondary" className="text-center text-base lg:text-lg">
Don&apos;t have an account?{' '}
<NavLink href="/signup" color="white">
Sign Up

View File

@@ -9,13 +9,13 @@ import { EnvelopeIcon } from '@/components/ui/v2/icons/EnvelopeIcon';
import { Input, inputClasses } from '@/components/ui/v2/Input';
import { Option } from '@/components/ui/v2/Option';
import { Text } from '@/components/ui/v2/Text';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
useGetAllWorkspacesAndProjectsQuery,
useGetOrganizationsQuery,
type GetAllWorkspacesAndProjectsQuery,
type GetOrganizationsQuery,
} from '@/utils/__generated__/graphql';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { styled } from '@mui/material';
import { useUserData } from '@nhost/nextjs';
@@ -175,14 +175,14 @@ function TicketPage() {
className="flex flex-col items-center justify-center py-10"
sx={{ backgroundColor: 'background.default' }}
>
<div className="flex flex-col w-full max-w-3xl">
<div className="flex flex-col items-center mb-4">
<div className="flex w-full max-w-3xl flex-col">
<div className="mb-4 flex flex-col items-center">
<Text variant="h4" className="font-bold">
Nhost Support
</Text>
<Text variant="h4">How can we help you?</Text>
</div>
<Box className="w-full p-10 border rounded-md">
<Box className="w-full rounded-md border p-10">
<Box className="grid grid-flow-row gap-4">
<Box className="flex flex-col gap-4">
<FormProvider {...form}>
@@ -205,7 +205,7 @@ function TicketPage() {
helperText={errors.organization?.message}
disabled={!!selectedWorkspace}
renderValue={(option) => (
<span className="inline-grid items-center grid-flow-col gap-2">
<span className="inline-grid grid-flow-col items-center gap-2">
{option?.label}
</span>
)}
@@ -238,7 +238,7 @@ function TicketPage() {
helperText={errors.workspace?.message}
disabled={!!selectedOrganization}
renderValue={(option) => (
<span className="inline-grid items-center grid-flow-col gap-2">
<span className="inline-grid grid-flow-col items-center gap-2">
{option?.label}
</span>
)}
@@ -267,7 +267,7 @@ function TicketPage() {
error={!!errors.project}
helperText={errors.project?.message}
renderValue={(option) => (
<span className="inline-grid items-center grid-flow-col gap-2">
<span className="inline-grid grid-flow-col items-center gap-2">
{option?.label}
</span>
)}
@@ -318,7 +318,7 @@ function TicketPage() {
root: { className: 'grid grid-flow-col gap-1 mb-4' },
}}
renderValue={(option) => (
<span className="inline-grid items-center grid-flow-col gap-2">
<span className="inline-grid grid-flow-col items-center gap-2">
{option?.label}
</span>
)}
@@ -401,8 +401,8 @@ function TicketPage() {
helperText={errors.ccs?.message}
/>
<Box className="flex flex-col gap-4 ml-auto w-80">
<Text color="secondary" className="text-sm text-right">
<Box className="ml-auto flex w-80 flex-col gap-4">
<Text color="secondary" className="text-right text-sm">
We will contact you at <strong>{user?.email}</strong>
</Text>
<Button
@@ -429,12 +429,7 @@ function TicketPage() {
TicketPage.getLayout = function getLayout(page: ReactElement) {
return (
<AuthenticatedLayout
title="Help & Support | Nhost"
contentContainerProps={{
className: 'flex w-full flex-col h-screen overflow-auto',
}}
>
<AuthenticatedLayout title="Help & Support | Nhost">
{page}
</AuthenticatedLayout>
);

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,28 @@
# @nhost/docs
## 2.26.0
### Minor Changes
- 04d2ce1: feat: add reference documentation for signin security key
### Patch Changes
- 1fa6cc4: chore: added docs for pg_jsonschema
## 2.25.0
### Minor Changes
- 46fc520: chore: add support to next.js 15, update quickstart template commands in docs
- cdf6776: fix: update links to create new project in dashboard
## 2.24.0
### Minor Changes
- a99f034: chore: fix function name
## 2.23.0
### Minor Changes

View File

@@ -44,6 +44,7 @@ In the table below you can find a list of available extensions with Nhost Postgr
| pg_freespacemap | 1.2 | examine the free space map (FSM) |
| pg_hashids | 1.3 | pg_hashids |
| pg_ivm | 1.9 | incremental view maintenance on PostgreSQL |
| pg_jsonschema | 0.3.3 | pg_jsonschema |
| pg_prewarm | 1.2 | prewarm relation data |
| pg_repack | 1.5.1 | Reorganize tables in PostgreSQL databases with minimal locks |
| pg_squeeze | 1.7 | A tool to remove unused space from a relation. |
@@ -75,7 +76,6 @@ In the table below you can find a list of available extensions with Nhost Postgr
In addition, you can find more information about some of the extensions below
## hypopg
HypoPG is a PostgreSQL extension adding support for hypothetical indexes.
@@ -277,6 +277,30 @@ DROP EXTENSION pg_ivm;
- [GitHub](https://github.com/sraoss/pg_ivm)
## pg_jsonschema
pg_jsonschema is a PostgreSQL extension adding support for JSON schema validation on json and jsonb data types.
### Managing
To install the extension you can create a migration with the following contents:
```sql SQL
SET ROLE postgres;
CREATE EXTENSION pg_jsonschema;
```
To uninstall it, you can use the following migration:
```sql SQL
SET ROLE postgres;
DROP EXTENSION pg_jsonschema;
```
### Resources
- [GitHub](https://github.com/supabase/pg_jsonschema)
## pg_repack
pg_repack is a PostgreSQL extension which lets you remove bloat from tables and indexes, and optionally restore the physical order of clustered indexes. Unlike CLUSTER and VACUUM FULL it works online, without holding an exclusive lock on the processed tables during processing. pg_repack is efficient to boot, with performance comparable to using CLUSTER directly.

View File

@@ -7,7 +7,7 @@ icon: react
<Steps>
<Step title="Create Project">
If you haven't, please create a project through the [Nhost Dashboard](https://app.nhost.io/new).
If you haven't, please create a project through the [Nhost Dashboard](https://app.nhost.io).
</Step>
<Step title="Setup Database">
@@ -47,7 +47,7 @@ icon: react
Create a Next.js application.
```bash Terminal
npx create-next-app@latest --no-eslint \
npx create-next-app@next-14 --no-eslint \
--src-dir \
--no-tailwind \
--import-alias "@/*" \
@@ -59,7 +59,7 @@ icon: react
</Step>
<Step title="Install the Nhost package for Next.js">
Navidate to the React application and install `@nhost/nextjs`.
Navigate to the React application and install `@nhost/nextjs`.
```bash Terminal
cd nhost-nextjs-quickstart && npm install @nhost/nextjs

View File

@@ -20,7 +20,7 @@ icon: mobile-notch
<Steps>
<Step title="Create Nhost Project">
Create your project through the [Nhost Dashboard](https://app.nhost.io/new).
Create your project through the [Nhost Dashboard](https://app.nhost.io).
</Step>
<Step title="Setup Database">

View File

@@ -7,7 +7,7 @@ icon: react
<Steps>
<Step title="Create Nhost Project">
Create your project through the [Nhost Dashboard](https://app.nhost.io/new).
Create your project through the [Nhost Dashboard](https://app.nhost.io).
</Step>
<Step title="Setup Database">

View File

@@ -7,7 +7,7 @@ icon: vuejs
<Steps>
<Step title="Create Project">
If you haven't, please create a project through the [Nhost Dashboard](https://app.nhost.io/new).
If you haven't, please create a project through the [Nhost Dashboard](https://app.nhost.io).
</Step>
<Step title="Setup Database">
@@ -53,7 +53,7 @@ icon: vuejs
</Step>
<Step title="Install the Nhost package for Vue">
Navidate to the React application and install `@nhost/vue`.
Navigate to the React application and install `@nhost/vue`.
```bash Terminal
cd nhost-vue-quickstart && npm install @nhost/vue

View File

@@ -30,7 +30,7 @@ In this section, you will create and setup your first Nhost project.
### Create project
Create a new project in the [Nhost Dashboard](https://app.nhost.io/new).
Create a new project in the [Nhost Dashboard](https://app.nhost.io).
Enter the details for your project and wait a couple of minutes while Nhost provisions your backend infrastructure:
@@ -156,7 +156,7 @@ Now that we have Nhost configured, let's move on to setup the React application
Run the following command in your terminal to create a React application using Vite.
```bash Terminal
npx create-next-app@latest --no-eslint \
npx create-next-app@next-14 --no-eslint \
--src-dir \
--no-tailwind \
--import-alias "@/*" \

View File

@@ -30,7 +30,7 @@ In this section, you will create and setup your first Nhost project.
### Create project
Create a new project in the [Nhost Dashboard](https://app.nhost.io/new).
Create a new project in the [Nhost Dashboard](https://app.nhost.io).
Enter the details for your project and wait a couple of minutes while Nhost provisions your backend infrastructure:

View File

@@ -29,7 +29,7 @@ In this section, you will create and setup your first Nhost project.
### Create project
Create a new project in the [Nhost Dashboard](https://app.nhost.io/new).
Create a new project in the [Nhost Dashboard](https://app.nhost.io).
Enter the details for your project and wait a couple of minutes while Nhost provisions your backend infrastructure:

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