Compare commits

..

22 Commits

Author SHA1 Message Date
github-actions[bot]
6921526cf5 chore: update versions (#3221)
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@8.0.6

### Patch Changes

-   @nhost/nhost-js@3.2.6

## @nhost/react-apollo@17.0.2

### Patch Changes

-   Updated dependencies [ad57a9e]
    -   @nhost/react@3.10.2
    -   @nhost/apollo@8.0.6

## @nhost/react-urql@14.0.2

### Patch Changes

-   Updated dependencies [ad57a9e]
    -   @nhost/react@3.10.2

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

### Patch Changes

-   ad57a9e: fix(auth): allow to pass custom headers to signup

## @nhost/nextjs@2.2.5

### Patch Changes

-   Updated dependencies [ad57a9e]
    -   @nhost/react@3.10.2

## @nhost/nhost-js@3.2.6

### Patch Changes

-   Updated dependencies [ad57a9e]
    -   @nhost/hasura-auth-js@2.10.2

## @nhost/react@3.10.2

### Patch Changes

-   ad57a9e: fix(auth): allow to pass custom headers to signup
    -   @nhost/nhost-js@3.2.6

## @nhost/vue@2.9.3

### Patch Changes

-   @nhost/nhost-js@3.2.6

## @nhost/dashboard@2.22.0

# @nhost/dashboard

## 2.17.0

### Minor Changes

-   fd59918: fix: redirect to 404 with nhost cli dashboard

## 2.16.0

### Minor Changes

-   f8e6b61: fix: can add rule groups in table permissions
-   9e404c8: fix: not redirect to 404 page if using local Nhost backend
-   ac4aa01: fix: can delete column in database page
-   4385524: fix: update url to check service health in local dashboard

### Patch Changes

-   @nhost/react-apollo@16.0.1
-   @nhost/nextjs@2.2.2

## 2.15.0

### Minor Changes

- f1052a8: fix: improve stability of the dashboard when pausing projects
-   30daa41: fix: update links to docs in overview page
-   7537237: feat: add image preview toggle in storage

## 2.14.0

### Minor Changes

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

### Patch Changes

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

## 2.13.0

### Minor Changes

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

### Patch Changes

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

## 2.12.0

### Minor Changes

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

### Patch Changes

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

## 2.11.3

### Patch Changes

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

## 2.11.2

### Patch Changes

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

## 2.11.1

### Patch Changes

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

## 2.11.0

### Minor Changes

-   cea3ef5: Feat: add org and project placeholders

## 2.10.0

### Minor Changes

-   86ecf27: feat: add support for additional metrics in overview
- 21708be: feat: dashboard: add support for storage buckets to AI
assistants

## 1.30.0

### Minor Changes

- 50441a8: feat: add ui for project autoscaler settings and run services
autoscaler settings

## 1.29.0

### Minor Changes

-   55d8bb5: feat: integrate turnstile for signup verification
-   2a2e54c: fix: update docs url in run services form tooltip
- 18f942f: fix: display long error messages in error toast without
overflow

### Patch Changes

-   @nhost/react-apollo@13.0.0
-   @nhost/nextjs@2.1.22

## 1.28.2

### Patch Changes

- 52a38fe: chore: update dependencies to address security
vulnerabilities
-   Updated dependencies [52a38fe]
    -   @nhost/nextjs@2.1.21

## 1.28.1

### Patch Changes

-   9735fa2: chore: remove broken link

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

- b519862: fix: get configuration in configuration editor using local
development environment

## 1.18.0

### Minor Changes

- 502abad: feat: add services health checks indicators to the overview
page
-   b3ff6ad: chore: update title text on service status modal
- dbadf59: feat: add project configuration TOML editor to the settings
page

## 1.17.0

### Minor Changes

- 77fba27: fix: postgres version validation when activating ai in ai
settings page
-   ac6d1b6: feat: use name instead of awsName

## 1.16.3

### Patch Changes

- 87a37cf: fix: remove unnecessary isPlatform check from verify button
disable logic on custom domains
    -   @nhost/react-apollo@12.0.2
    -   @nhost/nextjs@2.1.16

## 1.16.2

### Patch Changes

- a9413af: fix: update `GetAllWorkspacesAndProjects` query polling to
use exponential backoff
    -   @nhost/react-apollo@12.0.1
    -   @nhost/nextjs@2.1.15

## 1.16.1

### Patch Changes

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

## 1.16.0

### Minor Changes

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

## 1.15.2

### Patch Changes

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

## 1.15.1

### Patch Changes

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

## 1.15.0

### Minor Changes

-   a7bde37: feat: send metadata in the edit form

### Patch Changes

- 1bc615b: feat: improve error message handling in `ErrorToast`
component
    -   @nhost/react-apollo@11.0.2
    -   @nhost/nextjs@2.1.11

## 1.14.0

### Minor Changes

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

## 1.13.3

### Patch Changes

-   5924bc3: fix: include password in `GetSmtpSettings` query
- c5ad634: fix: resolved an issue where one-click install links were
broken on Safari
- 7278991: fix: update graphql auto-embeddings configuration to use
String type for model field

## 1.13.2

### Patch Changes

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

## 1.13.1

### Patch Changes

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

## 1.13.0

### Minor Changes

-   dd5d262: feat: add model field to the auto-embeddings form
- 09962be: feat: enable settings and run services when running the
dashboard locally
- 9cdecb6: feat: enable users to update their email address from the
account settings page

## 1.12.2

### Patch Changes

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

## 1.12.1

### Patch Changes

- 93ebdf8: fix: use service urls when initilizaing NhostClient running
local dashboard
    -   @nhost/react-apollo@11.0.1
    -   @nhost/nextjs@2.1.10

## 1.12.0

### Minor Changes

- f242e4b: feat: add connect with github to the user's account settings
-   768ca17: chore: update dependencies
- d62bd0f: fix: "Track this" option within the SQL editor now correctly
updates the metadata
- 91c2bb6: feat: refactor sign-in and sign-up pages to enforce email
verification

### Patch Changes

-   943831f: fix: resolve an error toast issue when unpausing a project
-   Updated dependencies [768ca17]
    -   @nhost/react-apollo@11.0.0
    -   @nhost/nextjs@2.1.9

## 1.11.2

### Patch Changes

-   @nhost/react-apollo@10.0.2
-   @nhost/nextjs@2.1.8

## 1.11.1

### Patch Changes

-   981404f: fix: set default value for healthCheck field validation

## 1.11.0

### Minor Changes

- 7789469: chore: upgrade dependency `@graphql-codegen/cli` to `5.0.2`
to address vulnerability
- 6c11b75: feat: add update user displayName section in account settings

### Patch Changes

-   @nhost/react-apollo@10.0.1
-   @nhost/nextjs@2.1.7

## 1.10.0

### Minor Changes

-   49a80c2: chore: update dependencies
-   150c04a: feat: add healthcheck config to run services

### Patch Changes

- e03f141: fix: allow insert, update and delete on tables in `auth` and
`storage` schemas
- 28676f4: feat: add min postgres version check to enable the ai service
-   Updated dependencies [49a80c2]
    -   @nhost/react-apollo@10.0.0
    -   @nhost/nextjs@2.1.6

## 1.9.0

### Minor Changes

-   d86e5c9: feat: add support for filtering the logs using a RegExp

## 1.8.3

### Patch Changes

-   @nhost/react-apollo@9.0.3
-   @nhost/nextjs@2.1.5

## 1.8.2

### Patch Changes

- 6df4f02: fix: use custom error toast and show correct message when
sending an invite

## 1.8.1

### Patch Changes

-   @nhost/react-apollo@9.0.2
-   @nhost/nextjs@2.1.4

## 1.8.0

### Minor Changes

- 713d53c: feat: add catch-all route for workspace/project - useful for
documentation

### Patch Changes

-   3db2999: fix: refresh table list after running SQL using the editor
- 3c4dd55: fix: handle `Error` objects properly in the `ErrorToast`
component
- 92b434e: fix: resolve an issue where the checkbox in the data-grid
header did not select all rows
    -   @nhost/react-apollo@9.0.1
    -   @nhost/nextjs@2.1.3

## 1.7.0

### Minor Changes

-   0d8d0eb: Update docs and dashboard references

## 1.6.9

### Patch Changes

-   @nhost/react-apollo@9.0.0
-   @nhost/nextjs@2.1.2

## 1.6.8

### Patch Changes

-   @nhost/react-apollo@8.0.1
-   @nhost/nextjs@2.1.1

## 1.6.7

### Patch Changes

-   5ef5189: fix: update `@apollo/client` to `3.9.4` to fix a cache bug

## 1.6.6

### Patch Changes

-   3ba485e: fix: added discord.com to connect-src
-   e5bab6a: chore: update dependencies
-   Updated dependencies [b19ffed]
-   Updated dependencies [e5bab6a]
    -   @nhost/nextjs@2.1.0
    -   @nhost/react-apollo@8.0.0

## 1.6.5

### Patch Changes

- ba73bb4: fix: update ErrorToast component to show the internal graphql
error
- d5337ff: fix: utilize accumulator in the creation of validation schema
within data grid utils

## 1.6.4

### Patch Changes

-   7c2a1c2: feat: show error and debug info in the error toast

## 1.6.3

### Patch Changes

-   6b8aad5: fix: add bare nhost.run to CSP

## 1.6.2

### Patch Changes

-   b18edc0: feat: added CSP and X-Frame-Options

## 1.6.1

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
- 3b8473b: chore: update turbo to `1.11.3` and pnpm to `8.10.5` in
Dockerfile
-   Updated dependencies [8d91f71]
    -   @nhost/react-apollo@7.0.2
    -   @nhost/nextjs@2.0.2

## 1.6.0

### Minor Changes

-   3ff1c2b53: fix: show upgrade option for pro projects

## 1.5.0

### Minor Changes

-   c2ef17c0a: feat: add support for new Team plan

## 1.4.0

### Minor Changes

-   7883bbcbd: feat: don't show deprecated plans
- 44be6dc0a: feat: set redirectTo during sign-in to support preview
environments

### Patch Changes

- 3c3594898: fix: allow access to graphite when configured running in
local dashboard
-   32c246b7a: chore: update docs icon

## 1.3.2

### Patch Changes

-   174b4165b: chore: use env variables when running graphql codegen
-   7c977e714: chore: change `Allowed Roles` to `Default Allowed Roles`
-   46f028b9f: fix: remove hardcoded ai version setting

## 1.3.1

### Patch Changes

- af33c21d1: chore: remove backendUrl deprecation notice and remove all
references to `providersUpdated`

## 1.3.0

### Minor Changes

-   04784d880: Fix graphite's default version

## 1.2.0

### Minor Changes

-   5733162ed: feat: add settings and ui for graphite

## 1.1.0

### Minor Changes

-   e2b79b5ec: chore: remove sharp from deps

## 1.0.1

### Patch Changes

-   @nhost/react-apollo@7.0.1
-   @nhost/nextjs@2.0.1

## 1.0.0

### Major Changes

- bc9eff6e4: chore: remove support for using backendUrl when
instantiating the Nhost client

### Patch Changes

-   Updated dependencies [bc9eff6e4]
    -   @nhost/nextjs@2.0.0
    -   @nhost/react-apollo@7.0.0

## 0.21.1

### Patch Changes

-   97ced73a3: fix(dashboard): prevent dashboard from resolving secrets

## 0.21.0

### Minor Changes

- ed1a8d458: Update alert message on increasing PostgreSQL's volume
capacity
-   2e2248fd4: feat(dashboard): add SQL editor

## 0.20.28

### Patch Changes

-   7c2c31082: feat: add support for users to delete their account
    -   @nhost/react-apollo@6.0.1
    -   @nhost/nextjs@1.13.40

## 0.20.27

### Patch Changes

- fa79b7709: chore(dashboard): tweaks and fixes to the service form and
dialog
-   8df84d782: fix(dashboard): allow resetting custom domains
    -   @nhost/react-apollo@6.0.0
    -   @nhost/nextjs@1.13.39

## 0.20.26

### Patch Changes

- 331ba0376: feat(dashboard): add postgres storage capacity modifier in
the settings
-   b7f801874: feat(dashboard): add new settings page for custom domains

## 0.20.25

### Patch Changes

-   @nhost/react-apollo@5.0.38

## 0.20.24

### Patch Changes

-   e10389ecf: fix(dashboard): disable run tab when developing locally
    -   @nhost/react-apollo@5.0.37

## 0.20.23

### Patch Changes

-   c01568a7d: chore(dashboard): show alert to update oauth providers

## 0.20.22

### Patch Changes

-   c3efb7ec8: feat(dashboard): query latest announcement from platform

## 0.20.21

### Patch Changes

-   3e46d3873: chore: update link to node18 announcement

## 0.20.20

### Patch Changes

-   @nhost/react-apollo@5.0.36
-   @nhost/nextjs@1.13.38

## 0.20.19

### Patch Changes

-   75c4c8ae3: feat(dashboard): make env value input multiline

## 0.20.18

### Patch Changes

- 425d485f8: fix(dashboard): make sure dedicated resources pricing
follows total resources

## 0.20.17

### Patch Changes

-   ae324f67f: fix(dashboard): remove unused graphql fields

## 0.20.16

### Patch Changes

-   df5b4302c: chore(dashboard): remove run feature flag
- bf4a1f6c2: feat(dashboard): fetch auth, postgres, hasura and storage
versions from dashboard
- 34fc08ca7: fix(dashboard/run): show correct private registry in
service details
-   885d10620: chore(dashboard): change feedback to contact us

## 0.20.15

### Patch Changes

- ed16c8b5d: feat(run): add a confirmation dialog when deleting a run
service
- 216990888: fix(run): center loading indicator when selecting a project

## 0.20.14

### Patch Changes

-   9fbea9787: feat: add node18 announcement

## 0.20.13

### Patch Changes

- e84acf469: fix(run): handle subdomain undefined error when creating a
new service

## 0.20.12

### Patch Changes

-   b7c799d62: feat(run): add dialog to copy registry and URLs

## 0.20.11

### Patch Changes

-   8903e6abd: fix(dashboard): show correct egress limit in usage stats

## 0.20.10

### Patch Changes

- 666a75a23: feat(dashboard): add functions execution time and egress
volume to usage stats

## 0.20.9

### Patch Changes

-   5e1e80aa8: fix(dashboard): show correct locales in user details
    -   @nhost/react-apollo@5.0.35
    -   @nhost/nextjs@1.13.37

## 0.20.8

### Patch Changes

-   @nhost/react-apollo@5.0.34
-   @nhost/nextjs@1.13.36

## 0.20.7

### Patch Changes

-   4a7ede11e: fix: distinguish files that were not uploaded
- 202b64723: feat(nhost-run): add support for one-click-install run
services
- 074a0fa11: feat(dashboard): add settings toggle to enable/disable
antivirus
    -   @nhost/react-apollo@5.0.33
    -   @nhost/nextjs@1.13.35

## 0.20.6

### Patch Changes

-   b20761e97: feat(services): add pricing info and confirmation dialog
-   90df6d81d: fix(services): handle null values when editing a service
-   aa8508467: fix: query service logs correctly
    feat: enable multiline support for environment value input

## 0.20.5

### Patch Changes

-   8d7f84b8d: fix: make announcement adapt to theme

## 0.20.4

### Patch Changes

-   3b75bfce2: fix: make announcement close properly
- f49819075: fix: show correct values when dedicated resources are
disabled

## 0.20.3

### Patch Changes

-   e643bd362: fix(services): fix errors when config is null
-   bcdab66bf: feat: add annoucement for nhost run
-   f967a2e59: added note about storage not being able to be downsized
-   311c7756d: chore(services): consistent naming for compute

## 0.20.2

### Patch Changes

-   9073182d5: chore(dashboard): bump `turbo` to 1.10.11
-   ece717d6e: feat(logs): show services in the logs page
- 82b335311: feat(metrics): change grafana link to point to the
dashboards
- b135ef695: fix(services): set command as optional and set min replicas
to 0

## 0.20.1

### Patch Changes

-   3d5c34f4c: fix(auth): fix users pagination limit

## 0.20.0

### Minor Changes

-   c99d117d1: feat(services): add support for custom services

## 0.19.2

### Patch Changes

-   face99ccd: chore(deps): bump turbo version
-   cfe527307: style: tweak pull config warning in dark mode
- a9d7da8af: chore(deps): update dependency @types/pluralize to ^0.0.30
-   9aa4371ef: chore: add hasura-auth version 0.21.2
- d14e112bf: chore(deps): update dependency prettier-plugin-tailwindcss
to ^0.4.0
-   d3e8bb94a: chore(deps): update dependency vite-plugin-dts to v3

## 0.19.1

### Patch Changes

-   @nhost/react-apollo@5.0.32
-   @nhost/nextjs@1.13.34

## 0.19.0

### Minor Changes

- 9c61c69a7: chore(dashboard):add postgres 14.6-20230705-1 to the
version selector

### Patch Changes

-   47bda15ff: feat(settings): add warning to pull config

## 0.18.0

### Minor Changes

- ee0b9b8ed: chore(dashboard):add hasura v2.28.2 and v2.29.0 to the
version selector

## 0.17.20

### Patch Changes

-   @nhost/react-apollo@5.0.31
-   @nhost/nextjs@1.13.33

## 0.17.19

### Patch Changes

-   f866120a6: fix(users): use the password length from the config

## 0.17.18

### Patch Changes

-   @nhost/react-apollo@5.0.30
-   @nhost/nextjs@1.13.32

## 0.17.17

### Patch Changes

-   ea7b102c0: fix(pat): highlight expired tokens

## 0.17.16

### Patch Changes

- b3b64a3b7: chore(deps): bump `@types/react` to `v18.2.14` and
`@types/react-dom` to `v18.2.6`
-   32b221f94: chore(deps): bump `graphiql` to `v3`
-   3a56c12df: chore(deps): bump `turbo` to `v1.10.6`
-   Updated dependencies [b3b64a3b7]
    -   @nhost/react-apollo@5.0.29
    -   @nhost/nextjs@1.13.31

## 0.17.15

### Patch Changes

-   f41fdc12a: chore(deps): bump `turbo` to `1.10.5`
-   6199c1c55: fix(projects): don't redirect to 404 page
-   Updated dependencies [07a45fde0]
    -   @nhost/react-apollo@5.0.28
    -   @nhost/nextjs@1.13.30

## 0.17.14

### Patch Changes

- 80b22724d: chore(deps): bump `@types/react` to `v18.2.13`,
`@types/react-dom` to `v18.2.6` and `@storybook/testing-library` to
`v0.2.0`

## 0.17.13

### Patch Changes

-   cc02902cb: chore(docs): update environment variable documentation

## 0.17.12

### Patch Changes

-   660d339e1: fix(storybook): don't break storybook
-   660d339e1: fix(tests): prevent warnings during tests
    -   @nhost/react-apollo@5.0.27
    -   @nhost/nextjs@1.13.29

## 0.17.11

### Patch Changes

- bd4d0c270: chore(dashboard):add postgres 14.6-20230613-1 to the
version selector

## 0.17.10

### Patch Changes

-   c8c2a10b2: fix(database): don't break the password reset flow
- e70b45498: chore(deps): bump `@types/react` to `v18.2.12` and
`@types/react-dom` to `v18.2.5`

## 0.17.9

### Patch Changes

- 842055099: chore(deps): bump `turbo` to `v1.10.3` and `pnpm` to
`v8.6.2`
- fd12aa0a8: chore(projects): remove the postgres password input from
the project creation screen
-   022b76e78: chore(deps): bump `@types/react` to `v18.2.11`
-   3555ab2b7: chore(deps): bump `vitest` monorepo to `v0.32.0`
-   c43e54922: feat(backups): add download button to backups

## 0.17.8

### Patch Changes

-   d0457fe5c: feat(settings): improve the dashboard and config parity
    -   @nhost/react-apollo@5.0.26
    -   @nhost/nextjs@1.13.28

## 0.17.7

### Patch Changes

-   4f0368b95: fix(account): don't break account settings page

## 0.17.6

### Patch Changes

- 64a8f41d0: chore(resources): lower the maximum allowed resources per
service

## 0.17.5

### Patch Changes

-   @nhost/react-apollo@5.0.25
-   @nhost/nextjs@1.13.27

## 0.17.4

### Patch Changes

- 9b1d0f7a5: fix(deployments): use correct timestamp for deployment
details
-   6d2963ffa: chore(deps): bump `@types/react` to `v18.2.8`
- 8871267b9: chore(deps): downgrade `pnpm` to `v8.5.1` because of no
Turborepo support

## 0.17.3

### Patch Changes

-   01eeef9de: chore(misc): under the hood improvements
- 21e13db05: chore(deps): bump `@types/react` to `v18.2.7` and `turbo`
to `v1.10.1`
- f16433ae6: chore(secrets): allow empty secrets and environment
variables
-   aa3c62989: chore(cli): bump Nhost CLI version to v1.0
    -   @nhost/react-apollo@5.0.24
    -   @nhost/nextjs@1.13.26

## 0.17.2

### Patch Changes

-   88a4983f: chore(misc): under the hood improvements

## 0.17.1

### Patch Changes

-   9b0d4dde: feat(secrets): enable secrets

## 0.17.0

### Minor Changes

-   15d84a19: Add postgres 14.6-20230525

## 0.16.14

### Patch Changes

-   4c626174: chore: updated import paths, improved directory structure
-   cc047b71: chore(deps): bump `@fontsource` monorepo to `v5.0.0`
-   99edd012: feat(account): add support for personal access tokens

## 0.16.13

### Patch Changes

-   78c7109c: feat(settings): allow selecting service versions

## 0.16.12

### Patch Changes

- 399009d6: fix(gql): don't enter an infinite loop when fetching remote
app data
- 329e5a91: fix(deployments): use the same sorting of deployments
everywhere
- 6d559d6e: chore(settings): add under the hood improvements to the
settings page
- 12eb236c: chore(deps): bump `prettier-plugin-tailwindcss` to `v0.3.0`
-   f9b81a2a: chore(deps): bump `turbo` to `v1.9.8`
-   1345741b: fix(projects): don't redirect to 404 on project creation
-   Updated dependencies [7fea29a8]
    -   @nhost/react-apollo@5.0.23
    -   @nhost/nextjs@1.13.25

## 0.16.11

### Patch Changes

- 1230b722: fix(projects): don't redirect to 404 on when the project is
renamed
    -   @nhost/react-apollo@5.0.22
    -   @nhost/nextjs@1.13.24

## 0.16.10

### Patch Changes

-   Updated dependencies [da03bf39]
    -   @nhost/react-apollo@5.0.21
    -   @nhost/nextjs@1.13.23

## 0.16.9

### Patch Changes

- 349aac36: fix(settings): use region domain when constructing the
postgres connection string

## 0.16.8

### Patch Changes

- 20fb69fa: chore(projects): change the way how API URLs are constructed

## 0.16.7

### Patch Changes

- 49f9b837: chore(docker): bump `pnpm` to `v8.4.0` and `turbo` to
`v1.9.3`
- 3f478a4e: chore(deps): bump `vitest` to `v0.31.0`, `@types/react` to
`v18.2.6` and `@types/react-dom` to `v18.2.4`

## 0.16.6

### Patch Changes

- d926f156: fix(projects): redirect to 404 when an invalid project is
opened
- 49b99728: fix(projects): disable features for non-owner members of
workspaces

## 0.16.5

### Patch Changes

-   12e2855f: chore(deps): bump `jsdom` to v22
-   e4972b83: feat(metrics): add Grafana page

## 0.16.4

### Patch Changes

- 3f396a9e: fix(projects): unpause after upgrading a paused project to
pro
- 3f396a9e: fix(projects): don't redirect to 404 page after project
creation

## 0.16.3

### Patch Changes

-   Updated dependencies [90c60311]
    -   @nhost/react-apollo@5.0.20
    -   @nhost/nextjs@1.13.22

## 0.16.2

### Patch Changes

-   0f34f0c6: fix(projects): disallow downgrading to free plan
- 8da291ad: chore(deps): bump `@types/react` to v18.2.0 and
`@types/react-dom` to v18.2.1

## 0.16.1

### Patch Changes

- adc828a5: fix(gql): don't enter an infinite loop when fetching remote
app data

## 0.16.0

### Minor Changes

-   2fb1145f: feat(compute): add support for replicas

### Patch Changes

- d8ceccec: chore(env): remove deprecated `NHOST_BACKEND_URL`
environment variable

## 0.15.2

### Patch Changes

-   84b84ab7: fix(projects): filter projects by workspace

## 0.15.1

### Patch Changes

-   2faf7907: chore(deps): bump `graphql-request` to v6
-   f1b5a944: chore(deps): bump `@vitejs/plugin-react` to v4
-   7f1785ac: chore(deps): bump `@types/react` to v18.0.37
    -   @nhost/react-apollo@5.0.19

## 0.15.0

### Minor Changes

-   85889ee8: feat(dashboard): add Compute management to the settings

## 0.14.8

### Patch Changes

-   668c8771: chore(dialogs): unify dialog management of payment dialogs

## 0.14.7

### Patch Changes

-   d4ccc656: chore: cleanup unused code
    -   @nhost/react-apollo@5.0.18
    -   @nhost/nextjs@1.13.21

## 0.14.6

### Patch Changes

-   b299cfc9: chore(deps): bump `vitest` to v0.30.0
-   411cb65b: chore(projects): refactor workspace and project hooks
- 43b1b144: chore(deps): bump `@types/react` to v18.0.34 and
`@types/react-dom` to v18.0.11
-   Updated dependencies [43b1b144]
    -   @nhost/react-apollo@5.0.17
    -   @nhost/nextjs@1.13.20

## 0.14.5

### Patch Changes

-   ba0d57ee: fix(i18n): revert i18n library
-   3328ed05: feat(projects): improve overview when there is an error

## 0.14.4

### Patch Changes

-   5e0920ba: chore(deps): bump `next-seo` to v6
-   706c9dc3: chore(deps): bump `@types/react` to 18.0.33
-   99f8f6b3: feat(metrics): show metrics on the overview

## 0.14.3

### Patch Changes

-   @nhost/react-apollo@5.0.16

## 0.14.2

### Patch Changes

-   3cb67300: fix(logs): don't break UI when clearing time picker
-   7453bf3b: feat(projects): show project creator info
-   c166dad0: chore(tests): improve auth page tests
-   6a290bb2: chore(deps): bump `@types/react` to 18.0.32

## 0.14.1

### Patch Changes

-   @nhost/react-apollo@5.0.15
-   @nhost/nextjs@1.13.19

## 0.14.0

### Minor Changes

-   6e1f03ea: feat(dashboard): add support for the Azure AD provider

### Patch Changes

-   1bd2c373: chore(deps): bump `turbo` to 1.8.6
-   d329b621: chore(deps): bump `@types/react` to 18.0.30
-   cb248f0d: fix(tests): avoid name collision in database tests
-   867c8076: chore(deps): bump `@types/react` to 18.0.29

## 0.13.10

### Patch Changes

- e93b06ab: fix(dashboard): remove left margin from workspace list on
mobile
-   1c4806bf: chore(deps): bump `sharp` to 0.32.0
    -   @nhost/react-apollo@5.0.14
    -   @nhost/nextjs@1.13.18

## 0.13.9

### Patch Changes

-   912ed76c: chore(dashboard): bump `@apollo/client` to 3.7.10
-   Updated dependencies [912ed76c]
    -   @nhost/react-apollo@5.0.13

## 0.13.8

### Patch Changes

-   7c127372: chore(dashboard): bump `react-error-boundary` to v4

## 0.13.7

### Patch Changes

- 9130ab12: chore(dashboard): bump `yup` to v1 and `@hookform/resolvers`
to v3

## 0.13.6

### Patch Changes

- 253dd235: using new mutation to create projects + refactor Create
Project page.

## 0.13.5

### Patch Changes

-   @nhost/react-apollo@5.0.12
-   @nhost/nextjs@1.13.17

## 0.13.4

### Patch Changes

-   b48bc034: fix(dashboard): disable new users
-   798e591b: fix(dashboard): show correct date in data grid

## 0.13.3

### Patch Changes

-   bfb4c1a6: chore(dashboard): remove `useAxios` property
-   d8d8394b: Dashboard: allow to override hasura admin secret in docker
-   Updated dependencies [ce1ee40d]
    -   @nhost/nextjs@1.13.16
    -   @nhost/react-apollo@5.0.11

## 0.13.2

### Patch Changes

-   beed2eba: Fix docker entrypoint for dashboard
- 2c8559a3: fix(dashboard): refresh project list after deleting a
project
-   4329d048: chore(dashboard): bump `graphiql` dependencies

## 0.13.1

### Patch Changes

-   cbb1fc5b: chore(dashboard): cleanup GraphQL operations

## 0.13.0

### Minor Changes

-   088584e7: feat(dashboard): add support for custom local subdomains

### Patch Changes

-   2ac90dfd: fix(dashboard): improve mobile responsive layout
-   Updated dependencies [f375eacc]
    -   @nhost/nextjs@1.13.15
    -   @nhost/react-apollo@5.0.10

## 0.12.4

### Patch Changes

-   @nhost/react-apollo@5.0.9
-   @nhost/nextjs@1.13.14

## 0.12.3

### Patch Changes

-   2b1338f7: chore(dashboard): bump `turbo` to 1.8.3
- 5223ee93: fix(dashboard): show correct deployment status on the main
page
-   850a049c: chore(deps): update docker/build-push-action action to v4
-   Updated dependencies [850a049c]
    -   @nhost/nextjs@1.13.13
    -   @nhost/react-apollo@5.0.8

## 0.12.2

### Patch Changes

-   4bf40995: chore(deps): bump `typescript` to `4.9.5`
-   8bb097c9: chore(deps): bump `vitest`
- 35d52aab: chore(deps): replace `cross-fetch` with `isomorphic-unfetch`
-   Updated dependencies [4bf40995]
-   Updated dependencies [8bb097c9]
-   Updated dependencies [35d52aab]
    -   @nhost/react-apollo@5.0.7
    -   @nhost/nextjs@1.13.12

## 0.12.1

### Patch Changes

-   c96d7ccd: fix(dashboard): fix docker builds

## 0.12.0

### Minor Changes

-   d1671210: feat(dashboard): use mimir to manage project configuration

### Patch Changes

-   f65e4de9: chore(deps): bump @graphql-codegen monorepo to v3

## 0.11.20

### Patch Changes

-   4b4f0d01: chore(dashboard): improve dialog management

## 0.11.19

### Patch Changes

-   @nhost/react-apollo@5.0.6
-   @nhost/nextjs@1.13.11

## 0.11.18

### Patch Changes

-   01318860: fix(nhost-js): use correct URL for functions requests
-   Updated dependencies [01318860]
    -   @nhost/react-apollo@5.0.5
    -   @nhost/nextjs@1.13.10

## 0.11.17

### Patch Changes

-   f673adea: fix(dashboard): set correct Content-Type for user creation
-   445d8ef4: chore(deps): bump `@nhost/react-apollo` to 5.0.4
-   445d8ef4: chore(deps): bump `@nhost/nextjs` to 1.13.9
- 0368663d: fix(dashboard): allow permission editing for auth and
storage schemas
-   Updated dependencies [445d8ef4]
-   Updated dependencies [445d8ef4]
    -   @nhost/react-apollo@5.0.4
    -   @nhost/nextjs@1.13.9

## 0.11.16

### Patch Changes

-   b755e908: fix(dashboard): use correct date for last seen
-   2d9145f9: chore(deps): revert GraphQL client
- 1ddf704c: fix(dashboard): don't show false positive message for failed
user creation
    -   @nhost/react-apollo@5.0.3
    -   @nhost/nextjs@1.13.8

## 0.11.15

### Patch Changes

-   @nhost/react-apollo@5.0.2
-   @nhost/nextjs@1.13.7

## 0.11.14

### Patch Changes

- 2cc18dcb: fix(dashboard): prevent permission editor dropdown from
being always open

## 0.11.13

### Patch Changes

- 3343a363: chore(dashboard): bump `@testing-library/react` to v14 and
`@testing-library/dom` to v9
    -   @nhost/react-apollo@5.0.1
    -   @nhost/nextjs@1.13.6

## 0.11.12

### Patch Changes

- 87eda76e: chore(dashboard): bump `@types/react` to v18.0.28 and
`@types/react-dom` to v18.0.11
-   6f0ac570: feat(dashboard): show dashboard version in account menu

## 0.11.11

### Patch Changes

-   bf1e4071: chore(dashboard): bump `react-is` version to `18.2.0`
-   Updated dependencies [bf1e4071]
-   Updated dependencies [5013213b]
    -   @nhost/nextjs@1.13.5
    -   @nhost/react-apollo@4.13.5

## 0.11.10

### Patch Changes

- a37a430b: fix(dashboard): don't break UI when deployments are
unavailable
    -   @nhost/react-apollo@4.13.4
    -   @nhost/nextjs@1.13.4

## 0.11.9

### Patch Changes

-   7b970e68: fix(dashboard): fix header link color

## 0.11.8

### Patch Changes

- f33242f2: feat(dashboard): add new sign up, sign in and reset password
pages

## 0.11.7

### Patch Changes

-   e9c8909c: fix(dashboard): use correct theme color in dark mode

## 0.11.6

### Patch Changes

-   902f486b: fix(dashboard): re-enable Hasura on logs page

## 0.11.5

### Patch Changes

-   1f9720fa: fix(dashboard): apply select permissions properly

## 0.11.4

### Patch Changes

-   deb14b51: fix(dashboard): don't break billing form

## 0.11.3

### Patch Changes

-   @nhost/react-apollo@4.13.3
-   @nhost/nextjs@1.13.3

## 0.11.2

### Patch Changes

-   f143e51d: chore(dashboard): pin Turborepo to 1.6.3

## 0.11.1

### Patch Changes

-   c2b5a41a: chore(dashboard): select system colors by default

## 0.11.0

### Minor Changes

-   1ebaf429: feat(dashboard): introduce Dark Mode 🌚

### Patch Changes

- 63b445c4: fixed duplicated logs bug and made to date count during live
mode

## 0.10.1

### Patch Changes

-   e146d32e: chore(deps): update dependency @types/react to v18.0.27
-   59347fcd: correct allowed role name
-   5b65cac9: updated authentication documentation
-   963f9b5e: feat(dashboard): include project info in feedback

## 0.10.0

### Minor Changes

-   ed4c7801: chore(dashboard): remove Functions section

## 0.9.10

### Patch Changes

-   4e2f8ccd: fix(dashboard): don't break Auth page in local mode

## 0.9.9

### Patch Changes

-   31abbe5f: fix(dashboard): enable toggle when settings are filled in

## 0.9.8

### Patch Changes

- 5bdd31ad: chore(dashboard): list fewer images per page on the Storage
page
- 5121851c: fix(dashboard): don't throw validation error for valid
permission rules

## 0.9.7

### Patch Changes

-   c126b20d: fix(dashboard): correct redeployment button

## 0.9.6

### Patch Changes

-   36c3519c: feat(dashboard): retrigger deployments

## 0.9.5

### Patch Changes

- 200e9f77: chore(deps): update dependency @types/react-dom to v18.0.10
-   Updated dependencies [200e9f77]
    -   @nhost/nextjs@1.13.2
    -   @nhost/react-apollo@4.13.2

## 0.9.4

### Patch Changes

- dbd3ded5: fix(dashboard): workspaces creation, new form, correct
redirects.

## 0.9.3

### Patch Changes

-   85f0f943: fix(dashboard): don't break the table creation process

## 0.9.2

### Patch Changes

-   Updated dependencies [d42c27ae]
-   Updated dependencies [927be4a2]
    -   @nhost/nextjs@1.13.1
    -   @nhost/react-apollo@4.13.1

## 0.9.1

### Patch Changes

-   d0f80811: fix(dashboard): don't show error when signing out the user

## 0.9.0

### Minor Changes

- d92891b2: feat(dashboard): add Permission Editor to the Database
section

### Patch Changes

-   3d379128: fix(dashboard): create new user
    -   @nhost/react-apollo@4.13.0
    -   @nhost/nextjs@1.13.0

## 0.8.1

### Patch Changes

-   7cadd944: fix(dashboard): display Twitter provider settings

## 0.8.0

### Minor Changes

-   9a1aa7bb: add functions to the log dashboard
-   f29abe62: feat(dashboard): Users Management v2

### Patch Changes

-   7766624b: feat(dashboard): add JWT secret editor modal
    -   @nhost/react-apollo@4.12.1
    -   @nhost/nextjs@1.12.1

## 0.7.13

### Patch Changes

-   dd0738d5: fix(dashboard): provisioning status polling

## 0.7.12

### Patch Changes

-   b21222b3: chore(deps): update dependency @types/node to v16
-   9e0486a3: fix(dashboard): close modals when navigating
-   Updated dependencies [b21222b3]
-   Updated dependencies [65687bee]
-   Updated dependencies [54df0df4]
    -   @nhost/nextjs@1.12.0
    -   @nhost/react-apollo@4.12.0

## 0.7.11

### Patch Changes

-   d6527122: fix(dashboard): use correct service URLs

## 0.7.10

### Patch Changes

-   Updated dependencies [57db5b83]
    -   @nhost/nextjs@1.11.0
    -   @nhost/nhost-js@1.7.0
    -   @nhost/react@0.17.0
    -   @nhost/react-apollo@4.11.0

## 0.7.9

### Patch Changes

- a6d31dc2: fix(dashboard): don't break the UI when project is not
loaded yet

## 0.7.8

### Patch Changes

- 7f251111: Use `NhostProvider` instead of `NhostReactProvider` and
`NhostNextProvider`

    `NhostReactProvider` and `NhostNextProvider` are now deprecated

-   f4d70f88: fix(dashboard): do not break when region is nullish

- 4a9471cc: Windows Live Provider displayed link updated to match
backend url

- 594488e4: fix(dashboard): do not show error when submitting Apple
provider settings

-   Updated dependencies [7f251111]
    -   @nhost/nextjs@1.10.0
    -   @nhost/react@0.16.0
    -   @nhost/react-apollo@4.10.0

## 0.7.7

### Patch Changes

-   80b604ad: fix(dashboard): use correct Hasura slug

## 0.7.6

### Patch Changes

-   2d2beb53: fix(dashboard): prevent error on GraphQL page
-   ac8efcbd: chore(dashboard): deprecate old DNS name

## 0.7.5

### Patch Changes

-   132a4f4b: chore(dashboard): remove unused dependencies
- 132a4f4b: chore(deps): synchronize @types/react-dom and @types/react
versions
-   db57572f: fix(dashboard): correct section paddings when no env vars
-   Updated dependencies [132a4f4b]
    -   @nhost/react@0.15.2
    -   @nhost/react-apollo@4.9.2
    -   @nhost/nextjs@1.9.3

## 0.7.4

### Patch Changes

-   34d85e54: chore(deps): update dependency critters to ^0.0.16
- 9b93cf95: chore(deps): update dependency @netlify/functions to ^0.11.0
-   e0439030: chore(deps): update dependency @types/react-dom to v18.0.9
-   Updated dependencies [82124329]
    -   @nhost/nextjs@1.9.2

## 0.7.3

### Patch Changes

-   a1193da4: fix(dashboard): remove character limit from env var inputs

## 0.7.2

### Patch Changes

-   44f13f62: chore(dashboard): cleanup unused files

## 0.7.1

### Patch Changes

- e01cb2ed: chore(dashboard): change settings sidebar menu item density

## 0.7.0

### Minor Changes

- db342f45: chore(dashboard): refactor Roles and Permissions settings
sections
-   8b9fa0b1: feat(dashboard): add Environment Variables page

### Patch Changes

-   Updated dependencies [66b4f3d0]
-   Updated dependencies [2e6923dc]
-   Updated dependencies [ef117c28]
-   Updated dependencies [aebb8225]
    -   @nhost/core@0.9.4
    -   @nhost/nhost-js@1.6.2
    -   @nhost/nextjs@1.9.1
    -   @nhost/react@0.15.1
    -   @nhost/react-apollo@4.9.1

## 0.6.0

### Minor Changes

-   eef9c914: feat(dashboard): add Roles and Permissions page

## 0.5.0

### Minor Changes

-   a48dd5bf: feat(dashboard): make backend port configurable

## 0.4.3

### Patch Changes

-   5de965d9: fix(dashboard): alphabetic ordering of providers
-   b9087a4a: fix(dashboard): console -> dashboard terminology
-   ca012d79: docs(workos): WorkOS Docs

## 0.4.2

### Patch Changes

-   89bd37bc: fix(dashboard): correct redirect URL input opacity
-   Updated dependencies [4601d84e]
-   Updated dependencies [843087cb]
    -   @nhost/react@0.15.0
    -   @nhost/nextjs@1.9.0
    -   @nhost/react-apollo@4.9.0

## 0.4.1

### Patch Changes

-   766cb612: fix(dashboard): correct redirect URL for oauth providers
-   Updated dependencies [53bdc294]
-   Updated dependencies [f2aaff05]
    -   @nhost/nextjs@1.8.3
    -   @nhost/core@0.9.3
    -   @nhost/react@0.14.3
    -   @nhost/nhost-js@1.6.1
    -   @nhost/react-apollo@4.8.3

## 0.4.0

### Minor Changes

-   9211743d: feat(dashboard): migrate Settings page features

## 0.3.0

### Minor Changes

-   73da6a67: fix(dashboard): avoid using BACKEND_URL locally

## 0.2.0

### Minor Changes

-   db118f97: feat(dashboard): generate Docker image

## @nhost/docs@2.29.0

### Minor Changes

-   b40d375: feat: added pitr docs

## @nhost-examples/cli@0.3.19

### Patch Changes

-   @nhost/nhost-js@3.2.6

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

### Patch Changes

-   Updated dependencies [ad57a9e]
    -   @nhost/react@3.10.2
    -   @nhost/react-apollo@17.0.2

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

### Patch Changes

-   Updated dependencies [ad57a9e]
    -   @nhost/react@3.10.2

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

### Patch Changes

-   Updated dependencies [ad57a9e]
    -   @nhost/react@3.10.2
    -   @nhost/react-urql@14.0.2

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

### Patch Changes

-   @nhost/nhost-js@3.2.6

## @nhost-examples/nextjs@0.4.5

### Patch Changes

-   Updated dependencies [ad57a9e]
    -   @nhost/react@3.10.2
    -   @nhost/react-apollo@17.0.2
    -   @nhost/nextjs@2.2.5

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

### Patch Changes

-   @nhost/nhost-js@3.2.6

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

### Patch Changes

-   @nhost/nhost-js@3.2.6

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

### Patch Changes

-   Updated dependencies [ad57a9e]
    -   @nhost/react@3.10.2
    -   @nhost/react-apollo@17.0.2

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

### Patch Changes

-   Updated dependencies [ad57a9e]
    -   @nhost/react@3.10.2

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

### Patch Changes

-   Updated dependencies [ad57a9e]
    -   @nhost/react@3.10.2
    -   @nhost/react-apollo@17.0.2

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

### Patch Changes

-   @nhost/nhost-js@3.2.6
-   @nhost/apollo@8.0.6
-   @nhost/vue@2.9.3

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

### Patch Changes

-   @nhost/apollo@8.0.6
-   @nhost/vue@2.9.3

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-12 13:35:39 +01:00
robertkasza
ad57a9e473 fix (auth): allow to pass headers to email/password sign up (#3201)
### **PR Type**
Enhancement, Bug fix


___

### **Description**
- Allow passing headers to email/password sign-up

- Add tests for custom headers in sign-up

- Fix typos and improve comments

- Update e2e backend script


___



### **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>4
files</summary><table>
<tr>
<td><strong>hasura-auth-client.ts</strong><dd><code>Add RequestOptions
to signUp method</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3201/files#diff-0dbc30932ed723b7fd458066893f29f2f77658436c84adf42613813ea042c992">+7/-4</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>events.ts</strong><dd><code>Add RequestOptions to
SIGNUP_SECURITY_KEY event</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3201/files#diff-a1a1ecc9ad9d8ed8e460e0401007a8d479b4d9ba66bc909e1d1458947b5fdf85">+6/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>signUpEmailSecurityKey.ts</strong><dd><code>Add
RequestOptions to signUpEmailSecurityKeyPromise</code>&nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3201/files#diff-55c8c8ec957b0dc8c8c5a4fe262187b133109408b5664b72f1776e96ec9f1427">+5/-3</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>useSignUpEmailSecurityKey.ts</strong><dd><code>Add
RequestOptions to signUpEmailSecurityKey handler</code>&nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3201/files#diff-2cf827b3455da361a3ee39b49c1cf1ce6a2dca411b34441a63f7fc1f9e897cf0">+6/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Bug
fix</strong></td><td><details><summary>3 files</summary><table>
<tr>
<td><strong>machine.ts</strong><dd><code>Fix typos and improve
comments</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></td>
<td><a
href="https://github.com/nhost/nhost/pull/3201/files#diff-a8fdfee087ad5a72ea0a64667e2a0c7f25baa84eaaf73ebfee3f5a5a1b7584d1">+9/-7</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>fetch.ts</strong><dd><code>Fix typo in FetchResponse
interface name</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3201/files#diff-b1af9daf6c51514d5d514540f2318d87e926c5e8a57079b6e2c258b98a1163a2">+6/-6</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>useSignUpEmailPassword.ts</strong><dd><code>Remove type
assertion for password parameter</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3201/files#diff-88cf86ab14d0ece9af1761ddcf1d940ba829317852964748a6033658519370af">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

</table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>1
files</summary><table>
<tr>
<td><strong>hasura-auth-client.test.ts</strong><dd><code>Add tests for
custom headers in sign-up</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3201/files#diff-f2aedf396c71758c8b69f56f94e22b7f1f8f5cc5b9a2791d1e339dbabd8a8970">+60/-0</a>&nbsp;
&nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Configuration
changes</strong></td><td><details><summary>1 files</summary><table>
<tr>
<td><strong>package.json</strong><dd><code>Update e2e:start-backend
script</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3201/files#diff-d95dc3391741287366ea2e61f70e9ccc64452e0d22b1db91d6bf524f5aa4331c">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-03-12 13:14:05 +01:00
David Barroso
b40d375039 feat (docs): added pitr docs (#3223)
### **PR Type**
Documentation


___

### **Description**
- Added comprehensive documentation for database backups and
restoration.

- Introduced details on Point-in-Time Recovery (PITR) feature.

- Updated navigation structure to include the new backups guide.

- Created a changeset file for versioning and release notes.


___



### **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>afraid-seahorses-remain.md</strong><dd><code>Add
changeset for PITR documentation update</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

.changeset/afraid-seahorses-remain.md

<li>Added a changeset file for versioning.<br> <li> Marked the addition
of PITR documentation as a minor change.


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>mint.json</strong><dd><code>Update navigation to
include backups guide</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

docs/mint.json

<li>Updated navigation to include the new backups guide.<br> <li> Added
<code>guides/database/backups</code> to the database section.


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>backups.mdx</strong><dd><code>Add detailed guide for
database backups and PITR</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/database/backups.mdx

<li>Added a new guide for database backups.<br> <li> Included
instructions for restoring backups and PITR.<br> <li> Provided details
on backup retention and manual backups.<br> <li> Explained restoring
backups on different projects.


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-03-12 12:44:38 +01:00
David BM
af61cb737a fix (dashboard): correct user edit default role source (#3230)
### **User description**
Fixes #3219

Also adds 5 minutes timeout to CI install Nhost CLI action


___

### **PR Type**
Bug fix


___

### **Description**
- Correct default role source in user edit form

- Remove unused roles query and data processing

- Update default role options rendering

- Add changeset for version tracking


___



### **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>EditUserForm.tsx</strong><dd><code>Refactor default
role selection in EditUserForm</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


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

<li>Remove <code>useGetRolesPermissionsQuery</code> and related data
processing<br> <li> Update default role options to use
<code>roles</code> prop<br> <li> Replace
<code>allAvailableProjectRoles</code> with direct <code>roles</code>
mapping<br> <li> Adjust <code>Option</code> component key and value
props


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>friendly-beans-arrive.md</strong><dd><code>Add
changeset for dashboard version update</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

.changeset/friendly-beans-arrive.md

<li>Add new changeset file for version tracking<br> <li> Specify minor
version bump for @nhost/dashboard<br> <li> Include fix description for
default role source


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-03-12 12:16:39 +01:00
robertkasza
f84cd550d9 feat (dashboard): Add PiTR to backups page (#3229)
### **PR Type**
Enhancement


___

### **Description**
- Add Point-in-Time Recovery (PiTR) feature

- Implement backup import functionality

- Update database settings and UI components

- Refactor and improve existing backup components


___



### **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>19
files</summary><table>
<tr>
<td><strong>DateTimePicker.tsx</strong><dd><code>Add new DateTimePicker
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; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-1ad629af5bd959e35b4d7fa2ab0470d644e8762568b8eeda6c2b7190a17452d0">+125/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>TimezoneSettings.tsx</strong><dd><code>Create
TimezoneSettings component for DateTimePicker</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-2cbffc4f494f4b7fb9e1d410a953bfd770da22331fb4cdec8145e06c0fee6478">+39/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>TimePicker.tsx</strong><dd><code>Implement new TimePicker
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; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-6c4810545201a0dd4b0ee3fde901f736d745b184c289864ecbb06f8f3998c099">+64/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>VirtualizedCombobox.tsx</strong><dd><code>Add
VirtualizedCombobox component for efficient option
rendering</code></dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-1cc84483e00069f85c3c0288f25cd68cf24495439b5f1c40eac35a0f91933763">+245/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>useDatabasePiTRSettings.ts</strong><dd><code>Create hook for
managing PiTR settings</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-e90e7eb41daf536c2d9b59e6212a8f31cb5b698507d87794834ba05aa1bf5b34">+24/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>

<td><strong>useImportBackupSourceProjectList.tsx</strong><dd><code>Implement
hook for fetching import backup source projects</code></dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-136414e9495d95c3328184caf2703a412d4702ccfc8d1717ef526c0a8da828fd">+33/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>useIsPiTREnabled.ts</strong><dd><code>Create hook to check
if PiTR is enabled</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-4c0d423babd527155f9f3886b0034806522fbaa8601b767f4aa8dcd25ea441e3">+0/-16</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>useIsPiTREnabledLazy.ts</strong><dd><code>Implement lazy
loading hook for PiTR status</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-521aa1cfda4f8601fe11d0e27e52c62a85323830e878785e91eb3730818fd5a2">+30/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>usePiTRBaseBackups.ts</strong><dd><code>Create hook for
fetching PiTR base backups</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-eff86f36e559ed015499f44d7d8285417b68a2c6362f8d938412d7b4927bd328">+33/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>

<td><strong>useRestoreApplicationDatabasePiTR.ts</strong><dd><code>Implement
hook for restoring database from PiTR</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-dd5774f502b63d2d443069bedb4c9531a77794a95aaa5c07287093695a4dc60a">+34/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>BackupsContent.tsx</strong><dd><code>Create new
BackupsContent component with tabs</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-e543f89d133a420d24302785cb201bdd02d8841d043a6b9c95df4fce8fbf4a45">+27/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>ImportBackupTabContent.tsx</strong><dd><code>Implement
ImportBackupTabContent component</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-cb278aa33b215fdf0550e658d9e91f3d826467878d2ab30afa5e7d18e02422ef">+43/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>PointInTimeTabsContent.tsx</strong><dd><code>Add
PointInTimeTabsContent component for PiTR</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-f3f19f0f85928cb5e9249ba6cd63b408df62fc8e681ae638816977c9f1b9016f">+17/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>ScheduledBackupTabContent.tsx</strong><dd><code>Refactor
ScheduledBackupTabContent component</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-546fc678d74ad6b3e7ffdafa83df0d58cc0a49df31b647cae6a9220f0e7c15ef">+36/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>PointInTimeBackupInfo.tsx</strong><dd><code>Create
PointInTimeBackupInfo component for PiTR</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-35be453f6605231bcee5b7f7f78564eb7aa2be723f5169509f9dddfe84477fe6">+63/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>backups.tsx</strong><dd><code>Update backups page to use new
components</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-11c24d569a8109344819d2cc9ce6ffbcf3b75abfba604e299c01289690d322f9">+5/-26</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>graphql.ts</strong><dd><code>Update GraphQL types and
queries for PiTR</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-fbd5db84b560b1c91675004448c6c7fa0dcbfb28b9eb05d53b03e6cb7b83ebac">+146/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>getPiTRBaseBackups.graphql</strong><dd><code>Add GraphQL
query for PiTR base backups</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-66811bf0abb1c941e2501d90c4b7bffb39ef30338c98d6c9ee20facde3b5f2d5">+6/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>

<td><strong>restoreApplicationDatabasePiTR.graphql</strong><dd><code>Add
GraphQL mutation for PiTR database restore</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-4b8a543fb86cfca8e3041c00454ad9ff3cac7fc92b75ab3840e057961a169f34">+11/-0</a>&nbsp;
&nbsp; </td>

</tr>

</table></details></td></tr><tr><td><strong>Dependencies</strong></td><td><details><summary>1
files</summary><table>
<tr>
<td><strong>package.json</strong><dd><code>Update dependencies and add
new packages</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-2d8d55c799cd71f1b35e831f075f8178ed1734c4820a2ad548b4dd24d6938d7c">+6/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Additional
files</strong></td><td><details><summary>62 files</summary><table>
<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-e41e4cb8c926d6561aeed59ddaadd8a883462fbfc6e51081f48feb089599a982">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>TimePickerInput.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-970eb8f755f27a1e13f0d24230c403ffd1e5ad7829e14b67690373bcdade1277">+148/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-1c4caf14e81e8ebe413cd16c1fdf71a5b99059540eb8e6c230ec0ed7948eea70">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>time-picker-utils.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-539dd838376d43e44041d8ff8a8d3a9d3405661d2775788eadb50d4bad1db512">+244/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>TimezonePicker.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-1219e627fa38850183eec3d695ae39c4b607b2ed62255789917672e999ceca55">+33/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-1da85db0b242da76756df5ac70f3068f2c53f1b9616ccbd3da08805a80c549af">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-daf57b3357b1698296dcc41422780614a29e9451007df41e7153c270e05c0085">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>button.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-3128c8a9c83cc7319d992e31c2cba551cde0d85037f42a5a74a47464d7e80f82">+14/-1</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>calendar.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-63f846015a12e66f0c36ec550d502d6d00a250959957652d14460807f1fbe68d">+80/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>dialog.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-f2ef2b36b8f85ecc699ce3c64158a9018a0a15242b9f945d2bedc40b7c59c102">+12/-2</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>spinner.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-0404df81ce8503d881cd2f3f1ac59865aafd65f559e6a335cd79e2c49fe31476">+56/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>tabs.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-bc5ee4fa7f3a98e85560de1abffcae71bc527bf8087ea152f3c5ae8246ed1b1a">+53/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>InfoAlert.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-4b78a2f0e1c6c2f6b37e430bc6cad016e884bb34735bd6aaebac906743748d7b">+31/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-1ed5caad9d58ebed38957a3d8db2a395b84a6f11abd4fc9455b9e895ba0a0a76">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-d3e8f38360bc461e1c7d52084c21729bd3af11ca4ff72fe6e07cbe78f4a756a5">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>useUpdateDatabasePITRConfig.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-9c0f751418c04e20ce5b992961fe3ae779dd056e16188ad0684236e91debb333">+0/-24</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-9401c95cb391dc84bd88c87dc7bbb84f4a04a17ce3768a7aa03e4df600985ae8">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-ee9a711d701edfc23d3fdee2d93d574e9c7335c620852dfd6b0042d91928bdca">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-ada2a14aa3c012af7ef896b6b5b34073c7122e2e76e02fc528e0f67ed3a0762a">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>useIsPITREnabled.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-ccadf282438731d649162e5f7a2f778e235cee170c660e6db945107bac075485">+0/-16</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-7703cb7870d19609bb12fa3a5be2c0cc397cc017fbb723b896e789d9d475f185">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-f7e86e2560ce15ecfc052a73c255d1bf2fcbf4c5ce93701cb3d7ea0cdf076da9">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-7276ebfe36c94d90f1b8d25753263d068d4ab5af794a2fc0ca926ba18b30c982">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-f635e9c12762de8ab4d9ff62302eeffda1e3c8b3efb3760cd59a7c16052f6fb6">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-f1322a198c8a57293119cabf169954842e63df112322af72c7bdeb13366bf567">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>useUpdateDatabasePiTRConfig.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-b91403cefffce18a9a12888f9c702c8567fad60130fca863b7c7e790fa2219ee">+8/-8</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-e25cc47626c366456dd2206f613fa5d4bdfcdec5c750b5ae99c4c8d44d8df47f">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-b28f78fd315d021c6a3c1d46b90beac22d3a98eba6ca15f6ca79f75b84f84fa8">+0/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-45ea1cacd064ba79052f50bf5e9dace59c801c0b9c43b541c4b1fa1d1c53f76d">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>NoOtherProjectsInRegion.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-cb4d6fd1bc9222df18ec07bf803884bcd2abf2562b9ae9c4c66a66dbfc05c153">+18/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>PiTRNotEnabledOnSourceProject.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-bde1eb77a9da964612758a7795ef0bbc7d7ea40b9f4d278788e7f0d9ac0f74f4">+15/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>SourceProjectBackupInfo.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-4bf8851fed9997b78e62e1d94dced8ceff8eda855e11d1cfedb7d23130363c37">+28/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>SourceProjectSelect.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-a04579d221dea095e7af4b3ec68e7d07ce7fa3765e25708e3bec22f89160e9b4">+53/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-9ddf7da80e5964414404d5c715c3da5a614b97e3263698d4c0be4db1e7c1f8ed">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>PiTRNotEnabled.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-a1d251614eecca4406816b18302a1af226aee95d01d04858661a30e6fb07218d">+24/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>PointInTimeRecovery.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-b0756ee9cd0789a2de53c5389d6a102bac50500bef97b809f55357798a939b5a">+15/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>RecoveryRetentionPeriod.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-631ff2a064d17d9ee08a6fecbf204908dd2e72b0dab8c23670b343b74fd41e8c">+16/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-6bcbb9922adb6b20bb23a7dc1b1272751d9813391af6bcccc4cf3ed4f7a2c727">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-8ba7b8d6096ee695aa7c1a92402a4d377b6777296e5b13cb345964be623fcbf9">+0/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>BackupList.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-fbd66a866aec5e50da247ecf43eabf32909e0648f682525b9554dd908a40d910">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>BackupListItem.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-319aac4df7906607fc84a6e8d0ba510fbf49ea92d6ccda5ab48c50e8f1ec33d5">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>PiTREnabledInfoBanner.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-7dab58d90b66701b52fff767835c62a8218bfe3c844544869052d8d32a87e37e">+12/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>RestoreBackupModal.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-f7caf91cec87922fc8a3ea4941b0dd6f752a4d542652d93f6c9a6219763fcfd4">[link]</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-243bca977e6e898b72be8e6739efb1b14aedd34d616a367746d7e62705289a20">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>BackupScheduledInfo.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-b7b769edd56e42710aca4bfb85af7d6bd0173d67f024478e4c68f0f7d23dd957">+46/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>EarliestBackup.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-c4d1332e7f0b63d50f679d7b56696d5cc53712b22bb53d44b2fcf064d79c8cb0">+53/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>RestoreBackupDialogButton.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-eed1e2ca6d4002ec218cd16e69ed81160c8c8982877c65ace80d12b88aefd6ef">+208/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>StartRestoreConfirmationCheck.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-56f1657d2302ecc9f2e34cc78363cfb90954791517b1ad23ec06f34b10bde37e">+35/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-77b497855973d099f547efc622b403bf1bdbd475b1503e36d581b7c48c525952">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>getDateTimeStringWithUTCOffset.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-420bbeebf0a8b98242780ea3b78ff843f33559c9b766b0c343b1c5d180a92e69">+17/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-94eeb0af5cef2989a172bef83e67e72d1dcfdd18da56b04c274c34eac5ac19eb">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>DatabasePiTRSettings.test.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-85d1f82a571b56469eab40dcc164fdd1e107fba79611ddd5cca7c191fe5117b4">+11/-11</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>DatabasePiTRSettings.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-7a638c446af8419249770dc8da1ea522f950163b1d0045020927216c38db8cec">+12/-12</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-398e23d6c879dd409f4ef19d13cba6372ec1e6044d3293a34e3c23fe2f8e02bd">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>DeploymentStatusMessage.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-7e077798c520eb4aada9d1a39d2e3f1a1ac573a821d57c64608e682b41150390">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>DeploymentStatusMessage.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-54e74ec65ab87fbf5a4344bbaf282c68809608ca9cfa8566a515b3f31259cad0">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>utils.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-57035bfd3b91de326fec3e5a0bf19487f03130a9a09dc3e428c79f556677081b">+2/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>database.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-00045ae38a73178045bcda39c80a03a0cb46413641586896a628c3a2a22c7855">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-b6be6198288cbb31a42f700a54e21842f253626d36940f7011b5d29a9311d0fd">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>timezone-utils.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-f80a0f62570505fcb30922366a5aea365d62b1ba25065d7717277704c4161741">+73/-0</a>&nbsp;
&nbsp; </td>

</tr>

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

</tr>

<tr>
  <td><strong>package.json</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3229/files#diff-27f71682a447c654ff4a94d33944ebb70e10d07a4279107c230bd8ec7dce7391">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-03-12 12:13:49 +01:00
robertkasza
1986178f7a feat (dashboard): add PITR setting to database setting page (#3224)
### **PR Type**
Enhancement


___

### **Description**
- Add Point-in-Time Recovery (PITR) settings to database

- Implement hooks for PITR configuration management

- Create new components for PITR UI

- Update GraphQL schema for PITR support


___



### **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>15
files</summary><table>
<tr>
<td><strong>index.ts</strong><dd><code>Export useDatabasePITRSettings
hook</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3224/files#diff-d3e8f38360bc461e1c7d52084c21729bd3af11ca4ff72fe6e07cbe78f4a756a5">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>useUpdateDatabasePITRConfig.ts</strong><dd><code>Implement
useDatabasePITRSettings hook</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3224/files#diff-9c0f751418c04e20ce5b992961fe3ae779dd056e16188ad0684236e91debb333">+21/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>index.ts</strong><dd><code>Export useIsPITREnabled
hook</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3224/files#diff-ada2a14aa3c012af7ef896b6b5b34073c7122e2e76e02fc528e0f67ed3a0762a">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>useIsPITREnabled.ts</strong><dd><code>Implement
useIsPITREnabled hook</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3224/files#diff-ccadf282438731d649162e5f7a2f778e235cee170c660e6db945107bac075485">+16/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>index.ts</strong><dd><code>Export
useUpdateDatabasePITRConfig hook</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3224/files#diff-f635e9c12762de8ab4d9ff62302eeffda1e3c8b3efb3760cd59a7c16052f6fb6">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>useUpdateDatabasePITRConfig.ts</strong><dd><code>Implement
useUpdateDatabasePITRConfig hook</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3224/files#diff-e8f221b95a9e889c359f7b3ff35ce8ea5f3b7090abfdae57c4f1d2ef54d89fb7">+57/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>index.ts</strong><dd><code>Add export for
useIsNotPlatform</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3224/files#diff-7e4f0dbd79622a9b7ff5b2e04717d645a98b1b723f107658e18c274f890ed627">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>useIsPlatform.ts</strong><dd><code>Add useIsNotPlatform
function</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></td>
<td><a
href="https://github.com/nhost/nhost/pull/3224/files#diff-eea9e9627c2314fd248713d113a88db30a99809d00ebbfdcca83689a35c4bdbc">+4/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>postgresqlConstants.ts</strong><dd><code>Add
RECOVERY_RETENTION_PERIOD_7 constant</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3224/files#diff-b497da90feca5bff94b0d38b69e519d171d43acc292098054d672a73a89b4717">+2/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>DatabasePitrSettings.tsx</strong><dd><code>Implement
DatabasePitrSettings component</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3224/files#diff-d7657adf6ee83cf09cebc9854dc4742bab9a23517e3277d09db923ca309be91b">+50/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>index.ts</strong><dd><code>Export DatabasePitrSettings
component</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3224/files#diff-20e8098340a0bcb8723cfd67e938f481d7172dcf6de4610e826a2d568948b0f4">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>database.tsx</strong><dd><code>Add DatabasePitrSettings to
database settings page</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3224/files#diff-00045ae38a73178045bcda39c80a03a0cb46413641586896a628c3a2a22c7855">+3/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>graphql.ts</strong><dd><code>Update GraphQL types for PITR
support</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3224/files#diff-fbd5db84b560b1c91675004448c6c7fa0dcbfb28b9eb05d53b03e6cb7b83ebac">+75/-1</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>getPostgresSettings.gql</strong><dd><code>Add PITR retention
to PostgreSQL settings query</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3224/files#diff-1acee16f252288335d1bb4d8af5ca313d66f75edd0d62fe29e6d38d4653393f7">+3/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>getPostgresSettings.gql</strong><dd><code>Add PITR retention
to PostgreSQL settings query</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3224/files#diff-d77873957f82dd65a26ae7beca2440148d8ba6911f5bed4bdeb6cf1ad6de7f25">+3/-0</a>&nbsp;
&nbsp; &nbsp; </td>

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-03-12 08:55:53 +01:00
David BM
aa9210c838 fix (dashboard): run services with secrets in the command field (#3208)
### **User description**
Fixes #3070


___

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


___

### **Description**
- Refactor command input to list format

- Fix parsing issues with secrets in commands

- Improve UI for command input section

- Update related components and types


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>ServiceForm.tsx</strong><dd><code>Replace command input
with new CommandFormSection component</code></dd></summary>
<hr>


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

<li>Remove shell-quote parsing<br> <li> Replace single command input
with CommandFormSection<br> <li> Update service configuration to use new
command format


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>ServiceFormTypes.ts</strong><dd><code>Update command
validation schema and type definition</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>


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

<li>Update command validation schema<br> <li> Change command type from
string to array of objects


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>CommandFormSection.tsx</strong><dd><code>Create new
CommandFormSection component for improved command
input</code></dd></summary>
<hr>


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

<li>Add new CommandFormSection component<br> <li> Implement dynamic
command input fields<br> <li> Add space detection warning<br> <li>
Include tooltip with usage instructions


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3208/files#diff-952b9cf5789c2075527c9289b6464a1a678bb3e2f1073e5493eb871eec33c650">+112/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>index.ts</strong><dd><code>Add index file for
CommandFormSection component</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/services/components/ServiceForm/components/CommandFormSection/index.ts

- Export CommandFormSection component


</details>


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

</tr>
</table></td></tr><tr><td><strong>Bug fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>ServiceConfirmationDialog.tsx</strong><dd><code>Update
import path in ServiceConfirmationDialog</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


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

- Update import path for ServiceFormValues type


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>ServicesList.tsx</strong><dd><code>Adjust command
formatting in ServicesList component</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>


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

- Update command formatting in initialData


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>run.tsx</strong><dd><code>Modify command formatting in
run page configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

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

- Update command formatting in parsedConfig


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>famous-seahorses-taste.md</strong><dd><code>Add
changeset for command field secrets fix</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

.changeset/famous-seahorses-taste.md

<li>Add changeset for minor version bump<br> <li> Describe fix for
secrets in command field


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-03-05 13:40:12 +01:00
David BM
695466df95 chore (dashboard): simplify personal project deletion checkboxes (#3225)
### **User description**
This PR removes the `I understand I need to delete the organization if I
want to cancel the subscription` checkbox when deleting a project if
it's part of a personal organization (`org.plan.isFree == true`)


___

### **PR Type**
Enhancement


___

### **Description**
- Simplify personal project deletion flow

- Remove subscription cancellation checkbox for free plans

- Add conditional rendering based on plan type

- Update project deletion button disabled state


___



### **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>RemoveApplicationModal.tsx</strong><dd><code>Conditional
rendering of deletion checkboxes based on plan
type</code></dd></summary>
<hr>


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

<li>Add <code>isPlanFree</code> variable to check if org plan is
free<br> <li> Conditionally render subscription cancellation
checkbox<br> <li> Update button disabled state based on
<code>isPlanFree</code>


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>grumpy-pigs-cough.md</strong><dd><code>Add changeset
for personal project deletion simplification</code></dd></summary>
<hr>

.changeset/grumpy-pigs-cough.md

<li>Add changeset file for minor version bump<br> <li> Include
description of changes for personal project deletion


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-03-04 19:38:44 +01:00
David BM
fe23bde306 chore (ci): local dashboard e2e tests with CLI (#3222)
### **User description**
Resolves #3173


___

### **PR Type**
Enhancement, Tests


___

### **Description**
- Add e2e tests for local dashboard with CLI

- Create new playwright configuration for local tests

- Update GitHub Actions for local dashboard testing

- Modify Nhost CLI action to support initialization


___



### **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>cli-local-dashboard.test.ts</strong><dd><code>Add e2e
tests for local dashboard</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/e2e/cli-local-dashboard/cli-local-dashboard.test.ts

<li>Add test for redirecting '/' to correct project URL<br> <li> Add
test for loading project URL correctly<br> <li> Verify visibility of
'Subdomain' text on project page


</details>


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

</tr>
</table></td></tr><tr><td><strong>Configuration
changes</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>playwright.config.ts</strong><dd><code>Update main
playwright config</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/playwright.config.ts

- Exclude 'Local Dashboard CLI e2e tests' from main configuration


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>playwright.local.config.ts</strong><dd><code>Add new
playwright config for local tests</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

dashboard/playwright.local.config.ts

<li>Create new playwright configuration for local dashboard tests<br>
<li> Set specific test matching for CLI local dashboard tests<br> <li>
Configure browser and test settings for local testing


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3222/files#diff-416d6b4a1c916809500432ef6baeba2fe8b3074f996ed53a7225524305d0c65d">+31/-0</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>dirty-snakes-tell.md</strong><dd><code>Add changeset
for dashboard e2e tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/dirty-snakes-tell.md

<li>Add changeset for minor version bump of @nhost/dashboard<br> <li>
Describe addition of e2e tests for local dashboard with CLI


</details>


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

</tr>
</table></td></tr><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>action.yaml</strong><dd><code>Enhance Nhost CLI action
with new features</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.github/actions/nhost-cli/action.yaml

<li>Add 'init' input to initialize new projects<br> <li> Add
'dashboard-image' input for custom dashboard images<br> <li> Implement
logic for initializing projects and using custom images


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>ci.yaml</strong><dd><code>Update CI workflow for local
dashboard testing</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.github/workflows/ci.yaml

<li>Add steps to build and test dashboard locally<br> <li> Implement
Nhost CLI initialization for dashboard tests<br> <li> Add step to run
local dashboard e2e tests


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>test-nhost-cli-action.yaml</strong><dd><code>Refactor
Nhost CLI action test workflow</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.github/workflows/test-nhost-cli-action.yaml

<li>Update Nhost CLI action usage to use 'init' parameter<br> <li>
Modify version checks and expected output<br> <li> Adjust paths and
commands for testing


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Add local e2e test
script to package.json</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

dashboard/package.json

<li>Add new 'e2e-local' script for running local dashboard tests<br>
<li> Update 'e2e' script to use specific playwright config


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-03-04 17:35:27 +01:00
David BM
ea2dbf8734 fix (dashboard): can change minor database version with unhealthy project (#3210) 2025-03-04 14:26:44 +01:00
David BM
f4167e328c fix (dashboard): split postgres version if using a beta like 17.2-0.0.0-beta1 (#3211)
### **User description**
Fixes #3175


___

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


___

### **Description**
- Fix Postgres version splitting for beta versions

- Improve major version validation in settings

- Add utility function for version splitting

- Implement tests for version splitting function


___



### **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>DatabaseServiceVersionSettings.tsx</strong><dd><code>Update
version handling in DatabaseServiceVersionSettings</code></dd></summary>
<hr>


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

<li>Import new <code>splitPostgresMajorMinorVersions</code> utility<br>
<li> Update major version validation to check for positive number<br>
<li> Use new utility function to split Postgres versions


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>index.ts</strong><dd><code>Add export for new version
splitting utility</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/database/settings/utils/splitPostgresMajorMinorVersions/index.ts

- Export `splitPostgresMajorMinorVersions` function


</details>


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

</tr>

<tr>
  <td>
    <details>

<summary><strong>splitPostgresMajorMinorVersions.ts</strong><dd><code>Implement
Postgres version splitting utility</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>


dashboard/src/features/orgs/projects/database/settings/utils/splitPostgresMajorMinorVersions/splitPostgresMajorMinorVersions.ts

<li>Implement <code>splitPostgresMajorMinorVersions</code> function<br>
<li> Split version string into major and minor parts


</details>


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

</tr>
</table></td></tr><tr><td><strong>Tests</strong></td><td><table>
<tr>
  <td>
    <details>

<summary><strong>splitPostgresMajorMinorVersions.test.ts</strong><dd><code>Add
tests for Postgres version splitting utility</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/database/settings/utils/splitPostgresMajorMinorVersions/splitPostgresMajorMinorVersions.test.ts

<li>Add tests for <code>splitPostgresMajorMinorVersions</code>
function<br> <li> Test regular and beta Postgres versions


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>twenty-pets-brake.md</strong><dd><code>Add changeset
for version bump and fix description</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/twenty-pets-brake.md

<li>Add changeset for minor version bump<br> <li> Describe fix for
Postgres beta version splitting


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>

---------

Co-authored-by: robertkasza <167509084+robertkasza@users.noreply.github.com>
2025-02-27 00:27:19 +01:00
Nuno Pato
56ebb1f719 fix: dashboard: run analytics only in prod (#3220)
### **User description**
- only run analytics on production


___

### **PR Type**
Enhancement


___

### **Description**
- Run analytics only in production environment

- Add `isDevOrStaging` check for analytics rendering

- Update `Analytics` component conditional rendering

- Include changeset for version bump


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>_app.tsx</strong><dd><code>Conditionally Render
Analytics Component</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

dashboard/src/pages/_app.tsx

<li>Import <code>isDevOrStaging</code> helper function<br> <li> Modify
<code>Analytics</code> component rendering condition<br> <li> Add check
to prevent analytics in dev/staging


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>short-games-sniff.md</strong><dd><code>Add Changeset
for Version Bump</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.changeset/short-games-sniff.md

<li>Add new changeset file<br> <li> Specify minor version bump for
'@nhost/dashboard'<br> <li> Include fix description for analytics


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-02-25 22:13:57 -01:00
github-actions[bot]
2b6a4adf40 chore: update versions (#3202)
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@8.0.5

### Patch Changes

-   Updated dependencies [6b8163d]
    -   @nhost/nhost-js@3.2.5

## @nhost/google-translation@0.2.2

### Patch Changes

- 6b8163d: fix(nhost-js) update service URL generation for local
environments

## @nhost/react-apollo@17.0.1

### Patch Changes

-   @nhost/apollo@8.0.5
-   @nhost/react@3.10.1

## @nhost/react-urql@14.0.1

### Patch Changes

-   @nhost/react@3.10.1

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

### Patch Changes

- 6b8163d: fix(nhost-js) update service URL generation for local
environments

## @nhost/nextjs@2.2.4

### Patch Changes

-   @nhost/react@3.10.1

## @nhost/nhost-js@3.2.5

### Patch Changes

- 6b8163d: fix(nhost-js) update service URL generation for local
environments
-   Updated dependencies [6b8163d]
    -   @nhost/hasura-auth-js@2.10.1

## @nhost/react@3.10.1

### Patch Changes

-   Updated dependencies [6b8163d]
    -   @nhost/nhost-js@3.2.5

## @nhost/vue@2.9.2

### Patch Changes

-   Updated dependencies [6b8163d]
    -   @nhost/nhost-js@3.2.5

## @nhost/dashboard@2.21.0

# @nhost/dashboard

## 2.17.0

### Minor Changes

-   fd59918: fix: redirect to 404 with nhost cli dashboard

## 2.16.0

### Minor Changes

-   f8e6b61: fix: can add rule groups in table permissions
-   9e404c8: fix: not redirect to 404 page if using local Nhost backend
-   ac4aa01: fix: can delete column in database page
-   4385524: fix: update url to check service health in local dashboard

### Patch Changes

-   @nhost/react-apollo@16.0.1
-   @nhost/nextjs@2.2.2

## 2.15.0

### Minor Changes

- f1052a8: fix: improve stability of the dashboard when pausing projects
-   30daa41: fix: update links to docs in overview page
-   7537237: feat: add image preview toggle in storage

## 2.14.0

### Minor Changes

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

### Patch Changes

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

## 2.13.0

### Minor Changes

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

### Patch Changes

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

## 2.12.0

### Minor Changes

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

### Patch Changes

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

## 2.11.3

### Patch Changes

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

## 2.11.2

### Patch Changes

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

## 2.11.1

### Patch Changes

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

## 2.11.0

### Minor Changes

-   cea3ef5: Feat: add org and project placeholders

## 2.10.0

### Minor Changes

-   86ecf27: feat: add support for additional metrics in overview
- 21708be: feat: dashboard: add support for storage buckets to AI
assistants

## 1.30.0

### Minor Changes

- 50441a8: feat: add ui for project autoscaler settings and run services
autoscaler settings

## 1.29.0

### Minor Changes

-   55d8bb5: feat: integrate turnstile for signup verification
-   2a2e54c: fix: update docs url in run services form tooltip
- 18f942f: fix: display long error messages in error toast without
overflow

### Patch Changes

-   @nhost/react-apollo@13.0.0
-   @nhost/nextjs@2.1.22

## 1.28.2

### Patch Changes

- 52a38fe: chore: update dependencies to address security
vulnerabilities
-   Updated dependencies [52a38fe]
    -   @nhost/nextjs@2.1.21

## 1.28.1

### Patch Changes

-   9735fa2: chore: remove broken link

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

- b519862: fix: get configuration in configuration editor using local
development environment

## 1.18.0

### Minor Changes

- 502abad: feat: add services health checks indicators to the overview
page
-   b3ff6ad: chore: update title text on service status modal
- dbadf59: feat: add project configuration TOML editor to the settings
page

## 1.17.0

### Minor Changes

- 77fba27: fix: postgres version validation when activating ai in ai
settings page
-   ac6d1b6: feat: use name instead of awsName

## 1.16.3

### Patch Changes

- 87a37cf: fix: remove unnecessary isPlatform check from verify button
disable logic on custom domains
    -   @nhost/react-apollo@12.0.2
    -   @nhost/nextjs@2.1.16

## 1.16.2

### Patch Changes

- a9413af: fix: update `GetAllWorkspacesAndProjects` query polling to
use exponential backoff
    -   @nhost/react-apollo@12.0.1
    -   @nhost/nextjs@2.1.15

## 1.16.1

### Patch Changes

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

## 1.16.0

### Minor Changes

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

## 1.15.2

### Patch Changes

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

## 1.15.1

### Patch Changes

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

## 1.15.0

### Minor Changes

-   a7bde37: feat: send metadata in the edit form

### Patch Changes

- 1bc615b: feat: improve error message handling in `ErrorToast`
component
    -   @nhost/react-apollo@11.0.2
    -   @nhost/nextjs@2.1.11

## 1.14.0

### Minor Changes

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

## 1.13.3

### Patch Changes

-   5924bc3: fix: include password in `GetSmtpSettings` query
- c5ad634: fix: resolved an issue where one-click install links were
broken on Safari
- 7278991: fix: update graphql auto-embeddings configuration to use
String type for model field

## 1.13.2

### Patch Changes

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

## 1.13.1

### Patch Changes

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

## 1.13.0

### Minor Changes

-   dd5d262: feat: add model field to the auto-embeddings form
- 09962be: feat: enable settings and run services when running the
dashboard locally
- 9cdecb6: feat: enable users to update their email address from the
account settings page

## 1.12.2

### Patch Changes

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

## 1.12.1

### Patch Changes

- 93ebdf8: fix: use service urls when initilizaing NhostClient running
local dashboard
    -   @nhost/react-apollo@11.0.1
    -   @nhost/nextjs@2.1.10

## 1.12.0

### Minor Changes

- f242e4b: feat: add connect with github to the user's account settings
-   768ca17: chore: update dependencies
- d62bd0f: fix: "Track this" option within the SQL editor now correctly
updates the metadata
- 91c2bb6: feat: refactor sign-in and sign-up pages to enforce email
verification

### Patch Changes

-   943831f: fix: resolve an error toast issue when unpausing a project
-   Updated dependencies [768ca17]
    -   @nhost/react-apollo@11.0.0
    -   @nhost/nextjs@2.1.9

## 1.11.2

### Patch Changes

-   @nhost/react-apollo@10.0.2
-   @nhost/nextjs@2.1.8

## 1.11.1

### Patch Changes

-   981404f: fix: set default value for healthCheck field validation

## 1.11.0

### Minor Changes

- 7789469: chore: upgrade dependency `@graphql-codegen/cli` to `5.0.2`
to address vulnerability
- 6c11b75: feat: add update user displayName section in account settings

### Patch Changes

-   @nhost/react-apollo@10.0.1
-   @nhost/nextjs@2.1.7

## 1.10.0

### Minor Changes

-   49a80c2: chore: update dependencies
-   150c04a: feat: add healthcheck config to run services

### Patch Changes

- e03f141: fix: allow insert, update and delete on tables in `auth` and
`storage` schemas
- 28676f4: feat: add min postgres version check to enable the ai service
-   Updated dependencies [49a80c2]
    -   @nhost/react-apollo@10.0.0
    -   @nhost/nextjs@2.1.6

## 1.9.0

### Minor Changes

-   d86e5c9: feat: add support for filtering the logs using a RegExp

## 1.8.3

### Patch Changes

-   @nhost/react-apollo@9.0.3
-   @nhost/nextjs@2.1.5

## 1.8.2

### Patch Changes

- 6df4f02: fix: use custom error toast and show correct message when
sending an invite

## 1.8.1

### Patch Changes

-   @nhost/react-apollo@9.0.2
-   @nhost/nextjs@2.1.4

## 1.8.0

### Minor Changes

- 713d53c: feat: add catch-all route for workspace/project - useful for
documentation

### Patch Changes

-   3db2999: fix: refresh table list after running SQL using the editor
- 3c4dd55: fix: handle `Error` objects properly in the `ErrorToast`
component
- 92b434e: fix: resolve an issue where the checkbox in the data-grid
header did not select all rows
    -   @nhost/react-apollo@9.0.1
    -   @nhost/nextjs@2.1.3

## 1.7.0

### Minor Changes

-   0d8d0eb: Update docs and dashboard references

## 1.6.9

### Patch Changes

-   @nhost/react-apollo@9.0.0
-   @nhost/nextjs@2.1.2

## 1.6.8

### Patch Changes

-   @nhost/react-apollo@8.0.1
-   @nhost/nextjs@2.1.1

## 1.6.7

### Patch Changes

-   5ef5189: fix: update `@apollo/client` to `3.9.4` to fix a cache bug

## 1.6.6

### Patch Changes

-   3ba485e: fix: added discord.com to connect-src
-   e5bab6a: chore: update dependencies
-   Updated dependencies [b19ffed]
-   Updated dependencies [e5bab6a]
    -   @nhost/nextjs@2.1.0
    -   @nhost/react-apollo@8.0.0

## 1.6.5

### Patch Changes

- ba73bb4: fix: update ErrorToast component to show the internal graphql
error
- d5337ff: fix: utilize accumulator in the creation of validation schema
within data grid utils

## 1.6.4

### Patch Changes

-   7c2a1c2: feat: show error and debug info in the error toast

## 1.6.3

### Patch Changes

-   6b8aad5: fix: add bare nhost.run to CSP

## 1.6.2

### Patch Changes

-   b18edc0: feat: added CSP and X-Frame-Options

## 1.6.1

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
- 3b8473b: chore: update turbo to `1.11.3` and pnpm to `8.10.5` in
Dockerfile
-   Updated dependencies [8d91f71]
    -   @nhost/react-apollo@7.0.2
    -   @nhost/nextjs@2.0.2

## 1.6.0

### Minor Changes

-   3ff1c2b53: fix: show upgrade option for pro projects

## 1.5.0

### Minor Changes

-   c2ef17c0a: feat: add support for new Team plan

## 1.4.0

### Minor Changes

-   7883bbcbd: feat: don't show deprecated plans
- 44be6dc0a: feat: set redirectTo during sign-in to support preview
environments

### Patch Changes

- 3c3594898: fix: allow access to graphite when configured running in
local dashboard
-   32c246b7a: chore: update docs icon

## 1.3.2

### Patch Changes

-   174b4165b: chore: use env variables when running graphql codegen
-   7c977e714: chore: change `Allowed Roles` to `Default Allowed Roles`
-   46f028b9f: fix: remove hardcoded ai version setting

## 1.3.1

### Patch Changes

- af33c21d1: chore: remove backendUrl deprecation notice and remove all
references to `providersUpdated`

## 1.3.0

### Minor Changes

-   04784d880: Fix graphite's default version

## 1.2.0

### Minor Changes

-   5733162ed: feat: add settings and ui for graphite

## 1.1.0

### Minor Changes

-   e2b79b5ec: chore: remove sharp from deps

## 1.0.1

### Patch Changes

-   @nhost/react-apollo@7.0.1
-   @nhost/nextjs@2.0.1

## 1.0.0

### Major Changes

- bc9eff6e4: chore: remove support for using backendUrl when
instantiating the Nhost client

### Patch Changes

-   Updated dependencies [bc9eff6e4]
    -   @nhost/nextjs@2.0.0
    -   @nhost/react-apollo@7.0.0

## 0.21.1

### Patch Changes

-   97ced73a3: fix(dashboard): prevent dashboard from resolving secrets

## 0.21.0

### Minor Changes

- ed1a8d458: Update alert message on increasing PostgreSQL's volume
capacity
-   2e2248fd4: feat(dashboard): add SQL editor

## 0.20.28

### Patch Changes

-   7c2c31082: feat: add support for users to delete their account
    -   @nhost/react-apollo@6.0.1
    -   @nhost/nextjs@1.13.40

## 0.20.27

### Patch Changes

- fa79b7709: chore(dashboard): tweaks and fixes to the service form and
dialog
-   8df84d782: fix(dashboard): allow resetting custom domains
    -   @nhost/react-apollo@6.0.0
    -   @nhost/nextjs@1.13.39

## 0.20.26

### Patch Changes

- 331ba0376: feat(dashboard): add postgres storage capacity modifier in
the settings
-   b7f801874: feat(dashboard): add new settings page for custom domains

## 0.20.25

### Patch Changes

-   @nhost/react-apollo@5.0.38

## 0.20.24

### Patch Changes

-   e10389ecf: fix(dashboard): disable run tab when developing locally
    -   @nhost/react-apollo@5.0.37

## 0.20.23

### Patch Changes

-   c01568a7d: chore(dashboard): show alert to update oauth providers

## 0.20.22

### Patch Changes

-   c3efb7ec8: feat(dashboard): query latest announcement from platform

## 0.20.21

### Patch Changes

-   3e46d3873: chore: update link to node18 announcement

## 0.20.20

### Patch Changes

-   @nhost/react-apollo@5.0.36
-   @nhost/nextjs@1.13.38

## 0.20.19

### Patch Changes

-   75c4c8ae3: feat(dashboard): make env value input multiline

## 0.20.18

### Patch Changes

- 425d485f8: fix(dashboard): make sure dedicated resources pricing
follows total resources

## 0.20.17

### Patch Changes

-   ae324f67f: fix(dashboard): remove unused graphql fields

## 0.20.16

### Patch Changes

-   df5b4302c: chore(dashboard): remove run feature flag
- bf4a1f6c2: feat(dashboard): fetch auth, postgres, hasura and storage
versions from dashboard
- 34fc08ca7: fix(dashboard/run): show correct private registry in
service details
-   885d10620: chore(dashboard): change feedback to contact us

## 0.20.15

### Patch Changes

- ed16c8b5d: feat(run): add a confirmation dialog when deleting a run
service
- 216990888: fix(run): center loading indicator when selecting a project

## 0.20.14

### Patch Changes

-   9fbea9787: feat: add node18 announcement

## 0.20.13

### Patch Changes

- e84acf469: fix(run): handle subdomain undefined error when creating a
new service

## 0.20.12

### Patch Changes

-   b7c799d62: feat(run): add dialog to copy registry and URLs

## 0.20.11

### Patch Changes

-   8903e6abd: fix(dashboard): show correct egress limit in usage stats

## 0.20.10

### Patch Changes

- 666a75a23: feat(dashboard): add functions execution time and egress
volume to usage stats

## 0.20.9

### Patch Changes

-   5e1e80aa8: fix(dashboard): show correct locales in user details
    -   @nhost/react-apollo@5.0.35
    -   @nhost/nextjs@1.13.37

## 0.20.8

### Patch Changes

-   @nhost/react-apollo@5.0.34
-   @nhost/nextjs@1.13.36

## 0.20.7

### Patch Changes

-   4a7ede11e: fix: distinguish files that were not uploaded
- 202b64723: feat(nhost-run): add support for one-click-install run
services
- 074a0fa11: feat(dashboard): add settings toggle to enable/disable
antivirus
    -   @nhost/react-apollo@5.0.33
    -   @nhost/nextjs@1.13.35

## 0.20.6

### Patch Changes

-   b20761e97: feat(services): add pricing info and confirmation dialog
-   90df6d81d: fix(services): handle null values when editing a service
-   aa8508467: fix: query service logs correctly
    feat: enable multiline support for environment value input

## 0.20.5

### Patch Changes

-   8d7f84b8d: fix: make announcement adapt to theme

## 0.20.4

### Patch Changes

-   3b75bfce2: fix: make announcement close properly
- f49819075: fix: show correct values when dedicated resources are
disabled

## 0.20.3

### Patch Changes

-   e643bd362: fix(services): fix errors when config is null
-   bcdab66bf: feat: add annoucement for nhost run
-   f967a2e59: added note about storage not being able to be downsized
-   311c7756d: chore(services): consistent naming for compute

## 0.20.2

### Patch Changes

-   9073182d5: chore(dashboard): bump `turbo` to 1.10.11
-   ece717d6e: feat(logs): show services in the logs page
- 82b335311: feat(metrics): change grafana link to point to the
dashboards
- b135ef695: fix(services): set command as optional and set min replicas
to 0

## 0.20.1

### Patch Changes

-   3d5c34f4c: fix(auth): fix users pagination limit

## 0.20.0

### Minor Changes

-   c99d117d1: feat(services): add support for custom services

## 0.19.2

### Patch Changes

-   face99ccd: chore(deps): bump turbo version
-   cfe527307: style: tweak pull config warning in dark mode
- a9d7da8af: chore(deps): update dependency @types/pluralize to ^0.0.30
-   9aa4371ef: chore: add hasura-auth version 0.21.2
- d14e112bf: chore(deps): update dependency prettier-plugin-tailwindcss
to ^0.4.0
-   d3e8bb94a: chore(deps): update dependency vite-plugin-dts to v3

## 0.19.1

### Patch Changes

-   @nhost/react-apollo@5.0.32
-   @nhost/nextjs@1.13.34

## 0.19.0

### Minor Changes

- 9c61c69a7: chore(dashboard):add postgres 14.6-20230705-1 to the
version selector

### Patch Changes

-   47bda15ff: feat(settings): add warning to pull config

## 0.18.0

### Minor Changes

- ee0b9b8ed: chore(dashboard):add hasura v2.28.2 and v2.29.0 to the
version selector

## 0.17.20

### Patch Changes

-   @nhost/react-apollo@5.0.31
-   @nhost/nextjs@1.13.33

## 0.17.19

### Patch Changes

-   f866120a6: fix(users): use the password length from the config

## 0.17.18

### Patch Changes

-   @nhost/react-apollo@5.0.30
-   @nhost/nextjs@1.13.32

## 0.17.17

### Patch Changes

-   ea7b102c0: fix(pat): highlight expired tokens

## 0.17.16

### Patch Changes

- b3b64a3b7: chore(deps): bump `@types/react` to `v18.2.14` and
`@types/react-dom` to `v18.2.6`
-   32b221f94: chore(deps): bump `graphiql` to `v3`
-   3a56c12df: chore(deps): bump `turbo` to `v1.10.6`
-   Updated dependencies [b3b64a3b7]
    -   @nhost/react-apollo@5.0.29
    -   @nhost/nextjs@1.13.31

## 0.17.15

### Patch Changes

-   f41fdc12a: chore(deps): bump `turbo` to `1.10.5`
-   6199c1c55: fix(projects): don't redirect to 404 page
-   Updated dependencies [07a45fde0]
    -   @nhost/react-apollo@5.0.28
    -   @nhost/nextjs@1.13.30

## 0.17.14

### Patch Changes

- 80b22724d: chore(deps): bump `@types/react` to `v18.2.13`,
`@types/react-dom` to `v18.2.6` and `@storybook/testing-library` to
`v0.2.0`

## 0.17.13

### Patch Changes

-   cc02902cb: chore(docs): update environment variable documentation

## 0.17.12

### Patch Changes

-   660d339e1: fix(storybook): don't break storybook
-   660d339e1: fix(tests): prevent warnings during tests
    -   @nhost/react-apollo@5.0.27
    -   @nhost/nextjs@1.13.29

## 0.17.11

### Patch Changes

- bd4d0c270: chore(dashboard):add postgres 14.6-20230613-1 to the
version selector

## 0.17.10

### Patch Changes

-   c8c2a10b2: fix(database): don't break the password reset flow
- e70b45498: chore(deps): bump `@types/react` to `v18.2.12` and
`@types/react-dom` to `v18.2.5`

## 0.17.9

### Patch Changes

- 842055099: chore(deps): bump `turbo` to `v1.10.3` and `pnpm` to
`v8.6.2`
- fd12aa0a8: chore(projects): remove the postgres password input from
the project creation screen
-   022b76e78: chore(deps): bump `@types/react` to `v18.2.11`
-   3555ab2b7: chore(deps): bump `vitest` monorepo to `v0.32.0`
-   c43e54922: feat(backups): add download button to backups

## 0.17.8

### Patch Changes

-   d0457fe5c: feat(settings): improve the dashboard and config parity
    -   @nhost/react-apollo@5.0.26
    -   @nhost/nextjs@1.13.28

## 0.17.7

### Patch Changes

-   4f0368b95: fix(account): don't break account settings page

## 0.17.6

### Patch Changes

- 64a8f41d0: chore(resources): lower the maximum allowed resources per
service

## 0.17.5

### Patch Changes

-   @nhost/react-apollo@5.0.25
-   @nhost/nextjs@1.13.27

## 0.17.4

### Patch Changes

- 9b1d0f7a5: fix(deployments): use correct timestamp for deployment
details
-   6d2963ffa: chore(deps): bump `@types/react` to `v18.2.8`
- 8871267b9: chore(deps): downgrade `pnpm` to `v8.5.1` because of no
Turborepo support

## 0.17.3

### Patch Changes

-   01eeef9de: chore(misc): under the hood improvements
- 21e13db05: chore(deps): bump `@types/react` to `v18.2.7` and `turbo`
to `v1.10.1`
- f16433ae6: chore(secrets): allow empty secrets and environment
variables
-   aa3c62989: chore(cli): bump Nhost CLI version to v1.0
    -   @nhost/react-apollo@5.0.24
    -   @nhost/nextjs@1.13.26

## 0.17.2

### Patch Changes

-   88a4983f: chore(misc): under the hood improvements

## 0.17.1

### Patch Changes

-   9b0d4dde: feat(secrets): enable secrets

## 0.17.0

### Minor Changes

-   15d84a19: Add postgres 14.6-20230525

## 0.16.14

### Patch Changes

-   4c626174: chore: updated import paths, improved directory structure
-   cc047b71: chore(deps): bump `@fontsource` monorepo to `v5.0.0`
-   99edd012: feat(account): add support for personal access tokens

## 0.16.13

### Patch Changes

-   78c7109c: feat(settings): allow selecting service versions

## 0.16.12

### Patch Changes

- 399009d6: fix(gql): don't enter an infinite loop when fetching remote
app data
- 329e5a91: fix(deployments): use the same sorting of deployments
everywhere
- 6d559d6e: chore(settings): add under the hood improvements to the
settings page
- 12eb236c: chore(deps): bump `prettier-plugin-tailwindcss` to `v0.3.0`
-   f9b81a2a: chore(deps): bump `turbo` to `v1.9.8`
-   1345741b: fix(projects): don't redirect to 404 on project creation
-   Updated dependencies [7fea29a8]
    -   @nhost/react-apollo@5.0.23
    -   @nhost/nextjs@1.13.25

## 0.16.11

### Patch Changes

- 1230b722: fix(projects): don't redirect to 404 on when the project is
renamed
    -   @nhost/react-apollo@5.0.22
    -   @nhost/nextjs@1.13.24

## 0.16.10

### Patch Changes

-   Updated dependencies [da03bf39]
    -   @nhost/react-apollo@5.0.21
    -   @nhost/nextjs@1.13.23

## 0.16.9

### Patch Changes

- 349aac36: fix(settings): use region domain when constructing the
postgres connection string

## 0.16.8

### Patch Changes

- 20fb69fa: chore(projects): change the way how API URLs are constructed

## 0.16.7

### Patch Changes

- 49f9b837: chore(docker): bump `pnpm` to `v8.4.0` and `turbo` to
`v1.9.3`
- 3f478a4e: chore(deps): bump `vitest` to `v0.31.0`, `@types/react` to
`v18.2.6` and `@types/react-dom` to `v18.2.4`

## 0.16.6

### Patch Changes

- d926f156: fix(projects): redirect to 404 when an invalid project is
opened
- 49b99728: fix(projects): disable features for non-owner members of
workspaces

## 0.16.5

### Patch Changes

-   12e2855f: chore(deps): bump `jsdom` to v22
-   e4972b83: feat(metrics): add Grafana page

## 0.16.4

### Patch Changes

- 3f396a9e: fix(projects): unpause after upgrading a paused project to
pro
- 3f396a9e: fix(projects): don't redirect to 404 page after project
creation

## 0.16.3

### Patch Changes

-   Updated dependencies [90c60311]
    -   @nhost/react-apollo@5.0.20
    -   @nhost/nextjs@1.13.22

## 0.16.2

### Patch Changes

-   0f34f0c6: fix(projects): disallow downgrading to free plan
- 8da291ad: chore(deps): bump `@types/react` to v18.2.0 and
`@types/react-dom` to v18.2.1

## 0.16.1

### Patch Changes

- adc828a5: fix(gql): don't enter an infinite loop when fetching remote
app data

## 0.16.0

### Minor Changes

-   2fb1145f: feat(compute): add support for replicas

### Patch Changes

- d8ceccec: chore(env): remove deprecated `NHOST_BACKEND_URL`
environment variable

## 0.15.2

### Patch Changes

-   84b84ab7: fix(projects): filter projects by workspace

## 0.15.1

### Patch Changes

-   2faf7907: chore(deps): bump `graphql-request` to v6
-   f1b5a944: chore(deps): bump `@vitejs/plugin-react` to v4
-   7f1785ac: chore(deps): bump `@types/react` to v18.0.37
    -   @nhost/react-apollo@5.0.19

## 0.15.0

### Minor Changes

-   85889ee8: feat(dashboard): add Compute management to the settings

## 0.14.8

### Patch Changes

-   668c8771: chore(dialogs): unify dialog management of payment dialogs

## 0.14.7

### Patch Changes

-   d4ccc656: chore: cleanup unused code
    -   @nhost/react-apollo@5.0.18
    -   @nhost/nextjs@1.13.21

## 0.14.6

### Patch Changes

-   b299cfc9: chore(deps): bump `vitest` to v0.30.0
-   411cb65b: chore(projects): refactor workspace and project hooks
- 43b1b144: chore(deps): bump `@types/react` to v18.0.34 and
`@types/react-dom` to v18.0.11
-   Updated dependencies [43b1b144]
    -   @nhost/react-apollo@5.0.17
    -   @nhost/nextjs@1.13.20

## 0.14.5

### Patch Changes

-   ba0d57ee: fix(i18n): revert i18n library
-   3328ed05: feat(projects): improve overview when there is an error

## 0.14.4

### Patch Changes

-   5e0920ba: chore(deps): bump `next-seo` to v6
-   706c9dc3: chore(deps): bump `@types/react` to 18.0.33
-   99f8f6b3: feat(metrics): show metrics on the overview

## 0.14.3

### Patch Changes

-   @nhost/react-apollo@5.0.16

## 0.14.2

### Patch Changes

-   3cb67300: fix(logs): don't break UI when clearing time picker
-   7453bf3b: feat(projects): show project creator info
-   c166dad0: chore(tests): improve auth page tests
-   6a290bb2: chore(deps): bump `@types/react` to 18.0.32

## 0.14.1

### Patch Changes

-   @nhost/react-apollo@5.0.15
-   @nhost/nextjs@1.13.19

## 0.14.0

### Minor Changes

-   6e1f03ea: feat(dashboard): add support for the Azure AD provider

### Patch Changes

-   1bd2c373: chore(deps): bump `turbo` to 1.8.6
-   d329b621: chore(deps): bump `@types/react` to 18.0.30
-   cb248f0d: fix(tests): avoid name collision in database tests
-   867c8076: chore(deps): bump `@types/react` to 18.0.29

## 0.13.10

### Patch Changes

- e93b06ab: fix(dashboard): remove left margin from workspace list on
mobile
-   1c4806bf: chore(deps): bump `sharp` to 0.32.0
    -   @nhost/react-apollo@5.0.14
    -   @nhost/nextjs@1.13.18

## 0.13.9

### Patch Changes

-   912ed76c: chore(dashboard): bump `@apollo/client` to 3.7.10
-   Updated dependencies [912ed76c]
    -   @nhost/react-apollo@5.0.13

## 0.13.8

### Patch Changes

-   7c127372: chore(dashboard): bump `react-error-boundary` to v4

## 0.13.7

### Patch Changes

- 9130ab12: chore(dashboard): bump `yup` to v1 and `@hookform/resolvers`
to v3

## 0.13.6

### Patch Changes

- 253dd235: using new mutation to create projects + refactor Create
Project page.

## 0.13.5

### Patch Changes

-   @nhost/react-apollo@5.0.12
-   @nhost/nextjs@1.13.17

## 0.13.4

### Patch Changes

-   b48bc034: fix(dashboard): disable new users
-   798e591b: fix(dashboard): show correct date in data grid

## 0.13.3

### Patch Changes

-   bfb4c1a6: chore(dashboard): remove `useAxios` property
-   d8d8394b: Dashboard: allow to override hasura admin secret in docker
-   Updated dependencies [ce1ee40d]
    -   @nhost/nextjs@1.13.16
    -   @nhost/react-apollo@5.0.11

## 0.13.2

### Patch Changes

-   beed2eba: Fix docker entrypoint for dashboard
- 2c8559a3: fix(dashboard): refresh project list after deleting a
project
-   4329d048: chore(dashboard): bump `graphiql` dependencies

## 0.13.1

### Patch Changes

-   cbb1fc5b: chore(dashboard): cleanup GraphQL operations

## 0.13.0

### Minor Changes

-   088584e7: feat(dashboard): add support for custom local subdomains

### Patch Changes

-   2ac90dfd: fix(dashboard): improve mobile responsive layout
-   Updated dependencies [f375eacc]
    -   @nhost/nextjs@1.13.15
    -   @nhost/react-apollo@5.0.10

## 0.12.4

### Patch Changes

-   @nhost/react-apollo@5.0.9
-   @nhost/nextjs@1.13.14

## 0.12.3

### Patch Changes

-   2b1338f7: chore(dashboard): bump `turbo` to 1.8.3
- 5223ee93: fix(dashboard): show correct deployment status on the main
page
-   850a049c: chore(deps): update docker/build-push-action action to v4
-   Updated dependencies [850a049c]
    -   @nhost/nextjs@1.13.13
    -   @nhost/react-apollo@5.0.8

## 0.12.2

### Patch Changes

-   4bf40995: chore(deps): bump `typescript` to `4.9.5`
-   8bb097c9: chore(deps): bump `vitest`
- 35d52aab: chore(deps): replace `cross-fetch` with `isomorphic-unfetch`
-   Updated dependencies [4bf40995]
-   Updated dependencies [8bb097c9]
-   Updated dependencies [35d52aab]
    -   @nhost/react-apollo@5.0.7
    -   @nhost/nextjs@1.13.12

## 0.12.1

### Patch Changes

-   c96d7ccd: fix(dashboard): fix docker builds

## 0.12.0

### Minor Changes

-   d1671210: feat(dashboard): use mimir to manage project configuration

### Patch Changes

-   f65e4de9: chore(deps): bump @graphql-codegen monorepo to v3

## 0.11.20

### Patch Changes

-   4b4f0d01: chore(dashboard): improve dialog management

## 0.11.19

### Patch Changes

-   @nhost/react-apollo@5.0.6
-   @nhost/nextjs@1.13.11

## 0.11.18

### Patch Changes

-   01318860: fix(nhost-js): use correct URL for functions requests
-   Updated dependencies [01318860]
    -   @nhost/react-apollo@5.0.5
    -   @nhost/nextjs@1.13.10

## 0.11.17

### Patch Changes

-   f673adea: fix(dashboard): set correct Content-Type for user creation
-   445d8ef4: chore(deps): bump `@nhost/react-apollo` to 5.0.4
-   445d8ef4: chore(deps): bump `@nhost/nextjs` to 1.13.9
- 0368663d: fix(dashboard): allow permission editing for auth and
storage schemas
-   Updated dependencies [445d8ef4]
-   Updated dependencies [445d8ef4]
    -   @nhost/react-apollo@5.0.4
    -   @nhost/nextjs@1.13.9

## 0.11.16

### Patch Changes

-   b755e908: fix(dashboard): use correct date for last seen
-   2d9145f9: chore(deps): revert GraphQL client
- 1ddf704c: fix(dashboard): don't show false positive message for failed
user creation
    -   @nhost/react-apollo@5.0.3
    -   @nhost/nextjs@1.13.8

## 0.11.15

### Patch Changes

-   @nhost/react-apollo@5.0.2
-   @nhost/nextjs@1.13.7

## 0.11.14

### Patch Changes

- 2cc18dcb: fix(dashboard): prevent permission editor dropdown from
being always open

## 0.11.13

### Patch Changes

- 3343a363: chore(dashboard): bump `@testing-library/react` to v14 and
`@testing-library/dom` to v9
    -   @nhost/react-apollo@5.0.1
    -   @nhost/nextjs@1.13.6

## 0.11.12

### Patch Changes

- 87eda76e: chore(dashboard): bump `@types/react` to v18.0.28 and
`@types/react-dom` to v18.0.11
-   6f0ac570: feat(dashboard): show dashboard version in account menu

## 0.11.11

### Patch Changes

-   bf1e4071: chore(dashboard): bump `react-is` version to `18.2.0`
-   Updated dependencies [bf1e4071]
-   Updated dependencies [5013213b]
    -   @nhost/nextjs@1.13.5
    -   @nhost/react-apollo@4.13.5

## 0.11.10

### Patch Changes

- a37a430b: fix(dashboard): don't break UI when deployments are
unavailable
    -   @nhost/react-apollo@4.13.4
    -   @nhost/nextjs@1.13.4

## 0.11.9

### Patch Changes

-   7b970e68: fix(dashboard): fix header link color

## 0.11.8

### Patch Changes

- f33242f2: feat(dashboard): add new sign up, sign in and reset password
pages

## 0.11.7

### Patch Changes

-   e9c8909c: fix(dashboard): use correct theme color in dark mode

## 0.11.6

### Patch Changes

-   902f486b: fix(dashboard): re-enable Hasura on logs page

## 0.11.5

### Patch Changes

-   1f9720fa: fix(dashboard): apply select permissions properly

## 0.11.4

### Patch Changes

-   deb14b51: fix(dashboard): don't break billing form

## 0.11.3

### Patch Changes

-   @nhost/react-apollo@4.13.3
-   @nhost/nextjs@1.13.3

## 0.11.2

### Patch Changes

-   f143e51d: chore(dashboard): pin Turborepo to 1.6.3

## 0.11.1

### Patch Changes

-   c2b5a41a: chore(dashboard): select system colors by default

## 0.11.0

### Minor Changes

-   1ebaf429: feat(dashboard): introduce Dark Mode 🌚

### Patch Changes

- 63b445c4: fixed duplicated logs bug and made to date count during live
mode

## 0.10.1

### Patch Changes

-   e146d32e: chore(deps): update dependency @types/react to v18.0.27
-   59347fcd: correct allowed role name
-   5b65cac9: updated authentication documentation
-   963f9b5e: feat(dashboard): include project info in feedback

## 0.10.0

### Minor Changes

-   ed4c7801: chore(dashboard): remove Functions section

## 0.9.10

### Patch Changes

-   4e2f8ccd: fix(dashboard): don't break Auth page in local mode

## 0.9.9

### Patch Changes

-   31abbe5f: fix(dashboard): enable toggle when settings are filled in

## 0.9.8

### Patch Changes

- 5bdd31ad: chore(dashboard): list fewer images per page on the Storage
page
- 5121851c: fix(dashboard): don't throw validation error for valid
permission rules

## 0.9.7

### Patch Changes

-   c126b20d: fix(dashboard): correct redeployment button

## 0.9.6

### Patch Changes

-   36c3519c: feat(dashboard): retrigger deployments

## 0.9.5

### Patch Changes

- 200e9f77: chore(deps): update dependency @types/react-dom to v18.0.10
-   Updated dependencies [200e9f77]
    -   @nhost/nextjs@1.13.2
    -   @nhost/react-apollo@4.13.2

## 0.9.4

### Patch Changes

- dbd3ded5: fix(dashboard): workspaces creation, new form, correct
redirects.

## 0.9.3

### Patch Changes

-   85f0f943: fix(dashboard): don't break the table creation process

## 0.9.2

### Patch Changes

-   Updated dependencies [d42c27ae]
-   Updated dependencies [927be4a2]
    -   @nhost/nextjs@1.13.1
    -   @nhost/react-apollo@4.13.1

## 0.9.1

### Patch Changes

-   d0f80811: fix(dashboard): don't show error when signing out the user

## 0.9.0

### Minor Changes

- d92891b2: feat(dashboard): add Permission Editor to the Database
section

### Patch Changes

-   3d379128: fix(dashboard): create new user
    -   @nhost/react-apollo@4.13.0
    -   @nhost/nextjs@1.13.0

## 0.8.1

### Patch Changes

-   7cadd944: fix(dashboard): display Twitter provider settings

## 0.8.0

### Minor Changes

-   9a1aa7bb: add functions to the log dashboard
-   f29abe62: feat(dashboard): Users Management v2

### Patch Changes

-   7766624b: feat(dashboard): add JWT secret editor modal
    -   @nhost/react-apollo@4.12.1
    -   @nhost/nextjs@1.12.1

## 0.7.13

### Patch Changes

-   dd0738d5: fix(dashboard): provisioning status polling

## 0.7.12

### Patch Changes

-   b21222b3: chore(deps): update dependency @types/node to v16
-   9e0486a3: fix(dashboard): close modals when navigating
-   Updated dependencies [b21222b3]
-   Updated dependencies [65687bee]
-   Updated dependencies [54df0df4]
    -   @nhost/nextjs@1.12.0
    -   @nhost/react-apollo@4.12.0

## 0.7.11

### Patch Changes

-   d6527122: fix(dashboard): use correct service URLs

## 0.7.10

### Patch Changes

-   Updated dependencies [57db5b83]
    -   @nhost/nextjs@1.11.0
    -   @nhost/nhost-js@1.7.0
    -   @nhost/react@0.17.0
    -   @nhost/react-apollo@4.11.0

## 0.7.9

### Patch Changes

- a6d31dc2: fix(dashboard): don't break the UI when project is not
loaded yet

## 0.7.8

### Patch Changes

- 7f251111: Use `NhostProvider` instead of `NhostReactProvider` and
`NhostNextProvider`

    `NhostReactProvider` and `NhostNextProvider` are now deprecated

-   f4d70f88: fix(dashboard): do not break when region is nullish

- 4a9471cc: Windows Live Provider displayed link updated to match
backend url

- 594488e4: fix(dashboard): do not show error when submitting Apple
provider settings

-   Updated dependencies [7f251111]
    -   @nhost/nextjs@1.10.0
    -   @nhost/react@0.16.0
    -   @nhost/react-apollo@4.10.0

## 0.7.7

### Patch Changes

-   80b604ad: fix(dashboard): use correct Hasura slug

## 0.7.6

### Patch Changes

-   2d2beb53: fix(dashboard): prevent error on GraphQL page
-   ac8efcbd: chore(dashboard): deprecate old DNS name

## 0.7.5

### Patch Changes

-   132a4f4b: chore(dashboard): remove unused dependencies
- 132a4f4b: chore(deps): synchronize @types/react-dom and @types/react
versions
-   db57572f: fix(dashboard): correct section paddings when no env vars
-   Updated dependencies [132a4f4b]
    -   @nhost/react@0.15.2
    -   @nhost/react-apollo@4.9.2
    -   @nhost/nextjs@1.9.3

## 0.7.4

### Patch Changes

-   34d85e54: chore(deps): update dependency critters to ^0.0.16
- 9b93cf95: chore(deps): update dependency @netlify/functions to ^0.11.0
-   e0439030: chore(deps): update dependency @types/react-dom to v18.0.9
-   Updated dependencies [82124329]
    -   @nhost/nextjs@1.9.2

## 0.7.3

### Patch Changes

-   a1193da4: fix(dashboard): remove character limit from env var inputs

## 0.7.2

### Patch Changes

-   44f13f62: chore(dashboard): cleanup unused files

## 0.7.1

### Patch Changes

- e01cb2ed: chore(dashboard): change settings sidebar menu item density

## 0.7.0

### Minor Changes

- db342f45: chore(dashboard): refactor Roles and Permissions settings
sections
-   8b9fa0b1: feat(dashboard): add Environment Variables page

### Patch Changes

-   Updated dependencies [66b4f3d0]
-   Updated dependencies [2e6923dc]
-   Updated dependencies [ef117c28]
-   Updated dependencies [aebb8225]
    -   @nhost/core@0.9.4
    -   @nhost/nhost-js@1.6.2
    -   @nhost/nextjs@1.9.1
    -   @nhost/react@0.15.1
    -   @nhost/react-apollo@4.9.1

## 0.6.0

### Minor Changes

-   eef9c914: feat(dashboard): add Roles and Permissions page

## 0.5.0

### Minor Changes

-   a48dd5bf: feat(dashboard): make backend port configurable

## 0.4.3

### Patch Changes

-   5de965d9: fix(dashboard): alphabetic ordering of providers
-   b9087a4a: fix(dashboard): console -> dashboard terminology
-   ca012d79: docs(workos): WorkOS Docs

## 0.4.2

### Patch Changes

-   89bd37bc: fix(dashboard): correct redirect URL input opacity
-   Updated dependencies [4601d84e]
-   Updated dependencies [843087cb]
    -   @nhost/react@0.15.0
    -   @nhost/nextjs@1.9.0
    -   @nhost/react-apollo@4.9.0

## 0.4.1

### Patch Changes

-   766cb612: fix(dashboard): correct redirect URL for oauth providers
-   Updated dependencies [53bdc294]
-   Updated dependencies [f2aaff05]
    -   @nhost/nextjs@1.8.3
    -   @nhost/core@0.9.3
    -   @nhost/react@0.14.3
    -   @nhost/nhost-js@1.6.1
    -   @nhost/react-apollo@4.8.3

## 0.4.0

### Minor Changes

-   9211743d: feat(dashboard): migrate Settings page features

## 0.3.0

### Minor Changes

-   73da6a67: fix(dashboard): avoid using BACKEND_URL locally

## 0.2.0

### Minor Changes

-   db118f97: feat(dashboard): generate Docker image

## @nhost/docs@2.28.1

### Patch Changes

- 6b8163d: fix(nhost-js) update service URL generation for local
environments

## @nhost-examples/cli@0.3.18

### Patch Changes

-   Updated dependencies [6b8163d]
    -   @nhost/nhost-js@3.2.5

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

### Patch Changes

-   @nhost/react@3.10.1
-   @nhost/react-apollo@17.0.1

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

### Patch Changes

- 6b8163d: fix(nhost-js) update service URL generation for local
environments
    -   @nhost/react@3.10.1

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

### Patch Changes

-   @nhost/react@3.10.1
-   @nhost/react-urql@14.0.1

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

### Patch Changes

-   Updated dependencies [6b8163d]
    -   @nhost/nhost-js@3.2.5

## @nhost-examples/nextjs@0.4.4

### Patch Changes

-   @nhost/react@3.10.1
-   @nhost/react-apollo@17.0.1
-   @nhost/nextjs@2.2.4

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

### Patch Changes

-   Updated dependencies [6b8163d]
    -   @nhost/nhost-js@3.2.5

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

### Patch Changes

- 6b8163d: fix(nhost-js) update service URL generation for local
environments
-   Updated dependencies [6b8163d]
    -   @nhost/nhost-js@3.2.5

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

### Patch Changes

-   @nhost/react@3.10.1
-   @nhost/react-apollo@17.0.1

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

### Patch Changes

-   @nhost/react@3.10.1

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

### Patch Changes

-   @nhost/react@3.10.1
-   @nhost/react-apollo@17.0.1

## @nhost-examples/seed-data-storage@0.0.5

### Patch Changes

- 6b8163d: fix(nhost-js) update service URL generation for local
environments

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

### Patch Changes

-   Updated dependencies [6b8163d]
    -   @nhost/nhost-js@3.2.5
    -   @nhost/apollo@8.0.5
    -   @nhost/vue@2.9.2

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

### Patch Changes

-   @nhost/apollo@8.0.5
-   @nhost/vue@2.9.2

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-25 15:11:23 -01:00
Nuno Pato
6cc8f954e1 chore: dashboard: add org slug and project subdomain properites to se… (#3216)
### **User description**
- adds 2 properties (org slug and project subdomain) to the page event
on segment


___

### **PR Type**
Enhancement


___

### **Description**
- Add organization slug and project subdomain to Segment page event

- Update Analytics component to include new properties

- Refactor route change handling for analytics

- Add changeset for minor version bump


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>analytics.tsx</strong><dd><code>Enhance Analytics
component with org and project data</code>&nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

dashboard/src/components/analytics/analytics.tsx

<li>Import hooks for current org and project<br> <li> Add org slug and
project subdomain to analytics page event<br> <li> Refactor route change
handling with custom properties<br> <li> Update dependency array in
useEffect


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>wild-rings-count.md</strong><dd><code>Add changeset for
analytics enhancement</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

.changeset/wild-rings-count.md

<li>Add changeset file for minor version bump<br> <li> Include
description of changes to Segment page event


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-02-25 13:59:30 -01:00
David BM
1821df7a96 fix (ci): github actions signature checks when installing packages (#3217)
### **User description**
Should fix `dashboard: release form` action

Reference:
https://vercel.com/guides/corepack-errors-github-actions#how-to-fix-it


___

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


___

### **Description**
- Update Corepack to latest version

- Enable Corepack for package management

- Install and verify Corepack and pnpm versions

- Improve CI workflow for package installation


___



### **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>action.yaml</strong><dd><code>Update CI workflow with
Corepack and version checks</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

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

<li>Add step to install and enable latest Corepack<br> <li> Display
Corepack and pnpm versions for verification<br> <li> Improve package
installation process in CI


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-02-25 15:06:16 +01:00
David BM
ab8a55ede4 chore: update nix config to use pnpm10 (#3218)
### **PR Type**
Enhancement


___

### **Description**
- Update pnpm version from 9 to 10

- Modify flake.nix to use pnpm_10


___



### **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>flake.nix</strong><dd><code>Upgrade pnpm from version 9
to 10</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>

flake.nix

<li>Replace <code>pnpm_9</code> with <code>pnpm_10</code> in
nativeBuildInputs<br> <li> Update <code>pnpm_9</code> to
<code>pnpm_10</code> in devShells buildInputs


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-02-25 14:35:40 +01:00
David BM
39eb70678b fix (dashboard): database storage capacity setting paywall (#3214)
### **PR Type**
Enhancement


___

### **Description**
- Add paywall for database storage capacity setting

- Implement UpgradeNotification component for free projects

- Replace legacy plan check with isFreeProject flag

- Add TransferProjectDialog for project upgrades


___



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

<summary><strong>DatabaseStorageCapacity.tsx</strong><dd><code>Implement
paywall for database storage capacity</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


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

<li>Implemented UpgradeNotification component for free projects<br> <li>
Added TransferProjectDialog for project upgrades<br> <li> Replaced
legacy plan check with isFreeProject flag<br> <li> Updated UI components
and imports


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>neat-bikes-provide.md</strong><dd><code>Add changeset
for paywall implementation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

.changeset/neat-bikes-provide.md

<li>Added changeset file for minor version bump<br> <li> Described fix
for database storage capacity setting paywall


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-02-24 20:04:39 -01:00
Nuno Pato
e3cd5f858f chore: dashboard: add segment analytics (#3213)
### **User description**
- adds segment `page` event


___

### **PR Type**
Enhancement


___

### **Description**
- Add Segment analytics to dashboard

- Implement page tracking for route changes

- Replace Segment snippet with Analytics-Next library

- Update environment variables and dependencies


___



### **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>analytics.tsx</strong><dd><code>Add Analytics component
for Segment integration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/components/analytics/analytics.tsx

<li>Create new Analytics component<br> <li> Initialize Segment
analytics<br> <li> Set up page tracking on route changes


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3213/files#diff-8879790b4a5836d5613a5ee9442b801b87b3385be9b4767e85e880cefda4d52e">+19/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>segment.ts</strong><dd><code>Set up Segment
AnalyticsBrowser configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/lib/segment.ts

<li>Initialize AnalyticsBrowser from Segment<br> <li> Load Segment with
CDN URL and write key


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>_app.tsx</strong><dd><code>Integrate Analytics
component in _app.tsx</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

dashboard/src/pages/_app.tsx

<li>Import and use new Analytics component<br> <li> Remove old Segment
snippet implementation<br> <li> Update page tracking logic


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>weak-clocks-approve.md</strong><dd><code>Add changeset
for Segment analytics integration</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/weak-clocks-approve.md

<li>Add changeset for minor version bump<br> <li> Describe addition of
Segment and page event tracking


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3213/files#diff-9ae958fefce4c3b404dba6efd018e882be2e5c88c70bf695bb869d3bc4b97205">+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>.env.example</strong><dd><code>Update .env.example with
Segment CDN URL</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

dashboard/.env.example

- Add NEXT_PUBLIC_SEGMENT_CDN_URL environment variable


</details>


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

</tr>
</table></td></tr><tr><td><strong>Dependencies</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Update dependencies for
Segment Analytics-Next</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/package.json

<li>Replace @segment/snippet with @segment/analytics-next<br> <li>
Remove analytics-node dependency


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-02-24 13:44:14 -01:00
David BM
69d9ab60c8 fix (CI): update PNPM overrides to address security vulnerabilities (#3204)
### **User description**
Fixes PNPM vulnerabilities with PNPM version overrides

vulnerable advisories were:

https://github.com/advisories/GHSA-67mh-4wv8-2f99
https://github.com/advisories/GHSA-76p7-773f-r4q5
https://github.com/advisories/GHSA-h5c3-5r3r-rr8q
https://github.com/advisories/GHSA-rmvr-2pp2-xj38
https://github.com/advisories/GHSA-xx4v-prfh-6cgc


___

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


___

### **Description**
- Update PNPM overrides to address security vulnerabilities

- Add new package version constraints for security

- Upgrade multiple dependencies to secure versions

- Enhance CI/CD pipeline security measures


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Dependencies</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Update PNPM overrides to
address security vulnerabilities</code></dd></summary>
<hr>

package.json

<li>Added new PNPM overrides for @octokit packages<br> <li> Included
override for serialize-javascript<br> <li> Added override for
esbuild<br> <li> Updated existing overrides for better security


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-02-17 23:40:10 +01:00
David BM
a8961c0ab0 fix (dashboard): regex search with service filter (#3193)
### **User description**
Fixes #3154


___

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


___

### **Description**
- Fix regex search with service filter

- Refactor service label mapping

- Add new service types (Grafana, Backup Jobs, AI)

- Improve code maintainability and readability


___



### **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>LogsHeader.tsx</strong><dd><code>Refactor service label
mapping in LogsHeader</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/logs/components/LogsHeader/LogsHeader.tsx

<li>Import LOGS_SERVICE_TO_LABEL constant<br> <li> Replace hardcoded
label mappings with LOGS_SERVICE_TO_LABEL<br> <li> Simplify service
label mapping logic


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>services.ts</strong><dd><code>Enhance and centralize
service type definitions</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/logs/utils/constants/services/services.ts

<li>Add new service types: GRAFANA, JOB_BACKUP, AI<br> <li> Replace
LOGS_AVAILABLE_SERVICES array with LOGS_SERVICE_TO_LABEL <br>object<br>
<li> Simplify and centralize service label mappings


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>twenty-icons-kneel.md</strong><dd><code>Add changeset
for version bump and bug fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.changeset/twenty-icons-kneel.md

<li>Add changeset file for version bump<br> <li> Describe fix for regex
filtering with service type filter


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-02-17 17:16:54 +01:00
robertkasza
6b8163d21f fix(nhost-js): update service URL generation for local environments (#3197)
### **User description**
- remove unnecessary redirectTo option from nextjs quickstart/example
- update README for nextjs quickstart/example
- update local URL-s in the repo to use the correct format


___

### **PR Type**
Bug fix


___

### **Description**
- Update local service URLs to include '.local'

- Remove unnecessary redirectTo option in NextJS example

- Update README for NextJS quickstart/example


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Bug
fix</strong></td><td><details><summary>19 files</summary><table>
<tr>
<td><strong>OverviewDeployments.test.tsx</strong><dd><code>Update local
GraphQL URL in 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; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-a9440d76cf165e4df8e9db020ee2ab3896281633dbe5ba3691e775d57188bc80">+148/-136</a></td>

</tr>

<tr>
<td><strong>OverviewDeployments.test.tsx</strong><dd><code>Update local
GraphQL URL in 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; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-7cc449a1b6f28a29590e4ea85aa4318961f1dcf9770447080b689432bb58e697">+117/-105</a></td>

</tr>

<tr>
<td><strong>nhostGraphQLLink.ts</strong><dd><code>Update local GraphQL
URL</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; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-c687b3a6fa4667366a494cee0a0cd8a956e97ab435d1dcca0e3d9758952db695">+3/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>hasuraMetadataQuery.ts</strong><dd><code>Update local Hasura
URL</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></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-2828f4a1163f0d281abf2517e76fc9dd393bb870478aea874019a42f9c4b7ac3">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>tableQuery.ts</strong><dd><code>Update local Hasura
URL</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></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-fdb6ad2a7e58c374f3a6772219e7f7e72ca2927def74ec75893b064caba12639">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>tokenQuery.ts</strong><dd><code>Update local Auth
URL</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; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-c86f0dec25fa37d82ed9765646ffaa9812f3b8b6f36d74056ab9e2dbe3416d0a">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>testUtils.tsx</strong><dd><code>Update local service URLs in
test utils</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-6ebbd73e167641a1706f1b8d30b00569336d10f3c2ab7626d81e639015383e5e">+9/-8</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>testUtils.tsx</strong><dd><code>Update local service URLs in
test utils</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-78f29250407edf853a353b48242d3cee59aa5724f38a60bb23bebdfc1ea2f9b5">+9/-8</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>env.ts</strong><dd><code>Update local service URLs in
environment utils</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-38801f053432e037993a6c8359ff512d7a6cfa9579597b92449f12c05c9c14e9">+11/-8</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>SignIn.tsx</strong><dd><code>Update local Mailhog
URL</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; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-0a87ee2c1dd8b5251f9b633fec0796102216844f7839e9182309fabe5c86dafe">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>config.ts</strong><dd><code>Update local Mailhog and Auth
URLs</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-2fb71947fac3f4de3e100b1e28b8c4a7141cdac93155b6635a19eb414eb62e8d">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>helpers.ts</strong><dd><code>Update local Auth
URL</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; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-4aa73c18d80efc7dbe2fe5c76039c0df6d155f6e43835a4aecbd08cd1186dd77">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>config.ts</strong><dd><code>Update local Auth
URL</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; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-ee105dd14f110b5642103092f69d3310bb652bfb12e68b1588c62a270fd3f603">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>helpers.ts</strong><dd><code>Update local Storage
URL</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; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-f50172c9dba0fa2de72135ff70ff1d96f8a524f1388a9429182d3e8809909d3f">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>helpers.ts</strong><dd><code>Update URL generation for local
services</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-772264ae234cf88eeab12134a272a425ac41273afc392a07316fb26d7c573023">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>utils.test.ts</strong><dd><code>Update local service URLs in
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; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-940f24899a0f0f423f25dc4f3809920f2cfbdf3211f892f16011d964ac4ac319">+5/-5</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>seed-storage.sh</strong><dd><code>Update local Storage URL
in seed script</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-211c009c503a489da990b07865b1ad981ddcaae38b96fbb327e832d66eab63b9">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>test-nhost-cli-action.yaml</strong><dd><code>Update local
service URLs in CLI action tests</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-defe9dba19ff27b93c6b21e6308904b606b75cad29218bf849d29f7a8506a30e">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>graphite.graphql.config.yaml</strong><dd><code>Update local
GraphQL URL in config</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-74a8fb68417df04d2af42e94ab298fd54e22d42676b50572b16a2293446f0988">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

</table></details></td></tr><tr><td><strong>Enhancement</strong></td><td><details><summary>2
files</summary><table>
<tr>
<td><strong>sign-in-apple.ts</strong><dd><code>Remove redirectTo option
from Apple sign-in</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-1b6954885dd5b8b4406ed46dfd8102b5cc92175f093cd3c0ebe26477e1346d42">+1/-4</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>sign-in-google.ts</strong><dd><code>Remove redirectTo option
from Google sign-in</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-a155d7f9e27b15453c109c16e2d1c76d3632b28bfafcdaa4180caf40f50102d9">+1/-4</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

</table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>21
files</summary><table>
<tr>
<td><strong>hip-falcons-applaud.md</strong><dd><code>Add changeset for
URL generation update</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-a41c3544b060502eb895d87553a84ecf238b60567347dafb4168d64fe119e385">+12/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>.env.example</strong><dd><code>Update local service URLs in
example env file</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-b47cf46119af2f0298d96e5657e53e57161833e8b02d87526ac5c1ed9393d477">+8/-8</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>README.md</strong><dd><code>Update local service URLs in
README</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-c15729e6c35a283a4b0eda60a991303b6c36c03903ba42dbf832bb8d0daa1a1a">+7/-7</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>commands.mdx</strong><dd><code>Update local service URLs in
CLI documentation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-2053eb5138f4c468b9aa94e6fd7302ad2f577839be107741f265ae1b2d9bfcaa">+17/-21</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>getting-started.mdx</strong><dd><code>Update local service
URLs in CLI documentation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-05cc8d760dce63f257bee91e9c0293424a63e0ed210d26c7bca78bc3a3d5d763">+8/-8</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>overview.mdx</strong><dd><code>Update local service URLs in
CLI overview</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-dfcca51e047037e649bbf76e68ab3aa9161a85c1bd25cf385acc5e764bea0cd3">+9/-9</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>assistants.mdx</strong><dd><code>Update local Functions URL
in AI assistants guide</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-3fe162cc8ef81dc6517dcd3e7a5531ed695df9fc300595d6b923677a2de7af99">+85/-116</a></td>

</tr>

<tr>
<td><strong>custom-jwts.mdx</strong><dd><code>Update local Functions URL
in custom JWTs guide</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-3e3f4be3ef4e3ddc1f5dbf30b76be0aa275309755cfbcc1afa865a13b433522c">+2/-5</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>local-development.mdx</strong><dd><code>Update local service
URLs in CLI guide</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-00fc275afa2ec0920232682aec7a0f553457675b09fede84fff1cf33fd928422">+14/-16</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>migrate-config.mdx</strong><dd><code>Update local service
URLs in migration guide</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-d85182dc59541186f337991dcbc95179140091cd62bc64acf484f7e9c74dd247">+7/-8</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>multiple-projects.mdx</strong><dd><code>Update local service
URLs in multiple projects guide</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-842182f7c0367b8667570df1f6903fb09b6e9ee5062feac58733dbb238e9c252">+7/-9</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>seeds.mdx</strong><dd><code>Update local Hasura URL in seeds
guide</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-3d5c1396d5c9c028ffa0c8493cb64f0dc06223e651665c173fdb6df30c7f5cb0">+12/-5</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>local-development.mdx</strong><dd><code>Update local GraphQL
URL in run guide</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-9ddcc07c6add2efbb2630dfa5b718656444e9c566e84d38f577eb6a026f4d870">+21/-7</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>overview.mdx</strong><dd><code>Update local Storage URL in
storage overview</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-c2641bf1319034c16ea03895511bac4dcabd62660fe49f713371529041495c5e">+7/-6</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>diagrams.txt</strong><dd><code>Update local Functions and
GraphQL URLs in diagrams</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-b0815782a1cb44a8e50e0616704b74f4e2b4785b4358ce705872f94bf635b573">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>openapi-auth-old.yaml</strong><dd><code>Update local Auth
URL in OpenAPI spec</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-bc23e66a45c276e58845db366df3759415034d4e045b655953a3c557b9c0f09f">+57/-63</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>openapi-storage.yaml</strong><dd><code>Update local Storage
URL in OpenAPI spec</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-81afd91a70cb5516a39e6900392de5136771981bec072a97e5f48975bbf6afd3">+8/-8</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>upload-file.mdx</strong><dd><code>Update local Storage URL
in file upload reference</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-d0a3eae50a19e63cf2d66ab4f644104fa20a946b24122254ec4a368f847292d1">+8/-10</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>README.md</strong><dd><code>Update README with new local
dashboard URL</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-411e8e55ab182bb59f03f2a8e1539b08afbc0f42796f73f5bfcb0c47a015c5c4">+24/-2</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>README.md</strong><dd><code>Update local Storage URLs in
seed data example</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-d2af072cf65c3cc8f02d82d7346a492ddb81a768948ccf02ccb2e8ec1800029c">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>README.md</strong><dd><code>Update local Functions URLs in
Google Translation README</code>&nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-b3790312c229004898229cf8ee576c443fce25ef8fdfe6079e1242db932e9d94">+2/-2</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><strong>nhost.toml</strong><dd><code>Update OAuth and WebAuthn
configurations</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3197/files#diff-eff7b73b949a7fd005b4c51ae54a7757b8447c831168c0d014b6034adc7539bb">+2/-4</a>&nbsp;
&nbsp; &nbsp; </td>

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-02-17 11:11:21 +01:00
David BM
a21553c774 chore: update links in README (#3199)
### **PR Type**
Documentation


___

### **Description**
- Updated links in README to reflect new documentation structure

- Revised quickstart and CLI documentation URLs

- Updated links for framework-specific quickstart guides

- Refreshed 'Try out Nhost' link in contribution section


___



### **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>README.md</strong><dd><code>Update documentation and
quickstart guide links</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

README.md

<li>Updated quickstart link to new documentation structure<br> <li>
Revised CLI documentation link<br> <li> Updated links for Next.js,
React, and Vue.js quickstart guides<br> <li> Refreshed 'Try out Nhost'
link in contribution section


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-02-14 16:47:30 +01:00
232 changed files with 6253 additions and 2073 deletions

View File

@@ -30,6 +30,14 @@ runs:
uses: actions/setup-node@v3
with:
node-version: 20
- shell: bash
name: Use Latest Corepack
run: |
echo "Before: corepack version => $(corepack --version || echo 'not installed')"
npm install -g corepack@latest
echo "After : corepack version => $(corepack --version)"
corepack enable
pnpm --version
- shell: bash
name: Install packages
run: pnpm install --frozen-lockfile

View File

@@ -1,6 +1,9 @@
name: Nhost CLI
description: 'Action to install the Nhost CLI and to run an application'
inputs:
init:
description: 'Initialize the application'
default: 'false'
start:
description: "Start the application. If false, the application won't be started"
default: 'false'
@@ -16,6 +19,9 @@ inputs:
version:
description: 'Version of the Nhost CLI'
default: 'latest'
dashboard-image:
description: 'Image of the dashboard'
default: 'nhost/dashboard:latest'
config:
description: 'Values to be injected into nhost/config.yaml'
@@ -40,6 +46,13 @@ runs:
timeout_minutes: 3
max_attempts: 10
command: bash <(curl --silent -L https://raw.githubusercontent.com/nhost/cli/main/get.sh) ${{ inputs.version }}
- name: Initialize a new project from scratch
if: ${{ inputs.init == 'true' }}
shell: bash
working-directory: ${{ inputs.path }}
run: |
rm -rf ./*
nhost init
- name: Set custom configuration
if: ${{ inputs.config }}
shell: bash
@@ -50,7 +63,12 @@ runs:
shell: bash
working-directory: ${{ inputs.path }}
run: |
cp .secrets.example .secrets
if [ -n "${{ inputs.dashboard-image }}" ]; then
export NHOST_DASHBOARD_VERSION=${{ inputs.dashboard-image }}
fi
if [ -f .secrets.example ]; then
cp .secrets.example .secrets
fi
nhost up
- name: Log on failure
if: steps.wait.outcome == 'failure'

View File

@@ -134,10 +134,27 @@ jobs:
with:
TURBO_TOKEN: ${{ env.TURBO_TOKEN }}
TURBO_TEAM: ${{ env.TURBO_TEAM }}
# * Build Dashboard image to test it locally
- name: Build Dashboard local image
if: matrix.package.path == 'dashboard'
run: |
docker build -t nhost/dashboard:0.0.0-dev -f ${{ matrix.package.path }}/Dockerfile .
mkdir -p nhost-test-project
# * Install Nhost CLI if a `nhost/config.yaml` file is found
- name: Install Nhost CLI
if: hashFiles(format('{0}/nhost/config.yaml', matrix.package.path)) != ''
if: hashFiles(format('{0}/nhost/config.yaml', matrix.package.path)) != '' && matrix.package.path != 'dashboard'
uses: ./.github/actions/nhost-cli
# * Install Nhost CLI to test Dashboard locally
- name: Install Nhost CLI (Local Dashboard tests)
timeout-minutes: 5
if: matrix.package.path == 'dashboard'
uses: ./.github/actions/nhost-cli
with:
init: 'true' # Initialize the application
start: 'true' # Start the application
path: ./nhost-test-project
wait: 'true' # Wait until the application is ready
dashboard-image: 'nhost/dashboard:0.0.0-dev'
- name: Fetch Dashboard Preview URL
id: fetch-dashboard-preview-url
uses: zentered/vercel-preview-url@v1.1.9
@@ -157,6 +174,17 @@ jobs:
- name: Run e2e tests
timeout-minutes: 20
run: pnpm --filter="${{ matrix.package.name }}" run e2e
# * Run the `e2e-local` script of the dashboard
- name: Run Local Dashboard e2e tests
if: matrix.package.path == 'dashboard'
timeout-minutes: 5
run: |
pnpm --filter="${{ matrix.package.name }}" run e2e-local
- name: Stop Nhost CLI
if: matrix.package.path == 'dashboard'
working-directory: ./nhost-test-project
run: nhost down
- id: file-name
if: ${{ failure() }}
name: Transform package name into a valid file name

View File

@@ -25,10 +25,10 @@ jobs:
- name: Install the Nhost CLI and start the application
uses: ./.github/actions/nhost-cli
with:
path: packages/nhost-js
init: true
start: true
- name: should be running
run: curl -sSf 'https://local.hasura.nhost.run' > /dev/null
run: curl -sSf 'https://local.hasura.local.nhost.run/' > /dev/null
stop:
runs-on: ubuntu-latest
@@ -37,7 +37,7 @@ jobs:
- name: Install the Nhost CLI, start and stop the application
uses: ./.github/actions/nhost-cli
with:
path: packages/nhost-js
init: true
start: true
stop: true
- name: should have no live docker container
@@ -55,12 +55,13 @@ jobs:
- name: Install the Nhost CLI and run the application
uses: ./.github/actions/nhost-cli
with:
path: packages/nhost-js
init: true
version: v1.29.3
start: true
- name: should find the injected hasura-auth version
run: |
VERSION=$(curl -sSf 'https://local.auth.nhost.run/v1/version')
EXPECTED_VERSION='{"version":"v0.20.1"}'
VERSION=$(curl -sSf 'https://local.auth.local.nhost.run/v1/version')
EXPECTED_VERSION='{"version":"0.36.1"}'
if [ "$VERSION" != "$EXPECTED_VERSION" ]; then
echo "Expected version $EXPECTED_VERSION but got $VERSION"
exit 1
@@ -73,6 +74,6 @@ jobs:
- name: Install the Nhost CLI
uses: ./.github/actions/nhost-cli
with:
version: v1.0.1
version: v1.27.2
- name: should find the correct version
run: nhost --version | head -n 1 | grep v1.0.1 || exit 1
run: nhost --version | head -n 1 | grep v1.27.2 || exit 1

View File

@@ -4,7 +4,7 @@
# Nhost
<a href="https://docs.nhost.io/#quickstart">Quickstart</a>
<a href="https://docs.nhost.io/introduction#quick-start-guides">Quickstart</a>
<span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
<a href="http://nhost.io/">Website</a>
<span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
@@ -36,7 +36,7 @@ Nhost consists of open source software:
- Authentication: [Hasura Auth](https://github.com/nhost/hasura-auth/)
- Storage: [Hasura Storage](https://github.com/nhost/hasura-storage)
- Serverless Functions: Node.js (JavaScript and TypeScript)
- [Nhost CLI](https://docs.nhost.io/cli) for local development
- [Nhost CLI](https://docs.nhost.io/development/cli/overview) for local development
## Architecture of Nhost
@@ -89,12 +89,12 @@ await nhost.graphql.request(`{
Nhost is frontend agnostic, which means Nhost works with all frontend frameworks.
<div align="center">
<a href="https://docs.nhost.io/platform/quickstarts/nextjs"><img src="assets/nextjs.svg"/></a>
<a href="https://docs.nhost.io/guides/quickstarts/nextjs"><img src="assets/nextjs.svg"/></a>
<a href="https://docs.nhost.io/reference/javascript"><img src="assets/nuxtjs.svg"/></a>
<a href="https://docs.nhost.io/platform/quickstarts/react"><img src="assets/react.svg"/></a>
<a href="https://docs.nhost.io/guides/quickstarts/react"><img src="assets/react.svg"/></a>
<a href="https://docs.nhost.io/reference/javascript"><img src="assets/react-native.svg"/></a>
<a href="https://docs.nhost.io/reference/javascript"><img src="assets/svelte.svg"/></a>
<a href="https://docs.nhost.io/platform/quickstarts/vue"><img src="assets/vuejs.svg"/></a>
<a href="https://docs.nhost.io/guides/quickstarts/vue"><img src="assets/vuejs.svg"/></a>
</div>
# Resources
@@ -140,7 +140,7 @@ This repository, and most of our other open source projects, are licensed under
Here are some ways of contributing to making Nhost better:
- **[Try out Nhost](https://docs.nhost.io/get-started/quick-start)**, and think of ways to make the service better. Let us know here on GitHub.
- **[Try out Nhost](https://docs.nhost.io/introduction)**, and think of ways to make the service better. Let us know here on GitHub.
- Join our [Discord](https://discord.com/invite/9V7Qb2U) and connect with other members to share and learn from.
- Send a pull request to any of our [open source repositories](https://github.com/nhost) on Github. Check our [contribution guide](https://github.com/nhost/nhost/blob/main/CONTRIBUTING.md) and our [developers guide](https://github.com/nhost/nhost/blob/main/DEVELOPERS.md) for more details about how to contribute. We're looking forward to your contribution!

View File

@@ -3,18 +3,19 @@ NEXT_PUBLIC_ENV=dev
NEXT_PUBLIC_NHOST_PLATFORM=false
# Environment Variables for Self Hosting and Local Development
NEXT_PUBLIC_NHOST_AUTH_URL=https://local.auth.nhost.run/v1
NEXT_PUBLIC_NHOST_FUNCTIONS_URL=https://local.functions.nhost.run/v1
NEXT_PUBLIC_NHOST_GRAPHQL_URL=https://local.graphql.nhost.run/v1
NEXT_PUBLIC_NHOST_STORAGE_URL=https://local.storage.nhost.run/v1
NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL=https://local.hasura.nhost.run
NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL=https://local.hasura.nhost.run/v1/migrations
NEXT_PUBLIC_NHOST_HASURA_API_URL=https://local.hasura.nhost.run
NEXT_PUBLIC_NHOST_AUTH_URL=https://local.auth.nhost.local.run/v1
NEXT_PUBLIC_NHOST_FUNCTIONS_URL=https://local.functions.local.nhost.run/v1
NEXT_PUBLIC_NHOST_GRAPHQL_URL=https://local.graphql.local.nhost.run/v1
NEXT_PUBLIC_NHOST_STORAGE_URL=https://local.storage.local.nhost.run/v1
NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL=https://local.hasura.local.nhost.run
NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL=https://local.hasura.local.nhost.run/v1/migrations
NEXT_PUBLIC_NHOST_HASURA_API_URL=https://local.hasura.local.nhost.run
# Environment Variables when running the Nhost Dashboard against the Nhost Backend
NEXT_PUBLIC_STRIPE_PK=<nhost_stripe_public_key>
NEXT_PUBLIC_GITHUB_APP_INSTALL_URL=<github_app_install_url>
NEXT_PUBLIC_ANALYTICS_WRITE_KEY=<analytics_write_key>
NEXT_PUBLIC_SEGMENT_CDN_URL=<segment_cdn_url>
NEXT_PUBLIC_NHOST_BRAGI_WEBSOCKET=<nhost_bragi_websocket>
NEXT_PUBLIC_ZENDESK_URL=
@@ -22,6 +23,6 @@ NEXT_PUBLIC_ZENDESK_API_KEY=
NEXT_PUBLIC_ZENDESK_USER_EMAIL=
CODEGEN_GRAPHQL_URL=https://local.graphql.nhost.run/v1
CODEGEN_GRAPHQL_URL=https://local.graphql.local.nhost.run/v1
CODEGEN_HASURA_ADMIN_SECRET=nhost-admin-secret
NEXT_PUBLIC_TURNSTILE_SITE_KEY=FIXME

View File

@@ -51,13 +51,13 @@ You can connect the Nhost Dashboard to your locally running backend by setting t
```bash
NEXT_PUBLIC_ENV=dev
NEXT_PUBLIC_NHOST_PLATFORM=false
NEXT_PUBLIC_NHOST_AUTH_URL=https://local.auth.nhost.run/v1
NEXT_PUBLIC_NHOST_FUNCTIONS_URL=https://local.functions.nhost.run/v1
NEXT_PUBLIC_NHOST_GRAPHQL_URL=https://local.graphql.nhost.run/v1
NEXT_PUBLIC_NHOST_STORAGE_URL=https://local.storage.nhost.run/v1
NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL=https://local.hasura.nhost.run
NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL=https://local.hasura.nhost.run/v1/migrations
NEXT_PUBLIC_NHOST_HASURA_API_URL=https://local.hasura.nhost.run
NEXT_PUBLIC_NHOST_AUTH_URL=https://local.auth.local.nhost.run/v1
NEXT_PUBLIC_NHOST_FUNCTIONS_URL=https://local.functions.local.nhost.run/v1
NEXT_PUBLIC_NHOST_GRAPHQL_URL=https://local.graphql.local.nhost.run/v1
NEXT_PUBLIC_NHOST_STORAGE_URL=https://local.storage.local.nhost.run/v1
NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL=https://local.hasura.local.nhost.run
NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL=https://local.hasura.local.nhost.run/v1/migrations
NEXT_PUBLIC_NHOST_HASURA_API_URL=https://local.hasura.local.nhost.run
```
This will connect the Nhost Dashboard to your locally running Nhost backend.

View File

@@ -0,0 +1,43 @@
import { TEST_ORGANIZATION_SLUG, TEST_PROJECT_SUBDOMAIN } from '@/e2e/env';
import { createUser, generateTestEmail } from '@/e2e/utils';
import { faker } from '@faker-js/faker';
import type { Page } from '@playwright/test';
import test, { expect } from '@playwright/test';
let page: Page;
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
});
test.beforeEach(async () => {
const authUrl = `/orgs/${TEST_ORGANIZATION_SLUG}/projects/${TEST_PROJECT_SUBDOMAIN}/users`;
await page.goto(authUrl);
await page.waitForURL(authUrl, { waitUntil: 'networkidle' });
});
test.afterAll(async () => {
await page.close();
});
test('should be able to edit user roles from the details page', async () => {
const email = generateTestEmail();
const password = faker.internet.password();
await createUser({ page, email, password });
await page
.getByRole('button', { name: `View ${email}`, exact: true })
.click();
await page.locator('#defaultRole').click();
await page.getByRole('option', { name: /anonymous/i }).click();
await page.getByLabel('anonymous').click();
await page.getByRole('button', { name: /save/i }).click();
await expect(
page.getByText('User settings have been updated successfully.'),
).toBeVisible();
});

View File

@@ -0,0 +1,21 @@
import { expect, test } from '@playwright/test';
test.describe('Local Dashboard CLI e2e tests', () => {
test('should redirect / to the correct project URL', async ({ page }) => {
await page.goto('https://local.dashboard.local.nhost.run/');
await page.waitForURL(
'https://local.dashboard.local.nhost.run/orgs/local/projects/local',
);
expect(page.url()).toBe(
'https://local.dashboard.local.nhost.run/orgs/local/projects/local',
);
});
test('should load the project URL correctly', async ({ page }) => {
const projectUrl =
'https://local.dashboard.local.nhost.run/orgs/local/projects/local';
await page.goto(projectUrl);
await expect(page).toHaveURL(projectUrl);
await expect(page.getByText(/Subdomain/i)).toBeVisible();
});
});

View File

@@ -1,5 +1,5 @@
schema:
- https://local.graphql.nhost.run/v1:
- https://local.graphql.local.nhost.run/v1:
headers:
x-hasura-admin-secret: nhost-admin-secret
generates:

View File

@@ -7,7 +7,7 @@ const { version } = require('./package.json');
const cspHeader = `
default-src 'self' *.nhost.run ws://*.nhost.run nhost.run ws://nhost.run;
script-src 'self' 'unsafe-eval' 'unsafe-inline' cdn.segment.com js.stripe.com;
connect-src 'self' *.nhost.run ws://*.nhost.run nhost.run ws://nhost.run discord.com;
connect-src 'self' *.nhost.run ws://*.nhost.run nhost.run ws://nhost.run discord.com api.segment.io api.segment.com cdn.segment.com;
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data: avatars.githubusercontent.com s.gravatar.com *.nhost.run nhost.run;
font-src 'self' data:;
@@ -16,6 +16,8 @@ const cspHeader = `
form-action 'self';
frame-ancestors 'none';
frame-src 'self' js.stripe.com;
block-all-mixed-content;
upgrade-insecure-requests;
`;
module.exports = withBundleAnalyzer({
@@ -36,9 +38,13 @@ module.exports = withBundleAnalyzer({
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: cspHeader.replace(/\s+/g, ' ').trim(),
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN',
value: 'DENY',
},
],
},

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "2.20.0",
"version": "2.22.0",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -16,13 +16,15 @@
"storybook": "start-storybook -p 6006 -s public",
"build-storybook": "build-storybook",
"install-browsers": "pnpm playwright install && pnpm playwright install-deps",
"e2e": "pnpm install-browsers && pnpm playwright test"
"e2e": "pnpm install-browsers && pnpm playwright test --config=playwright.config.ts",
"e2e-local": "pnpm install-browsers && pnpm playwright test --config=playwright.local.config.ts"
},
"dependencies": {
"@apollo/client": "^3.9.9",
"@codemirror/lang-sql": "^6.6.2",
"@codemirror/language": "^6.10.1",
"@codemirror/legacy-modes": "^6.4.0",
"@date-fns/tz": "^1.2.0",
"@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.4",
"@emotion/server": "^11.11.0",
@@ -55,24 +57,25 @@
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-tooltip": "^1.1.2",
"@segment/snippet": "^4.16.2",
"@segment/analytics-next": "^1.77.0",
"@stripe/react-stripe-js": "^2.6.2",
"@stripe/stripe-js": "^1.54.2",
"@tailwindcss/forms": "^0.5.7",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-table": "^8.15.3",
"@tanstack/react-virtual": "^3.2.0",
"@tanstack/react-virtual": "^3.5.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",
"analytics-node": "^6.2.0",
"bcryptjs": "^2.4.3",
"class-variance-authority": "^0.7.0",
"clsx": "^1.2.1",
"cmdk": "1.0.0",
"date-fns": "^2.30.0",
"date-fns-v4": "npm:date-fns@4.1.0",
"dequal": "^2.0.3",
"framer-motion": "^10.18.0",
"generate-password": "^1.7.1",
@@ -93,6 +96,7 @@
"react": "18.2.0",
"react-children-utilities": "^2.10.0",
"react-complex-tree": "^2.4.5",
"react-day-picker": "8.10.1",
"react-dom": "18.2.0",
"react-error-boundary": "^4.0.13",
"react-hook-form": "^7.53.0",
@@ -113,6 +117,7 @@
"stripe": "^10.17.0",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7",
"timezones-list": "^3.1.0",
"utility-types": "^3.11.0",
"uuid": "^9.0.1",
"validator": "^13.11.0",

View File

@@ -40,6 +40,7 @@ export default defineConfig({
storageState: 'e2e/.auth/user.json',
},
dependencies: ['setup'],
grepInvert: [/Local Dashboard CLI e2e tests/],
},
],
});

View File

@@ -0,0 +1,31 @@
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
timeout: 30 * 1000,
expect: {
timeout: 5000,
},
fullyParallel: false,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: 1,
reporter: 'html',
use: {
actionTimeout: 0,
trace: 'on-first-retry',
baseURL: '', // Local dashboard URL
launchOptions: {
slowMo: 500,
},
},
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
testMatch: ['**/e2e/cli-local-dashboard/**'],
},
],
});

View File

@@ -0,0 +1,30 @@
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { analytics } from '@/lib/segment';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
export default function Analytics() {
const router = useRouter();
const { org } = useCurrentOrg();
const { project } = useProject();
useEffect(() => {
const customProperties = {
organizationSlug: org?.slug || '',
projectSubdomain: project?.subdomain || '',
};
analytics.page(customProperties);
const handleRouteChange = () => analytics.page(customProperties);
router.events.on('routeChangeComplete', handleRouteChange);
return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
}, [router.events, org?.slug, project?.subdomain]);
return null;
}

View File

@@ -0,0 +1,164 @@
'use client';
import { TimePicker } from '@/components/common/TimePicker';
import { Button } from '@/components/ui/v3/button';
import { Calendar } from '@/components/ui/v3/calendar';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/v3/popover';
import { cn } from '@/lib/utils';
import { guessTimezone } from '@/utils/timezoneUtils';
import { TZDate } from '@date-fns/tz';
import { add, format, parseISO } from 'date-fns-v4';
import { Calendar as CalendarIcon } from 'lucide-react';
import { useState } from 'react';
import TimezoneSettings from './TimezoneSettings';
export interface DateTimePickerProps {
dateTime: string;
onDateTimeChange: (newDate: string) => void;
withTimezone?: boolean;
defaultTimezone?: string;
formatDateFn?: (date: Date | string) => string;
isCalendarDayDisabled?: (date: Date) => boolean;
align?: 'start' | 'center' | 'end';
validateDateFn?: (date: Date) => string;
}
// in: UTC datetime
// out: UTC dateTime
function DateTimePicker({
dateTime,
withTimezone = false,
defaultTimezone,
formatDateFn,
onDateTimeChange,
isCalendarDayDisabled,
align = 'start',
validateDateFn,
}: DateTimePickerProps) {
const [date, setDate] = useState(() => {
if (withTimezone) {
const tz = defaultTimezone || guessTimezone();
return new TZDate(dateTime, tz);
}
return parseISO(dateTime);
});
const [open, setOpen] = useState(false);
function emitNewDateTime() {
onDateTimeChange(new Date(date.getTime()).toISOString());
}
/**
* carry over the current time when a user clicks a new day
* instead of resetting to 00:00
*/
function handleSelect(newDay: Date | undefined) {
if (!newDay) {
return;
}
if (!date) {
setDate(newDay);
return;
}
const diff = newDay.getTime() - date.getTime();
const diffInDays = diff / (1000 * 60 * 60 * 24);
const newDateFull = add(date, { days: Math.ceil(diffInDays) });
setDate(newDateFull);
}
function handleTimezoneChange(newTimezone: string) {
const newDateWithTimezone = new TZDate(date.toISOString(), newTimezone);
setDate(newDateWithTimezone);
}
function handleOpenChange(newOpenState: boolean) {
if (!newOpenState) {
if (withTimezone) {
const tz = defaultTimezone || guessTimezone();
setDate(new TZDate(dateTime, tz));
}
setDate(parseISO(dateTime));
}
setOpen(newOpenState);
}
function onSelect() {
emitNewDateTime();
setOpen(false);
}
const dateString = formatDateFn?.(date) || format(date, 'PPP HH:mm:ss');
const errorText = validateDateFn?.(date);
const hasError = !!errorText;
return (
<Popover open={open} onOpenChange={handleOpenChange}>
<PopoverTrigger asChild>
<Button
variant="outline"
className={cn(
'w-full justify-between text-left font-normal',
!date && 'text-muted-foreground',
{ 'border-destructive': hasError },
)}
onClick={() => setOpen(true)}
>
{date ? dateString : <span>Pick a date</span>}
<CalendarIcon className="h-4 w-4" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align={align}>
<div className="flex">
<div className="flex">
<Calendar
mode="single"
selected={date}
onSelect={(d) => handleSelect(d)}
initialFocus
disabled={isCalendarDayDisabled}
/>
<div className="flex flex-col justify-between">
<div>
<div className="border-t border-border p-3">
<TimePicker setDate={setDate} date={date} />
</div>
{withTimezone && (
<div className="border-t border-border p-3">
<TimezoneSettings
dateTime={dateTime}
onTimezoneChange={handleTimezoneChange}
/>
</div>
)}
</div>
<div className="flex flex-row justify-between gap-5 p-3">
<Button
className="w-full"
onClick={onSelect}
disabled={hasError}
>
Select
</Button>
</div>
</div>
</div>
</div>
<div
className={cn('p-3 text-center text-[11px] text-destructive', {
invisible: !hasError,
})}
>
{errorText}
</div>
</PopoverContent>
</Popover>
);
}
export default DateTimePicker;

View File

@@ -0,0 +1,39 @@
import { TimezonePicker } from '@/components/common/TimezonePicker';
import { Button } from '@/components/ui/v3/button';
import { getUTCOffsetInHours, guessTimezone } from '@/utils/timezoneUtils';
import { Settings2 } from 'lucide-react';
import { useState } from 'react';
interface Props {
dateTime: string;
onTimezoneChange: (timezone: string) => void;
}
function TimezoneSettings({ dateTime, onTimezoneChange }: Props) {
const [selectedTimezone, setTimezone] = useState<string>(() =>
guessTimezone(),
);
function handleTimezoneSelect(tz: { value: string; label: string }) {
setTimezone(tz.value);
onTimezoneChange?.(tz.value);
}
const utcOffset = getUTCOffsetInHours(selectedTimezone, dateTime, 'OOOO');
return (
<div className="flex w-full items-center justify-between">
Timezone: {utcOffset}{' '}
<TimezonePicker
dateTime={dateTime}
selectedTimezone={selectedTimezone}
onTimezoneSelect={handleTimezoneSelect}
button={
<Button variant="ghost" size="icon">
<Settings2 className="h-4 w-4 dark:text-foreground" />
</Button>
}
/>
</div>
);
}
export default TimezoneSettings;

View File

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

View File

@@ -0,0 +1,105 @@
import { render, screen } from '@/tests/orgs/testUtils';
import { guessTimezone } from '@/utils/timezoneUtils';
import { TZDate } from '@date-fns/tz';
import userEvent from '@testing-library/user-event';
import { parseISO } from 'date-fns';
import { format } from 'date-fns-v4';
import { useState } from 'react';
import TimePicker from './TimePicker';
function TestComponent({
dateTime,
withTimezone,
}: {
dateTime: string;
withTimezone?: boolean;
}) {
const [date, setDate] = useState(() => {
if (withTimezone) {
const tz = guessTimezone();
return new TZDate(dateTime, tz);
}
return parseISO(dateTime);
});
return (
<>
<h1>Time: {format(date, 'HH:mm:ss')}</h1>
<h1>Date class: {date instanceof TZDate ? 'TZDate' : 'Date'}</h1>
<TimePicker date={date} setDate={setDate} />
</>
);
}
describe('TimePicker', () => {
test('Updates only the hour of the date object', async () => {
render(<TestComponent dateTime="2025-03-10T03:00:05" />);
expect(await screen.getByText(/Time:/i)).toHaveTextContent(
'Time: 03:00:05',
);
const user = userEvent.setup();
const hoursInput = await screen.getByLabelText('Hours');
await user.type(hoursInput, '18');
expect(await screen.getByText(/Time:/i)).toHaveTextContent(
'Time: 18:00:05',
);
});
test('only valid hours(0-23), minutes(0-59) and seconds(0-59) are allowed', async () => {
render(<TestComponent dateTime="2025-03-10T03:00:05" />);
const user = userEvent.setup();
const hoursInput = await screen.getByLabelText('Hours');
await user.type(hoursInput, '30');
expect(await screen.getByText(/Time:/i)).toHaveTextContent(
'Time: 23:00:05',
);
const minutesInput = await screen.getByLabelText('Minutes');
await user.type(minutesInput, '66');
expect(await screen.getByText(/Time:/i)).toHaveTextContent(
'Time: 23:59:05',
);
});
test('Updates only the minutes of the date object', async () => {
render(<TestComponent dateTime="2025-03-10T03:00:05" />);
const user = userEvent.setup();
const minutesInput = await screen.getByLabelText('Minutes');
await user.type(minutesInput, '44');
expect(await screen.getByText(/Time:/i)).toHaveTextContent(
'Time: 03:44:05',
);
});
test('Updates only the seconds of the date object', async () => {
render(<TestComponent dateTime="2025-03-10T03:00:05" />);
const user = userEvent.setup();
const secondsInput = await screen.getByLabelText('Seconds');
await user.type(secondsInput, '11');
expect(await screen.getByText(/Time:/i)).toHaveTextContent(
'Time: 03:00:11',
);
});
test("will preserve the date's class after changing the date", async () => {
render(<TestComponent dateTime="2025-03-10T03:00:05" withTimezone />);
expect(await screen.getByText(/Date class:/i)).toHaveTextContent(
'Date class: TZDate',
);
const user = userEvent.setup();
const hoursInput = await screen.getByLabelText('Hours');
await user.type(hoursInput, '18');
expect(await screen.getByText(/Date class:/i)).toHaveTextContent(
'Date class: TZDate',
);
const secondsInput = await screen.getByLabelText('Seconds');
await user.type(secondsInput, '11');
expect(await screen.getByText(/Date class:/i)).toHaveTextContent(
'Date class: TZDate',
);
const minutesInput = await screen.getByLabelText('Minutes');
await user.type(minutesInput, '44');
expect(await screen.getByText(/Date class:/i)).toHaveTextContent(
'Date class: TZDate',
);
});
});

View File

@@ -0,0 +1,64 @@
'use client';
import { Label } from '@/components/ui/v3/label';
import { Clock } from 'lucide-react';
import * as React from 'react';
import { TimePickerInput } from './TimePickerInput';
interface TimePickerProps {
date: Date | undefined;
setDate: (date: Date | undefined) => void;
}
function TimePicker({ date, setDate }: TimePickerProps) {
const minuteRef = React.useRef<HTMLInputElement>(null);
const hourRef = React.useRef<HTMLInputElement>(null);
const secondRef = React.useRef<HTMLInputElement>(null);
return (
<div className="flex items-end gap-2">
<div className="grid gap-1 text-center">
<Label htmlFor="hours" className="text-xs">
Hours
</Label>
<TimePickerInput
picker="hours"
date={date}
setDate={setDate}
ref={hourRef}
onRightFocus={() => minuteRef.current?.focus()}
/>
</div>
<div className="grid gap-1 text-center">
<Label htmlFor="minutes" className="text-xs">
Minutes
</Label>
<TimePickerInput
picker="minutes"
date={date}
setDate={setDate}
ref={minuteRef}
onLeftFocus={() => hourRef.current?.focus()}
onRightFocus={() => secondRef.current?.focus()}
/>
</div>
<div className="grid gap-1 text-center">
<Label htmlFor="seconds" className="text-xs">
Seconds
</Label>
<TimePickerInput
picker="seconds"
date={date}
setDate={setDate}
ref={secondRef}
onLeftFocus={() => minuteRef.current?.focus()}
/>
</div>
<div className="flex h-10 items-center">
<Clock className="ml-2 h-4 w-4" />
</div>
</div>
);
}
export default TimePicker;

View File

@@ -0,0 +1,148 @@
import { Input } from '@/components/ui/v3/input';
import { cn } from '@/lib/utils';
import React from 'react';
import {
type Period,
type TimePickerType,
copyDate,
getArrowByType,
getDateByType,
setDateByType,
} from './time-picker-utils';
export interface TimePickerInputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
picker: TimePickerType;
date: Date | undefined;
setDate: (date: Date | undefined) => void;
period?: Period;
onRightFocus?: () => void;
onLeftFocus?: () => void;
}
const TimePickerInput = React.forwardRef<
HTMLInputElement,
TimePickerInputProps
>(
(
{
className,
type = 'tel',
value,
id,
name,
date = new Date(new Date().setHours(0, 0, 0, 0)),
setDate,
onChange,
onKeyDown,
picker,
period,
onLeftFocus,
onRightFocus,
...props
},
ref,
) => {
const [flag, setFlag] = React.useState<boolean>(false);
const [prevIntKey, setPrevIntKey] = React.useState<string>('0');
/**
* allow the user to enter the second digit within 2 seconds
* otherwise start again with entering first digit
*/
// eslint-disable-next-line consistent-return
React.useEffect(() => {
if (flag) {
const timer = setTimeout(() => {
setFlag(false);
}, 2000);
return () => clearTimeout(timer);
}
}, [flag]);
const calculatedValue = React.useMemo(
() => getDateByType(date, picker),
[date, picker],
);
const calculateNewValue = (key: string) => {
/*
* If picker is '12hours' and the first digit is 0, then the second digit is automatically set to 1.
* The second entered digit will break the condition and the value will be set to 10-12.
*/
if (picker === '12hours') {
if (flag && calculatedValue.slice(1, 2) === '1' && prevIntKey === '0') {
return `0${key}`;
}
}
return !flag ? `0${key}` : calculatedValue.slice(1, 2) + key;
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Tab') {
return;
}
e.preventDefault();
if (e.key === 'ArrowRight') {
onRightFocus?.();
}
if (e.key === 'ArrowLeft') {
onLeftFocus?.();
}
if (['ArrowUp', 'ArrowDown'].includes(e.key)) {
const step = e.key === 'ArrowUp' ? 1 : -1;
const newValue = getArrowByType(calculatedValue, step, picker);
if (flag) {
setFlag(false);
}
const tempDate = copyDate(date);
setDate(setDateByType(tempDate, newValue, picker, period));
}
if (e.key >= '0' && e.key <= '9') {
if (picker === '12hours') {
setPrevIntKey(e.key);
}
const newValue = calculateNewValue(e.key);
if (flag) {
onRightFocus?.();
}
setFlag((prev) => !prev);
const tempDate = copyDate(date);
setDate(setDateByType(tempDate, newValue, picker, period));
}
};
return (
<Input
ref={ref}
id={id || picker}
name={name || picker}
className={cn(
'w-[48px] text-center font-mono text-base tabular-nums focus:bg-accent focus:text-accent-foreground [&::-webkit-inner-spin-button]:appearance-none',
className,
)}
value={value || calculatedValue}
onChange={(e) => {
e.preventDefault();
onChange?.(e);
}}
type={type}
inputMode="decimal"
onKeyDown={(e) => {
onKeyDown?.(e);
handleKeyDown(e);
}}
{...props}
/>
);
},
);
TimePickerInput.displayName = 'TimePickerInput';
export { TimePickerInput };

View File

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

View File

@@ -0,0 +1,336 @@
import { vi } from 'vitest';
import {
convert12HourTo24Hour,
display12HourValue,
getArrowByType,
getDateByType,
getValid12Hour,
getValidArrow12Hour,
getValidArrowHour,
getValidArrowMinuteOrSecond,
getValidArrowNumber,
getValidHour,
getValidMinuteOrSecond,
getValidNumber,
isValid12Hour,
isValidHour,
isValidMinuteOrSecond,
set12Hours,
setDateByType,
setHours,
setMinutes,
setSeconds,
type TimePickerType,
} from './time-picker-utils';
// Mock TZDate if needed
vi.mock('@date-fns/tz', () => ({
TZDate: class MockTZDate extends Date {
timeZone: string;
constructor(date: string | Date, timeZone: string) {
super(date);
this.timeZone = timeZone;
}
},
}));
describe('time-picker-utils', () => {
describe('validation functions', () => {
test('isValidHour validates hour format correctly', () => {
// Valid hours
expect(isValidHour('00')).toBe(true);
expect(isValidHour('01')).toBe(true);
expect(isValidHour('12')).toBe(true);
expect(isValidHour('23')).toBe(true);
// Invalid hours
expect(isValidHour('24')).toBe(false);
expect(isValidHour('-1')).toBe(false);
expect(isValidHour('1')).toBe(false); // not padded
expect(isValidHour('ab')).toBe(false);
});
test('isValid12Hour validates 12-hour format correctly', () => {
// Valid 12-hour values
expect(isValid12Hour('01')).toBe(true);
expect(isValid12Hour('09')).toBe(true);
expect(isValid12Hour('12')).toBe(true);
// Invalid 12-hour values
expect(isValid12Hour('00')).toBe(false);
expect(isValid12Hour('13')).toBe(false);
expect(isValid12Hour('1')).toBe(false); // not padded
expect(isValid12Hour('ab')).toBe(false);
});
test('isValidMinuteOrSecond validates minute/second format correctly', () => {
// Valid minutes/seconds
expect(isValidMinuteOrSecond('00')).toBe(true);
expect(isValidMinuteOrSecond('01')).toBe(true);
expect(isValidMinuteOrSecond('30')).toBe(true);
expect(isValidMinuteOrSecond('59')).toBe(true);
// Invalid minutes/seconds
expect(isValidMinuteOrSecond('60')).toBe(false);
expect(isValidMinuteOrSecond('-1')).toBe(false);
expect(isValidMinuteOrSecond('1')).toBe(false); // not padded
expect(isValidMinuteOrSecond('ab')).toBe(false);
});
});
describe('number validation and correction functions', () => {
test('getValidNumber handles number validation correctly', () => {
// Basic validation
expect(getValidNumber('5', { max: 10 })).toBe('05');
expect(getValidNumber('15', { max: 10 })).toBe('10');
expect(getValidNumber('-1', { max: 10, min: 0 })).toBe('00');
// With looping
expect(getValidNumber('15', { max: 10, min: 0, loop: true })).toBe('00');
expect(getValidNumber('-1', { max: 10, min: 0, loop: true })).toBe('10');
// Invalid input
expect(getValidNumber('abc', { max: 10 })).toBe('00');
});
test('getValidHour returns valid 24-hour format', () => {
expect(getValidHour('12')).toBe('12');
expect(getValidHour('23')).toBe('23');
expect(getValidHour('24')).toBe('23'); // Capped at 23
expect(getValidHour('-1')).toBe('00'); // Min is 0
expect(getValidHour('abc')).toBe('00'); // Invalid input
});
test('getValid12Hour returns valid 12-hour format', () => {
// expect(getValid12Hour('06')).toBe('06');
// expect(getValid12Hour('12')).toBe('12');
expect(getValid12Hour('00')).toBe('01'); // Min is 1
expect(getValid12Hour('13')).toBe('12'); // Capped at 12
expect(getValid12Hour('abc')).toBe('00'); // Invalid input defaults to 00
});
test('getValidMinuteOrSecond returns valid minute/second format', () => {
expect(getValidMinuteOrSecond('30')).toBe('30');
expect(getValidMinuteOrSecond('59')).toBe('59');
expect(getValidMinuteOrSecond('60')).toBe('59'); // Capped at 59
expect(getValidMinuteOrSecond('-1')).toBe('00'); // Min is 0
expect(getValidMinuteOrSecond('abc')).toBe('00'); // Invalid input
});
});
describe('arrow navigation functions', () => {
test('getValidArrowNumber handles arrow navigation with looping', () => {
// Incrementing
expect(getValidArrowNumber('05', { min: 0, max: 10, step: 1 })).toBe(
'06',
);
expect(getValidArrowNumber('10', { min: 0, max: 10, step: 1 })).toBe(
'00',
); // Loops back to min
// Decrementing
expect(getValidArrowNumber('05', { min: 0, max: 10, step: -1 })).toBe(
'04',
);
expect(getValidArrowNumber('00', { min: 0, max: 10, step: -1 })).toBe(
'10',
); // Loops to max
// Invalid input
expect(getValidArrowNumber('abc', { min: 0, max: 10, step: 1 })).toBe(
'00',
);
});
test('getValidArrowHour handles hour navigation correctly', () => {
expect(getValidArrowHour('05', 1)).toBe('06');
expect(getValidArrowHour('23', 1)).toBe('00'); // Loops to 0
expect(getValidArrowHour('00', -1)).toBe('23'); // Loops to 23
});
test('getValidArrow12Hour handles 12-hour navigation correctly', () => {
expect(getValidArrow12Hour('05', 1)).toBe('06');
expect(getValidArrow12Hour('12', 1)).toBe('01'); // Loops to 1
expect(getValidArrow12Hour('01', -1)).toBe('12'); // Loops to 12
});
test('getValidArrowMinuteOrSecond handles minute/second navigation correctly', () => {
expect(getValidArrowMinuteOrSecond('30', 1)).toBe('31');
expect(getValidArrowMinuteOrSecond('59', 1)).toBe('00'); // Loops to 0
expect(getValidArrowMinuteOrSecond('00', -1)).toBe('59'); // Loops to 59
});
});
describe('date manipulation functions', () => {
test('setMinutes sets minutes correctly on a Date object', () => {
const date = new Date(2023, 0, 1, 12, 0, 0);
setMinutes(date, '30');
expect(date.getMinutes()).toBe(30);
// Invalid values are corrected
setMinutes(date, '60');
expect(date.getMinutes()).toBe(59);
});
test('setSeconds sets seconds correctly on a Date object', () => {
const date = new Date(2023, 0, 1, 12, 30, 0);
setSeconds(date, '45');
expect(date.getSeconds()).toBe(45);
// Invalid values are corrected
setSeconds(date, '60');
expect(date.getSeconds()).toBe(59);
});
test('setHours sets hours correctly on a Date object', () => {
const date = new Date(2023, 0, 1, 12, 30, 0);
setHours(date, '14');
expect(date.getHours()).toBe(14);
// Invalid values are corrected
setHours(date, '24');
expect(date.getHours()).toBe(23);
});
test('convert12HourTo24Hour converts 12-hour to 24-hour format correctly', () => {
// AM conversions
expect(convert12HourTo24Hour(1, 'AM')).toBe(1);
expect(convert12HourTo24Hour(11, 'AM')).toBe(11);
expect(convert12HourTo24Hour(12, 'AM')).toBe(0); // 12 AM is 00:00
// PM conversions
expect(convert12HourTo24Hour(1, 'PM')).toBe(13);
expect(convert12HourTo24Hour(11, 'PM')).toBe(23);
expect(convert12HourTo24Hour(12, 'PM')).toBe(12); // 12 PM is 12:00
});
test('set12Hours sets 12-hour format correctly on a Date object', () => {
const date = new Date(2023, 0, 1, 0, 0, 0);
// Morning hours (AM)
set12Hours(date, '09', 'AM');
expect(date.getHours()).toBe(9);
// 12 AM
set12Hours(date, '12', 'AM');
expect(date.getHours()).toBe(0);
// Afternoon/evening hours (PM)
set12Hours(date, '03', 'PM');
expect(date.getHours()).toBe(15);
// 12 PM
set12Hours(date, '12', 'PM');
expect(date.getHours()).toBe(12);
});
test('display12HourValue converts 24-hour to 12-hour display format', () => {
expect(display12HourValue(0)).toBe('12'); // 00:00 -> 12 AM
expect(display12HourValue(1)).toBe('01'); // 01:00 -> 1 AM
expect(display12HourValue(11)).toBe('11'); // 11:00 -> 11 AM
expect(display12HourValue(12)).toBe('12'); // 12:00 -> 12 PM
expect(display12HourValue(13)).toBe('01'); // 13:00 -> 1 PM
expect(display12HourValue(23)).toBe('11'); // 23:00 -> 11 PM
expect(display12HourValue(22)).toBe('10'); // 22:00 -> 10 PM
});
});
describe('integrated date manipulation functions', () => {
test('getDateByType returns date component according to the picker type', () => {
const date = new Date(2023, 0, 1, 14, 30, 45);
// Test hours
expect(getDateByType(date, 'hours')).toBe('14');
// Test minutes
expect(getDateByType(date, 'minutes')).toBe('30');
// Test seconds
expect(getDateByType(date, 'seconds')).toBe('45');
// Test 12-hour format
expect(getDateByType(date, '12hours')).toBe('02'); // 14:00 -> 2 PM
// Test 12 noon and midnight special cases
const noon = new Date(2023, 0, 1, 12, 0, 0);
expect(getDateByType(noon, '12hours')).toBe('12');
const midnight = new Date(2023, 0, 1, 0, 0, 0);
expect(getDateByType(midnight, '12hours')).toBe('12');
// Test with invalid picker type
expect(getDateByType(date, 'invalid' as TimePickerType)).toBe('00');
});
test('getArrowByType handles arrow navigation based on picker type', () => {
// Test hours
expect(getArrowByType('14', 1, 'hours')).toBe('15');
expect(getArrowByType('23', 1, 'hours')).toBe('00'); // Loops back to 00
// Test minutes
expect(getArrowByType('30', 1, 'minutes')).toBe('31');
expect(getArrowByType('59', 1, 'minutes')).toBe('00'); // Loops back to 00
// Test seconds
expect(getArrowByType('45', 1, 'seconds')).toBe('46');
expect(getArrowByType('59', 1, 'seconds')).toBe('00'); // Loops back to 00
// Test 12-hour format
expect(getArrowByType('09', 1, '12hours')).toBe('10');
expect(getArrowByType('12', 1, '12hours')).toBe('01'); // Loops back to 01
// Test with invalid picker type
expect(getArrowByType('14', 1, 'invalid' as TimePickerType)).toBe('00');
});
test('setDateByType updates date according to the picker type', () => {
const date = new Date(2023, 0, 1, 12, 30, 45);
// Test updating hours
const hourDate = setDateByType(date, '14', 'hours');
expect(hourDate.getHours()).toBe(14);
expect(hourDate.getMinutes()).toBe(30); // Other fields unchanged
expect(hourDate.getSeconds()).toBe(45); // Other fields unchanged
// Test updating minutes
const minuteDate = setDateByType(date, '15', 'minutes');
expect(minuteDate.getHours()).toBe(14); // Other fields unchanged
expect(minuteDate.getMinutes()).toBe(15);
expect(minuteDate.getSeconds()).toBe(45); // Other fields unchanged
// Test updating seconds
const secondDate = setDateByType(date, '20', 'seconds');
expect(secondDate.getHours()).toBe(14); // Other fields unchanged
expect(secondDate.getMinutes()).toBe(15); // Other fields unchanged
expect(secondDate.getSeconds()).toBe(20);
// Test updating 12-hour format with AM
const amDate = setDateByType(date, '09', '12hours', 'AM');
expect(amDate.getHours()).toBe(9);
// Test updating 12-hour format with PM
const pmDate = setDateByType(date, '09', '12hours', 'PM');
expect(pmDate.getHours()).toBe(21);
// Test 12 AM (midnight)
const midnightDate = setDateByType(date, '12', '12hours', 'AM');
expect(midnightDate.getHours()).toBe(0);
// Test 12 PM (noon)
const noonDate = setDateByType(date, '12', '12hours', 'PM');
expect(noonDate.getHours()).toBe(12);
// Test with missing period for 12-hour format
const missingPeriodDate = setDateByType(date, '09', '12hours');
expect(missingPeriodDate).toBe(date); // Should return original date unchanged
// Test with invalid picker type
const invalidTypeDate = setDateByType(
date,
'14',
'invalid' as TimePickerType,
);
expect(invalidTypeDate).toBe(date); // Should return original date unchanged
});
});
});

View File

@@ -0,0 +1,244 @@
import { TZDate } from '@date-fns/tz';
/**
* regular expression to check for valid hour format (01-23)
*/
export function isValidHour(value: string) {
return /^(0[0-9]|1[0-9]|2[0-3])$/.test(value);
}
/**
* regular expression to check for valid 12 hour format (01-12)
*/
export function isValid12Hour(value: string) {
return /^(0[1-9]|1[0-2])$/.test(value);
}
/**
* regular expression to check for valid minute format (00-59)
*/
export function isValidMinuteOrSecond(value: string) {
return /^[0-5][0-9]$/.test(value);
}
type GetValidNumberConfig = { max: number; min?: number; loop?: boolean };
export function getValidNumber(
value: string,
{ max, min = 0, loop = false }: GetValidNumberConfig,
) {
let numericValue = parseInt(value, 10);
if (!Number.isNaN(numericValue)) {
if (!loop) {
if (numericValue > max) {
numericValue = max;
}
if (numericValue < min) {
numericValue = min;
}
} else {
if (numericValue > max) {
numericValue = min;
}
if (numericValue < min) {
numericValue = max;
}
}
return numericValue.toString().padStart(2, '0');
}
return '00';
}
export function getValidHour(value: string) {
if (isValidHour(value)) {
return value;
}
return getValidNumber(value, { max: 23 });
}
export function getValid12Hour(value: string) {
if (isValid12Hour(value)) {
return value;
}
return getValidNumber(value, { min: 1, max: 12 });
}
export function getValidMinuteOrSecond(value: string) {
if (isValidMinuteOrSecond(value)) {
return value;
}
return getValidNumber(value, { max: 59 });
}
type GetValidArrowNumberConfig = {
min: number;
max: number;
step: number;
};
export function getValidArrowNumber(
value: string,
{ min, max, step }: GetValidArrowNumberConfig,
) {
let numericValue = parseInt(value, 10);
if (!Number.isNaN(numericValue)) {
numericValue += step;
return getValidNumber(String(numericValue), { min, max, loop: true });
}
return '00';
}
export function getValidArrowHour(value: string, step: number) {
return getValidArrowNumber(value, { min: 0, max: 23, step });
}
export function getValidArrow12Hour(value: string, step: number) {
return getValidArrowNumber(value, { min: 1, max: 12, step });
}
export function getValidArrowMinuteOrSecond(value: string, step: number) {
return getValidArrowNumber(value, { min: 0, max: 59, step });
}
export function setMinutes(date: Date, value: string) {
const minutes = getValidMinuteOrSecond(value);
date.setMinutes(parseInt(minutes, 10));
return date;
}
export function setSeconds(date: Date, value: string) {
const seconds = getValidMinuteOrSecond(value);
date.setSeconds(parseInt(seconds, 10));
return date;
}
export function setHours(date: Date, value: string) {
const hours = getValidHour(value);
date.setHours(parseInt(hours, 10));
return date;
}
/**
* handles value change of 12-hour input
* 12:00 PM is 12:00
* 12:00 AM is 00:00
*/
export function convert12HourTo24Hour(hour: number, period: Period) {
if (period === 'PM') {
if (hour <= 11) {
return hour + 12;
}
return hour;
}
if (period === 'AM') {
if (hour === 12) {
return 0;
}
return hour;
}
return hour;
}
export function set12Hours(date: Date, value: string, period: Period) {
const hours = parseInt(getValid12Hour(value), 10);
const convertedHours = convert12HourTo24Hour(hours, period);
date.setHours(convertedHours);
return date;
}
export type TimePickerType = 'minutes' | 'seconds' | 'hours' | '12hours';
export type Period = 'AM' | 'PM';
export function setDateByType(
date: Date,
value: string,
type: TimePickerType,
period?: Period,
) {
switch (type) {
case 'minutes':
return setMinutes(date, value);
case 'seconds':
return setSeconds(date, value);
case 'hours':
return setHours(date, value);
case '12hours': {
if (!period) {
return date;
}
return set12Hours(date, value, period);
}
default:
return date;
}
}
/**
* time is stored in the 24-hour form,
* but needs to be displayed to the user
* in its 12-hour representation
*/
export function display12HourValue(hours: number) {
if (hours === 0 || hours === 12) {
return '12';
}
if (hours >= 22) {
return `${hours - 12}`;
}
if (hours % 12 > 9) {
return `${hours}`;
}
return `0${hours % 12}`;
}
export function getDateByType(date: Date, type: TimePickerType) {
switch (type) {
case 'minutes':
return getValidMinuteOrSecond(String(date.getMinutes()));
case 'seconds':
return getValidMinuteOrSecond(String(date.getSeconds()));
case 'hours':
return getValidHour(String(date.getHours()));
case '12hours': {
const hours = display12HourValue(date.getHours());
return getValid12Hour(String(hours));
}
default:
return '00';
}
}
export function getArrowByType(
value: string,
step: number,
type: TimePickerType,
) {
switch (type) {
case 'minutes':
return getValidArrowMinuteOrSecond(value, step);
case 'seconds':
return getValidArrowMinuteOrSecond(value, step);
case 'hours':
return getValidArrowHour(value, step);
case '12hours':
return getValidArrow12Hour(value, step);
default:
return '00';
}
}
function isTZDate(date: Date | TZDate): date is TZDate {
return date instanceof TZDate;
}
export function copyDate(date: Date | TZDate) {
if (isTZDate(date)) {
const { timeZone } = date;
const dateTime = date.toISOString();
return new TZDate(dateTime, timeZone);
}
return new Date(date);
}

View File

@@ -0,0 +1,34 @@
import { VirtualizedCombobox } from '@/components/common/VirtualizedCombobox';
import { createTimezoneOptions } from '@/utils/timezoneUtils';
import { memo, useMemo } from 'react';
interface Props {
selectedTimezone: string;
onTimezoneSelect: (timezone: { value: string; label: string }) => void;
button?: React.JSX.Element;
dateTime: string;
}
function TimezonePicker({
selectedTimezone,
onTimezoneSelect,
button,
dateTime,
}: Props) {
const timezoneOptions = useMemo(
() => createTimezoneOptions(dateTime),
[dateTime],
);
return (
<VirtualizedCombobox
options={timezoneOptions}
selectedOption={selectedTimezone}
onSelectOption={onTimezoneSelect}
searchPlaceholder="Search timezones..."
button={button}
side="right"
/>
);
}
export default memo(TimezonePicker);

View File

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

View File

@@ -0,0 +1,252 @@
import { Button } from '@/components/ui/v3/button';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/v3/command';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/v3/popover';
import { cn } from '@/lib/utils';
import { useVirtualizer } from '@tanstack/react-virtual';
import { Check, ChevronsUpDown } from 'lucide-react';
import * as React from 'react';
type Option = {
value: string;
label: string;
key?: string;
};
interface VirtualizedCommandProps<O extends Option> {
height: string;
options: O[];
placeholder: string;
selectedOption: string;
onSelectOption?: (option: O) => void;
emptyText?: string;
}
function VirtualizedCommand<O extends Option>({
height,
options,
placeholder,
selectedOption,
onSelectOption,
emptyText,
}: VirtualizedCommandProps<O>) {
const [filteredOptions, setFilteredOptions] = React.useState<O[]>(options);
const [focusedIndex, setFocusedIndex] = React.useState(0);
const [isKeyboardNavActive, setIsKeyboardNavActive] = React.useState(false);
const parentRef = React.useRef(null);
const virtualizer = useVirtualizer({
count: filteredOptions.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 35,
});
const virtualOptions = virtualizer.getVirtualItems();
const scrollToIndex = (index: number) => {
virtualizer.scrollToIndex(index, {
align: 'center',
});
};
const handleSearch = (search: string) => {
setIsKeyboardNavActive(false);
setFilteredOptions(
options.filter((option) =>
option.label.toLowerCase().includes(search.toLowerCase()),
),
);
};
const handleKeyDown = (event: React.KeyboardEvent) => {
switch (event.key) {
case 'ArrowDown': {
event.preventDefault();
setIsKeyboardNavActive(true);
setFocusedIndex((prev) => {
const newIndex =
prev === -1 ? 0 : Math.min(prev + 1, filteredOptions.length - 1);
scrollToIndex(newIndex);
return newIndex;
});
break;
}
case 'ArrowUp': {
event.preventDefault();
setIsKeyboardNavActive(true);
setFocusedIndex((prev) => {
const newIndex =
prev === -1 ? filteredOptions.length - 1 : Math.max(prev - 1, 0);
scrollToIndex(newIndex);
return newIndex;
});
break;
}
case 'Enter': {
event.preventDefault();
if (filteredOptions[focusedIndex]) {
onSelectOption?.(filteredOptions[focusedIndex]);
}
break;
}
default:
break;
}
};
React.useEffect(() => {
if (selectedOption) {
const option = filteredOptions.find(
(opt) => opt.value === selectedOption,
);
if (option) {
const index = filteredOptions.indexOf(option);
setFocusedIndex(index);
}
}
}, [selectedOption, filteredOptions, virtualizer]);
return (
<Command shouldFilter={false} onKeyDown={handleKeyDown}>
<CommandInput onValueChange={handleSearch} placeholder={placeholder} />
<CommandList
ref={parentRef}
style={{
height,
width: '100%',
overflow: 'auto',
}}
onMouseDown={() => setIsKeyboardNavActive(false)}
onMouseMove={() => setIsKeyboardNavActive(false)}
>
<CommandEmpty>{emptyText || 'No item found.'}</CommandEmpty>
<CommandGroup>
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
}}
>
{virtualOptions.map((virtualOption) => (
<CommandItem
key={
filteredOptions[virtualOption.index].key ??
filteredOptions[virtualOption.index].value
}
disabled={isKeyboardNavActive}
className={cn(
'absolute left-0 top-0 w-full bg-transparent',
focusedIndex === virtualOption.index &&
'bg-accent text-accent-foreground',
isKeyboardNavActive &&
focusedIndex !== virtualOption.index &&
'aria-selected:bg-transparent aria-selected:text-primary',
)}
style={{
height: `${virtualOption.size}px`,
transform: `translateY(${virtualOption.start}px)`,
}}
value={filteredOptions[virtualOption.index].value}
onMouseEnter={() =>
!isKeyboardNavActive && setFocusedIndex(virtualOption.index)
}
onMouseLeave={() => !isKeyboardNavActive && setFocusedIndex(-1)}
onSelect={() => onSelectOption?.(filteredOptions[focusedIndex])}
>
<Check
className={cn(
'mr-2 h-4 w-4',
selectedOption ===
filteredOptions[virtualOption.index].value
? 'opacity-100'
: 'opacity-0',
)}
/>
{filteredOptions[virtualOption.index].label}
</CommandItem>
))}
</div>
</CommandGroup>
</CommandList>
</Command>
);
}
interface VirtualizedComboboxProps<O extends Option> {
options: O[];
searchPlaceholder?: string;
width?: string;
height?: string;
button?: React.JSX.Element;
onSelectOption?: (option: O) => void;
selectedOption: string;
align?: 'start' | 'center' | 'end';
side?: 'right' | 'top' | 'bottom' | 'left';
}
function VirtualizedCombobox<O extends Option>({
options,
searchPlaceholder = 'Search items...',
width,
height,
button,
onSelectOption,
selectedOption,
align = 'start',
side,
}: VirtualizedComboboxProps<O>) {
const [open, setOpen] = React.useState(false);
const defaultButton = (
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="justify-between"
style={{
width,
}}
>
{selectedOption
? options.find((option) => option.value === selectedOption).value
: searchPlaceholder}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>{button || defaultButton}</PopoverTrigger>
<PopoverContent
className="p-0"
style={{ width }}
align={align}
side={side}
>
<VirtualizedCommand
height={height}
options={options}
placeholder={searchPlaceholder}
selectedOption={selectedOption}
onSelectOption={(currentValue) => {
onSelectOption(currentValue);
setOpen(false);
}}
/>
</PopoverContent>
</Popover>
);
}
export default VirtualizedCombobox;

View File

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

View File

@@ -3,6 +3,7 @@ import { cva, type VariantProps } from 'class-variance-authority';
import * as React from 'react';
import { cn } from '@/lib/utils';
import { Loader2 } from 'lucide-react';
const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
@@ -53,4 +54,16 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
);
Button.displayName = 'Button';
export { Button, buttonVariants };
const ButtonWithLoading = React.forwardRef<
HTMLButtonElement,
ButtonProps & { loading?: boolean }
>(({ loading, disabled, children, ...props }, ref) => {
return (
<Button disabled={loading || disabled} ref={ref} {...props}>
{loading && <Loader2 className="mr-2 animate-spin" />}
{children}
</Button>
);
});
export { Button, buttonVariants, ButtonWithLoading };

View File

@@ -0,0 +1,80 @@
'use client';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import {
DayPicker,
type DayPickerProps,
type StyledComponent,
} from 'react-day-picker';
import { buttonVariants } from '@/components/ui/v3/button';
import { cn } from '@/lib/utils';
const IconLeft = ({ className, ...props }: StyledComponent) => (
<ChevronLeft className={cn('h-4 w-4', className)} {...props} />
);
const IconRight = ({ className, ...props }: StyledComponent) => (
<ChevronRight className={cn('h-4 w-4', className)} {...props} />
);
function Calendar({
className,
classNames,
showOutsideDays = true,
...props
}: DayPickerProps) {
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn('p-3', className)}
classNames={{
months: 'flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0',
month: 'space-y-4',
caption: 'flex justify-center pt-1 relative items-center',
caption_label: 'text-sm font-medium',
nav: 'space-x-1 flex items-center',
nav_button: cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
),
nav_button_previous: 'absolute left-1',
nav_button_next: 'absolute right-1',
table: 'w-full border-collapse space-y-1',
head_row: 'flex',
head_cell:
'text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]',
row: 'flex w-full mt-2',
cell: cn(
'relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md',
props.mode === 'range'
? '[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md'
: '[&:has([aria-selected])]:rounded-md',
),
day: cn(
buttonVariants({ variant: 'ghost' }),
'h-8 w-8 p-0 font-normal aria-selected:opacity-100',
),
day_range_start: 'day-range-start',
day_range_end: 'day-range-end',
day_selected:
'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
day_today: 'bg-accent text-accent-foreground',
day_outside:
'day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground',
day_disabled: 'text-muted-foreground opacity-50',
day_range_middle:
'aria-selected:bg-accent aria-selected:text-accent-foreground',
day_hidden: 'invisible',
...classNames,
}}
components={{
IconLeft,
IconRight,
}}
{...props}
/>
);
}
Calendar.displayName = 'Calendar';
export { Calendar };

View File

@@ -27,10 +27,15 @@ const DialogOverlay = React.forwardRef<
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
interface DialogContentProps
extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
disableOutsideClick?: boolean;
}
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
DialogContentProps
>(({ className, children, disableOutsideClick, ...props }, ref) => (
<DialogPortal>
<DialogOverlay>
<DialogPrimitive.Content
@@ -39,6 +44,11 @@ const DialogContent = React.forwardRef<
'relative z-50 grid w-full max-w-lg gap-4 bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:rounded-lg md:w-full',
className,
)}
onInteractOutside={
disableOutsideClick
? (e) => e.preventDefault()
: props.onInteractOutside
}
{...props}
>
{children}

View File

@@ -0,0 +1,56 @@
import { cn } from '@/lib/utils';
import { type VariantProps, cva } from 'class-variance-authority';
import { Loader2 } from 'lucide-react';
import React from 'react';
const spinnerVariants = cva('flex-col items-center justify-center', {
variants: {
show: {
true: 'flex',
false: 'hidden',
},
},
defaultVariants: {
show: true,
},
});
const loaderVariants = cva('animate-spin text-primary', {
variants: {
size: {
small: 'size-6',
medium: 'size-8',
large: 'size-12',
},
},
defaultVariants: {
size: 'medium',
},
});
interface SpinnerContentProps
extends VariantProps<typeof spinnerVariants>,
VariantProps<typeof loaderVariants> {
className?: string;
children?: React.ReactNode;
}
export function Spinner({
size,
show,
children,
className,
}: SpinnerContentProps) {
return (
<span className={spinnerVariants({ show })}>
<Loader2
className={cn(
loaderVariants({ size }),
className,
'stroke-[#1e324b] dark:stroke-[#dfecf5]',
)}
/>
{children}
</span>
);
}

View File

@@ -0,0 +1,53 @@
import * as TabsPrimitive from '@radix-ui/react-tabs';
import * as React from 'react';
import { cn } from '@/lib/utils';
const Tabs = TabsPrimitive.Root;
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
className,
)}
{...props}
/>
));
TabsList.displayName = TabsPrimitive.List.displayName;
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
className,
)}
{...props}
/>
));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
className,
)}
{...props}
/>
));
TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsContent, TabsList, TabsTrigger };

