Compare commits

..

17 Commits

Author SHA1 Message Date
Pilou
bebf9e1f2b Merge pull request #443 from nhost/changeset-release/main
chore: update versions
2022-04-22 17:35:44 +02:00
github-actions[bot]
2413c10283 chore: update versions 2022-04-22 15:32:40 +00:00
Pilou
0f7fbdab97 Merge pull request #440 from nhost/fix/token-refresher
fix: improve reliability of the token refresher
2022-04-22 17:31:47 +02:00
Pilou
14e5fd63a6 Merge branch 'main' into fix/token-refresher 2022-04-22 17:27:11 +02:00
Pilou
2446913836 Merge pull request #439 from nhost/fix/refresh-session
fix: fix and improve `nhost.auth.refreshSession`
2022-04-22 17:22:47 +02:00
Pierre-Louis Mercereau
1f88a9f47a style: improve readability 2022-04-22 17:20:05 +02:00
Pierre-Louis Mercereau
261e37cda4 fix: fix nullable value miss 2022-04-22 16:50:30 +02:00
Pierre-Louis Mercereau
5ee395ea8e fix: ensure the session is destroyed when signout is done 2022-04-22 16:44:52 +02:00
Pilou
828633ffc9 Merge pull request #435 from nhost/chore/include-examples-in-monorepo
Chore/include examples in monorepo
2022-04-22 13:28:02 +02:00
Pierre-Louis Mercereau
7b7527a5e6 fix: improve reliability of the token refresher 2022-04-22 11:32:25 +02:00
Pierre-Louis Mercereau
e0cfcafead fix: fix and improve nhost.auth.refreshSession 2022-04-21 16:57:55 +02:00
Pierre-Louis Mercereau
75a1428114 chore(examples): fix resolution error with nextjs 2022-04-21 13:42:23 +02:00
Pierre-Louis Mercereau
d82d830849 chore: update pnpm lock file 2022-04-21 13:23:13 +02:00
Pierre-Louis Mercereau
2def59fc6c chore: merge main 2022-04-21 13:21:36 +02:00
Pierre-Louis Mercereau
64ceb2c6bf docs: update examples readme 2022-04-21 12:46:05 +02:00
Pierre-Louis Mercereau
3ee007620c chore: use vite to build react-apollo-crm, and prettier examples 2022-04-21 12:40:19 +02:00
Pierre-Louis Mercereau
b9cf8172a0 chore(examples): include react-apollo and nextjs examples back in the monorepo workspace 2022-04-21 11:24:32 +02:00
126 changed files with 8259 additions and 21985 deletions

View File

@@ -2,14 +2,32 @@
This demo is a work in progress, further improvements are to come This demo is a work in progress, further improvements are to come
### Installation ## Get started
First, clone this repo. Then run the commands: 1. Clone the repository
```sh
git clone https://github.com/nhost/nhost
cd nhost
```
2. Install dependencies
```sh ```sh
cd examples/nextjs cd examples/nextjs
yarn pnpm install
yarn dev ```
3. Terminal 1: Start Nhost
```sh
nhost dev
```
4. Terminal 2: Start React App
```sh
pnpm run dev
``` ```
If you want to use this demo with your own cloud instance: If you want to use this demo with your own cloud instance:
@@ -18,8 +36,3 @@ If you want to use this demo with your own cloud instance:
- don't forget to change the client URL in the Nhost console so email verification will work: `Users -> Login Settings -> Client login URLs`: `http://localhost:4000` - don't forget to change the client URL in the Nhost console so email verification will work: `Users -> Login Settings -> Client login URLs`: `http://localhost:4000`
If you want to use a local Nhost instance, start the CLI in parallel to Nextjs: If you want to use a local Nhost instance, start the CLI in parallel to Nextjs:
```sh
# Inside examples/nextjs
nhost -d
```

View File

@@ -0,0 +1,13 @@
/** @type {import('next').NextConfig} */
const nextConfig = {}
const pkg = require('./package.json')
// * Only required to make it work with the monorepo. Is not required otherwise
const withTM = require('next-transpile-modules')(
// * All references to workspace packages are transpiled
Object.entries(pkg.dependencies)
.filter(([name, version]) => version.startsWith('workspace'))
.map(([name]) => name)
)
module.exports = withTM(nextConfig)

View File

@@ -11,4 +11,4 @@
max_connections: 50 max_connections: 50
retries: 20 retries: 20
use_prepared_statements: true use_prepared_statements: true
tables: "!include default/tables/tables.yaml" tables: '!include default/tables/tables.yaml'

View File

@@ -16,10 +16,10 @@ configuration:
update: updateAuthProviders update: updateAuthProviders
update_by_pk: updateAuthProvider update_by_pk: updateAuthProvider
array_relationships: array_relationships:
- name: userProviders - name: userProviders
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: provider_id column: provider_id
table: table:
name: user_providers name: user_providers
schema: auth schema: auth

View File

@@ -19,6 +19,6 @@ configuration:
update: updateAuthRefreshTokens update: updateAuthRefreshTokens
update_by_pk: updateAuthRefreshToken update_by_pk: updateAuthRefreshToken
object_relationships: object_relationships:
- name: user - name: user
using: using:
foreign_key_constraint_on: user_id foreign_key_constraint_on: user_id

View File

@@ -16,17 +16,17 @@ configuration:
update: updateAuthRoles update: updateAuthRoles
update_by_pk: updateAuthRole update_by_pk: updateAuthRole
array_relationships: array_relationships:
- name: userRoles - name: userRoles
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: role column: role
table: table:
name: user_roles name: user_roles
schema: auth schema: auth
- name: usersByDefaultRole - name: usersByDefaultRole
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: default_role column: default_role
table: table:
name: users name: users
schema: auth schema: auth

View File

@@ -23,9 +23,9 @@ configuration:
update: updateAuthUserProviders update: updateAuthUserProviders
update_by_pk: updateAuthUserProvider update_by_pk: updateAuthUserProvider
object_relationships: object_relationships:
- name: provider - name: provider
using: using:
foreign_key_constraint_on: provider_id foreign_key_constraint_on: provider_id
- name: user - name: user
using: using:
foreign_key_constraint_on: user_id foreign_key_constraint_on: user_id

View File

@@ -19,9 +19,9 @@ configuration:
update: updateAuthUserRoles update: updateAuthUserRoles
update_by_pk: updateAuthUserRole update_by_pk: updateAuthUserRole
object_relationships: object_relationships:
- name: roleByRole - name: roleByRole
using: using:
foreign_key_constraint_on: role foreign_key_constraint_on: role
- name: user - name: user
using: using:
foreign_key_constraint_on: user_id foreign_key_constraint_on: user_id

View File

@@ -38,28 +38,28 @@ configuration:
update: updateUsers update: updateUsers
update_by_pk: updateUser update_by_pk: updateUser
object_relationships: object_relationships:
- name: defaultRoleByRole - name: defaultRoleByRole
using: using:
foreign_key_constraint_on: default_role foreign_key_constraint_on: default_role
array_relationships: array_relationships:
- name: refreshTokens - name: refreshTokens
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: user_id column: user_id
table: table:
name: refresh_tokens name: refresh_tokens
schema: auth schema: auth
- name: roles - name: roles
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: user_id column: user_id
table: table:
name: user_roles name: user_roles
schema: auth schema: auth
- name: userProviders - name: userProviders
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: user_id column: user_id
table: table:
name: user_providers name: user_providers
schema: auth schema: auth

View File

@@ -2,9 +2,9 @@ table:
name: books name: books
schema: public schema: public
select_permissions: select_permissions:
- permission: - permission:
columns: columns:
- id - id
- title - title
filter: {} filter: {}
role: user role: user

View File

@@ -2,9 +2,9 @@ table:
name: test name: test
schema: public schema: public
select_permissions: select_permissions:
- permission: - permission:
columns: columns:
- bidon - bidon
- id - id
filter: {} filter: {}
role: user role: user

View File

@@ -23,10 +23,10 @@ configuration:
update: updateBuckets update: updateBuckets
update_by_pk: updateBucket update_by_pk: updateBucket
array_relationships: array_relationships:
- name: files - name: files
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: bucket_id column: bucket_id
table: table:
name: files name: files
schema: storage schema: storage

View File

@@ -25,6 +25,6 @@ configuration:
update: updateFiles update: updateFiles
update_by_pk: updateFile update_by_pk: updateFile
object_relationships: object_relationships:
- name: bucket - name: bucket
using: using:
foreign_key_constraint_on: bucket_id foreign_key_constraint_on: bucket_id

View File

@@ -1,10 +1,10 @@
- "!include auth_provider_requests.yaml" - '!include auth_provider_requests.yaml'
- "!include auth_providers.yaml" - '!include auth_providers.yaml'
- "!include auth_refresh_tokens.yaml" - '!include auth_refresh_tokens.yaml'
- "!include auth_roles.yaml" - '!include auth_roles.yaml'
- "!include auth_user_providers.yaml" - '!include auth_user_providers.yaml'
- "!include auth_user_roles.yaml" - '!include auth_user_roles.yaml'
- "!include auth_users.yaml" - '!include auth_users.yaml'
- "!include public_books.yaml" - '!include public_books.yaml'
- "!include storage_buckets.yaml" - '!include storage_buckets.yaml'
- "!include storage_files.yaml" - '!include storage_files.yaml'

View File

