Compare commits

..

12 Commits

Author SHA1 Message Date
github-actions[bot]
fdc50b32d8 chore: update versions (#2484)
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@6.0.2

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/nhost-js@3.0.2

## @nhost/google-translation@0.0.7

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit

## @nhost/react-apollo@7.0.2

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/apollo@6.0.2
    -   @nhost/react@3.0.2

## @nhost/react-urql@4.0.2

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/react@3.0.2

## @nhost/stripe-graphql-js@1.0.6

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit

## @nhost/graphql-js@0.1.5

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit

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

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit

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

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit

## @nhost/nextjs@2.0.2

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/react@3.0.2

## @nhost/nhost-js@3.0.2

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/hasura-storage-js@2.2.6
    -   @nhost/hasura-auth-js@2.1.11
    -   @nhost/graphql-js@0.1.5

## @nhost/react@3.0.2

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/nhost-js@3.0.2

## @nhost/vue@2.0.3

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/nhost-js@3.0.2

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

## @nhost-examples/cli@0.1.3

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/nhost-js@3.0.2

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

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/react-apollo@7.0.2
    -   @nhost/react@3.0.2

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

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/react@3.0.2

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

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/react-urql@4.0.2
    -   @nhost/react@3.0.2

## @nhost-examples/docker-compose@0.0.7

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit

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

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/nhost-js@3.0.2

## @nhost-examples/nextjs@0.1.13

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/react-apollo@7.0.2
    -   @nhost/nextjs@2.0.2
    -   @nhost/react@3.0.2

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

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/nhost-js@3.0.2

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

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/nhost-js@3.0.2

## @nhost-examples/sveltekit@0.2.2

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit

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

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/react-apollo@7.0.2
    -   @nhost/react@3.0.2

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

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/react@3.0.2

## @nhost-examples/serverless-functions@0.0.10

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/stripe-graphql-js@1.0.6

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

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/apollo@6.0.2
    -   @nhost/nhost-js@3.0.2
    -   @nhost/vue@2.0.3

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

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
-   Updated dependencies [8d91f71]
    -   @nhost/apollo@6.0.2
    -   @nhost/vue@2.0.3

## @nhost/docgen@0.1.12

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit

## @nhost/sync-versions@0.0.9

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-01-23 20:04:39 +01:00
Hassan Ben Jobrane
3cdca8d4b3 chore: update sveltekit pnpm-lockl.yaml (#2485) 2024-01-23 19:18:44 +01:00
Hassan Ben Jobrane
c425c9f265 fix(quickstarts): use turbo to build nextjs-server-components and fix sveltekit lockfile (#2483) 2024-01-23 18:53:21 +01:00
Hassan Ben Jobrane
3b8473b168 chore: update pnpm and turbo versions in Dockerfile (#2482) 2024-01-23 16:51:14 +01:00
David Barroso
8d91f7103f chore: update deps and enable pnpm audit (#2466)
Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-01-23 13:58:48 +01:00
github-actions[bot]
11ce93d64b chore: update versions (#2478)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@1.6.0

### Minor Changes

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

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-01-22 15:08:49 -01:00
Nuno Pato
3ff1c2b531 fix: dashboard: show upgrade option to pro projects (#2477) 2024-01-22 15:03:33 -01:00
github-actions[bot]
e4341c3706 chore: update versions (#2462)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@1.5.0

### Minor Changes

-   c2ef17c0a: feat: add support for new Team plan

## @nhost/docs@2.1.0

### Minor Changes

-   65b6a48d5: feat: added graphite/cli documentation

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-01-22 11:31:49 -01:00
Nuno Pato
c2ef17c0a0 feat: dashboard: new Team plan (#2473) 2024-01-22 11:13:26 -01:00
David Barroso
1045ea0a46 fix(docs): set correct hostname for connecting to run service internally (#2471) 2024-01-17 12:34:01 +01:00
Nevada Le Master
5faaf36e26 chore: add maskedErrors param to CreateServerProps (#2129)
this PR addresses https://github.com/nhost/nhost/issues/1218, adding
`maskedErrors` param when creating Stripe and Google Translate GraphQL
Yoga servers

Co-authored-by: David Barroso <dbarrosop@dravetech.com>
2024-01-16 16:36:51 +01:00
David Barroso
65b6a48d51 feat(docs): added graphite/cli documentation (#2457)
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2024-01-10 17:19:58 +01:00
166 changed files with 10770 additions and 15421 deletions

View File

@@ -14,7 +14,7 @@ runs:
steps: steps:
- uses: pnpm/action-setup@v2.2.4 - uses: pnpm/action-setup@v2.2.4
with: with:
version: 8.6.2 version: 8.10.5
run_install: false run_install: false
- name: Get pnpm cache directory - name: Get pnpm cache directory
id: pnpm-cache-dir id: pnpm-cache-dir

23
.github/renovate.json vendored
View File

@@ -1,23 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"docker-compose": {
"enabled": true
},
"ignoreDeps": [
"pnpm",
"node",
"@types/node"
],
"labels": [
"dependencies"
],
"enabledManagers": [
"npm",
"dockerfile",
"docker-compose",
"github-actions"
]
}

View File

@@ -106,6 +106,8 @@ jobs:
# * Run every `lint` script in the workspace . Dependencies build is cached by Turborepo # * Run every `lint` script in the workspace . Dependencies build is cached by Turborepo
- name: Lint - name: Lint
run: pnpm run lint:all run: pnpm run lint:all
- name: Audit for vulnerabilities
run: pnpx audit-ci --config ./audit-ci.jsonc
e2e: e2e:
name: 'E2E (Package: ${{ matrix.package.path }})' name: 'E2E (Package: ${{ matrix.package.path }})'

3
.npmrc
View File

@@ -1 +1,2 @@
prefer-workspace-packages = true prefer-workspace-packages = true
auto-install-peers = false

6
audit-ci.jsonc Normal file
View File

@@ -0,0 +1,6 @@
{
// $schema provides code completion hints to IDEs.
"$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json",
"moderate": true,
"allowlist": ["trim-newlines"]
}

View File

@@ -1,5 +1,27 @@
# @nhost/dashboard # @nhost/dashboard
## 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 ## 1.4.0
### Minor Changes ### Minor Changes

View File

@@ -3,7 +3,7 @@ RUN apk add --no-cache libc6-compat
RUN apk update RUN apk update
WORKDIR /app WORKDIR /app
RUN yarn global add turbo@1.10.11 RUN yarn global add turbo@1.11.3
COPY . . COPY . .
RUN turbo prune --scope="@nhost/dashboard" --docker RUN turbo prune --scope="@nhost/dashboard" --docker
@@ -29,7 +29,7 @@ ENV NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL __NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL_
ENV NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL __NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__ ENV NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL __NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__
ENV NEXT_PUBLIC_NHOST_HASURA_API_URL __NEXT_PUBLIC_NHOST_HASURA_API_URL__ ENV NEXT_PUBLIC_NHOST_HASURA_API_URL __NEXT_PUBLIC_NHOST_HASURA_API_URL__
RUN yarn global add pnpm@8.6.2 RUN yarn global add pnpm@8.10.5
COPY .gitignore .gitignore COPY .gitignore .gitignore
COPY --from=pruner /app/out/json/ . COPY --from=pruner /app/out/json/ .
COPY --from=pruner /app/out/pnpm-*.yaml . COPY --from=pruner /app/out/pnpm-*.yaml .

View File

@@ -27,7 +27,7 @@ test('should be able to create then delete a personal access token', async () =>
const patName = faker.lorem.slug(3); const patName = faker.lorem.slug(3);
await page.getByRole('textbox', { name: /name/i }).fill(patName); await page.getByRole('textbox', { name: /name/i }).fill(patName);
await page.getByRole('button', { name: /expiration/i }).click(); await page.getByLabel('Expiration').click();
await page.getByRole('option', { name: /7 days/i }).click(); await page.getByRole('option', { name: /7 days/i }).click();
await page.getByRole('button', { name: /create/i }).click(); await page.getByRole('button', { name: /create/i }).click();

View File

@@ -138,7 +138,8 @@ test('should create a table with an identity column', async () => {
], ],
}); });
await page.getByRole('button', { name: /identity/i }).click(); // await page.getByRole('button', { name: /identity/i }).click();
await page.getByLabel('Identity').click();
await page.getByRole('option', { name: /id/i }).click(); await page.getByRole('option', { name: /id/i }).click();
// create table // create table
@@ -194,26 +195,18 @@ test('should create table with foreign key constraint', async () => {
await page.getByRole('button', { name: /add foreign key/i }).click(); await page.getByRole('button', { name: /add foreign key/i }).click();
// select column in current table await page.locator('#columnName').click();
await page
.getByRole('button', { name: /column/i })
.first()
.click();
await page.getByRole('option', { name: /author_id/i }).click(); await page.getByRole('option', { name: /author_id/i }).click();
// select reference schema // select reference schema
await page.getByRole('button', { name: /schema/i }).click(); await page.getByLabel('Schema').click();
await page.getByRole('option', { name: /public/i }).click(); await page.getByRole('option', { name: /public/i }).click();
// select reference table // select reference table
await page.getByRole('button', { name: /table/i }).click(); await page.getByLabel('Table').click();
await page.getByRole('option', { name: firstTableName, exact: true }).click(); await page.getByRole('option', { name: firstTableName, exact: true }).click();
// select reference column await page.locator('#referencedColumn').click();
await page
.getByRole('button', { name: /column/i })
.nth(1)
.click();
await page.getByRole('option', { name: /id/i }).click(); await page.getByRole('option', { name: /id/i }).click();
await page.getByRole('button', { name: /add/i }).click(); await page.getByRole('button', { name: /add/i }).click();

View File

@@ -113,27 +113,21 @@ test('should not be able to delete a table if other tables have foreign keys ref
await page.getByRole('button', { name: /add foreign key/i }).click(); await page.getByRole('button', { name: /add foreign key/i }).click();
// select column in current table // select column in current table
await page await page.locator('#columnName').click();
.getByRole('button', { name: /column/i })
.first()
.click();
await page.getByRole('option', { name: /author_id/i }).click(); await page.getByRole('option', { name: /author_id/i }).click();
// select reference schema // select reference schema
await page.getByRole('button', { name: /schema/i }).click(); await page.getByLabel('Schema').click();
await page.getByRole('option', { name: /public/i }).click(); await page.getByRole('option', { name: /public/i }).click();
// select reference table // select reference table
await page.getByRole('button', { name: /table/i }).click(); await page.getByLabel('Table').click();
await page.getByRole('option', { name: firstTableName, exact: true }).click(); await page.getByRole('option', { name: firstTableName, exact: true }).click();
// select reference column // select reference column
await page await page.locator('#referencedColumn').click();
.getByRole('button', { name: /column/i })
.nth(1)
.click();
await page.getByRole('option', { name: /id/i }).click(); await page.getByRole('option', { name: /id/i }).click();
await page.getByRole('button', { name: /add/i }).click(); await page.getByRole('button', { name: /add/i }).click();
await expect( await expect(

View File

@@ -93,7 +93,7 @@ test("should show the project's region and subdomain", async () => {
test('should not have a GitHub repository connected', async () => { test('should not have a GitHub repository connected', async () => {
await expect( await expect(
page.getByRole('button', { name: /connect to github/i }), page.getByRole('button', { name: /connect to github/i }).first(),
).toBeVisible(); ).toBeVisible();
}); });

View File

@@ -116,7 +116,8 @@ export async function prepareTable({
); );
// select the first column as primary key // select the first column as primary key
await page.getByRole('button', { name: /primary key/i }).click(); // await page.getByRole('button', { name: /primary key/i }).click();
await page.getByLabel('Primary Key').click();
await page.getByRole('option', { name: primaryKey, exact: true }).click(); await page.getByRole('option', { name: primaryKey, exact: true }).click();
} }

View File

@@ -1,5 +0,0 @@
query InitQuery {
root {
enableServices
}
}

View File

@@ -1,5 +0,0 @@
{
"projectId": 2596,
"token": "U2FsdGVkX19+V8BJnVR0xLEC+42OW5qZl/A0i6beAaRmJoIhFh5Yf6eIKBzLbV9h",
"outputDirectoryPath": "src/hypertune"
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/dashboard", "name": "@nhost/dashboard",
"version": "1.4.0", "version": "1.6.1",
"private": true, "private": true,
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
@@ -15,62 +15,61 @@
"format": "prettier --write \"src/**/*.{js,ts,tsx,jsx,json,md}\" --plugin-search-dir=.", "format": "prettier --write \"src/**/*.{js,ts,tsx,jsx,json,md}\" --plugin-search-dir=.",
"storybook": "start-storybook -p 6006 -s public", "storybook": "start-storybook -p 6006 -s public",
"build-storybook": "build-storybook", "build-storybook": "build-storybook",
"install-browsers": "pnpm dlx playwright@1.31.0 install --with-deps", "install-browsers": "pnpm playwright install && pnpm playwright install-deps",
"e2e": "pnpm install-browsers && pnpm dlx playwright@1.31.0 test" "e2e": "pnpm install-browsers && pnpm playwright test"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.7.10", "@apollo/client": "^3.8.9",
"@codemirror/lang-sql": "^6.5.4", "@codemirror/lang-sql": "^6.5.5",
"@emotion/cache": "^11.10.5", "@emotion/cache": "^11.11.0",
"@emotion/react": "^11.10.5", "@emotion/react": "^11.11.3",
"@emotion/server": "^11.4.0", "@emotion/server": "^11.11.0",
"@emotion/styled": "^11.10.5", "@emotion/styled": "^11.11.0",
"@fontsource/inter": "^5.0.0", "@fontsource/inter": "^5.0.16",
"@fontsource/roboto-mono": "^5.0.0", "@fontsource/roboto-mono": "^5.0.16",
"@graphiql/react": "^0.18.0", "@graphiql/react": "^0.20.2",
"@graphiql/toolkit": "^0.8.2", "@graphiql/toolkit": "^0.9.1",
"@headlessui/react": "^1.6.5", "@headlessui/react": "^1.7.18",
"@heroicons/react": "^1.0.6", "@heroicons/react": "^1.0.6",
"@hookform/resolvers": "^3.0.0", "@hookform/resolvers": "^3.3.4",
"@mui/base": "^5.0.0-alpha.106", "@mui/base": "5.0.0-beta.31",
"@mui/material": "^5.10.14", "@mui/material": "^5.15.4",
"@mui/system": "^5.10.14", "@mui/system": "^5.15.4",
"@mui/x-date-pickers": "^5.0.8", "@mui/x-date-pickers": "^5.0.20",
"@nhost/nextjs": "workspace:*", "@nhost/nextjs": "workspace:*",
"@nhost/react-apollo": "workspace:*", "@nhost/react-apollo": "workspace:*",
"@segment/snippet": "^4.15.3", "@segment/snippet": "^4.16.2",
"@stripe/react-stripe-js": "^2.0.0", "@stripe/react-stripe-js": "^2.4.0",
"@stripe/stripe-js": "^1.35.0", "@stripe/stripe-js": "^1.54.2",
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.7",
"@tanstack/react-query": "^4.16.1", "@tanstack/react-query": "^4.36.1",
"@tanstack/react-table": "^8.5.30", "@tanstack/react-table": "^8.11.6",
"@tanstack/react-virtual": "^3.0.0-beta.23", "@tanstack/react-virtual": "^3.0.1",
"@uiw/codemirror-theme-github": "^4.21.20", "@uiw/codemirror-theme-github": "^4.21.21",
"@uiw/react-codemirror": "^4.21.20", "@uiw/react-codemirror": "^4.21.21",
"analytics-node": "^6.2.0", "analytics-node": "^6.2.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"date-fns": "^2.29.3", "date-fns": "^2.30.0",
"generate-password": "^1.7.0", "generate-password": "^1.7.1",
"graphiql": "^3.0.0", "graphiql": "^3.1.0",
"graphql": "^16.6.0", "graphql": "16.8.1",
"graphql-request": "^6.0.0", "graphql-request": "^6.1.0",
"graphql-tag": "^2.12.6", "graphql-tag": "^2.12.6",
"graphql-ws": "^5.11.2", "graphql-ws": "^5.14.3",
"hypertune": "^1.4.4", "just-kebab-case": "^4.2.0",
"just-kebab-case": "^4.1.1",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"next": "^12.3.1", "next": "^14.0.4",
"next-seo": "^6.0.0", "next-seo": "^6.4.0",
"node-pg-format": "^1.3.5", "node-pg-format": "^1.3.5",
"pluralize": "^8.0.0", "pluralize": "^8.0.0",
"react": "18.2.0", "react": "18.2.0",
"react-children-utilities": "^2.9.0", "react-children-utilities": "^2.10.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-error-boundary": "^4.0.0", "react-error-boundary": "^4.0.12",
"react-hook-form": "^7.42.1", "react-hook-form": "^7.49.3",
"react-hot-toast": "^2.4.0", "react-hot-toast": "^2.4.1",
"react-intersection-observer": "^9.5.2", "react-intersection-observer": "^9.5.3",
"react-is": "18.2.0", "react-is": "18.2.0",
"react-loading-skeleton": "^2.2.0", "react-loading-skeleton": "^2.2.0",
"react-markdown": "^9.0.1", "react-markdown": "^9.0.1",
@@ -82,87 +81,87 @@
"rehype-highlight": "^7.0.0", "rehype-highlight": "^7.0.0",
"remark-gfm": "^4.0.0", "remark-gfm": "^4.0.0",
"shell-quote": "^1.8.1", "shell-quote": "^1.8.1",
"slugify": "^1.6.5", "slugify": "^1.6.6",
"stripe": "^10.17.0", "stripe": "^10.17.0",
"tailwind-merge": "^1.8.0", "tailwind-merge": "^1.14.0",
"utility-types": "^3.10.0", "utility-types": "^3.10.0",
"validator": "^13.7.0", "validator": "^13.11.0",
"yup": "^1.0.2", "yup": "^1.3.3",
"yup-password": "^0.2.2" "yup-password": "^0.2.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.2", "@babel/core": "^7.23.7",
"@faker-js/faker": "^7.6.0", "@faker-js/faker": "^7.6.0",
"@graphql-codegen/cli": "^3.0.0", "@graphql-codegen/cli": "^3.3.1",
"@graphql-codegen/typescript": "^3.0.0", "@graphql-codegen/typescript": "^3.0.4",
"@graphql-codegen/typescript-operations": "^3.0.0", "@graphql-codegen/typescript-operations": "^3.0.4",
"@graphql-codegen/typescript-react-apollo": "^3.3.1", "@graphql-codegen/typescript-react-apollo": "^3.3.7",
"@next/bundle-analyzer": "^12.3.1", "@next/bundle-analyzer": "^12.3.4",
"@playwright/test": "1.31.0", "@playwright/test": "1.41.0",
"@storybook/addon-actions": "^6.5.14", "@storybook/addon-actions": "^6.5.16",
"@storybook/addon-essentials": "^6.5.14", "@storybook/addon-essentials": "^6.5.16",
"@storybook/addon-interactions": "^6.5.14", "@storybook/addon-interactions": "^6.5.16",
"@storybook/addon-links": "^6.5.14", "@storybook/addon-links": "^6.5.16",
"@storybook/addon-postcss": "^2.0.0", "@storybook/addon-postcss": "^2.0.0",
"@storybook/builder-webpack5": "^6.5.14", "@storybook/builder-webpack5": "^6.5.16",
"@storybook/manager-webpack5": "^6.5.14", "@storybook/manager-webpack5": "^6.5.16",
"@storybook/react": "^6.5.14", "@storybook/react": "^6.5.16",
"@storybook/testing-library": "^0.2.0", "@storybook/testing-library": "^0.2.2",
"@tailwindcss/typography": "^0.5.10", "@tailwindcss/typography": "^0.5.10",
"@testing-library/dom": "^9.0.0", "@testing-library/dom": "^9.3.4",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^14.0.0", "@testing-library/react": "^14.1.2",
"@testing-library/user-event": "^14.4.3", "@testing-library/user-event": "^14.5.2",
"@types/ace": "^0.0.48", "@types/ace": "^0.0.48",
"@types/bcryptjs": "^2.4.2", "@types/bcryptjs": "^2.4.6",
"@types/jest": "^29.5.3", "@types/jest": "^29.5.11",
"@types/lodash.debounce": "^4.0.7", "@types/lodash.debounce": "^4.0.9",
"@types/node": "^16.11.7", "@types/node": "^16.18.70",
"@types/pluralize": "^0.0.30", "@types/pluralize": "^0.0.30",
"@types/react": "^18.2.14", "@types/react": "^18.2.47",
"@types/react-dom": "^18.2.6", "@types/react-dom": "^18.2.18",
"@types/react-table": "^7.7.12", "@types/react-table": "^7.7.19",
"@types/shell-quote": "^1.7.1", "@types/shell-quote": "^1.7.5",
"@types/testing-library__jest-dom": "^5.14.5", "@types/testing-library__jest-dom": "^5.14.9",
"@types/validator": "^13.7.10", "@types/validator": "^13.11.8",
"@typescript-eslint/eslint-plugin": "^6.15.0", "@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser": "^6.15.0", "@typescript-eslint/parser": "^6.18.1",
"@vitejs/plugin-react": "^4.0.0", "@vitejs/plugin-react": "^4.2.1",
"@vitest/coverage-v8": "^0.32.0", "@vitest/coverage-v8": "^0.32.4",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.16",
"babel-loader": "^8.3.0", "babel-loader": "^8.3.0",
"babel-plugin-transform-remove-console": "^6.9.4", "babel-plugin-transform-remove-console": "^6.9.4",
"csstype": "^3.0.10", "csstype": "^3.1.3",
"dotenv": "^16.0.3", "dotenv": "^16.3.1",
"encoding": "^0.1.13", "encoding": "^0.1.13",
"eslint": "^8.28.0", "eslint": "^8.56.0",
"eslint-config-airbnb": "19.0.4", "eslint-config-airbnb": "19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-airbnb-typescript": "^17.1.0",
"eslint-config-next": "^13.0.2", "eslint-config-next": "^13.5.6",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.10.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.6.1", "eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react": "^7.31.11", "eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"jsdom": "^22.0.0", "jsdom": "^22.1.0",
"lint-staged": ">=13", "lint-staged": "^15.2.0",
"msw": "^1.0.1", "msw": "^1.3.2",
"msw-storybook-addon": "^1.6.3", "msw-storybook-addon": "^1.10.0",
"node-fetch": "^3.3.0", "node-fetch": "^3.3.2",
"postcss": "^8.4.19", "postcss": "^8.4.33",
"prettier": "^2.7.1", "prettier": "^2.8.8",
"prettier-plugin-organize-imports": "^3.2.0", "prettier-plugin-organize-imports": "^3.2.4",
"prettier-plugin-tailwindcss": "^0.4.0", "prettier-plugin-tailwindcss": "^0.4.1",
"react-date-fns-hooks": "^0.9.4", "react-date-fns-hooks": "^0.9.4",
"require-from-string": "^2.0.2", "require-from-string": "^2.0.2",
"snake-case": "^3.0.4", "snake-case": "^3.0.4",
"storybook-addon-next-router": "^4.0.1", "storybook-addon-next-router": "^4.0.2",
"tailwindcss": "^3.1.2", "tailwindcss": "^3.4.1",
"ts-node": "^10.9.1", "ts-node": "^10.9.2",
"tsconfig-paths-webpack-plugin": "^4.0.0", "tsconfig-paths-webpack-plugin": "^4.1.0",
"vite": "^4.0.2", "vite": "^5.0.12",
"vite-tsconfig-paths": "^4.0.3", "vite-tsconfig-paths": "^4.2.3",
"vitest": "^0.32.0" "vitest": "^0.32.4"
}, },
"browserslist": { "browserslist": {
"production": [ "production": [

View File

@@ -4,9 +4,17 @@ import type { DetailedHTMLProps, HTMLProps } from 'react';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export interface ContactUsProps export interface ContactUsProps
extends DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> {} extends DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> {
isTeam?: boolean;
isOwner?: boolean;
}
export default function FeedbackForm({ className, ...props }: ContactUsProps) { export default function FeedbackForm({
className,
isTeam,
isOwner,
...props
}: ContactUsProps) {
return ( return (
<div <div
className={twMerge( className={twMerge(
@@ -19,6 +27,30 @@ export default function FeedbackForm({ className, ...props }: ContactUsProps) {
Contact us Contact us
</Text> </Text>
{isTeam && isOwner && (
<Text>
If this is a new Team project, or you need to manage members, reach
out to us on discord or via email at{' '}
<Link
href="mailto:support@nhost.io"
target="_blank"
rel="noopener noreferrer"
underline="hover"
>
support@nhost.io
</Link>{' '}
so we can have your dedicated channel set up.
</Text>
)}
{isTeam && !isOwner && (
<Text>
As part of a team plan you can reach out to us on the private channel
for this workspace. If you haven&apos;t been added to the channel, ask
the workspace owner to add you.
</Text>
)}
<Text> <Text>
To report issues with Nhost, please open a GitHub issue in the{' '} To report issues with Nhost, please open a GitHub issue in the{' '}
<Link <Link

View File

@@ -17,7 +17,7 @@ function NavLink(
ref: ForwardedRef<HTMLAnchorElement>, ref: ForwardedRef<HTMLAnchorElement>,
) { ) {
return ( return (
<NextLink href={href} passHref> <NextLink href={href} passHref legacyBehavior>
<Link className={twMerge('font-display', className)} ref={ref} {...props}> <Link className={twMerge('font-display', className)} ref={ref} {...props}>
{children} {children}
</Link> </Link>

View File

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

View File

@@ -26,7 +26,7 @@ export default function UpgradeToProBanner({
return ( return (
<Box <Box
sx={{ backgroundColor: 'primary.light' }} sx={{ backgroundColor: 'primary.light' }}
className="flex flex-col justify-between space-y-4 rounded-md p-4 lg:flex-row lg:items-center lg:space-y-0" className="flex flex-col justify-between p-4 space-y-4 rounded-md lg:flex-row lg:items-center lg:space-y-0"
> >
<div className="flex flex-col justify-between space-y-4"> <div className="flex flex-col justify-between space-y-4">
<div className="space-y-2"> <div className="space-y-2">
@@ -81,13 +81,13 @@ export default function UpgradeToProBanner({
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
underline="hover" underline="hover"
className="text-center font-medium" className="font-medium text-center"
sx={{ sx={{
color: 'text.secondary', color: 'text.secondary',
}} }}
> >
See all features See all features
<ArrowSquareOutIcon className="ml-1 h-4 w-4" /> <ArrowSquareOutIcon className="w-4 h-4 ml-1" />
</Link> </Link>
</div> </div>
</div> </div>
@@ -97,6 +97,7 @@ export default function UpgradeToProBanner({
width={300} width={300}
height={140} height={140}
objectFit="contain" objectFit="contain"
alt='Upgrade to Pro illustration'
/> />
</Box> </Box>
); );

View File

@@ -8,7 +8,7 @@ import type {
DataBrowserGridCellProps, DataBrowserGridCellProps,
} from '@/features/database/dataGrid/types/dataBrowser'; } from '@/features/database/dataGrid/types/dataBrowser';
import { triggerToast } from '@/utils/toast'; import { triggerToast } from '@/utils/toast';
import type { FocusEvent, KeyboardEvent, MouseEvent } from 'react'; import type { FocusEvent, JSXElementConstructor, KeyboardEvent, MouseEvent, ReactElement, ReactNode, ReactPortal } from 'react';
import { import {
Children, Children,
cloneElement, cloneElement,
@@ -320,7 +320,7 @@ function DataGridCellContent<TData extends object = {}, TValue = unknown>({
sx={{ backgroundColor: 'transparent' }} sx={{ backgroundColor: 'transparent' }}
{...props} {...props}
> >
{Children.map(children, (child) => { {Children.map(children, (child: ReactNode | ReactPortal | ReactElement<unknown, string | JSXElementConstructor<any>>) => {
if (!isValidElement(child)) { if (!isValidElement(child)) {
return null; return null;
} }

View File

@@ -13,6 +13,7 @@ import { Dropdown } from '@/components/ui/v2/Dropdown';
import { GraphiteIcon } from '@/components/ui/v2/icons/GraphiteIcon'; import { GraphiteIcon } from '@/components/ui/v2/icons/GraphiteIcon';
import { DevAssistant } from '@/features/ai/DevAssistant'; import { DevAssistant } from '@/features/ai/DevAssistant';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCurrentUserOwner';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform'; import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { ApplicationStatus } from '@/types/application'; import { ApplicationStatus } from '@/types/application';
import { getToastStyleProps } from '@/utils/constants/settings'; import { getToastStyleProps } from '@/utils/constants/settings';
@@ -37,6 +38,8 @@ export default function Header({ className, ...props }: HeaderProps) {
const { currentProject, refetch: refetchProject } = const { currentProject, refetch: refetchProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const isOwner = useIsCurrentUserOwner();
const isProjectUpdating = const isProjectUpdating =
currentProject?.appStates[0]?.stateId === ApplicationStatus.Updating; currentProject?.appStates[0]?.stateId === ApplicationStatus.Updating;
@@ -114,7 +117,11 @@ export default function Header({ className, ...props }: HeaderProps) {
transformOrigin={{ vertical: 'top', horizontal: 'right' }} transformOrigin={{ vertical: 'top', horizontal: 'right' }}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }} anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
> >
<ContactUs className="max-w-md" /> <ContactUs
className="max-w-md"
isTeam={currentProject?.plan?.name === 'Team'}
isOwner={isOwner}
/>
</Dropdown.Content> </Dropdown.Content>
</Dropdown.Root> </Dropdown.Root>
)} )}

View File

@@ -8,9 +8,9 @@ import { Input, inputClasses } from '@/components/ui/v2/Input';
import { OptionBase } from '@/components/ui/v2/Option'; import { OptionBase } from '@/components/ui/v2/Option';
import { OptionGroupBase } from '@/components/ui/v2/OptionGroup'; import { OptionGroupBase } from '@/components/ui/v2/OptionGroup';
import type { StyledComponent } from '@emotion/styled'; import type { StyledComponent } from '@emotion/styled';
import type { UseAutocompleteProps } from '@mui/base/AutocompleteUnstyled'; import type { UseAutocompleteProps } from '@mui/base/useAutocomplete';
import { createFilterOptions } from '@mui/base/AutocompleteUnstyled'; import { createFilterOptions } from '@mui/base/useAutocomplete';
import PopperUnstyled from '@mui/base/PopperUnstyled'; import { Popper } from '@mui/base'
import { styled } from '@mui/material'; import { styled } from '@mui/material';
import type { AutocompleteProps as MaterialAutocompleteProps } from '@mui/material/Autocomplete'; import type { AutocompleteProps as MaterialAutocompleteProps } from '@mui/material/Autocomplete';
import MaterialAutocomplete, { import MaterialAutocomplete, {
@@ -142,7 +142,7 @@ const StyledOptionBase = styled(OptionBase)(({ theme }) => ({
gap: theme.spacing(0.5), gap: theme.spacing(0.5),
})); }));
export const AutocompletePopper = styled(PopperUnstyled)(({ theme }) => ({ export const AutocompletePopper = styled(Popper)(({ theme }) => ({
zIndex: theme.zIndex.modal + 1, zIndex: theme.zIndex.modal + 1,
boxShadow: 'none', boxShadow: 'none',
minWidth: 320, minWidth: 320,

View File

@@ -1,9 +1,7 @@
import { styled } from '@mui/material'; import { styled } from '@mui/material';
import type { import type { BoxProps as MaterialBoxProps } from '@mui/material/Box';
BoxProps as MaterialBoxProps,
BoxTypeMap,
} from '@mui/material/Box';
import MaterialBox from '@mui/material/Box'; import MaterialBox from '@mui/material/Box';
import { type BoxTypeMap } from '@mui/system';
import type { ForwardedRef, PropsWithoutRef } from 'react'; import type { ForwardedRef, PropsWithoutRef } from 'react';
import { forwardRef } from 'react'; import { forwardRef } from 'react';

View File

@@ -10,7 +10,7 @@ export interface ChipProps extends MaterialChipProps {
/** /**
* Custom component for the root node. * Custom component for the root node.
*/ */
component?: string | ElementType; component?: ElementType;
} }
const Chip = styled(MaterialChip)<ChipProps>(({ theme }) => ({ const Chip = styled(MaterialChip)<ChipProps>(({ theme }) => ({

View File

@@ -7,7 +7,7 @@ export interface HelperTextProps extends MaterialFormHelperTextProps {
/** /**
* Custom component for the root node. * Custom component for the root node.
*/ */
component?: string | ElementType; component?: ElementType;
} }
const HelperText = styled(MaterialFormHelperText)<HelperTextProps>({ const HelperText = styled(MaterialFormHelperText)<HelperTextProps>({

View File

@@ -1,41 +1,42 @@
import type { OptionUnstyledProps } from '@mui/base/OptionUnstyled'; import {
import OptionUnstyled, { Option as BaseOption,
optionUnstyledClasses, optionClasses as baseOptionClasses,
} from '@mui/base/OptionUnstyled'; type OptionProps as BaseOptionProps,
} from '@mui/base';
import { darken, styled } from '@mui/material'; import { darken, styled } from '@mui/material';
import type { ForwardedRef } from 'react'; import type { ForwardedRef } from 'react';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
import OptionBase from './OptionBase'; import OptionBase from './OptionBase';
export interface OptionProps<TValue extends {}> export interface OptionProps<TValue extends {}>
extends OptionUnstyledProps<TValue> {} extends BaseOptionProps<TValue> {}
const StyledOption = styled(OptionUnstyled)(({ theme }) => ({ const StyledOption = styled(BaseOption)(({ theme }) => ({
transition: theme.transitions.create(['background-color']), transition: theme.transitions.create(['background-color']),
color: theme.palette.text.primary, color: theme.palette.text.primary,
[`&.${optionUnstyledClasses.selected}`]: { [`&.${baseOptionClasses.selected}`]: {
backgroundColor: backgroundColor:
theme.palette.mode === 'dark' theme.palette.mode === 'dark'
? `${darken(theme.palette.action.hover, 0.1)} !important` ? `${darken(theme.palette.action.hover, 0.1)} !important`
: `${darken(theme.palette.action.hover, 0.05)} !important`, : `${darken(theme.palette.action.hover, 0.05)} !important`,
}, },
[`&.${optionUnstyledClasses.selected}:hover, &.${optionUnstyledClasses.selected}.${optionUnstyledClasses.highlighted}`]: [`&.${baseOptionClasses.selected}:hover, &.${baseOptionClasses.selected}.${baseOptionClasses.highlighted}`]:
{ {
backgroundColor: backgroundColor:
theme.palette.mode === 'dark' theme.palette.mode === 'dark'
? `${darken(theme.palette.action.hover, 0.25)} !important` ? `${darken(theme.palette.action.hover, 0.25)} !important`
: `${darken(theme.palette.action.hover, 0.075)} !important`, : `${darken(theme.palette.action.hover, 0.075)} !important`,
}, },
[`&.${optionUnstyledClasses.highlighted}, &:hover`]: { [`&.${baseOptionClasses.highlighted}, &:hover`]: {
backgroundColor: backgroundColor:
theme.palette.mode === 'dark' theme.palette.mode === 'dark'
? `${darken(theme.palette.action.hover, 0.15)} !important` ? `${darken(theme.palette.action.hover, 0.15)} !important`
: `${theme.palette.action.hover} !important`, : `${theme.palette.action.hover} !important`,
}, },
[`&.${optionUnstyledClasses.disabled}`]: { [`&.${baseOptionClasses.disabled}`]: {
color: theme.palette.text.disabled, color: theme.palette.text.disabled,
}, },
[`&.${optionUnstyledClasses.disabled}:hover`]: { [`&.${baseOptionClasses.disabled}:hover`]: {
backgroundColor: 'transparent !important', backgroundColor: 'transparent !important',
}, },
})); }));

View File

@@ -1,11 +1,13 @@
import type { OptionGroupUnstyledProps } from '@mui/base/OptionGroupUnstyled'; import {
import OptionGroupUnstyled from '@mui/base/OptionGroupUnstyled'; OptionGroup as BaseOptionGroup,
type OptionGroupProps as BaseOptionGroupProps,
} from '@mui/base';
import { styled } from '@mui/material'; import { styled } from '@mui/material';
import type { ForwardedRef } from 'react'; import type { ForwardedRef } from 'react';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
import OptionGroupBase from './OptionGroupBase'; import OptionGroupBase from './OptionGroupBase';
export interface OptionGroupProps extends OptionGroupUnstyledProps {} export interface OptionGroupProps extends BaseOptionGroupProps {}
const StyledGroupRoot = styled('li')(({ theme }) => ({ const StyledGroupRoot = styled('li')(({ theme }) => ({
listStyle: 'none', listStyle: 'none',
@@ -25,7 +27,7 @@ function OptionGroup(
...externalSlots, ...externalSlots,
}; };
return <OptionGroupUnstyled {...props} ref={ref} slots={slots} />; return <BaseOptionGroup {...props} ref={ref} slots={slots} />;
} }
OptionGroup.displayName = 'NhostOptionGroup'; OptionGroup.displayName = 'NhostOptionGroup';

View File

@@ -1,9 +1,9 @@
import type { FormControlProps } from '@/components/ui/v2/FormControl'; import type { FormControlProps } from '@/components/ui/v2/FormControl';
import { FormControl } from '@/components/ui/v2/FormControl'; import { FormControl } from '@/components/ui/v2/FormControl';
import PopperUnstyled from '@mui/base/PopperUnstyled'; import { Popper as BasePopper } from '@mui/base/Popper';
import type { SelectUnstyledProps } from '@mui/base/SelectUnstyled'; import type { SelectProps as BaseSelectProps } from '@mui/base/Select';
import SelectUnstyled from '@mui/base/SelectUnstyled'; import { Select as BaseSelect } from '@mui/base/Select';
import { styled } from '@mui/material'; import { styled } from '@mui/system';
import clsx from 'clsx'; import clsx from 'clsx';
import type { ForwardedRef, PropsWithoutRef } from 'react'; import type { ForwardedRef, PropsWithoutRef } from 'react';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
@@ -11,7 +11,7 @@ import type { ToggleButtonProps } from './ToggleButton';
import ToggleButton from './ToggleButton'; import ToggleButton from './ToggleButton';
export interface SelectProps<TValue extends {}> export interface SelectProps<TValue extends {}>
extends SelectUnstyledProps<TValue>, extends BaseSelectProps<TValue, false>,
Pick< Pick<
FormControlProps, FormControlProps,
| 'fullWidth' | 'fullWidth'
@@ -25,7 +25,7 @@ export interface SelectProps<TValue extends {}>
/** /**
* Props for component slots. * Props for component slots.
*/ */
slotProps?: SelectUnstyledProps<TValue>['slotProps'] & { slotProps?: BaseSelectProps<TValue, false>['slotProps'] & {
root?: Partial<PropsWithoutRef<ToggleButtonProps>>; root?: Partial<PropsWithoutRef<ToggleButtonProps>>;
label?: Partial<FormControlProps['labelProps']>; label?: Partial<FormControlProps['labelProps']>;
formControl?: Partial<FormControlProps>; formControl?: Partial<FormControlProps>;
@@ -59,8 +59,8 @@ const StyledListbox = styled('ul')(({ theme }) => ({
}, },
})); }));
const StyledPopper = styled(PopperUnstyled)` const StyledPopper = styled(BasePopper)`
z-index: 10; z-index: 9999;
`; `;
function Select<TValue>( function Select<TValue>(
@@ -80,7 +80,7 @@ function Select<TValue>(
}: SelectProps<TValue>, }: SelectProps<TValue>,
ref: ForwardedRef<HTMLButtonElement>, ref: ForwardedRef<HTMLButtonElement>,
) { ) {
const slots: SelectUnstyledProps<TValue>['slots'] = { const slots: BaseSelectProps<TValue, false>['slots'] = {
root: ToggleButton, root: ToggleButton,
popper: StyledPopper, popper: StyledPopper,
listbox: StyledListbox, listbox: StyledListbox,
@@ -107,7 +107,7 @@ function Select<TValue>(
htmlFor: props.id, htmlFor: props.id,
}} }}
> >
<SelectUnstyled <BaseSelect
aria-label={typeof label === 'string' ? label : undefined} aria-label={typeof label === 'string' ? label : undefined}
{...props} {...props}
className={clsx(error && 'error')} className={clsx(error && 'error')}
@@ -117,7 +117,6 @@ function Select<TValue>(
...slotProps, ...slotProps,
root: { root: {
...slotProps?.root, ...slotProps?.root,
placeholder,
}, },
listbox: { listbox: {
...slotProps?.listbox, ...slotProps?.listbox,
@@ -132,7 +131,7 @@ function Select<TValue>(
placeholder={placeholder} placeholder={placeholder}
> >
{children} {children}
</SelectUnstyled> </BaseSelect>
</FormControl> </FormControl>
); );
} }

View File

@@ -1,8 +1,10 @@
import { ChevronDownIcon } from '@/components/ui/v2/icons/ChevronDownIcon'; import { ChevronDownIcon } from '@/components/ui/v2/icons/ChevronDownIcon';
import { ChevronUpIcon } from '@/components/ui/v2/icons/ChevronUpIcon'; import { ChevronUpIcon } from '@/components/ui/v2/icons/ChevronUpIcon';
import type { ButtonUnstyledProps } from '@mui/base/ButtonUnstyled'; import {
import ButtonUnstyled from '@mui/base/ButtonUnstyled'; Button as ButtonUnstyled,
import { selectUnstyledClasses } from '@mui/base/SelectUnstyled'; type ButtonProps as ButtonUnstyledProps,
} from '@mui/base';
import { selectClasses as selectUnstyledClasses } from '@mui/base/Select';
import type { SxProps } from '@mui/material'; import type { SxProps } from '@mui/material';
import { styled } from '@mui/material'; import { styled } from '@mui/material';
import type { Theme } from '@mui/system'; import type { Theme } from '@mui/system';
@@ -24,6 +26,7 @@ export interface ToggleButtonProps
Omit<DetailedHTMLProps<HTMLProps<HTMLSpanElement>, HTMLSpanElement>, 'as'> Omit<DetailedHTMLProps<HTMLProps<HTMLSpanElement>, HTMLSpanElement>, 'as'>
>; >;
}; };
placeholder?: string;
} }
const StyledButton = styled(ButtonUnstyled)(({ theme }) => ({ const StyledButton = styled(ButtonUnstyled)(({ theme }) => ({

View File

@@ -1,14 +1,15 @@
import type { FormControlLabelProps } from '@/components/ui/v2/FormControlLabel'; import type { FormControlLabelProps } from '@/components/ui/v2/FormControlLabel';
import { FormControlLabel } from '@/components/ui/v2/FormControlLabel'; import { FormControlLabel } from '@/components/ui/v2/FormControlLabel';
import SwitchUnstyled, { import {
switchUnstyledClasses, Switch as BaseSwitch,
} from '@mui/base/SwitchUnstyled'; switchClasses as baseSwitchClasses,
import type { SwitchUnstyledProps } from '@mui/base/SwitchUnstyled/SwitchUnstyled.types'; } from '@mui/base';
import type { SwitchProps as BaseSwitchProps } from '@mui/base/Switch';
import { styled } from '@mui/material'; import { styled } from '@mui/material';
import type { ForwardedRef, PropsWithoutRef } from 'react'; import type { ForwardedRef, PropsWithoutRef } from 'react';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
export interface SwitchProps extends SwitchUnstyledProps { export interface SwitchProps extends BaseSwitchProps {
/** /**
* Label to be displayed next to the checkbox. * Label to be displayed next to the checkbox.
*/ */
@@ -16,11 +17,11 @@ export interface SwitchProps extends SwitchUnstyledProps {
/** /**
* Props to be passed to the internal components. * Props to be passed to the internal components.
*/ */
slotProps?: SwitchUnstyledProps['slotProps'] & { slotProps?: BaseSwitchProps['slotProps'] & {
/** /**
* Props to be passed to the `Switch` component. * Props to be passed to the `Switch` component.
*/ */
root?: Partial<SwitchUnstyledProps>; root?: Partial<BaseSwitchProps>;
/** /**
* Props to be passed to the `FormControlLabel` component. * Props to be passed to the `FormControlLabel` component.
*/ */
@@ -35,23 +36,23 @@ const StyledFormControlLabel = styled(FormControlLabel)(({ theme }) => ({
justifyContent: 'start', justifyContent: 'start',
})); }));
const StyledSwitch = styled(SwitchUnstyled)(({ theme }) => ({ const StyledSwitch = styled(BaseSwitch)(({ theme }) => ({
position: 'relative', position: 'relative',
display: 'inline-block', display: 'inline-block',
width: '40px', width: '40px',
height: '24px', height: '24px',
cursor: 'pointer', cursor: 'pointer',
[`&.${switchUnstyledClasses.disabled}`]: { [`&.${baseSwitchClasses.disabled}`]: {
cursor: 'not-allowed', cursor: 'not-allowed',
[`& .${switchUnstyledClasses.track}`]: { [`& .${baseSwitchClasses.track}`]: {
backgroundColor: theme.palette.grey[200], backgroundColor: theme.palette.grey[200],
color: theme.palette.grey[200], color: theme.palette.grey[200],
}, },
}, },
[`& .${switchUnstyledClasses.track}`]: { [`& .${baseSwitchClasses.track}`]: {
backgroundColor: backgroundColor:
theme.palette.mode === 'dark' theme.palette.mode === 'dark'
? theme.palette.grey[500] ? theme.palette.grey[500]
@@ -63,7 +64,7 @@ const StyledSwitch = styled(SwitchUnstyled)(({ theme }) => ({
position: 'absolute', position: 'absolute',
}, },
[` & .${switchUnstyledClasses.thumb}`]: { [` & .${baseSwitchClasses.thumb}`]: {
display: 'block', display: 'block',
width: '18px', width: '18px',
height: '18px', height: '18px',
@@ -77,24 +78,24 @@ const StyledSwitch = styled(SwitchUnstyled)(({ theme }) => ({
transitionDuration: '120ms', transitionDuration: '120ms',
}, },
[`&.${switchUnstyledClasses.focusVisible} .${switchUnstyledClasses.thumb}`]: { [`&.${baseSwitchClasses.focusVisible} .${baseSwitchClasses.thumb}`]: {
backgroundColor: theme.palette.action.focus, backgroundColor: theme.palette.action.focus,
boxShadow: '0 0 1px 8px rgba(0, 0, 0, 0.25)', boxShadow: '0 0 1px 8px rgba(0, 0, 0, 0.25)',
}, },
[`&.${switchUnstyledClasses.checked}`]: { [`&.${baseSwitchClasses.checked}`]: {
[`.${switchUnstyledClasses.thumb}`]: { [`.${baseSwitchClasses.thumb}`]: {
left: '19px', left: '19px',
top: '3px', top: '3px',
backgroundColor: theme.palette.common.white, backgroundColor: theme.palette.common.white,
}, },
[`.${switchUnstyledClasses.track}`]: { [`.${baseSwitchClasses.track}`]: {
backgroundColor: theme.palette.primary.main, backgroundColor: theme.palette.primary.main,
}, },
[`&.${switchUnstyledClasses.disabled}`]: { [`&.${baseSwitchClasses.disabled}`]: {
[`.${switchUnstyledClasses.track}`]: { [`.${baseSwitchClasses.track}`]: {
opacity: 0.5, opacity: 0.5,
backgroundColor: backgroundColor:
theme.palette.mode === 'dark' theme.palette.mode === 'dark'
@@ -104,7 +105,7 @@ const StyledSwitch = styled(SwitchUnstyled)(({ theme }) => ({
}, },
}, },
[`& .${switchUnstyledClasses.input}`]: { [`& .${baseSwitchClasses.input}`]: {
cursor: 'inherit', cursor: 'inherit',
position: 'absolute', position: 'absolute',
width: '100%', width: '100%',

View File

@@ -1,7 +1,6 @@
import { import messagesState, {
messagesState,
type ProjectMessage, type ProjectMessage,
} from '@/features/ai/DevAssistant/state'; } from '@/features/ai/DevAssistant/state/messages';
import { selectorFamily } from 'recoil'; import { selectorFamily } from 'recoil';
const projectMessagesState = selectorFamily<ProjectMessage[], string>({ const projectMessagesState = selectorFamily<ProjectMessage[], string>({

View File

@@ -196,14 +196,14 @@ export default function EditUserForm({
className="flex flex-col overflow-hidden border-t-1 lg:flex-auto lg:content-between" className="flex flex-col overflow-hidden border-t-1 lg:flex-auto lg:content-between"
onSubmit={onSubmit} onSubmit={onSubmit}
> >
<Box className="flex-auto divide-y overflow-y-auto"> <Box className="flex-auto overflow-y-auto divide-y">
<Box <Box
component="section" component="section"
className="grid grid-flow-col p-6 lg:grid-cols-7" className="grid grid-flow-col p-6 lg:grid-cols-7"
> >
<div className="col-span-6 grid grid-flow-col place-content-start items-center gap-4"> <div className="grid items-center grid-flow-col col-span-6 gap-4 place-content-start">
<Avatar className="h-12 w-12" src={user.avatarUrl} /> <Avatar className="w-12 h-12" src={user.avatarUrl} />
<div className="grid grid-flow-row items-center"> <div className="grid items-center grid-flow-row">
<Text className="text-lg font-medium">{user.displayName}</Text> <Text className="text-lg font-medium">{user.displayName}</Text>
<Text className="text-sm+ font-normal" color="secondary"> <Text className="text-sm+ font-normal" color="secondary">
{user.email} {user.email}
@@ -225,7 +225,7 @@ export default function EditUserForm({
Actions Actions
</Button> </Button>
</Dropdown.Trigger> </Dropdown.Trigger>
<Dropdown.Content menu className="h-full w-full"> <Dropdown.Content menu className="w-full h-full">
<Dropdown.Item <Dropdown.Item
className="font-medium" className="font-medium"
sx={{ color: 'error.main' }} sx={{ color: 'error.main' }}
@@ -253,11 +253,11 @@ export default function EditUserForm({
component="section" component="section"
className="grid grid-flow-row grid-cols-4 gap-8 p-6" className="grid grid-flow-row grid-cols-4 gap-8 p-6"
> >
<InputLabel as="h3" className="col-span-1 self-center"> <InputLabel as="h3" className="self-center col-span-1">
User ID User ID
</InputLabel> </InputLabel>
<div className="col-span-3 grid grid-flow-col items-center justify-start gap-2"> <div className="grid items-center justify-start grid-flow-col col-span-3 gap-2">
<Text className="truncate font-medium">{user.id}</Text> <Text className="font-medium truncate">{user.id}</Text>
<IconButton <IconButton
variant="borderless" variant="borderless"
color="secondary" color="secondary"
@@ -267,18 +267,18 @@ export default function EditUserForm({
copy(user.id, 'User ID'); copy(user.id, 'User ID');
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</div> </div>
<InputLabel as="h3" className="col-span-1 self-center "> <InputLabel as="h3" className="self-center col-span-1 ">
Created At Created At
</InputLabel> </InputLabel>
<Text className="col-span-3 font-medium"> <Text className="col-span-3 font-medium">
{format(new Date(user.createdAt), 'yyyy-MM-dd HH:mm:ss')} {format(new Date(user.createdAt), 'yyyy-MM-dd HH:mm:ss')}
</Text> </Text>
<InputLabel as="h3" className="col-span-1 self-center "> <InputLabel as="h3" className="self-center col-span-1 ">
Last Seen Last Seen
</InputLabel> </InputLabel>
<Text className="col-span-3 font-medium"> <Text className="col-span-3 font-medium">
@@ -336,14 +336,14 @@ export default function EditUserForm({
autoComplete="off" autoComplete="off"
/> />
<div className="col-span-1 my-1 grid grid-flow-col grid-cols-8 items-center"> <div className="grid items-center grid-flow-col grid-cols-8 col-span-1 my-1">
<div className="col-span-2 "> <div className="col-span-2 ">
<InputLabel as="h3">Password</InputLabel> <InputLabel as="h3">Password</InputLabel>
</div> </div>
<Button <Button
color="primary" color="primary"
variant="borderless" variant="borderless"
className="col-span-6 place-self-start px-2" className="col-span-6 px-2 place-self-start"
onClick={handleChangeUserPassword} onClick={handleChangeUserPassword}
> >
Change Change
@@ -392,12 +392,12 @@ export default function EditUserForm({
</Box> </Box>
<Box <Box
component="section" component="section"
className="grid place-content-start gap-4 p-6 lg:grid-cols-4" className="grid gap-4 p-6 place-content-start lg:grid-cols-4"
> >
<div className="col-span-1 items-center self-center align-middle"> <div className="items-center self-center col-span-1 align-middle">
<InputLabel as="h3">OAuth Providers</InputLabel> <InputLabel as="h3">OAuth Providers</InputLabel>
</div> </div>
<div className="col-span-3 grid w-full grid-flow-row gap-y-6"> <div className="grid w-full grid-flow-row col-span-3 gap-y-6">
{user.userProviders.length === 0 && ( {user.userProviders.length === 0 && (
<div className="grid grid-flow-col place-content-between gap-x-1"> <div className="grid grid-flow-col place-content-between gap-x-1">
<Text className="font-normal" color="disabled"> <Text className="font-normal" color="disabled">
@@ -408,10 +408,10 @@ export default function EditUserForm({
{user.userProviders.map((provider) => ( {user.userProviders.map((provider) => (
<div <div
className="grid grid-flow-col place-content-between gap-3" className="grid grid-flow-col gap-3 place-content-between"
key={provider.id} key={provider.id}
> >
<div className="span-cols-1 grid grid-flow-col gap-2"> <div className="grid grid-flow-col gap-2 span-cols-1">
<Image <Image
src={ src={
theme.palette.mode === 'dark' theme.palette.mode === 'dark'
@@ -424,6 +424,7 @@ export default function EditUserForm({
} }
width={25} width={25}
height={25} height={25}
alt='Oauth provider logo'
/> />
<Text className="font-medium capitalize"> <Text className="font-medium capitalize">
{getReadableProviderName(provider.providerId)} {getReadableProviderName(provider.providerId)}
@@ -436,7 +437,7 @@ export default function EditUserForm({
{!isAnonymous && ( {!isAnonymous && (
<Box <Box
component="section" component="section"
className="grid grid-flow-row gap-y-10 p-6" className="grid grid-flow-row p-6 gap-y-10"
> >
<ControlledSelect <ControlledSelect
{...register('defaultRole')} {...register('defaultRole')}
@@ -456,11 +457,11 @@ export default function EditUserForm({
</Option> </Option>
))} ))}
</ControlledSelect> </ControlledSelect>
<div className="grid grid-flow-row place-content-start gap-6 lg:grid-flow-col lg:grid-cols-8"> <div className="grid grid-flow-row gap-6 place-content-start lg:grid-flow-col lg:grid-cols-8">
<InputLabel as="h3" className="col-span-2"> <InputLabel as="h3" className="col-span-2">
Allowed Roles Allowed Roles
</InputLabel> </InputLabel>
<div className="col-span-3 grid grid-flow-row gap-6"> <div className="grid grid-flow-row col-span-3 gap-6">
{roles.map((role, i) => ( {roles.map((role, i) => (
<ControlledCheckbox <ControlledCheckbox
id={`roles.${i}`} id={`roles.${i}`}
@@ -476,7 +477,7 @@ export default function EditUserForm({
)} )}
</Box> </Box>
<Box className="grid w-full flex-shrink-0 snap-end grid-flow-col justify-between gap-3 place-self-end border-t-1 p-2"> <Box className="grid justify-between flex-shrink-0 w-full grid-flow-col gap-3 p-2 snap-end place-self-end border-t-1">
<Button <Button
variant="outlined" variant="outlined"
color="secondary" color="secondary"

View File

@@ -226,12 +226,12 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
if (!users) { if (!users) {
return ( return (
<div className="h-screen w-screen overflow-hidden"> <div className="w-screen h-screen overflow-hidden">
<div className="absolute top-0 left-0 z-50 block h-full w-full"> <div className="absolute top-0 left-0 z-50 block w-full h-full">
<span className="top50percent relative top-1/2 mx-auto my-0 block"> <span className="relative block mx-auto my-0 top50percent top-1/2">
<ActivityIndicator <ActivityIndicator
label="Loading users..." label="Loading users..."
className="my-auto flex items-center justify-center" className="flex items-center justify-center my-auto"
/> />
</span> </span>
</div> </div>
@@ -269,7 +269,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
}} }}
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium" className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
> >
<UserIcon className="h-4 w-4" /> <UserIcon className="w-4 h-4" />
<Text className="font-medium">View User</Text> <Text className="font-medium">View User</Text>
</Dropdown.Item> </Dropdown.Item>
@@ -280,7 +280,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
sx={{ color: 'error.main' }} sx={{ color: 'error.main' }}
onClick={() => handleDeleteUser(user)} onClick={() => handleDeleteUser(user)}
> >
<TrashIcon className="h-4 w-4" /> <TrashIcon className="w-4 h-4" />
<Text className="font-medium" color="error"> <Text className="font-medium" color="error">
Delete User Delete User
</Text> </Text>
@@ -294,14 +294,14 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
onClick={() => handleViewUser(user)} onClick={() => handleViewUser(user)}
aria-label={`View ${user.displayName}`} aria-label={`View ${user.displayName}`}
> >
<div className="col-span-2 grid grid-flow-col place-content-start gap-4"> <div className="grid grid-flow-col col-span-2 gap-4 place-content-start">
<Avatar <Avatar
src={user.avatarUrl} src={user.avatarUrl}
alt={`Avatar of ${user.displayName}`} alt={`Avatar of ${user.displayName}`}
/> />
<div className="grid grid-flow-row items-center"> <div className="grid items-center grid-flow-row">
<div className="grid grid-flow-col items-center gap-2"> <div className="grid items-center grid-flow-col gap-2">
<Text className="truncate font-medium leading-5"> <Text className="font-medium leading-5 truncate">
{user.displayName} {user.displayName}
</Text> </Text>
{user.disabled && ( {user.disabled && (
@@ -314,7 +314,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
)} )}
</div> </div>
<Text className="truncate font-normal" color="secondary"> <Text className="font-normal truncate" color="secondary">
{user.email} {user.email}
</Text> </Text>
</div> </div>
@@ -334,7 +334,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
: '-'} : '-'}
</Text> </Text>
<div className="col-span-2 hidden grid-flow-col place-content-start gap-3 px-4 lg:grid"> <div className="hidden grid-flow-col col-span-2 gap-3 px-4 place-content-start lg:grid">
{user.userProviders.length === 0 && ( {user.userProviders.length === 0 && (
<Text className="col-span-3 font-medium">-</Text> <Text className="col-span-3 font-medium">-</Text>
)} )}
@@ -362,6 +362,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
} }
width={16} width={16}
height={16} height={16}
alt='Oauth provider logo'
/> />
} }
/> />

View File

@@ -102,10 +102,10 @@ export default function BaseColumnForm({
return ( return (
<Form <Form
onSubmit={handleExternalSubmit} onSubmit={handleExternalSubmit}
className="flex flex-auto flex-col content-between overflow-hidden border-t-1" className="flex flex-col content-between flex-auto overflow-hidden border-t-1"
> >
<div className="flex-auto overflow-y-auto"> <div className="flex-auto overflow-y-auto">
<section className="grid grid-cols-8 py-3 px-6"> <section className="grid grid-cols-8 px-6 py-3">
<Input <Input
{...register('name', { {...register('name', {
onChange: (event) => { onChange: (event) => {
@@ -184,7 +184,7 @@ export default function BaseColumnForm({
</Text> </Text>
</span> </span>
} }
className="col-span-8 m-0 w-full py-3 sm:col-span-6 sm:col-start-3 sm:ml-1" className="w-full col-span-8 py-3 m-0 sm:col-span-6 sm:col-start-3 sm:ml-1"
onChange={(_event, checked) => { onChange={(_event, checked) => {
if (checked) { if (checked) {
setDefaultValueInputText(''); setDefaultValueInputText('');
@@ -197,7 +197,7 @@ export default function BaseColumnForm({
<Box <Box
component="section" component="section"
className="grid grid-cols-8 border-t-1 py-3 px-6" className="grid grid-cols-8 px-6 py-3 border-t-1"
> >
<ControlledAutocomplete <ControlledAutocomplete
id="defaultValue" id="defaultValue"
@@ -249,7 +249,7 @@ export default function BaseColumnForm({
/> />
<ControlledCheckbox <ControlledCheckbox
className="col-span-8 m-0 w-full py-3 sm:col-span-6 sm:col-start-3 sm:ml-1" className="w-full col-span-8 py-3 m-0 sm:col-span-6 sm:col-start-3 sm:ml-1"
name="isNullable" name="isNullable"
label={ label={
<span className="inline-grid grid-flow-row"> <span className="inline-grid grid-flow-row">
@@ -269,7 +269,7 @@ export default function BaseColumnForm({
/> />
<ControlledCheckbox <ControlledCheckbox
className="col-span-8 m-0 w-full py-3 sm:col-span-6 sm:col-start-3 sm:ml-1" className="w-full col-span-8 py-3 m-0 sm:col-span-6 sm:col-start-3 sm:ml-1"
name="isUnique" name="isUnique"
label={ label={
<span className="inline-grid grid-flow-row"> <span className="inline-grid grid-flow-row">
@@ -306,7 +306,7 @@ export default function BaseColumnForm({
</Box> </Box>
</div> </div>
<Box className="grid flex-shrink-0 grid-flow-col justify-between gap-3 border-t-1 p-2"> <Box className="grid justify-between flex-shrink-0 grid-flow-col gap-3 p-2 border-t-1">
<Button <Button
variant="borderless" variant="borderless"
color="secondary" color="secondary"

View File

@@ -107,9 +107,9 @@ export default function BaseForeignKeyForm({
selectedColumn?.isPrimary || selectedColumn?.isUnique || false, selectedColumn?.isPrimary || selectedColumn?.isUnique || false,
}); });
}} }}
className="flex flex-auto flex-col content-between overflow-hidden pb-4" className="flex flex-col content-between flex-auto pb-4 overflow-hidden"
> >
<Box className="grid flex-auto grid-flow-row gap-4 overflow-y-auto border-t-1 py-4"> <Box className="grid flex-auto grid-flow-row gap-4 py-4 overflow-y-auto border-t-1">
<Box component="section" className="grid grid-flow-row gap-4 px-6"> <Box component="section" className="grid grid-flow-row gap-4 px-6">
<Text variant="h3">From</Text> <Text variant="h3">From</Text>
@@ -185,7 +185,7 @@ export default function BaseForeignKeyForm({
</Box> </Box>
</Box> </Box>
<Box className="grid flex-shrink-0 grid-flow-row gap-2 border-t-1 px-6 pt-4"> <Box className="grid flex-shrink-0 grid-flow-row gap-2 px-6 pt-4 border-t-1">
<Button loading={isSubmitting} disabled={isSubmitting} type="submit"> <Button loading={isSubmitting} disabled={isSubmitting} type="submit">
{submitButtonText} {submitButtonText}
</Button> </Button>

View File

@@ -14,8 +14,8 @@ import { Text } from '@/components/ui/v2/Text';
import { useMetadataQuery } from '@/features/database/dataGrid/hooks/useMetadataQuery'; import { useMetadataQuery } from '@/features/database/dataGrid/hooks/useMetadataQuery';
import { useTableQuery } from '@/features/database/dataGrid/hooks/useTableQuery'; import { useTableQuery } from '@/features/database/dataGrid/hooks/useTableQuery';
import { getTruncatedText } from '@/utils/getTruncatedText'; import { getTruncatedText } from '@/utils/getTruncatedText';
import type { AutocompleteGroupedOption } from '@mui/base/AutocompleteUnstyled'; import type { AutocompleteGroupedOption } from '@mui/base/useAutocomplete';
import { useAutocomplete } from '@mui/base/AutocompleteUnstyled'; import { useAutocomplete } from '@mui/base/useAutocomplete';
import type { AutocompleteRenderGroupParams } from '@mui/material/Autocomplete'; import type { AutocompleteRenderGroupParams } from '@mui/material/Autocomplete';
import { autocompleteClasses } from '@mui/material/Autocomplete'; import { autocompleteClasses } from '@mui/material/Autocomplete';
import type { import type {
@@ -366,11 +366,11 @@ function ColumnAutocomplete(
); );
}} }}
> >
<ArrowLeftIcon className="h-4 w-4" /> <ArrowLeftIcon className="w-4 h-4" />
</IconButton> </IconButton>
)} )}
<Text className="direction-rtl truncate text-left"> <Text className="text-left truncate direction-rtl">
<Text component="span" color="disabled"> <Text component="span" color="disabled">
{defaultTable} {defaultTable}
</Text> </Text>

View File

@@ -14,6 +14,7 @@ import { triggerToast } from '@/utils/toast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import type * as Yup from 'yup';
export interface CreateColumnFormProps export interface CreateColumnFormProps
extends Pick<BaseColumnFormProps, 'onCancel' | 'location'> { extends Pick<BaseColumnFormProps, 'onCancel' | 'location'> {
@@ -50,7 +51,9 @@ export default function CreateColumnForm({
resetForeignKeyError(); resetForeignKeyError();
} }
const form = useForm<BaseColumnFormValues>({ const form = useForm<
BaseColumnFormValues | Yup.InferType<typeof baseColumnValidationSchema>
>({
defaultValues: { defaultValues: {
name: '', name: '',
type: null, type: null,

View File

@@ -11,6 +11,7 @@ import {
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useState } from 'react'; import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import type * as Yup from 'yup';
export interface CreateForeignKeyFormProps export interface CreateForeignKeyFormProps
extends Pick< extends Pick<
@@ -34,7 +35,10 @@ export default function CreateForeignKeyForm({
}: CreateForeignKeyFormProps) { }: CreateForeignKeyFormProps) {
const [error, setError] = useState<Error>(null); const [error, setError] = useState<Error>(null);
const form = useForm<BaseForeignKeyFormValues>({ const form = useForm<
| BaseForeignKeyFormValues
| Yup.InferType<typeof baseForeignKeyValidationSchema>
>({
defaultValues: { defaultValues: {
id: null, id: null,
name: '', name: '',

View File

@@ -16,6 +16,7 @@ import { triggerToast } from '@/utils/toast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import type * as Yup from 'yup';
export interface CreateTableFormProps export interface CreateTableFormProps
extends Pick<BaseTableFormProps, 'onCancel' | 'location'> { extends Pick<BaseTableFormProps, 'onCancel' | 'location'> {
@@ -53,7 +54,9 @@ export default function CreateTableForm({
const error = createTableError || trackTableError || foreignKeyError; const error = createTableError || trackTableError || foreignKeyError;
const form = useForm<BaseTableFormValues>({ const form = useForm<
BaseTableFormValues | Yup.InferType<typeof baseTableValidationSchema>
>({
defaultValues: { defaultValues: {
columns: [ columns: [
{ {

View File

@@ -241,7 +241,7 @@ function DataBrowserSidebarContent({
) { ) {
openDrawer({ openDrawer({
title: ( title: (
<span className="inline-grid grid-flow-col items-center gap-2"> <span className="inline-grid items-center grid-flow-col gap-2">
Permissions Permissions
<InlineCode className="!text-sm+ font-normal">{table}</InlineCode> <InlineCode className="!text-sm+ font-normal">{table}</InlineCode>
<Chip label="Preview" size="small" color="info" component="span" /> <Chip label="Preview" size="small" color="info" component="span" />
@@ -263,12 +263,12 @@ function DataBrowserSidebarContent({
} }
return ( return (
<Box className="flex h-full flex-col justify-between"> <Box className="flex flex-col justify-between h-full">
<Box className="flex flex-col px-2"> <Box className="flex flex-col px-2">
{schemas && schemas.length > 0 && ( {schemas && schemas.length > 0 && (
<Select <Select
renderValue={(option) => ( renderValue={(option) => (
<span className="grid grid-flow-col items-center gap-1"> <span className="grid items-center grid-flow-col gap-1">
{option?.label} {option?.label}
</span> </span>
)} )}
@@ -281,7 +281,7 @@ function DataBrowserSidebarContent({
> >
{schemas.map((schema) => ( {schemas.map((schema) => (
<Option <Option
className="grid grid-flow-col items-center gap-1" className="grid items-center grid-flow-col gap-1"
value={schema.schema_name} value={schema.schema_name}
key={schema.schema_name} key={schema.schema_name}
> >
@@ -295,7 +295,7 @@ function DataBrowserSidebarContent({
</Text> </Text>
{(isSchemaLocked(schema.schema_name) || isGitHubConnected) && ( {(isSchemaLocked(schema.schema_name) || isGitHubConnected) && (
<LockIcon <LockIcon
className="h-3 w-3" className="w-3 h-3"
sx={{ color: 'text.secondary' }} sx={{ color: 'text.secondary' }}
/> />
)} )}
@@ -317,7 +317,7 @@ function DataBrowserSidebarContent({
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
underline="hover" underline="hover"
className="grid grid-flow-col items-center justify-start gap-1" className="grid items-center justify-start grid-flow-col gap-1"
> >
Learn More <ArrowRightIcon /> Learn More <ArrowRightIcon />
</Link> </Link>
@@ -327,7 +327,7 @@ function DataBrowserSidebarContent({
<Button <Button
variant="borderless" variant="borderless"
endIcon={<PlusIcon />} endIcon={<PlusIcon />}
className="mt-1 w-full justify-between px-2" className="justify-between w-full px-2 mt-1"
onClick={() => { onClick={() => {
openDrawer({ openDrawer({
title: 'Create a New Table', title: 'Create a New Table',
@@ -396,7 +396,7 @@ function DataBrowserSidebarContent({
} }
> >
<UsersIcon <UsersIcon
className="h-4 w-4" className="w-4 h-4"
sx={{ color: 'text.secondary' }} sx={{ color: 'text.secondary' }}
/> />
<span>View Permissions</span> <span>View Permissions</span>
@@ -426,7 +426,7 @@ function DataBrowserSidebarContent({
} }
> >
<PencilIcon <PencilIcon
className="h-4 w-4" className="w-4 h-4"
sx={{ color: 'text.secondary' }} sx={{ color: 'text.secondary' }}
/> />
<span>Edit Table</span> <span>Edit Table</span>
@@ -449,7 +449,7 @@ function DataBrowserSidebarContent({
} }
> >
<UsersIcon <UsersIcon
className="h-4 w-4" className="w-4 h-4"
sx={{ color: 'text.secondary' }} sx={{ color: 'text.secondary' }}
/> />
<span>Edit Permissions</span> <span>Edit Permissions</span>
@@ -473,7 +473,7 @@ function DataBrowserSidebarContent({
} }
> >
<TrashIcon <TrashIcon
className="h-4 w-4" className="w-4 h-4"
sx={{ color: 'error.main' }} sx={{ color: 'error.main' }}
/> />
<span>Delete Table</span> <span>Delete Table</span>
@@ -521,7 +521,7 @@ function DataBrowserSidebarContent({
component={NavLink} component={NavLink}
href={sqlEditorHref} href={sqlEditorHref}
> >
<div className="flex w-full flex-row items-center justify-center space-x-4"> <div className="flex flex-row items-center justify-center w-full space-x-4">
<TerminalIcon /> <TerminalIcon />
<span className="flex">SQL Editor</span> <span className="flex">SQL Editor</span>
</div> </div>

View File

@@ -17,6 +17,7 @@ import { triggerToast } from '@/utils/toast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import type * as Yup from 'yup';
export interface EditColumnFormProps export interface EditColumnFormProps
extends Pick<BaseColumnFormProps, 'onCancel' | 'location'> { extends Pick<BaseColumnFormProps, 'onCancel' | 'location'> {
@@ -78,7 +79,9 @@ export default function EditColumnForm({
comment: originalColumn.comment || null, comment: originalColumn.comment || null,
}; };
const form = useForm<BaseColumnFormValues>({ const form = useForm<
BaseColumnFormValues | Yup.InferType<typeof baseColumnValidationSchema>
>({
defaultValues: columnValues, defaultValues: columnValues,
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
resolver: yupResolver(baseColumnValidationSchema), resolver: yupResolver(baseColumnValidationSchema),

View File

@@ -12,6 +12,7 @@ import type { ForeignKeyRelation } from '@/features/database/dataGrid/types/data
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useState } from 'react'; import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import type * as Yup from 'yup';
export interface EditForeignKeyFormProps export interface EditForeignKeyFormProps
extends Pick< extends Pick<
@@ -40,7 +41,10 @@ export default function EditForeignKeyForm({
}: EditForeignKeyFormProps) { }: EditForeignKeyFormProps) {
const [error, setError] = useState<Error>(null); const [error, setError] = useState<Error>(null);
const form = useForm<BaseForeignKeyFormValues>({ const form = useForm<
| BaseForeignKeyFormValues
| Yup.InferType<typeof baseForeignKeyValidationSchema>
>({
defaultValues: { defaultValues: {
id: foreignKeyRelation.id, id: foreignKeyRelation.id,
name: foreignKeyRelation.name, name: foreignKeyRelation.name,

View File

@@ -22,6 +22,7 @@ import { yupResolver } from '@hookform/resolvers/yup';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import type * as Yup from 'yup';
export interface EditTableFormProps export interface EditTableFormProps
extends Pick<BaseTableFormProps, 'onCancel' | 'location'> { extends Pick<BaseTableFormProps, 'onCancel' | 'location'> {
@@ -85,7 +86,9 @@ export default function EditTableForm({
resetUpdateError(); resetUpdateError();
} }
const form = useForm<BaseTableFormValues>({ const form = useForm<
BaseTableFormValues | Yup.InferType<typeof baseTableValidationSchema>
>({
defaultValues: { defaultValues: {
name: originalTable.table_name, name: originalTable.table_name,
columns: [], columns: [],

View File

@@ -45,7 +45,7 @@ export default function AuthDomain() {
const [updateConfig] = useUpdateConfigMutation(); const [updateConfig] = useUpdateConfigMutation();
const form = useForm<{ capacity: number }>({ const form = useForm<Yup.InferType<typeof validationSchema>>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
defaultValues: { capacity }, defaultValues: { capacity },
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),

View File

@@ -4,7 +4,6 @@ import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button'; import { Button } from '@/components/ui/v2/Button';
import { Checkbox } from '@/components/ui/v2/Checkbox'; import { Checkbox } from '@/components/ui/v2/Checkbox';
import { BaseDialog } from '@/components/ui/v2/Dialog'; import { BaseDialog } from '@/components/ui/v2/Dialog';
import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useAppState } from '@/features/projects/common/hooks/useAppState'; import { useAppState } from '@/features/projects/common/hooks/useAppState';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
@@ -85,6 +84,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
const currentPlan = plans.find((plan) => plan.id === app.plan.id); const currentPlan = plans.find((plan) => plan.id === app.plan.id);
const selectedPlan = plans.find((plan) => plan.id === selectedPlanId); const selectedPlan = plans.find((plan) => plan.id === selectedPlanId);
const higherPlans = plans.filter((plan) => plan.price > currentPlan.price);
useEffect(() => { useEffect(() => {
if (!pollingCurrentProject || state === ApplicationStatus.Paused) { if (!pollingCurrentProject || state === ApplicationStatus.Paused) {
@@ -201,53 +201,6 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
); );
} }
if (app.plan.id !== plans.find((plan) => plan.isFree)?.id) {
return (
<Box className="mx-auto w-full max-w-xl rounded-lg p-6 text-left">
<div className="flex flex-col">
<div className="mx-auto">
<Image
src="/assets/upgrade.svg"
alt="Nhost Logo"
width={72}
height={72}
/>
</div>
<Text variant="h3" component="h2" className="mt-2 text-center">
Downgrade is not available
</Text>
<Text className="mt-1 text-center">
You can&apos;t downgrade from a paid plan to a free plan here.
</Text>
<Text className="text-center">
Please contact us at{' '}
<Link href="mailto:info@nhost.io">info@nhost.io</Link> if you want
to downgrade.
</Text>
<div className="mt-6 grid grid-flow-row gap-2">
<Button
variant="outlined"
color="secondary"
className="mx-auto w-full max-w-sm"
onClick={() => {
if (close) {
close();
}
closeAlertDialog();
}}
>
Cancel
</Button>
</div>
</div>
</Box>
);
}
return ( return (
<Box className="w-full max-w-xl rounded-lg p-6 text-left"> <Box className="w-full max-w-xl rounded-lg p-6 text-left">
<BaseDialog <BaseDialog
@@ -277,7 +230,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
</Text> </Text>
<div className="mt-2"> <div className="mt-2">
{plans {higherPlans
.filter((plan) => plan.id !== app.plan.id) .filter((plan) => plan.id !== app.plan.id)
.map((plan) => ( .map((plan) => (
<div className="mt-4" key={plan.id}> <div className="mt-4" key={plan.id}>

View File

@@ -102,7 +102,7 @@ export default function WorkspaceAndProjectList({
placeholder="Find Project" placeholder="Find Project"
startAdornment={ startAdornment={
<SearchIcon <SearchIcon
className="ml-2 -mr-1 h-4 w-4 shrink-0" className="w-4 h-4 ml-2 -mr-1 shrink-0"
sx={{ color: 'text.disabled' }} sx={{ color: 'text.disabled' }}
/> />
} }
@@ -123,7 +123,7 @@ export default function WorkspaceAndProjectList({
</NavLink> </NavLink>
</Box> </Box>
<Box className="my-8 grid grid-flow-row gap-8"> <Box className="grid grid-flow-row gap-8 my-8">
{filteredWorkspaces.map((workspace) => ( {filteredWorkspaces.map((workspace) => (
<div key={workspace.slug}> <div key={workspace.slug}>
<NavLink href={`/${workspace.slug}`} passHref> <NavLink href={`/${workspace.slug}`} passHref>
@@ -147,7 +147,7 @@ export default function WorkspaceAndProjectList({
secondaryAction={ secondaryAction={
<div className="grid grid-flow-col gap-px"> <div className="grid grid-flow-col gap-px">
{latestDeployment && ( {latestDeployment && (
<div className="mr-2 flex self-center align-middle"> <div className="flex self-center mr-2 align-middle">
<StatusCircle <StatusCircle
status={ status={
latestDeployment.deploymentStatus as DeploymentStatus latestDeployment.deploymentStatus as DeploymentStatus
@@ -171,10 +171,11 @@ export default function WorkspaceAndProjectList({
<NavLink <NavLink
href={`${workspace?.slug}/${project.slug}`} href={`${workspace?.slug}/${project.slug}`}
passHref passHref
className='w-full'
> >
<ListItem.Button className="rounded-none"> <ListItem.Button className="rounded-none">
<ListItem.Avatar> <ListItem.Avatar>
<div className="h-10 w-10 overflow-hidden rounded-lg"> <div className="w-10 h-10 overflow-hidden rounded-lg">
<Image <Image
src="/logos/new.svg" src="/logos/new.svg"
alt="Nhost Logo" alt="Nhost Logo"

View File

@@ -48,14 +48,14 @@ export default function WorkspaceSidebar({
<List className="grid grid-flow-row gap-2"> <List className="grid grid-flow-row gap-2">
{workspaces.map(({ id, name, slug }) => ( {workspaces.map(({ id, name, slug }) => (
<ListItem.Root key={id}> <ListItem.Root key={id}>
<NavLink href={`/${slug}`} passHref> <NavLink href={`/${slug}`} passHref className='w-full'>
<ListItem.Button <ListItem.Button
dense dense
aria-label={`View ${name}`} aria-label={`View ${name}`}
className="!p-1" className="!p-1"
> >
<ListItem.Avatar className="h-8 w-8"> <ListItem.Avatar className="w-8 h-8">
<div className="inline-block h-8 w-8 overflow-hidden rounded-lg"> <div className="inline-block w-8 h-8 overflow-hidden rounded-lg">
<Image <Image
src="/logos/new.svg" src="/logos/new.svg"
alt="Nhost Logo" alt="Nhost Logo"
@@ -131,7 +131,7 @@ export default function WorkspaceSidebar({
rel="noreferrer noopener" rel="noreferrer noopener"
> >
<Button <Button
className="grid grid-flow-col gap-1" className="grid w-full grid-flow-col gap-1"
variant="outlined" variant="outlined"
color="secondary" color="secondary"
startIcon={<GitHubIcon />} startIcon={<GitHubIcon />}
@@ -147,7 +147,7 @@ export default function WorkspaceSidebar({
rel="noreferrer noopener" rel="noreferrer noopener"
> >
<Button <Button
className="grid grid-flow-col gap-1" className="grid w-full grid-flow-col gap-1"
variant="outlined" variant="outlined"
color="secondary" color="secondary"
aria-labelledby="discord-button-label" aria-labelledby="discord-button-label"

View File

@@ -10,7 +10,7 @@ export default function useIsCurrentUserOwner() {
const { currentWorkspace, loading } = useCurrentWorkspaceAndProject(); const { currentWorkspace, loading } = useCurrentWorkspaceAndProject();
const currentUser = useUserData(); const currentUser = useUserData();
if (loading || !currentWorkspace.workspaceMembers || !currentUser) { if (loading || !currentWorkspace?.workspaceMembers || !currentUser) {
return false; return false;
} }

View File

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

View File

@@ -32,7 +32,7 @@ export default function AuthDomain() {
const [updateConfig] = useUpdateConfigMutation(); const [updateConfig] = useUpdateConfigMutation();
const form = useForm<{ auth_fqdn: string }>({ const form = useForm<Yup.InferType<typeof validationSchema>>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
defaultValues: { auth_fqdn: null }, defaultValues: { auth_fqdn: null },
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),

View File

@@ -32,7 +32,7 @@ export default function HasuraDomain() {
const [updateConfig] = useUpdateConfigMutation(); const [updateConfig] = useUpdateConfigMutation();
const form = useForm<{ hasura_fqdn: string }>({ const form = useForm<Yup.InferType<typeof validationSchema>>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
defaultValues: { hasura_fqdn: null }, defaultValues: { hasura_fqdn: null },
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),

View File

@@ -40,7 +40,7 @@ export default function RunServicePortDomain({
const runServicePort = service.config.ports.find((p) => p.port === port); const runServicePort = service.config.ports.find((p) => p.port === port);
const initialValue = runServicePort?.ingresses?.[0]?.fqdn?.[0]; const initialValue = runServicePort?.ingresses?.[0]?.fqdn?.[0];
const form = useForm<{ runServicePortFQDN: string }>({ const form = useForm<RunServicePortFormValues>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
defaultValues: { defaultValues: {
runServicePortFQDN: initialValue, runServicePortFQDN: initialValue,

View File

@@ -34,7 +34,7 @@ export default function ServerlessFunctionsDomain() {
const [updateConfig] = useUpdateConfigMutation(); const [updateConfig] = useUpdateConfigMutation();
const form = useForm<{ functions_fqdn: string }>({ const form = useForm<ServerlessFunctionsDomainFormValues>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
defaultValues: { functions_fqdn: null }, defaultValues: { functions_fqdn: null },
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),

View File

@@ -16,7 +16,8 @@ export default function OverviewTopBar() {
const isPlatform = useIsPlatform(); const isPlatform = useIsPlatform();
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject(); const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
const isOwner = useIsCurrentUserOwner(); const isOwner = useIsCurrentUserOwner();
const isPro = !currentProject?.plan?.isFree; const isStarter = currentProject?.plan?.name === 'Starter';
const isPro = currentProject?.plan?.name === 'Pro';
const { openDialog } = useDialog(); const { openDialog } = useDialog();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
@@ -65,7 +66,6 @@ export default function OverviewTopBar() {
> >
{currentProject.name} {currentProject.name}
</Text> </Text>
{currentProject.creator && ( {currentProject.creator && (
<Text <Text
color="secondary" color="secondary"
@@ -81,15 +81,14 @@ export default function OverviewTopBar() {
ago ago
</Text> </Text>
)} )}
<div className="mt-1 inline-grid grid-flow-col items-center justify-start gap-2 md:mt-0"> <div className="mt-1 inline-grid grid-flow-col items-center justify-start gap-2 md:mt-0">
<Chip <Chip
size="small" size="small"
label={currentProject.plan.name} label={currentProject.plan.name}
color={isPro ? 'primary' : 'default'} color={!isStarter ? 'primary' : 'default'}
/> />
{!isPro && isOwner && ( {(isStarter || isPro) && isOwner && (
<Button <Button
variant="borderless" variant="borderless"
className="mr-2" className="mr-2"

View File

@@ -27,7 +27,7 @@ function AllWorkspaceApps() {
if (currentWorkspace?.projects?.length === 0) { if (currentWorkspace?.projects?.length === 0) {
return ( return (
<Box className="flex flex-row border-y py-4"> <Box className="flex flex-row py-4 border-y">
<Text className="text-xs" color="secondary"> <Text className="text-xs" color="secondary">
No projects on this workspace. No projects on this workspace.
</Text> </Text>
@@ -45,11 +45,12 @@ function AllWorkspaceApps() {
<NavLink <NavLink
href={`${currentWorkspace?.slug}/${project.slug}`} href={`${currentWorkspace?.slug}/${project.slug}`}
passHref passHref
className='w-full'
> >
<ListItem.Button className="grid grid-flow-col items-center justify-between gap-2"> <ListItem.Button className="grid items-center justify-between grid-flow-col gap-2">
<div className="grid grid-flow-col items-center justify-start gap-2"> <div className="grid items-center justify-start grid-flow-col gap-2">
<ListItem.Avatar> <ListItem.Avatar>
<div className="h-8 w-8 overflow-hidden rounded-lg"> <div className="w-8 h-8 overflow-hidden rounded-lg">
<Image <Image
src="/logos/new.svg" src="/logos/new.svg"
alt="Nhost Logo" alt="Nhost Logo"
@@ -99,8 +100,8 @@ export default function WorkspaceApps() {
return ( return (
<div className="mt-9"> <div className="mt-9">
<div className="mx-auto max-w-3xl font-display"> <div className="max-w-3xl mx-auto font-display">
<div className="mb-4 grid grid-flow-col items-center justify-between gap-2"> <div className="grid items-center justify-between grid-flow-col gap-2 mb-4">
<Text className="text-lg font-medium">Projects</Text> <Text className="text-lg font-medium">Projects</Text>
{!loading && ( {!loading && (

View File

@@ -13,20 +13,18 @@ import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useHostName } from '@/features/projects/common/hooks/useHostName'; import { useHostName } from '@/features/projects/common/hooks/useHostName';
import { InfoCard } from '@/features/projects/overview/components/InfoCard'; import { InfoCard } from '@/features/projects/overview/components/InfoCard';
import { import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
COST_PER_VCPU,
MAX_SERVICES_CPU,
MAX_SERVICES_MEM,
MAX_SERVICE_REPLICAS,
MIN_SERVICES_CPU,
MIN_SERVICES_MEM,
} from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection'; import { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection';
import { EnvironmentFormSection } from '@/features/services/components/ServiceForm/components/EnvironmentFormSection'; import { EnvironmentFormSection } from '@/features/services/components/ServiceForm/components/EnvironmentFormSection';
import { PortsFormSection } from '@/features/services/components/ServiceForm/components/PortsFormSection'; import { PortsFormSection } from '@/features/services/components/ServiceForm/components/PortsFormSection';
import { ReplicasFormSection } from '@/features/services/components/ServiceForm/components/ReplicasFormSection'; import { ReplicasFormSection } from '@/features/services/components/ServiceForm/components/ReplicasFormSection';
import { StorageFormSection } from '@/features/services/components/ServiceForm/components/StorageFormSection'; import { StorageFormSection } from '@/features/services/components/ServiceForm/components/StorageFormSection';
import type { DialogFormProps } from '@/types/common';
import {
validationSchema,
type ServiceFormProps,
type ServiceFormValues,
} from '@/features/services/components/ServiceForm/ServiceFormTypes';
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common'; import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
import { getToastStyleProps } from '@/utils/constants/settings'; import { getToastStyleProps } from '@/utils/constants/settings';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
@@ -42,72 +40,9 @@ import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast'; import { toast } from 'react-hot-toast';
import { parse } from 'shell-quote'; import { parse } from 'shell-quote';
import * as Yup from 'yup';
import { ServiceConfirmationDialog } from './components/ServiceConfirmationDialog'; import { ServiceConfirmationDialog } from './components/ServiceConfirmationDialog';
import { ServiceDetailsDialog } from './components/ServiceDetailsDialog'; import { ServiceDetailsDialog } from './components/ServiceDetailsDialog';
export enum PortTypes {
HTTP = 'http',
TCP = 'tcp',
UDP = 'udp',
}
export const validationSchema = Yup.object({
name: Yup.string().required('The name is required.'),
image: Yup.string().label('Image to run'),
command: Yup.string(),
environment: Yup.array().of(
Yup.object().shape({
name: Yup.string().required(),
value: Yup.string().required(),
}),
),
compute: Yup.object({
cpu: Yup.number().min(MIN_SERVICES_CPU).max(MAX_SERVICES_CPU).required(),
memory: Yup.number().min(MIN_SERVICES_MEM).max(MAX_SERVICES_MEM).required(),
}),
replicas: Yup.number().min(0).max(MAX_SERVICE_REPLICAS).required(),
ports: Yup.array().of(
Yup.object().shape({
port: Yup.number().required(),
type: Yup.mixed<PortTypes>().oneOf(Object.values(PortTypes)).required(),
publish: Yup.boolean().default(false),
}),
),
storage: Yup.array().of(
Yup.object()
.shape({
name: Yup.string().required(),
path: Yup.string().required(),
capacity: Yup.number().nonNullable().required(),
})
.required(),
),
});
export type ServiceFormValues = Yup.InferType<typeof validationSchema>;
export interface ServiceFormProps extends DialogFormProps {
/**
* To use in conjunction with initialData to allow for updating the service
*/
serviceID?: string;
/**
* if there is initialData then it's an update operation
*/
initialData?: ServiceFormValues & { subdomain?: string }; // subdomain is only set on the backend
/**
* Function to be called when the operation is cancelled.
*/
onCancel?: VoidFunction;
/**
* Function to be called when the submit is successful.
*/
onSubmit?: VoidFunction | ((args?: any) => Promise<any>);
}
export default function ServiceForm({ export default function ServiceForm({
serviceID, serviceID,
initialData, initialData,

View File

@@ -0,0 +1,67 @@
import { PortTypes } from '@/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes';
import type { DialogFormProps } from '@/types/common';
import * as Yup from 'yup';
import {
MAX_SERVICES_CPU,
MAX_SERVICES_MEM,
MAX_SERVICE_REPLICAS,
MIN_SERVICES_CPU,
MIN_SERVICES_MEM,
} from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
export const validationSchema = Yup.object({
name: Yup.string().required('The name is required.'),
image: Yup.string().label('Image to run'),
command: Yup.string(),
environment: Yup.array().of(
Yup.object().shape({
name: Yup.string().required(),
value: Yup.string().required(),
}),
),
compute: Yup.object({
cpu: Yup.number().min(MIN_SERVICES_CPU).max(MAX_SERVICES_CPU).required(),
memory: Yup.number().min(MIN_SERVICES_MEM).max(MAX_SERVICES_MEM).required(),
}),
replicas: Yup.number().min(0).max(MAX_SERVICE_REPLICAS).required(),
ports: Yup.array().of(
Yup.object().shape({
port: Yup.number().required(),
type: Yup.mixed<PortTypes>().oneOf(Object.values(PortTypes)).required(),
publish: Yup.boolean().default(false),
}),
),
storage: Yup.array().of(
Yup.object()
.shape({
name: Yup.string().required(),
path: Yup.string().required(),
capacity: Yup.number().nonNullable().required(),
})
.required(),
),
});
export type ServiceFormValues = Yup.InferType<typeof validationSchema>;
export interface ServiceFormProps extends DialogFormProps {
/**
* To use in conjunction with initialData to allow for updating the service
*/
serviceID?: string;
/**
* if there is initialData then it's an update operation
*/
initialData?: ServiceFormValues & { subdomain?: string }; // subdomain is only set on the backend
/**
* Function to be called when the operation is cancelled.
*/
onCancel?: VoidFunction;
/**
* Function to be called when the submit is successful.
*/
onSubmit?: VoidFunction | ((args?: any) => Promise<any>);
}

View File

@@ -11,7 +11,7 @@ import {
MEM_CPU_RATIO, MEM_CPU_RATIO,
MIN_SERVICES_MEM, MIN_SERVICES_MEM,
} from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema'; } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import type { ServiceFormValues } from '@/features/services/components/ServiceForm'; import type { ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
import { useFormContext, useWatch } from 'react-hook-form'; import { useFormContext, useWatch } from 'react-hook-form';
interface ComputeFormSectionProps { interface ComputeFormSectionProps {

View File

@@ -6,7 +6,7 @@ import { TrashIcon } from '@/components/ui/v2/icons/TrashIcon';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip'; import { Tooltip } from '@/components/ui/v2/Tooltip';
import type { ServiceFormValues } from '@/features/services/components/ServiceForm'; import type { ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
import { useState } from 'react'; import { useState } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form'; import { useFieldArray, useFormContext } from 'react-hook-form';

View File

@@ -11,10 +11,8 @@ import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip'; import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { InfoCard } from '@/features/projects/overview/components/InfoCard'; import { InfoCard } from '@/features/projects/overview/components/InfoCard';
import { import { PortTypes } from '@/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes';
PortTypes, import { type ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
type ServiceFormValues,
} from '@/features/services/components/ServiceForm';
import { useFieldArray, useFormContext, useWatch } from 'react-hook-form'; import { useFieldArray, useFormContext, useWatch } from 'react-hook-form';
export default function PortsFormSection() { export default function PortsFormSection() {

View File

@@ -0,0 +1,5 @@
export enum PortTypes {
HTTP = 'http',
TCP = 'tcp',
UDP = 'udp',
}

View File

@@ -1,2 +1 @@
/* eslint-disable import/no-cycle */
export { default as PortsFormSection } from './PortsFormSection'; export { default as PortsFormSection } from './PortsFormSection';

View File

@@ -4,7 +4,7 @@ import { Slider } from '@/components/ui/v2/Slider';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip'; import { Tooltip } from '@/components/ui/v2/Tooltip';
import { MAX_SERVICE_REPLICAS } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema'; import { MAX_SERVICE_REPLICAS } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import type { ServiceFormValues } from '@/features/services/components/ServiceForm'; import type { ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
import { useFormContext, useWatch } from 'react-hook-form'; import { useFormContext, useWatch } from 'react-hook-form';
export default function ReplicasFormSection() { export default function ReplicasFormSection() {

View File

@@ -5,7 +5,7 @@ import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip'; import { Tooltip } from '@/components/ui/v2/Tooltip';
import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema'; import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import type { ServiceFormValues } from '@/features/services/components/ServiceForm'; import type { ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common'; import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
export interface ServiceConfirmationDialogProps { export interface ServiceConfirmationDialogProps {

View File

@@ -10,7 +10,7 @@ import {
MAX_STORAGE_CAPACITY, MAX_STORAGE_CAPACITY,
MIN_STORAGE_CAPACITY, MIN_STORAGE_CAPACITY,
} from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema'; } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import type { ServiceFormValues } from '@/features/services/components/ServiceForm'; import type { ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
import { useFieldArray, useFormContext } from 'react-hook-form'; import { useFieldArray, useFormContext } from 'react-hook-form';
export default function StorageFormSection() { export default function StorageFormSection() {
@@ -53,7 +53,8 @@ export default function StorageFormSection() {
<span> <span>
By default, services do not have persistent storage. You can add By default, services do not have persistent storage. You can add
SSD disks to the service here. It is important to note that SSD disks to the service here. It is important to note that
capacity can not be decreased after creation, only expanded. Refer to{' '} capacity can not be decreased after creation, only expanded.
Refer to{' '}
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
@@ -82,7 +83,7 @@ export default function StorageFormSection() {
{fields.map((field, index) => ( {fields.map((field, index) => (
<Box <Box
key={field.id} key={field.id}
className="flex w-full flex-col space-y-2 xs+:flex-row xs+:space-y-0 xs+:space-x-2" className="flex w-full flex-col space-y-2 xs+:flex-row xs+:space-x-2 xs+:space-y-0"
> >
<Input <Input
{...register(`storage.${index}.name`)} {...register(`storage.${index}.name`)}

View File

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

View File

@@ -11,10 +11,8 @@ import { UserIcon } from '@/components/ui/v2/icons/UserIcon';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip'; import { Tooltip } from '@/components/ui/v2/Tooltip';
import { DeleteServiceModal } from '@/features/projects/common/components/DeleteServiceModal'; import { DeleteServiceModal } from '@/features/projects/common/components/DeleteServiceModal';
import { import { ServiceForm } from '@/features/services/components/ServiceForm';
ServiceForm, import { type PortTypes } from '@/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes';
type PortTypes,
} from '@/features/services/components/ServiceForm';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
import type { RunService } from 'pages/[workspaceSlug]/[appSlug]/services'; import type { RunService } from 'pages/[workspaceSlug]/[appSlug]/services';

View File

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

View File

@@ -1,14 +0,0 @@
import hypertune from '@/hypertune/hypertune';
import { useEffect, useState } from 'react';
export default function useHypertune() {
const [, setIsInitialized] = useState<boolean>(hypertune.isInitialized());
useEffect(() => {
hypertune.waitForInitialization().then(() => {
setIsInitialized(true);
});
}, []);
return hypertune;
}

View File

@@ -1,5 +0,0 @@
import { initializeHypertune } from './project_2596';
const hypertune = initializeHypertune({});
export default hypertune;

View File

@@ -1,107 +0,0 @@
/* eslint-disable */
import * as sdk from "hypertune";
const projectId = 2596;
const businessToken = `U2FsdGVkX19+V8BJnVR0xLEC+42OW5qZl/A0i6beAaRmJoIhFh5Yf6eIKBzLbV9h`;
const queryCode = `query InitQuery {
root {
enableServices
}
}
`;
const query = {"Query":{"objectTypeName":"Query","selection":{"root":{"fieldArguments":{"__isPartialObject__":true},"fieldQuery":{"Root":{"objectTypeName":"Root","selection":{"enableServices":{"fieldArguments":{},"fieldQuery":null}}}}}}}};
const fallbackInitData: sdk.FallbackInitData & { [key: string]: unknown } = {"commitId":3297,"reducedExpression":{"id":"caxyeQqTKX3UGOXClvbnW","logs":{"events":{},"exposures":{},"evaluations":{}},"type":"ObjectExpression","fields":{"root":{"id":"PoMWxsy7KbW9fCq5XXvx4","body":{"id":"IUICRjZ7iSnh9k0cWBmnd","logs":{"events":{},"exposures":{},"evaluations":{}},"type":"ObjectExpression","fields":{"enableServices":{"id":"7WZWy2AIy_q9Vbz4cn9KB","logs":{"evaluations":{"XNOtHkUBpglrY1nkYa_bf":1},"events":{},"exposures":{}},"type":"BooleanExpression","value":true,"valueType":{"type":"BooleanValueType"}}},"valueType":{"type":"ObjectValueType","objectTypeName":"Root"},"objectTypeName":"Root"},"logs":{"events":{},"exposures":{},"evaluations":{}},"type":"FunctionExpression","valueType":{"type":"FunctionValueType","returnValueType":{"type":"ObjectValueType","objectTypeName":"Root"},"parameterValueTypes":[{"type":"ObjectValueType","objectTypeName":"Query_root_args"}]},"parameters":[{"id":"Ygjhl2LqjiwcousTABFQz","name":"rootArgs"}]}},"metadata":{"permissions":{"user":{},"group":{"team":{"write":"allow"}}}},"valueType":{"type":"ObjectValueType","objectTypeName":"Query"},"objectTypeName":"Query"},"splits":{},"eventTypes":{},"commitConfig":{"splitConfig":{}},"initLogId":0,"commitHash":"4178461588049503","sdkConfig":{"hashPollInterval":1000,"flushLogsInterval":1000,"maxLogsPerFlush":1},"query":{"Query":{"objectTypeName":"Query","selection":{"root":{"fieldArguments":{"__isPartialObject__":true},"fieldQuery":{"Root":{"objectTypeName":"Root","selection":{"enableServices":{"fieldArguments":{},"fieldQuery":null}}}}}}}}};
export function initializeHypertune(
variableValues: Rec,
options: sdk.InitializeOptions = {}
): QueryNode {
const defaultOptions = { businessToken, query, fallbackInitData };
return sdk.initialize(
QueryNode,
projectId,
queryCode,
variableValues,
{ ...defaultOptions, ...options }
);
}
// Enum types
// Input object types
export type Rec = {
//
};
export type Rec2 = {
context: Rec3;
//
};
export type Rec3 = {
workSpace: Rec4;
//
};
export type Rec4 = {
id: string;
//
};
// Enum node classes
// Fragment node classes
export class QueryNode extends sdk.Node {
typeName = "Query" as const;
root(args: Rec2): RootNode {
const props0 = this.getField("root", args);
const expression0 = props0.expression;
if (
expression0 &&
expression0.type === "ObjectExpression"
&& expression0.objectTypeName === "Root"
) {
return new RootNode(props0);
}
const node = new RootNode(props0);
node._logUnexpectedTypeError();
return node;
}
}
export class RootNode extends sdk.Node {
typeName = "Root" as const;
enableServices(args: Rec): sdk.BooleanNode {
const props0 = this.getField("enableServices", args);
const expression0 = props0.expression;
if (
expression0 &&
expression0.type === "BooleanExpression"
) {
return new sdk.BooleanNode(props0);
}
const node = new sdk.BooleanNode(props0);
node._logUnexpectedTypeError();
return node;
}
}

View File

@@ -117,7 +117,7 @@ function GraphiQLHeader({ onUserChange, onRoleChange }: GraphiQLHeaderProps) {
} }
return ( return (
<header className="grid grid-flow-row items-end gap-2 p-2 md:grid-flow-col md:justify-between"> <header className="grid items-end grid-flow-row gap-2 p-2 md:grid-flow-col md:justify-between">
<div className="grid grid-flow-row gap-2 md:grid-flow-col md:items-end"> <div className="grid grid-flow-row gap-2 md:grid-flow-col md:items-end">
<div className="grid grid-cols-2 gap-2 md:grid-flow-col md:grid-cols-[initial]"> <div className="grid grid-cols-2 gap-2 md:grid-flow-col md:grid-cols-[initial]">
<UserSelect <UserSelect

View File

@@ -13,10 +13,8 @@ import type { GetRunServicesQuery } from '@/utils/__generated__/graphql';
import { useGetRunServicesQuery } from '@/utils/__generated__/graphql'; import { useGetRunServicesQuery } from '@/utils/__generated__/graphql';
import { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification'; import { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification';
import { import { ServiceForm } from '@/features/services/components/ServiceForm';
ServiceForm, import { type PortTypes } from '@/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes';
type PortTypes,
} from '@/features/services/components/ServiceForm';
import ServicesList from '@/features/services/components/ServicesList/ServicesList'; import ServicesList from '@/features/services/components/ServicesList/ServicesList';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { import {

View File

@@ -11,7 +11,6 @@ import { Option } from '@/components/ui/v2/Option';
import { Radio } from '@/components/ui/v2/Radio'; import { Radio } from '@/components/ui/v2/Radio';
import { RadioGroup } from '@/components/ui/v2/RadioGroup'; import { RadioGroup } from '@/components/ui/v2/RadioGroup';
import { Select } from '@/components/ui/v2/Select'; import { Select } from '@/components/ui/v2/Select';
import type { TextProps } from '@/components/ui/v2/Text';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip'; import { Tooltip } from '@/components/ui/v2/Tooltip';
import { planDescriptions } from '@/features/projects/common/utils/planDescriptions'; import { planDescriptions } from '@/features/projects/common/utils/planDescriptions';
@@ -34,9 +33,10 @@ import {
import type { ApolloError } from '@apollo/client'; import type { ApolloError } from '@apollo/client';
import { useUserData } from '@nhost/nextjs'; import { useUserData } from '@nhost/nextjs';
import Image from 'next/image'; import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import type { FormEvent, ReactElement } from 'react'; import type { FormEvent, ReactElement } from 'react';
import { cloneElement, isValidElement, useState } from 'react'; import { useState } from 'react';
import { toast } from 'react-hot-toast'; import { toast } from 'react-hot-toast';
import slugify from 'slugify'; import slugify from 'slugify';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
@@ -320,22 +320,22 @@ export function NewProjectPageContent({
}); });
}} }}
value={selectedRegion.id} value={selectedRegion.id}
renderValue={(option) => { renderValue={() => (
const [flag, , country] = (option?.label as any[]) || []; <div className="relative grid grid-flow-col items-center justify-start gap-x-3">
<span className="row-span-2 flex">
return ( <Image
<span className="inline-grid grid-flow-col grid-rows-none items-center gap-x-2"> src={`/assets/flags/${selectedRegion.code}.svg`}
{flag} alt={`${selectedRegion.name} country flag`}
width={16}
{isValidElement<TextProps>(country) height={12}
? cloneElement(country, { />
...country.props,
variant: 'body1',
})
: null}
</span> </span>
);
}} <Text variant="body1" className="row-span-1">
{selectedRegion.name}
</Text>
</div>
)}
> >
{regionOptions.map((option) => ( {regionOptions.map((option) => (
<Option <Option
@@ -365,7 +365,7 @@ export function NewProjectPageContent({
{option.disabled && ( {option.disabled && (
<Text <Text
variant="subtitle2" variant="subtitle2"
className="absolute top-1/2 right-4 -translate-y-1/2" className="absolute right-4 top-1/2 -translate-y-1/2"
> >
Disabled Disabled
</Text> </Text>
@@ -444,6 +444,19 @@ export function NewProjectPageContent({
</Tooltip> </Tooltip>
); );
})} })}
<Text variant="subtitle2">
Select a plan that suits your infrastructure needs.{' '}
<Link href="https://nhost.io/pricing">
<a
href="https://nhost.io/pricing"
className="underline"
target="_blank"
rel="noopener noreferrer"
>
Learn more
</a>
</Link>
</Text>
</RadioGroup> </RadioGroup>
</div> </div>
</div> </div>

View File

@@ -40,6 +40,7 @@ export const mockRouter: NextRouter = {
emit: vi.fn(), emit: vi.fn(),
}, },
isFallback: false, isFallback: false,
forward: vi.fn(),
}; };
export const mockApplication: Project = { export const mockApplication: Project = {

View File

@@ -21,7 +21,7 @@ import {
render as rtlRender, render as rtlRender,
waitForElementToBeRemoved as rtlWaitForElementToBeRemoved, waitForElementToBeRemoved as rtlWaitForElementToBeRemoved,
} from '@testing-library/react'; } from '@testing-library/react';
import { RouterContext } from 'next/dist/shared/lib/router-context'; import { RouterContext } from 'next/dist/shared/lib/router-context.shared-runtime';
import type { PropsWithChildren, ReactElement } from 'react'; import type { PropsWithChildren, ReactElement } from 'react';
import { Toaster } from 'react-hot-toast'; import { Toaster } from 'react-hot-toast';

View File

@@ -1,5 +1,11 @@
# @nhost/docs # @nhost/docs
## 2.1.0
### Minor Changes
- 65b6a48d5: feat: added graphite/cli documentation
## 2.0.0 ## 2.0.0
### Major Changes ### Major Changes

View File

@@ -8,8 +8,19 @@ You can enable Graphite, Nhost's AI service, with the following steps:
<Steps> <Steps>
<Step title="Check your database version"> <Step title="Check your database version">
Check your project's settings and make sure the database version is at least `14.6-20231018-1`. If it isn't upgrade your database version (latest available version is recommended). Check your project's settings and make sure the database version is at least `14.6-20231018-1`. If it isn't upgrade your database version (latest available version is recommended).
<Tabs>
<Tab title="Dashboard">
![database settings](/images/guides/ai/enabling/database.png)
</Tab>
<Tab title="toml">
```toml
[postgres]
version = '14.6-20231018-1'
```
</Tab>
</Tabs>
![database settings](/images/guides/ai/enabling/database.png)
</Step> </Step>
<Step title="Get an OpenAPI key"> <Step title="Get an OpenAPI key">
Get an OpenAI API key from their [customer portal](https://platform.openai.com/account/api-keys). Get an OpenAI API key from their [customer portal](https://platform.openai.com/account/api-keys).
@@ -27,7 +38,7 @@ You can enable Graphite, Nhost's AI service, with the following steps:
```toml ```toml
[ai] [ai]
# Version of the service to use. Check the settings page for available versions # Version of the service to use. Check the settings page for available versions
version = '0.1.0' version = '0.3.1'
# Used to validate requests between postgres and the AI service. # Used to validate requests between postgres and the AI service.
# The AI service will also include the header X-Graphite-Webhook-Secret # The AI service will also include the header X-Graphite-Webhook-Secret
@@ -41,13 +52,13 @@ You can enable Graphite, Nhost's AI service, with the following steps:
[ai.openai] [ai.openai]
# Key to use for authenticating API requests to OpenAI. # Key to use for authenticating API requests to OpenAI.
apiKey = '{{ secrets.OPEANAI_API_KEY }}' apiKey = '{{ secrets.OPENAI_API_KEY }}'
# OpenAI organization to use. # OpenAI organization to use.
organization = 'my-org' organization = 'my-org'
[ai.resources.compute] [ai.resources.compute]
# Dedicated resources allocated for the service # Dedicated resources allocated for the service
cpu = 125 cpu = 128
memory = 256 memory = 256
``` ```
</Tab> </Tab>

View File

@@ -0,0 +1,45 @@
---
title: "Local Development"
icon: code
---
If you are using the Nhost CLI for local development, as of [v0.12.0](https://github.com/nhost/cli/releases/tag/v1.12.0) you can also start Graphite locally. To do so, follow the next steps:
<Steps>
<Step title="Configuring the Service">
Follow the steps highlighed in the ["Enabling Service"](enabling-service) guide and don't forget to add the relevant secrets to your `.secrets` file.
</Step>
<Step title="Start nhost">
Run `nhost up`:
![nhost up](/images/guides/ai/local_development/nhost_up.png)
After starting the service the first thing you will notice is that there is a new `ai` service running.
</Step>
<Step title="Commit metadata changes">
As you start the AI service metadata changes may be proposed:
![git status](/images/guides/ai/local_development/git_status.png)
We strongly recommmend you to commit them to your git repository so they can be deployed alongside your application.
</Step>
</Steps>
### Synhcronizing Auto-Embeddings
If you add [auto-embeddings](/guides/ai/auto-embeddings) configuration locally and want to synchronize them with the cloud we recommend inserting them using a migration rather than with the auto-embeddings UI:
![migration](/images/guides/ai/local_development/migration.png)
And then running `nhost up` to download the updated metadata. Afterwards you should see both database migrations and functions' metadata changes in your local project:
![git status](/images/guides/ai/local_development/git_status_functions.png)
Pushing them to your deployment branch will also deploy them to your cloud project.
### Synhcronizing Assistants
Similar to auto-embeddings, if you want to synchronize [assistants](/guides/ai/assistants) we recommend you to insert them using a migration and then running `nhost up` to update any metadata if necessary. After pushing the proposed changes to the deployment branch all the changes should be deployed to the cloud project.

View File

@@ -19,7 +19,7 @@ To connect your service to the Nhost stack, use the following information:
To connect to your own service internally from another service, follow these steps: To connect to your own service internally from another service, follow these steps:
1. Expose the desired port(s). 1. Configure the desired port(s).
Example for Redis service: Example for Redis service:
@@ -40,9 +40,9 @@ publish = false
</Tabs> </Tabs>
2. Once the port is exposed, you can connect to the service using its name and the corresponding port. 2. Once the port is configured, you can connect to the service using as host `run-${name}` and the corresponding port.
Example: `redis://user:password@redis:6379` Example: `redis://user:password@run-redis:6379`
If needed, you can open internally more than one port by repeating the block for each one of them: If needed, you can open internally more than one port by repeating the block for each one of them:

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 916 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

View File

@@ -102,6 +102,7 @@
"group": "AI", "group": "AI",
"pages": [ "pages": [
"guides/ai/enabling-service", "guides/ai/enabling-service",
"guides/ai/local_development",
"guides/ai/auto-embeddings", "guides/ai/auto-embeddings",
"guides/ai/assistants", "guides/ai/assistants",
"guides/ai/dev-assistant" "guides/ai/dev-assistant"
@@ -110,8 +111,8 @@
{ {
"group": "Authentication", "group": "Authentication",
"pages": [ "pages": [
{ {
"group": "Social Sign In", "group": "Social Sign In",
"icon": "at", "icon": "at",
"pages": [ "pages": [
"guides/auth/social/sign-in-apple", "guides/auth/social/sign-in-apple",
@@ -161,7 +162,7 @@
"icon": "users", "icon": "users",
"pages": [ "pages": [
{ {
"group": "Email and Password", "group": "Email and Password",
"icon": "envelope", "icon": "envelope",
"pages": [ "pages": [
"reference/auth/sign-up-email-and-password", "reference/auth/sign-up-email-and-password",
@@ -169,7 +170,7 @@
] ]
}, },
{ {
"group": "Passwordless", "group": "Passwordless",
"icon": "message-sms", "icon": "message-sms",
"pages": [ "pages": [
"reference/auth/sign-in-email-passwordless", "reference/auth/sign-in-email-passwordless",
@@ -178,7 +179,7 @@
] ]
}, },
{ {
"group": "OAuth", "group": "OAuth",
"icon": "at", "icon": "at",
"pages": [ "pages": [
"reference/auth/sign-in-oauth-provider", "reference/auth/sign-in-oauth-provider",
@@ -186,7 +187,7 @@
] ]
}, },
{ {
"group": "WebAuthn", "group": "WebAuthn",
"icon": "atom", "icon": "atom",
"pages": [ "pages": [
"reference/auth/sign-up-using-email-via-fido2-webauthn-authentication", "reference/auth/sign-up-using-email-via-fido2-webauthn-authentication",
@@ -196,7 +197,7 @@
] ]
}, },
{ {
"group": "Anonymous", "group": "Anonymous",
"icon": "luchador-mask", "icon": "luchador-mask",
"pages": [ "pages": [
"reference/auth/sign-in-anonymous", "reference/auth/sign-in-anonymous",
@@ -204,7 +205,7 @@
] ]
}, },
{ {
"group": "MFA", "group": "MFA",
"icon": "message-sms", "icon": "message-sms",
"pages": [ "pages": [
"reference/auth/generate-a-secret-to-request-the-activation-of-time-based-one-time-password-totp-multi-factor-authentication", "reference/auth/generate-a-secret-to-request-the-activation-of-time-based-one-time-password-totp-multi-factor-authentication",
@@ -213,7 +214,7 @@
] ]
}, },
{ {
"group": "User", "group": "User",
"icon": "user", "icon": "user",
"pages": [ "pages": [
"reference/auth/change-the-current-users-email", "reference/auth/change-the-current-users-email",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/docs", "name": "@nhost/docs",
"version": "2.0.0", "version": "2.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "mintlify dev" "start": "mintlify dev"

View File

@@ -50,10 +50,12 @@ In addition, thanks to [pgvector](https://github.com/pgvector/pgvector) you can
<CardGroup cols={4}> <CardGroup cols={4}>
<Card title="Enabling Service" icon="square-1" href="../guides/ai/enabling-service"> <Card title="Enabling Service" icon="square-1" href="../guides/ai/enabling-service">
</Card> </Card>
<Card title="Auto-Embeddings" icon="square-2" href="../guides/ai/auto-embeddings"> <Card title="Local Development" icon="square-2" href="../guides/ai/local_development">
</Card> </Card>
<Card title="Assistants" icon="square-3" href="../guides/ai/assistants"> <Card title="Auto-Embeddings" icon="square-3" href="../guides/ai/auto-embeddings">
</Card> </Card>
<Card title="Developer Assistant" icon="square-4" href="../guides/ai/dev-assistant"> <Card title="Assistants" icon="square-4" href="../guides/ai/assistants">
</Card>
<Card title="Developer Assistant" icon="square-5" href="../guides/ai/dev-assistant">
</Card> </Card>
</CardGroup> </CardGroup>

View File

@@ -1,5 +1,13 @@
# @nhost-examples/cli # @nhost-examples/cli
## 0.1.3
### Patch Changes
- 8d91f71: chore: update deps and enable pnpm audit
- Updated dependencies [8d91f71]
- @nhost/nhost-js@3.0.2
## 0.1.2 ## 0.1.2
### Patch Changes ### Patch Changes

View File

@@ -1,18 +1,18 @@
{ {
"name": "@nhost-examples/cli", "name": "@nhost-examples/cli",
"version": "0.1.2", "version": "0.1.3",
"main": "src/index.mjs", "main": "src/index.mjs",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node src/index.mjs" "start": "node src/index.mjs"
}, },
"dependencies": { "dependencies": {
"@nhost/nhost-js": "*", "@nhost/nhost-js": "^3.0.2",
"commander": "^10.0.0", "commander": "^10.0.1",
"dotenv": "^16.0.3", "dotenv": "^16.3.1",
"graphql": "16.6.0", "graphql": "16.8.1",
"graphql-tag": "^2.12.6", "graphql-tag": "^2.12.6",
"pino": "^8.14.1" "pino": "^8.17.2"
}, },
"engines": { "engines": {
"node": ">=16.0.0" "node": ">=16.0.0"

View File

@@ -1,5 +1,14 @@
# @nhost-examples/codegen-react-apollo # @nhost-examples/codegen-react-apollo
## 0.1.11
### Patch Changes
- 8d91f71: chore: update deps and enable pnpm audit
- Updated dependencies [8d91f71]
- @nhost/react-apollo@7.0.2
- @nhost/react@3.0.2
## 0.1.10 ## 0.1.10
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost-examples/codegen-react-apollo", "name": "@nhost-examples/codegen-react-apollo",
"version": "0.1.10", "version": "0.1.11",
"private": true, "private": true,
"scripts": { "scripts": {
"codegen": "graphql-codegen", "codegen": "graphql-codegen",
@@ -15,27 +15,27 @@
] ]
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.7.3", "@apollo/client": "^3.8.9",
"@nhost/react": "*", "@nhost/react": "^3.0.2",
"@nhost/react-apollo": "*", "@nhost/react-apollo": "^7.0.2",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"graphql": "15.7.2", "graphql": "16.8.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "^2.14.1", "@graphql-codegen/cli": "^2.16.5",
"@graphql-codegen/client-preset": "^1.1.5", "@graphql-codegen/client-preset": "^1.3.0",
"@graphql-typed-document-node/core": "^3.1.1", "@graphql-typed-document-node/core": "^3.2.0",
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.7",
"@types/node": "^18.11.9", "@types/node": "^18.19.6",
"@types/react": "^18.2.14", "@types/react": "^18.2.47",
"@types/react-dom": "^18.2.6", "@types/react-dom": "^18.2.18",
"@vitejs/plugin-react": "^3.0.0", "@vitejs/plugin-react": "^3.1.0",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.16",
"postcss": "^8.4.19", "postcss": "^8.4.33",
"tailwindcss": "^3.2.1", "tailwindcss": "^3.4.1",
"typescript": "^4.6.4", "typescript": "^4.9.5",
"vite": "^4.0.2" "vite": "^5.0.12"
} }
} }

View File

@@ -1,5 +1,13 @@
# @nhost-examples/codegen-react-query # @nhost-examples/codegen-react-query
## 0.1.12
### Patch Changes
- 8d91f71: chore: update deps and enable pnpm audit
- Updated dependencies [8d91f71]
- @nhost/react@3.0.2
## 0.1.11 ## 0.1.11
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost-examples/codegen-react-query", "name": "@nhost-examples/codegen-react-query",
"version": "0.1.11", "version": "0.1.12",
"private": true, "private": true,
"scripts": { "scripts": {
"codegen": "graphql-codegen", "codegen": "graphql-codegen",
@@ -15,28 +15,28 @@
] ]
}, },
"dependencies": { "dependencies": {
"@nhost/react": "*", "@nhost/react": "^3.0.2",
"@tanstack/react-query": "^4.2.3", "@tanstack/react-query": "^4.36.1",
"@tanstack/react-query-devtools": "^4.2.3", "@tanstack/react-query-devtools": "^4.36.1",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"graphql": "15.7.2", "graphql": "16.8.1",
"graphql-request": "^6.0.0", "graphql-request": "^6.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "^2.14.1", "@graphql-codegen/cli": "^2.16.5",
"@graphql-codegen/client-preset": "^1.1.5", "@graphql-codegen/client-preset": "^1.3.0",
"@graphql-typed-document-node/core": "^3.1.1", "@graphql-typed-document-node/core": "^3.2.0",
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.7",
"@types/node": "^16.11.56", "@types/node": "^16.18.70",
"@types/react": "^18.2.14", "@types/react": "^18.2.47",
"@types/react-dom": "^18.2.6", "@types/react-dom": "^18.2.18",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.16",
"eslint": "^8.23.0", "eslint": "^8.56.0",
"postcss": "^8.4.20", "postcss": "^8.4.33",
"tailwindcss": "^3.2.4", "tailwindcss": "^3.4.1",
"typescript": "^4.8.2", "typescript": "^4.9.5",
"vite": "^4.0.2" "vite": "^5.0.12"
} }
} }

View File

@@ -1,5 +1,14 @@
# @nhost-examples/react-urql # @nhost-examples/react-urql
## 0.0.8
### Patch Changes
- 8d91f71: chore: update deps and enable pnpm audit
- Updated dependencies [8d91f71]
- @nhost/react-urql@4.0.2
- @nhost/react@3.0.2
## 0.0.7 ## 0.0.7
### Patch Changes ### Patch Changes

View File

@@ -1,7 +1,7 @@
{ {
"name": "@nhost-examples/codegen-react-urql", "name": "@nhost-examples/codegen-react-urql",
"private": true, "private": true,
"version": "0.0.7", "version": "0.0.8",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc && vite build",
@@ -9,27 +9,27 @@
"codegen": "graphql-codegen" "codegen": "graphql-codegen"
}, },
"dependencies": { "dependencies": {
"@nhost/react": "*", "@nhost/react": "^3.0.2",
"@nhost/react-urql": "*", "@nhost/react-urql": "^4.0.2",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"graphql": "15.7.2", "graphql": "16.8.1",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"urql": "^3.0.3" "urql": "^3.0.4"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "^2.14.1", "@graphql-codegen/cli": "^2.16.5",
"@graphql-codegen/client-preset": "^1.1.5", "@graphql-codegen/client-preset": "^1.3.0",
"@graphql-typed-document-node/core": "^3.1.1", "@graphql-typed-document-node/core": "^3.2.0",
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.7",
"@types/node": "^16.11.7", "@types/node": "^16.18.70",
"@types/react": "^18.2.14", "@types/react": "^18.2.47",
"@types/react-dom": "^18.2.6", "@types/react-dom": "^18.2.18",
"@vitejs/plugin-react": "^3.0.0", "@vitejs/plugin-react": "^3.1.0",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.16",
"postcss": "^8.4.19", "postcss": "^8.4.33",
"tailwindcss": "^3.2.1", "tailwindcss": "^3.4.1",
"typescript": "^4.6.4", "typescript": "^4.9.5",
"vite": "^4.0.2" "vite": "^5.0.12"
} }
} }

View File

@@ -1,5 +1,11 @@
# @nhost-examples/docker-compose # @nhost-examples/docker-compose
## 0.0.7
### Patch Changes
- 8d91f71: chore: update deps and enable pnpm audit
## 0.0.6 ## 0.0.6
### Patch Changes ### Patch Changes

View File

@@ -1,11 +1,14 @@
{ {
"name": "@nhost-examples/docker-compose", "name": "@nhost-examples/docker-compose",
"version": "0.0.6", "version": "0.0.7",
"private": true, "private": true,
"scripts": { "scripts": {
"e2e": "vitest run" "e2e": "vitest run"
}, },
"devDependencies": { "devDependencies": {
"cross-fetch": "^3.1.5" "cross-fetch": "^3.1.8"
},
"dependencies": {
"graphql": "16.8.1"
} }
} }

View File

@@ -1,5 +1,13 @@
# @nhost-examples/multi-tenant-one-to-many # @nhost-examples/multi-tenant-one-to-many
## 2.0.1
### Patch Changes
- 8d91f71: chore: update deps and enable pnpm audit
- Updated dependencies [8d91f71]
- @nhost/nhost-js@3.0.2
## 2.0.0 ## 2.0.0
### Major Changes ### Major Changes

View File

@@ -1,7 +1,7 @@
{ {
"name": "@nhost-examples/multi-tenant-one-to-many", "name": "@nhost-examples/multi-tenant-one-to-many",
"private": true, "private": true,
"version": "2.0.0", "version": "2.0.1",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": {}, "scripts": {},
@@ -9,11 +9,11 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@types/express": "^4.17.13", "@types/express": "^4.17.21",
"express": "^4.18.1", "express": "^4.18.2",
"typescript": "^4.8.2" "typescript": "^4.9.5"
}, },
"dependencies": { "dependencies": {
"@nhost/nhost-js": "*" "@nhost/nhost-js": "^3.0.2"
} }
} }

View File

@@ -1,5 +1,15 @@
# @nhost-examples/nextjs # @nhost-examples/nextjs
## 0.1.13
### Patch Changes
- 8d91f71: chore: update deps and enable pnpm audit
- Updated dependencies [8d91f71]
- @nhost/react-apollo@7.0.2
- @nhost/nextjs@2.0.2
- @nhost/react@3.0.2
## 0.1.12 ## 0.1.12
### Patch Changes ### Patch Changes

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