View File

@@ -15,6 +15,9 @@ query GetPostgresSettings($appId: uuid!) {
}
enablePublicAccess
}
pitr {
retention
}
}
}
}

View File

@@ -0,0 +1,31 @@
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/v3/alert';
import { cn } from '@/lib/utils';
import { type PropsWithChildren, type ReactNode } from 'react';
interface Props {
title?: string;
icon?: ReactNode;
}
function InfoAlert({ children, title, icon }: PropsWithChildren<Props>) {
const alertClassNames = cn('bg-[#ebf3ff] dark:bg-muted', {
'flex gap-2 items-center': !!icon,
});
const descClassNames = cn('text-[0.9375rem] leading-[22px]', {
'text-[0.875rem] leading-[1rem]': !!icon,
});
return (
<Alert className={alertClassNames}>
{icon && <div>{icon}</div>}
<div>
{title && <AlertTitle>{title}</AlertTitle>}
<AlertDescription className={descClassNames}>
{children}
</AlertDescription>
</div>
</Alert>
);
}
export default InfoAlert;

View File

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

View File

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

View File

@@ -0,0 +1,24 @@
import { useIsPiTREnabled } from '@/features/orgs/hooks/useIsPiTREnabled';
import { useEffect, useState } from 'react';
function useDatabasePiTRSettings() {
const [isPiTREnabled, setIsPiTREnabled] = useState(false);
const [isNotSwitchTouched, setIsNotSwitchTouched] = useState(true);
const { isPiTREnabled: isPiTREnabledData } = useIsPiTREnabled();
useEffect(() => {
setIsPiTREnabled(isPiTREnabledData);
}, [isPiTREnabledData]);
const isSwitchDisabled =
isPiTREnabled === isPiTREnabledData || isNotSwitchTouched;
return {
isPiTREnabled,
setIsPiTREnabled,
isSwitchDisabled,
setIsNotSwitchTouched,
};
}
export default useDatabasePiTRSettings;

