Compare commits

..

9 Commits

Author SHA1 Message Date
github-actions[bot]
cd6f37f2a6 chore: update versions (#304)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-03-29 16:11:46 +02:00
Pilou
39df4d5b9c Deprecate useAuthLoading and introduce useAuthenticationStatus (#303)
* fix(react): keep authentication and loading status in sync

fix #302

* style: typo
2022-03-29 13:21:50 +02:00
Pilou
e91215bbac Docs/nextjs (#299)
* fix: correct access to user/session information through getUser/getSession/isReady

* chore: use carret instead of star

* docs: explain all react hooks are available from @nhost/nextjs

* docs: correct imports in nextjs example

* chore: remove orphan changeset

* docs: next.js instead of NextJs
2022-03-28 14:16:41 +02:00
github-actions[bot]
ccaa4c4bba chore: update versions (#300)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-03-28 10:11:31 +02:00
Pilou
ab36f90cec fix: correct access to user/session information through getUser/getSession/isReady (#298)
* fix: correct access to user/session information through getUser/getSession/isReady

* chore: use carret instead of star
2022-03-28 10:09:38 +02:00
Johan Eliasson
cfbe2db430 fix: make it clear the @nhost/react-auth package is depricated (#297)
* fix: make it clear this package is depricated

* Update README.md

Co-authored-by: Pilou <24897252+plmercereau@users.noreply.github.com>
2022-03-28 09:57:55 +02:00
Pilou
6838ac6201 docs: fix deadlinks in README (#256) 2022-03-25 15:38:19 +00:00
Johan Eliasson
0caf43037d fix: updated react apollo crm package versions (#296)
* update

* update
2022-03-25 16:33:30 +01:00
Pilou
4ed626d5b5 chore: bump fixed versions in examples (#257)
* chore: bump fixed versions in examples

* chore: bump to latest sdk version

* chore: bump to latest version

* chore: bump example version
2022-03-25 14:49:35 +00:00
51 changed files with 12807 additions and 48582 deletions

View File

@@ -99,8 +99,8 @@ Nhost libraries and tools
- [JavaScript/TypeScript SDK](https://docs.nhost.io/reference/sdk) - [JavaScript/TypeScript SDK](https://docs.nhost.io/reference/sdk)
- [Dart and Flutter SDK](https://github.com/nhost/nhost-dart) - [Dart and Flutter SDK](https://github.com/nhost/nhost-dart)
- [Nhost CLI](https://docs.nhost.io/reference/cli) - [Nhost CLI](https://docs.nhost.io/reference/cli)
- [Nhost React Auth](https://docs.nhost.io/reference/supporting-libraries/react-auth) - [Nhost React](https://docs.nhost.io/reference/react)
- [Nhost React Apollo](https://docs.nhost.io/reference/supporting-libraries/react-apollo) - [Nhost Next.js](https://docs.nhost.io/reference/nextjs)
## Community ❤️ ## Community ❤️

View File

@@ -58,7 +58,7 @@ The logic is the same as in a classic React application:
import { NextPageContext } from 'next' import { NextPageContext } from 'next'
import React from 'react' import React from 'react'
import { useAccessToken, useAuthenticated, useUserData } from '@nhost/react' import { useAccessToken, useAuthenticated, useUserData } from '@nhost/nextjs'
const ClientSidePage: React.FC = () => { const ClientSidePage: React.FC = () => {
const isAuthenticated = useAuthenticated() const isAuthenticated = useAuthenticated()
@@ -93,7 +93,7 @@ import {
useAccessToken, useAccessToken,
useAuthenticated, useAuthenticated,
useUserData useUserData
} from '@nhost/react' } from '@nhost/nextjs'
export async function getServerSideProps(context: NextPageContext) { export async function getServerSideProps(context: NextPageContext) {
const nhostSession = await getNhostSession('my-app.nhost.run', context) const nhostSession = await getNhostSession('my-app.nhost.run', context)

View File

@@ -2,7 +2,7 @@
title: 'Introduction' title: 'Introduction'
--- ---
It is possible to use [`@nhost/react`](/reference/react) in any Next.js page that would be configured to render on the client-side. All the React hooks and helpers from [`@nhost/react`](/reference/react) are available in Next.js and are exported in the `@nhost/nextjs` package.
When rendering a page from the server-side, Next.js needs to get some information from the client to determine their authentication status. Such communication is only available from cookies, and the Nhost client is designed to enable such a mechanism. When rendering a page from the server-side, Next.js needs to get some information from the client to determine their authentication status. Such communication is only available from cookies, and the Nhost client is designed to enable such a mechanism.

View File

@@ -6,13 +6,12 @@ Create a `auth-protected.js` file:
```jsx ```jsx
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useAuthLoading, useAuthenticated } from '@nhost/nextjs' import { useAuthenticationStatus } from '@nhost/nextjs'
export function authProtected(Comp) { export function authProtected(Comp) {
return function AuthProtected(props) { return function AuthProtected(props) {
const router = useRouter() const router = useRouter()
const isLoading = useAuthLoading() const { isLoading, isAuthenticated } = useAuthenticationStatus()
const isAuthenticated = useAuthenticated()
if (isLoading) { if (isLoading) {
return <div>Loading...</div> return <div>Loading...</div>

View File

@@ -218,20 +218,19 @@ const Component = () => {
## Authentication status ## Authentication status
### `useAuthLoading` ### `useAuthenticationStatus`
The Nhost client may need some initial steps to determine the authentication status during startup, like fetching a new JWT from an existing refresh token. The Nhost client may need some initial steps to determine the authentication status during startup, like fetching a new JWT from an existing refresh token.
`useAuthLoading` will return `true` until the authentication status is known. `isLoading` will return `true` until the authentication status is known.
#### Usage #### Usage
```jsx ```jsx
import { useAuthLoading, useAuthenticated } from '@nhost/react' import { useAuthenticationStatus } from '@nhost/react'
const Component = () => { const Component = () => {
const isLoading = useAuthLoading() const { isLoading, isAuthenticated } = useAuthenticationStatus()
const isAuthenticated = useAuthenticated()
if (isLoading) return <div>Loading Nhost authentication status...</div> if (isLoading) return <div>Loading Nhost authentication status...</div>
else if (isAuthenticated) return <div>User is authenticated</div> else if (isAuthenticated) return <div>User is authenticated</div>
else return <div>Public section</div> else return <div>Public section</div>

View File

@@ -8,11 +8,10 @@ You can protect routes by creating an `AuthGate` component when using `@nhost/re
```jsx ```jsx
import { Redirect } from 'react-router-dom' import { Redirect } from 'react-router-dom'
import { useAuthLoading, useAuthenticated } from '@nhost/react' import { useAuthenticationStatus } from '@nhost/react'
export function AuthGate(children) { export function AuthGate(children) {
const isLoading = useAuthLoading() const { isLoading, isAuthenticated } = useAuthenticationStatus()
const isAuthenticated = useAuthenticated()
if (isLoading) { if (isLoading) {
return <div>Loading...</div> return <div>Loading...</div>

View File

@@ -4,12 +4,6 @@
### Patch Changes ### Patch Changes
- Updated dependencies [207ae38]
- Updated dependencies [207ae38]
- Updated dependencies [207ae38]
- Updated dependencies [207ae38]
- Updated dependencies [207ae38]
- Updated dependencies [207ae38]
- Updated dependencies [207ae38] - Updated dependencies [207ae38]
- @nhost/react-apollo@3.0.0 - @nhost/react-apollo@3.0.0
- @nhost/apollo@0.2.0 - @nhost/apollo@0.2.0

View File

@@ -7,7 +7,8 @@ export default function Header() {
<nav> <nav>
<Link href="/">Index</Link> <br /> <Link href="/">Index</Link> <br />
<Link href="/second">Second</Link> <br /> <Link href="/second">Second</Link> <br />
<Link href="/third">Third</Link> <br /> <Link href="/third">SSR auth-guarded page</Link> <br />
<Link href="/client-side-auth-guard">CSR auth-guarded page</Link> <br />
</nav> </nav>
</header> </header>
) )

View File

@@ -0,0 +1,21 @@
import { useRouter } from 'next/router'
import { useAuthenticationStatus } from '@nhost/nextjs'
export function authProtected(Comp) {
return function AuthProtected(props) {
const router = useRouter()
const { isLoading, isAuthenticated } = useAuthenticationStatus()
console.log('Authentication guard: check auth status', { isLoading, isAuthenticated })
if (isLoading) {
return <div>Loading...</div>
}
if (!isAuthenticated) {
router.push('/')
return null
}
return <Comp {...props} />
}
}

View File

@@ -4,7 +4,6 @@ table:
configuration: configuration:
custom_column_names: custom_column_names:
id: id id: id
redirect_url: redirectUrl
custom_name: authProviderRequests custom_name: authProviderRequests
custom_root_fields: custom_root_fields:
delete: deleteAuthProviderRequests delete: deleteAuthProviderRequests

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost-examples/nextjs", "name": "@nhost-examples/nextjs",
"version": "0.0.2", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
@@ -9,17 +9,18 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@nhost/nextjs": "^0.2.0", "@apollo/client": "^3.5.10",
"@nhost/react": "^0.2.0", "@nhost/nextjs": "^1.0.0",
"@nhost/react-apollo": "^3.0.0", "@nhost/react": "^0.3.0",
"@nhost/react-apollo": "^4.0.0",
"graphql": "^16.3.0", "graphql": "^16.3.0",
"next": "12.1.0", "next": "12.1.0",
"react": "17.0.2", "react": "17.0.2",
"react-dom": "17.0.2" "react-dom": "17.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "17.0.17", "@types/node": "17.0.23",
"@types/react": "17.0.39", "@types/react": "17.0.43",
"@xstate/inspect": "^0.6.2", "@xstate/inspect": "^0.6.2",
"eslint": "8.8.0", "eslint": "8.8.0",
"eslint-config-next": "12.0.10", "eslint-config-next": "12.0.10",

View File

@@ -0,0 +1,17 @@
import React from 'react'
import { useAccessToken } from '@nhost/nextjs'
import { authProtected } from '../components/protected-route'
const ClientSideAuthPage: React.FC = () => {
const accessToken = useAccessToken()
return (
<div>
<h1>Client-side rendered page only accessible to authenticated users</h1>
<div>Access token: {accessToken}</div>
</div>
)
}
export default authProtected(ClientSideAuthPage)

View File

@@ -6,11 +6,11 @@ import {
useAuthenticated, useAuthenticated,
useChangeEmail, useChangeEmail,
useChangePassword, useChangePassword,
useSignInEmailPasswordless,
useSignInEmailPassword, useSignInEmailPassword,
useSignUpEmailPassword, useSignInEmailPasswordless,
useSignOut useSignOut,
} from '@nhost/react' useSignUpEmailPassword
} from '@nhost/nextjs'
import { useAuthQuery } from '@nhost/react-apollo' import { useAuthQuery } from '@nhost/react-apollo'
import { BOOKS_QUERY } from '../helpers' import { BOOKS_QUERY } from '../helpers'

View File

@@ -1,8 +1,8 @@
import { NextPageContext } from 'next' import { NextPageContext } from 'next'
import React from 'react' import React from 'react'
import { getNhostSession, NhostSession } from '@nhost/nextjs' import { NhostSession } from '@nhost/core'
import { useAccessToken, useAuthenticated, useUserData } from '@nhost/react' import { getNhostSession, useAccessToken, useAuthenticated, useUserData } from '@nhost/nextjs'
import { BACKEND_URL } from '../helpers' import { BACKEND_URL } from '../helpers'

View File

@@ -1,9 +1,10 @@
import { NextPageContext } from 'next' import { NextPageContext } from 'next'
import React from 'react' import React from 'react'
import { getNhostSession, NhostSession } from '@nhost/nextjs' import { NhostSession } from '@nhost/core'
import { useAccessToken, useAuthenticated } from '@nhost/react' import { getNhostSession, useAccessToken } from '@nhost/nextjs'
import { authProtected } from '../components/protected-route'
import { BACKEND_URL } from '../helpers' import { BACKEND_URL } from '../helpers'
export async function getServerSideProps(context: NextPageContext) { export async function getServerSideProps(context: NextPageContext) {
@@ -17,15 +18,12 @@ export async function getServerSideProps(context: NextPageContext) {
const RefetchPage: React.FC<{ initial: NhostSession }> = () => { const RefetchPage: React.FC<{ initial: NhostSession }> = () => {
const accessToken = useAccessToken() const accessToken = useAccessToken()
const isAuthenticated = useAuthenticated()
if (!isAuthenticated) return <div>User it not authenticated </div>
return ( return (
<div> <div>
<h1>Third page</h1> <h1>SSR page only accessible to authenticated users</h1>
User is authenticated: {isAuthenticated ? 'yes' : 'no'}
<div>Access token: {accessToken}</div> <div>Access token: {accessToken}</div>
</div> </div>
) )
} }
export default RefetchPage export default authProtected(RefetchPage)

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
module.exports = {
style: {
postcss: {
plugins: [require("tailwindcss"), require("autoprefixer")],
},
},
};

View File

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

View File

@@ -1,107 +1,105 @@
import { Request, Response } from "express"; import { Request, Response } from 'express'
import { nhost } from "../../../src/utils/nhost"; import { nhost } from '../../_utils/nhost'
const handler = async (req: Request, res: Response) => { const handler = async (req: Request, res: Response) => {
if ( if (req.headers['nhsot-webhook-secret'] !== process.env.NHSOT_WEBHOOK_SECRET) {
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: {
"x-hasura-admin-secret": process.env.NHOST_ADMIN_SECRET,
}, },
} {
); headers: {
'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)
} }
);
if (error) { const { insertCompany } = data as any
return res.status(500).send(error);
}
const { insertCompany } = data as any; companyId = insertCompany.id
}
companyId = insertCompany.id; // We now have the company id of an existing, or a newly created company.
} // Now let's add the user to the company.
// We now have the company id of an existing, or a newly created company. const ADD_USER_TO_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: {
"x-hasura-admin-secret": process.env.NHOST_ADMIN_SECRET,
}, },
} {
); headers: {
'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

File diff suppressed because it is too large Load Diff

View File

@@ -4,13 +4,11 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@apollo/client": "^3.4.16", "@apollo/client": "^3.4.16",
"@craco/craco": "^6.4.0",
"@headlessui/react": "^1.4.2", "@headlessui/react": "^1.4.2",
"@heroicons/react": "^1.0.5", "@heroicons/react": "^1.0.5",
"@nhost/nhost-js": "^0.3.4", "@nhost/nhost-js": "^1.0.0",
"@nhost/react-apollo": "^2.0.7-0", "@nhost/react": "^0.3.0",
"@nhost/react-auth": "^2.0.3", "@nhost/react-apollo": "^4.0.0",
"@saeris/apollo-server-vercel": "^1.0.1",
"@tailwindcss/forms": "^0.3.4", "@tailwindcss/forms": "^0.3.4",
"@testing-library/jest-dom": "^5.11.4", "@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0", "@testing-library/react": "^11.1.0",
@@ -27,14 +25,14 @@
"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.0.2",
"react-scripts": "^4.0.3", "react-scripts": "^5.0.0",
"typescript": "^4.1.2", "typescript": "^4.1.2",
"web-vitals": "^1.0.1" "web-vitals": "^1.0.1"
}, },
"scripts": { "scripts": {
"start": "craco start", "start": "react-scripts start",
"build": "craco build", "build": "react-scripts build",
"test": "craco test", "test": "react-scripts test",
"eject": "react-scripts eject", "eject": "react-scripts eject",
"codegen": "graphql-codegen --config codegen.yaml --errors-only" "codegen": "graphql-codegen --config codegen.yaml --errors-only"
}, },
@@ -68,4 +66,4 @@
"postcss": "^7.0.39", "postcss": "^7.0.39",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17" "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17"
} }
} }

View File

@@ -1,26 +1,26 @@
import "./App.css"; import './App.css'
import { NhostAuthProvider } from "@nhost/react-auth"; 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'
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'
import { NewCustomer } from "./components/NewCustomer"; import { NewCustomer } from './components/NewCustomer'
import { RequireAuth } from "./components/RequireAuth"; import { RequireAuth } from './components/RequireAuth'
import { Customer } from "./components/Customer"; import { Customer } from './components/Customer'
import { SignUp } from "./components/SignUp"; import { SignUp } from './components/SignUp'
import { SignIn } from "./components/SignIn"; import { SignIn } from './components/SignIn'
import { ResetPassword } from "./components/ResetPassword"; import { ResetPassword } from './components/ResetPassword'
function App() { function App() {
return ( return (
<NhostAuthProvider nhost={nhost}> <NhostReactProvider nhost={nhost}>
<NhostApolloProvider nhost={nhost}> <NhostApolloProvider nhost={nhost}>
<AppRouter /> <AppRouter />
</NhostApolloProvider> </NhostApolloProvider>
</NhostAuthProvider> </NhostReactProvider>
); )
} }
function AppRouter() { function AppRouter() {
@@ -55,7 +55,7 @@ function AppRouter() {
</Route> </Route>
</Route> </Route>
</Routes> </Routes>
); )
} }
export default App; export default App

View File

@@ -1,98 +1,93 @@
import { Fragment, useState } from "react"; import { Fragment, useState } from 'react'
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from '@headlessui/react'
import { CheckIcon } from "@heroicons/react/outline"; 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 <Dialog as="div" className="fixed z-10 inset-0 overflow-y-auto" onClose={setOpen}>
as="div" <div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
className="fixed z-10 inset-0 overflow-y-auto" <Transition.Child
onClose={setOpen} as={Fragment}
> enter="ease-out duration-300"
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> enterFrom="opacity-0"
<Transition.Child enterTo="opacity-100"
as={Fragment} leave="ease-in duration-200"
enter="ease-out duration-300" leaveFrom="opacity-100"
enterFrom="opacity-0" leaveTo="opacity-0"
enterTo="opacity-100" >
leave="ease-in duration-200" <Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
leaveFrom="opacity-100" </Transition.Child>
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</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" className="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true" aria-hidden="true"
> >
&#8203; &#8203;
</span> </span>
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100" enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200" leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
> >
<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="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">
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div> <div>
<div className="mt-3 text-center sm:mt-5"> <div className="mt-3 text-center sm:mt-5">
<Dialog.Title <Dialog.Title
as="h3" as="h3"
className="text-lg leading-6 font-medium text-gray-900" className="text-lg leading-6 font-medium text-gray-900"
> >
Change Password Change Password
</Dialog.Title> </Dialog.Title>
<div className="mt-2"> <div className="mt-2">
<input <input
id="password" id="password"
name="password" name="password"
type="password" type="password"
autoComplete="current-password" autoComplete="current-password"
required required
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" 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} tabIndex={2}
value={newPassword} value={newPassword}
onChange={(e) => setNewPassword(e.target.value)} onChange={(e) => setNewPassword(e.target.value)}
/> />
</div> </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>
</div> </Transition.Child>
<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> </Dialog>
</div> </Transition.Root>
</Dialog> )
</Transition.Root>
);
} }

View File

@@ -1,18 +1,18 @@
import { useNhostAuth } from "@nhost/react-auth"; 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'
export function RequireAuth({ children }: { children: JSX.Element }) { export function RequireAuth({ children }: { children: JSX.Element }) {
const { isAuthenticated, isLoading } = useNhostAuth(); const { isAuthenticated, isLoading } = useNhostAuth()
const location = useLocation(); const location = useLocation()
if (isLoading) { if (isLoading) {
return <div>Loading user data...</div>; return <div>Loading user data...</div>
} }
if (!isAuthenticated) { if (!isAuthenticated) {
return <Navigate to="/sign-in" state={{ from: location }} />; return <Navigate to="/sign-in" state={{ from: location }} />
} }
return children; return children
} }

View File

@@ -1,29 +1,29 @@
import { useNhostAuth } from "@nhost/react-auth"; import { useNhostAuth } from '@nhost/react'
import { useState } from "react"; import { useState } from 'react'
import { useNavigate } from "react-router"; import { useNavigate } from 'react-router'
import { nhost } from "../utils/nhost"; import { nhost } from '../utils/nhost'
export function ResetPassword() { export function ResetPassword() {
const [email, setEmail] = useState(""); const [email, setEmail] = useState('')
const { isAuthenticated } = useNhostAuth(); const { isAuthenticated } = useNhostAuth()
let navigate = useNavigate(); let navigate = useNavigate()
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => { const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault()
const { error } = await nhost.auth.resetPassword({ email }); const { error } = await nhost.auth.resetPassword({ email })
if (error) { if (error) {
return alert(error.message); return alert(error.message)
} }
alert("Check out email inbox"); alert('Check out email inbox')
}; }
if (isAuthenticated) { if (isAuthenticated) {
navigate("/"); navigate('/')
} }
return ( return (
@@ -33,19 +33,14 @@ export function ResetPassword() {
<div className="flex justify-center"> <div className="flex justify-center">
<div className="text-2xl font-bold text-blue-700">AquaSystem</div> <div className="text-2xl font-bold text-blue-700">AquaSystem</div>
</div> </div>
<h2 className="mt-6 text-3xl font-extrabold text-center text-gray-900"> <h2 className="mt-6 text-3xl font-extrabold text-center text-gray-900">Reset Password</h2>
Reset Password
</h2>
</div> </div>
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="px-4 py-8 bg-white shadow sm:rounded-lg sm:px-10"> <div className="px-4 py-8 bg-white shadow sm:rounded-lg sm:px-10">
<form className="space-y-6" onSubmit={handleSubmit}> <form className="space-y-6" onSubmit={handleSubmit}>
<div> <div>
<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">
@@ -77,5 +72,5 @@ export function ResetPassword() {
</div> </div>
</div> </div>
</> </>
); )
} }

View File

@@ -1,31 +1,31 @@
import { useNhostAuth } from "@nhost/react-auth"; import { useNhostAuth } from '@nhost/react'
import { useState } from "react"; import { useState } from 'react'
import { useNavigate } from "react-router"; import { useNavigate } from 'react-router'
import { Link } from "react-router-dom"; import { Link } from 'react-router-dom'
import { nhost } from "../utils/nhost"; import { nhost } from '../utils/nhost'
export function SignIn() { export function SignIn() {
const [email, setEmail] = useState(""); const [email, setEmail] = useState('')
const [password, setPassword] = useState(""); const [password, setPassword] = useState('')
const { isAuthenticated } = useNhostAuth(); const { isAuthenticated } = useNhostAuth()
let navigate = useNavigate(); let navigate = useNavigate()
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => { const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault()
const { error } = await nhost.auth.signIn({ email, password }); const { error } = await nhost.auth.signIn({ email, password })
if (error) { if (error) {
return alert(error.message); return alert(error.message)
} }
navigate("/", { replace: true }); navigate('/', { replace: true })
}; }
if (isAuthenticated) { if (isAuthenticated) {
navigate("/"); navigate('/')
} }
return ( return (
@@ -44,10 +44,7 @@ export function SignIn() {
<div className="px-4 py-8 bg-white shadow sm:rounded-lg sm:px-10"> <div className="px-4 py-8 bg-white shadow sm:rounded-lg sm:px-10">
<form className="space-y-6" onSubmit={handleSubmit}> <form className="space-y-6" onSubmit={handleSubmit}>
<div> <div>
<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">
@@ -66,10 +63,7 @@ export function SignIn() {
</div> </div>
<div> <div>
<label <label htmlFor="password" className="block text-sm font-medium text-gray-700">
htmlFor="password"
className="block text-sm font-medium text-gray-700"
>
Password Password
</label> </label>
<div className="mt-1"> <div className="mt-1">
@@ -110,7 +104,7 @@ export function SignIn() {
</form> </form>
</div> </div>
<div className="text-center py-4"> <div className="text-center py-4">
Don't have an account?{" "} Don't have an account?{' '}
<Link to="/sign-up" className="text-blue-600 hover:text-blue-500"> <Link to="/sign-up" className="text-blue-600 hover:text-blue-500">
Sign Up Sign Up
</Link> </Link>
@@ -118,5 +112,5 @@ export function SignIn() {
</div> </div>
</div> </div>
</> </>
); )
} }

View File

@@ -1,31 +1,31 @@
import { useNhostAuth } from "@nhost/react-auth"; import { useNhostAuth } from '@nhost/react'
import { useState } from "react"; import { useState } from 'react'
import { useNavigate } from "react-router"; import { useNavigate } from 'react-router'
import { Link } from "react-router-dom"; import { Link } from 'react-router-dom'
import { nhost } from "../utils/nhost"; import { nhost } from '../utils/nhost'
export function SignUp() { export function SignUp() {
const [email, setEmail] = useState(""); const [email, setEmail] = useState('')
const [password, setPassword] = useState(""); const [password, setPassword] = useState('')
const { isAuthenticated } = useNhostAuth(); const { isAuthenticated } = useNhostAuth()
let navigate = useNavigate(); let navigate = useNavigate()
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => { const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault()
const { error } = await nhost.auth.signUp({ email, password }); const { error } = await nhost.auth.signUp({ email, password })
if (error) { if (error) {
return alert(error.message); return alert(error.message)
} }
navigate("/", { replace: true }); navigate('/', { replace: true })
}; }
if (isAuthenticated) { if (isAuthenticated) {
navigate("/"); navigate('/')
} }
return ( return (
@@ -44,10 +44,7 @@ export function SignUp() {
<div className="px-4 py-8 bg-white shadow sm:rounded-lg sm:px-10"> <div className="px-4 py-8 bg-white shadow sm:rounded-lg sm:px-10">
<form className="space-y-6" onSubmit={handleSubmit}> <form className="space-y-6" onSubmit={handleSubmit}>
<div> <div>
<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">
@@ -66,10 +63,7 @@ export function SignUp() {
</div> </div>
<div> <div>
<label <label htmlFor="password" className="block text-sm font-medium text-gray-700">
htmlFor="password"
className="block text-sm font-medium text-gray-700"
>
Password Password
</label> </label>
<div className="mt-1"> <div className="mt-1">
@@ -100,7 +94,7 @@ export function SignUp() {
</div> </div>
<div className="text-center py-4"> <div className="text-center py-4">
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
</Link> </Link>
@@ -108,5 +102,5 @@ export function SignUp() {
</div> </div>
</div> </div>
</> </>
); )
} }

View File

@@ -1,55 +1,51 @@
import React, { Fragment, useEffect, useState } from "react"; import React, { Fragment, useEffect, useState } from 'react'
import { Dialog, Menu, Transition } from "@headlessui/react"; import { Dialog, Menu, Transition } from '@headlessui/react'
import { import {
FolderIcon, FolderIcon,
HomeIcon, HomeIcon,
InboxIcon, InboxIcon,
MenuAlt2Icon, MenuAlt2Icon,
UsersIcon, UsersIcon,
XIcon, XIcon
} from "@heroicons/react/outline"; } from '@heroicons/react/outline'
import { SearchIcon } from "@heroicons/react/solid"; import { SearchIcon } from '@heroicons/react/solid'
import { NavLink, Outlet } from "react-router-dom"; import { NavLink, Outlet } from 'react-router-dom'
import { nhost } from "../../utils/nhost"; import { nhost } from '../../utils/nhost'
import { ChangePasswordModal } from "../ChangePasswordModal"; import { ChangePasswordModal } from '../ChangePasswordModal'
const navigation = [ const navigation = [
{ name: "Dashboard", href: "/", icon: HomeIcon, current: true }, { name: 'Dashboard', href: '/', icon: HomeIcon, current: true },
{ name: "Orders", href: "/orders", icon: UsersIcon, current: false }, { name: 'Orders', href: '/orders', icon: UsersIcon, current: false },
{ name: "Customers", href: "/customers", icon: FolderIcon, current: false }, { name: 'Customers', href: '/customers', icon: FolderIcon, current: false },
{ name: "Settings", href: "/settings", icon: InboxIcon, current: false }, { name: 'Settings', href: '/settings', icon: InboxIcon, current: false }
]; ]
function classNames(...classes: string[]) { function classNames(...classes: string[]) {
return classes.filter(Boolean).join(" "); return classes.filter(Boolean).join(' ')
} }
export function Layout() { export function Layout() {
const [sidebarOpen, setSidebarOpen] = useState(false); const [sidebarOpen, setSidebarOpen] = useState(false)
const [showChangePasswordModal, setShowChangePasswordModal] = useState(false); const [showChangePasswordModal, setShowChangePasswordModal] = useState(false)
console.log("Layout Reload"); console.log('Layout Reload')
useEffect(() => { useEffect(() => {
console.log("useEffect RUN"); console.log('useEffect RUN')
if (window.location.hash.search("type=passwordReset") !== -1) { if (window.location.hash.search('type=passwordReset') !== -1) {
console.log("FOUND!"); console.log('FOUND!')
setShowChangePasswordModal(true); setShowChangePasswordModal(true)
} }
}, []); }, [])
return ( return (
<> <>
{showChangePasswordModal && <ChangePasswordModal />} {showChangePasswordModal && <ChangePasswordModal />}
<div> <div>
<Transition.Root show={sidebarOpen} as={Fragment}> <Transition.Root show={sidebarOpen} as={Fragment}>
<Dialog <Dialog as="div" className="fixed inset-0 z-40 flex md:hidden" onClose={setSidebarOpen}>
as="div"
className="fixed inset-0 z-40 flex md:hidden"
onClose={setSidebarOpen}
>
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="transition-opacity ease-linear duration-300" enter="transition-opacity ease-linear duration-300"
@@ -87,10 +83,7 @@ export function Layout() {
onClick={() => setSidebarOpen(false)} onClick={() => setSidebarOpen(false)}
> >
<span className="sr-only">Close sidebar</span> <span className="sr-only">Close sidebar</span>
<XIcon <XIcon className="w-6 h-6 text-white" aria-hidden="true" />
className="w-6 h-6 text-white"
aria-hidden="true"
/>
</button> </button>
</div> </div>
</Transition.Child> </Transition.Child>
@@ -109,11 +102,9 @@ export function Layout() {
to={item.href} to={item.href}
className={({ isActive }) => { className={({ isActive }) => {
return classNames( return classNames(
isActive isActive ? 'bg-blue-800 text-white' : 'text-blue-100 hover:bg-blue-600',
? "bg-blue-800 text-white" 'group flex items-center px-2 py-2 text-base font-medium rounded-md'
: "text-blue-100 hover:bg-blue-600", )
"group flex items-center px-2 py-2 text-base font-medium rounded-md"
);
}} }}
> >
<item.icon <item.icon
@@ -138,9 +129,7 @@ export function Layout() {
{/* Sidebar component, swap this element with another sidebar if you like */} {/* Sidebar component, swap this element with another sidebar if you like */}
<div className="flex flex-col flex-grow pt-5 overflow-y-auto bg-blue-700"> <div className="flex flex-col flex-grow pt-5 overflow-y-auto bg-blue-700">
<div className="flex items-center flex-shrink-0 px-4"> <div className="flex items-center flex-shrink-0 px-4">
<span className="text-lg font-semibold text-white"> <span className="text-lg font-semibold text-white">AquaSystem</span>
AquaSystem
</span>
</div> </div>
<div className="flex flex-col flex-1 mt-5"> <div className="flex flex-col flex-1 mt-5">
<nav className="flex-1 px-2 pb-4 space-y-1"> <nav className="flex-1 px-2 pb-4 space-y-1">
@@ -150,11 +139,9 @@ export function Layout() {
to={item.href} to={item.href}
className={({ isActive }) => { className={({ isActive }) => {
return classNames( return classNames(
isActive isActive ? 'bg-blue-800 text-white' : 'text-blue-100 hover:bg-blue-600',
? "bg-blue-800 text-white" 'group flex items-center px-2 py-2 text-sm font-medium rounded-md'
: "text-blue-100 hover:bg-blue-600", )
"group flex items-center px-2 py-2 text-sm font-medium rounded-md"
);
}} }}
> >
<item.icon <item.icon
@@ -234,11 +221,11 @@ export function Layout() {
<div <div
// to={"/login"} // to={"/login"}
onClick={async () => { onClick={async () => {
await nhost.auth.signOut(); await nhost.auth.signOut()
}} }}
className={classNames( className={classNames(
active ? "bg-gray-100" : "", active ? 'bg-gray-100' : '',
"block px-4 py-2 text-sm text-gray-700" 'block px-4 py-2 text-sm text-gray-700'
)} )}
> >
Sign out Sign out
@@ -260,5 +247,5 @@ export function Layout() {
</div> </div>
</div> </div>
</> </>
); )
} }

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,11 @@
{ {
"name": "@nhost-examples/react-apollo", "name": "@nhost-examples/react-apollo",
"version": "0.1.0", "version": "0.2.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@nhost/react": "^0.2.0", "@apollo/client": "^3.5.10",
"@nhost/react-apollo": "^3.0.0", "@nhost/react": "^0.3.0",
"@nhost/react-apollo": "^4.0.0",
"@rsuite/icons": "^1.0.2", "@rsuite/icons": "^1.0.2",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"react": "^17.0.2", "react": "^17.0.2",
@@ -14,7 +15,6 @@
"react-router-dom": "^6.2.1", "react-router-dom": "^6.2.1",
"rsuite": "^5.6.2" "rsuite": "^5.6.2"
}, },
"lib": "workspace:*",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",

View File

@@ -1,9 +1,8 @@
import { Navigate, useLocation } from 'react-router-dom' import { Navigate, useLocation } from 'react-router-dom'
import { useAuthenticated, useAuthLoading } from '@nhost/react' import { useAuthenticationStatus } from '@nhost/react'
export const AuthGate: React.FC = ({ children }) => { export const AuthGate: React.FC = ({ children }) => {
const isAuthenticated = useAuthenticated() const { isLoading, isAuthenticated } = useAuthenticationStatus()
const isLoading = useAuthLoading()
const location = useLocation() const location = useLocation()
if (isLoading) { if (isLoading) {
return <div>Loading...</div> return <div>Loading...</div>
@@ -17,8 +16,7 @@ export const AuthGate: React.FC = ({ children }) => {
} }
export const PublicGate: React.FC = ({ children }) => { export const PublicGate: React.FC = ({ children }) => {
const isAuthenticated = useAuthenticated() const { isLoading, isAuthenticated } = useAuthenticationStatus()
const isLoading = useAuthLoading()
const location = useLocation() const location = useLocation()
if (isLoading) { if (isLoading) {
return <div>Loading...</div> return <div>Loading...</div>

File diff suppressed because it is too large Load Diff

View File

@@ -53,13 +53,13 @@
"@apollo/client": "^3.5.8" "@apollo/client": "^3.5.8"
}, },
"dependencies": { "dependencies": {
"@nhost/core": "workspace:*", "@nhost/core": "workspace:^",
"graphql": "16", "graphql": "16",
"subscriptions-transport-ws": "^0.11.0" "subscriptions-transport-ws": "^0.11.0"
}, },
"devDependencies": { "devDependencies": {
"@apollo/client": "^3.5.8", "@apollo/client": "^3.5.8",
"xstate": "^4.30.5", "xstate": "^4.30.5",
"@nhost/nhost-js": "workspace:*" "@nhost/nhost-js": "workspace:^"
} }
} }

View File

@@ -1,5 +1,12 @@
# @nhost/hasura-auth-js # @nhost/hasura-auth-js
## 1.0.1
### Patch Changes
- ab36f90: Correct access to user/session information through getUser/getSession/isReady function when authentication state is not ready yet
In some cases e.g. NextJS build, `auth.getUser()`, `auth.getSession()` or `auth.isReady()` should be accessible without raising an error.
## 1.0.0 ## 1.0.0
### Major Changes ### Major Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/hasura-auth-js", "name": "@nhost/hasura-auth-js",
"version": "1.0.0", "version": "1.0.1",
"description": "Hasura-auth client", "description": "Hasura-auth client",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [
@@ -51,7 +51,7 @@
"dist" "dist"
], ],
"dependencies": { "dependencies": {
"@nhost/core": "workspace:*" "@nhost/core": "workspace:^"
}, },
"devDependencies": { "devDependencies": {
"@types/faker": "5", "@types/faker": "5",

View File

@@ -598,7 +598,7 @@ export class HasuraAuthClient {
* @docs https://docs.nhost.io/TODO * @docs https://docs.nhost.io/TODO
*/ */
getSession() { getSession() {
return getSession(this.#client.interpreter?.state.context) return getSession(this.#client.interpreter?.state?.context)
} }
/** /**
@@ -612,11 +612,11 @@ export class HasuraAuthClient {
* @docs https://docs.nhost.io/reference/sdk/authentication#nhost-auth-getuser * @docs https://docs.nhost.io/reference/sdk/authentication#nhost-auth-getuser
*/ */
getUser() { getUser() {
return this.#client.interpreter?.state.context?.user || null return this.#client.interpreter?.state?.context?.user || null
} }
private isReady() { private isReady() {
return !!this.#client.interpreter?.state.hasTag('ready') return !!this.#client.interpreter?.state?.hasTag('ready')
} }
get client() { get client() {

View File

@@ -1,5 +1,19 @@
# @nhost/nextjs # @nhost/nextjs
## 2.0.0
### Patch Changes
- Updated dependencies [39df4d5]
- @nhost/react@0.4.0
## 1.0.1
### Patch Changes
- @nhost/nhost-js@1.0.1
- @nhost/react@0.3.1
## 1.0.0 ## 1.0.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/nextjs", "name": "@nhost/nextjs",
"version": "1.0.0", "version": "2.0.0",
"description": "Nhost NextJS library", "description": "Nhost NextJS library",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [
@@ -50,18 +50,18 @@
"dist" "dist"
], ],
"dependencies": { "dependencies": {
"@nhost/nhost-js": "workspace:*", "@nhost/nhost-js": "workspace:^",
"cookies": "^0.8.0" "cookies": "^0.8.0"
}, },
"peerDependencies": { "peerDependencies": {
"@nhost/react": "workspace:*", "@nhost/react": "workspace:^",
"next": "^12.0.10", "next": "^12.0.10",
"react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0" "react-dom": "^17.0.0 || ^18.0.0"
}, },
"devDependencies": { "devDependencies": {
"@nhost/core": "workspace:*", "@nhost/core": "workspace:^",
"@nhost/react": "workspace:*", "@nhost/react": "workspace:^",
"next": "12.0.10", "next": "12.0.10",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",

View File

@@ -1,5 +1,12 @@
# @nhost/nhost-js # @nhost/nhost-js
## 1.0.1
### Patch Changes
- Updated dependencies [ab36f90]
- @nhost/hasura-auth-js@1.0.1
## 1.0.0 ## 1.0.0
### Major Changes ### Major Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/nhost-js", "name": "@nhost/nhost-js",
"version": "1.0.0", "version": "1.0.1",
"description": "Nhost JavaScript SDK", "description": "Nhost JavaScript SDK",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [
@@ -52,13 +52,13 @@
"verify:fix": "run-p prettier:fix lint:fix" "verify:fix": "run-p prettier:fix lint:fix"
}, },
"dependencies": { "dependencies": {
"@nhost/hasura-auth-js": "workspace:*", "@nhost/hasura-auth-js": "workspace:^",
"@nhost/hasura-storage-js": "workspace:*", "@nhost/hasura-storage-js": "workspace:^",
"axios": "^0.23.0", "axios": "^0.23.0",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"query-string": "^7.0.1" "query-string": "^7.0.1"
}, },
"devDependencies": { "devDependencies": {
"@nhost/core": "workspace:*" "@nhost/core": "workspace:^"
} }
} }

View File

@@ -1,5 +1,19 @@
# @nhost/react-apollo # @nhost/react-apollo
## 5.0.0
### Patch Changes
- Updated dependencies [39df4d5]
- @nhost/react@0.4.0
## 4.0.1
### Patch Changes
- @nhost/react@0.3.1
- @nhost/apollo@0.3.0
## 4.0.0 ## 4.0.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/react-apollo", "name": "@nhost/react-apollo",
"version": "4.0.0", "version": "5.0.0",
"description": "Nhost React Apollo client", "description": "Nhost React Apollo client",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [
@@ -49,19 +49,19 @@
"dist" "dist"
], ],
"dependencies": { "dependencies": {
"@nhost/apollo": "workspace:*" "@nhost/apollo": "workspace:^"
}, },
"peerDependencies": { "peerDependencies": {
"@apollo/client": "^3.5.8", "@apollo/client": "^3.5.8",
"@nhost/react": "workspace:*", "@nhost/react": "workspace:^",
"graphql": "^16.0.0", "graphql": "^16.0.0",
"react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0" "react-dom": "^17.0.0 || ^18.0.0"
}, },
"devDependencies": { "devDependencies": {
"@apollo/client": "^3.5.8", "@apollo/client": "^3.5.8",
"@nhost/core": "workspace:*", "@nhost/core": "workspace:^",
"@nhost/react": "workspace:*", "@nhost/react": "workspace:^",
"@types/react": "^17.0.39", "@types/react": "^17.0.39",
"@types/ws": "^8.2.2", "@types/ws": "^8.2.2",
"graphql": "16", "graphql": "16",

View File

@@ -1,12 +1,11 @@
# ⚠️ This package is deprecated
Use the new [`@nhost/react`](https://github.com/nhost/nhost/tree/main/packages/react) package instead.
# Nhost React Auth # Nhost React Auth
For easy usage of Auth with [Nhost](https://nhost.io). For easy usage of Auth with [Nhost](https://nhost.io).
## Deprecation notice
This package is deprecated. It is recommended to use `@nhost/react` instead.
If you want to continue to use it, don't forget to add `@nhost/react` to your packages as it is now a peer dependency.
## Install ## Install
`$ npm install @nhost/react-auth @nhost/react` `$ npm install @nhost/react-auth @nhost/react`

View File

@@ -53,8 +53,8 @@
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
}, },
"devDependencies": { "devDependencies": {
"@nhost/react": "workspace:*", "@nhost/react": "workspace:^",
"@nhost/nhost-js": "workspace:*", "@nhost/nhost-js": "workspace:^",
"@types/react": "^17.0.38", "@types/react": "^17.0.38",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2" "react-dom": "^17.0.2"

View File

@@ -1,5 +1,37 @@
# @nhost/react # @nhost/react
## 0.4.0
### Minor Changes
- 39df4d5: Deprecate `useAuthLoading` and introduce `useAuthenticationStatus`
When using both `useAuthLoading` and `useAuthenticated` together, the hooks rerender independently from each other.
As a result, when a user loads the page while he previously authenticated, the hooks values were chronologically:
| isLoading | isAuthenticated |
| --------- | --------------- |
| `true` | `false` |
| `false` | `false` |
| `false` | `true` |
The intermediate (`false`, `false`) is incorrect and is causing issues when using an authentication gate.
It is therefore recommended to stop using `useAuthLoading`, and to use `useAuthenticationStatus` instead, in order to keep the loading state and the authentication in sync within the same hook.
Usage:
```js
const { isLoading, isAuthenticated } = useAuthenticationStatus()
```
Fixes [this issue](https://github.com/nhost/nhost/issues/302)
## 0.3.1
### Patch Changes
- @nhost/nhost-js@1.0.1
## 0.3.0 ## 0.3.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/react", "name": "@nhost/react",
"version": "0.3.0", "version": "0.4.0",
"description": "Nhost React library", "description": "Nhost React library",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [
@@ -48,13 +48,13 @@
"dist" "dist"
], ],
"dependencies": { "dependencies": {
"@nhost/nhost-js": "workspace:*", "@nhost/nhost-js": "workspace:^",
"@xstate/react": "^2.0.1", "@xstate/react": "^2.0.1",
"immer": "^9.0.12" "immer": "^9.0.12"
}, },
"devDependencies": { "devDependencies": {
"@nhost/hasura-auth-js": "workspace:*", "@nhost/hasura-auth-js": "workspace:^",
"@nhost/core": "workspace:*", "@nhost/core": "workspace:^",
"@xstate/inspect": "^0.6.2", "@xstate/inspect": "^0.6.2",
"react": "^17.0.2", "react": "^17.0.2",
"ws": "^8.4.2", "ws": "^8.4.2",

View File

@@ -24,18 +24,24 @@ export const useNhostBackendUrl = () => {
return nhost.auth.client.backendUrl.replace('/v1/auth', '') return nhost.auth.client.backendUrl.replace('/v1/auth', '')
} }
/**
* @deprecated When using both useAuthLoading and useAuthenticated together, their initial state will change three times: (true,false) -> (false,false) -> (false,true). Use useAuthenticationStatus instead.
*/
export const useAuthLoading = () => { export const useAuthLoading = () => {
const service = useAuthInterpreter() const service = useAuthInterpreter()
const [isLoading, setIsLoading] = useState(!service.status || !service?.state?.hasTag('ready')) return useSelector(service, (state) => !state.hasTag('ready'))
useEffect(() => { }
const subscription = service.subscribe((state) => {
const newValue = !state.hasTag('ready')
setIsLoading(newValue)
})
return subscription.unsubscribe
}, [service])
return isLoading export const useAuthenticationStatus = () => {
const service = useAuthInterpreter()
return useSelector(
service,
(state) => ({
isAuthenticated: state.matches({ authentication: 'signedIn' }),
isLoading: !state.hasTag('ready')
}),
(a, b) => a.isAuthenticated === b.isAuthenticated && a.isLoading === b.isLoading
)
} }
export const useAuthenticated = () => { export const useAuthenticated = () => {

View File

@@ -1,14 +1,13 @@
import { useMemo } from 'react' import { useMemo } from 'react'
import { useAuthenticated, useAuthLoading } from './common' import { useAuthenticationStatus } from './common'
import { useUserData } from './user' import { useUserData } from './user'
/** /**
* @deprecated This hooks ensures backward compatibility with `@nhost/react-auth`, which is deprecated * @deprecated This hook ensures backward compatibility with `@nhost/react-auth`, which is deprecated
*/ */
export const useNhostAuth = () => { export const useNhostAuth = () => {
const isLoading = useAuthLoading() const { isLoading, isAuthenticated } = useAuthenticationStatus()
const isAuthenticated = useAuthenticated()
const user = useUserData() const user = useUserData()
return useMemo(() => ({ isLoading, isAuthenticated, user }), [isLoading, isAuthenticated, user]) return useMemo(() => ({ isLoading, isAuthenticated, user }), [isLoading, isAuthenticated, user])
} }

View File

@@ -3,7 +3,7 @@ import { useMemo } from 'react'
import { SignUpOptions } from '@nhost/core' import { SignUpOptions } from '@nhost/core'
import { useSelector } from '@xstate/react' import { useSelector } from '@xstate/react'
import { useAuthenticated, useAuthInterpreter, useAuthLoading } from './common' import { useAuthenticationStatus, useAuthInterpreter } from './common'
export const useSignUpEmailPassword = ( export const useSignUpEmailPassword = (
stateEmail?: string, stateEmail?: string,
@@ -18,8 +18,7 @@ export const useSignUpEmailPassword = (
(state) => state.context.errors.registration, (state) => state.context.errors.registration,
(a, b) => a?.error === b?.error (a, b) => a?.error === b?.error
) )
const loading = useAuthLoading() const { isLoading: loading, isAuthenticated: isSuccess } = useAuthenticationStatus()
const isSuccess = useAuthenticated()
const isLoading = useMemo(() => loading && !isSuccess, [loading, isSuccess]) const isLoading = useMemo(() => loading && !isSuccess, [loading, isSuccess])
const needsEmailVerification = const needsEmailVerification =
!!service.status && !!service.status &&

34
pnpm-lock.yaml generated
View File

@@ -145,8 +145,8 @@ importers:
packages/apollo: packages/apollo:
specifiers: specifiers:
'@apollo/client': ^3.5.8 '@apollo/client': ^3.5.8
'@nhost/core': workspace:* '@nhost/core': workspace:^
'@nhost/nhost-js': workspace:* '@nhost/nhost-js': workspace:^
graphql: 15.7.2 graphql: 15.7.2
subscriptions-transport-ws: ^0.11.0 subscriptions-transport-ws: ^0.11.0
xstate: ^4.30.5 xstate: ^4.30.5
@@ -173,7 +173,7 @@ importers:
packages/hasura-auth-js: packages/hasura-auth-js:
specifiers: specifiers:
'@nhost/core': workspace:* '@nhost/core': workspace:^
'@types/faker': '5' '@types/faker': '5'
axios: ^0.26.0 axios: ^0.26.0
faker: '5' faker: '5'
@@ -198,9 +198,9 @@ importers:
packages/nextjs: packages/nextjs:
specifiers: specifiers:
'@nhost/core': workspace:* '@nhost/core': workspace:^
'@nhost/nhost-js': workspace:* '@nhost/nhost-js': workspace:^
'@nhost/react': workspace:* '@nhost/react': workspace:^
cookies: ^0.8.0 cookies: ^0.8.0
next: 12.0.10 next: 12.0.10
react: ^17.0.2 react: ^17.0.2
@@ -219,9 +219,9 @@ importers:
packages/nhost-js: packages/nhost-js:
specifiers: specifiers:
'@nhost/core': workspace:* '@nhost/core': workspace:^
'@nhost/hasura-auth-js': workspace:* '@nhost/hasura-auth-js': workspace:^
'@nhost/hasura-storage-js': workspace:* '@nhost/hasura-storage-js': workspace:^
axios: ^0.23.0 axios: ^0.23.0
jwt-decode: ^3.1.2 jwt-decode: ^3.1.2
query-string: ^7.0.1 query-string: ^7.0.1
@@ -236,9 +236,9 @@ importers:
packages/react: packages/react:
specifiers: specifiers:
'@nhost/core': workspace:* '@nhost/core': workspace:^
'@nhost/hasura-auth-js': workspace:* '@nhost/hasura-auth-js': workspace:^
'@nhost/nhost-js': workspace:* '@nhost/nhost-js': workspace:^
'@xstate/inspect': ^0.6.2 '@xstate/inspect': ^0.6.2
'@xstate/react': ^2.0.1 '@xstate/react': ^2.0.1
immer: ^9.0.12 immer: ^9.0.12
@@ -260,9 +260,9 @@ importers:
packages/react-apollo: packages/react-apollo:
specifiers: specifiers:
'@apollo/client': ^3.5.8 '@apollo/client': ^3.5.8
'@nhost/apollo': workspace:* '@nhost/apollo': workspace:^
'@nhost/core': workspace:* '@nhost/core': workspace:^
'@nhost/react': workspace:* '@nhost/react': workspace:^
'@types/react': ^17.0.39 '@types/react': ^17.0.39
'@types/ws': ^8.2.2 '@types/ws': ^8.2.2
graphql: 15.7.2 graphql: 15.7.2
@@ -282,8 +282,8 @@ importers:
packages/react-auth: packages/react-auth:
specifiers: specifiers:
'@nhost/nhost-js': workspace:* '@nhost/nhost-js': workspace:^
'@nhost/react': workspace:* '@nhost/react': workspace:^
'@types/react': ^17.0.38 '@types/react': ^17.0.38
react: ^17.0.2 react: ^17.0.2
react-dom: ^17.0.2 react-dom: ^17.0.2