@@ -6,13 +6,23 @@
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint" "prettier": "prettier --check .",
"prettier:fix": "prettier --write .",
"lint": "eslint . --ext .ts,.tsx",
"lint:fix": "eslint . --ext .ts,.tsx --fix",
"verify": "run-p prettier lint",
"verify:fix": "run-p prettier:fix lint:fix"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.5.10", "@apollo/client": "^3.5.10",
"@nhost/nextjs": "^1.0.10", "@nhost/apollo": "workspace:*",
"@nhost/react": "^0.5.0", "@nhost/core": "workspace:*",
"@nhost/react-apollo": "^4.0.10", "@nhost/nextjs": "workspace:*",
"@nhost/react": "workspace:*",
"@nhost/react-apollo": "workspace:*",
"@nhost/nhost-js": "workspace:*",
"@nhost/hasura-auth-js": "workspace:*",
"@nhost/hasura-storage-js": "workspace:*",
"graphql": "^16.3.0", "graphql": "^16.3.0",
"next": "12.1.0", "next": "12.1.0",
"react": "17.0.2", "react": "17.0.2",
@@ -22,9 +32,10 @@
"@types/node": "17.0.23", "@types/node": "17.0.23",
"@types/react": "17.0.43", "@types/react": "17.0.43",
"@xstate/inspect": "^0.6.2", "@xstate/inspect": "^0.6.2",
"eslint": "8.8.0",
"eslint-config-next": "12.0.10", "eslint-config-next": "12.0.10",
"next-transpile-modules": "^9.0.0",
"typescript": "4.5.5", "typescript": "4.5.5",
"ws": "^8.5.0" "ws": "^8.5.0",
"xstate": "^4.30.5"
} }
} }

View File