View File

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

View File

@@ -0,0 +1,33 @@
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useGetProjectsQuery } from '@/utils/__generated__/graphql';
import { useMemo } from 'react';
function useImportBackupSourceProjectList() {
const { org } = useCurrentOrg();
const { project } = useProject();
const currentProjectRegionId = project?.region.id;
const projectId = project?.id;
const { data, loading } = useGetProjectsQuery({
variables: {
orgSlug: org?.slug,
},
});
const filteredProjects = useMemo(
() =>
(data?.apps || [])
.filter(
(app) =>
app.id !== projectId && app.region.id === currentProjectRegionId,
)
.map((app) => ({
label: `${app.name} (${app.region.name})`,
id: app.id,
})),
[data?.apps, currentProjectRegionId, projectId],
);
return { filteredProjects, loading };
}
export default useImportBackupSourceProjectList;

View File

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

View File

@@ -0,0 +1,20 @@
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { isNotEmptyValue as isNotNull } from '@/lib/utils';
import { useGetPostgresSettingsQuery } from '@/utils/__generated__/graphql';
import { useMemo } from 'react';
function useIsPiTREnabled() {
const { project } = useProject();
const { data, loading } = useGetPostgresSettingsQuery({
variables: { appId: project?.id },
});
const isPiTREnabled = useMemo(
() => isNotNull(data?.config.postgres.pitr?.retention),
[data?.config.postgres.pitr?.retention],
);
return { isPiTREnabled, loading };
}
export default useIsPiTREnabled;

