Compare commits

..

50 Commits

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


# Releases
## @nhost/apollo@7.1.6

### Patch Changes

-   @nhost/nhost-js@3.1.9

## @nhost/react-apollo@12.0.6

### Patch Changes

-   @nhost/apollo@7.1.6
-   @nhost/react@3.5.6

## @nhost/react-urql@9.0.6

### Patch Changes

-   @nhost/react@3.5.6

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

### Patch Changes

- 8b12426: fix: correct signout to send accessToken when clearing all
session

## @nhost/nextjs@2.1.20

### Patch Changes

-   @nhost/react@3.5.6

## @nhost/nhost-js@3.1.9

### Patch Changes

-   Updated dependencies [8b12426]
    -   @nhost/hasura-auth-js@2.5.6

## @nhost/react@3.5.6

### Patch Changes

-   @nhost/nhost-js@3.1.9

## @nhost/vue@2.6.6

### Patch Changes

-   @nhost/nhost-js@3.1.9

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

### Major Changes

-   cffdec5: feat: rewrite example using shadcn ui components

### Patch Changes

-   @nhost/react@3.5.6
-   @nhost/react-apollo@12.0.6

## @nhost/dashboard@1.28.0

### Minor Changes

- 526183a: feat: allow filtering users in "make request as" in graphql
section
-   be3b85b: feat: add conceal errors toggle on auth settings page

### Patch Changes

- 35a2f12: fix: prevent run service details from opening when attempting
to delete
    -   @nhost/react-apollo@12.0.6
    -   @nhost/nextjs@2.1.20

## @nhost/docs@2.17.0

### Minor Changes

- cffdec5: feat: update react quickstart guide to use the nhost react
apollo template
-   4cf6677: feat: update list of postgres extensions

## @nhost-examples/cli@0.3.11

### Patch Changes

-   @nhost/nhost-js@3.1.9

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

### Patch Changes

-   @nhost/react@3.5.6
-   @nhost/react-apollo@12.0.6

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

### Patch Changes

-   @nhost/react@3.5.6

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

### Patch Changes

-   @nhost/react@3.5.6
-   @nhost/react-urql@9.0.6

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

### Patch Changes

-   @nhost/nhost-js@3.1.9

## @nhost-examples/nextjs@0.3.11

### Patch Changes

-   @nhost/react@3.5.6
-   @nhost/react-apollo@12.0.6
-   @nhost/nextjs@2.1.20

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

### Patch Changes

-   @nhost/nhost-js@3.1.9

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

### Patch Changes

-   @nhost/nhost-js@3.1.9

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

### Patch Changes

-   @nhost/react@3.5.6

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

### Patch Changes

-   @nhost/react@3.5.6
-   @nhost/react-apollo@12.0.6

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

### Patch Changes

-   @nhost/nhost-js@3.1.9
-   @nhost/apollo@7.1.6
-   @nhost/vue@2.6.6

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

### Patch Changes

