Compare commits
23 Commits
@nhost/cor
...
@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",
|
||||
"position": 4,
|
||||
"link": { "id": "platform/authentication/index", "type": "doc" }
|
||||
"position": 6
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: 'Nhost CLI'
|
||||
sidebar_position: 3
|
||||
title: 'CLI'
|
||||
sidebar_position: 11
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"label": "Database",
|
||||
"position": 2
|
||||
"position": 4
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
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.
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
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.
|
||||
@@ -21,7 +21,7 @@ Specifically, the following will be deployed:
|
||||
|
||||
## 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:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"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",
|
||||
"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`.
|
||||
|
||||
```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
|
||||
if (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"label": "Storage",
|
||||
"position": 5
|
||||
"position": 7
|
||||
}
|
||||
|
||||
@@ -38,17 +38,12 @@ import type { AppProps } from 'next/app';
|
||||
|
||||
import { NhostClient, NhostNextProvider } from '@nhost/nextjs';
|
||||
|
||||
import Header from '../components/Header';
|
||||
|
||||
const nhost = new NhostClient({ backendUrl: 'my-app.nhost.run' });
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<NhostNextProvider nhost={nhost} initial={pageProps.nhostSession}>
|
||||
<div>
|
||||
<Header />
|
||||
<Component {...pageProps} />
|
||||
</div>
|
||||
<Component {...pageProps} />
|
||||
</NhostNextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ const config = {
|
||||
href: 'https://app.nhost.io',
|
||||
className: 'header-get-started-link',
|
||||
position: 'right',
|
||||
label: 'Create an app',
|
||||
label: 'Get started',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -156,6 +156,10 @@
|
||||
color: var(--ifm-footer-link-hover-color);
|
||||
}
|
||||
|
||||
article {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.header-github-link:hover {
|
||||
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,
|
||||
"dependencies": {
|
||||
"@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/react": "workspace:*",
|
||||
"@nhost/react-apollo": "workspace:*",
|
||||
"@rsuite/icons": "^1.0.2",
|
||||
"graphql": "15.7.2",
|
||||
"less": "^4.1.2",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-icons": "^4.3.1",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-router": "^6.3.0",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"rsuite": "^5.10.0"
|
||||
"react-router-dom": "^6.3.0"
|
||||
},
|
||||
"lib": "workspace:*",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Container, Title } from '@mantine/core'
|
||||
import { useNhostClient } from '@nhost/react'
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Panel } from 'rsuite'
|
||||
|
||||
export const AboutPage: React.FC = () => {
|
||||
const nhost = useNhostClient()
|
||||
@@ -15,7 +15,8 @@ export const AboutPage: React.FC = () => {
|
||||
console.log(req)
|
||||
}
|
||||
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>
|
||||
<button onClick={fetch}>Fetch</button>
|
||||
<div>
|
||||
@@ -32,13 +33,13 @@ export const AboutPage: React.FC = () => {
|
||||
<ul>
|
||||
<li>React</li>
|
||||
<li>React-router</li>
|
||||
<li>RSuite</li>
|
||||
<li>Mantine</li>
|
||||
<li>and of course, the Nhost React client</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
Noew let's go to the <Link to="/">index page</Link>
|
||||
</div>
|
||||
</Panel>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
/* eslint-disable react/react-in-jsx-scope */
|
||||
import { useEffect } from 'react'
|
||||
import { Link, Route, Routes, useLocation, useNavigate } from 'react-router-dom'
|
||||
import { Container, Content, Header, Nav, Navbar } from 'rsuite'
|
||||
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom'
|
||||
|
||||
import { useAuthenticated, useSignOut } from '@nhost/react'
|
||||
import ExitIcon from '@rsuite/icons/Exit'
|
||||
|
||||
import { AuthGate, PublicGate } from './components/auth-gates'
|
||||
import { AboutPage } from './About'
|
||||
@@ -15,6 +13,10 @@ import { SignInPage } from './sign-in'
|
||||
import { SignUpPage } from './sign-up'
|
||||
|
||||
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() {
|
||||
const isAuthenticated = useAuthenticated()
|
||||
@@ -27,74 +29,76 @@ function App() {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [signedOut])
|
||||
return (
|
||||
<Container>
|
||||
<Header>
|
||||
<Navbar appearance="inverse">
|
||||
<Navbar.Brand as="div">
|
||||
<Link to="/">
|
||||
<img src="/logo.svg" alt="logo" style={{ maxHeight: '100%' }} />
|
||||
</Link>
|
||||
</Navbar.Brand>
|
||||
<Nav activeKey={location.pathname} onSelect={navigate}>
|
||||
{isAuthenticated && <Nav.Item eventKey="/profile">Profile</Nav.Item>}
|
||||
{isAuthenticated && <Nav.Item eventKey="/apollo">Apollo GraphQL</Nav.Item>}
|
||||
<Nav.Item eventKey="/about">About</Nav.Item>
|
||||
</Nav>
|
||||
<Nav pullRight>
|
||||
{isAuthenticated && (
|
||||
<Nav.Item icon={<ExitIcon />} onSelect={signOut}>
|
||||
Sign Out
|
||||
</Nav.Item>
|
||||
)}
|
||||
</Nav>
|
||||
</Navbar>
|
||||
</Header>
|
||||
<Content>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<AuthGate>
|
||||
<Home />
|
||||
</AuthGate>
|
||||
<MantineProvider
|
||||
withGlobalStyles
|
||||
withNormalizeCSS
|
||||
theme={{
|
||||
/** Put your mantine theme override here */
|
||||
colorScheme: 'light'
|
||||
}}
|
||||
>
|
||||
<NotificationsProvider>
|
||||
<AppShell
|
||||
padding="md"
|
||||
navbar={<NavBar />}
|
||||
header={
|
||||
<Header height={60} p="xs">
|
||||
{title}
|
||||
</Header>
|
||||
}
|
||||
styles={(theme) => ({
|
||||
main: {
|
||||
backgroundColor:
|
||||
theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.colors.gray[0]
|
||||
}
|
||||
/>
|
||||
<Route path="/about" element={<AboutPage />} />
|
||||
<Route
|
||||
path="/sign-in/*"
|
||||
element={
|
||||
<PublicGate>
|
||||
<SignInPage />
|
||||
</PublicGate>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/sign-up/*"
|
||||
element={
|
||||
<PublicGate>
|
||||
<SignUpPage />
|
||||
</PublicGate>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/profile"
|
||||
element={
|
||||
<AuthGate>
|
||||
<ProfilePage />
|
||||
</AuthGate>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/apollo"
|
||||
element={
|
||||
<AuthGate>
|
||||
<ApolloPage />
|
||||
</AuthGate>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</Content>
|
||||
</Container>
|
||||
})}
|
||||
>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<AuthGate>
|
||||
<Home />
|
||||
</AuthGate>
|
||||
}
|
||||
/>
|
||||
<Route path="/about" element={<AboutPage />} />
|
||||
<Route
|
||||
path="/sign-in/*"
|
||||
element={
|
||||
<PublicGate>
|
||||
<SignInPage />
|
||||
</PublicGate>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/sign-up/*"
|
||||
element={
|
||||
<PublicGate>
|
||||
<SignUpPage />
|
||||
</PublicGate>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/profile"
|
||||
element={
|
||||
<AuthGate>
|
||||
<ProfilePage />
|
||||
</AuthGate>
|
||||
}
|
||||
/>
|
||||
<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 { Panel } from 'rsuite'
|
||||
|
||||
const HomePage: React.FC = () => {
|
||||
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.
|
||||
</Panel>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
export default HomePage
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react'
|
||||
import { Panel, Table } from 'rsuite'
|
||||
|
||||
import { gql } from '@apollo/client'
|
||||
import { useAuthQuery } from '@nhost/react-apollo'
|
||||
import { Container, Loader, Title } from '@mantine/core'
|
||||
|
||||
const GET_BOOKS = gql`
|
||||
query BooksQuery {
|
||||
@@ -13,24 +13,22 @@ const GET_BOOKS = gql`
|
||||
}
|
||||
`
|
||||
|
||||
const { Column, Cell, HeaderCell } = Table
|
||||
export const ApolloPage: React.FC = () => {
|
||||
const { loading, data } = useAuthQuery(GET_BOOKS, {
|
||||
pollInterval: 5000,
|
||||
fetchPolicy: 'cache-and-network'
|
||||
})
|
||||
return (
|
||||
<Panel header="Apollo GraphQL">
|
||||
<Table loading={loading} data={data?.books || []} bordered cellBordered>
|
||||
<Column key="id" fixed width={300}>
|
||||
<HeaderCell>Id</HeaderCell>
|
||||
<Cell dataKey="id" />
|
||||
</Column>
|
||||
<Column key="title" fixed flexGrow={1}>
|
||||
<HeaderCell>Title</HeaderCell>
|
||||
<Cell dataKey="title" />
|
||||
</Column>
|
||||
</Table>
|
||||
</Panel>
|
||||
<Container>
|
||||
<Title>Apollo GraphQL</Title>
|
||||
{loading && <Loader />}
|
||||
{data?.books && (
|
||||
<ul>
|
||||
{data.books.map((book) => (
|
||||
<li key={book.id}>{book.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
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'
|
||||
|
||||
export const AuthGate: React.FC = ({ children }) => {
|
||||
export const AuthGate: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||
const { isLoading, isAuthenticated } = useAuthenticationStatus()
|
||||
const location = useLocation()
|
||||
if (isLoading) {
|
||||
@@ -17,7 +17,7 @@ export const AuthGate: React.FC = ({ children }) => {
|
||||
return <div>{children}</div>
|
||||
}
|
||||
|
||||
export const PublicGate: React.FC = ({ children }) => {
|
||||
export const PublicGate: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||
const { isLoading, isAuthenticated } = useAuthenticationStatus()
|
||||
const location = useLocation()
|
||||
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 { inspect } from '@xstate/inspect'
|
||||
|
||||
import 'rsuite/styles/index.less' // or 'rsuite/dist/rsuite.min.css'
|
||||
|
||||
import App from './App'
|
||||
|
||||
const nhost = new NhostClient({
|
||||
|
||||
@@ -1,63 +1,55 @@
|
||||
/* eslint-disable react/react-in-jsx-scope */
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Button, FlexboxGrid, Input, Message, Notification, Panel, toaster } from 'rsuite'
|
||||
import { useState } from 'react'
|
||||
|
||||
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 = () => {
|
||||
const [newEmail, setNewEmail] = useState('')
|
||||
const email = useEmail()
|
||||
const { changeEmail, error, needsEmailVerification } = useChangeEmail({
|
||||
const email = useUserEmail()
|
||||
const { changeEmail } = useChangeEmail({
|
||||
redirectTo: '/profile'
|
||||
})
|
||||
const [errorMessage, setErrorMessage] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (needsEmailVerification) {
|
||||
toaster.push(
|
||||
<Notification type="info" header="Info" closable>
|
||||
An email has been sent to {newEmail}. Please check your inbox and follow the link to
|
||||
confirm the email change.
|
||||
</Notification>
|
||||
)
|
||||
setNewEmail('')
|
||||
const change = async () => {
|
||||
if (newEmail && email === newEmail) {
|
||||
showNotification({
|
||||
title: 'Error',
|
||||
message: 'You need to set a different email as the current one'
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [needsEmailVerification])
|
||||
|
||||
// * Set error message from the registration hook errors
|
||||
useEffect(() => {
|
||||
setErrorMessage(error?.message || '')
|
||||
}, [error])
|
||||
// * Reset error message every time the email input changed
|
||||
useEffect(() => {
|
||||
setErrorMessage('')
|
||||
}, [newEmail])
|
||||
// * 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])
|
||||
|
||||
const result = await changeEmail(newEmail)
|
||||
if (result.needsEmailVerification) {
|
||||
showNotification({
|
||||
message: `An email has been sent to ${newEmail}. Please check your inbox and follow the link to confirm the email change.`
|
||||
})
|
||||
}
|
||||
if (result.error) {
|
||||
showNotification({
|
||||
color: 'red',
|
||||
title: 'Error',
|
||||
message: result.error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Panel header="Change email" bordered>
|
||||
<FlexboxGrid>
|
||||
<FlexboxGrid.Item colspan={12}>
|
||||
<Input value={newEmail} onChange={setNewEmail} placeholder="New email" />
|
||||
</FlexboxGrid.Item>
|
||||
<FlexboxGrid.Item colspan={12}>
|
||||
<Button onClick={() => changeEmail(email)} block appearance="primary">
|
||||
<Card shadow="sm" p="lg" m="sm">
|
||||
<Title>Change email</Title>
|
||||
<Grid>
|
||||
<Grid.Col>
|
||||
<TextInput
|
||||
value={newEmail}
|
||||
onChange={(e) => setNewEmail(e.target.value)}
|
||||
placeholder="New email"
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col>
|
||||
<Button onClick={change} fullWidth>
|
||||
Change
|
||||
</Button>
|
||||
</FlexboxGrid.Item>
|
||||
</FlexboxGrid>
|
||||
|
||||
{errorMessage && (
|
||||
<Message showIcon type="error">
|
||||
{errorMessage}
|
||||
</Message>
|
||||
)}
|
||||
</Panel>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,59 +1,46 @@
|
||||
import React from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Button, FlexboxGrid, Input, Message, Notification, Panel, toaster } from 'rsuite'
|
||||
import { useState } from '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 = () => {
|
||||
const [password, setPassword] = useState('')
|
||||
const { changePassword, isSuccess, error } = useChangePassword()
|
||||
const [errorMessage, setErrorMessage] = useState('')
|
||||
const { changePassword } = useChangePassword()
|
||||
|
||||
// * See https://github.com/rsuite/rsuite/issues/2336
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
setPassword('')
|
||||
toaster.push(
|
||||
<Notification type="info" header="Info" closable>
|
||||
Password changed successfully.
|
||||
</Notification>
|
||||
)
|
||||
setPassword('')
|
||||
const change = async () => {
|
||||
const result = await changePassword(password)
|
||||
if (result.isSuccess) {
|
||||
showNotification({
|
||||
message: `Password changed successfully.`
|
||||
})
|
||||
}
|
||||
}, [isSuccess])
|
||||
|
||||
// * Set error message from the registration hook errors
|
||||
useEffect(() => {
|
||||
setErrorMessage(error?.message || '')
|
||||
}, [error])
|
||||
// * Reset error message every time the password input changed
|
||||
useEffect(() => {
|
||||
setErrorMessage('')
|
||||
}, [password])
|
||||
|
||||
if (result.error) {
|
||||
showNotification({
|
||||
color: 'red',
|
||||
title: 'Error',
|
||||
message: result.error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Panel header="Change password" bordered>
|
||||
<FlexboxGrid>
|
||||
<FlexboxGrid.Item colspan={12}>
|
||||
<Input
|
||||
<Card shadow="sm" p="lg" m="sm">
|
||||
<Title>Change password</Title>
|
||||
<Grid>
|
||||
<Grid.Col>
|
||||
<PasswordInput
|
||||
value={password}
|
||||
onChange={setPassword}
|
||||
type="password"
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="New password"
|
||||
/>
|
||||
</FlexboxGrid.Item>
|
||||
<FlexboxGrid.Item colspan={12}>
|
||||
<Button onClick={() => changePassword(password)} block appearance="primary">
|
||||
</Grid.Col>
|
||||
<Grid.Col>
|
||||
<Button onClick={change} fullWidth>
|
||||
Change
|
||||
</Button>
|
||||
</FlexboxGrid.Item>
|
||||
</FlexboxGrid>
|
||||
|
||||
{errorMessage && (
|
||||
<Message showIcon type="error">
|
||||
{errorMessage}
|
||||
</Message>
|
||||
)}
|
||||
</Panel>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,59 +1,34 @@
|
||||
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 { ChangeEmail } from './change-email'
|
||||
import { ChangePassword } from './change-password'
|
||||
import { Mfa } from './mfa'
|
||||
import { Button, Card, Container, Title } from '@mantine/core'
|
||||
import { Prism } from '@mantine/prism'
|
||||
|
||||
export const ProfilePage: React.FC = () => {
|
||||
const claims = useHasuraClaims()
|
||||
const userData = useUserData()
|
||||
const nhost = useNhostClient()
|
||||
return (
|
||||
<Panel header="Profile page" bordered>
|
||||
<Row>
|
||||
<Col md={12} sm={24}>
|
||||
<Mfa />
|
||||
</Col>
|
||||
<Col md={12} sm={24}>
|
||||
<ChangeEmail />
|
||||
</Col>
|
||||
<Col md={12} sm={24}>
|
||||
<ChangePassword />
|
||||
</Col>
|
||||
<Col md={12} sm={24}>
|
||||
<Panel header="User information" bordered>
|
||||
{userData && (
|
||||
<ReactJson
|
||||
src={userData}
|
||||
displayDataTypes={false}
|
||||
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>
|
||||
<Container>
|
||||
<Title>Profile page</Title>
|
||||
<Mfa />
|
||||
<ChangeEmail />
|
||||
<ChangePassword />
|
||||
<Card shadow="sm" p="lg" m="sm">
|
||||
<Title>User information</Title>
|
||||
{userData && <Prism language="json">{JSON.stringify(userData, null, 2)}</Prism>}
|
||||
</Card>
|
||||
<Card shadow="sm" p="lg" m="sm">
|
||||
<Title>Hasura JWT claims</Title>
|
||||
<Button fullWidth onClick={() => nhost.auth.refreshSession()}>
|
||||
Refresh session
|
||||
</Button>
|
||||
{claims && <Prism language="json">{JSON.stringify(claims, null, 2)}</Prism>}
|
||||
</Card>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,30 +1,45 @@
|
||||
import React from 'react'
|
||||
import { useState } from 'react'
|
||||
import { Button, Input, Panel } from 'rsuite'
|
||||
|
||||
import { useConfigMfa } from '@nhost/react'
|
||||
import { Card, Button, TextInput, Title } from '@mantine/core'
|
||||
import { showNotification } from '@mantine/notifications'
|
||||
|
||||
export const Mfa: React.FC = () => {
|
||||
const [code, setCode] = useState('')
|
||||
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 (
|
||||
<Panel header="Activate 2-step verification" bordered>
|
||||
<Card shadow="sm" p="lg" m="sm">
|
||||
<Title>Activate 2-step verification</Title>
|
||||
{!isGenerated && (
|
||||
<Button block appearance="primary" onClick={generateQrCode}>
|
||||
<Button fullWidth onClick={generate}>
|
||||
Generate
|
||||
</Button>
|
||||
)}
|
||||
{isGenerated && !isActivated && (
|
||||
<div>
|
||||
<img alt="qrcode" src={qrCodeDataUrl} />
|
||||
<Input value={code} onChange={setCode} placeholder="Enter activation code" />
|
||||
<Button block appearance="primary" onClick={() => activateMfa(code)}>
|
||||
<TextInput
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
placeholder="Enter activation code"
|
||||
/>
|
||||
<Button fullWidth onClick={() => activateMfa(code)}>
|
||||
Activate
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{isActivated && <div>MFA has been activated!!!</div>}
|
||||
</Panel>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,100 +1,95 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { Button, Divider, Input, Message } from 'rsuite'
|
||||
/* eslint-disable react/react-in-jsx-scope */
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
import { useSignInEmailPassword } from '@nhost/react'
|
||||
|
||||
const Footer: React.FC = () => (
|
||||
<div>
|
||||
<Divider />
|
||||
<Button as={NavLink} to="/sign-in" block appearance="link">
|
||||
← Other Login Options
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
import { Button, Modal, TextInput } from '@mantine/core'
|
||||
import AuthLink from '../components/AuthLink'
|
||||
import { showNotification } from '@mantine/notifications'
|
||||
|
||||
export const EmailPassword: React.FC = () => {
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [otp, setOtp] = useState('')
|
||||
const { signInEmailPassword, error, needsMfaOtp, sendMfaOtp } = useSignInEmailPassword(
|
||||
email,
|
||||
password,
|
||||
otp
|
||||
)
|
||||
const { signInEmailPassword, needsMfaOtp, sendMfaOtp } = useSignInEmailPassword()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState('')
|
||||
// * 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 [emailVerificationToggle, setEmailVerificationToggle] = useState(false)
|
||||
|
||||
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)
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
<>
|
||||
<TextInput
|
||||
value={otp}
|
||||
onChange={setOtp}
|
||||
onChange={(e) => setOtp(e.target.value)}
|
||||
placeholder="One-time password"
|
||||
size="lg"
|
||||
autoFocus
|
||||
style={{ marginBottom: '0.5em' }}
|
||||
/>
|
||||
{errorMessage && (
|
||||
<Message showIcon type="error">
|
||||
{errorMessage}
|
||||
</Message>
|
||||
)}
|
||||
<Button appearance="primary" onClick={sendMfaOtp} block>
|
||||
<Button fullWidth onClick={sendOtp}>
|
||||
Send 2-step verification code
|
||||
</Button>
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
else
|
||||
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}
|
||||
onChange={setEmail}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="Email Address"
|
||||
size="lg"
|
||||
autoFocus
|
||||
style={{ marginBottom: '0.5em' }}
|
||||
/>
|
||||
<Input
|
||||
<TextInput
|
||||
value={password}
|
||||
onChange={setPassword}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
size="lg"
|
||||
style={{ marginBottom: '0.5em' }}
|
||||
/>
|
||||
|
||||
{errorMessage && (
|
||||
<Message showIcon type="error">
|
||||
{errorMessage}
|
||||
</Message>
|
||||
)}
|
||||
|
||||
<Button
|
||||
appearance="primary"
|
||||
onClick={async () => {
|
||||
const result = await signInEmailPassword(email, password)
|
||||
console.log(result)
|
||||
}}
|
||||
block
|
||||
>
|
||||
<Button fullWidth onClick={signIn}>
|
||||
Sign in
|
||||
</Button>
|
||||
<Button as={NavLink} block to="/sign-in/forgot-password">
|
||||
<AuthLink link="/sign-in/forgot-password" variant="white">
|
||||
Forgot password?
|
||||
</Button>
|
||||
<Footer />
|
||||
</div>
|
||||
</AuthLink>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { Divider } from '@mantine/core'
|
||||
import React from 'react'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { Button } from 'rsuite'
|
||||
import AuthLink from '../components/AuthLink'
|
||||
|
||||
import EmailPasswordlessForm from '../components/SignUpServerlessForm'
|
||||
|
||||
import { EmailPasswordlessForm } from '../components/email-passwordless-form'
|
||||
export const EmailPasswordless: React.FC = () => {
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<EmailPasswordlessForm />
|
||||
<Button as={NavLink} to="/sign-up" block appearance="link">
|
||||
<Divider />
|
||||
<AuthLink link="/sign-up" variant="white">
|
||||
← Other Login Options
|
||||
</Button>
|
||||
</div>
|
||||
</AuthLink>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,57 +1,43 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { Button, Divider, Input, Message, Notification, toaster } from 'rsuite'
|
||||
import React, { useState } from '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 = () => {
|
||||
const [email, setEmail] = useState('')
|
||||
const { resetPassword, isSent, error } = useResetPassword({ redirectTo: '/profile' })
|
||||
const { resetPassword } = useResetPassword({ redirectTo: '/profile' })
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState('')
|
||||
// * 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])
|
||||
|
||||
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>
|
||||
)
|
||||
const reset = async () => {
|
||||
const result = await resetPassword(email)
|
||||
if (result.isError) {
|
||||
showNotification({
|
||||
color: 'red',
|
||||
title: 'Error',
|
||||
message: result.error?.message
|
||||
})
|
||||
}
|
||||
}, [isSent])
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
<>
|
||||
<TextInput
|
||||
value={email}
|
||||
onChange={setEmail}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="Email Address"
|
||||
size="lg"
|
||||
autoFocus
|
||||
style={{ marginBottom: '0.5em' }}
|
||||
/>
|
||||
|
||||
{errorMessage && (
|
||||
<Message showIcon type="error">
|
||||
{errorMessage}
|
||||
</Message>
|
||||
)}
|
||||
|
||||
<Button appearance="primary" onClick={() => resetPassword(email)} block>
|
||||
<Button onClick={reset} fullWidth>
|
||||
Reset your password
|
||||
</Button>
|
||||
<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
|
||||
</Button>
|
||||
</div>
|
||||
</AuthLink>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,60 +1,49 @@
|
||||
import React from 'react'
|
||||
import { FaLock } from 'react-icons/fa'
|
||||
import { Link, NavLink, Route, Routes } from 'react-router-dom'
|
||||
import { Button, Divider, FlexboxGrid, IconButton, Panel } from 'rsuite'
|
||||
import { Link, Route, Routes } from 'react-router-dom'
|
||||
|
||||
import { Icon } from '@rsuite/icons'
|
||||
|
||||
import { OAuthLinks } from '../components'
|
||||
import { VerificationEmailSent } from '../verification-email-sent'
|
||||
import OAuthLinks from '../components/OauthLinks'
|
||||
|
||||
import { EmailPassword } from './email-password'
|
||||
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 { useSignInAnonymous } from '@nhost/react'
|
||||
|
||||
const Index: React.FC = () => (
|
||||
<div>
|
||||
<>
|
||||
<OAuthLinks />
|
||||
<Divider />
|
||||
<IconButton
|
||||
block
|
||||
icon={<Icon as={FaLock} />}
|
||||
appearance="ghost"
|
||||
as={NavLink}
|
||||
to="/sign-in/email-passwordless"
|
||||
>
|
||||
<Divider my="sm" />
|
||||
<AuthLink icon={<FaLock />} variant="outline" link="/sign-in/email-passwordless">
|
||||
Continue with passwordless email
|
||||
</IconButton>
|
||||
<Button as={NavLink} to="/sign-in/email-password" block appearance="link">
|
||||
</AuthLink>
|
||||
<AuthLink variant="subtle" link="/sign-in/email-password">
|
||||
Continue with email + password
|
||||
</Button>
|
||||
</div>
|
||||
</AuthLink>
|
||||
</>
|
||||
)
|
||||
|
||||
export const SignInPage: React.FC = () => {
|
||||
// const { signIn } = useSignInAnonymous()
|
||||
return (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<FlexboxGrid justify="center">
|
||||
<FlexboxGrid.Item colspan={12}>
|
||||
<Panel header={<h2>Log in to the Application</h2>} bordered>
|
||||
<Routes>
|
||||
<Route path="/" element={<Index />} />
|
||||
<Route path="/email-passwordless" element={<EmailPasswordless />} />
|
||||
<Route path="/email-password" element={<EmailPassword />} />
|
||||
<Route path="/verification-email-sent" element={<VerificationEmailSent />} />
|
||||
<Route path="/forgot-password" element={<ForgotPassword />} />
|
||||
</Routes>
|
||||
</Panel>
|
||||
</FlexboxGrid.Item>
|
||||
</FlexboxGrid>
|
||||
<Divider />
|
||||
Don‘t have an account? <Link to="/sign-up">Sign up</Link>
|
||||
{/* or{' '}
|
||||
<a href="#" onClick={signIn}>
|
||||
enter the app anonymously
|
||||
</a> */}
|
||||
</div>
|
||||
<AuthLayout
|
||||
title="Log in to the Application"
|
||||
footer={
|
||||
<Center>
|
||||
<Text>
|
||||
Don‘t have an account?{' '}
|
||||
<Anchor component={Link} to="/sign-up">
|
||||
Sign up
|
||||
</Anchor>
|
||||
</Text>
|
||||
</Center>
|
||||
}
|
||||
>
|
||||
<Routes>
|
||||
<Route path="/" element={<Index />} />
|
||||
<Route path="/email-password" element={<EmailPassword />} />
|
||||
<Route path="/forgot-password" element={<ForgotPassword />} />
|
||||
<Route path="/email-passwordless" element={<EmailPasswordless />} />
|
||||
</Routes>
|
||||
</AuthLayout>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,105 +1,95 @@
|
||||
/* eslint-disable react/react-in-jsx-scope */
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { NavLink, useNavigate } from 'react-router-dom'
|
||||
import { Button, Input, Message } from 'rsuite'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
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 = () => {
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [firstName, setFirstName] = 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(
|
||||
() => ({ displayName: `${firstName} ${lastName}`, metadata: { firstName, lastName } }),
|
||||
[firstName, lastName]
|
||||
)
|
||||
const navigate = useNavigate()
|
||||
const [confirmPassword, setConfirmPassword] = useState('')
|
||||
const { signUpEmailPassword, error, needsEmailVerification, isSuccess } =
|
||||
useSignUpEmailPassword(options)
|
||||
const [errorMessage, setErrorMessage] = useState('')
|
||||
useEffect(() => {
|
||||
if (needsEmailVerification) navigate('/sign-up/verification-email-sent')
|
||||
else if (isSuccess) navigate('/')
|
||||
const { signUpEmailPassword } = useSignUpEmailPassword(options)
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [needsEmailVerification, isSuccess])
|
||||
|
||||
// * Set error message from the registration hook errors
|
||||
useEffect(() => {
|
||||
setErrorMessage(error?.message || '')
|
||||
}, [error])
|
||||
// * Reset error message every time the email or password input changed
|
||||
useEffect(() => {
|
||||
setErrorMessage('')
|
||||
}, [email, password])
|
||||
// * Show an error message when passwords are different
|
||||
useEffect(() => {
|
||||
if (password !== confirmPassword) setErrorMessage('Both passwords must be the same')
|
||||
else setErrorMessage('')
|
||||
}, [password, confirmPassword])
|
||||
const signUp = async () => {
|
||||
const result = await signUpEmailPassword(email, password, { metadata: { firstName, lastName } })
|
||||
if (result.isError) {
|
||||
showNotification({
|
||||
color: 'red',
|
||||
title: 'Error',
|
||||
message: result.error?.message
|
||||
})
|
||||
} else if (result.needsEmailVerification) {
|
||||
setEmailVerificationToggle(true)
|
||||
} else {
|
||||
navigate('/', { replace: true })
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
value={firstName}
|
||||
onChange={setFirstName}
|
||||
placeholder="First name"
|
||||
size="lg"
|
||||
autoFocus
|
||||
style={{ marginBottom: '0.5em' }}
|
||||
/>
|
||||
<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)
|
||||
<>
|
||||
<Modal
|
||||
title="Verification email sent"
|
||||
transition="fade"
|
||||
centered
|
||||
transitionDuration={600}
|
||||
opened={emailVerificationToggle}
|
||||
onClose={() => {
|
||||
setEmailVerificationToggle(false)
|
||||
}}
|
||||
block
|
||||
>
|
||||
Sign up
|
||||
</Button>
|
||||
<Button as={NavLink} to="/sign-up" block appearance="link">
|
||||
A email has been sent to {email}. Please follow the link to verify your email address and to
|
||||
complete your registration.
|
||||
</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
|
||||
</Button>
|
||||
</div>
|
||||
</AuthLink>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { Divider } from '@mantine/core'
|
||||
import React from 'react'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { Button } from 'rsuite'
|
||||
import AuthLink from '../components/AuthLink'
|
||||
|
||||
import EmailPasswordlessForm from '../components/SignUpServerlessForm'
|
||||
|
||||
import { EmailPasswordlessForm } from '../components/email-passwordless-form'
|
||||
export const EmailPasswordless: React.FC = () => {
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<EmailPasswordlessForm />
|
||||
<Button as={NavLink} to="/sign-up" block appearance="link">
|
||||
<Divider />
|
||||
<AuthLink link="/sign-up" variant="white">
|
||||
← Other Registration Options
|
||||
</Button>
|
||||
</div>
|
||||
</AuthLink>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,52 +1,47 @@
|
||||
import React from 'react'
|
||||
import { FaLock } from 'react-icons/fa'
|
||||
import { Link, NavLink, Route, Routes } from 'react-router-dom'
|
||||
import { Button, Divider, FlexboxGrid, IconButton, Panel } from 'rsuite'
|
||||
import { Link, Route, Routes } from 'react-router-dom'
|
||||
|
||||
import { Icon } from '@rsuite/icons'
|
||||
|
||||
import { OAuthLinks } from '../components'
|
||||
import { VerificationEmailSent } from '../verification-email-sent'
|
||||
import OAuthLinks from '../components/OauthLinks'
|
||||
|
||||
import { EmailPassword } from './email-password'
|
||||
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 = () => (
|
||||
<div>
|
||||
<>
|
||||
<OAuthLinks />
|
||||
<Divider />
|
||||
<IconButton
|
||||
block
|
||||
icon={<Icon as={FaLock} />}
|
||||
appearance="ghost"
|
||||
as={NavLink}
|
||||
to="/sign-up/email-passwordless"
|
||||
>
|
||||
<Divider my="sm" />
|
||||
<AuthLink icon={<FaLock />} variant="outline" link="/sign-up/email-passwordless">
|
||||
Continue with passwordless email
|
||||
</IconButton>
|
||||
<Button as={NavLink} to="/sign-up/email-password" block appearance="link">
|
||||
</AuthLink>
|
||||
<AuthLink variant="subtle" link="/sign-up/email-password">
|
||||
Continue with email + password
|
||||
</Button>
|
||||
</div>
|
||||
</AuthLink>
|
||||
</>
|
||||
)
|
||||
|
||||
export const SignUpPage: React.FC = () => {
|
||||
return (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<FlexboxGrid justify="center">
|
||||
<FlexboxGrid.Item colspan={12}>
|
||||
<Panel header={<h2>Sign up</h2>} bordered>
|
||||
<Routes>
|
||||
<Route path="/" element={<Index />} />
|
||||
<Route path="/email-password" element={<EmailPassword />} />
|
||||
<Route path="/email-passwordless" element={<EmailPasswordless />} />
|
||||
<Route path="/verification-email-sent" element={<VerificationEmailSent />} />
|
||||
</Routes>
|
||||
</Panel>
|
||||
</FlexboxGrid.Item>
|
||||
</FlexboxGrid>
|
||||
<Divider />
|
||||
Already have an account? <Link to="/sign-in">Log in</Link>
|
||||
</div>
|
||||
<AuthLayout
|
||||
title="Sign up"
|
||||
footer={
|
||||
<Center>
|
||||
<Text>
|
||||
Already have an account?{' '}
|
||||
<Anchor component={Link} to="/sign-in">
|
||||
Log in
|
||||
</Anchor>
|
||||
</Text>
|
||||
</Center>
|
||||
}
|
||||
>
|
||||
<Routes>
|
||||
<Route path="/" element={<Index />} />
|
||||
<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
|
||||
|
||||
## 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
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/apollo",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.2",
|
||||
"description": "Nhost Apollo Client library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# @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
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/core",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.2",
|
||||
"description": "Nhost core client library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -75,38 +75,31 @@ export const createAuthMachine = ({
|
||||
type: 'parallel',
|
||||
states: {
|
||||
authentication: {
|
||||
initial: 'importingRefreshToken',
|
||||
initial: 'starting',
|
||||
on: {
|
||||
SESSION_UPDATE: [
|
||||
{
|
||||
cond: 'hasSession',
|
||||
actions: ['saveSession', 'persist', 'resetTimer', 'reportTokenChanged'],
|
||||
actions: ['saveSession', 'resetTimer', 'reportTokenChanged'],
|
||||
target: '.signedIn'
|
||||
}
|
||||
]
|
||||
},
|
||||
states: {
|
||||
importingRefreshToken: {
|
||||
starting: {
|
||||
tags: ['loading'],
|
||||
always: { cond: 'isSignedIn', target: 'signedIn' },
|
||||
invoke: {
|
||||
id: 'importRefreshToken',
|
||||
src: 'importRefreshToken',
|
||||
onDone: { actions: 'saveRefreshToken', target: 'starting' },
|
||||
onDone: {
|
||||
actions: ['saveSession', 'reportTokenChanged'],
|
||||
target: 'signedIn'
|
||||
},
|
||||
onError: { actions: ['saveAuthenticationError'], target: 'signedOut' }
|
||||
}
|
||||
},
|
||||
starting: {
|
||||
always: [
|
||||
{
|
||||
cond: 'hasRefreshTokenWithoutSession',
|
||||
target: 'authenticating.token'
|
||||
},
|
||||
{ cond: 'hasAuthenticationError', target: 'signedOut.failed' },
|
||||
'signedOut'
|
||||
]
|
||||
},
|
||||
signedOut: {
|
||||
tags: ['ready'],
|
||||
initial: 'noErrors',
|
||||
entry: 'reportSignedOut',
|
||||
states: {
|
||||
@@ -236,7 +229,7 @@ export const createAuthMachine = ({
|
||||
src: 'signInPasswordlessSmsOtp',
|
||||
id: 'authenticatePasswordlessSmsOtp',
|
||||
onDone: {
|
||||
actions: ['saveSession', 'persist', 'reportTokenChanged'],
|
||||
actions: ['saveSession', 'reportTokenChanged'],
|
||||
target: '#nhost.authentication.signedIn'
|
||||
},
|
||||
onError: {
|
||||
@@ -256,7 +249,7 @@ export const createAuthMachine = ({
|
||||
target: '#nhost.authentication.signedOut.needsMfa'
|
||||
},
|
||||
{
|
||||
actions: ['saveSession', 'persist', 'reportTokenChanged'],
|
||||
actions: ['saveSession', 'reportTokenChanged'],
|
||||
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: {
|
||||
invoke: {
|
||||
src: 'signInAnonymous',
|
||||
id: 'authenticateAnonymously',
|
||||
onDone: {
|
||||
actions: ['saveSession', 'persist', 'reportTokenChanged'],
|
||||
actions: ['saveSession', 'reportTokenChanged'],
|
||||
target: '#nhost.authentication.signedIn'
|
||||
},
|
||||
onError: {
|
||||
@@ -307,7 +286,7 @@ export const createAuthMachine = ({
|
||||
src: 'signInMfaTotp',
|
||||
id: 'signInMfaTotp',
|
||||
onDone: {
|
||||
actions: ['saveSession', 'persist', 'reportTokenChanged'],
|
||||
actions: ['saveSession', 'reportTokenChanged'],
|
||||
target: '#nhost.authentication.signedIn'
|
||||
},
|
||||
onError: {
|
||||
@@ -329,7 +308,7 @@ export const createAuthMachine = ({
|
||||
{
|
||||
cond: 'hasSession',
|
||||
target: '#nhost.authentication.signedIn',
|
||||
actions: ['saveSession', 'persist', 'reportTokenChanged']
|
||||
actions: ['saveSession', 'reportTokenChanged']
|
||||
},
|
||||
{
|
||||
target: '#nhost.authentication.signedOut.needsEmailVerification'
|
||||
@@ -347,11 +326,9 @@ export const createAuthMachine = ({
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
signedIn: {
|
||||
tags: ['ready'],
|
||||
type: 'parallel',
|
||||
entry: ['reportSignedIn', 'cleanUrl'],
|
||||
entry: ['reportSignedIn', 'cleanUrl', 'broadcastToken'],
|
||||
on: {
|
||||
SIGNOUT: '#nhost.authentication.signedOut.signingOut',
|
||||
DEANONYMIZE: {
|
||||
@@ -401,12 +378,7 @@ export const createAuthMachine = ({
|
||||
src: 'refreshToken',
|
||||
id: 'refreshToken',
|
||||
onDone: {
|
||||
actions: [
|
||||
'saveSession',
|
||||
'persist',
|
||||
'resetTimer',
|
||||
'reportTokenChanged'
|
||||
],
|
||||
actions: ['saveSession', 'resetTimer', 'reportTokenChanged'],
|
||||
target: 'pending'
|
||||
},
|
||||
onError: [
|
||||
@@ -455,7 +427,7 @@ export const createAuthMachine = ({
|
||||
src: 'refreshToken',
|
||||
id: 'authenticateWithToken',
|
||||
onDone: {
|
||||
actions: ['saveSession', 'persist', 'reportTokenChanged'],
|
||||
actions: ['saveSession', 'reportTokenChanged'],
|
||||
target: ['#nhost.authentication.signedIn', 'idle.noErrors']
|
||||
},
|
||||
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({
|
||||
user: (_, e: any) => e.data?.session?.user,
|
||||
accessToken: (_, e) => ({
|
||||
value: e.data?.session?.accessToken,
|
||||
expiresAt: new Date(Date.now() + e.data?.session?.accessTokenExpiresIn * 1_000)
|
||||
}),
|
||||
refreshToken: (_, e) => ({ value: e.data?.session?.refreshToken })
|
||||
user: (_, { data }: any) => data?.session?.user,
|
||||
accessToken: (_, { data }: any) => {
|
||||
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)
|
||||
}
|
||||
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({
|
||||
mfa: (_, e: any) => e.data?.mfa ?? null
|
||||
@@ -543,22 +530,7 @@ export const createAuthMachine = ({
|
||||
saveNoMfaTicketError: assign({
|
||||
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({
|
||||
refreshToken: (_) => {
|
||||
storageSetter(NHOST_REFRESH_TOKEN_KEY, null)
|
||||
@@ -591,12 +563,9 @@ export const createAuthMachine = ({
|
||||
|
||||
guards: {
|
||||
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,
|
||||
noMfaTicket: (ctx, { ticket }) => !ticket && !ctx.mfa?.ticket,
|
||||
hasRefreshToken: (ctx) => !!ctx.refreshToken.value,
|
||||
hasAuthenticationError: (ctx) => !!ctx.errors.authentication,
|
||||
isAutoRefreshDisabled: () => !autoRefreshToken,
|
||||
refreshTimerShouldRefresh: (ctx) => {
|
||||
const { expiresAt } = ctx.accessToken
|
||||
@@ -686,15 +655,17 @@ export const createAuthMachine = ({
|
||||
}),
|
||||
|
||||
importRefreshToken: async () => {
|
||||
const stringExpiresAt = await storageGetter(NHOST_JWT_EXPIRES_AT_KEY)
|
||||
const expiresAt = stringExpiresAt ? new Date(stringExpiresAt) : null
|
||||
let refreshToken = await storageGetter(NHOST_REFRESH_TOKEN_KEY)
|
||||
let error: ValidationErrorPayload | null = null
|
||||
if (autoSignIn) {
|
||||
const urlToken = getParameterByName('refreshToken') || null
|
||||
if (urlToken) {
|
||||
if (!refreshToken) {
|
||||
// ? Which takes precedence? localStorage or the url?
|
||||
refreshToken = urlToken
|
||||
try {
|
||||
const session = await postRequest('/token', {
|
||||
refreshToken: urlToken
|
||||
})
|
||||
return { session }
|
||||
} catch (exception) {
|
||||
error = (exception as { error: ValidationErrorPayload }).error
|
||||
}
|
||||
} else {
|
||||
const error = getParameterByName('error')
|
||||
@@ -709,14 +680,19 @@ export const createAuthMachine = ({
|
||||
}
|
||||
}
|
||||
}
|
||||
return refreshToken
|
||||
? {
|
||||
refreshToken,
|
||||
expiresAt
|
||||
}
|
||||
: Promise.reject<{ error: ValidationErrorPayload }>({
|
||||
error: null
|
||||
const storageToken = await storageGetter(NHOST_REFRESH_TOKEN_KEY)
|
||||
if (storageToken) {
|
||||
try {
|
||||
const session = await postRequest('/token', {
|
||||
refreshToken: storageToken
|
||||
})
|
||||
return { session }
|
||||
} catch (exception) {
|
||||
error = (exception as { error: ValidationErrorPayload }).error
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject<{ error: ValidationErrorPayload }>({ error })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,19 +5,9 @@ export interface Typegen0 {
|
||||
eventsCausingActions: {
|
||||
saveSession:
|
||||
| 'SESSION_UPDATE'
|
||||
| 'done.invoke.importRefreshToken'
|
||||
| 'done.invoke.authenticatePasswordlessSmsOtp'
|
||||
| '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.signInMfaTotp'
|
||||
| 'done.invoke.registerUser'
|
||||
@@ -26,22 +16,20 @@ export interface Typegen0 {
|
||||
resetTimer: 'SESSION_UPDATE' | 'done.invoke.refreshToken' | ''
|
||||
reportTokenChanged:
|
||||
| 'SESSION_UPDATE'
|
||||
| 'done.invoke.importRefreshToken'
|
||||
| 'done.invoke.authenticatePasswordlessSmsOtp'
|
||||
| 'done.invoke.authenticateUserWithPassword'
|
||||
| 'done.invoke.signInToken'
|
||||
| 'done.invoke.authenticateAnonymously'
|
||||
| 'done.invoke.signInMfaTotp'
|
||||
| 'done.invoke.registerUser'
|
||||
| 'done.invoke.refreshToken'
|
||||
| 'done.invoke.authenticateWithToken'
|
||||
saveRefreshToken: 'done.invoke.importRefreshToken'
|
||||
saveAuthenticationError:
|
||||
| 'error.platform.importRefreshToken'
|
||||
| 'error.platform.authenticatePasswordlessEmail'
|
||||
| 'error.platform.authenticatePasswordlessSms'
|
||||
| 'error.platform.authenticatePasswordlessSmsOtp'
|
||||
| 'error.platform.authenticateUserWithPassword'
|
||||
| 'error.platform.signInToken'
|
||||
| 'error.platform.authenticateAnonymously'
|
||||
| 'error.platform.signInMfaTotp'
|
||||
saveInvalidEmail: 'SIGNIN_PASSWORD' | 'SIGNIN_PASSWORDLESS_EMAIL'
|
||||
@@ -51,39 +39,50 @@ export interface Typegen0 {
|
||||
saveInvalidSignUpPassword: 'SIGNUP_EMAIL_PASSWORD'
|
||||
saveNoMfaTicketError: 'SIGNIN_MFA_TOTP'
|
||||
saveMfaTicket: 'done.invoke.authenticateUserWithPassword'
|
||||
broadcastToken: 'done.invoke.signInToken'
|
||||
saveRegisrationError: 'error.platform.registerUser'
|
||||
saveRefreshAttempt: 'error.platform.refreshToken'
|
||||
reportSignedOut:
|
||||
| 'error.platform.importRefreshToken'
|
||||
| ''
|
||||
| 'error.platform.authenticateWithToken'
|
||||
reportSignedOut: 'error.platform.importRefreshToken' | 'error.platform.authenticateWithToken'
|
||||
resetAuthenticationError: 'xstate.init'
|
||||
destroyRefreshToken: 'xstate.init'
|
||||
clearContextExceptRefreshToken: 'SIGNOUT'
|
||||
resetSignUpError: 'SIGNUP_EMAIL_PASSWORD'
|
||||
reportSignedIn:
|
||||
| 'SESSION_UPDATE'
|
||||
| 'done.invoke.importRefreshToken'
|
||||
| ''
|
||||
| 'done.invoke.authenticatePasswordlessSmsOtp'
|
||||
| 'done.invoke.authenticateUserWithPassword'
|
||||
| 'done.invoke.signInToken'
|
||||
| 'done.invoke.authenticateAnonymously'
|
||||
| 'done.invoke.signInMfaTotp'
|
||||
| 'done.invoke.registerUser'
|
||||
| 'done.invoke.authenticateWithToken'
|
||||
cleanUrl:
|
||||
| '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.authenticateUserWithPassword'
|
||||
| 'done.invoke.signInToken'
|
||||
| 'done.invoke.authenticateAnonymously'
|
||||
| 'done.invoke.signInMfaTotp'
|
||||
| 'done.invoke.registerUser'
|
||||
| 'done.invoke.authenticateWithToken'
|
||||
}
|
||||
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': {
|
||||
type: 'done.invoke.authenticatePasswordlessSmsOtp'
|
||||
data: unknown
|
||||
@@ -94,11 +93,6 @@ export interface Typegen0 {
|
||||
data: unknown
|
||||
__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': {
|
||||
type: 'done.invoke.authenticateAnonymously'
|
||||
data: unknown
|
||||
@@ -125,11 +119,6 @@ export interface Typegen0 {
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
||||
}
|
||||
'': { 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': {
|
||||
type: 'error.platform.importRefreshToken'
|
||||
data: unknown
|
||||
@@ -150,7 +139,6 @@ export interface Typegen0 {
|
||||
type: 'error.platform.authenticateUserWithPassword'
|
||||
data: unknown
|
||||
}
|
||||
'error.platform.signInToken': { type: 'error.platform.signInToken'; data: unknown }
|
||||
'error.platform.authenticateAnonymously': {
|
||||
type: 'error.platform.authenticateAnonymously'
|
||||
data: unknown
|
||||
@@ -190,13 +178,10 @@ export interface Typegen0 {
|
||||
signInPasswordlessSms: 'done.invoke.authenticatePasswordlessSms'
|
||||
signInPasswordlessSmsOtp: 'done.invoke.authenticatePasswordlessSmsOtp'
|
||||
signInPassword: 'done.invoke.authenticateUserWithPassword'
|
||||
refreshToken:
|
||||
| 'done.invoke.signInToken'
|
||||
| 'done.invoke.refreshToken'
|
||||
| 'done.invoke.authenticateWithToken'
|
||||
signInAnonymous: 'done.invoke.authenticateAnonymously'
|
||||
signInMfaTotp: 'done.invoke.signInMfaTotp'
|
||||
registerUser: 'done.invoke.registerUser'
|
||||
refreshToken: 'done.invoke.refreshToken' | 'done.invoke.authenticateWithToken'
|
||||
}
|
||||
missingImplementations: {
|
||||
actions: never
|
||||
@@ -206,7 +191,6 @@ export interface Typegen0 {
|
||||
}
|
||||
eventsCausingServices: {
|
||||
importRefreshToken: 'xstate.init'
|
||||
refreshToken: '' | 'TRY_TOKEN'
|
||||
signInPassword: 'SIGNIN_PASSWORD'
|
||||
signInPasswordlessEmail: 'SIGNIN_PASSWORDLESS_EMAIL'
|
||||
signInPasswordlessSms: 'SIGNIN_PASSWORDLESS_SMS'
|
||||
@@ -215,12 +199,11 @@ export interface Typegen0 {
|
||||
signInAnonymous: 'SIGNIN_ANONYMOUS'
|
||||
signInMfaTotp: 'SIGNIN_MFA_TOTP'
|
||||
signout: 'SIGNOUT'
|
||||
refreshToken: '' | 'TRY_TOKEN'
|
||||
}
|
||||
eventsCausingGuards: {
|
||||
hasSession: 'SESSION_UPDATE' | 'done.invoke.registerUser'
|
||||
isSignedIn: '' | 'error.platform.authenticateWithToken'
|
||||
hasRefreshTokenWithoutSession: ''
|
||||
hasAuthenticationError: ''
|
||||
invalidEmail: 'SIGNIN_PASSWORD' | 'SIGNIN_PASSWORDLESS_EMAIL' | 'SIGNUP_EMAIL_PASSWORD'
|
||||
invalidPassword: 'SIGNIN_PASSWORD' | 'SIGNUP_EMAIL_PASSWORD'
|
||||
invalidPhoneNumber: 'SIGNIN_PASSWORDLESS_SMS' | 'SIGNIN_PASSWORDLESS_SMS_OTP'
|
||||
@@ -235,7 +218,6 @@ export interface Typegen0 {
|
||||
eventsCausingDelays: {}
|
||||
matchesStates:
|
||||
| 'authentication'
|
||||
| 'authentication.importingRefreshToken'
|
||||
| 'authentication.starting'
|
||||
| 'authentication.signedOut'
|
||||
| 'authentication.signedOut.noErrors'
|
||||
@@ -255,7 +237,6 @@ export interface Typegen0 {
|
||||
| 'authentication.authenticating.passwordlessSms'
|
||||
| 'authentication.authenticating.passwordlessSmsOtp'
|
||||
| 'authentication.authenticating.password'
|
||||
| 'authentication.authenticating.token'
|
||||
| 'authentication.authenticating.anonymous'
|
||||
| 'authentication.authenticating.mfa'
|
||||
| 'authentication.authenticating.mfa.totp'
|
||||
@@ -278,7 +259,6 @@ export interface Typegen0 {
|
||||
| 'token.running'
|
||||
| {
|
||||
authentication?:
|
||||
| 'importingRefreshToken'
|
||||
| 'starting'
|
||||
| 'signedOut'
|
||||
| 'authenticating'
|
||||
@@ -304,7 +284,6 @@ export interface Typegen0 {
|
||||
| 'passwordlessSms'
|
||||
| 'passwordlessSmsOtp'
|
||||
| 'password'
|
||||
| 'token'
|
||||
| 'anonymous'
|
||||
| 'mfa'
|
||||
| { mfa?: 'totp' }
|
||||
@@ -323,5 +302,5 @@ export interface Typegen0 {
|
||||
}
|
||||
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 = (
|
||||
clientStorageType: ClientStorageType,
|
||||
clientStorage?: ClientStorage
|
||||
): StorageGetter => {
|
||||
if (!clientStorage || clientStorageType === 'localStorage' || clientStorageType === 'web') {
|
||||
if (clientStorageType === 'localStorage' || clientStorageType === 'web') {
|
||||
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') {
|
||||
return (key) => {
|
||||
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 (clientStorage.getItem && clientStorage.removeItem) {
|
||||
return clientStorage.getItem
|
||||
@@ -89,25 +77,9 @@ export const localStorageSetter = (
|
||||
clientStorageType: ClientStorageType,
|
||||
clientStorage?: ClientStorage
|
||||
): StorageSetter => {
|
||||
if (!clientStorage || clientStorageType === 'localStorage' || clientStorageType === 'web') {
|
||||
if (clientStorageType === 'localStorage' || clientStorageType === 'web') {
|
||||
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') {
|
||||
return (key, value) => {
|
||||
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 (!clientStorage.removeItem) {
|
||||
throw Error(
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# @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
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/hasura-auth-js",
|
||||
"version": "1.1.2",
|
||||
"version": "1.1.4",
|
||||
"description": "Hasura-auth client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -745,7 +745,7 @@ export class HasuraAuthClient {
|
||||
if (!interpreter) {
|
||||
throw Error('Auth interpreter not set')
|
||||
}
|
||||
if (interpreter.state.hasTag('ready')) {
|
||||
if (!interpreter.state.hasTag('loading')) {
|
||||
return Promise.resolve(interpreter)
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -754,7 +754,7 @@ export class HasuraAuthClient {
|
||||
TIMEOUT_IN_SECONS * 1_000
|
||||
)
|
||||
interpreter.onTransition((state) => {
|
||||
if (state.hasTag('ready')) {
|
||||
if (!state.hasTag('loading')) {
|
||||
clearTimeout(timer)
|
||||
return resolve(interpreter)
|
||||
}
|
||||
@@ -763,7 +763,7 @@ export class HasuraAuthClient {
|
||||
}
|
||||
|
||||
private isReady() {
|
||||
return !!this._client.interpreter?.state?.hasTag('ready')
|
||||
return !this._client.interpreter?.state?.hasTag('loading')
|
||||
}
|
||||
|
||||
get client() {
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# @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
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/nextjs",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.2",
|
||||
"description": "Nhost NextJS library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -46,8 +46,9 @@ export const createServerSideClient = async (
|
||||
autoSignIn: true
|
||||
})
|
||||
|
||||
await waitFor(nhost.auth.client.interpreter!, (state: StateFrom<AuthMachine>) =>
|
||||
state.hasTag('ready')
|
||||
await waitFor(
|
||||
nhost.auth.client.interpreter!,
|
||||
(state: StateFrom<AuthMachine>) => !state.hasTag('loading')
|
||||
)
|
||||
return nhost
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export const getNhostSession = async (
|
||||
backendUrl: string,
|
||||
context: GetServerSidePropsContext
|
||||
): 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
|
||||
return nhost.auth.isAuthenticated()
|
||||
? {
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @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
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/nhost-js",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.9",
|
||||
"description": "Nhost JavaScript SDK",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @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
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react-apollo",
|
||||
"version": "4.2.0",
|
||||
"version": "4.2.2",
|
||||
"description": "Nhost React Apollo client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# @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
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.2",
|
||||
"description": "Nhost React library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -58,7 +58,7 @@ export const useNhostBackendUrl = () => {
|
||||
*/
|
||||
export const useAuthLoading = () => {
|
||||
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,
|
||||
(state) => ({
|
||||
isAuthenticated: state.matches({ authentication: 'signedIn' }),
|
||||
isLoading: !state.hasTag('ready'),
|
||||
isLoading: state.hasTag('loading'),
|
||||
error: state.context.errors.authentication || null,
|
||||
isError: state.matches({ authentication: { signedOut: 'failed' } })
|
||||
}),
|
||||
|
||||
287
pnpm-lock.yaml
generated
287
pnpm-lock.yaml
generated
@@ -191,49 +191,49 @@ importers:
|
||||
examples/react-apollo:
|
||||
specifiers:
|
||||
'@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/react': workspace:*
|
||||
'@nhost/react-apollo': workspace:*
|
||||
'@rsuite/icons': ^1.0.2
|
||||
'@types/react': ^18.0.8
|
||||
'@types/react-dom': ^18.0.3
|
||||
'@vitejs/plugin-react': ^1.3.1
|
||||
'@xstate/inspect': ^0.6.2
|
||||
graphql: 15.7.2
|
||||
less: ^4.1.2
|
||||
react: ^18.1.0
|
||||
react-dom: ^18.1.0
|
||||
react-icons: ^4.3.1
|
||||
react-json-view: ^1.21.3
|
||||
react-router: ^6.3.0
|
||||
react-router-dom: ^6.3.0
|
||||
rsuite: ^5.10.0
|
||||
typescript: ^4.6.3
|
||||
vite: ^2.9.5
|
||||
ws: ^8.6.0
|
||||
xstate: ^4.31.0
|
||||
dependencies:
|
||||
'@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/react': link:../../packages/react
|
||||
'@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
|
||||
less: 4.1.2
|
||||
react: 18.1.0
|
||||
react-dom: 18.1.0_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-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:
|
||||
'@types/react': 18.0.8
|
||||
'@types/react-dom': 18.0.3
|
||||
'@vitejs/plugin-react': 1.3.1
|
||||
'@xstate/inspect': 0.6.5_ws@8.6.0+xstate@4.31.0
|
||||
typescript: 4.6.3
|
||||
vite: 2.9.5_less@4.1.2
|
||||
vite: 2.9.5
|
||||
ws: 8.6.0
|
||||
xstate: 4.31.0
|
||||
|
||||
@@ -5189,10 +5189,6 @@ packages:
|
||||
'@jridgewell/resolve-uri': 3.0.6
|
||||
'@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:
|
||||
resolution: {integrity: sha512-nkalE/f1RvRGChwBnEIoBfSEYOXnCRdleKuv6+lePbMDrMZXeDQnqak5XDOeBgrPPyPfAdcCu/B5z+v3VhplGg==}
|
||||
dev: false
|
||||
@@ -5257,6 +5253,21 @@ packages:
|
||||
react-transition-group: 4.4.2_react-dom@18.1.0+react@18.1.0
|
||||
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:
|
||||
resolution: {integrity: sha512-xZV+kAgiDIGoxTRXJFoY8LqjxTzmvX1YxRO5QCK0Ky7JuA/iiCwSDsHf5fRqGj6e/ZNJh0MHr/M52PdpYBrpuQ==}
|
||||
peerDependencies:
|
||||
@@ -5806,24 +5817,6 @@ packages:
|
||||
picomatch: 2.3.1
|
||||
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:
|
||||
resolution: {integrity: sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==}
|
||||
dev: true
|
||||
@@ -6285,10 +6278,6 @@ packages:
|
||||
'@types/node': 17.0.25
|
||||
dev: false
|
||||
|
||||
/@types/chai/4.3.1:
|
||||
resolution: {integrity: sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==}
|
||||
dev: false
|
||||
|
||||
/@types/connect-history-api-fallback/1.3.5:
|
||||
resolution: {integrity: sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==}
|
||||
dependencies:
|
||||
@@ -6448,10 +6437,6 @@ packages:
|
||||
resolution: {integrity: sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==}
|
||||
dev: true
|
||||
|
||||
/@types/lodash/4.14.182:
|
||||
resolution: {integrity: sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==}
|
||||
dev: false
|
||||
|
||||
/@types/mdast/3.0.10:
|
||||
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
|
||||
dependencies:
|
||||
@@ -6548,19 +6533,13 @@ packages:
|
||||
'@types/history': 4.7.11
|
||||
'@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:
|
||||
resolution: {integrity: sha512-Ye0nlw09GeMp2Suh8qoOv0odfgCoowfM/9MG6WeRD60Gq9wS90bdkdRtYbRkNhXOpG4H+YXGvj4wOWhAC0LJ1g==}
|
||||
dependencies:
|
||||
'@types/prop-types': 15.7.5
|
||||
'@types/scheduler': 0.16.2
|
||||
csstype: 3.0.11
|
||||
dev: true
|
||||
|
||||
/@types/react/18.0.5:
|
||||
resolution: {integrity: sha512-UPxNGInDCIKlfqBrm8LDXYWNfLHwIdisWcsH5GpMyGjhEDLFgTtlRBaoWuCua9HcyuE0rMkmAeZ3FXV1pYLIYQ==}
|
||||
@@ -8559,12 +8538,6 @@ packages:
|
||||
keygrip: 1.1.0
|
||||
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:
|
||||
resolution: {integrity: sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -9724,12 +9697,6 @@ packages:
|
||||
csstype: 3.0.11
|
||||
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:
|
||||
resolution: {integrity: sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==}
|
||||
dependencies:
|
||||
@@ -9956,15 +9923,6 @@ packages:
|
||||
resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
|
||||
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:
|
||||
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
|
||||
dependencies:
|
||||
@@ -11239,18 +11197,6 @@ packages:
|
||||
- encoding
|
||||
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:
|
||||
resolution: {integrity: sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==}
|
||||
engines: {node: '>=4.0'}
|
||||
@@ -12168,14 +12114,6 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
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:
|
||||
resolution: {integrity: sha512-VAwkvNSNGClRw9mDHhc5Efax8PLlsOGcUTh0T/LIriC8vPA3U5PdqXWqkz406MoYHMKW8Uf9gWr05T/rYB44kQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -12291,10 +12229,6 @@ packages:
|
||||
through: 2.3.8
|
||||
dev: true
|
||||
|
||||
/insert-css/2.0.0:
|
||||
resolution: {integrity: sha1-610Ql7dUL0x56jBg067gfQU4gPQ=}
|
||||
dev: false
|
||||
|
||||
/install-artifact-from-github/1.3.0:
|
||||
resolution: {integrity: sha512-iT8v1GwOAX0pPXifF/5ihnMhHOCo3OeK7z3TQa4CtSNCIg8k0UxqBEk9jRwz8OP68hHXvJ2gxRa89KYHtBkqGA==}
|
||||
hasBin: true
|
||||
@@ -12704,10 +12638,6 @@ packages:
|
||||
call-bind: 1.0.2
|
||||
dev: true
|
||||
|
||||
/is-what/3.14.1:
|
||||
resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==}
|
||||
dev: false
|
||||
|
||||
/is-whitespace-character/1.0.4:
|
||||
resolution: {integrity: sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==}
|
||||
dev: false
|
||||
@@ -13689,24 +13619,6 @@ packages:
|
||||
dependencies:
|
||||
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:
|
||||
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -14038,16 +13950,6 @@ packages:
|
||||
iconv-lite: 0.6.3
|
||||
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:
|
||||
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -14506,18 +14408,6 @@ packages:
|
||||
resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=}
|
||||
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:
|
||||
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -15088,11 +14978,6 @@ packages:
|
||||
json-parse-even-better-errors: 2.3.1
|
||||
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:
|
||||
resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==}
|
||||
dev: false
|
||||
@@ -15220,6 +15105,7 @@ packages:
|
||||
/pify/4.0.1:
|
||||
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/pirates/4.0.5:
|
||||
resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==}
|
||||
@@ -15803,6 +15689,14 @@ packages:
|
||||
react: 17.0.2
|
||||
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:
|
||||
resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -15865,11 +15759,6 @@ packages:
|
||||
resolution: {integrity: sha512-wapJ3h/w8fRSyPEG0y2WMV+tf9xwvj3nxM6aHVuPEOwKs/t5xLSKZb44ubNTiqq2T6lmEMHEWGMTaU2L6ddaFA==}
|
||||
dev: false
|
||||
|
||||
/prr/1.0.1:
|
||||
resolution: {integrity: sha1-0/wRS6BplaRexok/SEzrHXj19HY=}
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/pseudomap/1.0.2:
|
||||
resolution: {integrity: sha1-8FKijacOYYkX7wqKw0wa5aaChrM=}
|
||||
dev: true
|
||||
@@ -16163,23 +16052,6 @@ packages:
|
||||
/react-is/17.0.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==}
|
||||
peerDependencies:
|
||||
@@ -16404,22 +16276,6 @@ packages:
|
||||
react-dom: 18.1.0_react@18.1.0
|
||||
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:
|
||||
resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -16913,50 +16769,6 @@ packages:
|
||||
fsevents: 2.3.2
|
||||
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:
|
||||
resolution: {integrity: sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ==}
|
||||
dev: false
|
||||
@@ -17028,12 +16840,6 @@ packages:
|
||||
dependencies:
|
||||
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:
|
||||
resolution: {integrity: sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==}
|
||||
engines: {node: '>= 8.9.0'}
|
||||
@@ -19123,31 +18929,6 @@ packages:
|
||||
fsevents: 2.3.2
|
||||
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:
|
||||
resolution: {integrity: sha512-5hH7aNQe8rJiTTqCtPNX/6mIKlGw+1wg8UXwAxDIIN8XaSR+Zx3GT2zSu7QKa1vIaBqfUODGh3vpwY8r0AW/jw==}
|
||||
engines: {node: '>=12.2.0'}
|
||||
|
||||
Reference in New Issue
Block a user