View File

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

View File

@@ -0,0 +1,30 @@
import { isNotEmptyValue } from '@/lib/utils';
import { useGetPostgresSettingsLazyQuery } from '@/utils/__generated__/graphql';
import { useEffect, useMemo, useRef } from 'react';
function useIsPiTREnabledLazy(appId?: string) {
const [getPostgresSettings, { data, loading }] =
useGetPostgresSettingsLazyQuery({
fetchPolicy: 'no-cache',
});
const prevAppId = useRef<string>();
useEffect(() => {
async function fetchPiTRSettings() {
if (isNotEmptyValue(appId) && prevAppId.current !== appId) {
await getPostgresSettings({ variables: { appId } });
prevAppId.current = appId;
}
}
fetchPiTRSettings();
}, [appId, getPostgresSettings]);
const isPiTREnabled = useMemo(
() => isNotEmptyValue(data?.config.postgres.pitr?.retention),
[data?.config.postgres.pitr?.retention],
);
return { isPiTREnabled, loading };
}
export default useIsPiTREnabledLazy;

View File

@@ -0,0 +1,33 @@
import { isNotEmptyValue } from '@/lib/utils';
import { useGetPiTrBaseBackupsLazyQuery } from '@/utils/__generated__/graphql';
import { triggerToast } from '@/utils/toast';
import { useEffect, useState } from 'react';
function usePiTRBaseBackups(appId: string) {
const [earliestBackupDate, setEarliestBackup] = useState<string>();
const [fetchPiTRBaseBackups, { loading }] = useGetPiTrBaseBackupsLazyQuery();
useEffect(() => {
async function getPiTRBaseBackups() {
if (appId) {
const { data, error } = await fetchPiTRBaseBackups({
variables: { appId },
});
if (error) {
triggerToast(
'An error occurred while fetching the Point-in-Time backup data. Please try again later.',
);
}
if (isNotEmptyValue(data.getPiTRBaseBackups)) {
const earliestBackup = data.getPiTRBaseBackups.slice(-1).pop();
setEarliestBackup(earliestBackup.date);
}
}
}
getPiTRBaseBackups();
}, [appId, fetchPiTRBaseBackups]);
return { earliestBackupDate, loading };
}
export default usePiTRBaseBackups;