-   @nhost/apollo@7.1.6
-   @nhost/vue@2.6.6

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-09 12:19:53 +01:00
David BM
526183ab88 feat (dashboard): allow filtering users in "make request as" feature of graphql section (#2805)
Resolves #2593
2024-09-09 05:55:49 -04:00
Hassan Ben Jobrane
435b65a65a fix(react-apollo): change relyingParty ID to nhost.io (#2861) 2024-09-04 14:56:54 +01:00
Hassan Ben Jobrane
35a2f1203c fix(e2e): fix run service e2e test (#2859)
### **PR Type**
Bug fix, Enhancement


___

### **Description**
- Fixed the button selector in the end-to-end test for deleting a
service, ensuring the correct button is targeted.
- Refactored the `ServicesList` component to standardize icon sizes and
improve the order of class attributes for better readability.
- Added event stop propagation to the delete service action to prevent
unintended event bubbling.



___



### **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>run.test.ts</strong><dd><code>Fix button selector in
e2e test for service deletion</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

dashboard/e2e/run/run.test.ts

<li>Updated button selector for deleting a service.<br> <li> Removed
unnecessary click action on 'Close' button.<br>


</details>


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

</tr>                    
</table></td></tr><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>ServicesList.tsx</strong><dd><code>Refactor
ServicesList component for consistency and
readability</code></dd></summary>
<hr>

dashboard/src/features/services/components/ServicesList/ServicesList.tsx

<li>Standardized icon size and order of class attributes.<br> <li> Added
event stop propagation for delete service action.<br> <li> Improved code
readability and consistency.<br>


</details>


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

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-09-04 11:39:32 +01:00
David BM
be3b85bbc8 feat (dashboard): add conceal errors toggle on auth settings page (#2858) 2024-09-03 10:07:40 -04:00
Hassan Ben Jobrane
cffdec585c feat: templates: react-apollo (#2834)
### **PR Type**
Enhancement, Tests


___

### **Description**
- Added multiple new UI components including `Layout`, `DropdownMenu`,
`Sheet`, `Form`, `Dialog`, `Table`, `Card`, `Button`, `Alert`,
`OAuthLinks`, and more.
- Integrated Apollo Client for GraphQL queries and mutations in various
components.
- Implemented form handling using `react-hook-form` and validation with
`zod`.
- Refactored `App` component to use a new route structure and removed
Mantine components.
- Updated E2E tests for new UI components and improved test reliability
and readability.
- Removed `pnpm-lock.yaml` 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>45
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>layout.tsx</strong><dd><code>Implement Layout Component
with Navigation and Sign-Out</code>&nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/routes/app/layout.tsx

<li>Added <code>Layout</code> component with navigation and sign-out
functionality.<br> <li> Integrated <code>Tooltip</code>,
<code>DropdownMenu</code>, and <code>Sheet</code> components for UI
<br>elements.<br> <li> Implemented mobile navigation toggle.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>dropdown-menu.tsx</strong><dd><code>Add DropdownMenu
Component with Radix UI Integration</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/ui/dropdown-menu.tsx

<li>Added <code>DropdownMenu</code> component with various
sub-components.<br> <li> Integrated Radix UI primitives for dropdown
functionality.<br> <li> Styled dropdown menu items and content.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>dropdown-menu.tsx</strong><dd><code>Add DropdownMenu
Component with Radix UI Integration</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/ui/dropdown-menu.tsx

<li>Added <code>DropdownMenu</code> component with various
sub-components.<br> <li> Integrated Radix UI primitives for dropdown
functionality.<br> <li> Styled dropdown menu items and content.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>security-keys.tsx</strong><dd><code>Add SecurityKeys
Component for Managing Security Keys</code>&nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

examples/react-apollo/src/components/profile/security-keys.tsx

<li>Added <code>SecurityKeys</code> component for managing security
keys.<br> <li> Integrated Apollo Client for GraphQL queries and
mutations.<br> <li> Implemented form validation with
<code>react-hook-form</code> and <code>zod</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-20c5d7ececb3f500fc179a36ec957b0744197e88ca47d050e29b401967781be3">+178/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>App.tsx</strong><dd><code>Refactor App Component with
New Route Structure</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/App.tsx

<li>Refactored <code>App</code> component to use new route
structure.<br> <li> Added routes for authentication and application
pages.<br> <li> Removed Mantine components and replaced with custom
layout.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-cce00ef2ed124ca9c4fb6d5a27065cfb227de957db19fee484d79526bd243405">+48/-123</a></td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>layout.tsx</strong><dd><code>Implement Layout Component
with Navigation and Sign-Out</code>&nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/routes/app/layout.tsx

<li>Added <code>Layout</code> component with navigation and sign-out
functionality.<br> <li> Integrated <code>Tooltip</code> and
<code>Sheet</code> components for UI elements.<br> <li> Implemented
mobile navigation toggle.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>protected-notes.tsx</strong><dd><code>Add
ProtectedNotes Component for Managing Protected
Notes</code></dd></summary>
<hr>

examples/react-apollo/src/components/routes/app/protected-notes.tsx

<li>Added <code>ProtectedNotes</code> component for managing protected
notes.<br> <li> Integrated Apollo Client for GraphQL queries and
mutations.<br> <li> Implemented permission elevation for secure
actions.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sheet.tsx</strong><dd><code>Add Sheet Component with
Radix UI Integration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/ui/sheet.tsx

<li>Added <code>Sheet</code> component with various sub-components.<br>
<li> Integrated Radix UI primitives for sheet functionality.<br> <li>
Styled sheet content and overlay.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sheet.tsx</strong><dd><code>Add Sheet Component with
Radix UI Integration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/ui/sheet.tsx

<li>Added <code>Sheet</code> component with various sub-components.<br>
<li> Integrated Radix UI primitives for sheet functionality.<br> <li>
Styled sheet content and overlay.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>form.tsx</strong><dd><code>Add Form Component with
React Hook Form Integration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/ui/form.tsx

<li>Added <code>Form</code> component with various sub-components.<br>
<li> Integrated <code>react-hook-form</code> for form handling.<br> <li>
Styled form fields and messages.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-in-email-password.tsx</strong><dd><code>Add
SignInEmailPassword Component for Email/Password
Sign-In</code></dd></summary>
<hr>


examples/react-apollo/src/components/routes/auth/sign-in/sign-in-email-password.tsx

<li>Added <code>SignInEmailPassword</code> component for email/password
sign-in.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>form.tsx</strong><dd><code>Add Form Component with
React Hook Form Integration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/ui/form.tsx

<li>Added <code>Form</code> component with various sub-components.<br>
<li> Integrated <code>react-hook-form</code> for form handling.<br> <li>
Styled form fields and messages.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-5367598c9a64936b77080af5c57bd2110e2e835c23b306974b8d84fe8295e7f7">+168/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-up-email-password.tsx</strong><dd><code>Add
SignUpEmailPassword Component for Email/Password
Sign-Up</code></dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/routes/auth/sign-up/sign-up-email-password.tsx

<li>Added <code>SignUpEmailPassword</code> component for email/password
sign-up.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-4507fe991337f37a30f72f9411947046df23427c37b97e8c357c609d98288ff4">+143/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-up-email-password.tsx</strong><dd><code>Add
SignUpEmailPassword Component for Email/Password
Sign-Up</code></dd></summary>
<hr>


examples/react-apollo/src/components/routes/auth/sign-up/sign-up-email-password.tsx

<li>Added <code>SignUpEmailPassword</code> component for email/password
sign-up.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-4192d80c13ac7212e63c5ad0729fb4b509ac4ff8876fb919be24ab93dbfbdfcc">+143/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>dialog.tsx</strong><dd><code>Add Dialog Component with
Radix UI Integration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/ui/dialog.tsx

<li>Added <code>Dialog</code> component with various sub-components.<br>
<li> Integrated Radix UI primitives for dialog functionality.<br> <li>
Styled dialog content and overlay.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-233c2734414990fe89dc642d88a85cf8964f3c139c4fdb3b8d1845616118d41a">+120/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>dialog.tsx</strong><dd><code>Add Dialog Component with
Radix UI Integration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/ui/dialog.tsx

<li>Added <code>Dialog</code> component with various sub-components.<br>
<li> Integrated Radix UI primitives for dialog functionality.<br> <li>
Styled dialog content and overlay.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>todos.tsx</strong><dd><code>Add Todos Component for
Managing To-Do Items</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/routes/app/todos.tsx

<li>Added <code>Todos</code> component for managing to-do items.<br>
<li> Integrated Apollo Client for GraphQL queries and mutations.<br>
<li> Implemented UI for adding and deleting to-dos.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-56a00354330376b2f9bf79a2504fe05450ae972b4206b949f26ac58458c2c6aa">+132/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>upload-multiple-files.tsx</strong><dd><code>Add
UploadMultipleFiles Component for Multiple File
Uploads</code></dd></summary>
<hr>

examples/react-apollo/src/components/storage/upload-multiple-files.tsx

<li>Added <code>UploadMultipleFiles</code> component for uploading
multiple files.<br> <li> Integrated <code>react-dropzone</code> for file
drag-and-drop.<br> <li> Styled file upload progress and status
indicators.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-in-email-password.tsx</strong><dd><code>Add
SignInEmailPassword Component for Email/Password
Sign-In</code></dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/routes/auth/sign-in/sign-in-email-password.tsx

<li>Added <code>SignInEmailPassword</code> component for email/password
sign-in.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-up-security-key.tsx</strong><dd><code>Add
SignUpSecurityKey Component for Security Key Sign-Up</code>&nbsp;
</dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/routes/auth/sign-up/sign-up-security-key.tsx

<li>Added <code>SignUpSecurityKey</code> component for security key
sign-up.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-up-security-key.tsx</strong><dd><code>Add
SignUpSecurityKey Component for Security Key Sign-Up</code>&nbsp;
</dd></summary>
<hr>


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

<li>Added <code>SignUpSecurityKey</code> component for security key
sign-up.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-in-security-key.tsx</strong><dd><code>Add
SignInSecurityKey Component for Security Key Sign-In</code>&nbsp;
</dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/routes/auth/sign-in/sign-in-security-key.tsx

<li>Added <code>SignInSecurityKey</code> component for security key
sign-in.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-95755a5056e7f57caa2eeaec3801e06382792395f7436c60f3e4d0b7382c2703">+99/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-in-security-key.tsx</strong><dd><code>Add
SignInSecurityKey Component for Security Key Sign-In</code>&nbsp;
</dd></summary>
<hr>


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

<li>Added <code>SignInSecurityKey</code> component for security key
sign-in.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-in-magic-link.tsx</strong><dd><code>Add
SignInMagicLink Component for Magic Link Sign-In</code>&nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/routes/auth/sign-in/sign-in-magic-link.tsx

<li>Added <code>SignInMagicLink</code> component for magic link
sign-in.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-6ad692dad5c7240a085ab15146cc515308c295c6118562e81b41db89154a7f2a">+98/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-up-magic-link.tsx</strong><dd><code>Add
SignUpMagicLink Component for Magic Link Sign-Up</code>&nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/routes/auth/sign-up/sign-up-magic-link.tsx

<li>Added <code>SignUpMagicLink</code> component for magic link
sign-up.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-756d7107028cebec7b2f6da051f9003fa7689f33083e2388af887dc9de5886c9">+98/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-in-magic-link.tsx</strong><dd><code>Add
SignInMagicLink Component for Magic Link Sign-In</code>&nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


examples/react-apollo/src/components/routes/auth/sign-in/sign-in-magic-link.tsx

<li>Added <code>SignInMagicLink</code> component for magic link
sign-in.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-05d6dec736df35bf6b430e76320dff84744131f602c2d3752f82df43180f9660">+98/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-up-magic-link.tsx</strong><dd><code>Add
SignUpMagicLink Component for Magic Link Sign-Up</code>&nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


examples/react-apollo/src/components/routes/auth/sign-up/sign-up-magic-link.tsx

<li>Added <code>SignUpMagicLink</code> component for magic link
sign-up.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>change-password.tsx</strong><dd><code>Refactor
ChangePassword Component with New UI Components</code>&nbsp;
</dd></summary>
<hr>

examples/react-apollo/src/components/profile/change-password.tsx

<li>Refactored <code>ChangePassword</code> component with new UI
components.<br> <li> Integrated Apollo Client for GraphQL queries.<br>
<li> Implemented form validation with <code>react-hook-form</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-d4ea801f2778896b2e41f1f62e1d335b6b77a490a95adac7786bf896b1fd1bac">+40/-33</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>table.tsx</strong><dd><code>Add Table Component with
Styling and Utility Functions</code>&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/ui/table.tsx

<li>Added <code>Table</code> component with various sub-components.<br>
<li> Styled table header, body, and rows.<br> <li> Integrated utility
functions for class names.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>forgot-password.tsx</strong><dd><code>Add
ForgotPassword Component for Password Reset</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/routes/auth/forgot-password.tsx

<li>Added <code>ForgotPassword</code> component for password reset.<br>
<li> Integrated <code>react-hook-form</code> for form handling.<br> <li>
Implemented password reset functionality.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>mfa.tsx</strong><dd><code>Add Mfa Component for
Managing Multi-Factor Authentication</code></dd></summary>
<hr>

examples/react-apollo/src/components/profile/mfa.tsx

<li>Added <code>Mfa</code> component for managing multi-factor
authentication.<br> <li> Integrated Apollo Client for GraphQL
queries.<br> <li> Implemented QR code generation and activation.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>storage.tsx</strong><dd><code>Add Storage Component for
Managing File Storage</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/routes/app/storage.tsx

<li>Added <code>Storage</code> component for managing file storage.<br>
<li> Integrated Apollo Client for GraphQL queries.<br> <li> Implemented
file download functionality.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>change-email.tsx</strong><dd><code>Add ChangeEmail
Component for Changing User Email</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/profile/change-email.tsx

<li>Added <code>ChangeEmail</code> component for changing user
email.<br> <li> Integrated Apollo Client for GraphQL queries.<br> <li>
Implemented form validation with <code>react-hook-form</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-96cca643537cb9da5002543183dd894548fc0d7e34945686da8b966de79ec998">+91/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>forgot-password.tsx</strong><dd><code>Add
ForgotPassword Component for Password Reset</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/routes/auth/forgot-password.tsx

<li>Added <code>ForgotPassword</code> component for password reset.<br>
<li> Integrated <code>react-hook-form</code> for form handling.<br> <li>
Implemented password reset functionality.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>upload-single-file.tsx</strong><dd><code>Add
UploadSingleFile Component for Single File Upload</code>&nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/storage/upload-single-file.tsx

<li>Added <code>UploadSingleFile</code> component for uploading a single
file.<br> <li> Integrated <code>react-dropzone</code> for file
drag-and-drop.<br> <li> Styled file upload progress and status
indicators.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>upload-single-file.tsx</strong><dd><code>Add
UploadSingleFile Component for Single File Upload</code>&nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/storage/upload-single-file.tsx

<li>Added <code>UploadSingleFile</code> component for uploading a single
file.<br> <li> Integrated <code>react-dropzone</code> for file
drag-and-drop.<br> <li> Styled file upload progress and status
indicators.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>card.tsx</strong><dd><code>Add Card Component with
Styling and Utility Functions</code>&nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/ui/card.tsx

<li>Added <code>Card</code> component with various sub-components.<br>
<li> Styled card header, content, and footer.<br> <li> Integrated
utility functions for class names.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-6c08563777faac205d377afaf3160c75e80474663f2554e5dc23c691b82f2cfb">+79/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>card.tsx</strong><dd><code>Add Card Component with
Styling and Utility Functions</code>&nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

examples/react-apollo/src/components/ui/card.tsx

<li>Added <code>Card</code> component with various sub-components.<br>
<li> Styled card header, content, and footer.<br> <li> Integrated
utility functions for class names.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>connect-github.tsx</strong><dd><code>Add ConnectGithub
Component for Connecting GitHub Account</code></dd></summary>
<hr>

examples/react-apollo/src/components/profile/connect-github.tsx

<li>Added <code>ConnectGithub</code> component for connecting GitHub
account.<br> <li> Integrated Apollo Client for GraphQL queries.<br> <li>
Implemented GitHub connection status and link.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>oauth-links.tsx</strong><dd><code>Add OAuthLinks
Component for Social Sign-In Options</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/auth/oauth-links.tsx

<li>Added <code>OAuthLinks</code> component for social sign-in
options.<br> <li> Integrated provider links for GitHub, Google, Apple,
and LinkedIn.<br> <li> Styled social sign-in buttons.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>oauth-links.tsx</strong><dd><code>Add OAuthLinks
Component for Social Sign-In Options</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/auth/oauth-links.tsx

<li>Added <code>OAuthLinks</code> component for social sign-in
options.<br> <li> Integrated provider links for GitHub, Google, Apple,
and LinkedIn.<br> <li> Styled social sign-in buttons.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-97b7775bbdf0f75091067d77b6638f6a81a15467e2ab080a769602c7ab345010">+56/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>App.tsx</strong><dd><code>Add App Component with Route
Structure</code>&nbsp; &nbsp; &nbsp; &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/src/App.tsx

<li>Added <code>App</code> component with route structure.<br> <li>
Integrated authentication and application routes.<br> <li> Implemented
layout and page components.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>button.tsx</strong><dd><code>Add Button Component with
Styling and Utility Functions</code>&nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/ui/button.tsx

<li>Added <code>Button</code> component with various styles.<br> <li>
Integrated utility functions for class names.<br> <li> Styled button
variants and sizes.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>button.tsx</strong><dd><code>Add Button Component with
Styling and Utility Functions</code>&nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/ui/button.tsx

<li>Added <code>Button</code> component with various styles.<br> <li>
Integrated utility functions for class names.<br> <li> Styled button
variants and sizes.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-66624bef5f7762c254328a05baf2e434d8a431d2778ba5d51de1fb8d603090d2">+50/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>alert.tsx</strong><dd><code>Add Alert Component with
Styling and Utility Functions</code>&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/ui/alert.tsx

<li>Added <code>Alert</code> component with various styles.<br> <li>
Integrated utility functions for class names.<br> <li> Styled alert
variants and descriptions.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-223df6c627e0f93e279f9570b192ed98ac8c5cbe0e5dd553ce0e5dd6b8cbff8c">+59/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

</table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>6
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>utils.ts</strong><dd><code>Update Utility Functions for
E2E Tests</code>&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/e2e/utils.ts

<li>Updated utility functions for E2E tests.<br> <li> Modified selectors
and actions for new UI components.<br> <li> Improved test reliability
and readability.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-3bdd9b675af03a22eb7e8077183e8179504a9c3a085980da4938fd0c5e4b8907">+15/-19</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>file-upload.test.ts</strong><dd><code>Update File
Upload E2E Tests</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>

examples/react-apollo/e2e/authenticated/file-upload.test.ts

<li>Updated file upload E2E tests.<br> <li> Modified selectors and
actions for new UI components.<br> <li> Improved test reliability and
readability.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-ad496daecb0035a49fe21b4b54182bcca8188a20475bcc79a21bd433914a9200">+21/-13</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>change-email.test.ts</strong><dd><code>Update Change
Email E2E Tests</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>

examples/react-apollo/e2e/authenticated/change-email.test.ts

<li>Updated change email E2E tests.<br> <li> Modified selectors and
actions for new UI components.<br> <li> Improved test reliability and
readability.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-5bf556a7e19bcc9932603bd52dd41929f1cabd65924ea88ad4123efcd9daad13">+26/-9</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>apollo.test.ts</strong><dd><code>Update Apollo E2E
Tests</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; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/e2e/authenticated/apollo.test.ts

<li>Updated Apollo E2E tests.<br> <li> Modified selectors and actions
for new UI components.<br> <li> Improved test reliability and
readability.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-56686466b936643bcf1a1b180a367f11e52a123df8f1298f31a9eb077ddd4ffd">+13/-9</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>change-password.test.ts</strong><dd><code>Update Change
Password E2E Tests</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>

examples/react-apollo/e2e/authenticated/change-password.test.ts

<li>Updated change password E2E tests.<br> <li> Modified selectors and
actions for new UI components.<br> <li> Improved test reliability and
readability.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>email-password.test.ts</strong><dd><code>Update
Email/Password Sign-In E2E Tests</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/e2e/sign-in/email-password.test.ts

<li>Updated email/password sign-in E2E tests.<br> <li> Modified
selectors and actions for new UI components.<br> <li> Improved test
reliability and readability.<br>


</details>


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

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-09-03 09:40:58 +01:00
Hassan Ben Jobrane
8b12426157 fix(hasura-auth-js): update signout to use accessToken when clearing all sessions (#2857)
### **User description**
fixes https://github.com/nhost/nhost/issues/2836


___

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


___

### **Description**
- Enhanced the signout process to utilize the access token when clearing
all sessions.
- Updated the `signingOut` state to manage both access and refresh
tokens.
- Introduced a new function `destroyAccessToken` to clear the access
token.
- Adjusted type definitions to reflect changes in the signout process.
- Added a changeset document to outline the patch 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>machine.ts</strong><dd><code>Enhance signout process to
handle access token</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

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

<li>Updated <code>signingOut</code> state to handle access token.<br>
<li> Modified <code>clearContextExceptTokens</code> to retain access
token.<br> <li> Added <code>destroyAccessToken</code> function to clear
access token.<br> <li> Updated <code>signout</code> function to
optionally use access token.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>machine.typegen.ts</strong><dd><code>Update type
definitions for signout enhancements</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

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

<li>Updated type definitions for
<code>clearContextExceptTokens</code>.<br> <li> Added type definitions
for <code>destroyAccessToken</code>.<br>


</details>


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

</tr>                    
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>popular-rabbits-bake.md</strong><dd><code>Add changeset
for signout 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; </dd></summary>
<hr>

.changeset/popular-rabbits-bake.md

- Added changeset for signout fix using access token.



</details>


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

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-09-02 14:28:13 +01:00
David Barroso
4cf6677284 feat (docs): update list of postgres extensions (#2852)
### **PR Type**
Documentation, Enhancement, Other


___

### **Description**
- Updated the documentation to include new PostgreSQL extensions:
`hypopg`, `http`, `pg_hashids`, and `pg_squeeze`.
- Provided detailed installation and uninstallation instructions for
each new extension.
- Updated the `timescaledb` extension information and resources.
- Addressed vulnerabilities by updating the `svelte` dependency in the
SvelteKit quickstart example.
- Added `webpack` as a new dependency in the main `package.json`.



___



### **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>hungry-news-film.md</strong><dd><code>Add changeset
entry for PostgreSQL extensions update</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

.changeset/hungry-news-film.md

<li>Added changeset entry for updating the list of PostgreSQL
extensions.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>extensions.mdx</strong><dd><code>Update and expand
PostgreSQL extensions documentation</code>&nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

docs/guides/database/extensions.mdx

<li>Added new PostgreSQL extensions: <code>hypopg</code>,
<code>http</code>, <code>pg_hashids</code>, <code>pg_squeeze</code>.<br>
<li> Updated information for <code>timescaledb</code>.<br> <li> Provided
installation and uninstallation instructions for each <br>extension.<br>
<li> Included resource links for each extension.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2852/files#diff-7a41fa45d84db83a8c01a76ddb42ad614022ad94a4c3a6aa321f5b9a5300da8c">+73/-23</a>&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 Svelte dependency
version in package.json</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/quickstarts/sveltekit/package.json

- Updated `svelte` dependency version to `^4.2.19`.



</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Add webpack dependency
to package.json</code>&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 `webpack` dependency with version `^5.94.0`.



</details>


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

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions

---------

Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-09-02 08:37:56 +02:00
github-actions[bot]
fdaaf19057 chore: update versions (#2844)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@1.27.0

### Minor Changes

-   a7cd02c: fix: resolve rate limit query

## @nhost/docs@2.16.0

### Minor Changes

-   ba55c1b: feat: run: added a guide on using a private registry
-   3d70c63: feat: added rate-limiter guide for auth service

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-27 14:51:52 +01:00
David BM
a7cd02c965 fix (dashboard): resolve rate limit query (#2845)
### **PR Type**
Bug fix, Enhancement


___

### **Description**
- Removed the 'Auth' switch from the `AuthLimitingForm` component to
simplify the settings interface.
- Updated the rate limit query in `useGetRateLimits` hook to resolve by
default, fixing a potential issue.
- Added a changeset to document the fix for the rate limit query.



___



### **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>AuthLimitingForm.tsx</strong><dd><code>Remove 'Auth'
switch from AuthLimitingForm component</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>


dashboard/src/features/projects/rate-limiting/settings/components/AuthLimitingForm/AuthLimitingForm.tsx

<li>Removed the 'Auth' switch from the settings container.<br> <li>
Simplified the form component by removing unused props.<br>


</details>


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

</tr>                    
</table></td></tr><tr><td><strong>Bug fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>useGetRateLimits.ts</strong><dd><code>Update rate limit
query to resolve by default</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/projects/rate-limiting/settings/hooks/useGetRateLimits/useGetRateLimits.ts

- Changed the 'resolve' variable to true in the rate limit query.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2845/files#diff-82e380da100404643bd31504d42eb4d27a406dd9e1cccffc17b9dbcb0df5e8fa">+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-bears-confess.md</strong><dd><code>Add changeset
for rate limit query 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/smooth-bears-confess.md

- Added a changeset for the rate limit query fix.



</details>


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

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions

---------

Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-08-27 14:45:08 +01:00
David Barroso
3d70c63d1b feat (docs): added docs about rate-limits (#2812) 2024-08-27 15:17:03 +02:00
David Barroso
ba55c1b779 feat (docs): run: added a guide on using a private registry (#2843) 2024-08-27 12:36:09 +02:00
github-actions[bot]
852f13b273 chore: update versions (#2824)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/apollo@7.1.5

### Patch Changes

-   @nhost/nhost-js@3.1.8

## @nhost/react-apollo@12.0.5

### Patch Changes

-   @nhost/apollo@7.1.5
-   @nhost/react@3.5.5

## @nhost/react-urql@9.0.5

### Patch Changes

-   @nhost/react@3.5.5

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

### Patch Changes

- caa8bd7: fix: add error handling logic to transition to the signedOut
state when the token is invalid or expired

## @nhost/nextjs@2.1.19

### Patch Changes

-   @nhost/react@3.5.5

## @nhost/nhost-js@3.1.8

### Patch Changes

-   Updated dependencies [caa8bd7]
    -   @nhost/hasura-auth-js@2.5.5

## @nhost/react@3.5.5

### Patch Changes

-   @nhost/nhost-js@3.1.8

## @nhost/vue@2.6.5

### Patch Changes

-   @nhost/nhost-js@3.1.8

## @nhost/dashboard@1.26.0

### Minor Changes

-   3773ad7: chore: update pricing information
- b63250d: fix: not allow run service creation form resubmission while
creating a run service
-   a44a1d4: feat: add rate limits settings page

### Patch Changes

-   @nhost/react-apollo@12.0.5
-   @nhost/nextjs@2.1.19

## @nhost/docs@2.15.0

### Minor Changes

-   40c0d7b: │feat: added subdomain/region information
-   a18b545: feat: added postgres upgrade docs

## @nhost-examples/cli@0.3.10

### Patch Changes

-   @nhost/nhost-js@3.1.8

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

### Patch Changes

-   @nhost/react@3.5.5
-   @nhost/react-apollo@12.0.5

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

### Patch Changes

-   @nhost/react@3.5.5

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

### Patch Changes

-   @nhost/react@3.5.5
-   @nhost/react-urql@9.0.5

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

### Patch Changes

-   @nhost/nhost-js@3.1.8

## @nhost-examples/nextjs@0.3.10

### Patch Changes

-   @nhost/react@3.5.5
-   @nhost/react-apollo@12.0.5
-   @nhost/nextjs@2.1.19

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

### Patch Changes

-   @nhost/nhost-js@3.1.8

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

### Patch Changes

-   @nhost/nhost-js@3.1.8

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

### Patch Changes

-   @nhost/react@3.5.5
-   @nhost/react-apollo@12.0.5

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

### Patch Changes

-   @nhost/react@3.5.5

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

### Patch Changes

-   @nhost/react@3.5.5
-   @nhost/react-apollo@12.0.5

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

### Patch Changes

-   @nhost/nhost-js@3.1.8
-   @nhost/apollo@7.1.5
-   @nhost/vue@2.6.5

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

### Patch Changes

-   @nhost/apollo@7.1.5
-   @nhost/vue@2.6.5

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-27 10:48:43 +01:00
David BM
a44a1d48d6 feat (dashboard): change rate limits from dashboard (#2832) 2024-08-26 18:47:06 +02:00
David BM
b63250d1cb fix (dashboard): not allow run service creation form resubmission while creating a run service (#2838) 2024-08-23 15:20:27 +02:00
Hassan Ben Jobrane
caa8bd75ec fix(hasura-auth-js): transition to the signedOut state when the token is invalid or expired (#2835)
### **User description**
fixes https://github.com/nhost/nhost/issues/2817


___

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


___

### **Description**
- Added error handling logic to transition to the `signedOut` state when
the token is invalid or expired.
- Updated the authentication machine to handle 401 errors by signing out
the user.
- Enhanced test cases to verify the new behavior of signing out on
unauthorized errors.
- Updated Hasura page teardown logic to ensure the first matching
element is clicked.
- Added `micromatch` to the audit-ci allowlist for dependency
management.



___



### **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>machine.ts</strong><dd><code>Add error handling for
unauthorized token refresh</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

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

<li>Added error handling logic to transition to <code>signedOut</code>
state on <br>unauthorized error.<br> <li> Introduced a new condition
<code>isUnauthorizedError</code> to check for 401 <br>status.<br> <li>
Reordered imports for better organization.<br>


</details>


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

</tr>                    
</table></td></tr><tr><td><strong>Tests</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>refreshToken.test.ts</strong><dd><code>Update token
refresh test for unauthorized error handling</code></dd></summary>
<hr>

packages/hasura-auth-js/tests/refreshToken.test.ts

<li>Updated test to expect sign out on unauthorized error during token
<br>refresh.<br> <li> Adjusted test logic to match new authentication
state transitions.<br>


</details>


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

</tr>                    
</table></td></tr><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>global-teardown.ts</strong><dd><code>Update Hasura
locator to click first matching element</code>&nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

dashboard/global-teardown.ts

<li>Updated locator to click the first matching element for Hasura page
<br>teardown.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>machine.typegen.ts</strong><dd><code>Update type
definitions for unauthorized error handling</code>&nbsp; &nbsp;
</dd></summary>
<hr>

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

- Added `isUnauthorizedError` to type definitions.



</details>


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

</tr>                    
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>silent-lies-smoke.md</strong><dd><code>Document bug fix
for invalid token handling</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.changeset/silent-lies-smoke.md

<li>Documented the bug fix for transitioning to <code>signedOut</code>
state on invalid <br>token.<br>


</details>


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

</tr>                    
</table></td></tr><tr><td><strong>Configuration
changes</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>audit-ci.jsonc</strong><dd><code>Update audit-ci
allowlist with micromatch</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

audit-ci.jsonc

- Added `micromatch` to the audit-ci allowlist.



</details>


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

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-08-23 09:29:33 +01:00
David Barroso
40c0d7b914 feat (docs): added subdomain/region information (#2820) 2024-08-19 14:07:53 +02:00
David BM
3773ad7cca chore (dashboard): update pricing information (#2827)
Resolves #2822
2024-08-15 14:04:10 +02:00
Hassan Ben Jobrane
6f122521e9 fix: eval vulnerabilities (#2828)
### **PR Type**
enhancement, dependencies


___

### **Description**
- Removed `trim-newlines` from the `audit-ci` allowlist to address
potential vulnerabilities.
- Added `axios` version 1.7.4 to `package.json` resolutions to fix a
vulnerability.
- Updated `axios`, `@nhost/hasura-auth-js`, and `@nhost/nhost-js`
versions in `pnpm-lock.yaml` to ensure compatibility and security.



___



### **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 audit-ci
allowlist configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

audit-ci.jsonc

- Removed `trim-newlines` from the allowlist.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2828/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 axios version to
package resolutions</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

package.json

- Added `axios` version 1.7.4 to resolutions.



</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>pnpm-lock.yaml</strong><dd><code>Update dependencies in
pnpm-lock file</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

pnpm-lock.yaml

<li>Updated <code>axios</code> to version 1.7.4.<br> <li> Updated
<code>@nhost/hasura-auth-js</code> to version 2.5.4.<br> <li> Updated
<code>@nhost/nhost-js</code> to version 3.1.7.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2828/files#diff-32824c984905bb02bc7ffcef96a77addd1f1602cff71a11fbbfdd7f53ee026bb">+11/-10</a>&nbsp;
</td>

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-08-15 11:39:09 +01:00
David Barroso
a18b545d2a feat (docs): added postgres upgrade docs (#2823) 2024-08-13 10:42:08 +02:00
github-actions[bot]
0263cc9e92 chore: update versions (#2804)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/apollo@7.1.4

### Patch Changes

-   @nhost/nhost-js@3.1.7

## @nhost/react-apollo@12.0.4

### Patch Changes

-   @nhost/apollo@7.1.4
-   @nhost/react@3.5.4

## @nhost/react-urql@9.0.4

### Patch Changes

-   @nhost/react@3.5.4

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

### Patch Changes

-   4564232: chore: update `clientStorage` docs and add usage examples

## @nhost/nextjs@2.1.18

### Patch Changes

-   @nhost/react@3.5.4

## @nhost/nhost-js@3.1.7

### Patch Changes

-   Updated dependencies [4564232]
    -   @nhost/hasura-auth-js@2.5.4

## @nhost/react@3.5.4

### Patch Changes

-   @nhost/nhost-js@3.1.7

## @nhost/vue@2.6.4

### Patch Changes

-   @nhost/nhost-js@3.1.7

## @nhost/dashboard@1.25.0

### Minor Changes

- d1ceede: feat: add setting to migrate postgres major and/or minor
versions
- e5d3d1a: fix: allow manually typing column for custom check in
database row permissions

### Patch Changes

-   @nhost/react-apollo@12.0.4
-   @nhost/nextjs@2.1.18

## @nhost/docs@2.14.3

### Patch Changes

-   4564232: chore: update `clientStorage` docs and add usage examples

## @nhost-examples/cli@0.3.9

### Patch Changes

-   @nhost/nhost-js@3.1.7

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

### Patch Changes

-   @nhost/react@3.5.4
-   @nhost/react-apollo@12.0.4

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

### Patch Changes

-   @nhost/react@3.5.4

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

### Patch Changes

-   @nhost/react@3.5.4
-   @nhost/react-urql@9.0.4

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

### Patch Changes

-   @nhost/nhost-js@3.1.7

## @nhost-examples/nextjs@0.3.9

### Patch Changes

-   @nhost/react@3.5.4
-   @nhost/react-apollo@12.0.4
-   @nhost/nextjs@2.1.18

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

### Patch Changes

-   @nhost/nhost-js@3.1.7

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

### Patch Changes

-   @nhost/nhost-js@3.1.7

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

### Patch Changes

-   @nhost/react@3.5.4
-   @nhost/react-apollo@12.0.4

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

### Patch Changes

-   @nhost/react@3.5.4

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

### Patch Changes

-   @nhost/react@3.5.4
-   @nhost/react-apollo@12.0.4

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

### Patch Changes

-   @nhost/nhost-js@3.1.7
-   @nhost/apollo@7.1.4
-   @nhost/vue@2.6.4

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

### Patch Changes

-   @nhost/apollo@7.1.4
-   @nhost/vue@2.6.4

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-12 11:50:40 +01:00
David BM
d1ceedef05 feat (dashboard): UI for postgres migration (#2796)
Resolves #2748
2024-08-12 12:17:14 +02:00
Hassan Ben Jobrane
bdd84dd3ca chore: add e2e tests for run and ai pages (#2806)
### **User description**
resolves https://github.com/nhost/nhost/issues/2665


___

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


___

### **Description**
- Added e2e tests for creating and deleting run services, Assistants,
and Auto-Embeddings.
- Improved stability of PAT creation and deletion test by replacing
`waitForLoadState` with `waitForTimeout`.
- Added environment variables for pro test project in `env.ts`.
- Updated CI workflow to include `NHOST_PRO_TEST_PROJECT_NAME`
environment variable.
- Updated selectors and minor formatting changes for consistency.
- Addressed `fast-xml-parser` vulnerability by adding it to
dependencies.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Tests</strong></td><td><details><summary>4
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>manage-pat.test.ts</strong><dd><code>Improve stability
of PAT creation and deletion test</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

dashboard/e2e/account/pat/manage-pat.test.ts

<li>Replaced <code>waitForLoadState</code> with
<code>waitForTimeout</code> for better stability.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>assistants.test.ts</strong><dd><code>Add e2e test for
Assistants management</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/e2e/ai/assistants.test.ts

<li>Added e2e test for creating and deleting Assistants.<br> <li>
Utilized <code>openProject</code> utility for navigation.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2806/files#diff-95533e004b514add57a2c87201a68cac11c20ffa458afd78e045ed89559e7546">+60/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>auto-embeddings.test.ts</strong><dd><code>Add e2e test
for Auto-Embeddings management</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

dashboard/e2e/ai/auto-embeddings.test.ts

<li>Added e2e test for creating and deleting Auto-Embeddings.<br> <li>
Utilized <code>openProject</code> utility for navigation.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>run.test.ts</strong><dd><code>Add e2e test for run
services management</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

dashboard/e2e/run/run.test.ts

<li>Added e2e test for creating and deleting run services.<br> <li>
Utilized <code>openProject</code> utility for navigation.<br>


</details>


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

</tr>                    
</table></details></td></tr><tr><td><strong>Configuration
changes</strong></td><td><details><summary>2 files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>env.ts</strong><dd><code>Add environment variables for
pro test project</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/e2e/env.ts

- Added environment variables for pro test project.



</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>ci.yaml</strong><dd><code>Update CI workflow with pro
test project variable</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

.github/workflows/ci.yaml

- Added `NHOST_PRO_TEST_PROJECT_NAME` environment variable.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2806/files#diff-944291df2c9c06359d37cc8833d182d705c9e8c3108e7cfe132d61a06e9133dd">+1/-0</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>
    <details>
<summary><strong>global-teardown.ts</strong><dd><code>Update SQL link
selector in global teardown</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

dashboard/global-teardown.ts

- Updated selector for SQL link to use `data-test` attribute.



</details>


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

</tr>                    

</table></details></td></tr><tr><td><strong>Formatting</strong></td><td><details><summary>1
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>index.tsx</strong><dd><code>Minor formatting updates
for services page</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

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

- Minor formatting changes for consistency.



</details>


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

</tr>                    

</table></details></td></tr><tr><td><strong>Dependencies</strong></td><td><details><summary>2
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Add fast-xml-parser
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;
</dd></summary>
<hr>

package.json

- Added `fast-xml-parser` dependency.



</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>pnpm-lock.yaml</strong><dd><code>Update dependencies
and versions in lock file</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

pnpm-lock.yaml

<li>Updated dependencies and their versions.<br> <li> Added
<code>fast-xml-parser</code> dependency.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2806/files#diff-32824c984905bb02bc7ffcef96a77addd1f1602cff71a11fbbfdd7f53ee026bb">+91/-75</a>&nbsp;
</td>

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-08-06 15:24:09 +01:00
Hassan Ben Jobrane
45642322f4 chore: update clientStorage documentation for Capacitor and add usage examples (#2799)
### **User description**
closes https://github.com/nhost/nhost/issues/2237


___

### **PR Type**
Documentation


___

### **Description**
- Enhanced `clientStorage` documentation across multiple files with
detailed usage examples.
- Added specific instructions for Capacitor versions < 4 and >= 4.
- Included new documentation file for `AuthOptions`.
- Added changesets for documentation updates.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Documentation</strong></td><td><details><summary>9
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>client.ts</strong><dd><code>Enhanced `clientStorage`
documentation with usage examples</code></dd></summary>
<hr>

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

<li>Added detailed usage examples for different
<code>clientStorageType</code> values.<br> <li> Included specific
instructions for Capacitor versions < 4 and >= 4.<br> <li> Updated
documentation for <code>react-native</code>, <code>capacitor</code>, and
<br><code>expo-secure-store</code>.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>five-avocados-complain.md</strong><dd><code>Added
changeset for `clientStorage` documentation update</code>&nbsp;
</dd></summary>
<hr>

.changeset/five-avocados-complain.md

- Added changeset for `@nhost/hasura-auth-js` with patch update.



</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>orange-pears-hug.md</strong><dd><code>Added changeset
for `clientStorage` documentation update</code>&nbsp; </dd></summary>
<hr>

.changeset/orange-pears-hug.md

- Added changeset for `@nhost/docs` with patch update.



</details>


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

</tr>                    

<tr>
  <td>
    <details>

<summary><strong>nhost-auth-constructor-params.mdx</strong><dd><code>Updated
`clientStorage` documentation with examples</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/reference/javascript/auth/types/nhost-auth-constructor-params.mdx

<li>Updated <code>clientStorage</code> documentation with detailed usage
examples.<br> <li> Included specific instructions for Capacitor versions
< 4 and >= 4.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>auth-options.mdx</strong><dd><code>Added documentation
for `AuthOptions` with examples</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

docs/reference/javascript/nhost-js/types/auth-options.mdx

<li>Added new documentation file for <code>AuthOptions</code>.<br> <li>
Included detailed usage examples for different
<code>clientStorageType</code> <br>values.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2799/files#diff-6ce5cc2ba44a9038a1184472752551f699673a894c8decde0b436c90272bedaf">+126/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>

<summary><strong>nhost-client-constructor-params.mdx</strong><dd><code>Updated
`clientStorage` documentation with examples</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


docs/reference/javascript/nhost-js/types/nhost-client-constructor-params.mdx

<li>Updated <code>clientStorage</code> documentation with detailed usage
examples.<br> <li> Included specific instructions for Capacitor versions
< 4 and >= 4.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>

<summary><strong>nhost-react-client-constructor-params.mdx</strong><dd><code>Updated
`clientStorage` documentation with examples</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/reference/nextjs/types/nhost-react-client-constructor-params.mdx

<li>Updated <code>clientStorage</code> documentation with detailed usage
examples.<br> <li> Included specific instructions for Capacitor versions
< 4 and >= 4.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>

<summary><strong>nhost-react-client-constructor-params.mdx</strong><dd><code>Updated
`clientStorage` documentation with examples</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/reference/react/types/nhost-react-client-constructor-params.mdx

<li>Updated <code>clientStorage</code> documentation with detailed usage
examples.<br> <li> Included specific instructions for Capacitor versions
< 4 and >= 4.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>

<summary><strong>nhost-vue-client-constructor-params.mdx</strong><dd><code>Updated
`clientStorage` documentation with examples</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/reference/vue/types/nhost-vue-client-constructor-params.mdx

<li>Updated <code>clientStorage</code> documentation with detailed usage
examples.<br> <li> Included specific instructions for Capacitor versions
< 4 and >= 4.<br>


</details>


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

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions

---------

Co-authored-by: Nuno Pato <nunopato@gmail.com>
2024-08-05 10:38:40 +01:00
Hassan Ben Jobrane
d092a7c395 chore: add "vue-template-compiler" to allowlist in audit-ci.jsonc (#2810)
### **PR Type**
enhancement


___

### **Description**
- Added `vue-template-compiler` to the `allowlist` in `audit-ci.jsonc`
to address vulnerabilities.



___



### **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>Add
`vue-template-compiler` to audit-ci allowlist</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

audit-ci.jsonc

- Added `vue-template-compiler` to the `allowlist` array.



</details>


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

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-07-26 17:20:29 +01:00
Zephyr (David B.M.)
e5d3d1a39f dashboard: fix: type custom row permissions autocomplete (#2757)
Fixes #2746
2024-07-17 18:53:09 +02:00
github-actions[bot]
f88bf2d034 chore: update versions (#2803)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@1.24.1

### Patch Changes

- 49f2e55: fix: use service subdomain in service form and service
details dialog

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-17 00:35:14 +01:00
Hassan Ben Jobrane
49f2e55cb9 fix(dashboard): use service subdomain in service form and service details dialog (#2802)
### **PR Type**
Bug fix, Enhancement


___

### **Description**
- Added `subdomain` prop to `ServiceDetailsDialog` component and its
interface.
- Updated `ServiceForm` to pass `subdomain` to `ServiceDetailsDialog`.
- Changed subdomain source from `currentProject` to `formValues` in
`PortsFormSection` URL generation.
- Added a changeset for the fix.



___



### **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>Pass subdomain to
ServiceDetailsDialog in ServiceForm</code>&nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

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

- Added `subdomain` prop to `ServiceDetailsDialog` component.



</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>ServiceDetailsDialog.tsx</strong><dd><code>Add and use
subdomain prop in ServiceDetailsDialog</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/services/components/ServiceForm/components/ServiceDetailsDialog/ServiceDetailsDialog.tsx

<li>Added <code>subdomain</code> prop to
<code>ServiceDetailsDialogProps</code> interface.<br> <li> Updated
<code>getRunServicePortURL</code> call to use <code>subdomain</code>
prop.<br>


</details>


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

</tr>                    
</table></td></tr><tr><td><strong>Bug fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>PortsFormSection.tsx</strong><dd><code>Use formValues
subdomain in PortsFormSection URL generation</code></dd></summary>
<hr>


dashboard/src/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSection.tsx

<li>Changed subdomain source from <code>currentProject</code> to
<code>formValues</code> in <br><code>getRunServicePortURL</code>
call.<br>


</details>


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

</tr>                    
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>clever-hats-roll.md</strong><dd><code>Add changeset for
service subdomain fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

.changeset/clever-hats-roll.md

- Added changeset for the fix.



</details>


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

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-07-17 00:21:12 +01:00
Hassan Ben Jobrane
598b988fc1 fix: use current project subdomain in ServiceDetailsDialog component (#2800)
### **PR Type**
Bug fix, Enhancement


___

### **Description**
- Removed the `subdomain` prop from the `ServiceDetailsDialog` component
and its usage in `ServiceForm`.
- Updated `ServiceDetailsDialog` to use `currentProject?.subdomain`
directly.
- Added a changeset file documenting the fix.



___



### **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>Remove `subdomain`
prop from `ServiceDetailsDialog` usage</code></dd></summary>
<hr>

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

<li>Removed the <code>subdomain</code> prop from
<code>ServiceDetailsDialog</code> component.<br> <li> Updated the
<code>ServiceDetailsDialog</code> component usage.<br>


</details>


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

</tr>                    
</table></td></tr><tr><td><strong>Bug fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>ServiceDetailsDialog.tsx</strong><dd><code>Use
`currentProject?.subdomain` in
`ServiceDetailsDialog`</code></dd></summary>
<hr>


dashboard/src/features/services/components/ServiceForm/components/ServiceDetailsDialog/ServiceDetailsDialog.tsx

<li>Removed <code>subdomain</code> prop from
<code>ServiceDetailsDialogProps</code> interface.<br> <li> Updated
<code>ServiceDetailsDialog</code> to use
<code>currentProject?.subdomain</code> instead <br>of
<code>subdomain</code>.<br>


</details>


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

</tr>                    
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>long-plums-shave.md</strong><dd><code>Add changeset for
`ServiceDetailsDialog` fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/long-plums-shave.md

- Added changeset for the fix in `ServiceDetailsDialog` component.



</details>


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

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-07-16 23:31:21 +01:00
github-actions[bot]
2f0910367d chore: update versions (#2794)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@1.24.0

### Minor Changes

-   abb24af: chore: add redirect to support page when project is locked
- 18a6455: feat: show contact us info and locked reason when project is
locked

### Patch Changes

-   e31eefa: fix: include ingresses field when updating run services

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-15 15:51:18 +01:00
Hassan Ben Jobrane
e31eefae63 fix(dashboard): include ingresses field when updating a run service (#2798)
### **User description**
fixes https://github.com/nhost/nhost/issues/2797


___

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


___

### **Description**
- Added `ingresses` field to various components and validation schema to
support custom domains.
- Introduced `removeTypename` utility function to sanitize GraphQL
response objects.
- Replaced `getPortURL` with `getRunServicePortURL` helper function for
consistent URL generation.
- Updated changeset to document the inclusion of the `ingresses` field.



___



### **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>Add ingresses field
and sanitize values in ServiceForm</code>&nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

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

<li>Added <code>removeTypename</code> utility function to sanitize
values.<br> <li> Included <code>ingresses</code> field in the ports
mapping.<br> <li> Updated health check and other fields to use sanitized
values.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2798/files#diff-d62640c5c152c7b50a3a53deefcb29c6ed1fa685e15511863c09784497139c49">+19/-13</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>ServiceFormTypes.ts</strong><dd><code>Update validation
schema to include ingresses field</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>


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

<li>Added <code>ingresses</code> field to the validation schema.<br>
<li> Made <code>ingresses</code> field nullable.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>PortsFormSection.tsx</strong><dd><code>Use helper
function for port URL generation in
PortsFormSection</code></dd></summary>
<hr>


dashboard/src/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSection.tsx

<li>Replaced <code>getPortURL</code> with
<code>getRunServicePortURL</code> helper function.<br> <li> Minor
formatting changes.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2798/files#diff-64ce17ad73e4122e8c66a1968b6737ec98bd1623ac7e3cd3f4a34b549a78717b">+10/-13</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>ServiceDetailsDialog.tsx</strong><dd><code>Use helper
function for port URL generation in
ServiceDetailsDialog</code></dd></summary>
<hr>


dashboard/src/features/services/components/ServiceForm/components/ServiceDetailsDialog/ServiceDetailsDialog.tsx

<li>Replaced <code>getPortURL</code> with
<code>getRunServicePortURL</code> helper function.<br> <li> Filtered and
displayed only published ports.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>ServicesList.tsx</strong><dd><code>Include ingresses
field in ServicesList ports mapping</code>&nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

dashboard/src/features/services/components/ServicesList/ServicesList.tsx

- Included `ingresses` field in the ports mapping.



</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>helpers.ts</strong><dd><code>Add helper functions for
port URL generation and typename removal</code></dd></summary>
<hr>

dashboard/src/utils/helpers/helpers.ts

<li>Added <code>getRunServicePortURL</code> helper function.<br> <li>
Enhanced <code>removeTypename</code> function.<br>


</details>


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

</tr>                    
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>short-radios-retire.md</strong><dd><code>Add changeset
for ingresses field inclusion</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.changeset/short-radios-retire.md

- Added changeset for including `ingresses` field in run services.



</details>


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

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-07-15 15:38:04 +01:00
Zephyr (David B.M.)
abb24afad5 chore (dashboard): locked project contact support redirect (#2795) 2024-07-09 20:25:16 +02:00
Zephyr (David B.M.)
18a64555ce feat (dashboard): show contact us info when project is locked (#2775)
Resolves #2624
2024-07-09 15:11:58 +02:00
github-actions[bot]
60bcd8f949 chore: update versions (#2793)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/apollo@7.1.3

### Patch Changes

-   @nhost/nhost-js@3.1.6

## @nhost/react-apollo@12.0.3

### Patch Changes

-   @nhost/apollo@7.1.3
-   @nhost/react@3.5.3

## @nhost/react-urql@9.0.3

### Patch Changes

-   @nhost/react@3.5.3

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

### Patch Changes

- e28975d: fix: refactor refreshTimer logic to avoid an infinite loop
when refreshToken has expired

## @nhost/nextjs@2.1.17

### Patch Changes

-   @nhost/react@3.5.3

## @nhost/nhost-js@3.1.6

### Patch Changes

-   Updated dependencies [e28975d]
    -   @nhost/hasura-auth-js@2.5.3

## @nhost/react@3.5.3

### Patch Changes

-   @nhost/nhost-js@3.1.6

## @nhost/vue@2.6.3

### Patch Changes

-   @nhost/nhost-js@3.1.6

## @nhost/dashboard@1.23.0

### Minor Changes

-   33284d3: fix: don't show double scrollbar in configuration editor

### Patch Changes

-   @nhost/react-apollo@12.0.3
-   @nhost/nextjs@2.1.17

## @nhost-examples/cli@0.3.8

### Patch Changes

-   @nhost/nhost-js@3.1.6

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

### Patch Changes

-   @nhost/react@3.5.3
-   @nhost/react-apollo@12.0.3

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

### Patch Changes

-   @nhost/react@3.5.3

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

### Patch Changes

-   @nhost/react@3.5.3
-   @nhost/react-urql@9.0.3

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

### Patch Changes

-   @nhost/nhost-js@3.1.6

## @nhost-examples/nextjs@0.3.8

### Patch Changes

-   @nhost/react@3.5.3
-   @nhost/react-apollo@12.0.3
-   @nhost/nextjs@2.1.17

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

### Patch Changes

-   @nhost/nhost-js@3.1.6

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

### Patch Changes

-   @nhost/nhost-js@3.1.6

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

### Patch Changes

-   @nhost/react@3.5.3
-   @nhost/react-apollo@12.0.3

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

### Patch Changes

-   @nhost/react@3.5.3

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

### Patch Changes

-   @nhost/react@3.5.3
-   @nhost/react-apollo@12.0.3

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

### Patch Changes

-   @nhost/nhost-js@3.1.6
-   @nhost/apollo@7.1.3
-   @nhost/vue@2.6.3

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

### Patch Changes

-   @nhost/apollo@7.1.3
-   @nhost/vue@2.6.3

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-09 10:08:51 +01:00
Hassan Ben Jobrane
e28975d6a5 fix(hasura-auth-js): refactor refreshTimer logic to avoid an infinite loop when refreshToken has expired (#2790)
### **User description**
fixes https://github.com/nhost/nhost/issues/2763
related https://github.com/nhost/nhost/issues/2635


___

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


___

### **Description**
- Refactored the `refreshTimer` logic in `createAuthMachine` to avoid an
infinite loop when the `refreshToken` has expired.
- Moved the condition checking if `expiresAt` is less than the current
time to ensure proper token refresh handling.
- Added a changeset file to document the patch update.



___



### **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>machine.ts</strong><dd><code>Refactor refreshTimer
logic to prevent infinite loop</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

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

<li>Refactored the <code>refreshTimer</code> logic to avoid an infinite
loop when the <br><code>refreshToken</code> has expired.<br> <li> Moved
the condition checking if <code>expiresAt</code> is less than the
current <br>time.<br>


</details>


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

</tr>                    
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>small-trees-approve.md</strong><dd><code>Add changeset
for refreshTimer logic fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

.changeset/small-trees-approve.md

- Added a changeset file for the patch update.



</details>


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

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-07-09 09:56:58 +01:00
Zephyr (David B.M.)
33284d3cf0 fix (dashboard): don't show double scrollbar in configuration editor (#2792)
Fixes #2789
2024-07-08 19:15:37 +02:00
github-actions[bot]
1dbd65eb0e chore: update versions (#2787)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@1.22.0

### Minor Changes

-   998c037: fix: align drop-down list in select component
- 807b8c0: fix: show city name in region selection for project creation

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

### Patch Changes

- e3f0732: fix: add verify email button instead of doing an
auto-redirect

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-08 16:59:45 +01:00
Nuno Pato
6eec78f9c5 feat: dashboard: add support for zendesk (#2783)
### **PR Type**
Enhancement, Documentation


___

### **Description**
- Updated header to link to new support page.
- Added new `CommunityIcon`, `DiscordIcon`, and `EnvelopeIcon`
components.
- Created a new support page with links to documentation, GitHub issues,
and Discord community.
- Added a ticket creation page with a form for submitting support
tickets, integrated with Zendesk API.
- Added environment variables for Zendesk integration.



___



### **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>Header.tsx</strong><dd><code>Update header to link to
new support page</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

dashboard/src/components/layout/Header/Header.tsx

<li>Removed <code>ContactUs</code> component and <code>Dropdown</code>
component.<br> <li> Added <code>NavLink</code> to <code>/support</code>
page.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2783/files#diff-edac1cd4478dc0ad12911ea2e486f40e49f6dc64eaf8e72084225d1f4e8725af">+13/-21</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>CommunityIcon.tsx</strong><dd><code>Add CommunityIcon
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; </dd></summary>
<hr>

dashboard/src/components/ui/v2/icons/CommunityIcon/CommunityIcon.tsx

- Added new `CommunityIcon` component.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2783/files#diff-42970da68e2ef95e0aee273b264e69b21091866a9ba853fb594b08ab7e960ac1">+39/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>index.ts</strong><dd><code>Export CommunityIcon
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;
</dd></summary>
<hr>

dashboard/src/components/ui/v2/icons/CommunityIcon/index.ts

- Exported `CommunityIcon` component.



</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>DiscordIcon.tsx</strong><dd><code>Add DiscordIcon
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; &nbsp; </dd></summary>
<hr>

dashboard/src/components/ui/v2/icons/DiscordIcon/DiscordIcon.tsx

- Added new `DiscordIcon` component.



</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>index.ts</strong><dd><code>Export DiscordIcon
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; </dd></summary>
<hr>

dashboard/src/components/ui/v2/icons/DiscordIcon/index.ts

- Exported `DiscordIcon` component.



</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>EnvelopeIcon.tsx</strong><dd><code>Add EnvelopeIcon
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/ui/v2/icons/EnvelopeIcon/EnvelopeIcon.tsx

- Added new `EnvelopeIcon` component.



</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>index.ts</strong><dd><code>Export EnvelopeIcon
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;
</dd></summary>
<hr>

dashboard/src/components/ui/v2/icons/EnvelopeIcon/index.ts

- Exported `EnvelopeIcon` component.



</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>index.tsx</strong><dd><code>Add support page with
various help options</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

dashboard/src/pages/support/index.tsx

<li>Added new support page with links to documentation, GitHub issues,
and <br>Discord community.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2783/files#diff-64adb32f73092cbba8aedac54225398c237222d9ba03a702bbe9d676edcde49c">+128/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>ticket.tsx</strong><dd><code>Add ticket creation page
with Zendesk integration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/pages/support/ticket.tsx

<li>Added new ticket creation page with form for submitting support
<br>tickets.<br> <li> Integrated Zendesk API for ticket submission.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2783/files#diff-a66cba186d2014b03f1a0e005147ae7b48e88933700fe065d235cd819a949a97">+378/-0</a>&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>.env.example</strong><dd><code>Add Zendesk environment
variables</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/.env.example

- Added environment variables for Zendesk integration.



</details>


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

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions

---------

Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-07-05 16:20:57 +00:00
Hassan Ben Jobrane
e3f0732108 fix(react-apollo): add verify email button (#2782)
### **User description**
fixes https://github.com/nhost/nhost/issues/2741


___

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


___

### **Description**
- Added a `requestType` parameter to the `verifyEmail` function to
handle different types of email verification requests.
- Updated the email change test to include the `requestType` parameter.
- Replaced auto-redirect in the `VerifyPage` component with a
verification button and added error handling with notifications.
- Updated dependencies in `nhost.toml` to newer versions.
- Added a changeset file to document the email verification button
update.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Tests</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>change-email.test.ts</strong><dd><code>Update email
change test with request type parameter</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/e2e/authenticated/change-email.test.ts

- Added `requestType` parameter to `verifyEmail` function call.



</details>


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

</tr>                    
</table></td></tr><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>utils.ts</strong><dd><code>Enhance email verification
utility with request type</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

examples/react-apollo/e2e/utils.ts

<li>Added <code>requestType</code> parameter to <code>verifyEmail</code>
function.<br> <li> Implemented conditional logic based on
<code>requestType</code>.<br> <li> Added button click for
verification.<br>


</details>


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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>Verify.tsx</strong><dd><code>Add verification button
and error handling in VerifyPage</code>&nbsp; </dd></summary>
<hr>

examples/react-apollo/src/Verify.tsx

<li>Replaced auto-redirect with a verification button.<br> <li> Added
error handling with notifications.<br> <li> Updated UI components for
verification.<br>


</details>


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

</tr>                    
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>gentle-brooms-flash.md</strong><dd><code>Add changeset
for email verification button update</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/gentle-brooms-flash.md

- Added changeset for email verification button update.



</details>


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

</tr>                    
</table></td></tr><tr><td><strong>Dependencies</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>nhost.toml</strong><dd><code>Update dependencies in
nhost.toml</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>

examples/react-apollo/nhost/nhost.toml

- Updated Hasura, Auth, and Postgres versions.



</details>


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

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-07-05 15:31:15 +01:00
Zephyr (David B.M.)
807b8c049a fix (dashboard): show city name in region selector for project creation (#2788)
Fixes #2778
2024-07-05 12:46:38 +02:00
Zephyr (David B.M.)
998c0376bf fix (dashboard): align dropdown items in select component (#2786)
Fixes #2779
2024-07-05 12:02:18 +02:00
github-actions[bot]
cf5423dac6 chore: update versions (#2785)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@1.21.0

### Minor Changes

- a2efeed: fix: improve project health error handling, add unknown state
and polling interval for health state

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-04 19:11:02 +01:00
Zephyr (David B.M.)
a2efeed36f fix (dashboard): improve project health error handling, add poll interval (#2780)
Fixes #2776
2024-07-04 19:45:31 +02:00
Hassan Ben Jobrane
533b74d82d chore: update pnpm/action-setup to v4 (#2784)
### **PR Type**
enhancement, configuration changes


___

### **Description**
- Updated `pnpm/action-setup` to version 4 in GitHub Actions
configuration.
- Updated `packageManager` to `pnpm@8.10.5` in `package.json`.



___



### **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>Update pnpm/action-setup
to v4 in GitHub Actions</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

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

<li>Updated <code>pnpm/action-setup</code> version to v4.<br> <li> Set
<code>pnpm</code> version to 8.10.5.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2784/files#diff-342d59190b4737ee45e2062eb625ada477bcea5b4a843b25900ad55d7982f200">+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 packageManager to
pnpm@8.10.5 in package.json</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

package.json

- Updated `packageManager` to `pnpm@8.10.5`.



</details>


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

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-07-04 15:26:23 +01:00
github-actions[bot]
42cf86c8f1 chore: update versions (#2770)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@1.20.0

### Minor Changes

- 8ea4210: fix: error toasts can be closed individually, instead of
dismissing all toasts at once
- 58919ba: chore: add blink animation when project health service is
updating

## @nhost/docs@2.14.2

### Patch Changes

-   86f3f8d: chore: fix broken link in react-native reference

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-03 11:31:48 +01:00
Zephyr (David B.M.)
70e74f2f3d dashboard: chore: error toast message improvements (#2773)
Closes #2726
2024-07-02 12:13:09 +02:00
Hassan Ben Jobrane
a01985466e chore(templates/react-native): fix ESLint configuration for react native template (#2774)
### **PR Type**
enhancement, bug fix


___

### **Description**
- Refactored multiple components and screens to remove semicolons and
adjust formatting for consistency.
- Added ESLint configuration file for the React Native template.
- Cleaned up Babel and React Native configuration files by removing
trailing commas.
- Updated pnpm lockfile to reflect new dependencies and versions.
- Bumped React Native template version to 0.0.14.
- Updated ESLint dependency version to 8.57.0.



___



### **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>16
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>Button.tsx</strong><dd><code>Refactor Button component
for consistent formatting</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>
      
templates/react-native/template/src/components/Button.tsx

<li>Removed semicolons and adjusted formatting.<br> <li> Simplified JSX
structure.<br>


</details>
    

  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2774/files#diff-8c344335537207c9c9ae7fcc3ea55685d2724028d7e7b7bdf3b621e8cc9cf6bf">+16/-27</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>ControlledInput.tsx</strong><dd><code>Refactor
ControlledInput component for consistent
formatting</code></dd></summary>
<hr>
      
templates/react-native/template/src/components/ControlledInput.tsx

<li>Removed semicolons and adjusted formatting.<br> <li> Simplified JSX
structure.<br>


</details>
    

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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>Drawer.tsx</strong><dd><code>Refactor Drawer component
for consistent formatting</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>
      
templates/react-native/template/src/components/Drawer.tsx

<li>Removed semicolons and adjusted formatting.<br> <li> Simplified JSX
structure.<br>


</details>
    

  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2774/files#diff-6f93136e2b891b6559a1ddf61fc3c60da972208f5cf0f44e7f0caa2247378626">+20/-31</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>SignInWithAppleButton.tsx</strong><dd><code>Refactor
SignInWithAppleButton component for consistent
formatting</code></dd></summary>
<hr>
      
templates/react-native/template/src/components/SignInWithAppleButton.tsx

<li>Removed semicolons and adjusted formatting.<br> <li> Simplified JSX
structure.<br>


</details>
    

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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>SignInWithGoogleButton.tsx</strong><dd><code>Refactor
SignInWithGoogleButton component for consistent
formatting</code></dd></summary>
<hr>
      

templates/react-native/template/src/components/SignInWithGoogleButton.tsx

<li>Removed semicolons and adjusted formatting.<br> <li> Simplified JSX
structure.<br>


</details>
    

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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>UploadFile.tsx</strong><dd><code>Refactor UploadFile
component for consistent formatting</code>&nbsp; &nbsp; </dd></summary>
<hr>
      
templates/react-native/template/src/components/UploadFile.tsx

<li>Removed semicolons and adjusted formatting.<br> <li> Simplified JSX
structure.<br>


</details>
    

  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2774/files#diff-613fbf75a899e454b62df367e09d259d2966f196baaded5decf4cc1970eff45a">+24/-33</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>root.tsx</strong><dd><code>Clean up root component
imports</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>
      
templates/react-native/template/src/root.tsx

- Removed unused import statements.



</details>
    

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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>Main.tsx</strong><dd><code>Refactor Main screen for
consistent formatting</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>
      
templates/react-native/template/src/screens/Main.tsx

<li>Removed semicolons and adjusted formatting.<br> <li> Simplified JSX
structure.<br>


</details>
    

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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>Profile.tsx</strong><dd><code>Refactor Profile screen
for consistent formatting</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>
      
templates/react-native/template/src/screens/Profile.tsx

<li>Removed semicolons and adjusted formatting.<br> <li> Simplified JSX
structure.<br>


</details>
    

  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2774/files#diff-085837f3d86a1d36fb4dae5d15329e6353c7804cf6655270167484c33362e46f">+13/-20</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>SignIn.tsx</strong><dd><code>Refactor SignIn screen for
consistent formatting</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>
      
templates/react-native/template/src/screens/SignIn.tsx

<li>Removed semicolons and adjusted formatting.<br> <li> Simplified JSX
structure.<br>


</details>
    

  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2774/files#diff-316ef2753576fb251278ccc28461eaed1f57023f1ae4338c6a00d224d51a5f21">+50/-72</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>SignUp.tsx</strong><dd><code>Refactor SignUp screen for
consistent formatting</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>
      
templates/react-native/template/src/screens/SignUp.tsx

<li>Removed semicolons and adjusted formatting.<br> <li> Simplified JSX
structure.<br>


</details>
    

  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2774/files#diff-a08ab32eea3633f66c839543d7acc88aca48f9af1760d23d1ea517bce00f4a5a">+37/-58</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>Storage.tsx</strong><dd><code>Refactor Storage screen
for consistent formatting</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>
      
templates/react-native/template/src/screens/Storage.tsx

<li>Removed semicolons and adjusted formatting.<br> <li> Simplified JSX
structure.<br>


</details>
    

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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>babel.config.js</strong><dd><code>Clean up Babel
configuration file</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>
      
templates/react-native/template/babel.config.js

- Removed trailing commas.



</details>
    

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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>index.js</strong><dd><code>Refactor index.js for
consistent formatting</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>
      
templates/react-native/template/index.js

- Removed semicolons and adjusted formatting.



</details>
    

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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>react-native.config.js</strong><dd><code>Clean up React
Native configuration file</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>
      
templates/react-native/template/react-native.config.js

- Removed trailing commas.



</details>
    

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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Bump React Native
template version to 0.0.14</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>
      
templates/react-native/package.json

- Updated template version to 0.0.14.



</details>
    

  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2774/files#diff-a2693036cf925dde327b60e22d773fa5cf3ab260307cedd8b931262dad2e1821">+1/-1</a>&nbsp;
&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>.eslintrc.js</strong><dd><code>Add ESLint configuration
for React Native template</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>
      
templates/react-native/template/.eslintrc.js

- Added ESLint configuration file.



</details>
    

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

</tr>                    

</table></details></td></tr><tr><td><strong>Dependencies</strong></td><td><details><summary>2
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>pnpm-lock.yaml</strong><dd><code>Update pnpm
lockfile</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; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>
      
pnpm-lock.yaml

- Updated lockfile to reflect new dependencies and versions.



</details>
    

  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2774/files#diff-32824c984905bb02bc7ffcef96a77addd1f1602cff71a11fbbfdd7f53ee026bb">+83/-77</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Update ESLint dependency
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; </dd></summary>
<hr>
      
templates/react-native/template/package.json

- Updated ESLint version to 8.57.0.



</details>
    

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

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-07-01 18:31:43 +01:00
Zephyr (David B.M.)
8ea4210582 dashboard: fix: close error toasts individually (#2772)
Fixes #2656
2024-06-28 13:23:15 +02:00
Zephyr (David B.M.)
58919ba763 dashboard: chore: add blink animation to project health when service is updating (#2771) 2024-06-28 11:23:00 +02:00
Hassan Ben Jobrane
86f3f8d505 chore(docs): fix broken link in react-native reference (#2769)
### **PR Type**
Documentation


___

### **Description**
- Fixed a broken link in the React Native support documentation.
- Added a changeset file to document the fix.



___



### **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>shy-timers-turn.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/shy-timers-turn.md

- Added a changeset file for documenting the fix.



</details>
    

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

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>support.mdx</strong><dd><code>Fix broken link in React
Native support documentation</code>&nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>
      
docs/reference/react-native/support.mdx

- Fixed a broken link in the React Native support documentation.



</details>
    

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

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

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-06-28 09:53:37 +01:00
408 changed files with 75614 additions and 18621 deletions

View File

@@ -12,7 +12,7 @@ inputs:
runs:
using: 'composite'
steps:
- uses: pnpm/action-setup@v2.2.4
- uses: pnpm/action-setup@v4
with:
version: 8.10.5
run_install: false

View File

@@ -22,6 +22,7 @@ env:
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 }}
NHOST_PRO_TEST_PROJECT_NAME: ${{ vars.NHOST_PRO_TEST_PROJECT_NAME }}
NHOST_TEST_USER_EMAIL: ${{ secrets.NHOST_TEST_USER_EMAIL }}
NHOST_TEST_USER_PASSWORD: ${{ secrets.NHOST_TEST_USER_PASSWORD }}
NHOST_TEST_PROJECT_ADMIN_SECRET: ${{ secrets.NHOST_TEST_PROJECT_ADMIN_SECRET }}

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": ["trim-newlines"]
"allowlist": ["vue-template-compiler", "micromatch"]
}

View File

@@ -17,5 +17,10 @@ NEXT_PUBLIC_GITHUB_APP_INSTALL_URL=<github_app_install_url>
NEXT_PUBLIC_ANALYTICS_WRITE_KEY=<analytics_write_key>
NEXT_PUBLIC_NHOST_BRAGI_WEBSOCKET=<nhost_bragi_websocket>
NEXT_PUBLIC_ZENDESK_URL=
NEXT_PUBLIC_ZENDESK_API_KEY=
NEXT_PUBLIC_ZENDESK_USER_EMAIL=
CODEGEN_GRAPHQL_URL=https://local.graphql.nhost.run/v1
CODEGEN_HASURA_ADMIN_SECRET=nhost-admin-secret

View File

@@ -1 +1,3 @@
link-workspace-packages = false
link-workspace-packages = false
auto-install-peers = false
resolution-mode=highest

View File

@@ -1,5 +1,98 @@
# @nhost/dashboard
## 1.28.0
### Minor Changes
- 526183a: feat: allow filtering users in "make request as" in graphql section
- be3b85b: feat: add conceal errors toggle on auth settings page
### Patch Changes
- 35a2f12: fix: prevent run service details from opening when attempting to delete
- @nhost/react-apollo@12.0.6
- @nhost/nextjs@2.1.20
## 1.27.0
### Minor Changes
- a7cd02c: fix: resolve rate limit query
## 1.26.0
### Minor Changes
- 3773ad7: chore: update pricing information
- b63250d: fix: not allow run service creation form resubmission while creating a run service
- a44a1d4: feat: add rate limits settings page
### Patch Changes
- @nhost/react-apollo@12.0.5
- @nhost/nextjs@2.1.19
## 1.25.0
### Minor Changes
- d1ceede: feat: add setting to migrate postgres major and/or minor versions
- e5d3d1a: fix: allow manually typing column for custom check in database row permissions
### Patch Changes
- @nhost/react-apollo@12.0.4
- @nhost/nextjs@2.1.18
## 1.24.1
### Patch Changes
- 49f2e55: fix: use service subdomain in service form and service details dialog
- 598b988: fix: use current project subdomain in ServiceDetailsDialog component
## 1.24.0
### Minor Changes
- abb24af: chore: add redirect to support page when project is locked
- 18a6455: feat: show contact us info and locked reason when project is locked
### Patch Changes
- e31eefa: fix: include ingresses field when updating run services
## 1.23.0
### Minor Changes
- 33284d3: fix: don't show double scrollbar in configuration editor
### Patch Changes
- @nhost/react-apollo@12.0.3
- @nhost/nextjs@2.1.17
## 1.22.0
### Minor Changes
- 998c037: fix: align drop-down list in select component
- 807b8c0: fix: show city name in region selection for project creation
## 1.21.0
### Minor Changes
- a2efeed: fix: improve project health error handling, add unknown state and polling interval for health state
## 1.20.0
### Minor Changes
- 8ea4210: fix: error toasts can be closed individually, instead of dismissing all toasts at once
- 58919ba: chore: add blink animation when project health service is updating
## 1.19.0
### Minor Changes

View File

@@ -17,7 +17,7 @@ test.afterAll(async () => {
});
test('should be able to create then delete a personal access token', async () => {
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
await page.getByRole('banner').getByRole('button').last().click();
await page.getByRole('link', { name: /account settings/i }).click();
await page

View File

@@ -0,0 +1,60 @@
import {
PRO_TEST_PROJECT_NAME,
PRO_TEST_PROJECT_SLUG,
TEST_WORKSPACE_SLUG,
} from '@/e2e/env';
import { openProject } from '@/e2e/utils';
import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test';
let page: Page;
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
});
test.beforeEach(async () => {
await page.goto('/');
await openProject({
page,
projectName: PRO_TEST_PROJECT_NAME,
workspaceSlug: TEST_WORKSPACE_SLUG,
projectSlug: PRO_TEST_PROJECT_SLUG,
});
await page
.getByRole('navigation', { name: /main navigation/i })
.getByRole('link', { name: /ai/i })
.click();
});
test.afterAll(async () => {
await page.close();
});
test('should create and delete an Assistant', async () => {
await page.getByRole('link', { name: 'Assistants' }).click();
await expect(page.getByText(/no assistants are configured/i)).toBeVisible();
await page.getByRole('button', { name: 'Create a new assistant' }).click();
await page.getByLabel('Name').fill('test');
await page.getByLabel('Description').fill('test');
await page.getByLabel('Instructions').fill('test');
await page.getByLabel('Model').fill('gpt-3.5-turbo-1106');
await page.getByRole('button', { name: 'Create' }).click();
await expect(page.getByRole('heading', { name: /test/i })).toBeVisible();
await page.getByLabel(/more options/i).click();
await page.getByRole('menuitem', { name: /delete test/i }).click();
await page.getByLabel('Confirm Delete Assistant').check();
await page.getByRole('button', { name: 'Delete Assistant' }).click();
await expect(
page.getByRole('heading', { name: /no assistants are configured/i }),
).toBeVisible();
});

View File

@@ -0,0 +1,55 @@
import {
PRO_TEST_PROJECT_NAME,
PRO_TEST_PROJECT_SLUG,
TEST_WORKSPACE_SLUG,
} from '@/e2e/env';
import { openProject } from '@/e2e/utils';
import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test';
let page: Page;
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
});
test.beforeEach(async () => {
await page.goto('/');
await openProject({
page,
projectName: PRO_TEST_PROJECT_NAME,
workspaceSlug: TEST_WORKSPACE_SLUG,
projectSlug: PRO_TEST_PROJECT_SLUG,
});
await page
.getByRole('navigation', { name: /main navigation/i })
.getByRole('link', { name: /ai/i })
.click();
});
test.afterAll(async () => {
await page.close();
});
test('should create and delete an Auto-Embeddings', async () => {
await page.getByRole('button', { name: 'Add a new Auto-Embeddings' }).click();
await page.getByLabel('Name').fill('test');
await page.getByLabel('Schema').fill('auth');
await page.getByLabel('Table').fill('users');
await page.getByLabel('Column').fill('email');
await page.getByRole('button', { name: 'Create' }).click();
await expect(page.getByRole('heading', { name: /test/i })).toBeVisible();
await page.getByLabel(/more options/i).click();
await page.getByRole('menuitem', { name: /delete test/i }).click();
await page.getByLabel('Confirm Delete Auto-').check();
await page.getByRole('button', { name: 'Delete Auto-Embeddings' }).click();
await expect(
page.getByRole('heading', { name: /No Auto-Embeddings are configured/i }),
).toBeVisible();
});

View File

@@ -1,6 +1,6 @@
import {
TEST_PROJECT_NAME,
TEST_PROJECT_SLUG,
PRO_TEST_PROJECT_NAME,
PRO_TEST_PROJECT_SLUG,
TEST_WORKSPACE_SLUG,
} from '@/e2e/env';
import { createUser, generateTestEmail, openProject } from '@/e2e/utils';
@@ -12,9 +12,9 @@ test('should be able to ban and unban a user', async ({ page }) => {
await openProject({
page,
projectName: TEST_PROJECT_NAME,
projectName: PRO_TEST_PROJECT_NAME,
workspaceSlug: TEST_WORKSPACE_SLUG,
projectSlug: TEST_PROJECT_SLUG,
projectSlug: PRO_TEST_PROJECT_SLUG,
});
await page
@@ -22,7 +22,9 @@ test('should be able to ban and unban a user', async ({ page }) => {
.getByRole('link', { name: /auth/i })
.click();
await page.waitForURL(`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/users`);
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/users`,
);
const email = generateTestEmail();
const password = faker.internet.password();

View File

@@ -1,6 +1,6 @@
import {
TEST_PROJECT_NAME,
TEST_PROJECT_SLUG,
PRO_TEST_PROJECT_NAME,
PRO_TEST_PROJECT_SLUG,
TEST_WORKSPACE_SLUG,
} from '@/e2e/env';
import { createUser, generateTestEmail, openProject } from '@/e2e/utils';
@@ -19,9 +19,9 @@ test.beforeEach(async () => {
await openProject({
page,
projectName: TEST_PROJECT_NAME,
projectName: PRO_TEST_PROJECT_NAME,
workspaceSlug: TEST_WORKSPACE_SLUG,
projectSlug: TEST_PROJECT_SLUG,
projectSlug: PRO_TEST_PROJECT_SLUG,
});
await page
@@ -29,7 +29,9 @@ test.beforeEach(async () => {
.getByRole('link', { name: /auth/i })
.click();
await page.waitForURL(`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/users`);
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/users`,
);
});
test.afterAll(async () => {

View File

@@ -1,6 +1,6 @@
import {
TEST_PROJECT_NAME,
TEST_PROJECT_SLUG,
PRO_TEST_PROJECT_NAME,
PRO_TEST_PROJECT_SLUG,
TEST_WORKSPACE_SLUG,
} from '@/e2e/env';
import { createUser, generateTestEmail, openProject } from '@/e2e/utils';
@@ -19,9 +19,9 @@ test.beforeEach(async () => {
await openProject({
page,
projectName: TEST_PROJECT_NAME,
projectName: PRO_TEST_PROJECT_NAME,
workspaceSlug: TEST_WORKSPACE_SLUG,
projectSlug: TEST_PROJECT_SLUG,
projectSlug: PRO_TEST_PROJECT_SLUG,
});
await page
@@ -29,7 +29,9 @@ test.beforeEach(async () => {
.getByRole('link', { name: /auth/i })
.click();
await page.waitForURL(`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/users`);
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/users`,
);
});
test.afterAll(async () => {

View File

@@ -1,6 +1,6 @@
import {
TEST_PROJECT_NAME,
TEST_PROJECT_SLUG,
PRO_TEST_PROJECT_NAME,
PRO_TEST_PROJECT_SLUG,
TEST_WORKSPACE_SLUG,
} from '@/e2e/env';
import { createUser, generateTestEmail, openProject } from '@/e2e/utils';
@@ -19,9 +19,9 @@ test.beforeEach(async () => {
await openProject({
page,
projectName: TEST_PROJECT_NAME,
projectName: PRO_TEST_PROJECT_NAME,
workspaceSlug: TEST_WORKSPACE_SLUG,
projectSlug: TEST_PROJECT_SLUG,
projectSlug: PRO_TEST_PROJECT_SLUG,
});
await page
@@ -29,7 +29,9 @@ test.beforeEach(async () => {
.getByRole('link', { name: /auth/i })
.click();
await page.waitForURL(`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/users`);
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/users`,
);
});
test.afterAll(async () => {

View File

@@ -1,6 +1,6 @@
import {
TEST_PROJECT_NAME,
TEST_PROJECT_SLUG,
PRO_TEST_PROJECT_NAME,
PRO_TEST_PROJECT_SLUG,
TEST_WORKSPACE_SLUG,
} from '@/e2e/env';
import { openProject, prepareTable } from '@/e2e/utils';
@@ -20,9 +20,9 @@ test.beforeEach(async () => {
await openProject({
page,
projectName: TEST_PROJECT_NAME,
projectName: PRO_TEST_PROJECT_NAME,
workspaceSlug: TEST_WORKSPACE_SLUG,
projectSlug: TEST_PROJECT_SLUG,
projectSlug: PRO_TEST_PROJECT_SLUG,
});
await page
@@ -55,7 +55,7 @@ test('should create a simple table', async () => {
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
);
await expect(
@@ -84,7 +84,7 @@ test('should create a table with unique constraints', async () => {
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
);
await expect(
@@ -113,7 +113,7 @@ test('should create a table with nullable columns', async () => {
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
);
await expect(
@@ -146,7 +146,7 @@ test('should create a table with an identity column', async () => {
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
);
await expect(
@@ -174,7 +174,7 @@ test('should create table with foreign key constraint', async () => {
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${firstTableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${firstTableName}`,
);
await page.getByRole('button', { name: /new table/i }).click();
@@ -219,7 +219,7 @@ test('should create table with foreign key constraint', async () => {
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${secondTableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${secondTableName}`,
);
await expect(
@@ -247,7 +247,7 @@ test('should not be able to create a table with a name that already exists', asy
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
);
await page.getByRole('button', { name: /new table/i }).click();

View File

@@ -1,6 +1,6 @@
import {
TEST_PROJECT_NAME,
TEST_PROJECT_SLUG,
PRO_TEST_PROJECT_NAME,
PRO_TEST_PROJECT_SLUG,
TEST_WORKSPACE_SLUG,
} from '@/e2e/env';
import { deleteTable, openProject, prepareTable } from '@/e2e/utils';
@@ -20,9 +20,9 @@ test.beforeEach(async () => {
await openProject({
page,
projectName: TEST_PROJECT_NAME,
projectName: PRO_TEST_PROJECT_NAME,
workspaceSlug: TEST_WORKSPACE_SLUG,
projectSlug: TEST_PROJECT_SLUG,
projectSlug: PRO_TEST_PROJECT_SLUG,
});
await page
@@ -53,7 +53,7 @@ test('should delete a table', async () => {
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
);
await deleteTable({
@@ -63,7 +63,7 @@ test('should delete a table', async () => {
// navigate to next URL
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/**`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/**`,
);
await expect(
@@ -91,7 +91,7 @@ test('should not be able to delete a table if other tables have foreign keys ref
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${firstTableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${firstTableName}`,
);
await page.getByRole('button', { name: /new table/i }).click();
@@ -138,7 +138,7 @@ test('should not be able to delete a table if other tables have foreign keys ref
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${secondTableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${secondTableName}`,
);
await expect(

View File

@@ -23,6 +23,11 @@ export const TEST_WORKSPACE_SLUG = slugify(TEST_WORKSPACE_NAME, {
*/
export const TEST_PROJECT_NAME = process.env.NHOST_TEST_PROJECT_NAME;
/**
* Name of the pro test project to test against.
*/
export const PRO_TEST_PROJECT_NAME = process.env.NHOST_PRO_TEST_PROJECT_NAME;
/**
* Slugified name of the project to test against.
*/
@@ -31,6 +36,14 @@ export const TEST_PROJECT_SLUG = slugify(TEST_PROJECT_NAME, {
strict: true,
});
/**
* Slugified name of the pro project to test against.
*/
export const PRO_TEST_PROJECT_SLUG = slugify(PRO_TEST_PROJECT_NAME, {
lower: true,
strict: true,
});
/**
* Hasura admin secret of the test project to use.
*/

View File

@@ -0,0 +1,89 @@
import {
PRO_TEST_PROJECT_NAME,
PRO_TEST_PROJECT_SLUG,
TEST_WORKSPACE_SLUG,
} from '@/e2e/env';
import { openProject } from '@/e2e/utils';
import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test';
let page: Page;
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
});
test.beforeEach(async () => {
await page.goto('/');
await openProject({
page,
projectName: PRO_TEST_PROJECT_NAME,
workspaceSlug: TEST_WORKSPACE_SLUG,
projectSlug: PRO_TEST_PROJECT_SLUG,
});
await page
.getByRole('navigation', { name: /main navigation/i })
.getByRole('link', { name: /run/i })
.click();
});
test.afterAll(async () => {
await page.close();
});
test('should create and delete a run service', async () => {
await page.getByRole('button', { name: 'Add service' }).first().click();
await expect(page.getByText(/create a new service/i)).toBeVisible();
await page.getByPlaceholder(/service name/i).click();
await page.getByPlaceholder(/service name/i).fill('test');
const sliderRail = page.locator(
'.space-y-4 > .MuiSlider-root > .MuiSlider-rail',
);
// Get the bounding box of the slider rail to determine where to click
const box = await sliderRail.boundingBox();
if (box) {
// Calculate the position to click (start of the rail)
const x = box.x + 1; // A little offset to ensure click inside the rail
const y = box.y + box.height / 2; // Middle of the rail height-wise
// Perform the click
await page.mouse.click(x, y);
}
await page.getByRole('button', { name: /create/i }).click();
await expect(
page.getByRole('heading', { name: /confirm resources/i }),
).toBeVisible();
await page.getByRole('button', { name: /confirm/i }).click();
await expect(
page.getByRole('heading', { name: /service details/i }),
).toBeVisible();
await page.getByRole('button', { name: /ok/i }).click();
await expect(page.getByRole('heading', { name: /test/i })).toBeVisible();
await page.getByLabel(/more options/i).click();
await page.getByRole('menuitem', { name: /delete service/i }).click();
await page.getByLabel(/confirm delete project #/i).check();
await page.getByRole('button', { name: /delete service/i }).click();
await expect(
page
.getByRole('main')
.locator('div')
.filter({ hasText: 'No custom services are' })
.nth(2),
).toBeVisible();
});

View File

@@ -43,8 +43,8 @@ async function globalTeardown() {
await adminSecretInput.press('Enter');
// note: getByRole doesn't work here
await hasuraPage.locator('a', { hasText: /data/i }).click();
await hasuraPage.getByRole('link', { name: /sql/i }).click();
await hasuraPage.locator('a', { hasText: /data/i }).nth(0).click();
await hasuraPage.locator('[data-test="sql-link"]').click();
// Set the value of the Ace code editor using JavaScript evaluation in the browser context
await hasuraPage.evaluate(() => {

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "1.19.0",
"version": "1.28.0",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -48,6 +48,7 @@
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-table": "^8.15.3",
"@tanstack/react-virtual": "^3.2.0",
"@uidotdev/usehooks": "^2.4.1",
"@uiw/codemirror-theme-bbedit": "^4.22.2",
"@uiw/codemirror-theme-github": "^4.21.25",
"@uiw/react-codemirror": "^4.21.25",

View File

@@ -10,10 +10,10 @@ export default defineConfig({
expect: {
timeout: 5000,
},
fullyParallel: true,
fullyParallel: false,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
workers: 1,
reporter: 'html',
globalTeardown: require.resolve('./global-teardown'),
use: {

View File

@@ -0,0 +1,12 @@
<svg width="72" height="73" viewBox="0 0 72 73" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_10501_253)">
<path d="M0 8.5C0 4.08172 3.58172 0.5 8 0.5H64C68.4183 0.5 72 4.08172 72 8.5V64.5C72 68.9183 68.4183 72.5 64 72.5H8C3.58172 72.5 0 68.9183 0 64.5V8.5Z" fill="#9C73DF" fill-opacity="0.2"/>
<path d="M43.1203 35.5H29.7687C28.7153 35.5 27.8613 36.3954 27.8613 37.5V44.5C27.8613 45.6046 28.7153 46.5 29.7687 46.5H43.1203C44.1737 46.5 45.0276 45.6046 45.0276 44.5V37.5C45.0276 36.3954 44.1737 35.5 43.1203 35.5Z" stroke="#9C73DF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M31.6758 35.5V31.5C31.6758 30.1739 32.1782 28.9021 33.0724 27.9645C33.9667 27.0268 35.1795 26.5 36.4442 26.5C37.7089 26.5 38.9217 27.0268 39.816 27.9645C40.7102 28.9021 41.2126 30.1739 41.2126 31.5V35.5" stroke="#9C73DF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_10501_253">
<rect width="72" height="72" fill="white" transform="translate(0 0.5)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -29,7 +29,7 @@ export default function CountrySelector({
listbox: { className: 'min-w-0 w-full' },
popper: {
disablePortal: false,
className: 'z-[10000] w-[270px] w-full',
className: 'z-[10000] w-[270px]',
},
}}
>

View File

@@ -1,4 +1,3 @@
import { ContactUs } from '@/components/common/ContactUs';
import { useDialog } from '@/components/common/DialogProvider';
import { NavLink } from '@/components/common/NavLink';
import { AccountMenu } from '@/components/layout/AccountMenu';
@@ -9,11 +8,9 @@ import { Logo } from '@/components/presentational/Logo';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Chip } from '@/components/ui/v2/Chip';
import { Dropdown } from '@/components/ui/v2/Dropdown';
import { GraphiteIcon } from '@/components/ui/v2/icons/GraphiteIcon';
import { DevAssistant } from '@/features/ai/DevAssistant';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCurrentUserOwner';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { ApplicationStatus } from '@/types/application';
import { getToastStyleProps } from '@/utils/constants/settings';
@@ -38,14 +35,15 @@ export default function Header({ className, ...props }: HeaderProps) {
const { currentProject, refetch: refetchProject } =
useCurrentWorkspaceAndProject();
const isOwner = useIsCurrentUserOwner();
const isProjectUpdating =
currentProject?.appStates[0]?.stateId === ApplicationStatus.Updating;
const isProjectMigratingDatabase =
currentProject?.appStates[0]?.stateId === ApplicationStatus.Migrating;
// Poll for project updates
useEffect(() => {
if (!isProjectUpdating) {
if (!isProjectUpdating && !isProjectMigratingDatabase) {
return () => {};
}
@@ -56,7 +54,7 @@ export default function Header({ className, ...props }: HeaderProps) {
return () => {
clearInterval(interval);
};
}, [isProjectUpdating, refetchProject]);
}, [isProjectUpdating, isProjectMigratingDatabase, refetchProject]);
const openDevAssistant = () => {
// The dev assistant can be only answer questions related to a particular project
@@ -97,6 +95,13 @@ export default function Header({ className, ...props }: HeaderProps) {
{isProjectUpdating && (
<Chip size="small" label="Updating" color="warning" />
)}
{isProjectMigratingDatabase && (
<Chip
size="small"
label="Upgrading Postgres version"
color="warning"
/>
)}
</div>
<div className="hidden grid-flow-col items-center gap-2 sm:grid">
@@ -105,25 +110,19 @@ export default function Header({ className, ...props }: HeaderProps) {
</Button>
{isPlatform && (
<Dropdown.Root>
<Dropdown.Trigger
hideChevron
className="rounded-md px-2.5 py-1.5 text-sm motion-safe:transition-colors"
>
Contact us
</Dropdown.Trigger>
<Dropdown.Content
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
>
<ContactUs
className="max-w-md"
isTeam={currentProject?.plan?.name === 'Team'}
isOwner={isOwner}
/>
</Dropdown.Content>
</Dropdown.Root>
<NavLink
underline="none"
href="/support"
className="mr-2 rounded-md px-2.5 py-1.5 text-sm motion-safe:transition-colors"
sx={{
color: 'text.primary',
'&:hover': { backgroundColor: 'grey.200' },
}}
target="_blank"
rel="noopener noreferrer"
>
Support
</NavLink>
)}
<NavLink

View File

@@ -63,6 +63,10 @@ export interface SettingsContainerProps
* @default false
*/
showSwitch?: boolean;
/**
* Custom element to be rendered at the top-right corner of the section.
*/
topRightElement?: ReactNode;
/**
* Custom class names passed to the root element.
*/
@@ -108,6 +112,7 @@ export default function SettingsContainer({
showSwitch = false,
rootClassName,
docsTitle,
topRightElement,
slotProps: { root, switch: switchSlot, submitButton, footer } = {},
}: SettingsContainerProps) {
return (
@@ -137,6 +142,7 @@ export default function SettingsContainer({
{description && <Text color="secondary">{description}</Text>}
</div>
</div>
{topRightElement}
{!switchId && showSwitch && (
<Switch
checked={enabled}

View File

@@ -47,46 +47,51 @@ export default function SettingsLayout({
sx={{ backgroundColor: 'background.default' }}
className="flex w-full flex-auto flex-col overflow-y-auto overflow-x-hidden"
>
<RetryableErrorBoundary>
<div className="flex flex-col space-y-2">
{hasGitRepo && (
<Alert
severity="warning"
className="grid grid-flow-row place-content-center gap-2"
>
<Text color="warning" className="text-sm ">
As you have a connected repository, make sure to synchronize
your changes with{' '}
<code
className={twMerge(
'rounded-md px-2 py-px',
theme.palette.mode === 'dark'
? 'bg-brown text-copper'
: 'bg-slate-200 text-slate-700',
)}
>
nhost config pull
</code>{' '}
or they may be reverted with the next push.
<br />
If there are multiple projects linked to the same repository
and you only want these changes to apply to a subset of them,
please check out{' '}
<a
target="_blank"
rel="noopener noreferrer"
className="underline"
href="https://docs.nhost.io/cli/overlays"
>
docs.nhost.io/cli/overlays
</a>{' '}
for guidance.
</Text>
</Alert>
)}
</div>
{children}
</RetryableErrorBoundary>
<Box
sx={{ backgroundColor: 'background.default' }}
className="flex h-full flex-col"
>
<RetryableErrorBoundary>
<div className="flex flex-col space-y-2">
{hasGitRepo && (
<Alert
severity="warning"
className="grid grid-flow-row place-content-center gap-2"
>
<Text color="warning" className="text-sm ">
As you have a connected repository, make sure to synchronize
your changes with{' '}
<code
className={twMerge(
'rounded-md px-2 py-px',
theme.palette.mode === 'dark'
? 'bg-brown text-copper'
: 'bg-slate-200 text-slate-700',
)}
>
nhost config pull
</code>{' '}
or they may be reverted with the next push.
<br />
If there are multiple projects linked to the same repository
and you only want these changes to apply to a subset of
them, please check out{' '}
<a
target="_blank"
rel="noopener noreferrer"
className="underline"
href="https://docs.nhost.io/cli/overlays"
>
docs.nhost.io/cli/overlays
</a>{' '}
for guidance.
</Text>
</Alert>
)}
</div>
{children}
</RetryableErrorBoundary>
</Box>
</Box>
</ProjectLayout>
);

View File

@@ -221,6 +221,13 @@ export default function SettingsSidebar({
>
Custom Domains
</SettingsNavLink>
<SettingsNavLink
href="/rate-limiting"
exact={false}
onClick={handleSelect}
>
Rate Limiting
</SettingsNavLink>
<SettingsNavLink href="/ai" exact={false} onClick={handleSelect}>
AI
</SettingsNavLink>

View File

@@ -37,6 +37,9 @@ const Badge = styled(MaterialBadge)<BadgeProps>(({ theme }) => ({
'& .MuiBadge-colorSuccess': {
backgroundColor: theme.palette.success.dark,
},
'& .MuiBadge-colorSecondary': {
backgroundColor: theme.palette.grey[500],
},
}));
Badge.displayName = 'NhostBadge';

View File

@@ -0,0 +1,85 @@
import { render, screen } from '@/tests/testUtils';
import { test } from 'vitest';
import ErrorToast from './ErrorToast';
const oneMemberByWorkspaceError = {
name: 'ApolloError',
graphQLErrors: [
{
message: 'database query error',
extensions: {
path: '$.selectionSet.insertApp.args.object',
code: 'unexpected',
internal: {
arguments: [],
error: {
description: null,
exec_status: 'FatalError',
hint: null,
message:
'Only one workspace member is allowed for individual plans',
status_code: 'P0001',
},
prepared: false,
statement: '.....',
},
},
},
],
protocolErrors: [],
clientErrors: [],
networkError: null,
message: 'database query error',
};
const changeNodeInvalidVersionError = {
name: 'ApolloError',
graphQLErrors: [
{
message:
'failed to resolve config: failed to validate config: config is not valid: #Config.functions.node.version: 2 errors in empty disjunction: (and 2 more errors)',
path: ['replaceConfigRawJSON'],
},
],
protocolErrors: [],
clientErrors: [],
networkError: null,
message:
'failed to resolve config: failed to validate config: config is not valid: #Config.functions.node.version: 2 errors in empty disjunction: (and 2 more errors)',
};
test('should render the error message when creating a project with an individual plan in a workspace with multiple users', () => {
const errorMessage =
'An error occurred while creating the project. Please try again.';
render(
<ErrorToast
isVisible
errorMessage={errorMessage}
error={oneMemberByWorkspaceError}
close={() => {}}
/>,
);
expect(
screen.getByText(
/Only one workspace member is allowed for individual plans/i,
),
).toBeInTheDocument();
});
test('should render the error message when changing the node version to an invalid value in configuration editor', () => {
const errorMessage =
'An error occurred while saving configuration. Please try again.';
render(
<ErrorToast
isVisible
errorMessage={errorMessage}
error={changeNodeInvalidVersionError}
close={() => {}}
/>,
);
const regex =
/failed to resolve config: failed to validate config: config is not valid: #Config\.functions\.node\.version: 2 errors in empty disjunction: \(and 2 more errors\)/i;
expect(screen.getByText(regex)).toBeInTheDocument();
});

View File

@@ -32,7 +32,7 @@ const getInternalErrorMessage = (
const graphqlError = error.graphQLErrors?.[0];
const graphqlExtensionsError = graphqlError?.extensions?.internal
?.error as { message: string };
return graphqlError.message || graphqlExtensionsError?.message || null;
return graphqlExtensionsError?.message || graphqlError.message || null;
}
if (error instanceof Error) {

View File

@@ -8,6 +8,17 @@ import { createTheme as createMuiTheme } from '@mui/material/styles';
* @param mode - Color mode
* @returns Material UI theme
*/
declare module '@mui/material/styles' {
interface Palette {
beige: Palette['primary'];
}
interface PaletteOptions {
beige?: PaletteOptions['primary'];
}
}
export default function createTheme(mode: PaletteMode) {
return createMuiTheme({
shape: {

View File

@@ -0,0 +1,39 @@
import * as React from 'react';
export default function CommunityIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
width={16}
height={16}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M5.5 10C7.29493 10 8.75 8.54493 8.75 6.75C8.75 4.95507 7.29493 3.5 5.5 3.5C3.70507 3.5 2.25 4.95507 2.25 6.75C2.25 8.54493 3.70507 10 5.5 10Z"
stroke="currentColor"
strokeWidth="1.5"
strokeMiterlimit="10"
/>
<path
d="M9.71338 3.62107C10.1604 3.49513 10.6292 3.46644 11.0882 3.53693C11.5473 3.60743 11.9859 3.77547 12.3745 4.02975C12.7631 4.28403 13.0927 4.61863 13.3411 5.01102C13.5896 5.40342 13.751 5.84449 13.8146 6.30453C13.8782 6.76457 13.8425 7.2329 13.7098 7.67797C13.5772 8.12304 13.3507 8.53452 13.0457 8.8847C12.7406 9.23487 12.364 9.51561 11.9413 9.70799C11.5187 9.90038 11.0596 9.99996 10.5952 10"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
<path
d="M1 12.3373C1.50758 11.6153 2.18143 11.026 2.96466 10.6192C3.74788 10.2124 4.61748 10 5.50005 10C6.38262 9.99997 7.25224 10.2123 8.0355 10.619C8.81875 11.0258 9.49264 11.615 10.0003 12.337"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
<path
d="M10.5952 10C11.4779 9.99936 12.3477 10.2114 13.131 10.6182C13.9143 11.025 14.5881 11.6146 15.0952 12.337"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
</svg>
);
}

View File

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

View File

@@ -0,0 +1,29 @@
import * as React from 'react';
export default function DiscordIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
width={14}
height={14}
viewBox="0 0 14 14"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3.40571 1.5H12.5943C13.3691 1.5 14 2.13086 14 2.91257V15.2143L12.5257 13.9114L11.696 13.1434L10.8183 12.3274L11.1817 13.596H3.40571C2.63086 13.596 2 12.9651 2 12.1834V2.91257C2 2.13086 2.63086 1.5 3.40571 1.5ZM9.49486 9.9C9.70057 10.1606 9.94743 10.4554 9.94743 10.4554C11.4629 10.4074 12.0457 9.41314 12.0457 9.41314C12.0457 7.20514 11.0583 5.41543 11.0583 5.41543C10.0709 4.67486 9.13143 4.69543 9.13143 4.69543L9.03543 4.80514C10.2011 5.16171 10.7429 5.676 10.7429 5.676C10.0297 5.28514 9.33029 5.09314 8.67886 5.01771C8.18514 4.96286 7.712 4.97657 7.29371 5.03143C7.2578 5.03143 7.2271 5.03665 7.19251 5.04254C7.18748 5.0434 7.18237 5.04427 7.17714 5.04514C6.93714 5.06571 6.35429 5.15486 5.62057 5.47714C5.36686 5.59371 5.216 5.676 5.216 5.676C5.216 5.676 5.78514 5.13429 7.01943 4.77771L6.95086 4.69543C6.95086 4.69543 6.01143 4.67486 5.024 5.41543C5.024 5.41543 4.03657 7.20514 4.03657 9.41314C4.03657 9.41314 4.61257 10.4074 6.128 10.4554C6.128 10.4554 6.38171 10.1469 6.58743 9.88628C5.71657 9.62571 5.38743 9.07714 5.38743 9.07714C5.38743 9.07714 5.456 9.12514 5.57943 9.19371C5.58629 9.20057 5.59314 9.20743 5.60686 9.21428C5.61714 9.22114 5.62743 9.22628 5.63771 9.23143C5.648 9.23657 5.65829 9.24171 5.66857 9.24857C5.84 9.34457 6.01143 9.42 6.16914 9.48171C6.45029 9.59143 6.78629 9.70114 7.17714 9.77657C7.69143 9.87257 8.29486 9.90686 8.95314 9.78343C9.27543 9.72857 9.60457 9.63257 9.94743 9.48857C10.1874 9.39943 10.4549 9.26914 10.736 9.084C10.736 9.084 10.3931 9.64628 9.49486 9.9ZM6.05942 8.01421C6.05942 7.59593 6.36799 7.25307 6.75885 7.25307C7.1497 7.25307 7.46513 7.59593 7.45827 8.01421C7.45827 8.4325 7.1497 8.77536 6.75885 8.77536C6.37485 8.77536 6.05942 8.4325 6.05942 8.01421ZM8.56227 8.01421C8.56227 7.59593 8.87084 7.25307 9.2617 7.25307C9.65256 7.25307 9.96113 7.59593 9.96113 8.01421C9.96113 8.4325 9.65256 8.77536 9.2617 8.77536C8.8777 8.77536 8.56227 8.4325 8.56227 8.01421Z"
fill="currentColor"
/>
</svg>
</svg>
);
}

View File

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

View File

@@ -0,0 +1,37 @@
import * as React from 'react';
export default function EnvelopeIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
width={14}
height={14}
viewBox="0 0 14 14"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2 3.5H14V12C14 12.1326 13.9473 12.2598 13.8536 12.3536C13.7598 12.4473 13.6326 12.5 13.5 12.5H2.5C2.36739 12.5 2.24021 12.4473 2.14645 12.3536C2.05268 12.2598 2 12.1326 2 12V3.5Z"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M14 3.5L8 9L2 3.5"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</svg>
);
}

View File

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

View File

@@ -0,0 +1,27 @@
import type { IconProps } from '@/components/ui/v2/icons';
import { SvgIcon } from '@/components/ui/v2/icons/SvgIcon';
function PowerOffIcon(props: IconProps) {
return (
<SvgIcon
xmlns="http://www.w3.org/2000/svg"
width={24}
height={24}
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
{...props}
>
<path
fill="none"
d="M18.36 6.64A9 9 0 0 1 20.77 15M6.16 6.16a9 9 0 1 0 12.68 12.68M12 2v4M2 2l20 20"
/>
</SvgIcon>
);
}
PowerOffIcon.displayName = 'NhostPowerOffIcon';
export default PowerOffIcon;

View File

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

View File

@@ -0,0 +1,21 @@
import type { IconProps } from '@/components/ui/v2/icons';
import { SvgIcon } from '@/components/ui/v2/icons/SvgIcon';
function QuestionMarkIcon(props: IconProps) {
return (
<SvgIcon
width="320"
height="512"
aria-label="Question mark"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
{...props}
>
<path d="M80 160c0-35.3 28.7-64 64-64h32c35.3 0 64 28.7 64 64v3.6c0 21.8-11.1 42.1-29.4 53.8l-42.2 27.1c-25.2 16.2-40.4 44.1-40.4 74V320c0 17.7 14.3 32 32 32s32-14.3 32-32v-1.4c0-8.2 4.2-15.8 11-20.2l42.2-27.1c36.6-23.6 58.8-64.1 58.8-107.7V160c0-70.7-57.3-128-128-128H144C73.3 32 16 89.3 16 160c0 17.7 14.3 32 32 32s32-14.3 32-32zm80 320a40 40 0 1 0 0-80 40 40 0 1 0 0 80z" />
</SvgIcon>
);
}
QuestionMarkIcon.displayName = 'NhostQuestionMarkIcon';
export default QuestionMarkIcon;

View File

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

View File

@@ -0,0 +1,31 @@
import type { IconProps } from '@/components/ui/v2/icons';
import { SvgIcon } from '@/components/ui/v2/icons/SvgIcon';
function RepeatIcon(props: IconProps) {
return (
<SvgIcon
aria-label="Repeat"
width="16"
height="20"
viewBox="0 0 16 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M11.4062 11.9779H15.9998L13.7035 8L11.4062 11.9779Z"
fill="currentColor"
/>
<path
d="M13.1959 16.2243C13.1959 17.466 11.9655 18.4759 10.4525 18.4759L4.05328 18.4749C2.54037 18.4749 1.30989 17.2444 1.30989 15.7315V4.26843C1.30989 2.75552 2.54034 1.52504 4.05328 1.52504H10.4525C11.9654 1.52504 13.1959 2.535 13.1959 3.77661V6.53613C13.1959 6.81655 13.4235 7.04415 13.7039 7.04415C13.9844 7.04415 14.212 6.81655 14.212 6.53613V3.77557C14.212 1.97409 12.5253 0.508057 10.4526 0.508057L4.05333 0.509073C1.98056 0.509073 0.293945 2.19574 0.293945 4.26846V15.7326C0.293945 17.8054 1.98061 19.492 4.05333 19.492H10.4526C12.5253 19.492 14.212 18.0258 14.212 16.2245L14.212 13.5H13.1959L13.1959 16.2243Z"
fill="currentColor"
stroke="currentColor"
strokeWidth="0.5"
/>
</SvgIcon>
);
}
RepeatIcon.displayName = 'NhostRepeatIcon';
export default RepeatIcon;

View File

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

View File

@@ -63,6 +63,9 @@ export default function getDesignTokens(mode: PaletteMode): PaletteOptions {
paper: '#171d26',
},
divider: '#2f363d',
beige: {
main: '#362c22',
},
};
}
@@ -125,5 +128,8 @@ export default function getDesignTokens(mode: PaletteMode): PaletteOptions {
paper: '#ffffff',
},
divider: '#eaedf0',
beige: {
main: '#e5d1bf',
},
};
}

View File

@@ -117,7 +117,7 @@ export default function ArgumentsFormSection({
listbox: { className: 'min-w-0 w-full' },
popper: {
disablePortal: false,
className: 'z-[10000] w-[270px] w-full',
className: 'z-[10000] w-[270px]',
},
}}
>

View File

@@ -0,0 +1,136 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
} from '@/utils/__generated__/graphql';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
const validationSchema = Yup.object({
enabled: Yup.boolean(),
});
export type ToggleConcealErrorsFormValues = Yup.InferType<
typeof validationSchema
>;
export default function ConcealErrorsSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
...(!isPlatform ? { client: localMimirClient } : {}),
});
const form = useForm<ToggleConcealErrorsFormValues>({
reValidateMode: 'onSubmit',
defaultValues: {
enabled: data?.config?.auth?.misc?.concealErrors,
},
});
useEffect(() => {
if (!loading) {
form.reset({
enabled: data?.config?.auth?.misc?.concealErrors,
});
}
}, [loading, data, form]);
if (loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading conceal error settings..."
className="justify-center"
/>
);
}
if (error) {
throw error;
}
const { formState } = form;
const handleToggleConcealErrors = async (
values: ToggleConcealErrorsFormValues,
) => {
const updateConfigPromise = updateConfig({
variables: {
appId: currentProject.id,
config: {
auth: {
misc: {
concealErrors: values.enabled,
},
},
},
},
});
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Updating conceal error settings...',
successMessage: 'Conceal error settings updated successfully.',
errorMessage:
'Failed to update conceal error settings. Please try again.',
},
);
};
return (
<FormProvider {...form}>
<Form onSubmit={handleToggleConcealErrors}>
<SettingsContainer
title="Conceal errors"
description="If set, conceals sensitive error messages to prevent leaking information about user accounts."
switchId="enabled"
showSwitch
slotProps={{
submitButton: {
disabled: !formState.isDirty || maintenanceActive,
loading: formState.isSubmitting,
},
}}
className="hidden"
/>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -50,6 +50,9 @@ query GetAuthenticationSettings($appId: uuid!) {
default
}
}
misc {
concealErrors
}
version
}
}

View File

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

View File

@@ -0,0 +1,93 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import {
useGetApplicationBackupsQuery,
type GetApplicationBackupsQuery,
type GetApplicationBackupsQueryVariables,
} from '@/utils/__generated__/graphql';
import type { QueryHookOptions } from '@apollo/client';
interface TimePeriod {
value: number;
unit: 'hours' | 'minutes';
downtime: string;
downtimeShort: string;
}
export interface UseEstimatedDatabaseMigrationDowntimeOptions
extends QueryHookOptions<
GetApplicationBackupsQuery,
GetApplicationBackupsQueryVariables
> {}
const DEFAULT_ESTIMATED_DOWNTIME: TimePeriod = {
value: 10,
unit: 'minutes',
downtime: '10 minutes',
downtimeShort: '10min',
};
function getEstimatedTime(diff: number): TimePeriod {
if (diff > 1000 * 3600) {
const value = Math.floor(diff / (1000 * 3600));
const unitStr = value === 1 ? 'hour' : 'hours';
return {
value,
unit: 'hours',
downtime: `${value} ${unitStr}`,
downtimeShort: `${value}hr`,
};
}
// 10 minutes is the minimum estimated downtime
if (diff > 1000 * 60 * 10) {
const value = Math.floor(diff / (1000 * 60));
const unitStr = value === 1 ? 'minute' : 'minutes';
return {
value,
unit: 'minutes',
downtime: `${value} ${unitStr}`,
downtimeShort: `${value}min`,
};
}
return DEFAULT_ESTIMATED_DOWNTIME;
}
/*
* This hook returns the estimated downtime for a database migration.
* The estimated downtime is calculated based on the time taken to complete the last backup.
* If there are no backups, the estimated downtime is set to 10 minutes.
*/
export default function useEstimatedDatabaseMigrationDowntime(
options: UseEstimatedDatabaseMigrationDowntimeOptions = {},
): TimePeriod {
const { currentProject } = useCurrentWorkspaceAndProject();
const isPlanFree = currentProject?.plan?.isFree;
const { data, loading, error } = useGetApplicationBackupsQuery({
...options,
variables: { ...options.variables, appId: currentProject?.id },
skip: isPlanFree,
});
if (loading || error) {
return DEFAULT_ESTIMATED_DOWNTIME;
}
const backups = data?.app?.backups;
let estimatedMilliseconds = 1000 * 60 * 10; // DEFAULT ESTIMATED DOWNTIME is 10 minutes
if (!isPlanFree && backups?.length > 0) {
const lastBackup = backups[0];
const createdAt = new Date(lastBackup.createdAt);
const completedAt = new Date(lastBackup.completedAt);
const diff = completedAt.valueOf() - createdAt.valueOf();
estimatedMilliseconds = diff * 2;
}
const estimated = getEstimatedTime(estimatedMilliseconds);
return estimated;
}

View File

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

View File

@@ -0,0 +1,37 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { useGetPostgresSettingsQuery } from '@/utils/__generated__/graphql';
/**
* Queries the postgres version of the current project.
* @returns Major, minor and full version of the postgres database. Loading and error states.
*/
export default function useGetPostgresVersion() {
const { currentProject } = useCurrentWorkspaceAndProject();
const localMimirClient = useLocalMimirClient();
const isPlatform = useIsPlatform();
const {
data: postgresSettingsData,
loading,
error,
} = useGetPostgresSettingsQuery({
variables: { appId: currentProject?.id },
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { version } = postgresSettingsData?.config?.postgres || {};
const [postgresMajor, postgresMinor] = version?.split('.') || [
undefined,
undefined,
];
return {
version,
postgresMajor,
postgresMinor,
loading,
error,
};
}

View File

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

View File

@@ -0,0 +1,98 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import {
useGetApplicationStateQuery,
type GetApplicationStateQuery,
type GetApplicationStateQueryVariables,
} from '@/generated/graphql';
import { ApplicationStatus } from '@/types/application';
import type { QueryHookOptions } from '@apollo/client';
import { useVisibilityChange } from '@uidotdev/usehooks';
import { useEffect } from 'react';
export interface UseIsDatabaseMigratingOptions
extends QueryHookOptions<
GetApplicationStateQuery,
GetApplicationStateQueryVariables
> {
shouldPoll?: boolean;
}
/*
* This hook returns information about the current state of database migration.
* @param options - Options for the query.
*
* @returns - An object with two properties:
* - isMigrating: true if the database is currently migrating.
* - shouldShowUpgradeLogs: true if the database is currently migrating or the application is not live after a migration.
*/
export default function useIsDatabaseMigrating(
options: UseIsDatabaseMigratingOptions = {},
): {
isMigrating: boolean;
shouldShowUpgradeLogs: boolean;
} {
const { currentProject } = useCurrentWorkspaceAndProject();
const isVisible = useVisibilityChange();
const {
data: appStatesData,
startPolling,
stopPolling,
} = useGetApplicationStateQuery({
...options,
variables: { ...options.variables, appId: currentProject?.id },
skip: !currentProject,
skipPollAttempt: () => !isVisible,
});
useEffect(() => {
if (options.shouldPoll) {
startPolling(options.pollInterval || 5000);
}
return () => stopPolling();
}, [stopPolling, startPolling, options.shouldPoll, options.pollInterval]);
// Return true if the application is migrating or if the application is not live after a migration
const shouldShowUpgradeLogs = (
appStates: GetApplicationStateQuery['app']['appStates'],
) => {
for (let i = 0; i < appStates.length; i += 1) {
if (appStates[i].stateId === ApplicationStatus.Live) {
return false;
}
if (appStates[i].stateId === ApplicationStatus.Migrating) {
return true;
}
}
return false;
};
// Return true if the application is currently migrating
const isMigrating = (
appStates: GetApplicationStateQuery['app']['appStates'],
) => {
for (let i = 0; i < appStates.length; i += 1) {
if (appStates[i].stateId === ApplicationStatus.Live) {
return false;
}
if (appStates[i].stateId === ApplicationStatus.Errored) {
return false;
}
if (appStates[i].stateId === ApplicationStatus.Migrating) {
return true;
}
}
return false;
};
return {
isMigrating: isMigrating(appStatesData?.app?.appStates || []),
shouldShowUpgradeLogs: shouldShowUpgradeLogs(
appStatesData?.app?.appStates || [],
),
};
}

View File

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

View File

@@ -0,0 +1,107 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import {
useGetApplicationStateQuery,
useGetSystemLogsQuery,
type GetApplicationStateQuery,
type GetApplicationStateQueryVariables,
type GetSystemLogsQuery,
type GetSystemLogsQueryVariables,
} from '@/generated/graphql';
import { ApplicationStatus } from '@/types/application';
import type { ApolloError, QueryHookOptions } from '@apollo/client';
import { useVisibilityChange } from '@uidotdev/usehooks';
import { useEffect } from 'react';
export interface UseIsDatabaseMigratingOptions
extends QueryHookOptions<
GetApplicationStateQuery,
GetApplicationStateQueryVariables
> {
shouldPoll?: boolean;
}
export interface UseMigrationLogsOptions
extends QueryHookOptions<GetSystemLogsQuery, GetSystemLogsQueryVariables> {
shouldPoll?: boolean;
}
export interface Log {
level: string;
msg: string;
time: string;
}
/*
* Returns logs for the current database migration.
* @param options - Options for the getSystemLogs query.
* @returns - An object with three properties:
* - logs: Logs for the current/latest database migration.
* - loading: true if the getLogs query is in a loading state.
* - error: Error object if the query failed.
*/
export default function useMigrationLogs(
options: UseMigrationLogsOptions = {},
): {
logs: Partial<Log>[];
loading: boolean;
error: ApolloError;
} {
const { currentProject } = useCurrentWorkspaceAndProject();
const isVisible = useVisibilityChange();
const { data: appStatesData } = useGetApplicationStateQuery({
variables: { appId: currentProject?.id },
skip: !currentProject,
});
const migrationStartTimestamp = appStatesData?.app?.appStates?.find(
(state) => state.stateId === ApplicationStatus.Migrating,
)?.createdAt;
const from = new Date(migrationStartTimestamp);
const { data, loading, error, startPolling, stopPolling } =
useGetSystemLogsQuery({
...options,
variables: {
...options.variables,
appID: currentProject.id,
action: 'change-database-version',
from,
},
skip: !currentProject || !from,
skipPollAttempt: () => !isVisible,
});
useEffect(() => {
if (options.shouldPoll) {
startPolling(options.pollInterval || 5000);
}
return () => stopPolling();
}, [stopPolling, startPolling, options.shouldPoll, options.pollInterval]);
const systemLogs = data?.systemLogs ?? [];
const sortedLogs = [...systemLogs];
sortedLogs.sort(
(a, b) => new Date(a.timestamp).valueOf() - new Date(b.timestamp).valueOf(),
); // sort in ascending order
const logs = sortedLogs.map(({ log }) => {
let logObj: Partial<Log> = {};
try {
logObj = JSON.parse(log);
return logObj;
} catch (e) {
console.error('Failed to parse log', log);
return undefined;
}
});
return {
logs,
loading,
error,
};
}

View File

@@ -19,6 +19,7 @@ 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,
@@ -209,6 +210,7 @@ function ColumnAutocomplete(
]);
}
const options = useColumnGroups({
selectedSchema,
selectedTable,
@@ -241,6 +243,33 @@ function ColumnAutocomplete(
onChange: handleChange,
});
function handleInputValueChange(event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
const {value} = event.target
setInputValue(value)
setSelectedColumn(
{
value,
label: value,
metadata: selectedColumn?.metadata || {
table_schema: selectedSchema,
table_name: selectedTable,
}
});
onChange?.(event, {
value:
selectedRelationships.length > 0
? [relationshipDotNotation, value].join('.')
: value,
columnMetadata: {
table_schema: selectedSchema,
table_name: selectedTable,
},
});
}
return (
<>
<div {...getRootProps()} className={rootClassName}>
@@ -293,7 +322,7 @@ function ColumnAutocomplete(
helperText={
String(tableError || metadataError || '') || props.helperText
}
onChange={(event) => setInputValue(event.target.value)}
onChange={handleInputValueChange}
value={inputValue}
startAdornment={
selectedColumn || relationshipDotNotation ? (
@@ -305,7 +334,7 @@ function ColumnAutocomplete(
className="!ml-2 flex-shrink-0 truncate lg:max-w-[200px]"
>
<Text component="span" color="disabled">
{defaultTable}
{selectedTable}
</Text>
.
{relationshipDotNotation && (

View File

@@ -0,0 +1,27 @@
import { Alert } from '@/components/ui/v2/Alert';
import { XIcon } from '@/components/ui/v2/icons/XIcon';
import { Text } from '@/components/ui/v2/Text';
export default function DatabaseMigrateWarning() {
return (
<Alert severity="error" className="flex flex-col gap-3 text-left">
<Text
className="flex items-center gap-1 font-semibold"
sx={{
color: 'error.main',
}}
>
<XIcon className="h-4 w-4" /> Error: Database version upgrade not
possible
</Text>
<Text
sx={{
color: 'error.main',
}}
>
Your project isn&apos;t currently in a healthy state. Please, review
before proceeding with the upgrade.
</Text>
</Alert>
);
}

View File

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

View File

@@ -0,0 +1,38 @@
import { Alert } from '@/components/ui/v2/Alert';
import { Box } from '@/components/ui/v2/Box';
import { Text } from '@/components/ui/v2/Text';
import { useEstimatedDatabaseMigrationDowntime } from '@/features/database/common/hooks/useEstimatedDatabaseMigrationDowntime';
export default function DatabaseMigrateDowntimeWarning() {
const { downtimeShort } = useEstimatedDatabaseMigrationDowntime();
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: upgrading Postgres major version
</Text>
<div className="flex">
<Box
sx={{
backgroundColor: 'beige.main',
}}
className="py-1/2 flex items-center justify-center text-nowrap rounded-full px-2 font-semibold"
>
Estimated downtime ~{downtimeShort}
</Box>
</div>
</div>
<div className="flex flex-col gap-4">
<Text>
Upgrading a major version of Postgres requires downtime. The amount of
downtime will depend on your database size, so plan ahead in order to
reduce the impact on your users.
</Text>
<Text>
Note that it isn&apos;t possible to downgrade between major versions.
</Text>
</div>
</Alert>
);
}

View File

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

View File

@@ -0,0 +1,112 @@
import { Box } from '@/components/ui/v2/Box';
import { Text } from '@/components/ui/v2/Text';
import { useMigrationLogs } from '@/features/database/common/hooks/useMigrationLogs';
export default function DatabaseMigrateLogsModal() {
const { logs, loading, error } = useMigrationLogs({
shouldPoll: true,
});
if (error) {
return (
<Box className="pt-2">
<Box
className="min-h-80 p-4"
sx={{
backgroundColor: (theme) =>
theme.palette.mode === 'dark' ? 'grey.300' : 'grey.700',
}}
>
<Text
className="font-mono"
sx={{
color: (theme) =>
theme.palette.mode === 'dark' ? 'grey.900' : 'grey.100',
}}
>
Could not fetch logs. Error: {error.message}
</Text>
</Box>
</Box>
);
}
if (loading) {
return (
<Box className="pt-2">
<Box
className="min-h-80 p-4"
sx={{
backgroundColor: (theme) =>
theme.palette.mode === 'dark' ? 'grey.300' : 'grey.700',
}}
>
<Text
className="font-mono"
sx={{
color: (theme) =>
theme.palette.mode === 'dark' ? 'grey.900' : 'grey.100',
}}
>
Loading...
</Text>
</Box>
</Box>
);
}
if (logs.length === 0) {
return (
<Box className="pt-2">
<Box
className="min-h-80 p-4"
sx={{
backgroundColor: (theme) =>
theme.palette.mode === 'dark' ? 'grey.300' : 'grey.700',
}}
>
<Text
className="font-mono"
sx={{
color: (theme) =>
theme.palette.mode === 'dark' ? 'grey.900' : 'grey.100',
}}
>
No logs found
</Text>
</Box>
</Box>
);
}
return (
<Box className="pt-2">
<Box
className="min-h-80 p-4"
sx={{
backgroundColor: (theme) =>
theme.palette.mode === 'dark' ? 'grey.300' : 'grey.700',
}}
>
{logs.map((logObj) => {
if (logObj?.level && logObj?.msg) {
return (
<Text
key={`${logObj.msg}${logObj.time}`}
className="font-mono"
sx={{
color: (theme) =>
theme.palette.mode === 'dark' ? 'grey.900' : 'grey.100',
}}
>
{logObj.level.toUpperCase()}: {logObj.msg}
</Text>
);
}
return undefined;
})}
</Box>
</Box>
);
}

View File

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

View File

@@ -0,0 +1,121 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Text } from '@/components/ui/v2/Text';
import { useEstimatedDatabaseMigrationDowntime } from '@/features/database/common/hooks/useEstimatedDatabaseMigrationDowntime';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
GetPostgresSettingsDocument,
GetWorkspaceAndProjectDocument,
useUpdateDatabaseVersionMutation,
} from '@/utils/__generated__/graphql';
import { useState } from 'react';
import { twMerge } from 'tailwind-merge';
export interface DatabaseMigrateVersionConfirmationDialogProps {
/**
* Function to be called when the user clicks the cancel button.
*/
onCancel: () => void;
/**
* Function to be called when the user clicks the proceed button.
*/
onProceed: () => void;
/**
* New version to migrate to.
*/
postgresVersion: string;
}
export default function DatabaseMigrateVersionConfirmationDialog({
onCancel,
onProceed,
postgresVersion,
}: DatabaseMigrateVersionConfirmationDialogProps) {
const isPlatform = useIsPlatform();
const { openDialog, closeDialog } = useDialog();
const localMimirClient = useLocalMimirClient();
const [loading, setLoading] = useState(false);
const { currentProject } = useCurrentWorkspaceAndProject();
const [updatePostgresMajor] = useUpdateDatabaseVersionMutation({
refetchQueries: [
GetPostgresSettingsDocument,
GetWorkspaceAndProjectDocument,
],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { downtime } = useEstimatedDatabaseMigrationDowntime({
fetchPolicy: 'cache-only',
});
async function handleClick() {
setLoading(true);
await execPromiseWithErrorToast(
async () => {
await updatePostgresMajor({
variables: {
appId: currentProject.id,
version: postgresVersion,
},
});
onProceed();
closeDialog();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Updating postgres version...',
successMessage: 'Major version upgrade started.',
errorMessage:
'An error occurred while updating the database version. Please try again later.',
},
);
}
return (
<Box className={twMerge('w-full rounded-lg p-6 pt-0 text-left')}>
<div className="grid grid-flow-row gap-6">
<Text>
The upgrade process will require an{' '}
<span className="font-semibold">
estimated {downtime} of downtime
</span>
. To continue with the upgrade process, click on &quot;Proceed&quot;.
</Text>
<div className="grid grid-flow-col gap-4">
<Button
variant="outlined"
color="secondary"
onClick={() => {
onCancel();
closeDialog();
}}
>
Cancel
</Button>
<Button onClick={handleClick} loading={loading}>
Proceed
</Button>
</div>
</div>
</Box>
);
}

View File

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

View File

@@ -5,28 +5,46 @@ import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { RepeatIcon } from '@/components/ui/v2/icons/RepeatIcon';
import { useGetPostgresVersion } from '@/features/database/common/hooks/useGetPostgresVersion';
import { useIsDatabaseMigrating } from '@/features/database/common/hooks/useIsDatabaseMigrating';
import { DatabaseMigrateDisabledError } from '@/features/database/settings/components/DatabaseMigrateDisabledError';
import { DatabaseMigrateDowntimeWarning } from '@/features/database/settings/components/DatabaseMigrateDowntimeWarning';
import { DatabaseMigrateLogsModal } from '@/features/database/settings/components/DatabaseMigrateLogsModal';
import { DatabaseMigrateVersionConfirmationDialog } from '@/features/database/settings/components/DatabaseMigrateVersionConfirmationDialog';
import { DatabaseUpdateInProgressWarning } from '@/features/database/settings/components/DatabaseUpdateInProgressWarning';
import { useAppState } from '@/features/projects/common/hooks/useAppState';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetPostgresSettingsDocument,
GetWorkspaceAndProjectDocument,
Software_Type_Enum,
useGetPostgresSettingsQuery,
useGetSoftwareVersionsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { ApplicationStatus } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
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({
version: Yup.object({
majorVersion: Yup.object({
label: Yup.string().required(),
value: Yup.string().required(),
value: Yup.string().required('Major version is a required field'),
})
.label('Postgres Version')
.label('Postgres major version')
.required(),
minorVersion: Yup.object({
label: Yup.string().required(),
value: Yup.string().required('Minor version is a required field'),
})
.label('Postgres minor version')
.required(),
});
@@ -34,21 +52,31 @@ export type DatabaseServiceVersionFormValues = Yup.InferType<
typeof validationSchema
>;
type DatabaseServiceField = Required<
Yup.InferType<typeof validationSchema>['majorVersion']
>;
export default function DatabaseServiceVersionSettings() {
const isPlatform = useIsPlatform();
const { openDialog } = useDialog();
const { openDialog, closeDialog } = useDialog();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetPostgresSettingsDocument],
refetchQueries: [
GetPostgresSettingsDocument,
GetWorkspaceAndProjectDocument,
],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetPostgresSettingsQuery({
variables: { appId: currentProject?.id },
...(!isPlatform ? { client: localMimirClient } : {}),
});
const {
version: postgresVersion,
postgresMajor: currentPostgresMajor,
postgresMinor: currentPostgresMinor,
error: postgresSettingsError,
loading: loadingPostgresSettings,
} = useGetPostgresVersion();
const { data: databaseVersionsData } = useGetSoftwareVersionsQuery({
variables: {
@@ -57,14 +85,11 @@ export default function DatabaseServiceVersionSettings() {
skip: !isPlatform,
});
const { version } = data?.config?.postgres || {};
const databaseVersions = databaseVersionsData?.softwareVersions || [];
const availableVersions = Array.from(
new Set(databaseVersions.map((el) => el.version)).add(version),
new Set(databaseVersions.map((el) => el.version)).add(postgresVersion),
)
.sort()
.reverse()
.map((availableVersion) => ({
label: availableVersion,
value: availableVersion,
@@ -72,46 +97,140 @@ export default function DatabaseServiceVersionSettings() {
const form = useForm<DatabaseServiceVersionFormValues>({
reValidateMode: 'onSubmit',
defaultValues: { version: { label: '', value: '' } },
defaultValues: {
minorVersion: { label: '', value: '' },
majorVersion: { label: '', value: '' },
},
resolver: yupResolver(validationSchema),
});
const { formState, watch } = form;
const selectedMajor = watch('majorVersion').value;
const selectedMinor = watch('minorVersion').value;
const getMajorAndMinorVersions = (): {
availableMajorVersions: DatabaseServiceField[];
majorToMinorVersions: Record<string, DatabaseServiceField[]>;
} => {
const majorToMinorVersions = {};
const availableMajorVersions = [];
availableVersions.forEach((availableVersion) => {
if (!availableVersion.value) {
return;
}
const [major, minor] = availableVersion.value.split('.');
// Don't suggest versions that are lower than the current Postgres major version (can't downgrade)
if (Number(major) < Number(currentPostgresMajor)) {
return;
}
if (availableMajorVersions.every((item) => item.value !== major)) {
availableMajorVersions.push({
label: major,
value: major,
});
}
if (!majorToMinorVersions[major]) {
majorToMinorVersions[major] = [];
}
majorToMinorVersions[major].push({
label: minor,
value: minor,
});
});
return {
availableMajorVersions,
majorToMinorVersions,
};
};
const { availableMajorVersions, majorToMinorVersions } = useMemo(
getMajorAndMinorVersions,
[availableVersions, currentPostgresMajor],
);
const availableMinorVersions = majorToMinorVersions[selectedMajor] || [];
useEffect(() => {
if (!loading && version) {
if (
!loadingPostgresSettings &&
currentPostgresMajor &&
currentPostgresMinor
) {
form.reset({
version: {
label: version,
value: version,
majorVersion: {
label: currentPostgresMajor,
value: currentPostgresMajor,
},
minorVersion: {
label: currentPostgresMinor,
value: currentPostgresMinor,
},
});
}
}, [loading, version, form]);
}, [
loadingPostgresSettings,
currentPostgresMajor,
currentPostgresMinor,
form,
]);
if (loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading Postgres version..."
className="justify-center"
/>
);
}
const { isMigrating, shouldShowUpgradeLogs } = useIsDatabaseMigrating({
shouldPoll: true,
});
if (error) {
throw error;
}
const showMigrateWarning =
Number(selectedMajor) > Number(currentPostgresMajor);
const { formState } = form;
const { state } = useAppState();
const applicationUpdating =
state === ApplicationStatus.Updating ||
state === ApplicationStatus.Migrating;
const applicationUnhealthy =
state !== ApplicationStatus.Live && !applicationUpdating;
const isMajorVersionDirty = formState?.dirtyFields?.majorVersion;
const isMinorVersionDirty = formState?.dirtyFields?.minorVersion;
const isDirty = isMajorVersionDirty || isMinorVersionDirty;
const versionFieldsDisabled =
applicationUpdating || applicationUnhealthy || maintenanceActive;
const saveDisabled = versionFieldsDisabled || !isDirty;
const handleDatabaseServiceVersionsChange = async (
formValues: DatabaseServiceVersionFormValues,
) => {
const newVersion = `${formValues.majorVersion.value}.${formValues.minorVersion.value}`;
// Major version change
if (isMajorVersionDirty) {
openDialog({
title: 'Update Postgres MAJOR version',
component: (
<DatabaseMigrateVersionConfirmationDialog
postgresVersion={newVersion}
onCancel={() => {}}
onProceed={() => {}}
/>
),
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
return;
}
// Minor version change
const updateConfigPromise = updateConfig({
variables: {
appId: currentProject.id,
config: {
postgres: {
version: formValues.version.value,
version: newVersion,
},
},
},
@@ -143,6 +262,33 @@ export default function DatabaseServiceVersionSettings() {
);
};
const openLatestUpgradeLogsModal = async () => {
openDialog({
component: <DatabaseMigrateLogsModal />,
props: {
PaperProps: { className: 'p-0 max-w-2xl w-full' },
titleProps: {
onClose: closeDialog,
},
},
title: 'Postgres upgrade logs',
});
};
if (loadingPostgresSettings) {
return (
<ActivityIndicator
delay={1000}
label="Loading Postgres version..."
className="justify-center"
/>
);
}
if (postgresSettingsError) {
throw postgresSettingsError;
}
return (
<FormProvider {...form}>
<Form onSubmit={handleDatabaseServiceVersionsChange}>
@@ -151,54 +297,144 @@ export default function DatabaseServiceVersionSettings() {
description="The version of Postgres to use."
slotProps={{
submitButton: {
disabled: !formState.isDirty || maintenanceActive,
disabled: saveDisabled,
loading: formState.isSubmitting,
},
}}
docsLink="https://hub.docker.com/r/nhost/postgres/tags"
docsTitle="the latest releases"
className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
className="flex flex-col"
topRightElement={
shouldShowUpgradeLogs ? (
<Button
variant="outlined"
color="primary"
size="medium"
className="self-center"
onClick={openLatestUpgradeLogsModal}
startIcon={<RepeatIcon className="h-4 w-4" />}
>
View latest upgrade logs
</Button>
) : null
}
>
<ControlledAutocomplete
id="version"
name="version"
autoHighlight
freeSolo
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option || '';
}
return option.value;
}}
isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => {
const inputValueLower = inputValue.toLowerCase();
const matched = [];
const otherOptions = [];
options.forEach((option) => {
const optionLabelLower = option.label.toLowerCase();
if (optionLabelLower.startsWith(inputValueLower)) {
matched.push(option);
} else {
otherOptions.push(option);
<Box className="grid grid-flow-row gap-x-4 gap-y-2 lg:grid-cols-5">
<ControlledAutocomplete
id="majorVersion"
name="majorVersion"
autoHighlight
freeSolo
disabled={versionFieldsDisabled}
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option || '';
}
});
const result = [...matched, ...otherOptions];
return option.value;
}}
showCustomOption="auto"
isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => {
const inputValueLower = inputValue.toLowerCase();
const matched = [];
const otherOptions = [];
return result;
}}
fullWidth
className="lg:col-span-2"
options={availableVersions}
error={!!formState.errors?.version?.message}
helperText={formState.errors?.version?.message}
showCustomOption="auto"
customOptionLabel={(value) => `Use custom value: "${value}"`}
/>
options.forEach((option) => {
const optionLabelLower = option.label.toLowerCase();
if (optionLabelLower.startsWith(inputValueLower)) {
matched.push(option);
} else {
otherOptions.push(option);
}
});
const result = [...matched, ...otherOptions];
return result;
}}
onChange={(_event, value) => {
if (typeof value !== 'string' && !Array.isArray(value)) {
if (value.value !== selectedMajor) {
const nextAvailableMinorVersions =
majorToMinorVersions[value.value] || [];
const isSelectedMinorAvailable =
nextAvailableMinorVersions.some(
(minor) => minor.value === selectedMinor,
);
// If the selected minor version is not available in the new major version, select the first available minor version
if (
!isSelectedMinorAvailable &&
nextAvailableMinorVersions.length > 0
) {
form.setValue(
'minorVersion',
nextAvailableMinorVersions[0],
);
}
}
form.setValue('majorVersion', value);
}
}}
fullWidth
className="lg:col-span-1"
label="MAJOR"
options={availableMajorVersions}
error={!!formState.errors?.majorVersion?.value?.message}
helperText={formState.errors?.majorVersion?.value?.message}
customOptionLabel={(value) => `Use custom value: "${value}"`}
/>
<ControlledAutocomplete
id="minorVersion"
name="minorVersion"
autoHighlight
freeSolo
disabled={versionFieldsDisabled}
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option || '';
}
return option.value;
}}
isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => {
const inputValueLower = inputValue.toLowerCase();
const matched = [];
const otherOptions = [];
options.forEach((option) => {
const optionLabelLower = option.label.toLowerCase();
if (optionLabelLower.startsWith(inputValueLower)) {
matched.push(option);
} else {
otherOptions.push(option);
}
});
const result = [...matched, ...otherOptions];
return result;
}}
fullWidth
className="lg:col-span-2"
label="MINOR"
options={availableMinorVersions}
error={!!formState.errors?.minorVersion?.value?.message}
helperText={formState.errors?.minorVersion?.value?.message}
showCustomOption="auto"
customOptionLabel={(value) => `Use custom value: "${value}"`}
/>
</Box>
{showMigrateWarning && <DatabaseMigrateDowntimeWarning />}
{applicationUpdating && <DatabaseUpdateInProgressWarning />}
{applicationUnhealthy && !isMigrating && (
<DatabaseMigrateDisabledError />
)}
</SettingsContainer>
</Form>
</FormProvider>

View File

@@ -0,0 +1,14 @@
import { Alert } from '@/components/ui/v2/Alert';
import { ClockIcon } from '@/components/ui/v2/icons/ClockIcon';
import { Text } from '@/components/ui/v2/Text';
export default function DatabaseMigrateWarning() {
return (
<Alert severity="warning" className="flex flex-col gap-3 text-left">
<Text className="flex items-center gap-1 font-semibold">
<ClockIcon className="h-4 w-4" /> An update is in progress
</Text>
<Text>You can edit the version only after the update is complete.</Text>
</Alert>
);
}

View File

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

View File

@@ -1,11 +1,12 @@
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Option } from '@/components/ui/v2/Option';
import { Select } from '@/components/ui/v2/Select';
import { Autocomplete } from '@/components/ui/v2/Autocomplete';
import { DEFAULT_ROLES } from '@/features/graphql/common/utils/constants';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
import type { RemoteAppGetUsersCustomQuery } from '@/utils/__generated__/graphql';
import { useRemoteAppGetUsersCustomQuery } from '@/utils/__generated__/graphql';
import {
useRemoteAppGetUsersCustomLazyQuery,
type RemoteAppGetUsersCustomQuery,
} from '@/utils/__generated__/graphql';
import { debounce } from '@mui/material/utils';
import { useCallback, useEffect, useMemo, useState } from 'react';
export interface UserSelectProps {
/**
@@ -13,7 +14,7 @@ export interface UserSelectProps {
*/
onUserChange: (userId: string, availableRoles?: string[]) => void;
/**
* Class name to be applied to the `<Select />` element.
* Class name to be applied to the `<Autocomplete />` element.
*/
className?: string;
}
@@ -22,35 +23,87 @@ export default function UserSelect({
onUserChange,
...props
}: UserSelectProps) {
const { currentProject } = useCurrentWorkspaceAndProject();
const [inputValue, setInputValue] = useState('');
const [users, setUsers] = useState([]);
const [active, setActive] = useState(true);
const userApplicationClient = useRemoteApplicationGQLClient();
const { data, loading, error } = useRemoteAppGetUsersCustomQuery({
const [fetchAppUsers, { loading }] = useRemoteAppGetUsersCustomLazyQuery({
client: userApplicationClient,
variables: { where: {}, limit: 250, offset: 0 },
skip: !currentProject,
variables: {
where: {},
limit: 250,
offset: 0,
},
});
if (loading) {
return (
<div className={props.className}>
<ActivityIndicator label="Loading users..." delay={500} />
</div>
);
}
const fetchUsers = useCallback(
async (
request: { input: string },
callback: (results?: RemoteAppGetUsersCustomQuery['users']) => void,
) => {
const ilike = `%${request.input === 'Admin' ? '' : request.input}%`;
const { data } = await fetchAppUsers({
client: userApplicationClient,
variables: {
where: {
displayName: { _ilike: ilike },
},
limit: 250,
offset: 0,
},
});
if (error) {
throw error;
}
callback(data?.users);
},
[fetchAppUsers, userApplicationClient],
);
const fetchOptions = useMemo(() => debounce(fetchUsers, 1000), [fetchUsers]);
useEffect(() => {
fetchOptions({ input: inputValue }, (results) => {
if (active || inputValue === '') {
setUsers(results);
}
});
}, [inputValue, fetchOptions, active]);
const autocompleteOptions = [
{
value: 'admin',
label: 'Admin',
group: 'Admin',
},
...users.map((user) => ({
value: user.id,
label: user.displayName,
group: 'Users',
})),
];
return (
<Select
<Autocomplete
{...props}
id="user-select"
label="Make Request As"
hideEmptyHelperText
defaultValue="admin"
slotProps={{ root: { className: 'truncate' } }}
onChange={(_event, userId) => {
label="Make request as"
options={autocompleteOptions}
defaultValue={{
value: 'admin',
label: 'Admin',
group: 'Admin',
}}
autoComplete
fullWidth
autoSelect
groupBy={(option) => option.group}
autoHighlight
includeInputInList
loading={loading}
onChange={(_event, _value, reason, details) => {
setActive(false);
const userId = details.option.value;
if (typeof userId !== 'string') {
return;
}
@@ -61,22 +114,23 @@ export default function UserSelect({
return;
}
const user: RemoteAppGetUsersCustomQuery['users'][0] = data?.users.find(
const user: RemoteAppGetUsersCustomQuery['users'][0] = users.find(
({ id }) => id === userId,
);
const roles = user?.roles.map(({ role }) => role);
const roles = user?.roles?.map(({ role }) => role);
onUserChange(user.id, roles);
onUserChange(userId, roles ?? DEFAULT_ROLES);
fetchUsers({ input: '' }, (results) => {
if (results) {
setUsers(results);
}
});
}}
>
<Option value="admin">Admin</Option>
{data?.users.map(({ id, displayName, email, phoneNumber }) => (
<Option key={id} value={id}>
{displayName || email || phoneNumber || id}
</Option>
))}
</Select>
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
/>
);
}

View File

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

View File

@@ -0,0 +1,54 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
import type {
RemoteAppGetUsersCustomQuery,
RemoteAppGetUsersCustomQueryVariables,
} from '@/utils/__generated__/graphql';
import { useRemoteAppGetUsersCustomQuery } from '@/utils/__generated__/graphql';
import type { QueryHookOptions } from '@apollo/client';
export type UseFilesOptions = {
searchString?: string;
limit?: number;
offset?: number;
/**
* Custom options for the query.
*/
options?: QueryHookOptions<
RemoteAppGetUsersCustomQuery,
RemoteAppGetUsersCustomQueryVariables
>;
};
export default function useGetAppUsers({
searchString,
limit = 250,
offset = 0,
options = {},
}: UseFilesOptions) {
const { currentProject } = useCurrentWorkspaceAndProject();
const userApplicationClient = useRemoteApplicationGQLClient();
const { data, error, loading } = useRemoteAppGetUsersCustomQuery({
...options,
client: userApplicationClient,
variables: {
...options.variables,
where: searchString
? {
displayName: { _ilike: `%${searchString}%` },
}
: {},
limit,
offset,
},
skip: !currentProject,
});
const users = data?.users || [];
return {
users,
loading,
error,
};
}

View File

@@ -0,0 +1,40 @@
import { Alert } from '@/components/ui/v2/Alert';
import { Box } from '@/components/ui/v2/Box';
import { Text } from '@/components/ui/v2/Text';
import Link from 'next/link';
interface ApplicationLockedReasonProps {
reason?: string;
}
export default function ApplicationLockedReason({
reason,
}: ApplicationLockedReasonProps) {
return (
<Alert severity="warning" className="mx-auto max-w-xs gap-2 p-6 ">
<Text className="pb-4 text-left">
Your project has been temporarily locked due to the following reason:
</Text>
<Box
className="rounded-md p-2"
sx={{
backgroundColor: 'beige.main',
}}
>
<Text className="px-2 py-1 font-semibold">{reason}</Text>
</Box>
<Text className="pt-4 text-left">
Please{' '}
<Link
className="font-semibold underline underline-offset-2"
href="/support"
target="_blank"
rel="noopener noreferrer"
>
contact our support
</Link>{' '}
team for assistance.
</Text>
</Alert>
);
}

View File

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

View File

@@ -2,25 +2,24 @@ import { useDialog } from '@/components/common/DialogProvider';
import { Container } from '@/components/layout/Container';
import { Modal } from '@/components/ui/v1/Modal';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Alert } from '@/components/ui/v2/Alert';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Text } from '@/components/ui/v2/Text';
import { ApplicationInfo } from '@/features/projects/common/components/ApplicationInfo';
import { ApplicationLockedReason } from '@/features/projects/common/components/ApplicationLockedReason';
import { ApplicationPausedReason } from '@/features/projects/common/components/ApplicationPausedReason';
import { ApplicationPausedSymbol } from '@/features/projects/common/components/ApplicationPausedSymbol';
import { ChangePlanModal } from '@/features/projects/common/components/ChangePlanModal';
import { RemoveApplicationModal } from '@/features/projects/common/components/RemoveApplicationModal';
import { StagingMetadata } from '@/features/projects/common/components/StagingMetadata';
import { useAppPausedReason } from '@/features/projects/common/hooks/useAppPausedReason';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCurrentUserOwner';
import {
GetAllWorkspacesAndProjectsDocument,
useGetFreeAndActiveProjectsQuery,
useUnpauseApplicationMutation,
} from '@/generated/graphql';
import { MAX_FREE_PROJECTS } from '@/utils/constants/common';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { useUserData } from '@nhost/nextjs';
import Image from 'next/image';
import { useState } from 'react';
export default function ApplicationPaused() {
@@ -28,7 +27,6 @@ export default function ApplicationPaused() {
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const isOwner = useIsCurrentUserOwner();
const user = useUserData();
const [showDeletingModal, setShowDeletingModal] = useState(false);
const [unpauseApplication, { loading: changingApplicationStateLoading }] =
@@ -36,13 +34,8 @@ export default function ApplicationPaused() {
refetchQueries: [{ query: GetAllWorkspacesAndProjectsDocument }],
});
const { data, loading } = useGetFreeAndActiveProjectsQuery({
variables: { userId: user?.id },
skip: !user,
});
const numberOfFreeAndLiveProjects = data?.freeAndActiveProjects.length || 0;
const wakeUpDisabled = numberOfFreeAndLiveProjects >= MAX_FREE_PROJECTS;
const { isLocked, lockedReason, freeAndLiveProjectsNumberExceeded, loading } =
useAppPausedReason();
async function handleTriggerUnpausing() {
await execPromiseWithErrorToast(
@@ -77,75 +70,67 @@ export default function ApplicationPaused() {
/>
</Modal>
<Container className="mx-auto mt-20 grid max-w-lg grid-flow-row gap-4 text-center">
<Container className="mx-auto mt-20 grid max-w-lg grid-flow-row gap-6 text-center">
<div className="mx-auto flex w-centImage flex-col text-center">
<Image
src="/assets/PausedApp.svg"
alt="Closed Eye"
width={72}
height={72}
/>
<ApplicationPausedSymbol isLocked={isLocked} />
</div>
<Box className="grid grid-flow-row gap-1">
<Box className="grid grid-flow-row gap-6">
<Text variant="h3" component="h1">
{currentProject.name} is sleeping
{currentProject.name} is {isLocked ? 'locked' : 'paused'}
</Text>
{isLocked ? (
<ApplicationLockedReason reason={lockedReason} />
) : (
<>
<ApplicationPausedReason
freeAndLiveProjectsNumberExceeded={
freeAndLiveProjectsNumberExceeded
}
/>
<div className="grid grid-flow-row gap-4">
{isOwner && (
<Button
className="mx-auto w-full max-w-xs"
onClick={() => {
openDialog({
component: <ChangePlanModal />,
props: {
PaperProps: { className: 'p-0' },
maxWidth: 'lg',
},
});
}}
>
Upgrade to Pro
</Button>
)}
<Button
variant="borderless"
className="mx-auto w-full max-w-xs"
loading={changingApplicationStateLoading}
disabled={
changingApplicationStateLoading ||
freeAndLiveProjectsNumberExceeded
}
onClick={handleTriggerUnpausing}
>
Wake Up
</Button>
<Text>
Starter projects stop responding to API calls after 7 days of
inactivity. Upgrade to Pro to avoid autosleep.
</Text>
</Box>
<Box className="grid grid-flow-row gap-2">
{isOwner && (
<Button
className="mx-auto w-full max-w-[280px]"
onClick={() => {
openDialog({
component: <ChangePlanModal />,
props: {
PaperProps: { className: 'p-0' },
maxWidth: 'lg',
},
});
}}
>
Upgrade to Pro
</Button>
{isOwner && (
<Button
color="error"
variant="outlined"
className="mx-auto w-full max-w-xs"
onClick={() => setShowDeletingModal(true)}
>
Delete Project
</Button>
)}
</div>
</>
)}
<div className="grid grid-flow-row gap-2">
<Button
variant="borderless"
className="mx-auto w-full max-w-[280px]"
loading={changingApplicationStateLoading}
disabled={changingApplicationStateLoading || wakeUpDisabled}
onClick={handleTriggerUnpausing}
>
Wake Up
</Button>
{wakeUpDisabled && (
<Alert severity="warning" className="mx-auto max-w-xs text-left">
Note: Only one free project can be active at any given time.
Please pause your active free project before unpausing{' '}
{currentProject.name}.
</Alert>
)}
{isOwner && (
<Button
color="error"
variant="borderless"
className="mx-auto w-full max-w-[280px]"
onClick={() => setShowDeletingModal(true)}
>
Delete Project
</Button>
)}
</div>
</Box>
<StagingMetadata>

View File

@@ -0,0 +1,31 @@
import { Alert } from '@/components/ui/v2/Alert';
import { Text } from '@/components/ui/v2/Text';
interface ApplicationPausedReasonProps {
freeAndLiveProjectsNumberExceeded?: boolean;
}
export default function ApplicationPausedReason({
freeAndLiveProjectsNumberExceeded,
}: ApplicationPausedReasonProps) {
return (
<Alert
severity="warning"
className="mx-auto flex max-w-xs flex-col gap-4 p-6 text-left"
>
<Text>
Starter projects will stop responding to API calls after 7 days of
inactivity, so consider
<span className="font-semibold"> upgrading to Pro </span>to avoid
auto-sleep.
</Text>
{freeAndLiveProjectsNumberExceeded && (
<Text>
Additionally, only 1 free project can be active at any given time, so
please pause your current active free project before unpausing
another.
</Text>
)}
</Alert>
);
}

View File

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

View File

@@ -0,0 +1,23 @@
import Image from 'next/image';
export default function ApplicationPausedSymbol({
isLocked,
}: {
isLocked?: boolean;
}) {
if (isLocked) {
return (
<Image src="/assets/LockedApp.svg" alt="Lock" width={72} height={72} />
);
}
// paused
return (
<Image
src="/assets/PausedApp.svg"
alt="Closed Eye"
width={72}
height={72}
/>
);
}

View File

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

View File

@@ -2,8 +2,8 @@ import { useDialog } from '@/components/common/DialogProvider';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Checkbox } from '@/components/ui/v2/Checkbox';
import { BaseDialog } from '@/components/ui/v2/Dialog';
import { Radio } from '@/components/ui/v2/Radio';
import { Text } from '@/components/ui/v2/Text';
import { useAppState } from '@/features/projects/common/hooks/useAppState';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
@@ -31,7 +31,7 @@ function Plan({ planName, price, setPlan, planId, selectedPlanId }: any) {
>
<div className="grid grid-flow-row gap-y-0.5">
<div className="grid grid-flow-col items-center justify-start gap-2">
<Checkbox
<Radio
onChange={setPlan}
checked={selectedPlanId === planId}
aria-label={planName}
@@ -241,7 +241,21 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
))}
</div>
<div className="mt-2 grid grid-flow-row gap-2">
<div className="mt-0">
<Text variant="subtitle2" className="w-full px-1">
For a complete list of features, visit our{' '}
<a
href="https://nhost.io/pricing"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
pricing page
</a>
</Text>
</div>
<div className="mt-6 grid grid-flow-row gap-2">
<Button
onClick={handleChangePlanClick}
disabled={!selectedPlan}

View File

@@ -143,7 +143,7 @@ export default function TOMLEditor() {
};
return (
<Box className="flex h-full flex-col">
<>
<Box className="flex w-full flex-col space-y-2 border-b p-4">
<Text className="font-semibold">Configuration Editor</Text>
</Box>
@@ -187,6 +187,6 @@ export default function TOMLEditor() {
Save
</Button>
</Box>
</Box>
</>
);
}

View File

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

View File

@@ -0,0 +1,45 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { MAX_FREE_PROJECTS } from '@/utils/constants/common';
import {
useGetFreeAndActiveProjectsQuery,
useGetProjectIsLockedQuery,
} from '@/utils/__generated__/graphql';
import { useUserData } from '@nhost/nextjs';
/**
* This hook returns the reason why the application is paused.
* It returns the locked reason and if the user has exceeded the number of free and live projects.
*/
export default function useAppPausedReason(): {
isLocked: boolean;
lockedReason: string | undefined;
freeAndLiveProjectsNumberExceeded: boolean;
loading: boolean;
} {
const { currentProject } = useCurrentWorkspaceAndProject();
const user = useUserData();
const { data, loading } = useGetFreeAndActiveProjectsQuery({
variables: { userId: user?.id },
skip: !user,
});
const { data: isLockedData } = useGetProjectIsLockedQuery({
variables: { appId: currentProject.id },
skip: !currentProject,
});
const isLocked = isLockedData?.app?.isLocked;
const lockedReason = isLockedData?.app?.isLockedReason;
const numberOfFreeAndLiveProjects = data?.freeAndActiveProjects.length || 0;
const freeAndLiveProjectsNumberExceeded =
numberOfFreeAndLiveProjects >= MAX_FREE_PROJECTS;
return {
isLocked,
lockedReason,
freeAndLiveProjectsNumberExceeded,
loading,
};
}

View File

@@ -38,6 +38,13 @@ export default function useNavigationVisible() {
return false;
}
if (
state === ApplicationStatus.Migrating &&
currentProject.desiredState === ApplicationStatus.Live
) {
return true;
}
if (
state === ApplicationStatus.Live ||
state === ApplicationStatus.Updating

View File

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

View File

@@ -0,0 +1,72 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type { ServiceHealthInfo } from '@/features/projects/overview/health';
import {
useGetProjectServicesHealthQuery,
type GetProjectServicesHealthQuery,
type GetProjectServicesHealthQueryVariables,
} from '@/utils/__generated__/graphql';
import type { QueryHookOptions } from '@apollo/client';
import { useVisibilityChange } from '@uidotdev/usehooks';
import { useEffect } from 'react';
export interface UseServiceStatusOptions
extends QueryHookOptions<
GetProjectServicesHealthQuery,
GetProjectServicesHealthQueryVariables
> {
shouldPoll?: boolean;
}
export default function useServiceStatus(
options: UseServiceStatusOptions = {},
) {
const isPlatform = useIsPlatform();
const { currentProject } = useCurrentWorkspaceAndProject();
const isVisible = useVisibilityChange();
const { data, loading, refetch, startPolling, stopPolling } =
useGetProjectServicesHealthQuery({
...options,
variables: { ...options.variables, appId: currentProject?.id },
skip: !isPlatform || !currentProject,
skipPollAttempt: () => !isVisible,
});
// Fetch when mounted
useEffect(() => {
refetch();
}, [refetch]);
useEffect(() => {
if (options.shouldPoll) {
startPolling(options.pollInterval || 10000);
}
return () => stopPolling();
}, [stopPolling, startPolling, options.shouldPoll, options.pollInterval]);
const serviceMap: { [key: string]: ServiceHealthInfo | undefined } = {};
data?.getProjectStatus?.services.forEach((service) => {
serviceMap[service.name] = service;
});
const {
'hasura-auth': auth,
'hasura-storage': storage,
postgres,
hasura,
ai,
...run
} = serviceMap;
return {
loading,
auth,
storage,
postgres,
hasura,
ai,
run,
};
}

View File

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

View File

@@ -0,0 +1,156 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
Software_Type_Enum,
useGetConfiguredVersionsQuery,
useGetRecommendedSoftwareVersionsQuery,
type GetConfiguredVersionsQuery,
type GetConfiguredVersionsQueryVariables,
} from '@/utils/__generated__/graphql';
import type { QueryHookOptions } from '@apollo/client';
import { useVisibilityChange } from '@uidotdev/usehooks';
import { useEffect } from 'react';
export interface UseSoftwareVersionsInfoOptions
extends QueryHookOptions<
GetConfiguredVersionsQuery,
GetConfiguredVersionsQueryVariables
> {}
type ServiceVersionInfo = {
configuredVersion: string | undefined;
recommendedVersions: string[];
isVersionMismatch: boolean;
};
export default function useSoftwareVersionsInfo(
options: UseSoftwareVersionsInfoOptions = {},
): {
loading: boolean;
auth: ServiceVersionInfo;
storage: ServiceVersionInfo;
postgres: ServiceVersionInfo;
hasura: ServiceVersionInfo;
ai: ServiceVersionInfo;
isAIEnabled: boolean;
} {
const isPlatform = useIsPlatform();
const { currentProject } = useCurrentWorkspaceAndProject();
const isVisible = useVisibilityChange();
// Recommended software versions are not polled by default
const { data: recommendedVersionsData, loading: loadingRecommendedVersions } =
useGetRecommendedSoftwareVersionsQuery({
skip: !isPlatform || !currentProject,
});
const {
data: configuredVersionsData,
loading: loadingConfiguredVersions,
refetch: refetchConfiguredVersions,
stopPolling,
} = useGetConfiguredVersionsQuery({
...options,
variables: { ...options.variables, appId: currentProject?.id },
skip: !isPlatform || !currentProject,
skipPollAttempt: () => !isVisible,
pollInterval: options.pollInterval || 10000,
});
// fetch when mounted
useEffect(() => {
refetchConfiguredVersions();
return () => stopPolling();
}, [refetchConfiguredVersions, stopPolling]);
const recommendedVersions = {
'hasura-auth': [],
'hasura-storage': [],
postgres: [],
hasura: [],
ai: [],
};
recommendedVersionsData?.softwareVersions.forEach(({ software, version }) => {
switch (software) {
case Software_Type_Enum.Auth:
recommendedVersions['hasura-auth'].push(version);
break;
case Software_Type_Enum.Storage:
recommendedVersions['hasura-storage'].push(version);
break;
case Software_Type_Enum.PostgreSql:
recommendedVersions.postgres.push(version);
break;
case Software_Type_Enum.Hasura:
recommendedVersions.hasura.push(version);
break;
case Software_Type_Enum.Graphite:
recommendedVersions.ai.push(version);
break;
default:
break;
}
});
const isVersionMismatch = (
service: string,
configuredVersion: string | undefined,
) =>
!recommendedVersions[service].some(
(version) => version === configuredVersion,
);
// Check if configured version can't be found in recommended versions
const isAuthVersionMismatch = isVersionMismatch(
'hasura-auth',
configuredVersionsData?.config?.auth?.version,
);
const isStorageVersionMismatch = isVersionMismatch(
'hasura-storage',
configuredVersionsData?.config?.storage?.version,
);
const isPostgresVersionMismatch = isVersionMismatch(
'postgres',
configuredVersionsData?.config?.postgres?.version,
);
const isHasuraVersionMismatch = isVersionMismatch(
'hasura',
configuredVersionsData?.config?.hasura?.version,
);
const isAIVersionMismatch = isVersionMismatch(
'ai',
configuredVersionsData?.config?.ai?.version,
);
return {
loading: loadingConfiguredVersions || loadingRecommendedVersions,
auth: {
configuredVersion: configuredVersionsData?.config?.auth?.version,
recommendedVersions: recommendedVersions['hasura-auth'],
isVersionMismatch: isAuthVersionMismatch,
},
storage: {
configuredVersion: configuredVersionsData?.config?.storage?.version,
recommendedVersions: recommendedVersions['hasura-storage'],
isVersionMismatch: isStorageVersionMismatch,
},
postgres: {
configuredVersion: configuredVersionsData?.config?.postgres?.version,
recommendedVersions: recommendedVersions.postgres,
isVersionMismatch: isPostgresVersionMismatch,
},
hasura: {
configuredVersion: configuredVersionsData?.config?.hasura?.version,
recommendedVersions: recommendedVersions.hasura,
isVersionMismatch: isHasuraVersionMismatch,
},
ai: {
configuredVersion: configuredVersionsData?.config?.ai?.version,
recommendedVersions: recommendedVersions.ai,
isVersionMismatch: isAIVersionMismatch,
},
isAIEnabled: Boolean(configuredVersionsData?.config?.ai),
};
}

View File

@@ -1,6 +1,6 @@
const planDescriptions = {
Starter: '1 GB database, 5 GB of file storage, 10 GB of network traffic.',
Pro: '10 GB database, 25 GB of file storage, 50 GB of network traffic, and backups.',
Pro: '10 GB database, 50 GB of file storage, 50 GB of network traffic, and backups.',
Team: 'Reach out to us at support@nhost.io to have your private channel set up.',
};

View File

@@ -0,0 +1,67 @@
import { Box } from '@/components/ui/v2/Box';
import { CheckIcon } from '@/components/ui/v2/icons/CheckIcon';
import { QuestionMarkIcon } from '@/components/ui/v2/icons/QuestionMarkIcon';
import { serviceStateToThemeColor } from '@/features/projects/overview/health';
import { ServiceState } from '@/utils/__generated__/graphql';
interface AccordionHealthBadgeProps {
serviceState?: ServiceState;
unknownState?: boolean;
/*
* Blinking animation to indicate that the service is updating.
*/
blink?: boolean;
}
export default function AccordionHealthBadge({
serviceState,
unknownState,
blink,
}: AccordionHealthBadgeProps) {
if (unknownState) {
return (
<Box
sx={{
backgroundColor: serviceStateToThemeColor.get(serviceState),
}}
className="flex h-2.5 w-2.5 items-center justify-center rounded-full"
>
<QuestionMarkIcon
sx={{
color: (theme) =>
theme.palette.mode === 'dark' ? 'grey.200' : 'grey.100',
}}
className="h-3/4 w-3/4 stroke-2"
/>
</Box>
);
}
if (serviceState === ServiceState.Running) {
return (
<Box
sx={{
backgroundColor: serviceStateToThemeColor.get(serviceState),
}}
className="flex h-2.5 w-2.5 items-center justify-center rounded-full"
>
<CheckIcon
sx={{
color: (theme) =>
theme.palette.mode === 'dark' ? 'grey.200' : 'grey.100',
}}
className="h-3/4 w-3/4 stroke-2"
/>
</Box>
);
}
return (
<Box
sx={{
backgroundColor: serviceStateToThemeColor.get(serviceState),
}}
className={`h-2.5 w-2.5 rounded-full ${blink ? 'animate-pulse' : ''}`}
/>
);
}

View File

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

View File

@@ -1,6 +1,4 @@
import { useDialog } from '@/components/common/DialogProvider';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { AIIcon } from '@/components/ui/v2/icons/AIIcon';
import { DatabaseIcon } from '@/components/ui/v2/icons/DatabaseIcon';
import { HasuraIcon } from '@/components/ui/v2/icons/HasuraIcon';
@@ -9,105 +7,45 @@ import { StorageIcon } from '@/components/ui/v2/icons/StorageIcon';
import { UserIcon } from '@/components/ui/v2/icons/UserIcon';
import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useServiceStatus } from '@/features/projects/common/hooks/useServiceStatus';
import { useSoftwareVersionsInfo } from '@/features/projects/common/hooks/useSoftwareVersionsInfo';
import { OverviewProjectHealthModal } from '@/features/projects/overview/components/OverviewProjectHealthModal';
import { ProjectHealthCard } from '@/features/projects/overview/components/ProjectHealthCard';
import { RunStatusTooltip } from '@/features/projects/overview/components/RunStatusTooltip';
import { ServiceVersionTooltip } from '@/features/projects/overview/components/ServiceVersionTooltip';
import {
baseServices,
findHighestImportanceState,
serviceStateToThemeColor,
type ServiceHealthInfo,
} from '@/features/projects/overview/health';
import {
ServiceState,
useGetConfiguredVersionsQuery,
useGetProjectServicesHealthQuery,
useGetRecommendedSoftwareVersionsQuery,
} from '@/generated/graphql';
interface RunStatusTooltipProps {
servicesStatusInfo?: Array<ServiceHealthInfo>;
openHealthModal?: (
defaultExpanded?: keyof typeof baseServices | 'run',
) => void;
}
function RunStatusTooltip({
servicesStatusInfo,
openHealthModal,
}: RunStatusTooltipProps) {
return (
<div className="flex w-full flex-col gap-3 px-2 py-3">
<ol className="m-0 flex flex-col gap-3">
{servicesStatusInfo.map((service) => (
<li
key={service.name}
className="flex flex-row items-center gap-4 text-ellipsis text-nowrap leading-5"
>
<Box
sx={{
backgroundColor: serviceStateToThemeColor.get(service.state),
}}
className={`h-3 w-3 flex-shrink-0 rounded-full ${
service.state === ServiceState.Updating ? 'animate-pulse' : ''
}`}
/>
<Text
sx={{
color: (theme) =>
theme.palette.mode === 'dark'
? 'text.primary'
: 'text.primary',
}}
className="font-semibold"
>
{service.name}
</Text>
</li>
))}
</ol>
<Button variant="outlined" onClick={() => openHealthModal('run')}>
View state
</Button>
</div>
);
}
export default function OverviewProjectHealth() {
const isPlatform = useIsPlatform();
const { currentProject } = useCurrentWorkspaceAndProject();
const { data: recommendedVersionsData, loading: loadingRecommendedVersions } =
useGetRecommendedSoftwareVersionsQuery({
skip: !isPlatform || !currentProject,
});
const { openDialog, closeDialog } = useDialog();
const { data: configuredVersionsData, loading: loadingConfiguredVersions } =
useGetConfiguredVersionsQuery({
variables: {
appId: currentProject?.id,
},
skip: !isPlatform || !currentProject,
});
const {
loading: loadingVersions,
auth: authVersionInfo,
storage: storageVersionInfo,
postgres: postgresVersionInfo,
hasura: hasuraVersionInfo,
ai: aiVersionInfo,
isAIEnabled,
} = useSoftwareVersionsInfo();
const {
data: projectServicesHealthData,
loading: loadingProjectServicesHealth,
} = useGetProjectServicesHealthQuery({
variables: {
appId: currentProject?.id,
},
skip: !isPlatform || !currentProject,
auth: authStatus,
storage: storageStatus,
postgres: postgresStatus,
hasura: hasuraStatus,
ai: aiStatus,
run: runStatus,
} = useServiceStatus({
shouldPoll: true,
});
if (
loadingRecommendedVersions ||
loadingConfiguredVersions ||
loadingProjectServicesHealth
) {
if (loadingVersions || loadingProjectServicesHealth) {
return (
<div className="grid grid-flow-row content-start gap-6">
<Text variant="h3">Project Health</Text>
@@ -133,78 +71,12 @@ export default function OverviewProjectHealth() {
);
}
const isAIServiceEnabled = !!configuredVersionsData?.config?.ai;
const getRecommendedVersions = (softwareName: string): string[] =>
recommendedVersionsData?.softwareVersions.reduce(
(recommendedVersions, service) => {
if (service.software === softwareName) {
recommendedVersions.push(service.version);
}
return recommendedVersions;
},
[],
) ?? [];
const authRecommendedVersions = getRecommendedVersions(
baseServices['hasura-auth'].softwareVersionsName,
);
const hasuraRecommendedVersions = getRecommendedVersions(
baseServices.hasura.softwareVersionsName,
);
const postgresRecommendedVersions = getRecommendedVersions(
baseServices.postgres.softwareVersionsName,
);
const storageRecommendedVersions = getRecommendedVersions(
baseServices['hasura-storage'].softwareVersionsName,
);
const aiRecommendedVersions = getRecommendedVersions(
baseServices.ai.softwareVersionsName,
);
// Check if configured version can't be found in recommended versions
const isAuthVersionMismatch = !authRecommendedVersions.find(
(version) => configuredVersionsData?.config?.auth?.version === version,
);
const isHasuraVersionMismatch = !hasuraRecommendedVersions.find(
(version) => configuredVersionsData?.config?.hasura?.version === version,
);
const isPostgresVersionMismatch = !postgresRecommendedVersions.find(
(version) => configuredVersionsData?.config?.postgres?.version === version,
);
const isStorageVersionMismatch = !storageRecommendedVersions.find(
(version) => configuredVersionsData?.config?.storage?.version === version,
);
const isAIVersionMismatch = !aiRecommendedVersions.find(
(version) => configuredVersionsData?.config?.ai?.version === version,
);
const serviceMap: { [key: string]: ServiceHealthInfo | undefined } = {};
projectServicesHealthData?.getProjectStatus?.services.forEach((service) => {
serviceMap[service.name] = service;
});
const {
'hasura-auth': authStatus,
'hasura-storage': storageStatus,
postgres: postgresStatus,
hasura: hasuraStatus,
ai: aiStatus,
...otherServicesStatus
} = serviceMap;
const openHealthModal = async (
defaultExpanded: keyof typeof baseServices | 'run',
) => {
openDialog({
component: (
<OverviewProjectHealthModal
servicesHealth={projectServicesHealthData}
defaultExpanded={defaultExpanded}
/>
<OverviewProjectHealthModal defaultExpanded={defaultExpanded} />
),
props: {
PaperProps: { className: 'p-0 max-w-2xl w-full' },
@@ -220,9 +92,9 @@ export default function OverviewProjectHealth() {
<ServiceVersionTooltip
serviceName={baseServices['hasura-auth'].displayName}
serviceKey="hasura-auth"
usedVersion={configuredVersionsData?.config?.auth?.version ?? ''}
recommendedVersionMismatch={isAuthVersionMismatch}
recommendedVersions={authRecommendedVersions}
usedVersion={authVersionInfo?.configuredVersion ?? ''}
recommendedVersionMismatch={authVersionInfo?.isVersionMismatch}
recommendedVersions={authVersionInfo?.recommendedVersions}
openHealthModal={openHealthModal}
state={authStatus?.state}
/>
@@ -232,9 +104,9 @@ export default function OverviewProjectHealth() {
<ServiceVersionTooltip
serviceName={baseServices.hasura.displayName}
serviceKey="hasura"
usedVersion={configuredVersionsData?.config?.hasura?.version ?? ''}
recommendedVersionMismatch={isHasuraVersionMismatch}
recommendedVersions={hasuraRecommendedVersions}
usedVersion={hasuraVersionInfo?.configuredVersion ?? ''}
recommendedVersionMismatch={hasuraVersionInfo?.isVersionMismatch}
recommendedVersions={hasuraVersionInfo?.recommendedVersions}
openHealthModal={openHealthModal}
state={hasuraStatus?.state}
/>
@@ -244,9 +116,9 @@ export default function OverviewProjectHealth() {
<ServiceVersionTooltip
serviceName={baseServices.postgres.displayName}
serviceKey="postgres"
usedVersion={configuredVersionsData?.config?.postgres?.version ?? ''}
recommendedVersionMismatch={isPostgresVersionMismatch}
recommendedVersions={postgresRecommendedVersions}
usedVersion={postgresVersionInfo?.configuredVersion ?? ''}
recommendedVersionMismatch={postgresVersionInfo?.isVersionMismatch}
recommendedVersions={postgresVersionInfo?.recommendedVersions}
openHealthModal={openHealthModal}
state={postgresStatus?.state}
/>
@@ -256,9 +128,9 @@ export default function OverviewProjectHealth() {
<ServiceVersionTooltip
serviceName={baseServices['hasura-storage'].displayName}
serviceKey="hasura-storage"
usedVersion={configuredVersionsData?.config?.storage?.version ?? ''}
recommendedVersionMismatch={isStorageVersionMismatch}
recommendedVersions={storageRecommendedVersions}
usedVersion={storageVersionInfo?.configuredVersion ?? ''}
recommendedVersionMismatch={storageVersionInfo?.isVersionMismatch}
recommendedVersions={storageVersionInfo?.recommendedVersions}
openHealthModal={openHealthModal}
state={storageStatus?.state}
/>
@@ -268,16 +140,16 @@ export default function OverviewProjectHealth() {
<ServiceVersionTooltip
serviceName={baseServices.ai.displayName}
serviceKey="ai"
usedVersion={configuredVersionsData?.config?.ai?.version ?? ''}
recommendedVersionMismatch={isAIVersionMismatch}
recommendedVersions={aiRecommendedVersions}
usedVersion={aiVersionInfo?.configuredVersion ?? ''}
recommendedVersionMismatch={aiVersionInfo?.isVersionMismatch}
recommendedVersions={aiVersionInfo?.recommendedVersions}
openHealthModal={openHealthModal}
state={aiStatus?.state}
/>
);
const runServices = Object.values(otherServicesStatus).filter((service) =>
service.name.startsWith('run-'),
const runServices = Object.values(runStatus).filter((service) =>
service?.name?.startsWith('run-'),
);
const runServicesStates = runServices.map((service) => service.state);
@@ -293,32 +165,32 @@ export default function OverviewProjectHealth() {
<ProjectHealthCard
icon={<UserIcon className="m-1 h-6 w-6" />}
tooltip={authTooltipElem}
isVersionMismatch={isAuthVersionMismatch}
isVersionMismatch={authVersionInfo?.isVersionMismatch}
state={authStatus?.state}
/>
<ProjectHealthCard
icon={<DatabaseIcon className="m-1 h-6 w-6" />}
tooltip={postgresTooltipElem}
isVersionMismatch={isPostgresVersionMismatch}
isVersionMismatch={postgresVersionInfo?.isVersionMismatch}
state={postgresStatus?.state}
/>
<ProjectHealthCard
icon={<StorageIcon className="m-1 h-6 w-6" />}
tooltip={storageTooltipElem}
isVersionMismatch={isStorageVersionMismatch}
isVersionMismatch={storageVersionInfo?.isVersionMismatch}
state={storageStatus?.state}
/>
<ProjectHealthCard
icon={<HasuraIcon className="m-1 h-6 w-6" />}
tooltip={hasuraTooltipElem}
isVersionMismatch={isHasuraVersionMismatch}
isVersionMismatch={hasuraVersionInfo?.isVersionMismatch}
state={hasuraStatus?.state}
/>
{isAIServiceEnabled && (
{isAIEnabled && (
<ProjectHealthCard
icon={<AIIcon className="m-1 h-6 w-6" />}
tooltip={aiTooltipElem}
isVersionMismatch={isAIVersionMismatch}
isVersionMismatch={aiVersionInfo?.isVersionMismatch}
state={aiStatus?.state}
/>
)}

View File

@@ -1,220 +1,33 @@
import { CodeBlock } from '@/components/presentational/CodeBlock';
import { Accordion } from '@/components/ui/v2/Accordion';
import { Box } from '@/components/ui/v2/Box';
import { Divider } from '@/components/ui/v2/Divider';
import { AIIcon } from '@/components/ui/v2/icons/AIIcon';
import { CheckIcon } from '@/components/ui/v2/icons/CheckIcon';
import { ChevronDownIcon } from '@/components/ui/v2/icons/ChevronDownIcon';
import { DatabaseIcon } from '@/components/ui/v2/icons/DatabaseIcon';
import { HasuraIcon } from '@/components/ui/v2/icons/HasuraIcon';
import { ServicesOutlinedIcon } from '@/components/ui/v2/icons/ServicesOutlinedIcon';
import { StorageIcon } from '@/components/ui/v2/icons/StorageIcon';
import { UserIcon } from '@/components/ui/v2/icons/UserIcon';
import { Text } from '@/components/ui/v2/Text';
import { useServiceStatus } from '@/features/projects/common/hooks/useServiceStatus';
import { ServiceAccordion } from '@/features/projects/overview/components/ServiceAccordion';
import {
findHighestImportanceState,
serviceStateToThemeColor,
type baseServices,
type ServiceHealthInfo,
} from '@/features/projects/overview/health';
import { removeTypename } from '@/utils/helpers';
import {
ServiceState,
type GetProjectServicesHealthQuery,
} from '@/utils/__generated__/graphql';
import Image from 'next/image';
import { type ReactElement } from 'react';
import { twMerge } from 'tailwind-merge';
interface ServiceAccordionProps {
serviceName: string;
serviceHealth: ServiceHealthInfo;
replicas: ServiceHealthInfo['replicas'];
serviceState: ServiceState;
/**
* Icon to display on the accordion.
*/
icon?: string | ReactElement;
/**
* Label of the icon.
*/
alt?: string;
iconIsComponent?: boolean;
defaultExpanded?: boolean;
}
function ServiceAccordion({
serviceName,
serviceHealth,
replicas,
serviceState,
icon,
iconIsComponent = true,
alt,
defaultExpanded = false,
}: ServiceAccordionProps) {
const replicasLabel = replicas.length === 1 ? 'replica' : 'replicas';
const serviceInfo = removeTypename(serviceHealth);
return (
<Accordion.Root defaultExpanded={defaultExpanded}>
<Accordion.Summary
expandIcon={
<ChevronDownIcon
sx={{
color: 'text.primary',
}}
/>
}
aria-controls="panel1-content"
id="panel1-header"
className="px-6"
>
<div className="flex flex-row justify-between gap-2 py-2">
<div className="flex items-center gap-3">
{iconIsComponent
? icon
: typeof icon === 'string' && <Image src={icon} alt={alt} />}
<Text
sx={{ color: 'text.primary' }}
variant="h4"
className="font-semibold"
>
{serviceName}{' '}
<Text
sx={{
color: 'text.secondary',
}}
component="span"
className="font-semibold"
>
({replicas.length} {replicasLabel})
</Text>
</Text>
{serviceState === ServiceState.Running ? (
<Box
sx={{
backgroundColor: serviceStateToThemeColor.get(serviceState),
}}
className="flex h-2 w-2 items-center justify-center rounded-full"
>
<CheckIcon className="h-3/4 w-3/4 stroke-2 text-white" />
</Box>
) : (
<Box
sx={{
backgroundColor: serviceStateToThemeColor.get(serviceState),
}}
className="h-2 w-2 rounded-full"
/>
)}
</div>
</div>
</Accordion.Summary>
<Accordion.Details>
<CodeBlock copyToClipboardToastTitle={`${serviceName} status`}>
{JSON.stringify(serviceInfo, null, 2)}
</CodeBlock>
</Accordion.Details>
</Accordion.Root>
);
}
interface RunServicesAccordionProps {
servicesHealth: Array<ServiceHealthInfo>;
serviceStates: ServiceState[];
/**
* Icon to display on the accordion.
*/
icon?: string | ReactElement;
/**
* Label of the icon.
*/
alt?: string;
iconIsComponent?: boolean;
defaultExpanded?: boolean;
}
function RunServicesAccordion({
serviceStates,
servicesHealth,
icon,
iconIsComponent = true,
defaultExpanded = false,
alt,
}: RunServicesAccordionProps) {
const globalState = findHighestImportanceState(serviceStates);
const serviceInfo = removeTypename(servicesHealth);
return (
<Accordion.Root defaultExpanded={defaultExpanded}>
<Accordion.Summary
expandIcon={
<ChevronDownIcon
sx={{
color: 'text.primary',
}}
/>
}
aria-controls="panel1-content"
id="panel1-header"
className="px-6"
>
<div className="flex flex-row justify-between gap-2 py-2">
<div className="flex items-center gap-3">
{iconIsComponent
? icon
: typeof icon === 'string' && <Image src={icon} alt={alt} />}
<Text
sx={{ color: 'text.primary' }}
variant="h4"
className="font-semibold"
>
Run
</Text>
<Box
sx={{
backgroundColor: serviceStateToThemeColor.get(globalState),
}}
className="h-2 w-2 rounded-full"
/>
</div>
</div>
</Accordion.Summary>
<Accordion.Details>
<CodeBlock copyToClipboardToastTitle="Run services status">
{JSON.stringify(serviceInfo, null, 2)}
</CodeBlock>
</Accordion.Details>
</Accordion.Root>
);
}
export interface OverviewProjectHealthModalProps {
servicesHealth?: GetProjectServicesHealthQuery;
defaultExpanded?: keyof typeof baseServices | 'run';
}
export default function OverviewProjectHealthModal({
servicesHealth,
defaultExpanded,
}: OverviewProjectHealthModalProps) {
const serviceMap: { [key: string]: ServiceHealthInfo | undefined } = {};
servicesHealth.getProjectStatus.services.forEach((service) => {
serviceMap[service.name] = service;
const { auth, storage, postgres, hasura, ai, run } = useServiceStatus({
fetchPolicy: 'cache-only',
shouldPoll: false,
});
const {
'hasura-auth': auth,
'hasura-storage': storage,
postgres,
hasura,
ai,
...otherServices
} = serviceMap;
const runServices = Object.values(otherServices).filter((service) =>
const runServices = Object.values(run).filter((service) =>
service.name.startsWith('run-'),
);
@@ -225,6 +38,24 @@ export default function OverviewProjectHealthModal({
const isAIExpandedByDefault = defaultExpanded === 'ai';
const isRunExpandedByDefault = defaultExpanded === 'run';
const getServiceInfo = (service) => {
const info = removeTypename(service);
return JSON.stringify(info, null, 2);
};
const serviceInfo = {
auth: getServiceInfo(auth),
storage: getServiceInfo(storage),
postgres: getServiceInfo(postgres),
hasura: getServiceInfo(hasura),
ai: getServiceInfo(ai),
run: getServiceInfo(Object.values(runServices)),
};
const runServicesState = findHighestImportanceState(
Object.values(runServices).map((service) => service.state),
);
return (
<Box className={twMerge('w-full rounded-lg pt-2 text-left')}>
<Box
@@ -237,8 +68,8 @@ export default function OverviewProjectHealthModal({
<ServiceAccordion
icon={<UserIcon className="h-4 w-4" />}
serviceName="Auth"
serviceHealth={auth}
replicas={auth?.replicas}
serviceInfo={serviceInfo.auth}
replicaCount={auth?.replicas?.length}
serviceState={auth?.state}
defaultExpanded={isAuthExpandedByDefault}
/>
@@ -246,8 +77,8 @@ export default function OverviewProjectHealthModal({
<ServiceAccordion
icon={<DatabaseIcon className="h-4 w-4" />}
serviceName="Postgres"
serviceHealth={postgres}
replicas={postgres?.replicas}
serviceInfo={serviceInfo.postgres}
replicaCount={postgres?.replicas?.length}
serviceState={postgres?.state}
defaultExpanded={isPostgresExpandedByDefault}
/>
@@ -255,8 +86,8 @@ export default function OverviewProjectHealthModal({
<ServiceAccordion
icon={<StorageIcon className="h-4 w-4" />}
serviceName="Storage"
serviceHealth={storage}
replicas={storage?.replicas}
serviceInfo={serviceInfo.storage}
replicaCount={storage?.replicas?.length}
serviceState={storage?.state}
defaultExpanded={isStorageExpandedByDefault}
/>
@@ -264,8 +95,8 @@ export default function OverviewProjectHealthModal({
<ServiceAccordion
icon={<HasuraIcon className="h-4 w-4" />}
serviceName="Hasura"
serviceHealth={hasura}
replicas={hasura?.replicas}
serviceInfo={serviceInfo.hasura}
replicaCount={hasura?.replicas?.length}
serviceState={hasura?.state}
defaultExpanded={isHasuraExpandedByDefault}
/>
@@ -275,22 +106,22 @@ export default function OverviewProjectHealthModal({
<ServiceAccordion
icon={<AIIcon className="h-4 w-4" />}
serviceName="AI"
serviceHealth={ai}
replicas={ai.replicas}
serviceState={ai.state}
serviceInfo={serviceInfo.ai}
replicaCount={ai?.replicas?.length}
serviceState={ai?.state}
defaultExpanded={isAIExpandedByDefault}
/>
</>
) : null}
{Object.values(runServices).length > 0 ? (
{runServices && Object.values(runServices).length > 0 ? (
<>
<Divider />
<RunServicesAccordion
servicesHealth={Object.values(runServices)}
<ServiceAccordion
icon={<ServicesOutlinedIcon className="h-4 w-4" />}
serviceStates={Object.values(runServices).map(
(service) => service.state,
)}
serviceName="Run"
serviceInfo={serviceInfo.run}
replicaCount={0}
serviceState={runServicesState}
defaultExpanded={isRunExpandedByDefault}
/>
</>

View File

@@ -0,0 +1,107 @@
import { Badge, type BadgeProps } from '@/components/ui/v2/Badge';
import { CheckIcon } from '@/components/ui/v2/icons/CheckIcon';
import { ExclamationFilledIcon } from '@/components/ui/v2/icons/ExclamationFilledIcon';
import { QuestionMarkIcon } from '@/components/ui/v2/icons/QuestionMarkIcon';
export interface ProjectHealthBadgeProps extends BadgeProps {
badgeVariant?: 'standard' | 'dot';
badgeColor?: 'success' | 'error' | 'warning' | 'secondary';
unknownState?: boolean;
showExclamation?: boolean;
showCheckIcon?: boolean;
isLoading?: boolean;
blink?: boolean;
}
export default function ProjectHealthBadge({
badgeColor,
badgeVariant,
showExclamation,
showCheckIcon,
unknownState,
blink,
children,
...props
}: ProjectHealthBadgeProps) {
let innerBadgeContent = null;
if (unknownState) {
innerBadgeContent = (
<QuestionMarkIcon
sx={{
color: (theme) =>
theme.palette.mode === 'dark' ? 'grey.200' : 'grey.100',
}}
className="h-2 w-2 stroke-2"
/>
);
} else if (showCheckIcon) {
innerBadgeContent = (
<CheckIcon
sx={{
color: (theme) =>
theme.palette.mode === 'dark' ? 'grey.200' : 'grey.100',
}}
className="h-2 w-2 stroke-2"
/>
);
}
if (!badgeColor) {
return <div>{children}</div>;
}
if (showExclamation) {
return (
<Badge
variant="standard"
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
badgeContent={
<ExclamationFilledIcon
sx={{
color: (theme) =>
theme.palette.mode === 'dark' ? 'grey.900' : 'grey.600',
}}
className="h-2.5 w-2.5"
/>
}
>
<Badge
color={badgeColor}
variant={badgeVariant}
badgeContent={innerBadgeContent}
sx={{
color: (theme) =>
theme.palette.mode === 'dark' ? 'grey.900' : 'text.primary',
}}
componentsProps={{
badge: {
className: blink ? 'animate-pulse' : '',
},
}}
{...props}
>
{children}
</Badge>
</Badge>
);
}
return (
<Badge
color={badgeColor}
variant={badgeVariant}
badgeContent={innerBadgeContent}
componentsProps={{
badge: {
className: blink ? 'animate-pulse' : '',
},
}}
{...props}
>
{children}
</Badge>
);
}

View File

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

View File

@@ -1,9 +1,7 @@
import { Badge, type BadgeProps } from '@/components/ui/v2/Badge';
import type { BoxProps } from '@/components/ui/v2/Box';
import { Box } from '@/components/ui/v2/Box';
import { CheckIcon } from '@/components/ui/v2/icons/CheckIcon';
import { ExclamationFilledIcon } from '@/components/ui/v2/icons/ExclamationFilledIcon';
import { Tooltip, tooltipClasses } from '@/components/ui/v2/Tooltip';
import { ProjectHealthBadge } from '@/features/projects/overview/components/ProjectHealthBadge';
import { serviceStateToBadgeColor } from '@/features/projects/overview/health';
import { ServiceState } from '@/utils/__generated__/graphql';
import type { ImageProps } from 'next/image';
@@ -11,92 +9,6 @@ import Image from 'next/image';
import type { ReactElement } from 'react';
import { twMerge } from 'tailwind-merge';
interface HealthBadgeProps extends BadgeProps {
badgeVariant?: 'standard' | 'dot';
badgeColor?: 'success' | 'error' | 'warning';
showExclamation?: boolean;
showCheckIcon?: boolean;
isLoading?: boolean;
}
function HealthBadge({
badgeColor,
badgeVariant,
showExclamation,
showCheckIcon,
children,
...props
}: HealthBadgeProps) {
if (!badgeColor) {
return <div>{children}</div>;
}
if (showExclamation) {
return (
<Badge
variant="standard"
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
badgeContent={
<ExclamationFilledIcon
sx={{
color: (theme) =>
theme.palette.mode === 'dark' ? 'grey.900' : 'grey.600',
}}
className="h-2.5 w-2.5"
/>
}
>
<Badge
color={badgeColor}
variant={badgeVariant}
badgeContent={
showCheckIcon ? (
<CheckIcon
sx={{
color: (theme) =>
theme.palette.mode === 'dark' ? 'grey.200' : 'grey.100',
}}
className="h-2 w-2 stroke-2"
/>
) : null
}
sx={{
color: (theme) =>
theme.palette.mode === 'dark' ? 'grey.900' : 'text.primary',
}}
{...props}
>
{children}
</Badge>
</Badge>
);
}
return (
<Badge
color={badgeColor}
variant={badgeVariant}
badgeContent={
showCheckIcon ? (
<CheckIcon
sx={{
color: (theme) =>
theme.palette.mode === 'dark' ? 'grey.200' : 'grey.100',
}}
className="h-2 w-2 stroke-2"
/>
) : null
}
{...props}
>
{children}
</Badge>
);
}
export interface ProjectHealthCardProps extends BoxProps {
/**
* Label of the card icon.
@@ -159,8 +71,13 @@ export default function ProjectHealthCard({
...props
}: ProjectHealthCardProps) {
const badgeColor = serviceStateToBadgeColor.get(state);
const badgeVariant = state === ServiceState.Running ? 'standard' : 'dot';
const unknownState = state === undefined;
let badgeVariant: 'dot' | 'standard' = 'dot';
if (state === ServiceState.Running || unknownState) {
badgeVariant = 'standard';
}
const showCheckIcon = state === ServiceState.Running;
const shouldBlink = state === ServiceState.Updating;
return (
<Tooltip
@@ -186,11 +103,13 @@ export default function ProjectHealthCard({
{...props}
>
<div className="grid grid-flow-col items-center justify-center">
<HealthBadge
<ProjectHealthBadge
badgeColor={!isLoading ? badgeColor : undefined}
badgeVariant={badgeVariant}
showCheckIcon={showCheckIcon}
showExclamation={isVersionMismatch}
unknownState={unknownState}
blink={shouldBlink}
>
{iconIsComponent
? icon
@@ -203,7 +122,7 @@ export default function ProjectHealthCard({
{...slotProps.imgIcon}
/>
)}
</HealthBadge>
</ProjectHealthBadge>
</div>
</Box>
</Tooltip>

View File

@@ -0,0 +1,57 @@
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Text } from '@/components/ui/v2/Text';
import {
serviceStateToThemeColor,
type baseServices,
type ServiceHealthInfo,
} from '@/features/projects/overview/health';
import { ServiceState } from '@/generated/graphql';
export interface RunStatusTooltipProps {
servicesStatusInfo?: Array<ServiceHealthInfo>;
openHealthModal?: (
defaultExpanded?: keyof typeof baseServices | 'run',
) => void;
}
export default function RunStatusTooltip({
servicesStatusInfo,
openHealthModal,
}: RunStatusTooltipProps) {
return (
<div className="flex w-full flex-col gap-3 px-2 py-3">
<ol className="m-0 flex flex-col gap-3">
{servicesStatusInfo.map((service) => (
<li
key={service.name}
className="flex flex-row items-center gap-4 text-ellipsis text-nowrap leading-5"
>
<Box
sx={{
backgroundColor: serviceStateToThemeColor.get(service.state),
}}
className={`h-3 w-3 flex-shrink-0 rounded-full ${
service.state === ServiceState.Updating ? 'animate-pulse' : ''
}`}
/>
<Text
sx={{
color: (theme) =>
theme.palette.mode === 'dark'
? 'text.primary'
: 'text.primary',
}}
className="font-semibold"
>
{service.name}
</Text>
</li>
))}
</ol>
<Button variant="outlined" onClick={() => openHealthModal('run')}>
View state
</Button>
</div>
);
}

View File

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

View File

@@ -0,0 +1,95 @@
import { CodeBlock } from '@/components/presentational/CodeBlock';
import { Accordion } from '@/components/ui/v2/Accordion';
import { ChevronDownIcon } from '@/components/ui/v2/icons/ChevronDownIcon';
import { Text } from '@/components/ui/v2/Text';
import { AccordionHealthBadge } from '@/features/projects/overview/components/AccordionHealthBadge';
import { ServiceState } from '@/utils/__generated__/graphql';
import Image from 'next/image';
import { type ReactElement } from 'react';
export interface ServiceAccordionProps {
serviceName: string;
serviceInfo: string;
replicaCount: number;
serviceState: ServiceState;
/**
* Icon to display on the accordion.
*/
icon?: string | ReactElement;
/**
* Label of the icon.
*/
alt?: string;
iconIsComponent?: boolean;
defaultExpanded?: boolean;
}
export default function ServiceAccordion({
serviceName,
serviceInfo,
replicaCount,
serviceState,
icon,
iconIsComponent = true,
alt,
defaultExpanded = false,
}: ServiceAccordionProps) {
const unknownState = serviceState === undefined;
const replicasLabel = replicaCount === 1 ? 'replica' : 'replicas';
const blink = serviceState === ServiceState.Updating;
return (
<Accordion.Root defaultExpanded={defaultExpanded}>
<Accordion.Summary
expandIcon={
<ChevronDownIcon
sx={{
color: 'text.primary',
}}
/>
}
aria-controls="panel1-content"
id="panel1-header"
className="px-6"
>
<div className="flex flex-row justify-between gap-2 py-2">
<div className="flex items-center gap-3">
{iconIsComponent
? icon
: typeof icon === 'string' && <Image src={icon} alt={alt} />}
<Text
sx={{ color: 'text.primary' }}
variant="h4"
className="font-semibold"
>
{serviceName}{' '}
{!unknownState && replicaCount && replicasLabel ? (
<Text
sx={{
color: 'text.secondary',
}}
component="span"
className="font-semibold"
>
({replicaCount} {replicasLabel})
</Text>
) : null}
</Text>
<AccordionHealthBadge
serviceState={serviceState}
unknownState={unknownState}
blink={blink}
/>
</div>
</div>
</Accordion.Summary>
<Accordion.Details>
<CodeBlock copyToClipboardToastTitle={`${serviceName} status`}>
{serviceInfo}
</CodeBlock>
</Accordion.Details>
</Accordion.Root>
);
}

View File

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

View File

@@ -35,19 +35,19 @@ export const serviceStateToThemeColor = new Map<ServiceState, string>([
[ServiceState.UpdateError, 'error.main'],
[ServiceState.Updating, 'warning.dark'],
[ServiceState.None, 'error.main'],
[undefined, 'error.main'],
[undefined, 'grey.500'],
]);
export const serviceStateToBadgeColor = new Map<
ServiceState,
'success' | 'error' | 'warning'
'success' | 'error' | 'warning' | 'secondary'
>([
[ServiceState.Running, 'success'],
[ServiceState.Error, 'error'],
[ServiceState.UpdateError, 'error'],
[ServiceState.Updating, 'warning'],
[ServiceState.None, 'error'],
[undefined, 'error'],
[undefined, 'secondary'], // secondary is used for unknown states
]);
/**

View File

@@ -0,0 +1,303 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Divider } from '@/components/ui/v2/Divider';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { rateLimitingItemValidationSchema } from '@/features/projects/rate-limiting/settings/components/validationSchemas';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { useUpdateRateLimitConfigMutation } from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import { RateLimitField } from 'features/projects/rate-limiting/settings/components/RateLimitField';
import { useGetRateLimits } from 'features/projects/rate-limiting/settings/hooks/useGetRateLimits';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
export const validationSchema = Yup.object({
enabled: Yup.boolean().label('Enabled'),
bruteForce: rateLimitingItemValidationSchema,
emails: rateLimitingItemValidationSchema,
global: rateLimitingItemValidationSchema,
signups: rateLimitingItemValidationSchema,
sms: rateLimitingItemValidationSchema,
});
export type AuthLimitingFormValues = Yup.InferType<typeof validationSchema>;
export default function AuthLimitingForm() {
const { openDialog } = useDialog();
const { maintenanceActive } = useUI();
const isPlatform = useIsPlatform();
const { currentProject } = useCurrentWorkspaceAndProject();
const localMimirClient = useLocalMimirClient();
const [updateRateLimitConfig] = useUpdateRateLimitConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { authRateLimit, loading } = useGetRateLimits();
const {
bruteForce,
emails,
global,
signups,
sms,
enabled: authRateEnabled,
} = authRateLimit;
const {
limit: bruteForceLimit,
interval: bruteForceInterval,
intervalUnit: bruteForceIntervalUnit,
} = bruteForce;
const {
limit: emailsLimit,
interval: emailsInterval,
intervalUnit: emailsIntervalUnit,
} = emails;
const {
limit: globalLimit,
interval: globalInterval,
intervalUnit: globalIntervalUnit,
} = global;
const {
limit: signupsLimit,
interval: signupsInterval,
intervalUnit: signupsIntervalUnit,
} = signups;
const {
limit: smsLimit,
interval: smsInterval,
intervalUnit: smsIntervalUnit,
} = sms;
const form = useForm<AuthLimitingFormValues>({
defaultValues: {
enabled: authRateEnabled,
bruteForce: {
limit: bruteForceLimit,
interval: bruteForceInterval,
intervalUnit: bruteForceIntervalUnit,
},
emails: {
limit: emailsLimit,
interval: emailsInterval,
intervalUnit: emailsIntervalUnit,
},
global: {
limit: globalLimit,
interval: globalInterval,
intervalUnit: globalIntervalUnit,
},
signups: {
limit: signupsLimit,
interval: signupsInterval,
intervalUnit: signupsIntervalUnit,
},
sms: {
limit: smsLimit,
interval: smsInterval,
intervalUnit: smsIntervalUnit,
},
},
reValidateMode: 'onSubmit',
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading && authRateEnabled) {
form.reset({
enabled: authRateEnabled,
bruteForce: {
limit: bruteForceLimit,
interval: bruteForceInterval,
intervalUnit: bruteForceIntervalUnit,
},
emails: {
limit: emailsLimit,
interval: emailsInterval,
intervalUnit: emailsIntervalUnit,
},
global: {
limit: globalLimit,
interval: globalInterval,
intervalUnit: globalIntervalUnit,
},
signups: {
limit: signupsLimit,
interval: signupsInterval,
intervalUnit: signupsIntervalUnit,
},
sms: {
limit: smsLimit,
interval: smsInterval,
intervalUnit: smsIntervalUnit,
},
});
}
}, [
loading,
form,
authRateEnabled,
bruteForceLimit,
bruteForceInterval,
bruteForceIntervalUnit,
emailsLimit,
emailsInterval,
emailsIntervalUnit,
globalLimit,
globalInterval,
globalIntervalUnit,
signupsLimit,
signupsInterval,
signupsIntervalUnit,
smsLimit,
smsInterval,
smsIntervalUnit,
]);
if (loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading rate limits..."
className="justify-center"
/>
);
}
const {
register,
formState: { errors },
formState,
watch,
} = form;
const enabled = watch('enabled');
const handleSubmit = async (formValues: AuthLimitingFormValues) => {
const updateConfigPromise = updateRateLimitConfig({
variables: {
appId: currentProject.id,
config: {
auth: {
rateLimit: formValues.enabled
? {
bruteForce: {
limit: formValues.bruteForce.limit,
interval: `${formValues.bruteForce.interval}${formValues.bruteForce.intervalUnit}`,
},
emails: {
limit: formValues.emails.limit,
interval: `${formValues.emails.interval}${formValues.emails.intervalUnit}`,
},
global: {
limit: formValues.global.limit,
interval: `${formValues.global.interval}${formValues.global.intervalUnit}`,
},
signups: {
limit: formValues.signups.limit,
interval: `${formValues.signups.interval}${formValues.signups.intervalUnit}`,
},
sms: {
limit: formValues.sms.limit,
interval: `${formValues.sms.interval}${formValues.sms.intervalUnit}`,
},
}
: null,
},
},
},
});
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Updating Auth rate limit settings...',
successMessage: 'Auth rate limit settings updated successfully',
errorMessage: 'Failed to update Auth rate limit settings',
},
);
};
return (
<FormProvider {...form}>
<Form
onSubmit={handleSubmit}
className="flex h-full flex-col overflow-hidden"
>
<SettingsContainer
title="Auth"
slotProps={{
submitButton: {
disabled: !formState.isDirty || maintenanceActive,
loading: formState.isSubmitting,
},
}}
className="flex flex-col px-0"
>
<Divider />
<RateLimitField
disabled={!enabled}
register={register}
errors={errors.bruteForce}
id="bruteForce"
title="Brute Force"
/>
<Divider />
<RateLimitField
disabled={!enabled}
register={register}
errors={errors.emails}
id="emails"
title="Emails"
/>
<Divider />
<RateLimitField
disabled={!enabled}
register={register}
errors={errors.global}
id="global"
title="Global"
/>
<Divider />
<RateLimitField
disabled={!enabled}
register={register}
errors={errors.signups}
id="signups"
title="Signups"
/>
<Divider />
<RateLimitField
disabled={!enabled}
register={register}
errors={errors.sms}
id="sms"
title="SMS"
/>
</SettingsContainer>
</Form>
</FormProvider>
);
}

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