@@ -19,12 +19,14 @@ if (typeof window !== 'undefined' && process.env.NEXT_PUBLIC_DEBUG) {
const nhost = new NhostClient({ backendUrl: BACKEND_URL }) const nhost = new NhostClient({ backendUrl: BACKEND_URL })
function MyApp({ Component, pageProps }: AppProps) { function MyApp({ Component, pageProps }: AppProps) {
// * Monorepo-related. See: https://stackoverflow.com/questions/71843247/react-nextjs-type-error-component-cannot-be-used-as-a-jsx-component
const AnyComponent = Component as any
return ( return (
<NhostNextProvider nhost={nhost} initial={pageProps.nhostSession}> <NhostNextProvider nhost={nhost} initial={pageProps.nhostSession}>
<NhostApolloProvider nhost={nhost}> <NhostApolloProvider nhost={nhost}>
<div className="App"> <div className="App">
<Header /> <Header />
<Component {...pageProps} /> <AnyComponent {...pageProps} />
</div> </div>
</NhostApolloProvider> </NhostApolloProvider>
</NhostNextProvider> </NhostNextProvider>

View File

@@ -1,11 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es5",
"lib": [ "lib": ["dom", "dom.iterable", "esnext"],
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": false, "strict": false,
@@ -19,11 +15,6 @@
"jsx": "preserve", "jsx": "preserve",
"incremental": true "incremental": true
}, },
"include": [ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"next-env.d.ts", "exclude": ["node_modules"]
"**/*.ts", }
"**/*.tsx"],
"exclude": [
"node_modules"
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
REACT_APP_BACKEND_URL=http://localhost:1337 VITE_NHOST_URL=http://localhost:1337

View File

@@ -72,25 +72,33 @@ This example app has some work in progress:
## Get started ## Get started
1. Install dependencies 1. Clone the repository
``` ```sh
npm install git clone https://github.com/nhost/nhost
cd nhost
``` ```
2. Terminal 1: Start Nhost 2. Install dependencies
```sh
cd examples/react-apollo-crm
pnpm install
``` ```
3. Terminal 1: Start Nhost
```sh
nhost dev nhost dev
``` ```
2. Terminal 2: Start React App 4. Terminal 2: Start React App
``` ```sh
npm run start pnpm run dev
``` ```
3. Terminal 3: Start GraphQL Codegens 5. Terminal 3: Start GraphQL Codegens
> Make sure that the Nhost backend in step 2 has started and is available before you run this command > Make sure that the Nhost backend in step 2 has started and is available before you run this command

View File

@@ -1,12 +1,12 @@
module.exports = { module.exports = {
client: { client: {
service: { service: {
name: "backend", name: 'backend',
url: "http://localhost:1337/v1/graphql", url: 'http://localhost:1337/v1/graphql',
headers: { headers: {
"x-hasura-admin-secret": "nhost-admin-secret", 'x-hasura-admin-secret': 'nhost-admin-secret'
}, }
}, },
includes: ["src/**/*.graphql", "src/**/*.gql"], includes: ['src/**/*.graphql', 'src/**/*.gql']
}, }
}; }

View File

@@ -3,13 +3,13 @@ schema: http://localhost:1337/v1/graphql
headers: headers:
x-hasura-admin-secret: nhost-admin-secret x-hasura-admin-secret: nhost-admin-secret
documents: documents:
- "src/**/*.graphql" - 'src/**/*.graphql'
- "src/**/*.gql" - 'src/**/*.gql'
generates: generates:
src/utils/__generated__/graphql.ts: src/utils/__generated__/graphql.ts:
plugins: plugins:
- "typescript" - 'typescript'
- "typescript-operations" - 'typescript-operations'
- "typescript-react-apollo" - 'typescript-react-apollo'
config: config:
withRefetchFn: true withRefetchFn: true

View File

@@ -1,7 +1,7 @@
import { NhostClient } from '@nhost/nhost-js' import { NhostClient } from '@nhost/nhost-js'
const nhost = new NhostClient({ const nhost = new NhostClient({
backendUrl: process.env.NHOST_BACKEND_URL! backendUrl: process.env.NHOST_BACKEND_URL!
}) })
export { nhost } export { nhost }

View File

@@ -2,104 +2,104 @@ import { Request, Response } from 'express'
import { nhost } from '../../_utils/nhost' import { nhost } from '../../_utils/nhost'
const handler = async (req: Request, res: Response) => { const handler = async (req: Request, res: Response) => {
if (req.headers['nhsot-webhook-secret'] !== process.env.NHSOT_WEBHOOK_SECRET) { if (req.headers['nhsot-webhook-secret'] !== process.env.NHSOT_WEBHOOK_SECRET) {
return res.status(401).send('Unauthorized') return res.status(401).send('Unauthorized')
} }
// User who just signed up // User who just signed up
const user = req.body.event.data.new const user = req.body.event.data.new
// Get the user's email domain // Get the user's email domain
const emailDomain = user.email.split('@')[1] const emailDomain = user.email.split('@')[1]
// Check if a company with the user's email domain already exists. // Check if a company with the user's email domain already exists.
const GET_COMPANY_WITH_EMAIL_DOMAIN = ` const GET_COMPANY_WITH_EMAIL_DOMAIN = `
query getCompanyWithEmailDomain($emailDomain: String!) { query getCompanyWithEmailDomain($emailDomain: String!) {
companies(where: { emailDomain: { _eq: $emailDomain } }) { companies(where: { emailDomain: { _eq: $emailDomain } }) {
id id
} }
} }
` `
const { data, error } = await nhost.graphql.request( const { data, error } = await nhost.graphql.request(
GET_COMPANY_WITH_EMAIL_DOMAIN, GET_COMPANY_WITH_EMAIL_DOMAIN,
{ {
emailDomain emailDomain
}, },
{ {
headers: { headers: {
'x-hasura-admin-secret': process.env.NHOST_ADMIN_SECRET 'x-hasura-admin-secret': process.env.NHOST_ADMIN_SECRET
}
} }
) }
)
if (error) { if (error) {
return res.status(500).send(error) return res.status(500).send(error)
} }
const { companies } = data as any const { companies } = data as any
let companyId let companyId
if (companies.length === 1) { if (companies.length === 1) {
// if a company already exists, use that company's id // if a company already exists, use that company's id
companyId = companies[0].id companyId = companies[0].id
} else { } else {
// else, create a new company for the newly created user with the same email domain as the user // else, create a new company for the newly created user with the same email domain as the user
const CREATE_NEW_COMPANY = ` const CREATE_NEW_COMPANY = `
mutation insertCompany($emailDomain: String!) { mutation insertCompany($emailDomain: String!) {
insertCompany(object: { name: $emailDomain, emailDomain: $emailDomain }) { insertCompany(object: { name: $emailDomain, emailDomain: $emailDomain }) {
id id
} }
} }
` `
const { data, error } = await nhost.graphql.request( const { data, error } = await nhost.graphql.request(
CREATE_NEW_COMPANY, CREATE_NEW_COMPANY,
{ {
emailDomain emailDomain
}, },
{ {
headers: { headers: {
'x-hasura-admin-secret': process.env.NHOST_ADMIN_SECRET 'x-hasura-admin-secret': process.env.NHOST_ADMIN_SECRET
} }
}
)
if (error) {
return res.status(500).send(error)
} }
)
const { insertCompany } = data as any if (error) {
return res.status(500).send(error)
}
companyId = insertCompany.id const { insertCompany } = data as any
}
// We now have the company id of an existing, or a newly created company. companyId = insertCompany.id
// Now let's add the user to the company. }
const ADD_USER_TO_COMPANY = ` // We now have the company id of an existing, or a newly created company.
// Now let's add the user to the company.
const ADD_USER_TO_COMPANY = `
mutation addUserToCompany($userId: uuid!, $companyId: uuid!) { mutation addUserToCompany($userId: uuid!, $companyId: uuid!) {
insertCompanyUser(object: {userId: $userId, companyId: $companyId}) { insertCompanyUser(object: {userId: $userId, companyId: $companyId}) {
id id
} }
} }
` `
const { error: addUserToCompanyError } = await nhost.graphql.request( const { error: addUserToCompanyError } = await nhost.graphql.request(
ADD_USER_TO_COMPANY, ADD_USER_TO_COMPANY,
{ {
userId: user.id, userId: user.id,
companyId companyId
}, },
{ {
headers: { headers: {
'x-hasura-admin-secret': process.env.NHOST_ADMIN_SECRET 'x-hasura-admin-secret': process.env.NHOST_ADMIN_SECRET
}
} }
) }
)
if (addUserToCompanyError) { if (addUserToCompanyError) {
return res.status(500).send(error) return res.status(500).send(error)
} }
res.status(200).send(`OK`) res.status(200).send(`OK`)
} }
export default handler export default handler

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<title>React App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

View File

@@ -7,9 +7,7 @@
<h2>Verify Email</h2> <h2>Verify Email</h2>
<p>Use this link to verify your email:</p> <p>Use this link to verify your email:</p>
<p> <p>
<a <a href="${serverUrl}/verify?&ticket=${ticket}&type=emailVerify&redirectTo=${redirectTo}">
href="${serverUrl}/verify?&ticket=${ticket}&type=emailVerify&redirectTo=${redirectTo}"
>
Verify Email Verify Email
</a> </a>
</p> </p>

View File

@@ -7,9 +7,7 @@
<h2>Reset Password</h2> <h2>Reset Password</h2>
<p>Use this link to reset your password:</p> <p>Use this link to reset your password:</p>
<p> <p>
<a <a href="${serverUrl}/verify?&ticket=${ticket}&type=passwordReset&redirectTo=${redirectTo}">
href="${serverUrl}/verify?&ticket=${ticket}&type=passwordReset&redirectTo=${redirectTo}"
>
Reset password Reset password
</a> </a>
</p> </p>

View File

@@ -1,7 +1,5 @@
type Mutation { type Mutation {
actionName( actionName(arg1: SampleInput!): SampleOutput
arg1: SampleInput!
): SampleOutput
} }
input SampleInput { input SampleInput {
@@ -12,4 +10,3 @@ input SampleInput {
type SampleOutput { type SampleOutput {
accessToken: String! accessToken: String!
} }

View File

@@ -1,14 +1,14 @@
actions: actions:
- name: actionName - name: actionName
definition: definition:
kind: synchronous kind: synchronous
handler: http://host.docker.internal:3000 handler: http://host.docker.internal:3000
permissions: permissions:
- role: user - role: user
custom_types: custom_types:
enums: [] enums: []
input_objects: input_objects:
- name: SampleInput - name: SampleInput
objects: objects:
- name: SampleOutput - name: SampleOutput
scalars: [] scalars: []

View File

@@ -11,4 +11,4 @@
max_connections: 50 max_connections: 50
retries: 20 retries: 20
use_prepared_statements: true use_prepared_statements: true
tables: "!include default/tables/tables.yaml" tables: '!include default/tables/tables.yaml'

View File

@@ -16,10 +16,10 @@ configuration:
update: updateAuthProviders update: updateAuthProviders
update_by_pk: updateAuthProvider update_by_pk: updateAuthProvider
array_relationships: array_relationships:
- name: userProviders - name: userProviders
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: provider_id column: provider_id
table: table:
name: user_providers name: user_providers
schema: auth schema: auth

View File

@@ -19,6 +19,6 @@ configuration:
update: updateAuthRefreshTokens update: updateAuthRefreshTokens
update_by_pk: updateAuthRefreshToken update_by_pk: updateAuthRefreshToken
object_relationships: object_relationships:
- name: user - name: user
using: using:
foreign_key_constraint_on: user_id foreign_key_constraint_on: user_id

View File

@@ -16,17 +16,17 @@ configuration:
update: updateAuthRoles update: updateAuthRoles
update_by_pk: updateAuthRole update_by_pk: updateAuthRole
array_relationships: array_relationships:
- name: userRoles - name: userRoles
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: role column: role
table: table:
name: user_roles name: user_roles
schema: auth schema: auth
- name: usersByDefaultRole - name: usersByDefaultRole
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: default_role column: default_role
table: table:
name: users name: users
schema: auth schema: auth

View File

@@ -23,9 +23,9 @@ configuration:
update: updateAuthUserProviders update: updateAuthUserProviders
update_by_pk: updateAuthUserProvider update_by_pk: updateAuthUserProvider
object_relationships: object_relationships:
- name: provider - name: provider
using: using:
foreign_key_constraint_on: provider_id foreign_key_constraint_on: provider_id
- name: user - name: user
using: using:
foreign_key_constraint_on: user_id foreign_key_constraint_on: user_id

View File

@@ -19,9 +19,9 @@ configuration:
update: updateAuthUserRoles update: updateAuthUserRoles
update_by_pk: updateAuthUserRole update_by_pk: updateAuthUserRole
object_relationships: object_relationships:
- name: roleByRole - name: roleByRole
using: using:
foreign_key_constraint_on: role foreign_key_constraint_on: role
- name: user - name: user
using: using:
foreign_key_constraint_on: user_id foreign_key_constraint_on: user_id

View File

@@ -38,79 +38,79 @@ configuration:
update: updateUsers update: updateUsers
update_by_pk: updateUser update_by_pk: updateUser
object_relationships: object_relationships:
- name: companyUser - name: companyUser
using: using:
manual_configuration: manual_configuration:
column_mapping: column_mapping:
id: user_id id: user_id
insertion_order: null insertion_order: null
remote_table: remote_table:
name: company_users name: company_users
schema: public schema: public
- name: defaultRoleByRole - name: defaultRoleByRole
using: using:
foreign_key_constraint_on: default_role foreign_key_constraint_on: default_role
array_relationships: array_relationships:
- name: customer_comments - name: customer_comments
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: user_id column: user_id
table: table:
name: customer_comments name: customer_comments
schema: public schema: public
- name: customers - name: customers
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: user_id column: user_id
table: table:
name: customers name: customers
schema: public schema: public
- name: refreshTokens - name: refreshTokens
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: user_id column: user_id
table: table:
name: refresh_tokens name: refresh_tokens
schema: auth schema: auth
- name: roles - name: roles
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: user_id column: user_id
table: table:
name: user_roles name: user_roles
schema: auth schema: auth
- name: userProviders - name: userProviders
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: user_id column: user_id
table: table:
name: user_providers name: user_providers
schema: auth schema: auth
select_permissions: select_permissions:
- permission: - permission:
columns: columns:
- avatar_url - avatar_url
- display_name - display_name
- email - email
- id - id
filter: filter:
companyUser: companyUser:
company: company:
companyUsers: companyUsers:
user_id: user_id:
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
role: user role: user
event_triggers: event_triggers:
- definition: - definition:
enable_manual: false enable_manual: false
insert: insert:
columns: "*" columns: '*'
headers: headers:
- name: nhost-webhook-secret - name: nhost-webhook-secret
value_from_env: NHOST_WEBHOOK_SECRET value_from_env: NHOST_WEBHOOK_SECRET
name: users-insert-create-company-connection name: users-insert-create-company-connection
retry_conf: retry_conf:
interval_sec: 10 interval_sec: 10
num_retries: 0 num_retries: 0
timeout_sec: 60 timeout_sec: 60
webhook: "{{NHOST_BACKEND_URL}}/v1/functions/users/insert/create-company-connection" webhook: '{{NHOST_BACKEND_URL}}/v1/functions/users/insert/create-company-connection'

View File

@@ -18,29 +18,29 @@ configuration:
update: updateCompanies update: updateCompanies
update_by_pk: updateCompany update_by_pk: updateCompany
array_relationships: array_relationships:
- name: companyUsers - name: companyUsers
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: company_id column: company_id
table: table:
name: company_users name: company_users
schema: public schema: public
- name: customers - name: customers
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: company_id column: company_id
table: table:
name: customers name: customers
schema: public schema: public
select_permissions: select_permissions:
- permission: - permission:
columns: columns:
- id - id
- created_at - created_at
- updated_at - updated_at
- name - name
filter: filter:
companyUsers: companyUsers:
user_id: user_id:
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
role: user role: user

View File

@@ -20,23 +20,23 @@ configuration:
update: updateCompanyUsers update: updateCompanyUsers
update_by_pk: updateCompanyUser update_by_pk: updateCompanyUser
object_relationships: object_relationships:
- name: company - name: company
using: using:
foreign_key_constraint_on: company_id foreign_key_constraint_on: company_id
- name: user - name: user
using: using:
foreign_key_constraint_on: user_id foreign_key_constraint_on: user_id
select_permissions: select_permissions:
- permission: - permission:
columns: columns:
- id - id
- created_at - created_at
- updated_at - updated_at
- company_id - company_id
- user_id - user_id
filter: filter:
company: company:
companyUsers: companyUsers:
user_id: user_id:
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
role: user role: user

View File

@@ -19,65 +19,65 @@ configuration:
update: updateCustomerComments update: updateCustomerComments
update_by_pk: updateCustomerComment update_by_pk: updateCustomerComment
object_relationships: object_relationships:
- name: customer - name: customer
using: using:
foreign_key_constraint_on: customer_id foreign_key_constraint_on: customer_id
- name: file - name: file
using: using:
foreign_key_constraint_on: file_id foreign_key_constraint_on: file_id
- name: user - name: user
using: using:
foreign_key_constraint_on: user_id foreign_key_constraint_on: user_id
insert_permissions: insert_permissions:
- permission: - permission:
backend_only: false backend_only: false
check: check:
customer: customer:
company: company:
companyUsers: companyUsers:
user_id: user_id:
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
columns: columns:
- customer_id - customer_id
- file_id - file_id
- text - text
set: set:
user_id: x-hasura-user-id user_id: x-hasura-user-id
role: user role: user
select_permissions: select_permissions:
- permission: - permission:
columns: columns:
- created_at - created_at
- customer_id - customer_id
- file_id - file_id
- id - id
- text - text
- updated_at - updated_at
- user_id - user_id
filter: filter:
customer: customer:
company: company:
companyUsers: companyUsers:
user_id: user_id:
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
role: user role: user
update_permissions: update_permissions:
- permission: - permission:
check: null check: null
columns: [] columns: []
filter: filter:
customer: customer:
company: company:
companyUsers: companyUsers:
user_id: user_id:
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
role: user role: user
delete_permissions: delete_permissions:
- permission: - permission:
filter: filter:
customer: customer:
company: company:
companyUsers: companyUsers:
user_id: user_id:
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
role: user role: user

View File

@@ -20,48 +20,48 @@ configuration:
update: updateCustomers update: updateCustomers
update_by_pk: updateCustomer update_by_pk: updateCustomer
object_relationships: object_relationships:
- name: company - name: company
using: using:
foreign_key_constraint_on: company_id foreign_key_constraint_on: company_id
- name: user - name: user
using: using:
foreign_key_constraint_on: user_id foreign_key_constraint_on: user_id
array_relationships: array_relationships:
- name: customerComments - name: customerComments
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: customer_id column: customer_id
table: table:
name: customer_comments name: customer_comments
schema: public schema: public
insert_permissions: insert_permissions:
- permission: - permission:
backend_only: false backend_only: false
check: check:
company: company:
companyUsers: companyUsers:
user_id: user_id:
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
columns: columns:
- address_line_1 - address_line_1
- company_id - company_id
- name - name
set: set:
user_id: x-hasura-user-id user_id: x-hasura-user-id
role: user role: user
select_permissions: select_permissions:
- permission: - permission:
columns: columns:
- id - id
- created_at - created_at
- updated_at - updated_at
- name - name
- address_line_1 - address_line_1
- company_id - company_id
- user_id - user_id
filter: filter:
company: company:
companyUsers: companyUsers:
user_id: user_id:
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
role: user role: user

View File

@@ -23,10 +23,10 @@ configuration:
update: updateBuckets update: updateBuckets
update_by_pk: updateBucket update_by_pk: updateBucket
array_relationships: array_relationships:
- name: files - name: files
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: bucket_id column: bucket_id
table: table:
name: files name: files
schema: storage schema: storage

View File

@@ -25,51 +25,51 @@ configuration:
update: updateFiles update: updateFiles
update_by_pk: updateFile update_by_pk: updateFile
object_relationships: object_relationships:
- name: bucket - name: bucket
using: using:
foreign_key_constraint_on: bucket_id foreign_key_constraint_on: bucket_id
array_relationships: array_relationships:
- name: customerCommentFiles - name: customerCommentFiles
using: using:
manual_configuration: manual_configuration:
column_mapping: column_mapping:
id: file_id id: file_id
insertion_order: null insertion_order: null
remote_table: remote_table:
name: customer_comments name: customer_comments
schema: public schema: public
insert_permissions: insert_permissions:
- permission: - permission:
backend_only: false backend_only: false
check: check:
_or: _or:
- bucket_id: - bucket_id:
_eq: customerComments _eq: customerComments
columns: columns:
- bucket_id - bucket_id
- id - id
- mime_type - mime_type
- name - name
role: user role: user
select_permissions: select_permissions:
- permission: - permission:
columns: columns:
- id - id
- created_at - created_at
- updated_at - updated_at
- bucket_id - bucket_id
- name - name
- size - size
- mime_type - mime_type
- etag - etag
- is_uploaded - is_uploaded
- uploaded_by_user_id - uploaded_by_user_id
filter: filter:
_or: _or:
- customerCommentFiles: - customerCommentFiles:
customer: customer:
company: company:
companyUsers: companyUsers:
user_id: user_id:
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
role: user role: user

View File

@@ -1,13 +1,13 @@
- "!include auth_provider_requests.yaml" - '!include auth_provider_requests.yaml'
- "!include auth_providers.yaml" - '!include auth_providers.yaml'
- "!include auth_refresh_tokens.yaml" - '!include auth_refresh_tokens.yaml'
- "!include auth_roles.yaml" - '!include auth_roles.yaml'
- "!include auth_user_providers.yaml" - '!include auth_user_providers.yaml'
- "!include auth_user_roles.yaml" - '!include auth_user_roles.yaml'
- "!include auth_users.yaml" - '!include auth_users.yaml'
- "!include public_companies.yaml" - '!include public_companies.yaml'
- "!include public_company_users.yaml" - '!include public_company_users.yaml'
- "!include public_customer_comments.yaml" - '!include public_customer_comments.yaml'
- "!include public_customers.yaml" - '!include public_customers.yaml'
- "!include storage_buckets.yaml" - '!include storage_buckets.yaml'
- "!include storage_files.yaml" - '!include storage_files.yaml'

View File

@@ -3,38 +3,34 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@apollo/client": "^3.4.16", "@apollo/client": "^3.5.10",
"@headlessui/react": "^1.4.2", "@headlessui/react": "^1.5.0",
"@heroicons/react": "^1.0.5", "@heroicons/react": "^1.0.6",
"@nhost/nhost-js": "^1.0.0", "@nhost/nhost-js": "workspace:*",
"@nhost/react": "^0.3.0", "@nhost/react": "workspace:*",
"@nhost/react-apollo": "^4.0.0", "@nhost/react-apollo": "workspace:*",
"@tailwindcss/forms": "^0.3.4", "@tailwindcss/forms": "^0.5.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"@types/jest": "^26.0.15",
"@types/node": "^12.0.0",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"date-fns": "^2.28.0", "date-fns": "^2.28.0",
"graphql": "^15.8.0", "graphql": "15.7.2",
"graphql-tag": "^2.12.6", "graphql-tag": "^2.12.6",
"pretty-bytes": "^5.6.0", "pretty-bytes": "^5.6.0",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-router-dom": "^6.0.2", "react-router-dom": "^6.3.0",
"react-scripts": "^5.0.0", "tailwindcss": "^3.0.24"
"typescript": "^4.1.2",
"web-vitals": "^1.0.1"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "dev": "vite",
"build": "react-scripts build", "build": "vite build",
"test": "react-scripts test", "preview": "vite preview",
"eject": "react-scripts eject", "codegen": "graphql-codegen --config codegen.yaml --errors-only",
"codegen": "graphql-codegen --config codegen.yaml --errors-only" "prettier": "prettier --check .",
"prettier:fix": "prettier --write .",
"lint": "eslint . --ext .ts,.tsx",
"lint:fix": "eslint . --ext .ts,.tsx --fix",
"verify": "run-p prettier lint",
"verify:fix": "run-p prettier:fix lint:fix"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [
@@ -55,15 +51,21 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "^2.2.1", "@graphql-codegen/cli": "^2.6.2",
"@graphql-codegen/introspection": "^2.1.0", "@graphql-codegen/introspection": "^2.1.1",
"@graphql-codegen/typescript": "^2.2.4", "@graphql-codegen/typescript": "^2.4.8",
"@graphql-codegen/typescript-operations": "^2.1.8", "@graphql-codegen/typescript-operations": "^2.3.5",
"@graphql-codegen/typescript-react-apollo": "^3.1.6", "@graphql-codegen/typescript-react-apollo": "^3.2.11",
"@types/jest": "^26.0.24",
"@types/node": "^12.20.48",
"@types/react": "^17.0.44",
"@types/react-dom": "^17.0.15",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"autoprefixer": "^9.8.8", "@vitejs/plugin-react": "^1.3.1",
"express": "^4.17.1", "autoprefixer": "^10.4.4",
"postcss": "^7.0.39", "express": "^4.17.3",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17" "postcss": "^8.4.12",
"typescript": "^4.6.3",
"vite": "^2.9.5"
} }
} }

View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
}

View File

@@ -1,43 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@@ -1,6 +0,0 @@
import React from 'react'
import App from './App'
test('single noop test', () => {
expect(true).toBeTruthy()
})

View File

@@ -2,7 +2,7 @@ import './App.css'
import { NhostReactProvider } from '@nhost/react' import { NhostReactProvider } from '@nhost/react'
import { NhostApolloProvider } from '@nhost/react-apollo' import { NhostApolloProvider } from '@nhost/react-apollo'
import { nhost } from './utils/nhost' import { nhost } from './utils/nhost'
import { Route, Routes } from 'react-router' import { Route, Routes } from 'react-router-dom'
import { Layout } from './components/ui/Layout' import { Layout } from './components/ui/Layout'
import { Customers } from './components/Customers' import { Customers } from './components/Customers'
import { Dashboard } from './components/Dashboard' import { Dashboard } from './components/Dashboard'

View File

@@ -3,91 +3,85 @@ import { Dialog, Transition } from '@headlessui/react'
import { nhost } from '../utils/nhost' import { nhost } from '../utils/nhost'
export function ChangePasswordModal() { export function ChangePasswordModal() {
const [open, setOpen] = useState(true) const [open, setOpen] = useState(true)
const [newPassword, setNewPassword] = useState('') const [newPassword, setNewPassword] = useState('')
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => { const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
e.preventDefault() e.preventDefault()
const { error } = await nhost.auth.changePassword({ newPassword }) const { error } = await nhost.auth.changePassword({ newPassword })
if (error) { if (error) {
return alert(error.message) return alert(error.message)
} }
setOpen(false) setOpen(false)
} }
return ( return (
<Transition.Root show={open} as={Fragment}> <Transition.Root show={open} as={Fragment}>
<Dialog as="div" className="fixed z-10 inset-0 overflow-y-auto" onClose={setOpen}> <Dialog as="div" className="fixed inset-0 z-10 overflow-y-auto" onClose={setOpen}>
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> <div className="flex items-end justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"
enterFrom="opacity-0" enterFrom="opacity-0"
enterTo="opacity-100" enterTo="opacity-100"
leave="ease-in duration-200" leave="ease-in duration-200"
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" /> <Dialog.Overlay className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" />
</Transition.Child> </Transition.Child>
{/* This element is to trick the browser into centering the modal contents. */} {/* This element is to trick the browser into centering the modal contents. */}
<span <span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
className="hidden sm:inline-block sm:align-middle sm:h-screen" &#8203;
aria-hidden="true" </span>
> <Transition.Child
&#8203; as={Fragment}
</span> enter="ease-out duration-300"
<Transition.Child enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
as={Fragment} enterTo="opacity-100 translate-y-0 sm:scale-100"
enter="ease-out duration-300" leave="ease-in duration-200"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" leaveFrom="opacity-100 translate-y-0 sm:scale-100"
enterTo="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
leave="ease-in duration-200" >
leaveFrom="opacity-100 translate-y-0 sm:scale-100" <div className="inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transition-all transform bg-white rounded-lg shadow-xl sm:my-8 sm:align-middle sm:max-w-sm sm:w-full sm:p-6">
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" <form onSubmit={handleSubmit}>
> <div>
<div className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-sm sm:w-full sm:p-6"> <div className="mt-3 text-center sm:mt-5">
<form onSubmit={handleSubmit}> <Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-900">
<div> Change Password
<div className="mt-3 text-center sm:mt-5"> </Dialog.Title>
<Dialog.Title <div className="mt-2">
as="h3" <input
className="text-lg leading-6 font-medium text-gray-900" id="password"
> name="password"
Change Password type="password"
</Dialog.Title> autoComplete="current-password"
<div className="mt-2"> required
<input className="block w-full px-3 py-2 placeholder-gray-400 border border-gray-300 rounded-md shadow-sm appearance-none focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
id="password" tabIndex={2}
name="password" value={newPassword}
type="password" onChange={(e) => setNewPassword(e.target.value)}
autoComplete="current-password" />
required </div>
className="block w-full px-3 py-2 placeholder-gray-400 border border-gray-300 rounded-md shadow-sm appearance-none focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
tabIndex={2}
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
/>
</div>
</div>
</div>
<div className="mt-5 sm:mt-6">
<button
type="submit"
className="inline-flex justify-center w-full rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:text-sm"
>
Set new password
</button>
</div>
</form>
</div> </div>
</Transition.Child> </div>
<div className="mt-5 sm:mt-6">
<button
type="submit"
className="inline-flex justify-center w-full px-4 py-2 text-base font-medium text-white bg-blue-600 border border-transparent rounded-md shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:text-sm"
>
Set new password
</button>
</div>
</form>
</div> </div>
</Dialog> </Transition.Child>
</Transition.Root> </div>
) </Dialog>
</Transition.Root>
)
} }

View File

@@ -1,47 +1,47 @@
import { Main } from "./ui/Main"; import { Main } from './ui/Main'
import { Breadcrumbs } from "./ui/Breadcrumbs"; import { Breadcrumbs } from './ui/Breadcrumbs'
import { HeaderSection } from "./ui/HeaderSection"; import { HeaderSection } from './ui/HeaderSection'
import { PageHeader } from "./ui/PageHeader"; import { PageHeader } from './ui/PageHeader'
import { useParams } from "react-router"; import { useParams } from 'react-router-dom'
import { useCustomerQuery } from "../utils/__generated__/graphql"; import { useCustomerQuery } from '../utils/__generated__/graphql'
import { NavLink, Outlet } from "react-router-dom"; import { NavLink, Outlet } from 'react-router-dom'
import classNames from "classnames"; import classNames from 'classnames'
import { CustomerActivities } from "./CustomerActivities"; import { CustomerActivities } from './CustomerActivities'
import { CustomerAddComment } from "./CustomerAddComment"; import { CustomerAddComment } from './CustomerAddComment'
const tabs = [ const tabs = [
{ name: "Overview", href: "" }, { name: 'Overview', href: '' },
{ name: "Orders", href: "orders" }, { name: 'Orders', href: 'orders' },
{ name: "Files", href: "files" }, { name: 'Files', href: 'files' }
]; ]
export function Customer() { export function Customer() {
const { customerId } = useParams(); const { customerId } = useParams()
const { data, loading } = useCustomerQuery({ const { data, loading } = useCustomerQuery({
variables: { variables: {
customerId, customerId
}, }
}); })
if (loading) { if (loading) {
return <div>Loading..</div>; return <div>Loading..</div>
} }
if (!data || !data.customer) { if (!data || !data.customer) {
return <div>No customer..</div>; return <div>No customer..</div>
} }
const { customer } = data; const { customer } = data
return ( return (
<Main> <Main>
<Breadcrumbs <Breadcrumbs
backLink={""} backLink={''}
breadcrumbs={[ breadcrumbs={[
{ link: "/customers", text: "Customers" }, { link: '/customers', text: 'Customers' },
{ link: `customers/${customerId}`, text: customer.name }, { link: `customers/${customerId}`, text: customer.name }
]} ]}
/> />
<HeaderSection> <HeaderSection>
@@ -57,7 +57,7 @@ export function Customer() {
id="current-tab" id="current-tab"
name="current-tab" name="current-tab"
className="block w-full py-2 pl-3 pr-10 text-base border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" className="block w-full py-2 pl-3 pr-10 text-base border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
defaultValue={"ok"} defaultValue={'ok'}
> >
<option>1</option> <option>1</option>
<option>2</option> <option>2</option>
@@ -71,10 +71,10 @@ export function Customer() {
className={({ isActive }) => { className={({ isActive }) => {
return classNames( return classNames(
isActive isActive
? "border-blue-500 text-blue-600" ? 'border-blue-500 text-blue-600'
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300", : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300',
"whitespace-nowrap pb-4 px-1 border-b-2 font-medium text-sm" 'whitespace-nowrap pb-4 px-1 border-b-2 font-medium text-sm'
); )
}} }}
> >
{tab.name} {tab.name}
@@ -95,5 +95,5 @@ export function Customer() {
<CustomerAddComment /> <CustomerAddComment />
</div> </div>
</Main> </Main>
); )
} }

View File

@@ -1,35 +1,35 @@
import { ChatAltIcon } from "@heroicons/react/solid"; import { ChatAltIcon } from '@heroicons/react/solid'
import { useGetCustomerCommentsSubscription } from "../utils/__generated__/graphql"; import { useGetCustomerCommentsSubscription } from '../utils/__generated__/graphql'
import { useParams } from "react-router"; import { useParams } from 'react-router-dom'
import { nhost } from "../utils/nhost"; import { nhost } from '../utils/nhost'
import { PhotographIcon } from "@heroicons/react/outline"; import { PhotographIcon } from '@heroicons/react/outline'
import prettyBytes from "pretty-bytes"; import prettyBytes from 'pretty-bytes'
import { formatDistanceToNow, parseISO } from "date-fns"; import { formatDistanceToNow, parseISO } from 'date-fns'
export function CustomerActivities() { export function CustomerActivities() {
const { customerId } = useParams(); const { customerId } = useParams<{ customerId: string }>()
const { data, loading } = useGetCustomerCommentsSubscription({ const { data, loading } = useGetCustomerCommentsSubscription({
variables: { variables: {
where: { where: {
customerId: { customerId: {
_eq: customerId, _eq: customerId
}, }
}, }
}, }
}); })
console.log({ data }); console.log({ data })
if (loading) { if (loading) {
return <div>Loading...</div>; return <div>Loading...</div>
} }
if (!data || !data.customerComments) { if (!data || !data.customerComments) {
return <div>no comments</div>; return <div>no comments</div>
} }
const { customerComments } = data; const { customerComments } = data
return ( return (
<div className="flow-root"> <div className="flow-root">
@@ -54,10 +54,7 @@ export function CustomerActivities() {
/> />
<span className="absolute -bottom-0.5 -right-1 bg-white rounded-tl px-0.5 py-px"> <span className="absolute -bottom-0.5 -right-1 bg-white rounded-tl px-0.5 py-px">
<ChatAltIcon <ChatAltIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
className="w-5 h-5 text-gray-400"
aria-hidden="true"
/>
</span> </span>
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
@@ -69,7 +66,7 @@ export function CustomerActivities() {
</div> </div>
<p className="mt-0.5 text-sm text-gray-500"> <p className="mt-0.5 text-sm text-gray-500">
{formatDistanceToNow(parseISO(comment.createdAt), { {formatDistanceToNow(parseISO(comment.createdAt), {
addSuffix: true, addSuffix: true
})} })}
</p> </p>
</div> </div>
@@ -80,24 +77,22 @@ export function CustomerActivities() {
<div <div
className="flex items-center mt-3 text-sm text-gray-700 cursor-pointer" className="flex items-center mt-3 text-sm text-gray-700 cursor-pointer"
onClick={async () => { onClick={async () => {
const { presignedUrl, error } = const { presignedUrl, error } = await nhost.storage.getPresignedUrl({
await nhost.storage.getPresignedUrl({ fileId: comment.file!.id
fileId: comment.file!.id, })
});
if (error) { if (error) {
return alert(error.message); return alert(error.message)
} }
window.open(presignedUrl?.url, "_blank"); window.open(presignedUrl?.url, '_blank')
}} }}
> >
<div> <div>
<PhotographIcon className="w-5 mr-1 text-gray-500" /> <PhotographIcon className="w-5 mr-1 text-gray-500" />
</div> </div>
<div> <div>
{comment.file.name},{" "} {comment.file.name}, {prettyBytes(comment.file.size as number)}
{prettyBytes(comment.file.size as number)}
</div> </div>
</div> </div>
)} )}
@@ -106,9 +101,9 @@ export function CustomerActivities() {
</div> </div>
</div> </div>
</li> </li>
); )
})} })}
</ul> </ul>
</div> </div>
); )
} }