View File

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

View File

@@ -0,0 +1,34 @@
import { useRestoreApplicationDatabasePiTrMutation } from '@/utils/__generated__/graphql';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
function useRestoreApplicationDatabasePiTR() {
const [restoreApplicationDatabaseMutation, { loading }] =
useRestoreApplicationDatabasePiTrMutation();
async function restoreApplicationDatabase(
variables: { appId: string; recoveryTarget: string; fromAppId?: string },
onCompleted?: () => void,
) {
await execPromiseWithErrorToast(
async () => {
await restoreApplicationDatabaseMutation({
variables,
onCompleted,
});
},
{
loadingMessage: 'Starting restore from backup...',
successMessage: 'Backup has been scheduled successfully.',
errorMessage:
'An error occurred while attempting to schedule a backup. Please try again.',
},
);
}
return {
restoreApplicationDatabase,
loading,
};
}
export default useRestoreApplicationDatabasePiTR;

View File

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

View File

@@ -0,0 +1,51 @@
import { useCallback, useState } from 'react';
import { RECOVERY_RETENTION_PERIOD_7 } from '@/features/orgs/projects/database/dataGrid/utils/postgresqlConstants/postgresqlConstants';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
import { useUpdateConfigMutation } from '@/utils/__generated__/graphql';
function useUpdateDatabasePiTRConfig() {
const { project } = useProject();
const [loading, setLoading] = useState(false);
const [updateConfig] = useUpdateConfigMutation();
const updatePiTRConfig = useCallback(
async (isPiTREnabled: boolean) => {
const pitr = isPiTREnabled
? { retention: RECOVERY_RETENTION_PERIOD_7 }
: null;
const updateConfigMutationPromise = updateConfig({
variables: {
appId: project?.id,
config: {
postgres: {
pitr,
},
},
},
});
await execPromiseWithErrorToast(
async () => {
setLoading(true);
await updateConfigMutationPromise;
setLoading(false);
},
{
loadingMessage: `${isPiTREnabled ? 'Enabling' : 'Disabling'} Point-in-Time recovery...`,
successMessage: `Point-in-Time has been ${isPiTREnabled ? 'enabled' : 'disabled'} successfully.`,
errorMessage:
'An error occurred while trying to enable Point-in-Time recovery.',
onError: () => setLoading(false),
},
);
},
[updateConfig, project?.id],
);
return { updatePiTRConfig, loading };
}
export default useUpdateDatabasePiTRConfig;

