Compare commits
23 Commits
@nhost/rea
...
@nhost/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dee93bb873 | ||
|
|
173b587802 | ||
|
|
30ef1660b4 | ||
|
|
a613aa9f0c | ||
|
|
3c03b9b46f | ||
|
|
65a3061146 | ||
|
|
55864eac30 | ||
|
|
28494d6c1f | ||
|
|
6777738c53 | ||
|
|
0d60693c27 | ||
|
|
c159c9c98c | ||
|
|
58fa2a201c | ||
|
|
db4607ccac | ||
|
|
95b14557a0 | ||
|
|
8b527d0fcb | ||
|
|
fc50beec5e | ||
|
|
ed0de2d930 | ||
|
|
2192fdc92e | ||
|
|
eec2601a3a | ||
|
|
93eaa85b47 | ||
|
|
5a212aaa12 | ||
|
|
79056d8b48 | ||
|
|
f86883df88 |
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"label": "The Nhost Platform",
|
|
||||||
"position": 1,
|
|
||||||
"link": { "type": "generated-index", "slug": "/platform" }
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"label": "Authentication",
|
"label": "Authentication",
|
||||||
"position": 4,
|
"position": 6
|
||||||
"link": { "id": "platform/authentication/index", "type": "doc" }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: 'Nhost CLI'
|
title: 'CLI'
|
||||||
sidebar_position: 3
|
sidebar_position: 11
|
||||||
---
|
---
|
||||||
|
|
||||||
import Tabs from '@theme/Tabs';
|
import Tabs from '@theme/Tabs';
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"label": "Database",
|
"label": "Database",
|
||||||
"position": 2
|
"position": 4
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: 'Environment variables'
|
title: 'Environment variables'
|
||||||
sidebar_position: 1
|
sidebar_position: 9
|
||||||
---
|
---
|
||||||
|
|
||||||
Environment variables are key-value pairs configured outside your source code. They are used to store environment-specific values such as API keys.
|
Environment variables are key-value pairs configured outside your source code. They are used to store environment-specific values such as API keys.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: 'GitHub integration'
|
title: 'GitHub integration'
|
||||||
sidebar_position: 2
|
sidebar_position: 10
|
||||||
---
|
---
|
||||||
|
|
||||||
You can connect your Nhost app to a GitHub repository. When you do this, any updates you push to your code will automatically be deployed.
|
You can connect your Nhost app to a GitHub repository. When you do this, any updates you push to your code will automatically be deployed.
|
||||||
@@ -21,7 +21,7 @@ Specifically, the following will be deployed:
|
|||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
|
|
||||||
Create a new Nhost app. Then use [Nhost CLI](/platform/nhost/local-development) to initialize your Nhost app locally.
|
Create a new Nhost app. Then use [Nhost CLI](/platform/cli) to initialize your Nhost app locally.
|
||||||
|
|
||||||
The workflow is as follows:
|
The workflow is as follows:
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"label": "GraphQL",
|
"label": "GraphQL",
|
||||||
"position": 3
|
"position": 5
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
title: 'The Nhost Platform'
|
|
||||||
sidebar_position: 1
|
|
||||||
---
|
|
||||||
|
|
||||||
- [Database](/platform/database)
|
|
||||||
- [GraphQL](/platform/graphql)
|
|
||||||
- [Authentication](/platform/authentication)
|
|
||||||
- [Storage](/platform/storage)
|
|
||||||
- [Serverless Functions](/platform/serverless-functions)
|
|
||||||
- [Nhost](/platform/nhost)
|
|
||||||
28
docs/docs/platform/index.mdx
Normal file
28
docs/docs/platform/index.mdx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
title: 'Introduction to Nhost'
|
||||||
|
sidebar_label: Introduction
|
||||||
|
sidebar_position: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
Nhost is the open source GraphQL backend (Firebase Alternative) and a development platform. Nhost is doing for the backend, what [Netlify](https://netlify.com/) and [Vercel](https://vercel.com/) are doing for the frontend.
|
||||||
|
|
||||||
|
We provide a modern backend with the general building blocks required to build fantastic digital products.
|
||||||
|
|
||||||
|
We make it easy to build and deploy this backend using our platform that takes care of configuration, security, and performance. Things just works and scale automatically so you can focus on your product and on your business.
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
Get started quickly by following one of our quickstart guides:
|
||||||
|
|
||||||
|
- [Next.js](/platform/quickstarts/nextjs)
|
||||||
|
- [React](/platform/quickstarts/react)
|
||||||
|
|
||||||
|
## Products and features
|
||||||
|
|
||||||
|
Learn more about the product and features of Nhost.
|
||||||
|
|
||||||
|
- [Database](/platform/database)
|
||||||
|
- [GraphQL](/platform/graphql)
|
||||||
|
- [Authentication](/platform/authentication)
|
||||||
|
- [Storage](/platform/storage)
|
||||||
|
- [Serverless Functions](/platform/serverless-functions)
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"label": "Nhost",
|
|
||||||
"position": 7
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
---
|
|
||||||
title: 'Overview'
|
|
||||||
---
|
|
||||||
|
|
||||||
Documentation for other platform features:
|
|
||||||
|
|
||||||
- [Environment variables](/platform/nhost/environment-variables)
|
|
||||||
- [GitHub integration](/platform/nhost/github-integration)
|
|
||||||
- [Local development](/platform/nhost/local-development)
|
|
||||||
5
docs/docs/platform/overview/_category_.json
Normal file
5
docs/docs/platform/overview/_category_.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"label": "Overview",
|
||||||
|
"position": 2,
|
||||||
|
"collapsed": false
|
||||||
|
}
|
||||||
30
docs/docs/platform/overview/architecture.md
Normal file
30
docs/docs/platform/overview/architecture.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
title: 'Architecture'
|
||||||
|
sidebar_position: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
Nhost is a backend as a service built with open source tools to provide developers the general building blocks required to build fantastic digital apps and products.
|
||||||
|
|
||||||
|
Here's a diagram of the Nhost stack on a high level:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
As you see in the image above, Nhost provides endpoints for:
|
||||||
|
|
||||||
|
- GraphQL (`/graphql`)
|
||||||
|
- Authentication (`/auth`)
|
||||||
|
- Storage (`/storage`)
|
||||||
|
- Functions (`/functions`)
|
||||||
|
|
||||||
|
Data is stored in Postgres and files are stored in S3.
|
||||||
|
|
||||||
|
## Open Source
|
||||||
|
|
||||||
|
The open source tools used for the full Nhost stack are:
|
||||||
|
|
||||||
|
- Database: [Postgres](https://www.postgresql.org/)
|
||||||
|
- S3: [Minio](https://github.com/minio/minio)
|
||||||
|
- GraphQL: [Hasura](https://github.com/hasura/graphql-engine)
|
||||||
|
- Authentication: [Hasura Auth](https://github.com/nhost/hasura-auth)
|
||||||
|
- Storage: [Hasura Storage](https://github.com/nhost/hasura-storage)
|
||||||
|
- Functions: [Node.js](https://nodejs.org/en/)
|
||||||
5
docs/docs/platform/quickstarts/_category_.json
Normal file
5
docs/docs/platform/quickstarts/_category_.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"label": "Quickstarts",
|
||||||
|
"position": 3,
|
||||||
|
"collapsed": false
|
||||||
|
}
|
||||||
8
docs/docs/platform/quickstarts/nextjs.md
Normal file
8
docs/docs/platform/quickstarts/nextjs.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: 'Quickstart: Next.js'
|
||||||
|
sidebar_position: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
This is a quickstart guide for React with Nhost.
|
||||||
8
docs/docs/platform/quickstarts/react.md
Normal file
8
docs/docs/platform/quickstarts/react.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: 'Quickstart: React'
|
||||||
|
sidebar_position: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
This is a quickstart guide for React with Nhost.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"label": "Serverless Functions",
|
"label": "Serverless Functions",
|
||||||
"position": 6
|
"position": 8
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ Event triggers are managed in Hasura. Go to Hasura, then select **Events** in th
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
Nhost's [environment variables](/platform/nhost/environment-variables) can be used in event trigger headers. For example, you can attach `NHOST_WEBHOOK_SECRET` to an outgoing webhook here.
|
Nhost's [environment variables](/platform/environment-variables) can be used in event trigger headers. For example, you can attach `NHOST_WEBHOOK_SECRET` to an outgoing webhook here.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ In your serverless function, you need to make sure the request actually comes fr
|
|||||||
- Check the header in the serverless function. It should match the environment variable `NHOST_WEBHOOK_SECRET`.
|
- Check the header in the serverless function. It should match the environment variable `NHOST_WEBHOOK_SECRET`.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
export default function async handler(req, res) {
|
export default async function handler(req, res) {
|
||||||
|
|
||||||
// Check webhook secret to make sure the request is valid
|
// Check webhook secret to make sure the request is valid
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"label": "Storage",
|
"label": "Storage",
|
||||||
"position": 5
|
"position": 7
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,17 +38,12 @@ import type { AppProps } from 'next/app';
|
|||||||
|
|
||||||
import { NhostClient, NhostNextProvider } from '@nhost/nextjs';
|
import { NhostClient, NhostNextProvider } from '@nhost/nextjs';
|
||||||
|
|
||||||
import Header from '../components/Header';
|
|
||||||
|
|
||||||
const nhost = new NhostClient({ backendUrl: 'my-app.nhost.run' });
|
const nhost = new NhostClient({ backendUrl: 'my-app.nhost.run' });
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }: AppProps) {
|
function MyApp({ Component, pageProps }: AppProps) {
|
||||||
return (
|
return (
|
||||||
<NhostNextProvider nhost={nhost} initial={pageProps.nhostSession}>
|
<NhostNextProvider nhost={nhost} initial={pageProps.nhostSession}>
|
||||||
<div>
|
<Component {...pageProps} />
|
||||||
<Header />
|
|
||||||
<Component {...pageProps} />
|
|
||||||
</div>
|
|
||||||
</NhostNextProvider>
|
</NhostNextProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ const config = {
|
|||||||
href: 'https://app.nhost.io',
|
href: 'https://app.nhost.io',
|
||||||
className: 'header-get-started-link',
|
className: 'header-get-started-link',
|
||||||
position: 'right',
|
position: 'right',
|
||||||
label: 'Create an app',
|
label: 'Get started',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -156,6 +156,10 @@
|
|||||||
color: var(--ifm-footer-link-hover-color);
|
color: var(--ifm-footer-link-hover-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.header-github-link:hover {
|
.header-github-link:hover {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
docs/static/img/architecture/nhost-diagram.png
vendored
Normal file
BIN
docs/static/img/architecture/nhost-diagram.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
@@ -4,19 +4,19 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.6.2",
|
"@apollo/client": "^3.6.2",
|
||||||
|
"@mantine/core": "^4.2.2",
|
||||||
|
"@mantine/hooks": "^4.2.2",
|
||||||
|
"@mantine/notifications": "^4.2.2",
|
||||||
|
"@mantine/prism": "^4.2.2",
|
||||||
"@nhost/core": "workspace:*",
|
"@nhost/core": "workspace:*",
|
||||||
"@nhost/react": "workspace:*",
|
"@nhost/react": "workspace:*",
|
||||||
"@nhost/react-apollo": "workspace:*",
|
"@nhost/react-apollo": "workspace:*",
|
||||||
"@rsuite/icons": "^1.0.2",
|
|
||||||
"graphql": "15.7.2",
|
"graphql": "15.7.2",
|
||||||
"less": "^4.1.2",
|
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.1.0",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
"react-json-view": "^1.21.3",
|
|
||||||
"react-router": "^6.3.0",
|
"react-router": "^6.3.0",
|
||||||
"react-router-dom": "^6.3.0",
|
"react-router-dom": "^6.3.0"
|
||||||
"rsuite": "^5.10.0"
|
|
||||||
},
|
},
|
||||||
"lib": "workspace:*",
|
"lib": "workspace:*",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import { Container, Title } from '@mantine/core'
|
||||||
import { useNhostClient } from '@nhost/react'
|
import { useNhostClient } from '@nhost/react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { Panel } from 'rsuite'
|
|
||||||
|
|
||||||
export const AboutPage: React.FC = () => {
|
export const AboutPage: React.FC = () => {
|
||||||
const nhost = useNhostClient()
|
const nhost = useNhostClient()
|
||||||
@@ -15,7 +15,8 @@ export const AboutPage: React.FC = () => {
|
|||||||
console.log(req)
|
console.log(req)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Panel header="About this example" bordered>
|
<Container>
|
||||||
|
<Title>About this example</Title>
|
||||||
<p>This application demonstrates the available features of the Nhost stack.</p>
|
<p>This application demonstrates the available features of the Nhost stack.</p>
|
||||||
<button onClick={fetch}>Fetch</button>
|
<button onClick={fetch}>Fetch</button>
|
||||||
<div>
|
<div>
|
||||||
@@ -32,13 +33,13 @@ export const AboutPage: React.FC = () => {
|
|||||||
<ul>
|
<ul>
|
||||||
<li>React</li>
|
<li>React</li>
|
||||||
<li>React-router</li>
|
<li>React-router</li>
|
||||||
<li>RSuite</li>
|
<li>Mantine</li>
|
||||||
<li>and of course, the Nhost React client</li>
|
<li>and of course, the Nhost React client</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Noew let's go to the <Link to="/">index page</Link>
|
Noew let's go to the <Link to="/">index page</Link>
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
/* eslint-disable react/react-in-jsx-scope */
|
/* eslint-disable react/react-in-jsx-scope */
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { Link, Route, Routes, useLocation, useNavigate } from 'react-router-dom'
|
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { Container, Content, Header, Nav, Navbar } from 'rsuite'
|
|
||||||
|
|
||||||
import { useAuthenticated, useSignOut } from '@nhost/react'
|
import { useAuthenticated, useSignOut } from '@nhost/react'
|
||||||
import ExitIcon from '@rsuite/icons/Exit'
|
|
||||||
|
|
||||||
import { AuthGate, PublicGate } from './components/auth-gates'
|
import { AuthGate, PublicGate } from './components/auth-gates'
|
||||||
import { AboutPage } from './About'
|
import { AboutPage } from './About'
|
||||||
@@ -15,6 +13,10 @@ import { SignInPage } from './sign-in'
|
|||||||
import { SignUpPage } from './sign-up'
|
import { SignUpPage } from './sign-up'
|
||||||
|
|
||||||
import './App.css'
|
import './App.css'
|
||||||
|
import NavBar from './components/NavBar'
|
||||||
|
import { MantineProvider, AppShell, Header } from '@mantine/core'
|
||||||
|
import { NotificationsProvider } from '@mantine/notifications'
|
||||||
|
const title = 'Nhost with React and Apollo'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const isAuthenticated = useAuthenticated()
|
const isAuthenticated = useAuthenticated()
|
||||||
@@ -27,74 +29,76 @@ function App() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [signedOut])
|
}, [signedOut])
|
||||||
return (
|
return (
|
||||||
<Container>
|
<MantineProvider
|
||||||
<Header>
|
withGlobalStyles
|
||||||
<Navbar appearance="inverse">
|
withNormalizeCSS
|
||||||
<Navbar.Brand as="div">
|
theme={{
|
||||||
<Link to="/">
|
/** Put your mantine theme override here */
|
||||||
<img src="/logo.svg" alt="logo" style={{ maxHeight: '100%' }} />
|
colorScheme: 'light'
|
||||||
</Link>
|
}}
|
||||||
</Navbar.Brand>
|
>
|
||||||
<Nav activeKey={location.pathname} onSelect={navigate}>
|
<NotificationsProvider>
|
||||||
{isAuthenticated && <Nav.Item eventKey="/profile">Profile</Nav.Item>}
|
<AppShell
|
||||||
{isAuthenticated && <Nav.Item eventKey="/apollo">Apollo GraphQL</Nav.Item>}
|
padding="md"
|
||||||
<Nav.Item eventKey="/about">About</Nav.Item>
|
navbar={<NavBar />}
|
||||||
</Nav>
|
header={
|
||||||
<Nav pullRight>
|
<Header height={60} p="xs">
|
||||||
{isAuthenticated && (
|
{title}
|
||||||
<Nav.Item icon={<ExitIcon />} onSelect={signOut}>
|
</Header>
|
||||||
Sign Out
|
}
|
||||||
</Nav.Item>
|
styles={(theme) => ({
|
||||||
)}
|
main: {
|
||||||
</Nav>
|
backgroundColor:
|
||||||
</Navbar>
|
theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.colors.gray[0]
|
||||||
</Header>
|
|
||||||
<Content>
|
|
||||||
<Routes>
|
|
||||||
<Route
|
|
||||||
path="/"
|
|
||||||
element={
|
|
||||||
<AuthGate>
|
|
||||||
<Home />
|
|
||||||
</AuthGate>
|
|
||||||
}
|
}
|
||||||
/>
|
})}
|
||||||
<Route path="/about" element={<AboutPage />} />
|
>
|
||||||
<Route
|
<Routes>
|
||||||
path="/sign-in/*"
|
<Route
|
||||||
element={
|
path="/"
|
||||||
<PublicGate>
|
element={
|
||||||
<SignInPage />
|
<AuthGate>
|
||||||
</PublicGate>
|
<Home />
|
||||||
}
|
</AuthGate>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/sign-up/*"
|
<Route path="/about" element={<AboutPage />} />
|
||||||
element={
|
<Route
|
||||||
<PublicGate>
|
path="/sign-in/*"
|
||||||
<SignUpPage />
|
element={
|
||||||
</PublicGate>
|
<PublicGate>
|
||||||
}
|
<SignInPage />
|
||||||
/>
|
</PublicGate>
|
||||||
<Route
|
}
|
||||||
path="/profile"
|
/>
|
||||||
element={
|
<Route
|
||||||
<AuthGate>
|
path="/sign-up/*"
|
||||||
<ProfilePage />
|
element={
|
||||||
</AuthGate>
|
<PublicGate>
|
||||||
}
|
<SignUpPage />
|
||||||
/>
|
</PublicGate>
|
||||||
<Route
|
}
|
||||||
path="/apollo"
|
/>
|
||||||
element={
|
<Route
|
||||||
<AuthGate>
|
path="/profile"
|
||||||
<ApolloPage />
|
element={
|
||||||
</AuthGate>
|
<AuthGate>
|
||||||
}
|
<ProfilePage />
|
||||||
/>
|
</AuthGate>
|
||||||
</Routes>
|
}
|
||||||
</Content>
|
/>
|
||||||
</Container>
|
<Route
|
||||||
|
path="/apollo"
|
||||||
|
element={
|
||||||
|
<AuthGate>
|
||||||
|
<ApolloPage />
|
||||||
|
</AuthGate>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
|
</AppShell>
|
||||||
|
</NotificationsProvider>
|
||||||
|
</MantineProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
import { Container, Title } from '@mantine/core'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Panel } from 'rsuite'
|
|
||||||
|
|
||||||
const HomePage: React.FC = () => {
|
const HomePage: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<Panel header="Home page" bordered>
|
<Container>
|
||||||
|
<Title>Home page</Title>
|
||||||
You are authenticated. You have now access to the authorised part of the application.
|
You are authenticated. You have now access to the authorised part of the application.
|
||||||
</Panel>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default HomePage
|
export default HomePage
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Panel, Table } from 'rsuite'
|
|
||||||
|
|
||||||
import { gql } from '@apollo/client'
|
import { gql } from '@apollo/client'
|
||||||
import { useAuthQuery } from '@nhost/react-apollo'
|
import { useAuthQuery } from '@nhost/react-apollo'
|
||||||
|
import { Container, Loader, Title } from '@mantine/core'
|
||||||
|
|
||||||
const GET_BOOKS = gql`
|
const GET_BOOKS = gql`
|
||||||
query BooksQuery {
|
query BooksQuery {
|
||||||
@@ -13,24 +13,22 @@ const GET_BOOKS = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const { Column, Cell, HeaderCell } = Table
|
|
||||||
export const ApolloPage: React.FC = () => {
|
export const ApolloPage: React.FC = () => {
|
||||||
const { loading, data } = useAuthQuery(GET_BOOKS, {
|
const { loading, data } = useAuthQuery(GET_BOOKS, {
|
||||||
pollInterval: 5000,
|
pollInterval: 5000,
|
||||||
fetchPolicy: 'cache-and-network'
|
fetchPolicy: 'cache-and-network'
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
<Panel header="Apollo GraphQL">
|
<Container>
|
||||||
<Table loading={loading} data={data?.books || []} bordered cellBordered>
|
<Title>Apollo GraphQL</Title>
|
||||||
<Column key="id" fixed width={300}>
|
{loading && <Loader />}
|
||||||
<HeaderCell>Id</HeaderCell>
|
{data?.books && (
|
||||||
<Cell dataKey="id" />
|
<ul>
|
||||||
</Column>
|
{data.books.map((book) => (
|
||||||
<Column key="title" fixed flexGrow={1}>
|
<li key={book.id}>{book.title}</li>
|
||||||
<HeaderCell>Title</HeaderCell>
|
))}
|
||||||
<Cell dataKey="title" />
|
</ul>
|
||||||
</Column>
|
)}
|
||||||
</Table>
|
</Container>
|
||||||
</Panel>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
26
examples/react-apollo/src/components/AuthLayout.tsx
Normal file
26
examples/react-apollo/src/components/AuthLayout.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Card, Container, Divider, SimpleGrid, Title } from '@mantine/core'
|
||||||
|
|
||||||
|
export const AuthLayout: React.FC<{
|
||||||
|
title?: string
|
||||||
|
footer?: React.ReactNode
|
||||||
|
children: React.ReactNode
|
||||||
|
}> = ({ title, footer, children }) => {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Card shadow="sm" p="lg" m="lg">
|
||||||
|
{title && <Title p="lg">{title}</Title>}
|
||||||
|
<SimpleGrid cols={1} spacing={6}>
|
||||||
|
{children}
|
||||||
|
</SimpleGrid>
|
||||||
|
</Card>
|
||||||
|
{footer && (
|
||||||
|
<>
|
||||||
|
<Divider my="sm" />
|
||||||
|
{footer}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthLayout
|
||||||
41
examples/react-apollo/src/components/AuthLink.tsx
Normal file
41
examples/react-apollo/src/components/AuthLink.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { Button, ButtonVariant } from '@mantine/core'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
|
const AuthLink: React.FC<{
|
||||||
|
icon?: React.ReactNode
|
||||||
|
link: string
|
||||||
|
color?: string
|
||||||
|
children?: React.ReactNode
|
||||||
|
variant?: ButtonVariant
|
||||||
|
}> = ({ icon, color, link, variant, children }) => {
|
||||||
|
return (
|
||||||
|
// <Link to={link}>
|
||||||
|
<Button
|
||||||
|
component={Link}
|
||||||
|
fullWidth
|
||||||
|
radius="sm"
|
||||||
|
variant={variant}
|
||||||
|
to={link}
|
||||||
|
leftIcon={icon}
|
||||||
|
styles={(theme) => ({
|
||||||
|
root: {
|
||||||
|
backgroundColor: color,
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: color && theme.fn.darken(color, 0.05)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
leftIcon: {
|
||||||
|
marginRight: 15
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
|
// </Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthLink
|
||||||
80
examples/react-apollo/src/components/NavBar.tsx
Normal file
80
examples/react-apollo/src/components/NavBar.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { FaHouseUser, FaQuestion, FaSignOutAlt } from 'react-icons/fa'
|
||||||
|
import { SiApollographql } from 'react-icons/si'
|
||||||
|
import { Group, MantineColor, Navbar, Text, ThemeIcon, UnstyledButton } from '@mantine/core'
|
||||||
|
import { useAuthenticated, useSignOut } from '@nhost/react'
|
||||||
|
import { useNavigate, useLocation } from 'react-router'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
interface MenuItemProps {
|
||||||
|
icon: React.ReactNode
|
||||||
|
color?: MantineColor
|
||||||
|
label: string
|
||||||
|
link?: string
|
||||||
|
action?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const MenuItem: React.FC<MenuItemProps> = ({ icon, color, label, link, action }) => {
|
||||||
|
const location = useLocation()
|
||||||
|
const active = location.pathname === link
|
||||||
|
const Button = (
|
||||||
|
<UnstyledButton
|
||||||
|
onClick={action}
|
||||||
|
sx={(theme) => ({
|
||||||
|
display: 'block',
|
||||||
|
width: '100%',
|
||||||
|
padding: theme.spacing.xs,
|
||||||
|
borderRadius: theme.radius.sm,
|
||||||
|
color: active
|
||||||
|
? theme.colors[theme.primaryColor][theme.colorScheme === 'dark' ? 4 : 7]
|
||||||
|
: theme.colorScheme === 'dark'
|
||||||
|
? theme.colors.dark[0]
|
||||||
|
: theme.black,
|
||||||
|
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor:
|
||||||
|
theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0]
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Group>
|
||||||
|
<ThemeIcon color={color} variant="outline">
|
||||||
|
{icon}
|
||||||
|
</ThemeIcon>
|
||||||
|
|
||||||
|
<Text size="sm">{label}</Text>
|
||||||
|
</Group>
|
||||||
|
</UnstyledButton>
|
||||||
|
)
|
||||||
|
|
||||||
|
return link ? <Link to={link}>{Button}</Link> : Button
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: MenuItemProps[] = [
|
||||||
|
{ icon: <FaHouseUser size={16} />, label: 'Home', link: '/' },
|
||||||
|
{ icon: <FaHouseUser size={16} />, label: 'Profile', link: '/profile' },
|
||||||
|
{ icon: <SiApollographql size={16} />, label: 'Apollo', link: '/apollo' },
|
||||||
|
{ icon: <FaQuestion size={16} />, label: 'About', link: '/about' }
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function NavBar() {
|
||||||
|
const authenticated = useAuthenticated()
|
||||||
|
const { signOut } = useSignOut()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const links = data.map((link) => <MenuItem {...link} key={link.label} />)
|
||||||
|
return (
|
||||||
|
<Navbar width={{ sm: 300, lg: 400, base: 100 }}>
|
||||||
|
<Navbar.Section grow mt="md">
|
||||||
|
{links}
|
||||||
|
{authenticated && (
|
||||||
|
<MenuItem
|
||||||
|
icon={<FaSignOutAlt />}
|
||||||
|
label="Sign Out"
|
||||||
|
action={async () => {
|
||||||
|
await signOut()
|
||||||
|
navigate('/', { replace: true })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Navbar.Section>
|
||||||
|
</Navbar>
|
||||||
|
)
|
||||||
|
}
|
||||||
23
examples/react-apollo/src/components/OauthLinks.tsx
Normal file
23
examples/react-apollo/src/components/OauthLinks.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { FaFacebook, FaGithub, FaGoogle } from 'react-icons/fa'
|
||||||
|
|
||||||
|
import { useProviderLink } from '@nhost/react'
|
||||||
|
|
||||||
|
import AuthLink from './AuthLink'
|
||||||
|
|
||||||
|
export default function OauthLinks() {
|
||||||
|
const { github, google, facebook } = useProviderLink()
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AuthLink icon={<FaGithub />} link={github} color="#333">
|
||||||
|
Continue with GitHub
|
||||||
|
</AuthLink>
|
||||||
|
<AuthLink icon={<FaGoogle />} link={google} color="#de5246">
|
||||||
|
Continue with Google
|
||||||
|
</AuthLink>
|
||||||
|
<AuthLink icon={<FaFacebook />} link={facebook} color="#3b5998">
|
||||||
|
Continue with Facebook
|
||||||
|
</AuthLink>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
import { Button, Modal, SimpleGrid, TextInput } from '@mantine/core'
|
||||||
|
import { showNotification } from '@mantine/notifications'
|
||||||
|
import { useSignInEmailPasswordless } from '@nhost/react'
|
||||||
|
|
||||||
|
export const SignUpPasswordlessForm: React.FC = () => {
|
||||||
|
const { signInEmailPasswordless } = useSignInEmailPasswordless({ redirectTo: '/profile' })
|
||||||
|
const [emailVerificationToggle, setEmailVerificationToggle] = useState(false)
|
||||||
|
|
||||||
|
const [email, setEmail] = useState('')
|
||||||
|
const signIn = async () => {
|
||||||
|
const result = await signInEmailPasswordless(email)
|
||||||
|
if (result.isError) {
|
||||||
|
showNotification({
|
||||||
|
color: 'red',
|
||||||
|
title: 'Error',
|
||||||
|
message: result.error?.message || null
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setEmailVerificationToggle(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<SimpleGrid cols={1} spacing={6}>
|
||||||
|
<Modal
|
||||||
|
title="Verification email sent"
|
||||||
|
centered
|
||||||
|
opened={emailVerificationToggle}
|
||||||
|
onClose={() => {
|
||||||
|
setEmailVerificationToggle(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
A verification email has been sent. Please check your inbox and follow the link to complete
|
||||||
|
authentication. This page with automatically redirect to the authenticated home page once
|
||||||
|
the email has been verified.
|
||||||
|
</Modal>
|
||||||
|
<TextInput
|
||||||
|
type="email"
|
||||||
|
placeholder="Email Address"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Button fullWidth onClick={signIn}>
|
||||||
|
Continue with email
|
||||||
|
</Button>
|
||||||
|
</SimpleGrid>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SignUpPasswordlessForm
|
||||||
@@ -3,7 +3,7 @@ import { Navigate, useLocation } from 'react-router-dom'
|
|||||||
|
|
||||||
import { useAuthenticationStatus } from '@nhost/react'
|
import { useAuthenticationStatus } from '@nhost/react'
|
||||||
|
|
||||||
export const AuthGate: React.FC = ({ children }) => {
|
export const AuthGate: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const { isLoading, isAuthenticated } = useAuthenticationStatus()
|
const { isLoading, isAuthenticated } = useAuthenticationStatus()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@@ -17,7 +17,7 @@ export const AuthGate: React.FC = ({ children }) => {
|
|||||||
return <div>{children}</div>
|
return <div>{children}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PublicGate: React.FC = ({ children }) => {
|
export const PublicGate: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const { isLoading, isAuthenticated } = useAuthenticationStatus()
|
const { isLoading, isAuthenticated } = useAuthenticationStatus()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
|
||||||
import { useNavigate } from 'react-router-dom'
|
|
||||||
import { Button, Input, Message } from 'rsuite'
|
|
||||||
|
|
||||||
import { useSignInEmailPasswordless } from '@nhost/react'
|
|
||||||
|
|
||||||
export const EmailPasswordlessForm: React.FC = () => {
|
|
||||||
const [email, setEmail] = useState('')
|
|
||||||
const navigate = useNavigate()
|
|
||||||
const { signInEmailPasswordless, isError, isSuccess, error } = useSignInEmailPasswordless({
|
|
||||||
redirectTo: '/profile'
|
|
||||||
})
|
|
||||||
const [showError, setShowError] = useState(true)
|
|
||||||
useEffect(() => {
|
|
||||||
setShowError(false)
|
|
||||||
}, [email])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isSuccess) {
|
|
||||||
navigate('/sign-in/verification-email-sent')
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [isSuccess])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Input
|
|
||||||
placeholder="Email Address"
|
|
||||||
value={email}
|
|
||||||
onChange={setEmail}
|
|
||||||
size="lg"
|
|
||||||
autoFocus
|
|
||||||
style={{ marginBottom: '0.5em' }}
|
|
||||||
/>
|
|
||||||
{showError && isError && (
|
|
||||||
<Message showIcon type="error">
|
|
||||||
{error?.message}
|
|
||||||
</Message>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
block
|
|
||||||
appearance="primary"
|
|
||||||
style={{ marginTop: '0.5em' }}
|
|
||||||
onClick={() => {
|
|
||||||
setShowError(true)
|
|
||||||
signInEmailPasswordless(email)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Continue with email
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export * from './auth-gates'
|
|
||||||
export * from './email-passwordless-form'
|
|
||||||
export * from './oauth-links'
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
/* eslint-disable react/react-in-jsx-scope */
|
|
||||||
import { FaFacebook, FaGithub, FaGoogle } from 'react-icons/fa'
|
|
||||||
import { IconButton } from 'rsuite'
|
|
||||||
|
|
||||||
import { useAuthenticationStatus, useProviderLink } from '@nhost/react'
|
|
||||||
import { Icon } from '@rsuite/icons'
|
|
||||||
|
|
||||||
export const OAuthLinks: React.FC = () => {
|
|
||||||
// TODO show how to use options
|
|
||||||
const { github, google, facebook } = useProviderLink()
|
|
||||||
const { error } = useAuthenticationStatus()
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<IconButton
|
|
||||||
icon={<Icon as={FaGithub} style={{ backgroundColor: 'black' }} />}
|
|
||||||
as="a"
|
|
||||||
href={github}
|
|
||||||
block
|
|
||||||
appearance="primary"
|
|
||||||
style={{ backgroundColor: 'black' }}
|
|
||||||
>
|
|
||||||
Continue with GitHub
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
icon={<Icon as={FaGoogle} />}
|
|
||||||
as="a"
|
|
||||||
href={google}
|
|
||||||
block
|
|
||||||
appearance="primary"
|
|
||||||
color="red"
|
|
||||||
>
|
|
||||||
Continue with Google
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
icon={<Icon as={FaFacebook} />}
|
|
||||||
as="a"
|
|
||||||
href={facebook}
|
|
||||||
block
|
|
||||||
appearance="primary"
|
|
||||||
color="blue"
|
|
||||||
>
|
|
||||||
Continue with Facebook
|
|
||||||
</IconButton>
|
|
||||||
{error && <div>{error?.message}</div>}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -6,8 +6,6 @@ import { NhostClient, NhostReactProvider } from '@nhost/react'
|
|||||||
import { NhostApolloProvider } from '@nhost/react-apollo'
|
import { NhostApolloProvider } from '@nhost/react-apollo'
|
||||||
import { inspect } from '@xstate/inspect'
|
import { inspect } from '@xstate/inspect'
|
||||||
|
|
||||||
import 'rsuite/styles/index.less' // or 'rsuite/dist/rsuite.min.css'
|
|
||||||
|
|
||||||
import App from './App'
|
import App from './App'
|
||||||
|
|
||||||
const nhost = new NhostClient({
|
const nhost = new NhostClient({
|
||||||
|
|||||||
@@ -1,63 +1,55 @@
|
|||||||
/* eslint-disable react/react-in-jsx-scope */
|
/* eslint-disable react/react-in-jsx-scope */
|
||||||
import { useEffect, useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Button, FlexboxGrid, Input, Message, Notification, Panel, toaster } from 'rsuite'
|
|
||||||
|
|
||||||
import { useChangeEmail, useEmail } from '@nhost/react'
|
import { useChangeEmail, useUserEmail } from '@nhost/react'
|
||||||
|
import { Button, Card, Grid, TextInput, Title } from '@mantine/core'
|
||||||
|
import { showNotification } from '@mantine/notifications'
|
||||||
|
|
||||||
export const ChangeEmail: React.FC = () => {
|
export const ChangeEmail: React.FC = () => {
|
||||||
const [newEmail, setNewEmail] = useState('')
|
const [newEmail, setNewEmail] = useState('')
|
||||||
const email = useEmail()
|
const email = useUserEmail()
|
||||||
const { changeEmail, error, needsEmailVerification } = useChangeEmail({
|
const { changeEmail } = useChangeEmail({
|
||||||
redirectTo: '/profile'
|
redirectTo: '/profile'
|
||||||
})
|
})
|
||||||
const [errorMessage, setErrorMessage] = useState('')
|
|
||||||
|
|
||||||
useEffect(() => {
|
const change = async () => {
|
||||||
if (needsEmailVerification) {
|
if (newEmail && email === newEmail) {
|
||||||
toaster.push(
|
showNotification({
|
||||||
<Notification type="info" header="Info" closable>
|
title: 'Error',
|
||||||
An email has been sent to {newEmail}. Please check your inbox and follow the link to
|
message: 'You need to set a different email as the current one'
|
||||||
confirm the email change.
|
})
|
||||||
</Notification>
|
|
||||||
)
|
|
||||||
setNewEmail('')
|
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
const result = await changeEmail(newEmail)
|
||||||
}, [needsEmailVerification])
|
if (result.needsEmailVerification) {
|
||||||
|
showNotification({
|
||||||
// * Set error message from the registration hook errors
|
message: `An email has been sent to ${newEmail}. Please check your inbox and follow the link to confirm the email change.`
|
||||||
useEffect(() => {
|
})
|
||||||
setErrorMessage(error?.message || '')
|
}
|
||||||
}, [error])
|
if (result.error) {
|
||||||
// * Reset error message every time the email input changed
|
showNotification({
|
||||||
useEffect(() => {
|
color: 'red',
|
||||||
setErrorMessage('')
|
title: 'Error',
|
||||||
}, [newEmail])
|
message: result.error.message
|
||||||
// * Show an error message when passwords are different
|
})
|
||||||
useEffect(() => {
|
}
|
||||||
if (newEmail && email === newEmail)
|
}
|
||||||
setErrorMessage('You need to set a different email as the current one')
|
|
||||||
else setErrorMessage('')
|
|
||||||
}, [email, newEmail])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel header="Change email" bordered>
|
<Card shadow="sm" p="lg" m="sm">
|
||||||
<FlexboxGrid>
|
<Title>Change email</Title>
|
||||||
<FlexboxGrid.Item colspan={12}>
|
<Grid>
|
||||||
<Input value={newEmail} onChange={setNewEmail} placeholder="New email" />
|
<Grid.Col>
|
||||||
</FlexboxGrid.Item>
|
<TextInput
|
||||||
<FlexboxGrid.Item colspan={12}>
|
value={newEmail}
|
||||||
<Button onClick={() => changeEmail(email)} block appearance="primary">
|
onChange={(e) => setNewEmail(e.target.value)}
|
||||||
|
placeholder="New email"
|
||||||
|
/>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col>
|
||||||
|
<Button onClick={change} fullWidth>
|
||||||
Change
|
Change
|
||||||
</Button>
|
</Button>
|
||||||
</FlexboxGrid.Item>
|
</Grid.Col>
|
||||||
</FlexboxGrid>
|
</Grid>
|
||||||
|
</Card>
|
||||||
{errorMessage && (
|
|
||||||
<Message showIcon type="error">
|
|
||||||
{errorMessage}
|
|
||||||
</Message>
|
|
||||||
)}
|
|
||||||
</Panel>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +1,46 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useEffect, useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Button, FlexboxGrid, Input, Message, Notification, Panel, toaster } from 'rsuite'
|
|
||||||
|
|
||||||
import { useChangePassword } from '@nhost/react'
|
import { useChangePassword } from '@nhost/react'
|
||||||
|
import { Button, Card, Grid, PasswordInput, Title } from '@mantine/core'
|
||||||
|
import { showNotification } from '@mantine/notifications'
|
||||||
|
|
||||||
export const ChangePassword: React.FC = () => {
|
export const ChangePassword: React.FC = () => {
|
||||||
const [password, setPassword] = useState('')
|
const [password, setPassword] = useState('')
|
||||||
const { changePassword, isSuccess, error } = useChangePassword()
|
const { changePassword } = useChangePassword()
|
||||||
const [errorMessage, setErrorMessage] = useState('')
|
|
||||||
|
|
||||||
// * See https://github.com/rsuite/rsuite/issues/2336
|
const change = async () => {
|
||||||
useEffect(() => {
|
const result = await changePassword(password)
|
||||||
if (isSuccess) {
|
if (result.isSuccess) {
|
||||||
setPassword('')
|
showNotification({
|
||||||
toaster.push(
|
message: `Password changed successfully.`
|
||||||
<Notification type="info" header="Info" closable>
|
})
|
||||||
Password changed successfully.
|
|
||||||
</Notification>
|
|
||||||
)
|
|
||||||
setPassword('')
|
|
||||||
}
|
}
|
||||||
}, [isSuccess])
|
if (result.error) {
|
||||||
|
showNotification({
|
||||||
// * Set error message from the registration hook errors
|
color: 'red',
|
||||||
useEffect(() => {
|
title: 'Error',
|
||||||
setErrorMessage(error?.message || '')
|
message: result.error.message
|
||||||
}, [error])
|
})
|
||||||
// * Reset error message every time the password input changed
|
}
|
||||||
useEffect(() => {
|
}
|
||||||
setErrorMessage('')
|
|
||||||
}, [password])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel header="Change password" bordered>
|
<Card shadow="sm" p="lg" m="sm">
|
||||||
<FlexboxGrid>
|
<Title>Change password</Title>
|
||||||
<FlexboxGrid.Item colspan={12}>
|
<Grid>
|
||||||
<Input
|
<Grid.Col>
|
||||||
|
<PasswordInput
|
||||||
value={password}
|
value={password}
|
||||||
onChange={setPassword}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
type="password"
|
|
||||||
placeholder="New password"
|
placeholder="New password"
|
||||||
/>
|
/>
|
||||||
</FlexboxGrid.Item>
|
</Grid.Col>
|
||||||
<FlexboxGrid.Item colspan={12}>
|
<Grid.Col>
|
||||||
<Button onClick={() => changePassword(password)} block appearance="primary">
|
<Button onClick={change} fullWidth>
|
||||||
Change
|
Change
|
||||||
</Button>
|
</Button>
|
||||||
</FlexboxGrid.Item>
|
</Grid.Col>
|
||||||
</FlexboxGrid>
|
</Grid>
|
||||||
|
</Card>
|
||||||
{errorMessage && (
|
|
||||||
<Message showIcon type="error">
|
|
||||||
{errorMessage}
|
|
||||||
</Message>
|
|
||||||
)}
|
|
||||||
</Panel>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +1,34 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactJson from 'react-json-view'
|
|
||||||
import { Button, Col, Panel, Row } from 'rsuite'
|
|
||||||
|
|
||||||
import { useHasuraClaims, useNhostClient, useUserData } from '@nhost/react'
|
import { useHasuraClaims, useNhostClient, useUserData } from '@nhost/react'
|
||||||
|
|
||||||
import { ChangeEmail } from './change-email'
|
import { ChangeEmail } from './change-email'
|
||||||
import { ChangePassword } from './change-password'
|
import { ChangePassword } from './change-password'
|
||||||
import { Mfa } from './mfa'
|
import { Mfa } from './mfa'
|
||||||
|
import { Button, Card, Container, Title } from '@mantine/core'
|
||||||
|
import { Prism } from '@mantine/prism'
|
||||||
|
|
||||||
export const ProfilePage: React.FC = () => {
|
export const ProfilePage: React.FC = () => {
|
||||||
const claims = useHasuraClaims()
|
const claims = useHasuraClaims()
|
||||||
const userData = useUserData()
|
const userData = useUserData()
|
||||||
const nhost = useNhostClient()
|
const nhost = useNhostClient()
|
||||||
return (
|
return (
|
||||||
<Panel header="Profile page" bordered>
|
<Container>
|
||||||
<Row>
|
<Title>Profile page</Title>
|
||||||
<Col md={12} sm={24}>
|
<Mfa />
|
||||||
<Mfa />
|
<ChangeEmail />
|
||||||
</Col>
|
<ChangePassword />
|
||||||
<Col md={12} sm={24}>
|
<Card shadow="sm" p="lg" m="sm">
|
||||||
<ChangeEmail />
|
<Title>User information</Title>
|
||||||
</Col>
|
{userData && <Prism language="json">{JSON.stringify(userData, null, 2)}</Prism>}
|
||||||
<Col md={12} sm={24}>
|
</Card>
|
||||||
<ChangePassword />
|
<Card shadow="sm" p="lg" m="sm">
|
||||||
</Col>
|
<Title>Hasura JWT claims</Title>
|
||||||
<Col md={12} sm={24}>
|
<Button fullWidth onClick={() => nhost.auth.refreshSession()}>
|
||||||
<Panel header="User information" bordered>
|
Refresh session
|
||||||
{userData && (
|
</Button>
|
||||||
<ReactJson
|
{claims && <Prism language="json">{JSON.stringify(claims, null, 2)}</Prism>}
|
||||||
src={userData}
|
</Card>
|
||||||
displayDataTypes={false}
|
</Container>
|
||||||
displayObjectSize={false}
|
|
||||||
enableClipboard={false}
|
|
||||||
name={false}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Panel>
|
|
||||||
</Col>
|
|
||||||
<Col md={12} sm={24}>
|
|
||||||
<Panel header="Hasura JWT claims" bordered>
|
|
||||||
<Button block appearance="primary" onClick={() => nhost.auth.refreshSession()}>
|
|
||||||
Refresh session
|
|
||||||
</Button>
|
|
||||||
{claims && (
|
|
||||||
<ReactJson
|
|
||||||
src={claims}
|
|
||||||
displayDataTypes={false}
|
|
||||||
displayObjectSize={false}
|
|
||||||
enableClipboard={false}
|
|
||||||
name={false}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Panel>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Panel>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,45 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Button, Input, Panel } from 'rsuite'
|
|
||||||
|
|
||||||
import { useConfigMfa } from '@nhost/react'
|
import { useConfigMfa } from '@nhost/react'
|
||||||
|
import { Card, Button, TextInput, Title } from '@mantine/core'
|
||||||
|
import { showNotification } from '@mantine/notifications'
|
||||||
|
|
||||||
export const Mfa: React.FC = () => {
|
export const Mfa: React.FC = () => {
|
||||||
const [code, setCode] = useState('')
|
const [code, setCode] = useState('')
|
||||||
const { generateQrCode, activateMfa, isActivated, isGenerated, qrCodeDataUrl } = useConfigMfa()
|
const { generateQrCode, activateMfa, isActivated, isGenerated, qrCodeDataUrl } = useConfigMfa()
|
||||||
|
const generate = async () => {
|
||||||
|
const result = await generateQrCode()
|
||||||
|
if (result.error) {
|
||||||
|
showNotification({
|
||||||
|
color: 'red',
|
||||||
|
title: 'Error',
|
||||||
|
message: result.error.message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Panel header="Activate 2-step verification" bordered>
|
<Card shadow="sm" p="lg" m="sm">
|
||||||
|
<Title>Activate 2-step verification</Title>
|
||||||
{!isGenerated && (
|
{!isGenerated && (
|
||||||
<Button block appearance="primary" onClick={generateQrCode}>
|
<Button fullWidth onClick={generate}>
|
||||||
Generate
|
Generate
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{isGenerated && !isActivated && (
|
{isGenerated && !isActivated && (
|
||||||
<div>
|
<div>
|
||||||
<img alt="qrcode" src={qrCodeDataUrl} />
|
<img alt="qrcode" src={qrCodeDataUrl} />
|
||||||
<Input value={code} onChange={setCode} placeholder="Enter activation code" />
|
<TextInput
|
||||||
<Button block appearance="primary" onClick={() => activateMfa(code)}>
|
value={code}
|
||||||
|
onChange={(e) => setCode(e.target.value)}
|
||||||
|
placeholder="Enter activation code"
|
||||||
|
/>
|
||||||
|
<Button fullWidth onClick={() => activateMfa(code)}>
|
||||||
Activate
|
Activate
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isActivated && <div>MFA has been activated!!!</div>}
|
{isActivated && <div>MFA has been activated!!!</div>}
|
||||||
</Panel>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +1,95 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
/* eslint-disable react/react-in-jsx-scope */
|
||||||
import { NavLink } from 'react-router-dom'
|
import { useState } from 'react'
|
||||||
import { Button, Divider, Input, Message } from 'rsuite'
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
import { useSignInEmailPassword } from '@nhost/react'
|
import { useSignInEmailPassword } from '@nhost/react'
|
||||||
|
import { Button, Modal, TextInput } from '@mantine/core'
|
||||||
const Footer: React.FC = () => (
|
import AuthLink from '../components/AuthLink'
|
||||||
<div>
|
import { showNotification } from '@mantine/notifications'
|
||||||
<Divider />
|
|
||||||
<Button as={NavLink} to="/sign-in" block appearance="link">
|
|
||||||
← Other Login Options
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
export const EmailPassword: React.FC = () => {
|
export const EmailPassword: React.FC = () => {
|
||||||
const [email, setEmail] = useState('')
|
const [email, setEmail] = useState('')
|
||||||
const [password, setPassword] = useState('')
|
const [password, setPassword] = useState('')
|
||||||
const [otp, setOtp] = useState('')
|
const [otp, setOtp] = useState('')
|
||||||
const { signInEmailPassword, error, needsMfaOtp, sendMfaOtp } = useSignInEmailPassword(
|
const { signInEmailPassword, needsMfaOtp, sendMfaOtp } = useSignInEmailPassword()
|
||||||
email,
|
const navigate = useNavigate()
|
||||||
password,
|
|
||||||
otp
|
|
||||||
)
|
|
||||||
|
|
||||||
const [errorMessage, setErrorMessage] = useState('')
|
const [emailVerificationToggle, setEmailVerificationToggle] = useState(false)
|
||||||
// * Set error message from the authentication hook errors
|
|
||||||
useEffect(() => {
|
|
||||||
setErrorMessage(error?.message || '')
|
|
||||||
}, [error])
|
|
||||||
// * Reset error message every time the email or password input changed
|
|
||||||
useEffect(() => {
|
|
||||||
setErrorMessage('')
|
|
||||||
}, [email, password])
|
|
||||||
|
|
||||||
|
const signIn = async () => {
|
||||||
|
const result = await signInEmailPassword(email, password)
|
||||||
|
if (result.isError) {
|
||||||
|
showNotification({
|
||||||
|
color: 'red',
|
||||||
|
title: 'Error',
|
||||||
|
message: result.error?.message
|
||||||
|
})
|
||||||
|
} else if (result.needsEmailVerification) {
|
||||||
|
setEmailVerificationToggle(true)
|
||||||
|
} else if (!result.needsEmailVerification) {
|
||||||
|
navigate('/', { replace: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendOtp = async () => {
|
||||||
|
sendMfaOtp(otp)
|
||||||
|
console.log('TODO')
|
||||||
|
}
|
||||||
if (needsMfaOtp)
|
if (needsMfaOtp)
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<Input
|
<TextInput
|
||||||
value={otp}
|
value={otp}
|
||||||
onChange={setOtp}
|
onChange={(e) => setOtp(e.target.value)}
|
||||||
placeholder="One-time password"
|
placeholder="One-time password"
|
||||||
size="lg"
|
size="lg"
|
||||||
autoFocus
|
autoFocus
|
||||||
style={{ marginBottom: '0.5em' }}
|
style={{ marginBottom: '0.5em' }}
|
||||||
/>
|
/>
|
||||||
{errorMessage && (
|
<Button fullWidth onClick={sendOtp}>
|
||||||
<Message showIcon type="error">
|
|
||||||
{errorMessage}
|
|
||||||
</Message>
|
|
||||||
)}
|
|
||||||
<Button appearance="primary" onClick={sendMfaOtp} block>
|
|
||||||
Send 2-step verification code
|
Send 2-step verification code
|
||||||
</Button>
|
</Button>
|
||||||
<Footer />
|
</>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<Input
|
<Modal
|
||||||
|
title="Verification email sent"
|
||||||
|
transition="fade"
|
||||||
|
centered
|
||||||
|
transitionDuration={600}
|
||||||
|
opened={emailVerificationToggle}
|
||||||
|
onClose={() => {
|
||||||
|
setEmailVerificationToggle(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
A email has been sent to {email}. Please follow the link to verify your email address and
|
||||||
|
to complete your registration.
|
||||||
|
</Modal>
|
||||||
|
<TextInput
|
||||||
value={email}
|
value={email}
|
||||||
onChange={setEmail}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
placeholder="Email Address"
|
placeholder="Email Address"
|
||||||
size="lg"
|
size="lg"
|
||||||
autoFocus
|
autoFocus
|
||||||
style={{ marginBottom: '0.5em' }}
|
style={{ marginBottom: '0.5em' }}
|
||||||
/>
|
/>
|
||||||
<Input
|
<TextInput
|
||||||
value={password}
|
value={password}
|
||||||
onChange={setPassword}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
type="password"
|
type="password"
|
||||||
size="lg"
|
size="lg"
|
||||||
style={{ marginBottom: '0.5em' }}
|
style={{ marginBottom: '0.5em' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{errorMessage && (
|
<Button fullWidth onClick={signIn}>
|
||||||
<Message showIcon type="error">
|
|
||||||
{errorMessage}
|
|
||||||
</Message>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
appearance="primary"
|
|
||||||
onClick={async () => {
|
|
||||||
const result = await signInEmailPassword(email, password)
|
|
||||||
console.log(result)
|
|
||||||
}}
|
|
||||||
block
|
|
||||||
>
|
|
||||||
Sign in
|
Sign in
|
||||||
</Button>
|
</Button>
|
||||||
<Button as={NavLink} block to="/sign-in/forgot-password">
|
<AuthLink link="/sign-in/forgot-password" variant="white">
|
||||||
Forgot password?
|
Forgot password?
|
||||||
</Button>
|
</AuthLink>
|
||||||
<Footer />
|
</>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
|
import { Divider } from '@mantine/core'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { NavLink } from 'react-router-dom'
|
import AuthLink from '../components/AuthLink'
|
||||||
import { Button } from 'rsuite'
|
|
||||||
|
import EmailPasswordlessForm from '../components/SignUpServerlessForm'
|
||||||
|
|
||||||
import { EmailPasswordlessForm } from '../components/email-passwordless-form'
|
|
||||||
export const EmailPasswordless: React.FC = () => {
|
export const EmailPasswordless: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<EmailPasswordlessForm />
|
<EmailPasswordlessForm />
|
||||||
<Button as={NavLink} to="/sign-up" block appearance="link">
|
<Divider />
|
||||||
|
<AuthLink link="/sign-up" variant="white">
|
||||||
← Other Login Options
|
← Other Login Options
|
||||||
</Button>
|
</AuthLink>
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +1,43 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { NavLink } from 'react-router-dom'
|
|
||||||
import { Button, Divider, Input, Message, Notification, toaster } from 'rsuite'
|
|
||||||
|
|
||||||
import { useResetPassword } from '@nhost/react'
|
import { useResetPassword } from '@nhost/react'
|
||||||
|
import { showNotification } from '@mantine/notifications'
|
||||||
|
import { Button, Divider, TextInput } from '@mantine/core'
|
||||||
|
import AuthLink from '../components/AuthLink'
|
||||||
|
|
||||||
export const ForgotPassword: React.FC = () => {
|
export const ForgotPassword: React.FC = () => {
|
||||||
const [email, setEmail] = useState('')
|
const [email, setEmail] = useState('')
|
||||||
const { resetPassword, isSent, error } = useResetPassword({ redirectTo: '/profile' })
|
const { resetPassword } = useResetPassword({ redirectTo: '/profile' })
|
||||||
|
|
||||||
const [errorMessage, setErrorMessage] = useState('')
|
const reset = async () => {
|
||||||
// * Set error message from the authentication hook errors
|
const result = await resetPassword(email)
|
||||||
useEffect(() => {
|
if (result.isError) {
|
||||||
setErrorMessage(error?.message || '')
|
showNotification({
|
||||||
}, [error])
|
color: 'red',
|
||||||
// * Reset error message every time the email or password input changed
|
title: 'Error',
|
||||||
useEffect(() => {
|
message: result.error?.message
|
||||||
setErrorMessage('')
|
})
|
||||||
}, [email])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isSent) {
|
|
||||||
toaster.push(
|
|
||||||
<Notification type="info" header="Info" closable>
|
|
||||||
An email has been sent with a passwordless authentication link, so you will be able to
|
|
||||||
authenticate and change your password.
|
|
||||||
</Notification>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}, [isSent])
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<Input
|
<TextInput
|
||||||
value={email}
|
value={email}
|
||||||
onChange={setEmail}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
placeholder="Email Address"
|
placeholder="Email Address"
|
||||||
size="lg"
|
size="lg"
|
||||||
autoFocus
|
autoFocus
|
||||||
style={{ marginBottom: '0.5em' }}
|
style={{ marginBottom: '0.5em' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{errorMessage && (
|
<Button onClick={reset} fullWidth>
|
||||||
<Message showIcon type="error">
|
|
||||||
{errorMessage}
|
|
||||||
</Message>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button appearance="primary" onClick={() => resetPassword(email)} block>
|
|
||||||
Reset your password
|
Reset your password
|
||||||
</Button>
|
</Button>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Button as={NavLink} to="/sign-in/email-password" block appearance="link">
|
|
||||||
|
<AuthLink link="/sign-in/email-password" variant="white">
|
||||||
← Sign in with email + password
|
← Sign in with email + password
|
||||||
</Button>
|
</AuthLink>
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1,49 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { FaLock } from 'react-icons/fa'
|
import { FaLock } from 'react-icons/fa'
|
||||||
import { Link, NavLink, Route, Routes } from 'react-router-dom'
|
import { Link, Route, Routes } from 'react-router-dom'
|
||||||
import { Button, Divider, FlexboxGrid, IconButton, Panel } from 'rsuite'
|
|
||||||
|
|
||||||
import { Icon } from '@rsuite/icons'
|
import OAuthLinks from '../components/OauthLinks'
|
||||||
|
|
||||||
import { OAuthLinks } from '../components'
|
|
||||||
import { VerificationEmailSent } from '../verification-email-sent'
|
|
||||||
|
|
||||||
import { EmailPassword } from './email-password'
|
import { EmailPassword } from './email-password'
|
||||||
import { EmailPasswordless } from './email-passwordless'
|
import { EmailPasswordless } from './email-passwordless'
|
||||||
|
import AuthLayout from '../components/AuthLayout'
|
||||||
|
import { Center, Text, Anchor, Divider } from '@mantine/core'
|
||||||
|
import AuthLink from '../components/AuthLink'
|
||||||
import { ForgotPassword } from './forgot-password'
|
import { ForgotPassword } from './forgot-password'
|
||||||
// import { useSignInAnonymous } from '@nhost/react'
|
|
||||||
|
|
||||||
const Index: React.FC = () => (
|
const Index: React.FC = () => (
|
||||||
<div>
|
<>
|
||||||
<OAuthLinks />
|
<OAuthLinks />
|
||||||
<Divider />
|
<Divider my="sm" />
|
||||||
<IconButton
|
<AuthLink icon={<FaLock />} variant="outline" link="/sign-in/email-passwordless">
|
||||||
block
|
|
||||||
icon={<Icon as={FaLock} />}
|
|
||||||
appearance="ghost"
|
|
||||||
as={NavLink}
|
|
||||||
to="/sign-in/email-passwordless"
|
|
||||||
>
|
|
||||||
Continue with passwordless email
|
Continue with passwordless email
|
||||||
</IconButton>
|
</AuthLink>
|
||||||
<Button as={NavLink} to="/sign-in/email-password" block appearance="link">
|
<AuthLink variant="subtle" link="/sign-in/email-password">
|
||||||
Continue with email + password
|
Continue with email + password
|
||||||
</Button>
|
</AuthLink>
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
export const SignInPage: React.FC = () => {
|
export const SignInPage: React.FC = () => {
|
||||||
// const { signIn } = useSignInAnonymous()
|
|
||||||
return (
|
return (
|
||||||
<div style={{ textAlign: 'center' }}>
|
<AuthLayout
|
||||||
<FlexboxGrid justify="center">
|
title="Log in to the Application"
|
||||||
<FlexboxGrid.Item colspan={12}>
|
footer={
|
||||||
<Panel header={<h2>Log in to the Application</h2>} bordered>
|
<Center>
|
||||||
<Routes>
|
<Text>
|
||||||
<Route path="/" element={<Index />} />
|
Don‘t have an account?{' '}
|
||||||
<Route path="/email-passwordless" element={<EmailPasswordless />} />
|
<Anchor component={Link} to="/sign-up">
|
||||||
<Route path="/email-password" element={<EmailPassword />} />
|
Sign up
|
||||||
<Route path="/verification-email-sent" element={<VerificationEmailSent />} />
|
</Anchor>
|
||||||
<Route path="/forgot-password" element={<ForgotPassword />} />
|
</Text>
|
||||||
</Routes>
|
</Center>
|
||||||
</Panel>
|
}
|
||||||
</FlexboxGrid.Item>
|
>
|
||||||
</FlexboxGrid>
|
<Routes>
|
||||||
<Divider />
|
<Route path="/" element={<Index />} />
|
||||||
Don‘t have an account? <Link to="/sign-up">Sign up</Link>
|
<Route path="/email-password" element={<EmailPassword />} />
|
||||||
{/* or{' '}
|
<Route path="/forgot-password" element={<ForgotPassword />} />
|
||||||
<a href="#" onClick={signIn}>
|
<Route path="/email-passwordless" element={<EmailPasswordless />} />
|
||||||
enter the app anonymously
|
</Routes>
|
||||||
</a> */}
|
</AuthLayout>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,105 +1,95 @@
|
|||||||
/* eslint-disable react/react-in-jsx-scope */
|
/* eslint-disable react/react-in-jsx-scope */
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { NavLink, useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { Button, Input, Message } from 'rsuite'
|
|
||||||
|
|
||||||
import { useSignUpEmailPassword } from '@nhost/react'
|
import { useSignUpEmailPassword } from '@nhost/react'
|
||||||
|
import { Button, Divider, Modal, PasswordInput, SimpleGrid, TextInput } from '@mantine/core'
|
||||||
|
import AuthLink from '../components/AuthLink'
|
||||||
|
import { showNotification } from '@mantine/notifications'
|
||||||
|
|
||||||
export const EmailPassword: React.FC = () => {
|
export const EmailPassword: React.FC = () => {
|
||||||
const [email, setEmail] = useState('')
|
const [email, setEmail] = useState('')
|
||||||
const [password, setPassword] = useState('')
|
|
||||||
const [firstName, setFirstName] = useState('')
|
const [firstName, setFirstName] = useState('')
|
||||||
const [lastName, setLastName] = useState('')
|
const [lastName, setLastName] = useState('')
|
||||||
|
const [password, setPassword] = useState('')
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState('')
|
||||||
|
const [emailVerificationToggle, setEmailVerificationToggle] = useState(false)
|
||||||
|
const differentPassword = useMemo(
|
||||||
|
() => password && password !== confirmPassword && 'Should match the given password',
|
||||||
|
[password, confirmPassword]
|
||||||
|
)
|
||||||
const options = useMemo(
|
const options = useMemo(
|
||||||
() => ({ displayName: `${firstName} ${lastName}`, metadata: { firstName, lastName } }),
|
() => ({ displayName: `${firstName} ${lastName}`, metadata: { firstName, lastName } }),
|
||||||
[firstName, lastName]
|
[firstName, lastName]
|
||||||
)
|
)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [confirmPassword, setConfirmPassword] = useState('')
|
const { signUpEmailPassword } = useSignUpEmailPassword(options)
|
||||||
const { signUpEmailPassword, error, needsEmailVerification, isSuccess } =
|
|
||||||
useSignUpEmailPassword(options)
|
|
||||||
const [errorMessage, setErrorMessage] = useState('')
|
|
||||||
useEffect(() => {
|
|
||||||
if (needsEmailVerification) navigate('/sign-up/verification-email-sent')
|
|
||||||
else if (isSuccess) navigate('/')
|
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
const signUp = async () => {
|
||||||
}, [needsEmailVerification, isSuccess])
|
const result = await signUpEmailPassword(email, password, { metadata: { firstName, lastName } })
|
||||||
|
if (result.isError) {
|
||||||
// * Set error message from the registration hook errors
|
showNotification({
|
||||||
useEffect(() => {
|
color: 'red',
|
||||||
setErrorMessage(error?.message || '')
|
title: 'Error',
|
||||||
}, [error])
|
message: result.error?.message
|
||||||
// * Reset error message every time the email or password input changed
|
})
|
||||||
useEffect(() => {
|
} else if (result.needsEmailVerification) {
|
||||||
setErrorMessage('')
|
setEmailVerificationToggle(true)
|
||||||
}, [email, password])
|
} else {
|
||||||
// * Show an error message when passwords are different
|
navigate('/', { replace: true })
|
||||||
useEffect(() => {
|
}
|
||||||
if (password !== confirmPassword) setErrorMessage('Both passwords must be the same')
|
}
|
||||||
else setErrorMessage('')
|
|
||||||
}, [password, confirmPassword])
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<Input
|
<Modal
|
||||||
value={firstName}
|
title="Verification email sent"
|
||||||
onChange={setFirstName}
|
transition="fade"
|
||||||
placeholder="First name"
|
centered
|
||||||
size="lg"
|
transitionDuration={600}
|
||||||
autoFocus
|
opened={emailVerificationToggle}
|
||||||
style={{ marginBottom: '0.5em' }}
|
onClose={() => {
|
||||||
/>
|
setEmailVerificationToggle(false)
|
||||||
<Input
|
|
||||||
value={lastName}
|
|
||||||
onChange={setLastName}
|
|
||||||
placeholder="Last name"
|
|
||||||
size="lg"
|
|
||||||
style={{ marginBottom: '0.5em' }}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
value={email}
|
|
||||||
onChange={setEmail}
|
|
||||||
placeholder="Email Address"
|
|
||||||
size="lg"
|
|
||||||
style={{ marginBottom: '0.5em' }}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
value={password}
|
|
||||||
onChange={setPassword}
|
|
||||||
placeholder="Password"
|
|
||||||
type="password"
|
|
||||||
size="lg"
|
|
||||||
style={{ marginBottom: '0.5em' }}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
value={confirmPassword}
|
|
||||||
onChange={setConfirmPassword}
|
|
||||||
placeholder="Confirm Password"
|
|
||||||
type="password"
|
|
||||||
size="lg"
|
|
||||||
style={{ marginBottom: '0.5em' }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{errorMessage && (
|
|
||||||
<Message showIcon type="error">
|
|
||||||
{errorMessage}
|
|
||||||
</Message>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
appearance="primary"
|
|
||||||
onClick={async () => {
|
|
||||||
setErrorMessage('')
|
|
||||||
const result = await signUpEmailPassword(email, password)
|
|
||||||
console.log(result)
|
|
||||||
}}
|
}}
|
||||||
block
|
|
||||||
>
|
>
|
||||||
Sign up
|
A email has been sent to {email}. Please follow the link to verify your email address and to
|
||||||
</Button>
|
complete your registration.
|
||||||
<Button as={NavLink} to="/sign-up" block appearance="link">
|
</Modal>
|
||||||
|
<SimpleGrid cols={1} spacing={6}>
|
||||||
|
<TextInput
|
||||||
|
placeholder="First name"
|
||||||
|
value={firstName}
|
||||||
|
onChange={(e) => setFirstName(e.target.value)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Last name"
|
||||||
|
value={lastName}
|
||||||
|
onChange={(e) => setLastName(e.target.value)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
type="email"
|
||||||
|
placeholder="Email Address"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
/>
|
||||||
|
<PasswordInput
|
||||||
|
placeholder="Password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
<PasswordInput
|
||||||
|
placeholder="Confirm Password"
|
||||||
|
error={differentPassword}
|
||||||
|
value={confirmPassword}
|
||||||
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Button fullWidth onClick={signUp}>
|
||||||
|
Continue with email + password
|
||||||
|
</Button>
|
||||||
|
</SimpleGrid>
|
||||||
|
<Divider />
|
||||||
|
<AuthLink link="/sign-up" variant="white">
|
||||||
← Other Registration Options
|
← Other Registration Options
|
||||||
</Button>
|
</AuthLink>
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
|
import { Divider } from '@mantine/core'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { NavLink } from 'react-router-dom'
|
import AuthLink from '../components/AuthLink'
|
||||||
import { Button } from 'rsuite'
|
|
||||||
|
import EmailPasswordlessForm from '../components/SignUpServerlessForm'
|
||||||
|
|
||||||
import { EmailPasswordlessForm } from '../components/email-passwordless-form'
|
|
||||||
export const EmailPasswordless: React.FC = () => {
|
export const EmailPasswordless: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<EmailPasswordlessForm />
|
<EmailPasswordlessForm />
|
||||||
<Button as={NavLink} to="/sign-up" block appearance="link">
|
<Divider />
|
||||||
|
<AuthLink link="/sign-up" variant="white">
|
||||||
← Other Registration Options
|
← Other Registration Options
|
||||||
</Button>
|
</AuthLink>
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,52 +1,47 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { FaLock } from 'react-icons/fa'
|
import { FaLock } from 'react-icons/fa'
|
||||||
import { Link, NavLink, Route, Routes } from 'react-router-dom'
|
import { Link, Route, Routes } from 'react-router-dom'
|
||||||
import { Button, Divider, FlexboxGrid, IconButton, Panel } from 'rsuite'
|
|
||||||
|
|
||||||
import { Icon } from '@rsuite/icons'
|
import OAuthLinks from '../components/OauthLinks'
|
||||||
|
|
||||||
import { OAuthLinks } from '../components'
|
|
||||||
import { VerificationEmailSent } from '../verification-email-sent'
|
|
||||||
|
|
||||||
import { EmailPassword } from './email-password'
|
import { EmailPassword } from './email-password'
|
||||||
import { EmailPasswordless } from './email-passwordless'
|
import { EmailPasswordless } from './email-passwordless'
|
||||||
|
import AuthLayout from '../components/AuthLayout'
|
||||||
|
import { Center, Text, Anchor, Divider } from '@mantine/core'
|
||||||
|
import AuthLink from '../components/AuthLink'
|
||||||
|
|
||||||
const Index: React.FC = () => (
|
const Index: React.FC = () => (
|
||||||
<div>
|
<>
|
||||||
<OAuthLinks />
|
<OAuthLinks />
|
||||||
<Divider />
|
<Divider my="sm" />
|
||||||
<IconButton
|
<AuthLink icon={<FaLock />} variant="outline" link="/sign-up/email-passwordless">
|
||||||
block
|
|
||||||
icon={<Icon as={FaLock} />}
|
|
||||||
appearance="ghost"
|
|
||||||
as={NavLink}
|
|
||||||
to="/sign-up/email-passwordless"
|
|
||||||
>
|
|
||||||
Continue with passwordless email
|
Continue with passwordless email
|
||||||
</IconButton>
|
</AuthLink>
|
||||||
<Button as={NavLink} to="/sign-up/email-password" block appearance="link">
|
<AuthLink variant="subtle" link="/sign-up/email-password">
|
||||||
Continue with email + password
|
Continue with email + password
|
||||||
</Button>
|
</AuthLink>
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
export const SignUpPage: React.FC = () => {
|
export const SignUpPage: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div style={{ textAlign: 'center' }}>
|
<AuthLayout
|
||||||
<FlexboxGrid justify="center">
|
title="Sign up"
|
||||||
<FlexboxGrid.Item colspan={12}>
|
footer={
|
||||||
<Panel header={<h2>Sign up</h2>} bordered>
|
<Center>
|
||||||
<Routes>
|
<Text>
|
||||||
<Route path="/" element={<Index />} />
|
Already have an account?{' '}
|
||||||
<Route path="/email-password" element={<EmailPassword />} />
|
<Anchor component={Link} to="/sign-in">
|
||||||
<Route path="/email-passwordless" element={<EmailPasswordless />} />
|
Log in
|
||||||
<Route path="/verification-email-sent" element={<VerificationEmailSent />} />
|
</Anchor>
|
||||||
</Routes>
|
</Text>
|
||||||
</Panel>
|
</Center>
|
||||||
</FlexboxGrid.Item>
|
}
|
||||||
</FlexboxGrid>
|
>
|
||||||
<Divider />
|
<Routes>
|
||||||
Already have an account? <Link to="/sign-in">Log in</Link>
|
<Route path="/" element={<Index />} />
|
||||||
</div>
|
<Route path="/email-password" element={<EmailPassword />} />
|
||||||
|
<Route path="/email-passwordless" element={<EmailPasswordless />} />
|
||||||
|
</Routes>
|
||||||
|
</AuthLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import React, { useEffect } from 'react'
|
|
||||||
import { useNavigate } from 'react-router-dom'
|
|
||||||
|
|
||||||
import { useAuthenticated } from '@nhost/react'
|
|
||||||
|
|
||||||
export const VerificationEmailSent: React.FC = () => {
|
|
||||||
const isAuthenticated = useAuthenticated()
|
|
||||||
const navigate = useNavigate()
|
|
||||||
useEffect(() => {
|
|
||||||
if (isAuthenticated) navigate('/')
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [isAuthenticated])
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
A verification email has been sent. Please check your inbox and follow the link to complete
|
|
||||||
authentication. This page with automatically redirect to the authenticated home page once the
|
|
||||||
email has been verified.
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,20 @@
|
|||||||
# @nhost/apollo
|
# @nhost/apollo
|
||||||
|
|
||||||
|
## 0.5.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [65a3061]
|
||||||
|
- @nhost/core@0.5.2
|
||||||
|
|
||||||
|
## 0.5.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [58fa2a2]
|
||||||
|
- Updated dependencies [58fa2a2]
|
||||||
|
- @nhost/core@0.5.1
|
||||||
|
|
||||||
## 0.5.0
|
## 0.5.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/apollo",
|
"name": "@nhost/apollo",
|
||||||
"version": "0.5.0",
|
"version": "0.5.2",
|
||||||
"description": "Nhost Apollo Client library",
|
"description": "Nhost Apollo Client library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
# @nhost/core
|
# @nhost/core
|
||||||
|
|
||||||
|
## 0.5.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 65a3061: correct cookie storage type
|
||||||
|
|
||||||
|
## 0.5.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 58fa2a2: Improve loading status
|
||||||
|
The `loading` status indicates the authentication is not yet known to the client when it starts. Once the client is ready, the authentication status is either signed in, or signed out.
|
||||||
|
When the user was trying to authenticate, the `loading` status was set to `true` until the result of the authentication was known.
|
||||||
|
The client now only return `loading: true` on startup, and in no other cases.
|
||||||
|
- 58fa2a2: Look for a valid refresh token both the URL and local storage
|
||||||
|
When auto-signin was activated, the client was not taking into account the refresh token in the URL if a token was already stored locally.
|
||||||
|
The user was then not able to authenticate from a link when the refresh token stored locally was invalid or expired.
|
||||||
|
When auto-signin is activated, the client now checks and tries tokens from both the URL and the local storage, starting with the URL.
|
||||||
|
|
||||||
## 0.5.0
|
## 0.5.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/core",
|
"name": "@nhost/core",
|
||||||
"version": "0.5.0",
|
"version": "0.5.2",
|
||||||
"description": "Nhost core client library",
|
"description": "Nhost core client library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -75,38 +75,31 @@ export const createAuthMachine = ({
|
|||||||
type: 'parallel',
|
type: 'parallel',
|
||||||
states: {
|
states: {
|
||||||
authentication: {
|
authentication: {
|
||||||
initial: 'importingRefreshToken',
|
initial: 'starting',
|
||||||
on: {
|
on: {
|
||||||
SESSION_UPDATE: [
|
SESSION_UPDATE: [
|
||||||
{
|
{
|
||||||
cond: 'hasSession',
|
cond: 'hasSession',
|
||||||
actions: ['saveSession', 'persist', 'resetTimer', 'reportTokenChanged'],
|
actions: ['saveSession', 'resetTimer', 'reportTokenChanged'],
|
||||||
target: '.signedIn'
|
target: '.signedIn'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
states: {
|
states: {
|
||||||
importingRefreshToken: {
|
starting: {
|
||||||
|
tags: ['loading'],
|
||||||
always: { cond: 'isSignedIn', target: 'signedIn' },
|
always: { cond: 'isSignedIn', target: 'signedIn' },
|
||||||
invoke: {
|
invoke: {
|
||||||
id: 'importRefreshToken',
|
id: 'importRefreshToken',
|
||||||
src: 'importRefreshToken',
|
src: 'importRefreshToken',
|
||||||
onDone: { actions: 'saveRefreshToken', target: 'starting' },
|
onDone: {
|
||||||
|
actions: ['saveSession', 'reportTokenChanged'],
|
||||||
|
target: 'signedIn'
|
||||||
|
},
|
||||||
onError: { actions: ['saveAuthenticationError'], target: 'signedOut' }
|
onError: { actions: ['saveAuthenticationError'], target: 'signedOut' }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
starting: {
|
|
||||||
always: [
|
|
||||||
{
|
|
||||||
cond: 'hasRefreshTokenWithoutSession',
|
|
||||||
target: 'authenticating.token'
|
|
||||||
},
|
|
||||||
{ cond: 'hasAuthenticationError', target: 'signedOut.failed' },
|
|
||||||
'signedOut'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
signedOut: {
|
signedOut: {
|
||||||
tags: ['ready'],
|
|
||||||
initial: 'noErrors',
|
initial: 'noErrors',
|
||||||
entry: 'reportSignedOut',
|
entry: 'reportSignedOut',
|
||||||
states: {
|
states: {
|
||||||
@@ -236,7 +229,7 @@ export const createAuthMachine = ({
|
|||||||
src: 'signInPasswordlessSmsOtp',
|
src: 'signInPasswordlessSmsOtp',
|
||||||
id: 'authenticatePasswordlessSmsOtp',
|
id: 'authenticatePasswordlessSmsOtp',
|
||||||
onDone: {
|
onDone: {
|
||||||
actions: ['saveSession', 'persist', 'reportTokenChanged'],
|
actions: ['saveSession', 'reportTokenChanged'],
|
||||||
target: '#nhost.authentication.signedIn'
|
target: '#nhost.authentication.signedIn'
|
||||||
},
|
},
|
||||||
onError: {
|
onError: {
|
||||||
@@ -256,7 +249,7 @@ export const createAuthMachine = ({
|
|||||||
target: '#nhost.authentication.signedOut.needsMfa'
|
target: '#nhost.authentication.signedOut.needsMfa'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
actions: ['saveSession', 'persist', 'reportTokenChanged'],
|
actions: ['saveSession', 'reportTokenChanged'],
|
||||||
target: '#nhost.authentication.signedIn'
|
target: '#nhost.authentication.signedIn'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -272,26 +265,12 @@ export const createAuthMachine = ({
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
token: {
|
|
||||||
invoke: {
|
|
||||||
src: 'refreshToken',
|
|
||||||
id: 'signInToken',
|
|
||||||
onDone: {
|
|
||||||
actions: ['saveSession', 'persist', 'reportTokenChanged', 'broadcastToken'],
|
|
||||||
target: '#nhost.authentication.signedIn'
|
|
||||||
},
|
|
||||||
onError: {
|
|
||||||
actions: 'saveAuthenticationError',
|
|
||||||
target: '#nhost.authentication.signedOut.failed.server'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
anonymous: {
|
anonymous: {
|
||||||
invoke: {
|
invoke: {
|
||||||
src: 'signInAnonymous',
|
src: 'signInAnonymous',
|
||||||
id: 'authenticateAnonymously',
|
id: 'authenticateAnonymously',
|
||||||
onDone: {
|
onDone: {
|
||||||
actions: ['saveSession', 'persist', 'reportTokenChanged'],
|
actions: ['saveSession', 'reportTokenChanged'],
|
||||||
target: '#nhost.authentication.signedIn'
|
target: '#nhost.authentication.signedIn'
|
||||||
},
|
},
|
||||||
onError: {
|
onError: {
|
||||||
@@ -307,7 +286,7 @@ export const createAuthMachine = ({
|
|||||||
src: 'signInMfaTotp',
|
src: 'signInMfaTotp',
|
||||||
id: 'signInMfaTotp',
|
id: 'signInMfaTotp',
|
||||||
onDone: {
|
onDone: {
|
||||||
actions: ['saveSession', 'persist', 'reportTokenChanged'],
|
actions: ['saveSession', 'reportTokenChanged'],
|
||||||
target: '#nhost.authentication.signedIn'
|
target: '#nhost.authentication.signedIn'
|
||||||
},
|
},
|
||||||
onError: {
|
onError: {
|
||||||
@@ -329,7 +308,7 @@ export const createAuthMachine = ({
|
|||||||
{
|
{
|
||||||
cond: 'hasSession',
|
cond: 'hasSession',
|
||||||
target: '#nhost.authentication.signedIn',
|
target: '#nhost.authentication.signedIn',
|
||||||
actions: ['saveSession', 'persist', 'reportTokenChanged']
|
actions: ['saveSession', 'reportTokenChanged']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: '#nhost.authentication.signedOut.needsEmailVerification'
|
target: '#nhost.authentication.signedOut.needsEmailVerification'
|
||||||
@@ -347,11 +326,9 @@ export const createAuthMachine = ({
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
signedIn: {
|
signedIn: {
|
||||||
tags: ['ready'],
|
|
||||||
type: 'parallel',
|
type: 'parallel',
|
||||||
entry: ['reportSignedIn', 'cleanUrl'],
|
entry: ['reportSignedIn', 'cleanUrl', 'broadcastToken'],
|
||||||
on: {
|
on: {
|
||||||
SIGNOUT: '#nhost.authentication.signedOut.signingOut',
|
SIGNOUT: '#nhost.authentication.signedOut.signingOut',
|
||||||
DEANONYMIZE: {
|
DEANONYMIZE: {
|
||||||
@@ -401,12 +378,7 @@ export const createAuthMachine = ({
|
|||||||
src: 'refreshToken',
|
src: 'refreshToken',
|
||||||
id: 'refreshToken',
|
id: 'refreshToken',
|
||||||
onDone: {
|
onDone: {
|
||||||
actions: [
|
actions: ['saveSession', 'resetTimer', 'reportTokenChanged'],
|
||||||
'saveSession',
|
|
||||||
'persist',
|
|
||||||
'resetTimer',
|
|
||||||
'reportTokenChanged'
|
|
||||||
],
|
|
||||||
target: 'pending'
|
target: 'pending'
|
||||||
},
|
},
|
||||||
onError: [
|
onError: [
|
||||||
@@ -455,7 +427,7 @@ export const createAuthMachine = ({
|
|||||||
src: 'refreshToken',
|
src: 'refreshToken',
|
||||||
id: 'authenticateWithToken',
|
id: 'authenticateWithToken',
|
||||||
onDone: {
|
onDone: {
|
||||||
actions: ['saveSession', 'persist', 'reportTokenChanged'],
|
actions: ['saveSession', 'reportTokenChanged'],
|
||||||
target: ['#nhost.authentication.signedIn', 'idle.noErrors']
|
target: ['#nhost.authentication.signedIn', 'idle.noErrors']
|
||||||
},
|
},
|
||||||
onError: [
|
onError: [
|
||||||
@@ -484,13 +456,28 @@ export const createAuthMachine = ({
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
// * Save session in the context, and persist the refresh token and the jwt expiration outside of the machine
|
||||||
saveSession: assign({
|
saveSession: assign({
|
||||||
user: (_, e: any) => e.data?.session?.user,
|
user: (_, { data }: any) => data?.session?.user,
|
||||||
accessToken: (_, e) => ({
|
accessToken: (_, { data }: any) => {
|
||||||
value: e.data?.session?.accessToken,
|
if (data.session.accessTokenExpiresIn) {
|
||||||
expiresAt: new Date(Date.now() + e.data?.session?.accessTokenExpiresIn * 1_000)
|
const nextRefresh = new Date(
|
||||||
}),
|
Date.now() + data.session.accessTokenExpiresIn * 1_000
|
||||||
refreshToken: (_, e) => ({ value: e.data?.session?.refreshToken })
|
).toISOString()
|
||||||
|
storageSetter(NHOST_JWT_EXPIRES_AT_KEY, nextRefresh)
|
||||||
|
} else {
|
||||||
|
storageSetter(NHOST_JWT_EXPIRES_AT_KEY, null)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
value: data?.session?.accessToken,
|
||||||
|
expiresAt: new Date(Date.now() + data?.session?.accessTokenExpiresIn * 1_000)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
refreshToken: (_, { data }: any) => {
|
||||||
|
storageSetter(NHOST_REFRESH_TOKEN_KEY, data.session.refreshToken)
|
||||||
|
|
||||||
|
return { value: data?.session?.refreshToken }
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
saveMfaTicket: assign({
|
saveMfaTicket: assign({
|
||||||
mfa: (_, e: any) => e.data?.mfa ?? null
|
mfa: (_, e: any) => e.data?.mfa ?? null
|
||||||
@@ -543,22 +530,7 @@ export const createAuthMachine = ({
|
|||||||
saveNoMfaTicketError: assign({
|
saveNoMfaTicketError: assign({
|
||||||
errors: ({ errors }) => ({ ...errors, registration: NO_MFA_TICKET_ERROR })
|
errors: ({ errors }) => ({ ...errors, registration: NO_MFA_TICKET_ERROR })
|
||||||
}),
|
}),
|
||||||
saveRefreshToken: assign({
|
|
||||||
accessToken: (ctx, e: any) => ({ ...ctx.accessToken, expiresAt: e.data.expiresAt }),
|
|
||||||
refreshToken: (ctx, e: any) => ({ ...ctx.refreshToken, value: e.data.refreshToken })
|
|
||||||
}),
|
|
||||||
// * Persist the refresh token and the jwt expiration outside of the machine
|
|
||||||
persist: (_, { data }: any) => {
|
|
||||||
storageSetter(NHOST_REFRESH_TOKEN_KEY, data.session.refreshToken)
|
|
||||||
if (data.session.accessTokenExpiresIn) {
|
|
||||||
const nextRefresh = new Date(
|
|
||||||
Date.now() + data.session.accessTokenExpiresIn * 1_000
|
|
||||||
).toISOString()
|
|
||||||
storageSetter(NHOST_JWT_EXPIRES_AT_KEY, nextRefresh)
|
|
||||||
} else {
|
|
||||||
storageSetter(NHOST_JWT_EXPIRES_AT_KEY, null)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
destroyRefreshToken: assign({
|
destroyRefreshToken: assign({
|
||||||
refreshToken: (_) => {
|
refreshToken: (_) => {
|
||||||
storageSetter(NHOST_REFRESH_TOKEN_KEY, null)
|
storageSetter(NHOST_REFRESH_TOKEN_KEY, null)
|
||||||
@@ -591,12 +563,9 @@ export const createAuthMachine = ({
|
|||||||
|
|
||||||
guards: {
|
guards: {
|
||||||
isSignedIn: (ctx) => !!ctx.user && !!ctx.refreshToken.value && !!ctx.accessToken.value,
|
isSignedIn: (ctx) => !!ctx.user && !!ctx.refreshToken.value && !!ctx.accessToken.value,
|
||||||
hasRefreshTokenWithoutSession: (ctx) =>
|
|
||||||
!!ctx.refreshToken.value && !ctx.user && !ctx.accessToken.value,
|
|
||||||
noToken: (ctx) => !ctx.refreshToken.value,
|
noToken: (ctx) => !ctx.refreshToken.value,
|
||||||
noMfaTicket: (ctx, { ticket }) => !ticket && !ctx.mfa?.ticket,
|
noMfaTicket: (ctx, { ticket }) => !ticket && !ctx.mfa?.ticket,
|
||||||
hasRefreshToken: (ctx) => !!ctx.refreshToken.value,
|
hasRefreshToken: (ctx) => !!ctx.refreshToken.value,
|
||||||
hasAuthenticationError: (ctx) => !!ctx.errors.authentication,
|
|
||||||
isAutoRefreshDisabled: () => !autoRefreshToken,
|
isAutoRefreshDisabled: () => !autoRefreshToken,
|
||||||
refreshTimerShouldRefresh: (ctx) => {
|
refreshTimerShouldRefresh: (ctx) => {
|
||||||
const { expiresAt } = ctx.accessToken
|
const { expiresAt } = ctx.accessToken
|
||||||
@@ -686,15 +655,17 @@ export const createAuthMachine = ({
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
importRefreshToken: async () => {
|
importRefreshToken: async () => {
|
||||||
const stringExpiresAt = await storageGetter(NHOST_JWT_EXPIRES_AT_KEY)
|
let error: ValidationErrorPayload | null = null
|
||||||
const expiresAt = stringExpiresAt ? new Date(stringExpiresAt) : null
|
|
||||||
let refreshToken = await storageGetter(NHOST_REFRESH_TOKEN_KEY)
|
|
||||||
if (autoSignIn) {
|
if (autoSignIn) {
|
||||||
const urlToken = getParameterByName('refreshToken') || null
|
const urlToken = getParameterByName('refreshToken') || null
|
||||||
if (urlToken) {
|
if (urlToken) {
|
||||||
if (!refreshToken) {
|
try {
|
||||||
// ? Which takes precedence? localStorage or the url?
|
const session = await postRequest('/token', {
|
||||||
refreshToken = urlToken
|
refreshToken: urlToken
|
||||||
|
})
|
||||||
|
return { session }
|
||||||
|
} catch (exception) {
|
||||||
|
error = (exception as { error: ValidationErrorPayload }).error
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const error = getParameterByName('error')
|
const error = getParameterByName('error')
|
||||||
@@ -709,14 +680,19 @@ export const createAuthMachine = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return refreshToken
|
const storageToken = await storageGetter(NHOST_REFRESH_TOKEN_KEY)
|
||||||
? {
|
if (storageToken) {
|
||||||
refreshToken,
|
try {
|
||||||
expiresAt
|
const session = await postRequest('/token', {
|
||||||
}
|
refreshToken: storageToken
|
||||||
: Promise.reject<{ error: ValidationErrorPayload }>({
|
|
||||||
error: null
|
|
||||||
})
|
})
|
||||||
|
return { session }
|
||||||
|
} catch (exception) {
|
||||||
|
error = (exception as { error: ValidationErrorPayload }).error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject<{ error: ValidationErrorPayload }>({ error })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,19 +5,9 @@ export interface Typegen0 {
|
|||||||
eventsCausingActions: {
|
eventsCausingActions: {
|
||||||
saveSession:
|
saveSession:
|
||||||
| 'SESSION_UPDATE'
|
| 'SESSION_UPDATE'
|
||||||
|
| 'done.invoke.importRefreshToken'
|
||||||
| 'done.invoke.authenticatePasswordlessSmsOtp'
|
| 'done.invoke.authenticatePasswordlessSmsOtp'
|
||||||
| 'done.invoke.authenticateUserWithPassword'
|
| 'done.invoke.authenticateUserWithPassword'
|
||||||
| 'done.invoke.signInToken'
|
|
||||||
| 'done.invoke.authenticateAnonymously'
|
|
||||||
| 'done.invoke.signInMfaTotp'
|
|
||||||
| 'done.invoke.registerUser'
|
|
||||||
| 'done.invoke.refreshToken'
|
|
||||||
| 'done.invoke.authenticateWithToken'
|
|
||||||
persist:
|
|
||||||
| 'SESSION_UPDATE'
|
|
||||||
| 'done.invoke.authenticatePasswordlessSmsOtp'
|
|
||||||
| 'done.invoke.authenticateUserWithPassword'
|
|
||||||
| 'done.invoke.signInToken'
|
|
||||||
| 'done.invoke.authenticateAnonymously'
|
| 'done.invoke.authenticateAnonymously'
|
||||||
| 'done.invoke.signInMfaTotp'
|
| 'done.invoke.signInMfaTotp'
|
||||||
| 'done.invoke.registerUser'
|
| 'done.invoke.registerUser'
|
||||||
@@ -26,22 +16,20 @@ export interface Typegen0 {
|
|||||||
resetTimer: 'SESSION_UPDATE' | 'done.invoke.refreshToken' | ''
|
resetTimer: 'SESSION_UPDATE' | 'done.invoke.refreshToken' | ''
|
||||||
reportTokenChanged:
|
reportTokenChanged:
|
||||||
| 'SESSION_UPDATE'
|
| 'SESSION_UPDATE'
|
||||||
|
| 'done.invoke.importRefreshToken'
|
||||||
| 'done.invoke.authenticatePasswordlessSmsOtp'
|
| 'done.invoke.authenticatePasswordlessSmsOtp'
|
||||||
| 'done.invoke.authenticateUserWithPassword'
|
| 'done.invoke.authenticateUserWithPassword'
|
||||||
| 'done.invoke.signInToken'
|
|
||||||
| 'done.invoke.authenticateAnonymously'
|
| 'done.invoke.authenticateAnonymously'
|
||||||
| 'done.invoke.signInMfaTotp'
|
| 'done.invoke.signInMfaTotp'
|
||||||
| 'done.invoke.registerUser'
|
| 'done.invoke.registerUser'
|
||||||
| 'done.invoke.refreshToken'
|
| 'done.invoke.refreshToken'
|
||||||
| 'done.invoke.authenticateWithToken'
|
| 'done.invoke.authenticateWithToken'
|
||||||
saveRefreshToken: 'done.invoke.importRefreshToken'
|
|
||||||
saveAuthenticationError:
|
saveAuthenticationError:
|
||||||
| 'error.platform.importRefreshToken'
|
| 'error.platform.importRefreshToken'
|
||||||
| 'error.platform.authenticatePasswordlessEmail'
|
| 'error.platform.authenticatePasswordlessEmail'
|
||||||
| 'error.platform.authenticatePasswordlessSms'
|
| 'error.platform.authenticatePasswordlessSms'
|
||||||
| 'error.platform.authenticatePasswordlessSmsOtp'
|
| 'error.platform.authenticatePasswordlessSmsOtp'
|
||||||
| 'error.platform.authenticateUserWithPassword'
|
| 'error.platform.authenticateUserWithPassword'
|
||||||
| 'error.platform.signInToken'
|
|
||||||
| 'error.platform.authenticateAnonymously'
|
| 'error.platform.authenticateAnonymously'
|
||||||
| 'error.platform.signInMfaTotp'
|
| 'error.platform.signInMfaTotp'
|
||||||
saveInvalidEmail: 'SIGNIN_PASSWORD' | 'SIGNIN_PASSWORDLESS_EMAIL'
|
saveInvalidEmail: 'SIGNIN_PASSWORD' | 'SIGNIN_PASSWORDLESS_EMAIL'
|
||||||
@@ -51,39 +39,50 @@ export interface Typegen0 {
|
|||||||
saveInvalidSignUpPassword: 'SIGNUP_EMAIL_PASSWORD'
|
saveInvalidSignUpPassword: 'SIGNUP_EMAIL_PASSWORD'
|
||||||
saveNoMfaTicketError: 'SIGNIN_MFA_TOTP'
|
saveNoMfaTicketError: 'SIGNIN_MFA_TOTP'
|
||||||
saveMfaTicket: 'done.invoke.authenticateUserWithPassword'
|
saveMfaTicket: 'done.invoke.authenticateUserWithPassword'
|
||||||
broadcastToken: 'done.invoke.signInToken'
|
|
||||||
saveRegisrationError: 'error.platform.registerUser'
|
saveRegisrationError: 'error.platform.registerUser'
|
||||||
saveRefreshAttempt: 'error.platform.refreshToken'
|
saveRefreshAttempt: 'error.platform.refreshToken'
|
||||||
reportSignedOut:
|
reportSignedOut: 'error.platform.importRefreshToken' | 'error.platform.authenticateWithToken'
|
||||||
| 'error.platform.importRefreshToken'
|
|
||||||
| ''
|
|
||||||
| 'error.platform.authenticateWithToken'
|
|
||||||
resetAuthenticationError: 'xstate.init'
|
resetAuthenticationError: 'xstate.init'
|
||||||
destroyRefreshToken: 'xstate.init'
|
destroyRefreshToken: 'xstate.init'
|
||||||
clearContextExceptRefreshToken: 'SIGNOUT'
|
clearContextExceptRefreshToken: 'SIGNOUT'
|
||||||
resetSignUpError: 'SIGNUP_EMAIL_PASSWORD'
|
resetSignUpError: 'SIGNUP_EMAIL_PASSWORD'
|
||||||
reportSignedIn:
|
reportSignedIn:
|
||||||
| 'SESSION_UPDATE'
|
| 'SESSION_UPDATE'
|
||||||
|
| 'done.invoke.importRefreshToken'
|
||||||
| ''
|
| ''
|
||||||
| 'done.invoke.authenticatePasswordlessSmsOtp'
|
| 'done.invoke.authenticatePasswordlessSmsOtp'
|
||||||
| 'done.invoke.authenticateUserWithPassword'
|
| 'done.invoke.authenticateUserWithPassword'
|
||||||
| 'done.invoke.signInToken'
|
|
||||||
| 'done.invoke.authenticateAnonymously'
|
| 'done.invoke.authenticateAnonymously'
|
||||||
| 'done.invoke.signInMfaTotp'
|
| 'done.invoke.signInMfaTotp'
|
||||||
| 'done.invoke.registerUser'
|
| 'done.invoke.registerUser'
|
||||||
| 'done.invoke.authenticateWithToken'
|
| 'done.invoke.authenticateWithToken'
|
||||||
cleanUrl:
|
cleanUrl:
|
||||||
| 'SESSION_UPDATE'
|
| 'SESSION_UPDATE'
|
||||||
|
| 'done.invoke.importRefreshToken'
|
||||||
|
| ''
|
||||||
|
| 'done.invoke.authenticatePasswordlessSmsOtp'
|
||||||
|
| 'done.invoke.authenticateUserWithPassword'
|
||||||
|
| 'done.invoke.authenticateAnonymously'
|
||||||
|
| 'done.invoke.signInMfaTotp'
|
||||||
|
| 'done.invoke.registerUser'
|
||||||
|
| 'done.invoke.authenticateWithToken'
|
||||||
|
broadcastToken:
|
||||||
|
| 'SESSION_UPDATE'
|
||||||
|
| 'done.invoke.importRefreshToken'
|
||||||
| ''
|
| ''
|
||||||
| 'done.invoke.authenticatePasswordlessSmsOtp'
|
| 'done.invoke.authenticatePasswordlessSmsOtp'
|
||||||
| 'done.invoke.authenticateUserWithPassword'
|
| 'done.invoke.authenticateUserWithPassword'
|
||||||
| 'done.invoke.signInToken'
|
|
||||||
| 'done.invoke.authenticateAnonymously'
|
| 'done.invoke.authenticateAnonymously'
|
||||||
| 'done.invoke.signInMfaTotp'
|
| 'done.invoke.signInMfaTotp'
|
||||||
| 'done.invoke.registerUser'
|
| 'done.invoke.registerUser'
|
||||||
| 'done.invoke.authenticateWithToken'
|
| 'done.invoke.authenticateWithToken'
|
||||||
}
|
}
|
||||||
internalEvents: {
|
internalEvents: {
|
||||||
|
'done.invoke.importRefreshToken': {
|
||||||
|
type: 'done.invoke.importRefreshToken'
|
||||||
|
data: unknown
|
||||||
|
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
||||||
|
}
|
||||||
'done.invoke.authenticatePasswordlessSmsOtp': {
|
'done.invoke.authenticatePasswordlessSmsOtp': {
|
||||||
type: 'done.invoke.authenticatePasswordlessSmsOtp'
|
type: 'done.invoke.authenticatePasswordlessSmsOtp'
|
||||||
data: unknown
|
data: unknown
|
||||||
@@ -94,11 +93,6 @@ export interface Typegen0 {
|
|||||||
data: unknown
|
data: unknown
|
||||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
||||||
}
|
}
|
||||||
'done.invoke.signInToken': {
|
|
||||||
type: 'done.invoke.signInToken'
|
|
||||||
data: unknown
|
|
||||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
|
||||||
}
|
|
||||||
'done.invoke.authenticateAnonymously': {
|
'done.invoke.authenticateAnonymously': {
|
||||||
type: 'done.invoke.authenticateAnonymously'
|
type: 'done.invoke.authenticateAnonymously'
|
||||||
data: unknown
|
data: unknown
|
||||||
@@ -125,11 +119,6 @@ export interface Typegen0 {
|
|||||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
||||||
}
|
}
|
||||||
'': { type: '' }
|
'': { type: '' }
|
||||||
'done.invoke.importRefreshToken': {
|
|
||||||
type: 'done.invoke.importRefreshToken'
|
|
||||||
data: unknown
|
|
||||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
|
||||||
}
|
|
||||||
'error.platform.importRefreshToken': {
|
'error.platform.importRefreshToken': {
|
||||||
type: 'error.platform.importRefreshToken'
|
type: 'error.platform.importRefreshToken'
|
||||||
data: unknown
|
data: unknown
|
||||||
@@ -150,7 +139,6 @@ export interface Typegen0 {
|
|||||||
type: 'error.platform.authenticateUserWithPassword'
|
type: 'error.platform.authenticateUserWithPassword'
|
||||||
data: unknown
|
data: unknown
|
||||||
}
|
}
|
||||||
'error.platform.signInToken': { type: 'error.platform.signInToken'; data: unknown }
|
|
||||||
'error.platform.authenticateAnonymously': {
|
'error.platform.authenticateAnonymously': {
|
||||||
type: 'error.platform.authenticateAnonymously'
|
type: 'error.platform.authenticateAnonymously'
|
||||||
data: unknown
|
data: unknown
|
||||||
@@ -190,13 +178,10 @@ export interface Typegen0 {
|
|||||||
signInPasswordlessSms: 'done.invoke.authenticatePasswordlessSms'
|
signInPasswordlessSms: 'done.invoke.authenticatePasswordlessSms'
|
||||||
signInPasswordlessSmsOtp: 'done.invoke.authenticatePasswordlessSmsOtp'
|
signInPasswordlessSmsOtp: 'done.invoke.authenticatePasswordlessSmsOtp'
|
||||||
signInPassword: 'done.invoke.authenticateUserWithPassword'
|
signInPassword: 'done.invoke.authenticateUserWithPassword'
|
||||||
refreshToken:
|
|
||||||
| 'done.invoke.signInToken'
|
|
||||||
| 'done.invoke.refreshToken'
|
|
||||||
| 'done.invoke.authenticateWithToken'
|
|
||||||
signInAnonymous: 'done.invoke.authenticateAnonymously'
|
signInAnonymous: 'done.invoke.authenticateAnonymously'
|
||||||
signInMfaTotp: 'done.invoke.signInMfaTotp'
|
signInMfaTotp: 'done.invoke.signInMfaTotp'
|
||||||
registerUser: 'done.invoke.registerUser'
|
registerUser: 'done.invoke.registerUser'
|
||||||
|
refreshToken: 'done.invoke.refreshToken' | 'done.invoke.authenticateWithToken'
|
||||||
}
|
}
|
||||||
missingImplementations: {
|
missingImplementations: {
|
||||||
actions: never
|
actions: never
|
||||||
@@ -206,7 +191,6 @@ export interface Typegen0 {
|
|||||||
}
|
}
|
||||||
eventsCausingServices: {
|
eventsCausingServices: {
|
||||||
importRefreshToken: 'xstate.init'
|
importRefreshToken: 'xstate.init'
|
||||||
refreshToken: '' | 'TRY_TOKEN'
|
|
||||||
signInPassword: 'SIGNIN_PASSWORD'
|
signInPassword: 'SIGNIN_PASSWORD'
|
||||||
signInPasswordlessEmail: 'SIGNIN_PASSWORDLESS_EMAIL'
|
signInPasswordlessEmail: 'SIGNIN_PASSWORDLESS_EMAIL'
|
||||||
signInPasswordlessSms: 'SIGNIN_PASSWORDLESS_SMS'
|
signInPasswordlessSms: 'SIGNIN_PASSWORDLESS_SMS'
|
||||||
@@ -215,12 +199,11 @@ export interface Typegen0 {
|
|||||||
signInAnonymous: 'SIGNIN_ANONYMOUS'
|
signInAnonymous: 'SIGNIN_ANONYMOUS'
|
||||||
signInMfaTotp: 'SIGNIN_MFA_TOTP'
|
signInMfaTotp: 'SIGNIN_MFA_TOTP'
|
||||||
signout: 'SIGNOUT'
|
signout: 'SIGNOUT'
|
||||||
|
refreshToken: '' | 'TRY_TOKEN'
|
||||||
}
|
}
|
||||||
eventsCausingGuards: {
|
eventsCausingGuards: {
|
||||||
hasSession: 'SESSION_UPDATE' | 'done.invoke.registerUser'
|
hasSession: 'SESSION_UPDATE' | 'done.invoke.registerUser'
|
||||||
isSignedIn: '' | 'error.platform.authenticateWithToken'
|
isSignedIn: '' | 'error.platform.authenticateWithToken'
|
||||||
hasRefreshTokenWithoutSession: ''
|
|
||||||
hasAuthenticationError: ''
|
|
||||||
invalidEmail: 'SIGNIN_PASSWORD' | 'SIGNIN_PASSWORDLESS_EMAIL' | 'SIGNUP_EMAIL_PASSWORD'
|
invalidEmail: 'SIGNIN_PASSWORD' | 'SIGNIN_PASSWORDLESS_EMAIL' | 'SIGNUP_EMAIL_PASSWORD'
|
||||||
invalidPassword: 'SIGNIN_PASSWORD' | 'SIGNUP_EMAIL_PASSWORD'
|
invalidPassword: 'SIGNIN_PASSWORD' | 'SIGNUP_EMAIL_PASSWORD'
|
||||||
invalidPhoneNumber: 'SIGNIN_PASSWORDLESS_SMS' | 'SIGNIN_PASSWORDLESS_SMS_OTP'
|
invalidPhoneNumber: 'SIGNIN_PASSWORDLESS_SMS' | 'SIGNIN_PASSWORDLESS_SMS_OTP'
|
||||||
@@ -235,7 +218,6 @@ export interface Typegen0 {
|
|||||||
eventsCausingDelays: {}
|
eventsCausingDelays: {}
|
||||||
matchesStates:
|
matchesStates:
|
||||||
| 'authentication'
|
| 'authentication'
|
||||||
| 'authentication.importingRefreshToken'
|
|
||||||
| 'authentication.starting'
|
| 'authentication.starting'
|
||||||
| 'authentication.signedOut'
|
| 'authentication.signedOut'
|
||||||
| 'authentication.signedOut.noErrors'
|
| 'authentication.signedOut.noErrors'
|
||||||
@@ -255,7 +237,6 @@ export interface Typegen0 {
|
|||||||
| 'authentication.authenticating.passwordlessSms'
|
| 'authentication.authenticating.passwordlessSms'
|
||||||
| 'authentication.authenticating.passwordlessSmsOtp'
|
| 'authentication.authenticating.passwordlessSmsOtp'
|
||||||
| 'authentication.authenticating.password'
|
| 'authentication.authenticating.password'
|
||||||
| 'authentication.authenticating.token'
|
|
||||||
| 'authentication.authenticating.anonymous'
|
| 'authentication.authenticating.anonymous'
|
||||||
| 'authentication.authenticating.mfa'
|
| 'authentication.authenticating.mfa'
|
||||||
| 'authentication.authenticating.mfa.totp'
|
| 'authentication.authenticating.mfa.totp'
|
||||||
@@ -278,7 +259,6 @@ export interface Typegen0 {
|
|||||||
| 'token.running'
|
| 'token.running'
|
||||||
| {
|
| {
|
||||||
authentication?:
|
authentication?:
|
||||||
| 'importingRefreshToken'
|
|
||||||
| 'starting'
|
| 'starting'
|
||||||
| 'signedOut'
|
| 'signedOut'
|
||||||
| 'authenticating'
|
| 'authenticating'
|
||||||
@@ -304,7 +284,6 @@ export interface Typegen0 {
|
|||||||
| 'passwordlessSms'
|
| 'passwordlessSms'
|
||||||
| 'passwordlessSmsOtp'
|
| 'passwordlessSmsOtp'
|
||||||
| 'password'
|
| 'password'
|
||||||
| 'token'
|
|
||||||
| 'anonymous'
|
| 'anonymous'
|
||||||
| 'mfa'
|
| 'mfa'
|
||||||
| { mfa?: 'totp' }
|
| { mfa?: 'totp' }
|
||||||
@@ -323,5 +302,5 @@ export interface Typegen0 {
|
|||||||
}
|
}
|
||||||
token?: 'idle' | 'running' | { idle?: 'noErrors' | 'error' }
|
token?: 'idle' | 'running' | { idle?: 'noErrors' | 'error' }
|
||||||
}
|
}
|
||||||
tags: 'ready'
|
tags: 'loading'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,39 +29,13 @@ const defaultClientStorageSetter: StorageSetter = (key, value) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO see https://github.com/nhost/nhost/pull/507#discussion_r865873389
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const checkStorageAccessors = (
|
|
||||||
clientStorage: ClientStorage,
|
|
||||||
accessors: Array<keyof ClientStorage>
|
|
||||||
) => {
|
|
||||||
accessors.forEach((key) => {
|
|
||||||
if (typeof clientStorage[key] !== 'function') {
|
|
||||||
console.error(`clientStorage.${key} is not a function`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const localStorageGetter = (
|
export const localStorageGetter = (
|
||||||
clientStorageType: ClientStorageType,
|
clientStorageType: ClientStorageType,
|
||||||
clientStorage?: ClientStorage
|
clientStorage?: ClientStorage
|
||||||
): StorageGetter => {
|
): StorageGetter => {
|
||||||
if (!clientStorage || clientStorageType === 'localStorage' || clientStorageType === 'web') {
|
if (clientStorageType === 'localStorage' || clientStorageType === 'web') {
|
||||||
return defaultClientStorageGetter
|
return defaultClientStorageGetter
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientStorageType === 'react-native') {
|
|
||||||
// checkStorageAccessors(clientStorage, ['getItem'])
|
|
||||||
return (key) => clientStorage.getItem?.(key)
|
|
||||||
}
|
|
||||||
if (clientStorageType === 'capacitor') {
|
|
||||||
// checkStorageAccessors(clientStorage, ['get'])
|
|
||||||
return (key) => clientStorage.get?.({ key })
|
|
||||||
}
|
|
||||||
if (clientStorageType === 'expo-secure-storage') {
|
|
||||||
// checkStorageAccessors(clientStorage, ['getItemAsync'])
|
|
||||||
return (key) => clientStorage.getItemAsync?.(key)
|
|
||||||
}
|
|
||||||
if (clientStorageType === 'cookie') {
|
if (clientStorageType === 'cookie') {
|
||||||
return (key) => {
|
return (key) => {
|
||||||
if (isBrowser) {
|
if (isBrowser) {
|
||||||
@@ -71,6 +45,20 @@ export const localStorageGetter = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!clientStorage) {
|
||||||
|
throw Error(
|
||||||
|
`clientStorageType is set to '${clientStorageType}' but no clienStorage has been given`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (clientStorageType === 'react-native') {
|
||||||
|
return (key) => clientStorage.getItem?.(key)
|
||||||
|
}
|
||||||
|
if (clientStorageType === 'capacitor') {
|
||||||
|
return (key) => clientStorage.get?.({ key })
|
||||||
|
}
|
||||||
|
if (clientStorageType === 'expo-secure-storage') {
|
||||||
|
return (key) => clientStorage.getItemAsync?.(key)
|
||||||
|
}
|
||||||
if (clientStorageType === 'custom') {
|
if (clientStorageType === 'custom') {
|
||||||
if (clientStorage.getItem && clientStorage.removeItem) {
|
if (clientStorage.getItem && clientStorage.removeItem) {
|
||||||
return clientStorage.getItem
|
return clientStorage.getItem
|
||||||
@@ -89,25 +77,9 @@ export const localStorageSetter = (
|
|||||||
clientStorageType: ClientStorageType,
|
clientStorageType: ClientStorageType,
|
||||||
clientStorage?: ClientStorage
|
clientStorage?: ClientStorage
|
||||||
): StorageSetter => {
|
): StorageSetter => {
|
||||||
if (!clientStorage || clientStorageType === 'localStorage' || clientStorageType === 'web') {
|
if (clientStorageType === 'localStorage' || clientStorageType === 'web') {
|
||||||
return defaultClientStorageSetter
|
return defaultClientStorageSetter
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientStorageType === 'react-native') {
|
|
||||||
// checkStorageAccessors(clientStorage, ['setItem', 'removeItem'])
|
|
||||||
return (key, value) =>
|
|
||||||
value ? clientStorage.setItem?.(key, value) : clientStorage.removeItem?.(key)
|
|
||||||
}
|
|
||||||
if (clientStorageType === 'capacitor') {
|
|
||||||
// checkStorageAccessors(clientStorage, ['set', 'remove'])
|
|
||||||
return (key, value) =>
|
|
||||||
value ? clientStorage.set?.({ key, value }) : clientStorage.remove?.({ key })
|
|
||||||
}
|
|
||||||
if (clientStorageType === 'expo-secure-storage') {
|
|
||||||
// checkStorageAccessors(clientStorage, ['setItemAsync', 'deleteItemAsync'])
|
|
||||||
return async (key, value) =>
|
|
||||||
value ? clientStorage.setItemAsync?.(key, value) : clientStorage.deleteItemAsync?.(key)
|
|
||||||
}
|
|
||||||
if (clientStorageType === 'cookie') {
|
if (clientStorageType === 'cookie') {
|
||||||
return (key, value) => {
|
return (key, value) => {
|
||||||
if (isBrowser) {
|
if (isBrowser) {
|
||||||
@@ -119,6 +91,23 @@ export const localStorageSetter = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!clientStorage) {
|
||||||
|
throw Error(
|
||||||
|
`clientStorageType is set to '${clientStorageType}' but no clienStorage has been given`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (clientStorageType === 'react-native') {
|
||||||
|
return (key, value) =>
|
||||||
|
value ? clientStorage.setItem?.(key, value) : clientStorage.removeItem?.(key)
|
||||||
|
}
|
||||||
|
if (clientStorageType === 'capacitor') {
|
||||||
|
return (key, value) =>
|
||||||
|
value ? clientStorage.set?.({ key, value }) : clientStorage.remove?.({ key })
|
||||||
|
}
|
||||||
|
if (clientStorageType === 'expo-secure-storage') {
|
||||||
|
return async (key, value) =>
|
||||||
|
value ? clientStorage.setItemAsync?.(key, value) : clientStorage.deleteItemAsync?.(key)
|
||||||
|
}
|
||||||
if (clientStorageType === 'custom') {
|
if (clientStorageType === 'custom') {
|
||||||
if (!clientStorage.removeItem) {
|
if (!clientStorage.removeItem) {
|
||||||
throw Error(
|
throw Error(
|
||||||
|
|||||||
@@ -1,5 +1,28 @@
|
|||||||
# @nhost/hasura-auth-js
|
# @nhost/hasura-auth-js
|
||||||
|
|
||||||
|
## 1.1.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [65a3061]
|
||||||
|
- @nhost/core@0.5.2
|
||||||
|
|
||||||
|
## 1.1.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 58fa2a2: Improve loading status
|
||||||
|
The `loading` status indicates the authentication is not yet known to the client when it starts. Once the client is ready, the authentication status is either signed in, or signed out.
|
||||||
|
When the user was trying to authenticate, the `loading` status was set to `true` until the result of the authentication was known.
|
||||||
|
The client now only return `loading: true` on startup, and in no other cases.
|
||||||
|
- 58fa2a2: Look for a valid refresh token both the URL and local storage
|
||||||
|
When auto-signin was activated, the client was not taking into account the refresh token in the URL if a token was already stored locally.
|
||||||
|
The user was then not able to authenticate from a link when the refresh token stored locally was invalid or expired.
|
||||||
|
When auto-signin is activated, the client now checks and tries tokens from both the URL and the local storage, starting with the URL.
|
||||||
|
- Updated dependencies [58fa2a2]
|
||||||
|
- Updated dependencies [58fa2a2]
|
||||||
|
- @nhost/core@0.5.1
|
||||||
|
|
||||||
## 1.1.2
|
## 1.1.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/hasura-auth-js",
|
"name": "@nhost/hasura-auth-js",
|
||||||
"version": "1.1.2",
|
"version": "1.1.4",
|
||||||
"description": "Hasura-auth client",
|
"description": "Hasura-auth client",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -745,7 +745,7 @@ export class HasuraAuthClient {
|
|||||||
if (!interpreter) {
|
if (!interpreter) {
|
||||||
throw Error('Auth interpreter not set')
|
throw Error('Auth interpreter not set')
|
||||||
}
|
}
|
||||||
if (interpreter.state.hasTag('ready')) {
|
if (!interpreter.state.hasTag('loading')) {
|
||||||
return Promise.resolve(interpreter)
|
return Promise.resolve(interpreter)
|
||||||
}
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -754,7 +754,7 @@ export class HasuraAuthClient {
|
|||||||
TIMEOUT_IN_SECONS * 1_000
|
TIMEOUT_IN_SECONS * 1_000
|
||||||
)
|
)
|
||||||
interpreter.onTransition((state) => {
|
interpreter.onTransition((state) => {
|
||||||
if (state.hasTag('ready')) {
|
if (!state.hasTag('loading')) {
|
||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
return resolve(interpreter)
|
return resolve(interpreter)
|
||||||
}
|
}
|
||||||
@@ -763,7 +763,7 @@ export class HasuraAuthClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isReady() {
|
private isReady() {
|
||||||
return !!this._client.interpreter?.state?.hasTag('ready')
|
return !this._client.interpreter?.state?.hasTag('loading')
|
||||||
}
|
}
|
||||||
|
|
||||||
get client() {
|
get client() {
|
||||||
|
|||||||
@@ -1,5 +1,29 @@
|
|||||||
# @nhost/nextjs
|
# @nhost/nextjs
|
||||||
|
|
||||||
|
## 1.2.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@1.1.9
|
||||||
|
- @nhost/react@0.7.2
|
||||||
|
|
||||||
|
## 1.2.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 58fa2a2: Improve loading status
|
||||||
|
The `loading` status indicates the authentication is not yet known to the client when it starts. Once the client is ready, the authentication status is either signed in, or signed out.
|
||||||
|
When the user was trying to authenticate, the `loading` status was set to `true` until the result of the authentication was known.
|
||||||
|
The client now only return `loading: true` on startup, and in no other cases.
|
||||||
|
- 58fa2a2: Look for a valid refresh token both the URL and local storage
|
||||||
|
When auto-signin was activated, the client was not taking into account the refresh token in the URL if a token was already stored locally.
|
||||||
|
The user was then not able to authenticate from a link when the refresh token stored locally was invalid or expired.
|
||||||
|
When auto-signin is activated, the client now checks and tries tokens from both the URL and the local storage, starting with the URL.
|
||||||
|
- Updated dependencies [58fa2a2]
|
||||||
|
- Updated dependencies [58fa2a2]
|
||||||
|
- @nhost/react@0.7.1
|
||||||
|
- @nhost/nhost-js@1.1.8
|
||||||
|
|
||||||
## 1.2.0
|
## 1.2.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/nextjs",
|
"name": "@nhost/nextjs",
|
||||||
"version": "1.2.0",
|
"version": "1.2.2",
|
||||||
"description": "Nhost NextJS library",
|
"description": "Nhost NextJS library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -46,8 +46,9 @@ export const createServerSideClient = async (
|
|||||||
autoSignIn: true
|
autoSignIn: true
|
||||||
})
|
})
|
||||||
|
|
||||||
await waitFor(nhost.auth.client.interpreter!, (state: StateFrom<AuthMachine>) =>
|
await waitFor(
|
||||||
state.hasTag('ready')
|
nhost.auth.client.interpreter!,
|
||||||
|
(state: StateFrom<AuthMachine>) => !state.hasTag('loading')
|
||||||
)
|
)
|
||||||
return nhost
|
return nhost
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export const getNhostSession = async (
|
|||||||
backendUrl: string,
|
backendUrl: string,
|
||||||
context: GetServerSidePropsContext
|
context: GetServerSidePropsContext
|
||||||
): Promise<NhostSession | null> => {
|
): Promise<NhostSession | null> => {
|
||||||
const nhost = await createServerSideClient(backendUrl, context as any)
|
const nhost = await createServerSideClient(backendUrl, context)
|
||||||
const { accessToken, refreshToken, user } = nhost.auth.client.interpreter!.state.context
|
const { accessToken, refreshToken, user } = nhost.auth.client.interpreter!.state.context
|
||||||
return nhost.auth.isAuthenticated()
|
return nhost.auth.isAuthenticated()
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
# @nhost/nhost-js
|
# @nhost/nhost-js
|
||||||
|
|
||||||
|
## 1.1.9
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/hasura-auth-js@1.1.4
|
||||||
|
|
||||||
|
## 1.1.8
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [58fa2a2]
|
||||||
|
- Updated dependencies [58fa2a2]
|
||||||
|
- @nhost/hasura-auth-js@1.1.3
|
||||||
|
|
||||||
## 1.1.7
|
## 1.1.7
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/nhost-js",
|
"name": "@nhost/nhost-js",
|
||||||
"version": "1.1.7",
|
"version": "1.1.9",
|
||||||
"description": "Nhost JavaScript SDK",
|
"description": "Nhost JavaScript SDK",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
# @nhost/react-apollo
|
# @nhost/react-apollo
|
||||||
|
|
||||||
|
## 4.2.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/apollo@0.5.2
|
||||||
|
- @nhost/react@0.7.2
|
||||||
|
|
||||||
|
## 4.2.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [58fa2a2]
|
||||||
|
- Updated dependencies [58fa2a2]
|
||||||
|
- @nhost/react@0.7.1
|
||||||
|
- @nhost/apollo@0.5.1
|
||||||
|
|
||||||
## 4.2.0
|
## 4.2.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/react-apollo",
|
"name": "@nhost/react-apollo",
|
||||||
"version": "4.2.0",
|
"version": "4.2.2",
|
||||||
"description": "Nhost React Apollo client",
|
"description": "Nhost React Apollo client",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,25 @@
|
|||||||
# @nhost/react
|
# @nhost/react
|
||||||
|
|
||||||
|
## 0.7.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@1.1.9
|
||||||
|
|
||||||
|
## 0.7.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 58fa2a2: Improve loading status
|
||||||
|
The `loading` status indicates the authentication is not yet known to the client when it starts. Once the client is ready, the authentication status is either signed in, or signed out.
|
||||||
|
When the user was trying to authenticate, the `loading` status was set to `true` until the result of the authentication was known.
|
||||||
|
The client now only return `loading: true` on startup, and in no other cases.
|
||||||
|
- 58fa2a2: Look for a valid refresh token both the URL and local storage
|
||||||
|
When auto-signin was activated, the client was not taking into account the refresh token in the URL if a token was already stored locally.
|
||||||
|
The user was then not able to authenticate from a link when the refresh token stored locally was invalid or expired.
|
||||||
|
When auto-signin is activated, the client now checks and tries tokens from both the URL and the local storage, starting with the URL.
|
||||||
|
- @nhost/nhost-js@1.1.8
|
||||||
|
|
||||||
## 0.7.0
|
## 0.7.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/react",
|
"name": "@nhost/react",
|
||||||
"version": "0.7.0",
|
"version": "0.7.2",
|
||||||
"description": "Nhost React library",
|
"description": "Nhost React library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const useNhostBackendUrl = () => {
|
|||||||
*/
|
*/
|
||||||
export const useAuthLoading = () => {
|
export const useAuthLoading = () => {
|
||||||
const service = useAuthInterpreter()
|
const service = useAuthInterpreter()
|
||||||
return useSelector(service, (state) => !state.hasTag('ready'))
|
return useSelector(service, (state) => state.hasTag('loading'))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,7 +90,7 @@ export const useAuthenticationStatus = () => {
|
|||||||
service,
|
service,
|
||||||
(state) => ({
|
(state) => ({
|
||||||
isAuthenticated: state.matches({ authentication: 'signedIn' }),
|
isAuthenticated: state.matches({ authentication: 'signedIn' }),
|
||||||
isLoading: !state.hasTag('ready'),
|
isLoading: state.hasTag('loading'),
|
||||||
error: state.context.errors.authentication || null,
|
error: state.context.errors.authentication || null,
|
||||||
isError: state.matches({ authentication: { signedOut: 'failed' } })
|
isError: state.matches({ authentication: { signedOut: 'failed' } })
|
||||||
}),
|
}),
|
||||||
|
|||||||
287
pnpm-lock.yaml
generated
287
pnpm-lock.yaml
generated
@@ -191,49 +191,49 @@ importers:
|
|||||||
examples/react-apollo:
|
examples/react-apollo:
|
||||||
specifiers:
|
specifiers:
|
||||||
'@apollo/client': ^3.6.2
|
'@apollo/client': ^3.6.2
|
||||||
|
'@mantine/core': ^4.2.2
|
||||||
|
'@mantine/hooks': ^4.2.2
|
||||||
|
'@mantine/notifications': ^4.2.2
|
||||||
|
'@mantine/prism': ^4.2.2
|
||||||
'@nhost/core': workspace:*
|
'@nhost/core': workspace:*
|
||||||
'@nhost/react': workspace:*
|
'@nhost/react': workspace:*
|
||||||
'@nhost/react-apollo': workspace:*
|
'@nhost/react-apollo': workspace:*
|
||||||
'@rsuite/icons': ^1.0.2
|
|
||||||
'@types/react': ^18.0.8
|
'@types/react': ^18.0.8
|
||||||
'@types/react-dom': ^18.0.3
|
'@types/react-dom': ^18.0.3
|
||||||
'@vitejs/plugin-react': ^1.3.1
|
'@vitejs/plugin-react': ^1.3.1
|
||||||
'@xstate/inspect': ^0.6.2
|
'@xstate/inspect': ^0.6.2
|
||||||
graphql: 15.7.2
|
graphql: 15.7.2
|
||||||
less: ^4.1.2
|
|
||||||
react: ^18.1.0
|
react: ^18.1.0
|
||||||
react-dom: ^18.1.0
|
react-dom: ^18.1.0
|
||||||
react-icons: ^4.3.1
|
react-icons: ^4.3.1
|
||||||
react-json-view: ^1.21.3
|
|
||||||
react-router: ^6.3.0
|
react-router: ^6.3.0
|
||||||
react-router-dom: ^6.3.0
|
react-router-dom: ^6.3.0
|
||||||
rsuite: ^5.10.0
|
|
||||||
typescript: ^4.6.3
|
typescript: ^4.6.3
|
||||||
vite: ^2.9.5
|
vite: ^2.9.5
|
||||||
ws: ^8.6.0
|
ws: ^8.6.0
|
||||||
xstate: ^4.31.0
|
xstate: ^4.31.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@apollo/client': 3.6.2_graphql@15.7.2+react@18.1.0
|
'@apollo/client': 3.6.2_graphql@15.7.2+react@18.1.0
|
||||||
|
'@mantine/core': 4.2.2_ea9d0c51a4562a6544166c6277bf397c
|
||||||
|
'@mantine/hooks': 4.2.2_react@18.1.0
|
||||||
|
'@mantine/notifications': 4.2.2_af46c71ecf518c86fb9b76e8a89b700a
|
||||||
|
'@mantine/prism': 4.2.2_af46c71ecf518c86fb9b76e8a89b700a
|
||||||
'@nhost/core': link:../../packages/core
|
'@nhost/core': link:../../packages/core
|
||||||
'@nhost/react': link:../../packages/react
|
'@nhost/react': link:../../packages/react
|
||||||
'@nhost/react-apollo': link:../../packages/react-apollo
|
'@nhost/react-apollo': link:../../packages/react-apollo
|
||||||
'@rsuite/icons': 1.0.2_react-dom@18.1.0+react@18.1.0
|
|
||||||
graphql: 15.7.2
|
graphql: 15.7.2
|
||||||
less: 4.1.2
|
|
||||||
react: 18.1.0
|
react: 18.1.0
|
||||||
react-dom: 18.1.0_react@18.1.0
|
react-dom: 18.1.0_react@18.1.0
|
||||||
react-icons: 4.3.1_react@18.1.0
|
react-icons: 4.3.1_react@18.1.0
|
||||||
react-json-view: 1.21.3_47ae209ab5c2ea051c9a2540426c6b2f
|
|
||||||
react-router: 6.3.0_react@18.1.0
|
react-router: 6.3.0_react@18.1.0
|
||||||
react-router-dom: 6.3.0_react-dom@18.1.0+react@18.1.0
|
react-router-dom: 6.3.0_react-dom@18.1.0+react@18.1.0
|
||||||
rsuite: 5.10.0_react-dom@18.1.0+react@18.1.0
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/react': 18.0.8
|
'@types/react': 18.0.8
|
||||||
'@types/react-dom': 18.0.3
|
'@types/react-dom': 18.0.3
|
||||||
'@vitejs/plugin-react': 1.3.1
|
'@vitejs/plugin-react': 1.3.1
|
||||||
'@xstate/inspect': 0.6.5_ws@8.6.0+xstate@4.31.0
|
'@xstate/inspect': 0.6.5_ws@8.6.0+xstate@4.31.0
|
||||||
typescript: 4.6.3
|
typescript: 4.6.3
|
||||||
vite: 2.9.5_less@4.1.2
|
vite: 2.9.5
|
||||||
ws: 8.6.0
|
ws: 8.6.0
|
||||||
xstate: 4.31.0
|
xstate: 4.31.0
|
||||||
|
|
||||||
@@ -5189,10 +5189,6 @@ packages:
|
|||||||
'@jridgewell/resolve-uri': 3.0.6
|
'@jridgewell/resolve-uri': 3.0.6
|
||||||
'@jridgewell/sourcemap-codec': 1.4.12
|
'@jridgewell/sourcemap-codec': 1.4.12
|
||||||
|
|
||||||
/@juggle/resize-observer/3.3.1:
|
|
||||||
resolution: {integrity: sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@leichtgewicht/ip-codec/2.0.3:
|
/@leichtgewicht/ip-codec/2.0.3:
|
||||||
resolution: {integrity: sha512-nkalE/f1RvRGChwBnEIoBfSEYOXnCRdleKuv6+lePbMDrMZXeDQnqak5XDOeBgrPPyPfAdcCu/B5z+v3VhplGg==}
|
resolution: {integrity: sha512-nkalE/f1RvRGChwBnEIoBfSEYOXnCRdleKuv6+lePbMDrMZXeDQnqak5XDOeBgrPPyPfAdcCu/B5z+v3VhplGg==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -5257,6 +5253,21 @@ packages:
|
|||||||
react-transition-group: 4.4.2_react-dom@18.1.0+react@18.1.0
|
react-transition-group: 4.4.2_react-dom@18.1.0+react@18.1.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@mantine/prism/4.2.2_af46c71ecf518c86fb9b76e8a89b700a:
|
||||||
|
resolution: {integrity: sha512-I3S7xAX74EQD8LNO/FuyR5EHNAhcNaWcXcEQZZITTYKbUv3/ieAcV8k5DybWa2FbFtVEhCSTrT+9YIjhIcGWOA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@mantine/core': 4.2.2
|
||||||
|
'@mantine/hooks': 4.2.2
|
||||||
|
react: '>=16.8.0'
|
||||||
|
react-dom: '>=16.8.0'
|
||||||
|
dependencies:
|
||||||
|
'@mantine/core': 4.2.2_ea9d0c51a4562a6544166c6277bf397c
|
||||||
|
'@mantine/hooks': 4.2.2_react@18.1.0
|
||||||
|
prism-react-renderer: 1.3.1_react@18.1.0
|
||||||
|
react: 18.1.0
|
||||||
|
react-dom: 18.1.0_react@18.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@mantine/ssr/4.2.2_b49546c5ecaa39f6ec18d9e420cad0ed:
|
/@mantine/ssr/4.2.2_b49546c5ecaa39f6ec18d9e420cad0ed:
|
||||||
resolution: {integrity: sha512-xZV+kAgiDIGoxTRXJFoY8LqjxTzmvX1YxRO5QCK0Ky7JuA/iiCwSDsHf5fRqGj6e/ZNJh0MHr/M52PdpYBrpuQ==}
|
resolution: {integrity: sha512-xZV+kAgiDIGoxTRXJFoY8LqjxTzmvX1YxRO5QCK0Ky7JuA/iiCwSDsHf5fRqGj6e/ZNJh0MHr/M52PdpYBrpuQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -5806,24 +5817,6 @@ packages:
|
|||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@rsuite/icon-font/4.0.0:
|
|
||||||
resolution: {integrity: sha512-rZTgpTH3H3HLczCA2rnkWfoMKm0ZXoRzsrkVujfP/FfslnKUMvO6w56pa8pCvhWGpNEPUsLS2ULnFGpTEcup/Q==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@rsuite/icons/1.0.2_react-dom@18.1.0+react@18.1.0:
|
|
||||||
resolution: {integrity: sha512-Y7vJNDQpJnFlyYSUXQ2iQ9Meg7+ZKcrIenhpYDdM3c7vYDE/L7pml+hrK28jk6QfV/QkVv5B504D+l7aM6AAJQ==}
|
|
||||||
peerDependencies:
|
|
||||||
react: '>=16.8.0'
|
|
||||||
react-dom: '>=16.8.0'
|
|
||||||
dependencies:
|
|
||||||
'@rsuite/icon-font': 4.0.0
|
|
||||||
classnames: 2.3.1
|
|
||||||
insert-css: 2.0.0
|
|
||||||
lodash: 4.17.21
|
|
||||||
react: 18.1.0
|
|
||||||
react-dom: 18.1.0_react@18.1.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@rushstack/eslint-patch/1.1.3:
|
/@rushstack/eslint-patch/1.1.3:
|
||||||
resolution: {integrity: sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==}
|
resolution: {integrity: sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -6285,10 +6278,6 @@ packages:
|
|||||||
'@types/node': 17.0.25
|
'@types/node': 17.0.25
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@types/chai/4.3.1:
|
|
||||||
resolution: {integrity: sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/connect-history-api-fallback/1.3.5:
|
/@types/connect-history-api-fallback/1.3.5:
|
||||||
resolution: {integrity: sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==}
|
resolution: {integrity: sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6448,10 +6437,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==}
|
resolution: {integrity: sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/lodash/4.14.182:
|
|
||||||
resolution: {integrity: sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/mdast/3.0.10:
|
/@types/mdast/3.0.10:
|
||||||
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
|
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6548,19 +6533,13 @@ packages:
|
|||||||
'@types/history': 4.7.11
|
'@types/history': 4.7.11
|
||||||
'@types/react': 18.0.8
|
'@types/react': 18.0.8
|
||||||
|
|
||||||
/@types/react-virtualized/9.21.21:
|
|
||||||
resolution: {integrity: sha512-Exx6I7p4Qn+BBA1SRyj/UwQlZ0I0Pq7g7uhAp0QQ4JWzZunqEqNBGTmCmMmS/3N9wFgAGWuBD16ap7k8Y14VPA==}
|
|
||||||
dependencies:
|
|
||||||
'@types/prop-types': 15.7.5
|
|
||||||
'@types/react': 17.0.44
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/react/17.0.44:
|
/@types/react/17.0.44:
|
||||||
resolution: {integrity: sha512-Ye0nlw09GeMp2Suh8qoOv0odfgCoowfM/9MG6WeRD60Gq9wS90bdkdRtYbRkNhXOpG4H+YXGvj4wOWhAC0LJ1g==}
|
resolution: {integrity: sha512-Ye0nlw09GeMp2Suh8qoOv0odfgCoowfM/9MG6WeRD60Gq9wS90bdkdRtYbRkNhXOpG4H+YXGvj4wOWhAC0LJ1g==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/prop-types': 15.7.5
|
'@types/prop-types': 15.7.5
|
||||||
'@types/scheduler': 0.16.2
|
'@types/scheduler': 0.16.2
|
||||||
csstype: 3.0.11
|
csstype: 3.0.11
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/react/18.0.5:
|
/@types/react/18.0.5:
|
||||||
resolution: {integrity: sha512-UPxNGInDCIKlfqBrm8LDXYWNfLHwIdisWcsH5GpMyGjhEDLFgTtlRBaoWuCua9HcyuE0rMkmAeZ3FXV1pYLIYQ==}
|
resolution: {integrity: sha512-UPxNGInDCIKlfqBrm8LDXYWNfLHwIdisWcsH5GpMyGjhEDLFgTtlRBaoWuCua9HcyuE0rMkmAeZ3FXV1pYLIYQ==}
|
||||||
@@ -8559,12 +8538,6 @@ packages:
|
|||||||
keygrip: 1.1.0
|
keygrip: 1.1.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/copy-anything/2.0.6:
|
|
||||||
resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==}
|
|
||||||
dependencies:
|
|
||||||
is-what: 3.14.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/copy-text-to-clipboard/3.0.1:
|
/copy-text-to-clipboard/3.0.1:
|
||||||
resolution: {integrity: sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q==}
|
resolution: {integrity: sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -9724,12 +9697,6 @@ packages:
|
|||||||
csstype: 3.0.11
|
csstype: 3.0.11
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/dom-lib/3.1.2:
|
|
||||||
resolution: {integrity: sha512-FKw/w51guhTwiSnyCQKTxEwglnZG7Q1qJO62asKwedcqCCHwrwQP8DnMPBTXL9y3JTePFDaPS95GLha/Q9nd/w==}
|
|
||||||
dependencies:
|
|
||||||
'@babel/runtime': 7.17.9
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/dom-serializer/0.1.1:
|
/dom-serializer/0.1.1:
|
||||||
resolution: {integrity: sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==}
|
resolution: {integrity: sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -9956,15 +9923,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
|
resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/errno/0.1.8:
|
|
||||||
resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
|
|
||||||
hasBin: true
|
|
||||||
requiresBuild: true
|
|
||||||
dependencies:
|
|
||||||
prr: 1.0.1
|
|
||||||
dev: false
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
/error-ex/1.3.2:
|
/error-ex/1.3.2:
|
||||||
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
|
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -11239,18 +11197,6 @@ packages:
|
|||||||
- encoding
|
- encoding
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/flux/4.0.3_react@18.1.0:
|
|
||||||
resolution: {integrity: sha512-yKAbrp7JhZhj6uiT1FTuVMlIAT1J4jqEyBpFApi1kxpGZCvacMVc/t1pMQyotqHhAgvoE3bNvAykhCo2CLjnYw==}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^15.0.2 || ^16.0.0 || ^17.0.0
|
|
||||||
dependencies:
|
|
||||||
fbemitter: 3.0.0
|
|
||||||
fbjs: 3.0.4
|
|
||||||
react: 18.1.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- encoding
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/follow-redirects/1.14.9:
|
/follow-redirects/1.14.9:
|
||||||
resolution: {integrity: sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==}
|
resolution: {integrity: sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==}
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
@@ -12168,14 +12114,6 @@ packages:
|
|||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/image-size/0.5.5:
|
|
||||||
resolution: {integrity: sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
hasBin: true
|
|
||||||
requiresBuild: true
|
|
||||||
dev: false
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
/image-size/1.0.1:
|
/image-size/1.0.1:
|
||||||
resolution: {integrity: sha512-VAwkvNSNGClRw9mDHhc5Efax8PLlsOGcUTh0T/LIriC8vPA3U5PdqXWqkz406MoYHMKW8Uf9gWr05T/rYB44kQ==}
|
resolution: {integrity: sha512-VAwkvNSNGClRw9mDHhc5Efax8PLlsOGcUTh0T/LIriC8vPA3U5PdqXWqkz406MoYHMKW8Uf9gWr05T/rYB44kQ==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
@@ -12291,10 +12229,6 @@ packages:
|
|||||||
through: 2.3.8
|
through: 2.3.8
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/insert-css/2.0.0:
|
|
||||||
resolution: {integrity: sha1-610Ql7dUL0x56jBg067gfQU4gPQ=}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/install-artifact-from-github/1.3.0:
|
/install-artifact-from-github/1.3.0:
|
||||||
resolution: {integrity: sha512-iT8v1GwOAX0pPXifF/5ihnMhHOCo3OeK7z3TQa4CtSNCIg8k0UxqBEk9jRwz8OP68hHXvJ2gxRa89KYHtBkqGA==}
|
resolution: {integrity: sha512-iT8v1GwOAX0pPXifF/5ihnMhHOCo3OeK7z3TQa4CtSNCIg8k0UxqBEk9jRwz8OP68hHXvJ2gxRa89KYHtBkqGA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -12704,10 +12638,6 @@ packages:
|
|||||||
call-bind: 1.0.2
|
call-bind: 1.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/is-what/3.14.1:
|
|
||||||
resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/is-whitespace-character/1.0.4:
|
/is-whitespace-character/1.0.4:
|
||||||
resolution: {integrity: sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==}
|
resolution: {integrity: sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -13689,24 +13619,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
package-json: 6.5.0
|
package-json: 6.5.0
|
||||||
|
|
||||||
/less/4.1.2:
|
|
||||||
resolution: {integrity: sha512-EoQp/Et7OSOVu0aJknJOtlXZsnr8XE8KwuzTHOLeVSEx8pVWUICc8Q0VYRHgzyjX78nMEyC/oztWFbgyhtNfDA==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
hasBin: true
|
|
||||||
dependencies:
|
|
||||||
copy-anything: 2.0.6
|
|
||||||
parse-node-version: 1.0.1
|
|
||||||
tslib: 2.3.1
|
|
||||||
optionalDependencies:
|
|
||||||
errno: 0.1.8
|
|
||||||
graceful-fs: 4.2.10
|
|
||||||
image-size: 0.5.5
|
|
||||||
make-dir: 2.1.0
|
|
||||||
mime: 1.6.0
|
|
||||||
needle: 2.9.1
|
|
||||||
source-map: 0.6.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/leven/3.1.0:
|
/leven/3.1.0:
|
||||||
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
|
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -14038,16 +13950,6 @@ packages:
|
|||||||
iconv-lite: 0.6.3
|
iconv-lite: 0.6.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/make-dir/2.1.0:
|
|
||||||
resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
requiresBuild: true
|
|
||||||
dependencies:
|
|
||||||
pify: 4.0.1
|
|
||||||
semver: 5.7.1
|
|
||||||
dev: false
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
/make-dir/3.1.0:
|
/make-dir/3.1.0:
|
||||||
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
|
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -14506,18 +14408,6 @@ packages:
|
|||||||
resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=}
|
resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/needle/2.9.1:
|
|
||||||
resolution: {integrity: sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==}
|
|
||||||
engines: {node: '>= 4.4.x'}
|
|
||||||
hasBin: true
|
|
||||||
requiresBuild: true
|
|
||||||
dependencies:
|
|
||||||
debug: 3.2.7
|
|
||||||
iconv-lite: 0.4.24
|
|
||||||
sax: 1.2.4
|
|
||||||
dev: false
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
/negotiator/0.6.3:
|
/negotiator/0.6.3:
|
||||||
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@@ -15088,11 +14978,6 @@ packages:
|
|||||||
json-parse-even-better-errors: 2.3.1
|
json-parse-even-better-errors: 2.3.1
|
||||||
lines-and-columns: 1.2.4
|
lines-and-columns: 1.2.4
|
||||||
|
|
||||||
/parse-node-version/1.0.1:
|
|
||||||
resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
|
|
||||||
engines: {node: '>= 0.10'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/parse-numeric-range/1.3.0:
|
/parse-numeric-range/1.3.0:
|
||||||
resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==}
|
resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -15220,6 +15105,7 @@ packages:
|
|||||||
/pify/4.0.1:
|
/pify/4.0.1:
|
||||||
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
|
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/pirates/4.0.5:
|
/pirates/4.0.5:
|
||||||
resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==}
|
resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==}
|
||||||
@@ -15803,6 +15689,14 @@ packages:
|
|||||||
react: 17.0.2
|
react: 17.0.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/prism-react-renderer/1.3.1_react@18.1.0:
|
||||||
|
resolution: {integrity: sha512-xUeDMEz074d0zc5y6rxiMp/dlC7C+5IDDlaEUlcBOFE2wddz7hz5PNupb087mPwTt7T9BrFmewObfCBuf/LKwQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=0.14.9'
|
||||||
|
dependencies:
|
||||||
|
react: 18.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/prismjs/1.27.0:
|
/prismjs/1.27.0:
|
||||||
resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==}
|
resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -15865,11 +15759,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-wapJ3h/w8fRSyPEG0y2WMV+tf9xwvj3nxM6aHVuPEOwKs/t5xLSKZb44ubNTiqq2T6lmEMHEWGMTaU2L6ddaFA==}
|
resolution: {integrity: sha512-wapJ3h/w8fRSyPEG0y2WMV+tf9xwvj3nxM6aHVuPEOwKs/t5xLSKZb44ubNTiqq2T6lmEMHEWGMTaU2L6ddaFA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/prr/1.0.1:
|
|
||||||
resolution: {integrity: sha1-0/wRS6BplaRexok/SEzrHXj19HY=}
|
|
||||||
dev: false
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
/pseudomap/1.0.2:
|
/pseudomap/1.0.2:
|
||||||
resolution: {integrity: sha1-8FKijacOYYkX7wqKw0wa5aaChrM=}
|
resolution: {integrity: sha1-8FKijacOYYkX7wqKw0wa5aaChrM=}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -16163,23 +16052,6 @@ packages:
|
|||||||
/react-is/17.0.2:
|
/react-is/17.0.2:
|
||||||
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
|
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
|
||||||
|
|
||||||
/react-json-view/1.21.3_47ae209ab5c2ea051c9a2540426c6b2f:
|
|
||||||
resolution: {integrity: sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^17.0.0 || ^16.3.0 || ^15.5.4
|
|
||||||
react-dom: ^17.0.0 || ^16.3.0 || ^15.5.4
|
|
||||||
dependencies:
|
|
||||||
flux: 4.0.3_react@18.1.0
|
|
||||||
react: 18.1.0
|
|
||||||
react-base16-styling: 0.6.0
|
|
||||||
react-dom: 18.1.0_react@18.1.0
|
|
||||||
react-lifecycles-compat: 3.0.4
|
|
||||||
react-textarea-autosize: 8.3.3_@types+react@18.0.8+react@18.1.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/react'
|
|
||||||
- encoding
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/react-json-view/1.21.3_react-dom@17.0.2+react@17.0.2:
|
/react-json-view/1.21.3_react-dom@17.0.2+react@17.0.2:
|
||||||
resolution: {integrity: sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==}
|
resolution: {integrity: sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -16404,22 +16276,6 @@ packages:
|
|||||||
react-dom: 18.1.0_react@18.1.0
|
react-dom: 18.1.0_react@18.1.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-virtualized/9.22.3_react-dom@18.1.0+react@18.1.0:
|
|
||||||
resolution: {integrity: sha512-MKovKMxWTcwPSxE1kK1HcheQTWfuCxAuBoSTf2gwyMM21NdX/PXUhnoP8Uc5dRKd+nKm8v41R36OellhdCpkrw==}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^15.3.0 || ^16.0.0-alpha
|
|
||||||
react-dom: ^15.3.0 || ^16.0.0-alpha
|
|
||||||
dependencies:
|
|
||||||
'@babel/runtime': 7.17.9
|
|
||||||
clsx: 1.1.1
|
|
||||||
dom-helpers: 5.2.1
|
|
||||||
loose-envify: 1.4.0
|
|
||||||
prop-types: 15.8.1
|
|
||||||
react: 18.1.0
|
|
||||||
react-dom: 18.1.0_react@18.1.0
|
|
||||||
react-lifecycles-compat: 3.0.4
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/react/17.0.2:
|
/react/17.0.2:
|
||||||
resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==}
|
resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -16913,50 +16769,6 @@ packages:
|
|||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/rsuite-table/5.5.0_28428ba0c391cf6cfa125a69ce03058f:
|
|
||||||
resolution: {integrity: sha512-Bh4VUWsN8Q8q1oTvBx+Q4N05OpScsL7nEBi82M9ryY0gss7V0W8jaqBlJdvbxMZhZow3yfka0BLcbzeZfNADCA==}
|
|
||||||
peerDependencies:
|
|
||||||
prop-types: ^15.7.2
|
|
||||||
react: ^0.14.9 || >=15.3.0
|
|
||||||
react-dom: ^0.14.9 || >=15.3.0
|
|
||||||
dependencies:
|
|
||||||
'@babel/runtime': 7.17.9
|
|
||||||
'@juggle/resize-observer': 3.3.1
|
|
||||||
'@rsuite/icons': 1.0.2_react-dom@18.1.0+react@18.1.0
|
|
||||||
classnames: 2.3.1
|
|
||||||
dom-lib: 3.1.2
|
|
||||||
lodash: 4.17.21
|
|
||||||
prop-types: 15.8.1
|
|
||||||
react: 18.1.0
|
|
||||||
react-dom: 18.1.0_react@18.1.0
|
|
||||||
react-is: 17.0.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/rsuite/5.10.0_react-dom@18.1.0+react@18.1.0:
|
|
||||||
resolution: {integrity: sha512-7yUzv6s8UVzNmyBMHTiybAeqS0k3IM4aIpFMfdBxStbWc92I1VCb5o8pv4FKvOWF7gzq5g1Kj759nRxTwhouVw==}
|
|
||||||
peerDependencies:
|
|
||||||
react: '>=16.8.0'
|
|
||||||
react-dom: '>=16.8.0'
|
|
||||||
dependencies:
|
|
||||||
'@babel/runtime': 7.17.9
|
|
||||||
'@juggle/resize-observer': 3.3.1
|
|
||||||
'@rsuite/icons': 1.0.2_react-dom@18.1.0+react@18.1.0
|
|
||||||
'@types/chai': 4.3.1
|
|
||||||
'@types/lodash': 4.14.182
|
|
||||||
'@types/prop-types': 15.7.5
|
|
||||||
'@types/react-virtualized': 9.21.21
|
|
||||||
classnames: 2.3.1
|
|
||||||
date-fns: 2.28.0
|
|
||||||
dom-lib: 3.1.2
|
|
||||||
lodash: 4.17.21
|
|
||||||
prop-types: 15.8.1
|
|
||||||
react: 18.1.0
|
|
||||||
react-dom: 18.1.0_react@18.1.0
|
|
||||||
react-virtualized: 9.22.3_react-dom@18.1.0+react@18.1.0
|
|
||||||
rsuite-table: 5.5.0_28428ba0c391cf6cfa125a69ce03058f
|
|
||||||
schema-typed: 2.0.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/rtl-detect/1.0.4:
|
/rtl-detect/1.0.4:
|
||||||
resolution: {integrity: sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ==}
|
resolution: {integrity: sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -17028,12 +16840,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
|
|
||||||
/schema-typed/2.0.2:
|
|
||||||
resolution: {integrity: sha512-E8GAANjZ8oYwyQJEyf/93W1QERTCwu8N7aMUbgBWbYvqzZE8f/kAZaL5D9knr7yLEgMnwJQ4pd8x8EVZ0PpzUA==}
|
|
||||||
dependencies:
|
|
||||||
'@babel/runtime': 7.17.9
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/schema-utils/2.7.0:
|
/schema-utils/2.7.0:
|
||||||
resolution: {integrity: sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==}
|
resolution: {integrity: sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==}
|
||||||
engines: {node: '>= 8.9.0'}
|
engines: {node: '>= 8.9.0'}
|
||||||
@@ -19123,31 +18929,6 @@ packages:
|
|||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/vite/2.9.5_less@4.1.2:
|
|
||||||
resolution: {integrity: sha512-dvMN64X2YEQgSXF1lYabKXw3BbN6e+BL67+P3Vy4MacnY+UzT1AfkHiioFSi9+uiDUiaDy7Ax/LQqivk6orilg==}
|
|
||||||
engines: {node: '>=12.2.0'}
|
|
||||||
hasBin: true
|
|
||||||
peerDependencies:
|
|
||||||
less: '*'
|
|
||||||
sass: '*'
|
|
||||||
stylus: '*'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
less:
|
|
||||||
optional: true
|
|
||||||
sass:
|
|
||||||
optional: true
|
|
||||||
stylus:
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
esbuild: 0.14.37
|
|
||||||
less: 4.1.2
|
|
||||||
postcss: 8.4.12
|
|
||||||
resolve: 1.22.0
|
|
||||||
rollup: 2.70.2
|
|
||||||
optionalDependencies:
|
|
||||||
fsevents: 2.3.2
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/vite/2.9.7:
|
/vite/2.9.7:
|
||||||
resolution: {integrity: sha512-5hH7aNQe8rJiTTqCtPNX/6mIKlGw+1wg8UXwAxDIIN8XaSR+Zx3GT2zSu7QKa1vIaBqfUODGh3vpwY8r0AW/jw==}
|
resolution: {integrity: sha512-5hH7aNQe8rJiTTqCtPNX/6mIKlGw+1wg8UXwAxDIIN8XaSR+Zx3GT2zSu7QKa1vIaBqfUODGh3vpwY8r0AW/jw==}
|
||||||
engines: {node: '>=12.2.0'}
|
engines: {node: '>=12.2.0'}
|
||||||
|
|||||||
Reference in New Issue
Block a user