View File

@@ -1,32 +1,31 @@
import { useState } from "react"; import { useState } from 'react'
import { useParams } from "react-router"; import { useParams } from 'react-router-dom'
import { nhost } from "../utils/nhost"; import { nhost } from '../utils/nhost'
import { useInsertCustomerCommentMutation } from "../utils/__generated__/graphql"; import { useInsertCustomerCommentMutation } from '../utils/__generated__/graphql'
export function CustomerAddComment() { export function CustomerAddComment() {
const [text, setText] = useState(""); const [text, setText] = useState('')
const [file, setFile] = useState<null | File>(null); const [file, setFile] = useState<null | File>(null)
const { customerId } = useParams(); const { customerId } = useParams<{ customerId: string }>()
const [insertCustomerComment, { loading }] = const [insertCustomerComment, { loading }] = useInsertCustomerCommentMutation()
useInsertCustomerCommentMutation();
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => { const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault()
let fileMetadata; let fileMetadata
if (file) { if (file) {
const fileUploadRes = await nhost.storage.upload({ const fileUploadRes = await nhost.storage.upload({
file, file,
bucketId: "customerComments", bucketId: 'customerComments'
}); })
if (fileUploadRes.error) { if (fileUploadRes.error) {
alert(`error: ${fileUploadRes.error}`); alert(`error: ${fileUploadRes.error}`)
return; return
} }
fileMetadata = fileUploadRes.fileMetadata; fileMetadata = fileUploadRes.fileMetadata
} }
await insertCustomerComment({ await insertCustomerComment({
@@ -34,21 +33,18 @@ export function CustomerAddComment() {
customerComment: { customerComment: {
text, text,
customerId, customerId,
fileId: fileMetadata ? fileMetadata.id : null, fileId: fileMetadata ? fileMetadata.id : null
}, }
}, }
}); })
setText(""); setText('')
}; }
return ( return (
<div className="max-w-lg mx-auto"> <div className="max-w-lg mx-auto">
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<label <label htmlFor="email" className="block text-sm font-medium text-gray-700">
htmlFor="email"
className="block text-sm font-medium text-gray-700"
>
Comment Comment
</label> </label>
<div className="mt-1"> <div className="mt-1">
@@ -57,7 +53,7 @@ export function CustomerAddComment() {
name="about" name="about"
rows={3} rows={3}
className="block w-full max-w-lg border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm" className="block w-full max-w-lg border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
defaultValue={""} defaultValue={''}
value={text} value={text}
onChange={(e) => setText(e.target.value)} onChange={(e) => setText(e.target.value)}
/> />
@@ -94,7 +90,7 @@ export function CustomerAddComment() {
className="sr-only" className="sr-only"
onChange={(e) => { onChange={(e) => {
if (e.target.files && e.target.files.length > 0) { if (e.target.files && e.target.files.length > 0) {
setFile(e.target.files[0]); setFile(e.target.files[0])
} }
}} }}
/> />
@@ -104,9 +100,7 @@ export function CustomerAddComment() {
{file ? ( {file ? (
<div>{file.name}</div> <div>{file.name}</div>
) : ( ) : (
<p className="text-xs text-gray-500"> <p className="text-xs text-gray-500">PNG, JPG, GIF up to 10MB</p>
PNG, JPG, GIF up to 10MB
</p>
)} )}
</div> </div>
</div> </div>
@@ -124,5 +118,5 @@ export function CustomerAddComment() {
</div> </div>
</form> </form>
</div> </div>
); )
} }

View File

@@ -1,18 +1,15 @@
import { Main } from "./ui/Main"; import { Main } from './ui/Main'
import { Breadcrumbs } from "./ui/Breadcrumbs"; import { Breadcrumbs } from './ui/Breadcrumbs'
import { HeaderSection } from "./ui/HeaderSection"; import { HeaderSection } from './ui/HeaderSection'
import { PageHeader } from "./ui/PageHeader"; import { PageHeader } from './ui/PageHeader'
import { Link } from "react-router-dom"; import { Link } from 'react-router-dom'
import { useGetCustomersSubscription } from "../utils/__generated__/graphql"; import { useGetCustomersSubscription } from '../utils/__generated__/graphql'
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/solid"; import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid'
export function Customers() { export function Customers() {
return ( return (
<Main> <Main>
<Breadcrumbs <Breadcrumbs backLink={''} breadcrumbs={[{ link: '/customers', text: 'Customers' }]} />
backLink={""}
breadcrumbs={[{ link: "/customers", text: "Customers" }]}
/>
<HeaderSection> <HeaderSection>
<PageHeader>Customers</PageHeader> <PageHeader>Customers</PageHeader>
<div className="flex flex-shrink-0 mt-4 md:mt-0 md:ml-4"> <div className="flex flex-shrink-0 mt-4 md:mt-0 md:ml-4">
@@ -31,11 +28,11 @@ export function Customers() {
<CustomersList /> <CustomersList />
</div> </div>
</Main> </Main>
); )
} }
function CustomersList() { function CustomersList() {
const { data } = useGetCustomersSubscription(); const { data } = useGetCustomersSubscription()
return ( return (
<div> <div>
@@ -57,17 +54,14 @@ function CustomersList() {
<tbody> <tbody>
{data?.customers.map((customer, i) => { {data?.customers.map((customer, i) => {
return ( return (
<tr <tr key={customer.id} className={i % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
key={customer.id}
className={i % 2 === 0 ? "bg-white" : "bg-gray-50"}
>
<td className="text-sm font-medium text-gray-900 whitespace-nowrap"> <td className="text-sm font-medium text-gray-900 whitespace-nowrap">
<Link to={customer.id} className="block px-6 py-4"> <Link to={customer.id} className="block px-6 py-4">
{customer.name} {customer.name}
</Link> </Link>
</td> </td>
</tr> </tr>
); )
})} })}
</tbody> </tbody>
</table> </table>
@@ -88,9 +82,9 @@ function CustomersList() {
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between"> <div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div> <div>
<p className="text-sm text-gray-700"> <p className="text-sm text-gray-700">
Showing <span className="font-medium">1</span> to{" "} Showing <span className="font-medium">1</span> to{' '}
<span className="font-medium">10</span> of{" "} <span className="font-medium">10</span> of <span className="font-medium">97</span>{' '}
<span className="font-medium">97</span> results results
</p> </p>
</div> </div>
<div> <div>
@@ -136,5 +130,5 @@ function CustomersList() {
</div> </div>
</div> </div>
</div> </div>
); )
} }

View File

@@ -1,5 +1,5 @@
import { Main } from "./ui/Main"; import { Main } from './ui/Main'
export function Dashboard() { export function Dashboard() {
return <Main>Dashboard</Main>; return <Main>Dashboard</Main>
} }

View File

@@ -1,67 +1,64 @@
import { useState } from "react"; import { useState } from 'react'
import { Main } from "./ui/Main"; import { Main } from './ui/Main'
import { Breadcrumbs } from "./ui/Breadcrumbs"; import { Breadcrumbs } from './ui/Breadcrumbs'
import { HeaderSection } from "./ui/HeaderSection"; import { HeaderSection } from './ui/HeaderSection'
import { PageHeader } from "./ui/PageHeader"; import { PageHeader } from './ui/PageHeader'
import { import { useGetCompanyWhereQuery, useInsertCustomerMutation } from '../utils/__generated__/graphql'
useGetCompanyWhereQuery, import { nhost } from '../utils/nhost'
useInsertCustomerMutation, import { useNavigate } from 'react-router-dom'
} from "../utils/__generated__/graphql";
import { nhost } from "../utils/nhost";
import { useNavigate } from "react-router";
export function NewCustomer() { export function NewCustomer() {
const [name, setName] = useState(""); const [name, setName] = useState('')
const [email, setEmail] = useState(""); const [email, setEmail] = useState('')
const [addressLine1, setAddressLine1] = useState(""); const [addressLine1, setAddressLine1] = useState('')
const user = nhost.auth.getUser(); const user = nhost.auth.getUser()
let navigate = useNavigate(); let navigate = useNavigate()
const { data } = useGetCompanyWhereQuery({ const { data } = useGetCompanyWhereQuery({
variables: { variables: {
where: { where: {
companyUsers: { companyUsers: {
userId: { userId: {
_eq: user?.id, _eq: user?.id
}, }
}, }
}, }
}, }
}); })
const [insertCustomer, { loading }] = useInsertCustomerMutation(); const [insertCustomer, { loading }] = useInsertCustomerMutation()
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => { const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault()
console.log("handle submit"); console.log('handle submit')
let res; let res
try { try {
res = await insertCustomer({ res = await insertCustomer({
variables: { variables: {
customer: { customer: {
name, name,
addressLine1, addressLine1,
companyId: data?.companies[0].id, companyId: data?.companies[0].id
}, }
}, }
}); })
} catch (error) { } catch (error) {
return alert(`error: ${error}`); return alert(`error: ${error}`)
} }
navigate(`/customers/${res.data?.insertCustomer?.id}`); navigate(`/customers/${res.data?.insertCustomer?.id}`)
}; }
return ( return (
<Main> <Main>
<Breadcrumbs <Breadcrumbs
backLink={""} backLink={''}
breadcrumbs={[ breadcrumbs={[
{ link: "/customers", text: "Customers" }, { link: '/customers', text: 'Customers' },
{ link: "/new-customer", text: "New Customer" }, { link: '/new-customer', text: 'New Customer' }
]} ]}
/> />
<HeaderSection> <HeaderSection>
@@ -73,10 +70,7 @@ export function NewCustomer() {
<div className="pt-12"> <div className="pt-12">
<div className="grid grid-cols-1 mt-6 gap-y-6 gap-x-4 sm:grid-cols-6"> <div className="grid grid-cols-1 mt-6 gap-y-6 gap-x-4 sm:grid-cols-6">
<div className="sm:col-span-3"> <div className="sm:col-span-3">
<label <label htmlFor="first-name" className="block text-sm font-medium text-gray-700">
htmlFor="first-name"
className="block text-sm font-medium text-gray-700"
>
Name Name
</label> </label>
<div className="mt-1"> <div className="mt-1">
@@ -93,10 +87,7 @@ export function NewCustomer() {
</div> </div>
<div className="sm:col-span-3"> <div className="sm:col-span-3">
<label <label htmlFor="email" className="block text-sm font-medium text-gray-700">
htmlFor="email"
className="block text-sm font-medium text-gray-700"
>
Email address Email address
</label> </label>
<div className="mt-1"> <div className="mt-1">
@@ -113,10 +104,7 @@ export function NewCustomer() {
</div> </div>
<div className="sm:col-span-6"> <div className="sm:col-span-6">
<label <label htmlFor="street-address" className="block text-sm font-medium text-gray-700">
htmlFor="street-address"
className="block text-sm font-medium text-gray-700"
>
Street address Street address
</label> </label>
<div className="mt-1"> <div className="mt-1">
@@ -147,5 +135,5 @@ export function NewCustomer() {
</div> </div>
</form> </form>
</Main> </Main>
); )
} }

View File

@@ -1,6 +1,6 @@
import { useNhostAuth } from '@nhost/react' import { useNhostAuth } from '@nhost/react'
import React from 'react' import React from 'react'
import { Navigate, useLocation } from 'react-router' import { Navigate, useLocation } from 'react-router-dom'
export function RequireAuth({ children }: { children: JSX.Element }) { export function RequireAuth({ children }: { children: JSX.Element }) {
const { isAuthenticated, isLoading } = useNhostAuth() const { isAuthenticated, isLoading } = useNhostAuth()

View File

@@ -1,6 +1,6 @@
import { useNhostAuth } from '@nhost/react' import { useNhostAuth } from '@nhost/react'
import { useState } from 'react' import { useState } from 'react'
import { useNavigate } from 'react-router' import { useNavigate } from 'react-router-dom'
import { nhost } from '../utils/nhost' import { nhost } from '../utils/nhost'
export function ResetPassword() { export function ResetPassword() {

View File

@@ -1,6 +1,6 @@
import { useNhostAuth } from '@nhost/react' import { useNhostAuth } from '@nhost/react'
import { useState } from 'react' import { useState } from 'react'
import { useNavigate } from 'react-router' import { useNavigate } from 'react-router-dom'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { nhost } from '../utils/nhost' import { nhost } from '../utils/nhost'

View File

@@ -1,6 +1,6 @@
import { useNhostAuth } from '@nhost/react' import { useNhostAuth } from '@nhost/react'
import { useState } from 'react' import { useState } from 'react'
import { useNavigate } from 'react-router' import { useNavigate } from 'react-router-dom'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { nhost } from '../utils/nhost' import { nhost } from '../utils/nhost'
@@ -93,7 +93,7 @@ export function SignUp() {
</form> </form>
</div> </div>
<div className="text-center py-4"> <div className="py-4 text-center">
Already have an account?{' '} Already have an account?{' '}
<Link to="/sign-in" className="text-blue-600 hover:text-blue-500"> <Link to="/sign-in" className="text-blue-600 hover:text-blue-500">
Sign In Sign In

View File

@@ -1,19 +1,19 @@
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/solid"; import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid'
import classNames from "classnames"; import classNames from 'classnames'
import { Link } from "react-router-dom"; import { Link } from 'react-router-dom'
type BreadcrumbsProps = { type BreadcrumbsProps = {
backLink: string; backLink: string
breadcrumbs: Breadcrumb[]; breadcrumbs: Breadcrumb[]
}; }
type Breadcrumb = { type Breadcrumb = {
link: string; link: string
text: string; text: string
}; }
export function Breadcrumbs(props: BreadcrumbsProps) { export function Breadcrumbs(props: BreadcrumbsProps) {
const { backLink, breadcrumbs } = props; const { backLink, breadcrumbs } = props
return ( return (
<div> <div>
@@ -32,13 +32,10 @@ export function Breadcrumbs(props: BreadcrumbsProps) {
<nav className="hidden sm:flex" aria-label="Breadcrumb"> <nav className="hidden sm:flex" aria-label="Breadcrumb">
<ol className="flex items-center space-x-4"> <ol className="flex items-center space-x-4">
{breadcrumbs.map((breadcrumb, i) => { {breadcrumbs.map((breadcrumb, i) => {
const isFirstItem = i === 0; const isFirstItem = i === 0
const classes = classNames( const classes = classNames('text-sm font-medium text-gray-500 hover:text-gray-700', {
"text-sm font-medium text-gray-500 hover:text-gray-700", 'ml-4': !isFirstItem
{ })
"ml-4": !isFirstItem,
}
);
return ( return (
<li key={i}> <li key={i}>
@@ -54,10 +51,10 @@ export function Breadcrumbs(props: BreadcrumbsProps) {
</Link> </Link>
</div> </div>
</li> </li>
); )
})} })}
</ol> </ol>
</nav> </nav>
</div> </div>
); )
} }

View File

@@ -1,9 +1,5 @@
import React from "react"; import React from 'react'
export function HeaderSection({ children }: { children: React.ReactNode }) { export function HeaderSection({ children }: { children: React.ReactNode }) {
return ( return <div className="mt-2 md:flex md:items-center md:justify-between">{children}</div>
<div className="mt-2 md:flex md:items-center md:justify-between">
{children}
</div>
);
} }

View File

@@ -1,7 +1,5 @@
import React from "react"; import React from 'react'
export function Main({ children }: { children: React.ReactNode }) { export function Main({ children }: { children: React.ReactNode }) {
return ( return <div className="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">{children}</div>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">{children}</div>
);
} }

View File

@@ -1,4 +1,4 @@
import React from "react"; import React from 'react'
export function PageHeader({ children }: { children: React.ReactNode }) { export function PageHeader({ children }: { children: React.ReactNode }) {
return ( return (
@@ -7,5 +7,5 @@ export function PageHeader({ children }: { children: React.ReactNode }) {
{children} {children}
</h2> </h2>
</div> </div>
); )
} }

10
examples/react-apollo-crm/src/env.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_NHOST_URL: string
// more env variables...
}
interface ImportMeta {
readonly env: ImportMetaEnv
}

View File

@@ -1,6 +1,4 @@
mutation insertCustomerComment( mutation insertCustomerComment($customerComment: customerComments_insert_input!) {
$customerComment: customerComments_insert_input!
) {
insertCustomerComment(object: $customerComment) { insertCustomerComment(object: $customerComment) {
id id
} }

View File

@@ -1,15 +1,13 @@
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
code { code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
monospace;
} }
@tailwind base; @tailwind base;

View File

@@ -1,9 +1,8 @@
import React from "react"; import React from 'react'
import ReactDOM from "react-dom"; import ReactDOM from 'react-dom'
import "./index.css"; import './index.css'
import App from "./App"; import App from './App'
import reportWebVitals from "./reportWebVitals"; import { BrowserRouter } from 'react-router-dom'
import { BrowserRouter } from "react-router-dom";
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>
@@ -11,10 +10,5 @@ ReactDOM.render(
<App /> <App />
</BrowserRouter> </BrowserRouter>
</React.StrictMode>, </React.StrictMode>,
document.getElementById("root") document.getElementById('root')
); )
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View File

@@ -1 +0,0 @@
/// <reference types="react-scripts" />

View File

@@ -1,15 +0,0 @@
import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View File

@@ -2,4 +2,4 @@
// allows you to do things like: // allows you to do things like:
// expect(element).toHaveTextContent(/react/i) // expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom // learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom'; import '@testing-library/jest-dom'

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
import { NhostClient } from '@nhost/react' import { NhostClient } from '@nhost/react'
const nhost = new NhostClient({ const nhost = new NhostClient({
backendUrl: process.env.REACT_APP_BACKEND_URL! backendUrl: import.meta.env.VITE_NHOST_URL || 'http://localhost:1337'
}) })
export { nhost } export { nhost }

View File

@@ -1,11 +1,11 @@
module.exports = { module.exports = {
purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"], content: ['./src/**/*.{js,jsx,ts,tsx}', './index.html'],
darkMode: false, // or 'media' or 'class' darkMode: 'media',
theme: { theme: {
extend: {}, extend: {}
}, },
variants: { variants: {
extend: {}, extend: {}
}, },
plugins: [require("@tailwindcss/forms")], plugins: [require('@tailwindcss/forms')]
}; }

View File

@@ -1,20 +1,9 @@
{ {
"extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"target": "es6", "outDir": "dist",
"lib": ["dom", "dom.iterable", "esnext"], "composite": true,
"allowJs": true, "module": "esnext"
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
}, },
"include": ["src"] "include": ["src/**/*", "types/**/*", "../../types/**/*", "tests/**/*"]
} }

View File

@@ -0,0 +1,9 @@
import { defineConfig } from 'vite'
import tsconfigPaths from 'vite-tsconfig-paths'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [tsconfigPaths(), react()]
})

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,29 @@
# React-Apollo example # React-Apollo example
Once in the example's directory, run the two following commands in parallel: ## Get started
1. Clone the repository
```sh ```sh
# Start the Nhost CLI in the background git clone https://github.com/nhost/nhost
nhost -d cd nhost
```
# Start this project
yarn run dev 2. Install dependencies
```sh
cd examples/react-apollo
pnpm install
```
3. Terminal 1: Start Nhost
```sh
nhost dev
```
4. Terminal 2: Start React App
```sh
pnpm run dev
``` ```

View File

@@ -1,18 +1,14 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<meta charset="utf-8" />
</head>
<head> <body>
<meta charset="utf-8" /> <h2>Confirm Email Change</h2>
</head> <p>Use this link to confirm changing email:</p>
<p>
<body> <a href="${link}"> Change email </a>
<h2>Confirm Email Change</h2> </p>
<p>Use this link to confirm changing email:</p> </body>
<p> </html>
<a href="${link}">
Change email
</a>
</p>
</body>
</html>

View File

@@ -1,18 +1,14 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<meta charset="utf-8" />
</head>
<head> <body>
<meta charset="utf-8" /> <h2>Verify Email</h2>
</head> <p>Use this link to verify your email:</p>
<p>
<body> <a href="${link}"> Verify Email </a>
<h2>Verify Email</h2> </p>
<p>Use this link to verify your email:</p> </body>
<p> </html>
<a href="${link}">
Verify Email
</a>
</p>
</body>
</html>

View File

@@ -1,18 +1,14 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<meta charset="utf-8" />
</head>
<head> <body>
<meta charset="utf-8" /> <h2>Reset Password</h2>
</head> <p>Use this link to reset your password:</p>
<p>
<body> <a href="${link}"> Reset password </a>
<h2>Reset Password</h2> </p>
<p>Use this link to reset your password:</p> </body>
<p> </html>
<a href="${link}">
Reset password
</a>
</p>
</body>
</html>

View File

@@ -1,18 +1,14 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<meta charset="utf-8" />
</head>
<head> <body>
<meta charset="utf-8" /> <h2>Magic Link</h2>
</head> <p>Use this link to securely sign in:</p>
<p>
<body> <a href="${link}"> Sign In </a>
<h2>Magic Link</h2> </p>
<p>Use this link to securely sign in:</p> </body>
<p> </html>
<a href="${link}">
Sign In
</a>
</p>
</body>
</html>

View File

@@ -1,18 +1,14 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<meta charset="utf-8" />
</head>
<head> <body>
<meta charset="utf-8" /> <h2>Confirmer changement de courriel</h2>
</head> <p>Utilisez ce lien pour confirmer le changement de courriel:</p>
<p>
<body> <a href="${link}"> Changer courriel </a>
<h2>Confirmer changement de courriel</h2> </p>
<p>Utilisez ce lien pour confirmer le changement de courriel:</p> </body>
<p> </html>
<a href="${link}">
Changer courriel
</a>
</p>
</body>
</html>

View File

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

View File

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

View File

@@ -1,18 +1,14 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<meta charset="utf-8" />
</head>
<head> <body>
<meta charset="utf-8" /> <h2>Lien magique</h2>
</head> <p>Utilisez ce lien pour vous connecter de fa&ccedil;on s&eacute;curitaire:</p>
<p>
<body> <a href="${link}"> Connexion </a>
<h2>Lien magique</h2> </p>
<p>Utilisez ce lien pour vous connecter de fa&ccedil;on s&eacute;curitaire:</p> </body>
<p> </html>
<a href="${link}">
Connexion
</a>
</p>
</body>
</html>

View File

@@ -11,4 +11,4 @@
max_connections: 50 max_connections: 50
retries: 20 retries: 20
use_prepared_statements: true use_prepared_statements: true
tables: "!include default/tables/tables.yaml" tables: '!include default/tables/tables.yaml'

View File

@@ -15,10 +15,10 @@ configuration:
update: updateAuthProviders update: updateAuthProviders
update_by_pk: updateAuthProvider update_by_pk: updateAuthProvider
array_relationships: array_relationships:
- name: userProviders - name: userProviders
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: provider_id column: provider_id
table: table:
name: user_providers name: user_providers
schema: auth schema: auth

View File

@@ -19,6 +19,6 @@ configuration:
update: updateAuthRefreshTokens update: updateAuthRefreshTokens
update_by_pk: updateAuthRefreshToken update_by_pk: updateAuthRefreshToken
object_relationships: object_relationships:
- name: user - name: user
using: using:
foreign_key_constraint_on: user_id foreign_key_constraint_on: user_id

View File

@@ -15,17 +15,17 @@ configuration:
update: updateAuthRoles update: updateAuthRoles
update_by_pk: updateAuthRole update_by_pk: updateAuthRole
array_relationships: array_relationships:
- name: userRoles - name: userRoles
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: role column: role
table: table:
name: user_roles name: user_roles
schema: auth schema: auth
- name: usersByDefaultRole - name: usersByDefaultRole
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: default_role column: default_role
table: table:
name: users name: users
schema: auth schema: auth

View File

@@ -22,9 +22,9 @@ configuration:
update: updateAuthUserProviders update: updateAuthUserProviders
update_by_pk: updateAuthUserProvider update_by_pk: updateAuthUserProvider
object_relationships: object_relationships:
- name: provider - name: provider
using: using:
foreign_key_constraint_on: provider_id foreign_key_constraint_on: provider_id
- name: user - name: user
using: using:
foreign_key_constraint_on: user_id foreign_key_constraint_on: user_id

View File

@@ -17,9 +17,9 @@ configuration:
update: updateAuthUserRoles update: updateAuthUserRoles
update_by_pk: updateAuthUserRole update_by_pk: updateAuthUserRole
object_relationships: object_relationships:
- name: roleByRole - name: roleByRole
using: using:
foreign_key_constraint_on: role foreign_key_constraint_on: role
- name: user - name: user
using: using:
foreign_key_constraint_on: user_id foreign_key_constraint_on: user_id

View File

@@ -33,28 +33,28 @@ configuration:
update: updateUsers update: updateUsers
update_by_pk: updateUser update_by_pk: updateUser
object_relationships: object_relationships:
- name: defaultRoleByRole - name: defaultRoleByRole
using: using:
foreign_key_constraint_on: default_role foreign_key_constraint_on: default_role
array_relationships: array_relationships:
- name: refreshTokens - name: refreshTokens
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: user_id column: user_id
table: table:
name: refresh_tokens name: refresh_tokens
schema: auth schema: auth
- name: roles - name: roles
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: user_id column: user_id
table: table:
name: user_roles name: user_roles
schema: auth schema: auth
- name: userProviders - name: userProviders
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: user_id column: user_id
table: table:
name: user_providers name: user_providers
schema: auth schema: auth

View File

@@ -2,9 +2,9 @@ table:
name: books name: books
schema: public schema: public
select_permissions: select_permissions:
- permission: - permission:
columns: columns:
- id - id
- title - title
filter: {} filter: {}
role: user role: user

View File

@@ -23,10 +23,10 @@ configuration:
update: updateBuckets update: updateBuckets
update_by_pk: updateBucket update_by_pk: updateBucket
array_relationships: array_relationships:
- name: files - name: files
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
column: bucket_id column: bucket_id
table: table:
name: files name: files
schema: storage schema: storage

View File

@@ -25,6 +25,6 @@ configuration:
update: updateFiles update: updateFiles
update_by_pk: updateFile update_by_pk: updateFile
object_relationships: object_relationships:
- name: bucket - name: bucket
using: using:
foreign_key_constraint_on: bucket_id foreign_key_constraint_on: bucket_id

View File

@@ -1,10 +1,10 @@
- "!include auth_provider_requests.yaml" - '!include auth_provider_requests.yaml'
- "!include auth_providers.yaml" - '!include auth_providers.yaml'
- "!include auth_refresh_tokens.yaml" - '!include auth_refresh_tokens.yaml'
- "!include auth_roles.yaml" - '!include auth_roles.yaml'
- "!include auth_user_providers.yaml" - '!include auth_user_providers.yaml'
- "!include auth_user_roles.yaml" - '!include auth_user_roles.yaml'
- "!include auth_users.yaml" - '!include auth_users.yaml'
- "!include public_books.yaml" - '!include public_books.yaml'
- "!include storage_buckets.yaml" - '!include storage_buckets.yaml'
- "!include storage_files.yaml" - '!include storage_files.yaml'

View File

@@ -4,9 +4,11 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@apollo/client": "^3.5.10", "@apollo/client": "^3.5.10",
"@nhost/react": "^0.5.3", "@nhost/core": "workspace:*",
"@nhost/react-apollo": "^4.0.13", "@nhost/react": "workspace:*",
"@nhost/react-apollo": "workspace:*",
"@rsuite/icons": "^1.0.2", "@rsuite/icons": "^1.0.2",
"graphql": "15.7.2",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"less": "^4.1.2", "less": "^4.1.2",
"react": "^17.0.2", "react": "^17.0.2",
@@ -17,10 +19,17 @@
"react-router-dom": "^6.3.0", "react-router-dom": "^6.3.0",
"rsuite": "^5.8.1" "rsuite": "^5.8.1"
}, },
"lib": "workspace:*",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"preview": "vite preview" "preview": "vite preview",
"prettier": "prettier --check .",
"prettier:fix": "prettier --write .",
"lint": "eslint . --ext .ts,.tsx",
"lint:fix": "eslint . --ext .ts,.tsx --fix",
"verify": "run-p prettier lint",
"verify:fix": "run-p prettier:fix lint:fix"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [

View File

@@ -1,29 +1,9 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"target": "es5", "outDir": "dist",
"lib": [ "composite": true,
"dom", "module": "esnext"
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"baseUrl": "./src",
}, },
"include": [ "include": ["src/**/*", "types/**/*", "../../types/**/*", "tests/**/*"]
"src" }
],
}

View File

@@ -1,7 +1,9 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import tsconfigPaths from 'vite-tsconfig-paths'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()] plugins: [tsconfigPaths(), react()]
}) })

File diff suppressed because it is too large Load Diff

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