View File

@@ -20,13 +20,11 @@ import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatfo
import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimirClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
import { type RemoteAppUser } from '@/pages/orgs/[orgSlug]/projects/[appSubdomain]/users';
import type { DialogFormProps } from '@/types/common';
import {
RemoteAppGetUsersAndAuthRolesDocument,
useGetProjectLocalesQuery,
useGetRolesPermissionsQuery,
useUpdateRemoteAppUserMutation,
} from '@/utils/__generated__/graphql';
import { copy } from '@/utils/copy';
@@ -114,7 +112,6 @@ export default function EditUserForm({
const { onDirtyStateChange, openDialog } = useDialog();
const { project } = useProject();
const isAnonymous = user.roles.some((role) => role.role === 'anonymous');
const [isUserBanned, setIsUserBanned] = useState(user.disabled);
const remoteProjectGQLClient = useRemoteApplicationGQLClient();
@@ -198,15 +195,6 @@ export default function EditUserForm({
});
}
const { data: dataRoles } = useGetRolesPermissionsQuery({
variables: { appId: project?.id },
...(!isPlatform ? { client: localMimirClient } : {}),
});
const allAvailableProjectRoles = getUserRoles(
dataRoles?.config?.auth?.user?.roles?.allowed,
);
const { data } = useGetProjectLocalesQuery({
variables: {
appId: project?.id,
@@ -489,47 +477,46 @@ export default function EditUserForm({
))}
</div>
</Box>
{!isAnonymous && (
<Box
component="section"
className="grid grid-flow-row gap-y-10 p-6"
<Box component="section" className="grid grid-flow-row gap-y-10 p-6">
<ControlledSelect
{...register('defaultRole')}
id="defaultRole"
name="defaultRole"
variant="inline"
label="Default Role"
slotProps={{ root: { className: 'truncate' } }}
hideEmptyHelperText
fullWidth
error={!!errors.defaultRole}
helperText={errors?.defaultRole?.message}
>
<ControlledSelect
{...register('defaultRole')}
id="defaultRole"
name="defaultRole"
variant="inline"
label="Default Role"
slotProps={{ root: { className: 'truncate' } }}
hideEmptyHelperText
fullWidth
error={!!errors.defaultRole}
helperText={errors?.defaultRole?.message}
>
{allAvailableProjectRoles.map((role) => (
<Option key={role.name} value={role.name}>
{role.name}
</Option>
{roles.map((role, i) => (
<Option
// eslint-disable-next-line react/no-array-index-key
key={`defaultRoles.${i}`}
value={Object.keys(role)[0]}
>
{Object.keys(role)[0]}
</Option>
))}
</ControlledSelect>
<div className="grid grid-flow-row place-content-start gap-6 lg:grid-flow-col lg:grid-cols-8">
<InputLabel as="h3" className="col-span-2">
Allowed Roles
</InputLabel>
<div className="col-span-3 grid grid-flow-row gap-6">
{roles.map((role, i) => (
<ControlledCheckbox
id={`roles.${i}`}
label={Object.keys(role)[0]}
name={`roles.${i}`}
// eslint-disable-next-line react/no-array-index-key
key={`roles.${i}`}
/>
))}
</ControlledSelect>
<div className="grid grid-flow-row place-content-start gap-6 lg:grid-flow-col lg:grid-cols-8">
<InputLabel as="h3" className="col-span-2">
Allowed Roles
</InputLabel>
<div className="col-span-3 grid grid-flow-row gap-6">
{roles.map((role, i) => (
<ControlledCheckbox
id={`roles.${i}`}
label={Object.keys(role)[0]}
name={`roles.${i}`}
// eslint-disable-next-line react/no-array-index-key
key={`roles.${i}`}
/>
))}
</div>
</div>
</Box>
)}
</div>
</Box>
<Box component="section" className="grid grid-flow-row gap-8 p-6">
<Input
{...register('metadata', { onChange: handleMetadataChange })}

View File

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

View File

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

View File

@@ -0,0 +1,27 @@
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/v3/tabs';
import { ImportBackupTabContent } from '@/features/orgs/projects/backups/components/ImportBackupTabContent';
import { PointInTimeTabsContent } from '@/features/orgs/projects/backups/components/PointInTimeTabsContent';
import { ScheduledBackupTabContent } from '@/features/orgs/projects/backups/components/ScheduledBackupTabContent';
import { memo, useState } from 'react';
function BackupsContent({ isPiTREnabled }: { isPiTREnabled: boolean }) {
const [tab, setTab] = useState(() =>
isPiTREnabled ? 'pointInTime' : 'scheduledBackups',
);
return (
<Tabs value={tab} onValueChange={setTab}>
<TabsList>
<TabsTrigger value="scheduledBackups">Scheduled backups</TabsTrigger>
<TabsTrigger value="pointInTime">Point-in-time</TabsTrigger>
<TabsTrigger value="importBackup">Import backup</TabsTrigger>
</TabsList>
<div className="pt-7">
<ScheduledBackupTabContent />
{tab === 'pointInTime' && <PointInTimeTabsContent />}
{tab === 'importBackup' && <ImportBackupTabContent />}
</div>
</Tabs>
);
}
export default memo(BackupsContent);

View File

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

View File

@@ -0,0 +1,43 @@
import { TabsContent } from '@/components/ui/v3/tabs';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useState } from 'react';
import SourceProjectBackupInfo from './SourceProjectBackupInfo';
import SourceProjectSelect from './SourceProjectSelect';
function ImportBackupContent() {
const { project } = useProject();
const [sourceProject, setSourceProject] = useState<{
label: string;
id: string;
}>();
function handleProjectSelect(selectedProject: { label: string; id: string }) {
setSourceProject(selectedProject);
}
const title = sourceProject
? `Import backup from ${sourceProject.label}`
: '';
return (
<TabsContent value="importBackup">
<div className="flex flex-col gap-8">
<div>
<h1 className="mb-4 text-base leading-5">
<strong>Target project:</strong> {project?.name} (
{project?.region.name})
</h1>
<SourceProjectSelect
projectId={sourceProject?.id}
onProjectSelect={handleProjectSelect}
/>
</div>
{sourceProject && (
<SourceProjectBackupInfo appId={sourceProject.id} title={title} />
)}
</div>
</TabsContent>
);
}
export default ImportBackupContent;

View File

@@ -0,0 +1,18 @@
import { InfoAlert } from '@/features/orgs/components/InfoAlert';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { CircleAlert } from 'lucide-react';
function NoOtherProjectsInRegion() {
const { project } = useProject();
return (
<InfoAlert
title={`There are no other projects within the region: ${project.region.name}`}
icon={<CircleAlert className="h-[38px] w-[38px]" />}
>
Backups may be imported from projects that are in the same region and
organization.
</InfoAlert>
);
}
export default NoOtherProjectsInRegion;

View File

@@ -0,0 +1,15 @@
import { InfoAlert } from '@/features/orgs/components/InfoAlert';
import { DatabaseZap } from 'lucide-react';
function PiTRNotEnabledOnSourceProject() {
return (
<InfoAlert
title="Point-in-Time recovery is not enabled on the selected project"
icon={<DatabaseZap className="h-[38px] w-[38px]" />}
>
Importing from scheduled backups is not supported yet. Coming soon!
</InfoAlert>
);
}
export default PiTRNotEnabledOnSourceProject;

View File

@@ -0,0 +1,25 @@
import { useIsPiTREnabledLazy } from '@/features/orgs/hooks/useIsPiTREnabledLazy';
import { PointInTimeBackupInfo } from '@/features/orgs/projects/backups/components/common/PointInTimeBackupInfo';
import PiTRNotEnabledOnSourceProject from './PiTRNotEnabledOnSourceProject';
interface Props {
appId: string;
title?: string;
}
function SourceProjectBackupInfo({ appId, title }: Props) {
const { isPiTREnabled } = useIsPiTREnabledLazy(appId);
return isPiTREnabled ? (
<PointInTimeBackupInfo
appId={appId}
title={title}
dialogTitle="Import backup"
dialogButtonText="Import backup"
dialogTriggerText="Start import"
/>
) : (
<PiTRNotEnabledOnSourceProject />
);
}
export default SourceProjectBackupInfo;

View File

@@ -0,0 +1,53 @@
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/v3/select';
import { useImportBackupSourceProjectList } from '@/features/orgs/hooks/useImportBackupSourceProjectList';
import { isEmptyValue } from '@/lib/utils';
import NoOtherProjectsInRegion from './NoOtherProjectsInRegion';
interface Props {
onProjectSelect: (project: { label: string; id: string }) => void;
projectId: string;
}
function SourceProjectSelect({ onProjectSelect, projectId }: Props) {
const { filteredProjects, loading } = useImportBackupSourceProjectList();
if (!loading && isEmptyValue(filteredProjects)) {
return <NoOtherProjectsInRegion />;
}
function handleChange(value: string) {
const selectedProject = filteredProjects.find((fp) => fp.id === value);
onProjectSelect(selectedProject);
}
return (
<div className="w-max">
<p className="pb-1 text-[#21324B] dark:text-[#DFECF5]">Source project</p>
<Select value={projectId} onValueChange={handleChange} disabled={loading}>
<SelectTrigger>
<SelectValue placeholder="Select a project to import backup from" />
</SelectTrigger>
<SelectContent>
{filteredProjects.map((project) => (
<SelectItem key={project.id} value={project.id}>
{project.label}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="pt-1 text-[#9CA7B7] dark:text-[#68717A]">
Backups can be imported from projects that are in the same organization
and region.
</p>
</div>
);
}
export default SourceProjectSelect;

View File

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

View File

@@ -0,0 +1,24 @@
import { InfoAlert } from '@/features/orgs/components/InfoAlert';
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import Link from 'next/link';
function PiTRNotEnabled() {
const { org } = useCurrentOrg();
const { project } = useProject();
return (
<InfoAlert>
To enable Point-in-Time recovery, enable it in the{' '}
<Link
href={`/orgs/${org?.slug}/projects/${project?.subdomain}/settings/database`}
className="text-[0.9375rem] leading-[1.375rem] text-[#0052cd] hover:underline dark:text-[#3888ff]"
target="_blank"
rel="noopener noreferrer"
>
database settings.
</Link>
</InfoAlert>
);
}
export default PiTRNotEnabled;

View File

@@ -0,0 +1,15 @@
import { PointInTimeBackupInfo } from '@/features/orgs/projects/backups/components/common/PointInTimeBackupInfo';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import RecoveryRetentionPeriod from './RecoveryRetentionPeriod';
function PointInTimeRecovery() {
const { project } = useProject();
return (
<div className="flex flex-col gap-[1.875rem]">
<RecoveryRetentionPeriod />
<PointInTimeBackupInfo appId={project?.id} />
</div>
);
}
export default PointInTimeRecovery;

View File

@@ -0,0 +1,17 @@
import { Spinner } from '@/components/ui/v3/spinner';
import { TabsContent } from '@/components/ui/v3/tabs';
import { useIsPiTREnabled } from '@/features/orgs/hooks/useIsPiTREnabled';
import PiTRNotEnabled from './PiTRNotEnabled';
import PointInTimeRecovery from './PointInTimeRecovery';
function PointInTimeTabsContent() {
const { isPiTREnabled, loading } = useIsPiTREnabled();
const content = isPiTREnabled ? <PointInTimeRecovery /> : <PiTRNotEnabled />;
return (
<TabsContent value="pointInTime">
{loading ? <Spinner /> : content}
</TabsContent>
);
}
export default PointInTimeTabsContent;

View File

@@ -0,0 +1,16 @@
import { InfoAlert } from '@/features/orgs/components/InfoAlert';
import { CalendarClock } from 'lucide-react';
function RecoveryRetentionPeriod() {
return (
<InfoAlert
title="Recovery retention period"
icon={<CalendarClock className="h-[38px] w-[38px]" />}
>
Database changes are retained for up to 7 days, allowing restoration to
any point within this period.
</InfoAlert>
);
}
export default RecoveryRetentionPeriod;

View File

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

View File

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

View File

@@ -6,9 +6,9 @@ import { TableContainer } from '@/components/ui/v2/TableContainer';
import { TableHead } from '@/components/ui/v2/TableHead';
import { TableRow } from '@/components/ui/v2/TableRow';
import { Text } from '@/components/ui/v2/Text';
import { BackupListItem } from '@/features/orgs/projects/backups/components/BackupListItem';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useGetApplicationBackupsQuery } from '@/utils/__generated__/graphql';
import BackupListItem from './BackupListItem';
export default function BackupList() {
const { project, loading: loadingProject } = useProject();

View File

@@ -2,13 +2,13 @@ import { useDialog } from '@/components/common/DialogProvider';
import { Button } from '@/components/ui/v2/Button';
import { TableCell } from '@/components/ui/v2/TableCell';
import { TableRow } from '@/components/ui/v2/TableRow';
import { RestoreBackupModal } from '@/features/orgs/projects/backups/components/RestoreBackupModal';
import type { Backup } from '@/types/application';
import { useGetBackupPresignedUrlLazyQuery } from '@/utils/__generated__/graphql';
import { prettifySize } from '@/utils/prettifySize';
import { triggerToast } from '@/utils/toast';
import { format, formatDistanceStrict, parseISO } from 'date-fns';
import { twMerge } from 'tailwind-merge';
import RestoreBackupModal from './RestoreBackupModal';
export interface BackupListItemProps {
/**

View File

@@ -0,0 +1,12 @@
import { InfoAlert } from '@/features/orgs/components/InfoAlert';
function PiTREnabledInfoBanner() {
return (
<InfoAlert>
With PiTR enabled, Scheduled backups are no longer taken. PiTR provides
more precise recovery, making additional backups unnecessary.
</InfoAlert>
);
}
export default PiTREnabledInfoBanner;

View File

@@ -0,0 +1,36 @@
import { Text } from '@/components/ui/v2/Text';
import { Spinner } from '@/components/ui/v3/spinner';
import { TabsContent } from '@/components/ui/v3/tabs';
import { useIsPiTREnabled } from '@/features/orgs/hooks/useIsPiTREnabled';
import BackupList from './BackupList';
import PiTREnabledInfoBanner from './PiTREnabledInfoBanner';
function ScheduledBackupTabContent() {
const { isPiTREnabled, loading } = useIsPiTREnabled();
const content = isPiTREnabled ? (
<PiTREnabledInfoBanner />
) : (
<>
<div>
<Text variant="h3" className="pb-2">
Database
</Text>
<Text color="secondary">
The database backup includes database schema, database data and Hasura
metadata. It does not include the actual files in Storage.
</Text>
</div>
<BackupList />
</>
);
return (
<TabsContent value="scheduledBackups">
<div className="grid w-full grid-flow-row gap-6">
{loading ? <Spinner /> : content}
</div>
</TabsContent>
);
}
export default ScheduledBackupTabContent;

View File

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

View File

@@ -0,0 +1,46 @@
import { Button } from '@/components/ui/v3/button';
import { DialogFooter } from '@/components/ui/v3/dialog';
import Link from 'next/link';
import type { PropsWithChildren } from 'react';
import { memo } from 'react';
function LogsLink({ href, children }: PropsWithChildren<{ href: string }>) {
return (
<Link
href={href}
className="text-[0.9375rem] leading-[1.375rem] text-[#0052cd] hover:underline dark:text-[#3888ff]"
target="_blank"
rel="noopener noreferrer"
>
{children}
</Link>
);
}
interface Props {
onClose: () => void;
orgSlug: string;
subdomain: string;
}
//
function BackupScheduledInfo({ onClose, orgSlug, subdomain }: Props) {
return (
<>
<p>Your backup has been scheduled successfully and will start shortly.</p>
<p>
To follow its process go to the{' '}
<LogsLink href={`/orgs/${orgSlug}/projects/${subdomain}/logs`}>
Logs page
</LogsLink>{' '}
and select the service &quot;Backup Job&quot; to see the restore logs.
</p>
<DialogFooter>
<Button type="button" onClick={onClose}>
Close
</Button>
</DialogFooter>
</>
);
}
export default memo(BackupScheduledInfo);

View File

@@ -0,0 +1,55 @@
import { TimezonePicker } from '@/components/common/TimezonePicker';
import { Button } from '@/components/ui/v3/button';
import { Spinner } from '@/components/ui/v3/spinner';
import { getDateTimeStringWithUTCOffset } from '@/features/orgs/projects/backups/utils/getDateTimeStringWithUTCOffset';
import { isEmptyValue } from '@/lib/utils';
import { guessTimezone } from '@/utils/timezoneUtils';
import { useState } from 'react';
interface Props {
dateTime: string;
loading: boolean;
}
function EarliestBackupDateTime({ dateTime }: Pick<Props, 'dateTime'>) {
const [selectedTimezone, setTimezone] = useState<string>(() =>
guessTimezone(),
);
function handleSelect(tz: { value: string; label: string }) {
setTimezone(tz.value);
}
return (
<p className="flex items-center gap-2 text-[1.125rem]">
<span data-testid="EarliestBackupDateTime">
{getDateTimeStringWithUTCOffset(dateTime, selectedTimezone)}
</span>
<TimezonePicker
dateTime={dateTime}
selectedTimezone={selectedTimezone}
onTimezoneSelect={handleSelect}
button={
<Button className="h-auto p-0" variant="link">
Change timezone
</Button>
}
/>
</p>
);
}
function EarliestBackup({ dateTime, loading }: Props) {
if (loading) {
return (
<div className="flex h-[27px] max-w-fit">
<Spinner size="small" />
</div>
);
}
const hasNoPiTRBackups = !loading && isEmptyValue(dateTime);
if (hasNoPiTRBackups) {
return <p className="text-[1.125rem]">Project has no backups yet.</p>;
}
return <EarliestBackupDateTime dateTime={dateTime} />;
}
export default EarliestBackup;

View File

@@ -0,0 +1,240 @@
import {
fetchEmptyPiTRBaseBackups,
fetchPiTRBaseBackups,
mockApplication,
mockMatchMediaValue,
} from '@/tests/mocks';
import tokenQuery from '@/tests/msw/mocks/rest/tokenQuery';
import { render, screen, waitFor } from '@/tests/orgs/testUtils';
import userEvent from '@testing-library/user-event';
import { setupServer } from 'msw/node';
import { vi } from 'vitest';
import { getOrganization } from '@/tests/msw/mocks/graphql/getOrganizationQuery';
import { getProjectQuery } from '@/tests/msw/mocks/graphql/getProjectQuery';
import PointInTimeBackupInfo from './PointInTimeBackupInfo';
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation(mockMatchMediaValue),
});
const server = setupServer(tokenQuery);
const mocks = vi.hoisted(() => ({
useGetPiTrBaseBackupsLazyQuery: vi.fn(),
fetchPiTRBaseBackups: vi.fn(),
restoreApplicationDatabase: vi.fn(),
}));
vi.mock('@/utils/__generated__/graphql', async () => {
const actual = await vi.importActual<any>('@/utils/__generated__/graphql');
return {
...actual,
useGetPiTrBaseBackupsLazyQuery: mocks.useGetPiTrBaseBackupsLazyQuery,
};
});
vi.mock('@/utils/timezoneUtils', async () => {
const actualTimezoneUtils = await vi.importActual<any>(
'@/utils/timezoneUtils',
);
return {
...actualTimezoneUtils,
guessTimezone: () => 'Europe/Helsinki',
};
});
vi.mock('@/features/orgs/hooks/useRestoreApplicationDatabasePiTR', () => ({
useRestoreApplicationDatabasePiTR: () => ({
restoreApplicationDatabase: mocks.restoreApplicationDatabase,
loading: false,
}),
}));
vi.mock('@/features/orgs/projects/hooks/useProject', async () => ({
useProject: () => ({ project: mockApplication }),
}));
describe('PointInTimeBackupInfo', () => {
beforeAll(() => {
process.env.NEXT_PUBLIC_NHOST_PLATFORM = 'true';
process.env.NEXT_PUBLIC_ENV = 'production';
server.listen();
});
afterAll(() => {
server.close();
vi.restoreAllMocks();
});
test('will fetch the earliest backup and will display the date in with timezone', async () => {
server.use(getOrganization);
server.use(getProjectQuery);
mocks.useGetPiTrBaseBackupsLazyQuery.mockImplementation(() => [
fetchPiTRBaseBackups,
{ loading: false },
]);
await waitFor(() => render(<PointInTimeBackupInfo appId="randomId" />));
// '10 March 2025, 05:00:05 (UTC+02:00)'
const earliestBackup = await screen.getByTestId('EarliestBackupDateTime');
expect(earliestBackup).toHaveTextContent(
'10 Mar 2025, 05:00:05 (UTC+02:00)',
);
});
test('will update the date after the timezone is changed', async () => {
server.use(getOrganization);
server.use(getProjectQuery);
mocks.useGetPiTrBaseBackupsLazyQuery.mockImplementation(() => [
fetchPiTRBaseBackups,
{ loading: false },
]);
await waitFor(() => render(<PointInTimeBackupInfo appId="randomId" />));
const user = userEvent.setup();
// '10 March 2025, 05:00:05 (UTC+02:00)'
const earliestBackup = await screen.getByTestId('EarliestBackupDateTime');
expect(earliestBackup).toHaveTextContent(
'10 Mar 2025, 05:00:05 (UTC+02:00)',
);
const changeTimezoneButton = await screen.getByRole('button', {
name: 'Change timezone',
});
await user.click(changeTimezoneButton);
const tzInput = await screen.getByPlaceholderText('Search timezones...');
expect(tzInput).toBeInTheDocument();
await user.type(tzInput, 'Asia/Amman{ArrowDown}{Enter}');
await waitFor(() => expect(tzInput).not.toBeInTheDocument());
const updatedEarliestBackup = await screen.getByTestId(
'EarliestBackupDateTime',
);
expect(updatedEarliestBackup).toHaveTextContent(
'10 Mar 2025, 06:00:05 (UTC+03:00)',
);
});
test('will fetch the earliest backup and display "Project has no backups yet." test if there are now backups and start restore is disabled', async () => {
server.use(getOrganization);
server.use(getProjectQuery);
mocks.useGetPiTrBaseBackupsLazyQuery.mockImplementation(() => [
fetchEmptyPiTRBaseBackups,
{ loading: false },
]);
await waitFor(() => render(<PointInTimeBackupInfo appId="randomId" />));
// '10 March 2025, 05:00:05 (UTC+02:00)'
const earliestBackup = await screen.getByText(
'Project has no backups yet.',
);
expect(earliestBackup).toBeInTheDocument();
const startRestoreButton = await screen.getByRole('button', {
name: 'Start restore',
});
expect(startRestoreButton).toBeDisabled();
});
test('will schedule a restore', async () => {
server.use(getOrganization);
server.use(getProjectQuery);
mocks.useGetPiTrBaseBackupsLazyQuery.mockImplementation(() => [
fetchPiTRBaseBackups,
{ loading: false },
]);
await waitFor(() =>
render(<PointInTimeBackupInfo appId={mockApplication.id} />),
);
const user = userEvent.setup();
const startRestoreButton = await screen.getByRole('button', {
name: 'Start restore',
});
await user.click(startRestoreButton);
await waitFor(async () =>
expect(
await screen.getByText('Recover your database from a backup'),
).toBeInTheDocument(),
);
const dateTimePickerButton = await screen.getByRole('button', {
name: /UTC/i,
});
await user.click(dateTimePickerButton);
await waitFor(async () =>
expect(
await screen.getByRole('button', { name: 'Select' }),
).toBeInTheDocument(),
);
await user.click(
await screen.getByRole('gridcell', {
name: /13/i,
}),
);
const hoursInput = await screen.getByLabelText('Hours');
await user.type(hoursInput, '18');
const updatedDateTimeButton = await screen.getByRole('button', {
name: /UTC/i,
});
expect(updatedDateTimeButton).toHaveTextContent(
'13 Mar 2025, 18:00:05 (UTC+02:00)',
);
await user.click(await screen.getByRole('button', { name: 'Select' }));
await waitFor(async () =>
expect(
await screen.queryByRole('button', { name: 'Select' }),
).not.toBeInTheDocument(),
);
expect(updatedDateTimeButton).toHaveTextContent(
'13 Mar 2025, 18:00:05 (UTC+02:00)',
);
expect(
await screen.getByRole('button', { name: 'Restore backup' }),
).toBeDisabled();
// check checkboxes
await user.click(
await screen.getByLabelText(/I understand that restoring this backup/),
);
expect(
await screen.getByLabelText(/I understand that restoring this backup/),
).toBeChecked();
expect(
await screen.getByRole('button', { name: 'Restore backup' }),
).toBeDisabled();
await user.click(
await screen.getByLabelText(/I understand this cannot be undone/),
);
expect(
await screen.getByLabelText(/I understand this cannot be undone/),
).toBeChecked();
await waitFor(async () =>
expect(
await screen.getByRole('button', { name: 'Restore backup' }),
).not.toBeDisabled(),
);
await user.click(
await screen.getByRole('button', { name: 'Restore backup' }),
);
expect(
mocks.restoreApplicationDatabase.mock.calls[0][0].fromAppId,
).toBeNull();
expect(
mocks.restoreApplicationDatabase.mock.calls[0][0].recoveryTarget,
).toBe('2025-03-13T16:00:05.000Z');
});
});

View File

@@ -0,0 +1,63 @@
import usePiTRBaseBackups from '@/features/orgs/hooks/usePiTRBaseBackups/usePiTRBaseBackups';
import { isEmptyValue } from '@/lib/utils';
import { Info } from 'lucide-react';
import EarliestBackup from './EarliestBackup';
import RestoreBackupDialogButton from './RestoreBackupDialogButton';
interface Props {
appId: string;
title?: string;
dialogTitle?: string;
dialogButtonText?: string;
dialogTriggerText?: string;
}
function PointInTimeBackupInfo({
appId,
title,
dialogTitle = 'Recover your database from a backup',
dialogButtonText,
dialogTriggerText,
}: Props) {
const { earliestBackupDate, loading } = usePiTRBaseBackups(appId);
const disableStartRestoreButton = loading || isEmptyValue(earliestBackupDate);
return (
/* Move this part to a different component */
<div className="rounded-lg border border-[#EAEDF0] dark:border-[#2F363D]">
<div className="flex w-full flex-col items-start gap-6 p-4">
<h3 className="leading-[1.375] text-[0.9375]">
{title || 'Restore your database from a backup'}
</h3>
<div className="flex w-full flex-col gap-4">
<div>
<p className="text-[0.75rem]">Backups are available from</p>
<EarliestBackup dateTime={earliestBackupDate} loading={loading} />
</div>
<div>
<p className="text-[0.75rem]">Latest backup</p>
<div className="flex items-center gap-2">
<Info className="h-4 w-4" />
<p>
Restore available up to current time. System will restore up to
closest available target time if exact time unavailable.
</p>
</div>
</div>
</div>
</div>
<div className="flex w-full items-center justify-end border-t border-[#EAEDF0] p-4 dark:border-[#2F363D]">
<RestoreBackupDialogButton
disabled={disableStartRestoreButton}
earliestBackupDate={earliestBackupDate}
title={dialogTitle}
fromAppId={appId}
dialogButtonText={dialogButtonText}
dialogTriggerText={dialogTriggerText}
/>
</div>
</div>
);
}
export default PointInTimeBackupInfo;

View File

@@ -0,0 +1,210 @@
import { DateTimePicker } from '@/components/common/DateTimePicker';
import { ButtonWithLoading as Button } from '@/components/ui/v3/button';
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/v3/dialog';
import { useRestoreApplicationDatabasePiTR } from '@/features/orgs/hooks/useRestoreApplicationDatabasePiTR';
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import type { TZDate } from '@date-fns/tz';
import { DialogDescription } from '@radix-ui/react-dialog';
import { format, isBefore, startOfDay } from 'date-fns-v4';
import { memo, useCallback, useEffect, useState } from 'react';
import BackupScheduledInfo from './BackupScheduledInfo';
import StartRestoreConfirmationCheck from './StartRestoreConfirmationCheck';
interface Props {
fromAppId?: string;
title: string;
earliestBackupDate: string;
disabled?: boolean;
dialogButtonText?: string;
dialogTriggerText?: string;
}
function RestoreBackupDialogButton({
title,
disabled,
earliestBackupDate,
fromAppId,
dialogButtonText = 'Restore backup',
dialogTriggerText = 'Start restore',
}: Props) {
const [open, setOpen] = useState(false);
const [isRestoreScheduled, setIsRestoreScheduled] = useState(false);
const [restoreTargetTime, setRestoreTargetTime] =
useState(earliestBackupDate);
const [restoreTargetIsBeforeError, setRestoreTargetIsBeforeError] = useState<
string | undefined
>();
const [
permanentlyDeleteCurrentDataCheck,
setPermanentlyDeleteCurrentDataCheck,
] = useState(false);
const [cannotBeUndoneCheck, setCannotBeUndoneCheck] = useState(false);
const { project } = useProject();
const { org } = useCurrentOrg();
const { restoreApplicationDatabase, loading } =
useRestoreApplicationDatabasePiTR();
async function handleRestore() {
const variables = {
appId: project?.id,
recoveryTarget: restoreTargetTime,
fromAppId: fromAppId === project?.id ? null : fromAppId,
};
restoreApplicationDatabase(variables, () => setIsRestoreScheduled(true));
}
useEffect(() => {
if (earliestBackupDate) {
setRestoreTargetTime(earliestBackupDate);
}
}, [earliestBackupDate]);
function formatDateFn(date: Date | TZDate | string) {
return format(date, 'dd MMM yyyy, HH:mm:ss (OOOO)').replace('GMT', 'UTC');
}
function isCalendarDayDisabled(date: Date) {
return isBefore(startOfDay(date), startOfDay(earliestBackupDate));
}
const resetState = useCallback(() => {
setRestoreTargetTime(earliestBackupDate);
setPermanentlyDeleteCurrentDataCheck(false);
setCannotBeUndoneCheck(false);
setIsRestoreScheduled(false);
setRestoreTargetIsBeforeError(undefined);
}, [earliestBackupDate]);
const handleDateTimeChange = useCallback(
(newDateTime: string) => {
setRestoreTargetTime(newDateTime);
if (isBefore(newDateTime, earliestBackupDate)) {
setRestoreTargetIsBeforeError(
'Selected date is before the earliest restore target time.',
);
} else {
setRestoreTargetIsBeforeError(undefined);
}
},
[earliestBackupDate],
);
const handleOpenChange = useCallback(
(newState: boolean) => {
if (!newState) {
resetState();
}
setOpen(newState);
},
[setOpen, resetState],
);
const validateFn = useCallback(
(newDateTime: Date) => {
if (isBefore(newDateTime, earliestBackupDate)) {
return 'Selected date and time is before the earliest available backup';
}
return '';
},
[earliestBackupDate],
);
const handleClose = useCallback(() => {
handleOpenChange(false);
}, [handleOpenChange]);
const hasError = !!restoreTargetIsBeforeError;
const startRestoreButtonDisabled =
!(permanentlyDeleteCurrentDataCheck && cannotBeUndoneCheck) || hasError;
const permanentlyDeleteCurrentDataCheckLabel = (
<span>
I understand that restoring this backup will permanently delete all
current data for project <b>{project.name}</b>.
</span>
);
const dialogTitle = isRestoreScheduled
? 'Backup has been scheduled successfully.'
: title;
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogTrigger asChild>
<Button disabled={disabled}>{dialogTriggerText}</Button>
</DialogTrigger>
<DialogContent
className="text-foreground sm:max-w-xl"
disableOutsideClick
>
<DialogHeader>
<DialogTitle>{dialogTitle}</DialogTitle>
<DialogDescription />
</DialogHeader>
{isRestoreScheduled && (
<BackupScheduledInfo
onClose={handleClose}
subdomain={project.subdomain}
orgSlug={org?.slug}
/>
)}
{!isRestoreScheduled && (
<>
<div className="flex w-full flex-col gap-9">
<div className="flex flex-col gap-1">
<p>Restore target time</p>
<DateTimePicker
dateTime={restoreTargetTime}
formatDateFn={formatDateFn}
onDateTimeChange={handleDateTimeChange}
withTimezone
isCalendarDayDisabled={isCalendarDayDisabled}
validateDateFn={validateFn}
align="start"
/>
</div>
<div className="flex flex-col gap-3">
<StartRestoreConfirmationCheck
checked={permanentlyDeleteCurrentDataCheck}
onCheckedChange={setPermanentlyDeleteCurrentDataCheck}
id="permanentlyDeleteCurrentDataCheck"
label={permanentlyDeleteCurrentDataCheckLabel}
/>
<StartRestoreConfirmationCheck
checked={cannotBeUndoneCheck}
onCheckedChange={setCannotBeUndoneCheck}
id="cannotBeUndoneCheck"
label="I understand this cannot be undone"
/>
</div>
</div>
<DialogFooter>
<Button
type="button"
disabled={startRestoreButtonDisabled}
loading={loading}
onClick={handleRestore}
>
{dialogButtonText}
</Button>
</DialogFooter>
</>
)}
</DialogContent>
</Dialog>
);
}
export default memo(RestoreBackupDialogButton);

View File

@@ -0,0 +1,35 @@
import { Checkbox } from '@/components/ui/v3/checkbox';
import { Separator } from '@/components/ui/v3/separator';
import type { ReactNode } from 'react';
interface Props {
label: ReactNode;
checked: boolean;
onCheckedChange: (checked: boolean) => void;
id: string;
}
function StartRestoreConfirmationCheck({
id,
label,
checked,
onCheckedChange,
}: Props) {
return (
<div>
<div className="flex items-center space-x-2 pb-3">
<Checkbox id={id} checked={checked} onCheckedChange={onCheckedChange} />
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
<label
htmlFor={id}
className="text-sm font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{label}
</label>
</div>
<Separator />
</div>
);
}
export default StartRestoreConfirmationCheck;

View File

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

View File

@@ -0,0 +1,6 @@
query GetPiTRBaseBackups($appId: String!) {
getPiTRBaseBackups(appID: $appId) {
date
name
}
}

View File

@@ -0,0 +1,11 @@
mutation RestoreApplicationDatabasePiTR(
$appId: String!
$recoveryTarget: Timestamp!
$fromAppId: String
) {
restoreApplicationDatabasePiTR(
appID: $appId
recoveryTarget: $recoveryTarget
fromAppID: $fromAppId
)
}

View File

@@ -0,0 +1,17 @@
import { isEmptyValue } from '@/lib/utils';
import { TZDate } from '@date-fns/tz';
import { format } from 'date-fns-v4';
// This is the only one that is related to the feature
export function getDateTimeStringWithUTCOffset(
dateTime: string | undefined,
timezone: string,
) {
if (isEmptyValue(dateTime)) {
return '';
}
const date = new TZDate(dateTime, timezone);
return format(date, 'dd MMM yyyy, HH:mm:ss (OOOO)').replace('GMT', 'UTC');
}

View File

@@ -5,6 +5,7 @@ import { Divider } from '@/components/ui/v2/Divider';
import { Text } from '@/components/ui/v2/Text';
import { useOrgs } from '@/features/orgs/projects/hooks/useOrgs';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { isEmptyValue } from '@/lib/utils';
import { useBillingDeleteAppMutation } from '@/utils/__generated__/graphql';
import { discordAnnounce } from '@/utils/discordAnnounce';
import { triggerToast } from '@/utils/toast';
@@ -52,6 +53,9 @@ export default function RemoveApplicationModal({
const [remove3, setRemove3] = useState(false);
const appName = project?.name;
const isPaidPlan = isEmptyValue(org?.plan?.isFree)
? false
: !org?.plan?.isFree;
async function handleClick() {
setLoadingRemove(true);
@@ -121,21 +125,23 @@ export default function RemoveApplicationModal({
aria-label="Confirm Delete Project #2"
/>
<Checkbox
id="accept-3"
label="I understand I need to delete the organization if I want to cancel the subscription"
className="py-2"
checked={remove3}
onChange={(_event, checked) => setRemove3(checked)}
aria-label="Confirm Delete Project #3"
/>
{isPaidPlan && (
<Checkbox
id="accept-3"
label="I understand I need to delete the organization if I want to cancel the subscription"
className="py-2"
checked={remove3}
onChange={(_event, checked) => setRemove3(checked)}
aria-label="Confirm Delete Project #3"
/>
)}
</Box>
<div className="grid grid-flow-row gap-2">
<Button
color="error"
onClick={handleClick}
disabled={!remove || !remove2 || !remove3}
disabled={!remove || !remove2 || (isPaidPlan && !remove3)}
loading={loadingRemove}
>
Delete Project

View File

@@ -1,4 +1,5 @@
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import { splitPostgresMajorMinorVersions } from '@/features/orgs/projects/database/settings/utils/splitPostgresMajorMinorVersions';
import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimirClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useGetPostgresSettingsQuery } from '@/utils/__generated__/graphql';
@@ -22,15 +23,12 @@ export default function useGetPostgresVersion() {
});
const { version } = postgresSettingsData?.config?.postgres || {};
const [postgresMajor, postgresMinor] = version?.split('.') || [
undefined,
undefined,
];
const { major, minor } = splitPostgresMajorMinorVersions(version);
return {
version,
postgresMajor,
postgresMinor,
major,
minor,
loading,
error,
};

View File

@@ -201,3 +201,5 @@ export const postgresFunctions = {
* List of PostgreSQL data types that can be used as identity.
*/
export const identityTypes: ColumnType[] = ['int2', 'int4', 'int8'];
export const RECOVERY_RETENTION_PERIOD_7 = 7;

View File

@@ -22,7 +22,7 @@ export default function DatabaseMigrateWarning() {
color: 'error.main',
}}
>
<XIcon className="h-4 w-4" /> Error: Database version upgrade not
<XIcon className="h-4 w-4" /> Error: Database major version upgrade not
possible
</Text>
<Text
@@ -31,7 +31,7 @@ export default function DatabaseMigrateWarning() {
}}
>
Your project isn&apos;t currently in a healthy state. Please, review
before proceeding with the upgrade.
before proceeding with a major version upgrade.
</Text>
</Alert>
);

View File

@@ -0,0 +1,189 @@
import { mockMatchMediaValue } from '@/tests/mocks';
import { render, screen } from '@/tests/orgs/testUtils';
import userEvent from '@testing-library/user-event';
import { vi } from 'vitest';
import DatabasePiTRSettings from './DatabasePiTRSettings';
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation(mockMatchMediaValue),
});
function getCurrentOrg({ isFree }: { isFree: boolean }) {
return {
org: {
plan: {
isFree,
},
},
};
}
function mockUseGetPostgresSettingsQueryResponse({
retention,
}: {
retention: number | null;
}) {
const pitr = retention === null ? null : { retention };
return {
data: {
config: {
postgres: {
pitr,
},
},
},
};
}
const mocks = vi.hoisted(() => ({
useCurrentOrg: vi.fn(),
useUpdateConfigMutation: vi.fn(),
useGetPostgresSettingsQuery: vi.fn(),
updateConfigMock: vi.fn(),
}));
vi.mock('@/features/orgs/projects/hooks/useCurrentOrg', async () => {
const actualCurrentOrg = await vi.importActual<any>(
'@/features/orgs/projects/hooks/useCurrentOrg',
);
return {
...actualCurrentOrg,
useCurrentOrg: mocks.useCurrentOrg,
};
});
vi.mock('@/utils/__generated__/graphql', async () => {
const actual = await vi.importActual<any>('@/utils/__generated__/graphql');
return {
...actual,
useUpdateConfigMutation: mocks.useUpdateConfigMutation,
useGetPostgresSettingsQuery: mocks.useGetPostgresSettingsQuery,
};
});
afterEach(() => {
mocks.useCurrentOrg.mockRestore();
mocks.updateConfigMock.mockRestore();
mocks.useUpdateConfigMutation.mockRestore();
mocks.useGetPostgresSettingsQuery.mockRestore();
});
test('If the org is free the switch should not be available and the save button is disabled', async () => {
mocks.useCurrentOrg.mockImplementation(() => getCurrentOrg({ isFree: true }));
mocks.useUpdateConfigMutation.mockImplementation(() => [
mocks.updateConfigMock,
]);
mocks.useGetPostgresSettingsQuery.mockImplementation(() =>
mockUseGetPostgresSettingsQueryResponse({ retention: null }),
);
render(<DatabasePiTRSettings />);
const saveButton = await screen.findByRole('button', {
name: 'Save',
});
expect(saveButton).toBeDisabled();
expect(await screen.queryByRole('checkbox')).not.toBeInTheDocument();
});
test('the Save button is disabled until the switch in the header is not touched', async () => {
mocks.useCurrentOrg.mockImplementation(() =>
getCurrentOrg({ isFree: false }),
);
mocks.useUpdateConfigMutation.mockImplementation(() => [
mocks.updateConfigMock,
]);
mocks.useGetPostgresSettingsQuery.mockImplementation(() =>
mockUseGetPostgresSettingsQueryResponse({ retention: null }),
);
const user = userEvent.setup();
render(<DatabasePiTRSettings />);
const saveButton = await screen.findByRole('button', {
name: 'Save',
});
expect(saveButton).toBeDisabled();
const PiTR = screen.getByRole('checkbox');
await user.click(PiTR);
expect(PiTR).toBeChecked();
expect(
await screen.findByRole('button', {
name: 'Save',
}),
).not.toBeDisabled();
});
test('should disable the savebutton after toggling back to original state', async () => {
mocks.useCurrentOrg.mockImplementation(() =>
getCurrentOrg({ isFree: false }),
);
mocks.useUpdateConfigMutation.mockImplementation(() => [
mocks.updateConfigMock,
]);
mocks.useGetPostgresSettingsQuery.mockImplementation(() =>
mockUseGetPostgresSettingsQueryResponse({ retention: 7 }),
);
const user = userEvent.setup();
render(<DatabasePiTRSettings />);
await user.click(screen.getByRole('checkbox'));
expect(screen.getByRole('checkbox')).not.toBeChecked();
await user.click(screen.getByRole('checkbox'));
expect(
await screen.findByRole('button', {
name: 'Save',
}),
).toBeDisabled();
});
test('should send { retention: 7 } when enabling PiTR', async () => {
mocks.useCurrentOrg.mockImplementation(() =>
getCurrentOrg({ isFree: false }),
);
mocks.useUpdateConfigMutation.mockImplementation(() => [
mocks.updateConfigMock,
]);
mocks.useGetPostgresSettingsQuery.mockImplementation(() =>
mockUseGetPostgresSettingsQueryResponse({ retention: null }),
);
const user = userEvent.setup();
render(<DatabasePiTRSettings />);
await user.click(screen.getByRole('checkbox'));
expect(screen.getByRole('checkbox')).toBeChecked();
await user.click(
screen.getByRole('button', {
name: 'Save',
}),
);
expect(
mocks.updateConfigMock.mock.calls[0][0].variables.config.postgres.pitr,
).toStrictEqual({ retention: 7 });
});
test('should send { pitr: null } when disabling PiTR', async () => {
mocks.useCurrentOrg.mockImplementation(() =>
getCurrentOrg({ isFree: false }),
);
mocks.useUpdateConfigMutation.mockImplementation(() => [
mocks.updateConfigMock,
]);
mocks.useGetPostgresSettingsQuery.mockImplementation(() =>
mockUseGetPostgresSettingsQueryResponse({ retention: 7 }),
);
const user = userEvent.setup();
render(<DatabasePiTRSettings />);
await user.click(screen.getByRole('checkbox'));
expect(screen.getByRole('checkbox')).not.toBeChecked();
await user.click(
screen.getByRole('button', {
name: 'Save',
}),
);
expect(
mocks.updateConfigMock.mock.calls[0][0].variables.config.postgres,
).toStrictEqual({ pitr: null });
});

View File

@@ -0,0 +1,54 @@
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { useDatabasePiTRSettings } from '@/features/orgs/hooks/useDatabasePiTRSettings/';
import { useUpdateDatabasePiTRConfig } from '@/features/orgs/hooks/useUpdateDatabasePiTRConfig';
import { UpgradeNotification } from '@/features/orgs/projects/database/settings/components/UpgradeNotification';
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
import { isEmptyValue } from '@/lib/utils';
export default function DatabasePiTRSettings() {
const { org } = useCurrentOrg();
const {
isPiTREnabled,
setIsPiTREnabled,
isSwitchDisabled,
setIsNotSwitchTouched,
} = useDatabasePiTRSettings();
const { updatePiTRConfig, loading } = useUpdateDatabasePiTRConfig();
const isFreeProject = isEmptyValue(org) ? false : org.plan.isFree;
const shouldShowSwitch = isEmptyValue(org) ? false : !isFreeProject;
function handleEnabledChange(enabled: boolean) {
setIsPiTREnabled(enabled);
setIsNotSwitchTouched(false);
}
function handleSubmit() {
updatePiTRConfig(isPiTREnabled);
}
return (
<SettingsContainer
title="Point-in-time recovery"
description="Enable Point-in-Time recovery (PiTR). Available as an add-on for organizations on Pro, Team, or Enterprise plans."
slotProps={{
submitButton: {
disabled: isSwitchDisabled,
onClick: handleSubmit,
loading,
type: 'button',
},
}}
className="flex flex-col lg:flex-row"
showSwitch={shouldShowSwitch}
enabled={isPiTREnabled}
onEnabledChange={handleEnabledChange}
docsLink="https://docs.nhost.io/product/database#point-in-time-recovery"
docsTitle="enabling or disabling PiTR"
>
{isFreeProject && (
<UpgradeNotification description="To unlock this add-on, transfer this project to a Pro or Team organization." />
)}
</SettingsContainer>
);
}

View File

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

View File

@@ -1,6 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -15,7 +14,6 @@ import { DatabaseMigrateDisabledError } from '@/features/orgs/projects/database/
import { DatabaseMigrateDowntimeWarning } from '@/features/orgs/projects/database/settings/components/DatabaseMigrateDowntimeWarning';
import { DatabaseMigrateLogsModal } from '@/features/orgs/projects/database/settings/components/DatabaseMigrateLogsModal';
import { DatabaseMigrateVersionConfirmationDialog } from '@/features/orgs/projects/database/settings/components/DatabaseMigrateVersionConfirmationDialog';
import { DatabaseUpdateInProgressWarning } from '@/features/orgs/projects/database/settings/components/DatabaseUpdateInProgressWarning';
import { useAppState } from '@/features/orgs/projects/common/hooks/useAppState';
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
@@ -28,9 +26,11 @@ import {
useUpdateConfigMutation,
} from '@/generated/graphql';
import { splitPostgresMajorMinorVersions } from '@/features/orgs/projects/database/settings/utils/splitPostgresMajorMinorVersions';
import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimirClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
import { isNotEmptyValue } from '@/lib/utils';
import { ApplicationStatus } from '@/types/application';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useMemo } from 'react';
@@ -44,7 +44,11 @@ const validationSchema = Yup.object({
})
.label('Postgres major version')
.required()
.test('not-zero', 'Invalid major version', (value) => value?.value !== '0'),
.test(
'must-be-positive-number',
'Invalid major version',
(value) => Number(value?.value) > 0,
),
minorVersion: Yup.object({
label: Yup.string().required(),
value: Yup.string().required('Minor version is a required field'),
@@ -64,7 +68,6 @@ type DatabaseServiceField = Required<
export default function DatabaseServiceVersionSettings() {
const isPlatform = useIsPlatform();
const { openDialog, closeDialog } = useDialog();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { project } = useProject();
const [updateConfig] = useUpdateConfigMutation({
@@ -77,8 +80,8 @@ export default function DatabaseServiceVersionSettings() {
const {
version: postgresVersion,
postgresMajor: currentPostgresMajor,
postgresMinor: currentPostgresMinor,
major: currentPostgresMajor,
minor: currentPostgresMinor,
error: postgresSettingsError,
loading: loadingPostgresSettings,
} = useGetPostgresVersion();
@@ -124,7 +127,9 @@ export default function DatabaseServiceVersionSettings() {
if (!availableVersion.value) {
return;
}
const [major, minor] = availableVersion.value.split('.');
const { major, minor } = splitPostgresMajorMinorVersions(
availableVersion.value,
);
// Don't suggest versions that are lower than the current Postgres major version (can't downgrade)
if (Number(major) < Number(currentPostgresMajor)) {
@@ -142,10 +147,12 @@ export default function DatabaseServiceVersionSettings() {
majorToMinorVersions[major] = [];
}
majorToMinorVersions[major].push({
label: minor,
value: minor,
});
if (isNotEmptyValue(minor)) {
majorToMinorVersions[major].push({
label: minor,
value: minor,
});
}
});
return {
availableMajorVersions,
@@ -206,13 +213,12 @@ export default function DatabaseServiceVersionSettings() {
!applicationPaused &&
!applicationPausing &&
!applicationUpdating;
const isMajorVersionDirty = formState?.dirtyFields?.majorVersion;
const isMinorVersionDirty = formState?.dirtyFields?.minorVersion;
const isDirty = isMajorVersionDirty || isMinorVersionDirty;
const versionFieldsDisabled =
applicationUpdating || applicationUnhealthy || maintenanceActive;
const saveDisabled = versionFieldsDisabled || !isDirty;
const majorVersionFieldDisabled = applicationUnhealthy;
const handleDatabaseServiceVersionsChange = async (
formValues: DatabaseServiceVersionFormValues,
@@ -313,7 +319,7 @@ export default function DatabaseServiceVersionSettings() {
description="The version of Postgres to use."
slotProps={{
submitButton: {
disabled: saveDisabled,
disabled: !isDirty,
loading: formState.isSubmitting,
},
}}
@@ -341,7 +347,7 @@ export default function DatabaseServiceVersionSettings() {
name="majorVersion"
autoHighlight
freeSolo
disabled={versionFieldsDisabled}
disabled={majorVersionFieldDisabled}
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option || '';
@@ -408,7 +414,6 @@ export default function DatabaseServiceVersionSettings() {
name="minorVersion"
autoHighlight
freeSolo
disabled={versionFieldsDisabled}
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option || '';
@@ -448,7 +453,6 @@ export default function DatabaseServiceVersionSettings() {
/>
</Box>
{showMigrateWarning && <DatabaseMigrateDowntimeWarning />}
{applicationUpdating && <DatabaseUpdateInProgressWarning />}
{applicationUnhealthy && !isMigrating && (
<DatabaseMigrateDisabledError />
)}

View File

@@ -8,10 +8,10 @@ import { Input } from '@/components/ui/v2/Input';
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text';
import { UpgradeNotification } from '@/features/orgs/projects/common/components/UpgradeNotification';
import { useAppState } from '@/features/orgs/projects/common/hooks/useAppState';
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import { DatabaseStorageCapacityWarning } from '@/features/orgs/projects/database/settings/components/DatabaseStorageCapacityWarning';
import { UpgradeNotification } from '@/features/orgs/projects/database/settings/components/UpgradeNotification';
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimirClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
@@ -46,6 +46,8 @@ export default function DatabaseStorageCapacity() {
const localMimirClient = useLocalMimirClient();
const { project } = useProject();
const isFreeProject = !!org?.plan.isFree;
const {
data,
loading,
@@ -172,8 +174,8 @@ export default function DatabaseStorageCapacity() {
}}
className="flex flex-col"
>
{project.legacyPlan?.isFree && (
<UpgradeNotification message="Unlock by upgrading your project to the Pro plan." />
{isFreeProject && (
<UpgradeNotification description="To unlock more storage capacity, transfer this project to a Pro or Team organization." />
)}
<Box className="grid grid-flow-row lg:grid-cols-5">
<Input
@@ -187,13 +189,13 @@ export default function DatabaseStorageCapacity() {
</InputAdornment>
}
fullWidth
disabled={project.legacyPlan?.isFree}
disabled={isFreeProject}
className="lg:col-span-2"
error={Boolean(formState.errors.capacity?.message)}
helperText={formState.errors.capacity?.message}
/>
</Box>
{!project.legacyPlan?.isFree && (
{!isFreeProject && (
<DatabaseStorageCapacityWarning
state={state}
decreasingSize={decreasingSize}

View File

@@ -1,14 +0,0 @@
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

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

View File

@@ -0,0 +1,66 @@
import { OpenTransferDialogButton } from '@/components/common/OpenTransferDialogButton';
import { NhostIcon } from '@/components/presentational/NhostIcon';
import { Alert } from '@/components/ui/v2/Alert';
import { ArrowSquareOutIcon } from '@/components/ui/v2/icons/ArrowSquareOutIcon';
import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text';
import { TransferProjectDialog } from '@/features/orgs/components/common/TransferProjectDialog';
import { useState } from 'react';
interface Props {
description: string;
}
// P
function UpgradeNotification({ description }: Props) {
const [transferProjectDialogOpen, setTransferProjectDialogOpen] =
useState(false);
const handleTransferDialogOpen = () => setTransferProjectDialogOpen(true);
return (
<Alert className="flex w-full flex-col justify-between gap-4 lg:flex-row">
<div className="flex flex-col gap-4">
<div className="flex flex-col space-y-2 lg:flex-row lg:space-x-2 lg:space-y-0">
<Text className="text-left">Available with</Text>
<div className="flex flex-row space-x-2">
<NhostIcon />
<Text
sx={{ color: 'primary.main' }}
className="text-left font-semibold"
>
Nhost Pro & Team
</Text>
</div>
</div>
<Text component="span" className="max-w-[50ch] text-left">
{description}
</Text>
</div>
<Text className="flex flex-row items-center gap-4 self-end">
<Link
href="https://nhost.io/pricing"
target="_blank"
rel="noopener noreferrer"
underline="hover"
className="whitespace-nowrap text-center font-medium"
sx={{
color: 'text.secondary',
}}
>
See all features
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
</Link>
<OpenTransferDialogButton onClick={handleTransferDialogOpen} />
<TransferProjectDialog
open={transferProjectDialogOpen}
setOpen={setTransferProjectDialogOpen}
/>
</Text>
</Alert>
);
}
export default UpgradeNotification;

View File

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

View File

@@ -15,6 +15,9 @@ query GetPostgresSettings($appId: uuid!) {
}
enablePublicAccess
}
pitr {
retention
}
}
}
}

View File

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

View File

@@ -0,0 +1,22 @@
import splitPostgresMajorMinorVersions from './splitPostgresMajorMinorVersions';
test('can split a postgres version into major and minor', () => {
const { major, minor } = splitPostgresMajorMinorVersions('15.10-20250131-1');
expect(major).toBe('15');
expect(minor).toBe('10-20250131-1');
});
test('can get major and minor versions from a postgres beta version', () => {
const { major, minor } = splitPostgresMajorMinorVersions('17.2-0.0.0-beta1');
expect(major).toBe('17');
expect(minor).toBe('2-0.0.0-beta1');
});
test('gets only major version if no minor version is present', () => {
const { major, minor } = splitPostgresMajorMinorVersions('15');
expect(major).toBe('15');
expect(minor).toBe('');
});

View File

@@ -0,0 +1,24 @@
/**
* Splits a Postgres version string into major and minor versions.
* @param version - The Postgres version string to split.
* @returns An object containing the major and minor versions.
* It returns an empty string for the minor version if no minor version is present.
*/
export default function splitPostgresMajorMinorVersions(version: string) {
const splitIndex = version.indexOf('.');
if (splitIndex === -1) {
return {
major: version,
minor: '',
};
}
const major = version.slice(0, splitIndex);
const minor = version.slice(splitIndex + 1);
return {
major,
minor,
};
}

View File

@@ -1,7 +1,7 @@
import { Avatar } from '@/components/ui/v2/Avatar';
import { Text } from '@/components/ui/v2/Text';
import type { Deployment } from '@/types/application';
import formatDistance from 'date-fns/formatDistance';
import { formatDistance } from 'date-fns';
export interface DeploymentStatusMessageProps {
deployment: Partial<Deployment>;

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