Compare commits

...

33 Commits

Author SHA1 Message Date
Pilou
d212128815 Merge pull request #829 from nhost/changeset-release/main
chore: update versions
2022-07-16 15:22:46 +02:00
github-actions[bot]
9d9caf9834 chore: update versions 2022-07-15 20:20:38 +00:00
Pilou
96cbf023ca Merge pull request #824 from nhost/docs/react-apollo-example-improvements
React Apollo example improvements
2022-07-15 22:19:01 +02:00
Pierre-Louis Mercereau
1ed534cb4a Merge branch 'main' into docs/react-apollo-example-improvements 2022-07-15 22:06:07 +02:00
Pilou
dcdee0b426 Merge pull request #819 from nhost/docs/vue-example-improvements
Vue example improvements
2022-07-15 22:02:58 +02:00
Pierre-Louis Mercereau
e31f4756b4 fix(vue-example): disabled & loading buttons 2022-07-15 20:13:44 +02:00
Pierre-Louis Mercereau
259e198d80 fix(react-apollo-example): fixed logo width 2022-07-15 19:45:22 +02:00
Pierre-Louis Mercereau
1d10a47414 fix(react-apollo-example): fit image logo 2022-07-15 19:42:37 +02:00
Pierre-Louis Mercereau
f30d6779bb chore: bump @apollo/client to v3.6.9 2022-07-15 15:55:29 +02:00
Pierre-Louis Mercereau
2a3b2c3af5 Merge branch 'main' into docs/vue-example-improvements 2022-07-15 15:39:38 +02:00
Pierre-Louis Mercereau
a0db6b58de chore: add hasura-auth 0.10 metadata 2022-07-15 12:06:08 +02:00
Pierre-Louis Mercereau
523d52aa7f Merge branch 'main' into docs/react-apollo-example-improvements 2022-07-15 11:48:41 +02:00
Pierre-Louis Mercereau
6e1ee1802d docs: explicit pnpm run 2022-07-15 11:45:45 +02:00
Pierre-Louis Mercereau
51ad1eb355 fix(react-apollo-example): qrcode img by alttext 2022-07-15 11:39:22 +02:00
Pierre-Louis Mercereau
e084643032 docs: correct readme 2022-07-15 11:11:35 +02:00
Pierre-Louis Mercereau
5514e81186 docs: update readme 2022-07-15 11:10:44 +02:00
Pierre-Louis Mercereau
16f38aa893 docs(example): update readme instructions 2022-07-15 11:09:08 +02:00
Pierre-Louis Mercereau
76c6e8d0d6 feat(react-apollo-example): improve navbar & index 2022-07-15 10:33:27 +02:00
Pierre-Louis Mercereau
a7d5c85f60 fix(example): hide nav drawer on small screens 2022-07-15 09:35:28 +02:00
Pierre-Louis Mercereau
bc657251d6 fix: remove refresh token from the url 2022-07-14 22:07:59 +02:00
Pierre-Louis Mercereau
5abc362a4d fix(example): close magic link sent dialog 2022-07-14 15:10:11 +02:00
Pierre-Louis Mercereau
7a4c9fa806 refactor(example): use form submit 2022-07-14 15:06:57 +02:00
Pierre-Louis Mercereau
348318d709 feat(example): add dialog after magic link is sent 2022-07-14 14:48:30 +02:00
Pierre-Louis Mercereau
b5f7f7fe5f refactor(example): toast errors on top of window 2022-07-13 19:45:38 +02:00
Pierre-Louis Mercereau
7116a4df8a refactor(example): misc improvements 2022-07-13 19:39:11 +02:00
Pierre-Louis Mercereau
7e4c52dbd1 feat(example): add title and GitHub link 2022-07-13 17:20:55 +02:00
Pierre-Louis Mercereau
446dc01bde feat(example): toast anonymous user errors 2022-07-13 17:07:25 +02:00
Pierre-Louis Mercereau
a1989c51f8 feat(example): enable anonymous users in the backend 2022-07-13 17:00:59 +02:00
Pierre-Louis Mercereau
1383de558a feat(example): add email verification dialog on sign-in 2022-07-13 17:00:39 +02:00
Pierre-Louis Mercereau
d828107f74 fix(example): remove "return" from previous "setup()" syntax 2022-07-13 16:43:10 +02:00
Pierre-Louis Mercereau
4a1650cb35 refactor(example): use the template "setup" syntax 2022-07-13 16:37:23 +02:00
Pierre-Louis Mercereau
913651d440 feat(example): add "verification email sent" dialog 2022-07-13 16:29:40 +02:00
Pierre-Louis Mercereau
6af2d52666 chore(vue): bump dependencies 2022-07-13 15:52:12 +02:00
53 changed files with 938 additions and 1044 deletions

View File

@@ -15,7 +15,7 @@
"verify:fix": "run-p prettier:fix lint:fix"
},
"dependencies": {
"@apollo/client": "^3.6.2",
"@apollo/client": "^3.6.9",
"@mantine/core": "^4.2.2",
"@mantine/hooks": "^4.2.2",
"@mantine/next": "^4.2.2",

View File

@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@apollo/client": "^3.5.10",
"@apollo/client": "^3.6.9",
"@headlessui/react": "^1.5.0",
"@heroicons/react": "^1.0.6",
"@nhost/react": "*",
@@ -55,11 +55,11 @@
"@graphql-codegen/typescript": "^2.4.8",
"@graphql-codegen/typescript-operations": "^2.3.5",
"@graphql-codegen/typescript-react-apollo": "^3.2.11",
"@types/express": "^4.17.13",
"@types/jest": "^26.0.24",
"@types/node": "^12.20.48",
"@types/react": "^17.0.44",
"@types/react-dom": "^17.0.15",
"@types/express": "^4.17.13",
"@vitejs/plugin-react": "^1.3.2",
"autoprefixer": "^10.4.4",
"express": "^4.17.3",

View File

@@ -1,4 +1,4 @@
# React-Apollo example
# Nhost React + Apollo example
## See this example live
@@ -13,20 +13,26 @@ git clone https://github.com/nhost/nhost
cd nhost
```
2. Install dependencies
2. Install and build dependencies
```sh
pnpm install
pnpm run build
```
3. Go to the example folder
```sh
cd examples/react-apollo
pnpm install
```
3. Terminal 1: Start Nhost
4. Terminal 1: Start Nhost
```sh
nhost dev
```
4. Terminal 2: Start the React application
5. Terminal 2: Start the React application
```sh
pnpm run dev

View File

@@ -43,36 +43,32 @@ context('Sign in with email+password', () => {
.findByRole('button')
.click()
cy.findByText(/Activate 2-step verification/i)
.get('img')
.then(async (img) => {
// * Activate MFA
const result = await new Decoder().scan(img.prop('src'))
const [, params] = result.data.split('?')
const { secret, algorithm, digits, period } = Object.fromEntries(
new URLSearchParams(params)
)
const code = totp(secret, {
algorithm: algorithm.replace('SHA1', 'SHA-1'),
digits: parseInt(digits),
period: parseInt(period)
})
cy.findByPlaceholderText('Enter activation code').type(code)
cy.findByRole('button', { name: /Activate/i }).click()
cy.contains('MFA has been activated!!!')
cy.signOut()
// * Sign-in with MFA
cy.visit('/sign-in')
cy.findByRole('button', { name: /Continue with email \+ password/i }).click()
cy.findByPlaceholderText('Email Address').type(email)
cy.findByPlaceholderText('Password').type(password)
cy.findByRole('button', { name: /Sign in/i }).click()
cy.contains('Send 2-step verification code')
const newCode = totp(secret, { timestamp: Date.now() })
cy.findByPlaceholderText('One-time password').type(newCode)
cy.findByRole('button', { name: /Send 2-step verification code/i }).click()
cy.contains('You are authenticated')
cy.findAllByAltText(/qrcode/i).then(async (img) => {
// * Activate MFA
const result = await new Decoder().scan(img.prop('src'))
const [, params] = result.data.split('?')
const { secret, algorithm, digits, period } = Object.fromEntries(new URLSearchParams(params))
const code = totp(secret, {
algorithm: algorithm.replace('SHA1', 'SHA-1'),
digits: parseInt(digits),
period: parseInt(period)
})
cy.findByPlaceholderText('Enter activation code').type(code)
cy.findByRole('button', { name: /Activate/i }).click()
cy.contains('MFA has been activated!!!')
cy.signOut()
// * Sign-in with MFA
cy.visit('/sign-in')
cy.findByRole('button', { name: /Continue with email \+ password/i }).click()
cy.findByPlaceholderText('Email Address').type(email)
cy.findByPlaceholderText('Password').type(password)
cy.findByRole('button', { name: /Sign in/i }).click()
cy.contains('Send 2-step verification code')
const newCode = totp(secret, { timestamp: Date.now() })
cy.findByPlaceholderText('One-time password').type(newCode)
cy.findByRole('button', { name: /Send 2-step verification code/i }).click()
cy.contains('You are authenticated')
})
})
})

View File

@@ -1,13 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Nhost with React and Apollo</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,30 @@
table:
name: user_authenticators
schema: auth
configuration:
column_config:
credential_id:
custom_name: credentialId
credential_public_key:
custom_name: credentialPublicKey
user_id:
custom_name: userId
custom_column_names:
credential_id: credentialId
credential_public_key: credentialPublicKey
user_id: userId
custom_name: authUserAuthenticators
custom_root_fields:
delete: deleteAuthUserAuthenticators
delete_by_pk: deleteAuthUserAuthenticator
insert: insertAuthUserAuthenticators
insert_one: insertAuthUserAuthenticator
select: authUserAuthenticators
select_aggregate: authUserAuthenticatorsAggregate
select_by_pk: authUserAuthenticator
update: updateAuthUserAuthenticators
update_by_pk: updateAuthUserAuthenticator
object_relationships:
- name: user
using:
foreign_key_constraint_on: user_id

View File

@@ -39,6 +39,8 @@ configuration:
custom_name: totpSecret
updated_at:
custom_name: updatedAt
webauthn_current_challenge:
custom_name: currentChallenge
custom_column_names:
active_mfa_type: activeMfaType
avatar_url: avatarUrl
@@ -58,6 +60,7 @@ configuration:
ticket_expires_at: ticketExpiresAt
totp_secret: totpSecret
updated_at: updatedAt
webauthn_current_challenge: currentChallenge
custom_name: users
custom_root_fields:
delete: deleteUsers
@@ -74,6 +77,13 @@ object_relationships:
using:
foreign_key_constraint_on: default_role
array_relationships:
- name: authenticators
using:
foreign_key_constraint_on:
column: user_id
table:
name: user_authenticators
schema: auth
- name: refreshTokens
using:
foreign_key_constraint_on:

View File

@@ -2,6 +2,7 @@
- "!include auth_providers.yaml"
- "!include auth_refresh_tokens.yaml"
- "!include auth_roles.yaml"
- "!include auth_user_authenticators.yaml"
- "!include auth_user_providers.yaml"
- "!include auth_user_roles.yaml"
- "!include auth_users.yaml"

View File

@@ -3,7 +3,7 @@
"version": "0.0.1",
"private": true,
"dependencies": {
"@apollo/client": "^3.6.2",
"@apollo/client": "^3.6.9",
"@mantine/core": "^4.2.2",
"@mantine/dropzone": "^4.2.6",
"@mantine/hooks": "^4.2.2",
@@ -16,7 +16,8 @@
"react-dom": "^18.1.0",
"react-icons": "^4.3.1",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0"
"react-router-dom": "^6.3.0",
"tabler-icons-react": "^1.52.0"
},
"scripts": {
"dev": "vite",
@@ -66,4 +67,4 @@
"ws": "^8.7.0",
"xstate": "^4.32.1"
}
}
}

View File

@@ -0,0 +1,10 @@
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M88.0355 21.4793L53.1776 1.35118C50.0502 -0.450393 46.168 -0.450393 43.0343 1.35118C39.9069 3.1591 37.9657 6.52119 37.9657 10.1307V12.7569L35.6948 11.4438C32.5674 9.64222 28.6851 9.64222 25.5514 11.4438C22.424 13.2517 20.4829 16.6138 20.4829 20.2296V22.8559L18.2119 21.5428C15.0845 19.7412 11.2022 19.7412 8.06851 21.5428C4.94113 23.3507 3 26.7128 3 30.3286V93.3963C3 95.2106 4.05303 96.898 5.68967 97.6846C7.31996 98.4775 9.29916 98.2619 10.7201 97.1391L28.0063 83.5067L54.662 98.8962C55.3979 99.3212 56.2225 99.5306 57.0472 99.5306C57.8719 99.5306 58.6965 99.3149 59.4324 98.8962C60.9041 98.0462 61.8176 96.4666 61.8176 94.7666V56.813C61.8176 50.5836 58.4682 44.7856 53.0761 41.6709L44.3347 36.6214V10.137C44.3347 8.79218 45.0579 7.53616 46.2251 6.86374C47.3923 6.19132 48.8386 6.19132 50.0058 6.86374L84.8638 26.9855C88.2956 28.9647 90.4271 32.663 90.4271 36.6214V83.881C90.4271 85.2258 89.7039 86.4819 88.5367 87.1543L79.3004 92.4892V46.714C79.3004 40.4846 75.951 34.6866 70.559 31.5719L49.0987 19.1829V26.5225L67.3809 37.0782C70.8127 39.0573 72.9442 42.7493 72.9442 46.714V95.236C72.9442 96.9297 73.8577 98.5156 75.3294 99.3656C76.0652 99.7907 76.8899 100 77.7145 100C78.5392 100 79.3639 99.7843 80.0997 99.3656L91.7212 92.6541C94.8485 90.8462 96.7897 87.4841 96.7897 83.8683V36.6087C96.777 30.3984 93.4276 24.594 88.0355 21.4793ZM49.8853 47.1771C53.3172 49.1563 55.4486 52.8483 55.4486 56.813V92.0198L33.373 79.2756L40.4588 73.6932C42.9137 71.7584 44.322 68.8594 44.322 65.732V43.9736L49.8853 47.1771ZM37.9657 40.2943V65.7194C37.9657 66.8866 37.4392 67.9713 36.5258 68.6881L9.35626 90.1104V30.3223C9.35626 28.9774 10.0794 27.7214 11.2466 27.049C12.4139 26.3766 13.8602 26.3766 15.0274 27.049L20.4829 30.1954V75.2664L26.8391 70.255V20.2296C26.8391 18.8848 27.5623 17.6288 28.7295 16.9564C29.8967 16.2839 31.3431 16.2839 32.5103 16.9564L37.9657 20.1028V32.9485L31.6095 29.2756V36.6214L37.9657 40.2943Z" fill="#0052CD"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="93.7897" height="100" fill="white" transform="translate(3)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,6 +1,7 @@
import { Route, Routes } from 'react-router-dom'
import { BrandGithub } from 'tabler-icons-react'
import { AppShell, Header, MantineProvider } from '@mantine/core'
import { AppShell, Button, Group, Header, Image, MantineProvider, Title } from '@mantine/core'
import { NotificationsProvider } from '@mantine/notifications'
import { AuthGate, PublicGate } from './components/auth-gates'
@@ -17,13 +18,13 @@ import './App.css'
const title = 'Nhost with React and Apollo'
function App() {
const colorScheme = 'light'
return (
<MantineProvider
withGlobalStyles
withNormalizeCSS
theme={{
/** Put your mantine theme override here */
colorScheme: 'light'
colorScheme
}}
>
<NotificationsProvider>
@@ -32,7 +33,24 @@ function App() {
navbar={<NavBar />}
header={
<Header height={60} p="xs">
{title}
<Group position="apart" noWrap>
<Group noWrap>
<Image src="/logo.svg" height={35} fit="contain" width={120} />
<Title order={3} style={{ whiteSpace: 'nowrap' }}>
{title}
</Title>
</Group>
<Button
leftIcon={<BrandGithub />}
variant="outline"
color={colorScheme}
component="a"
href="https://github.com/nhost/nhost/tree/main/examples/react-apollo"
target="_blank"
>
GitHub
</Button>
</Group>
</Header>
}
styles={(theme) => ({

View File

@@ -1 +1,39 @@
# Nhost Vue3 Apollo example
# Nhost Vue 3 + Apollo example
## See this example live
Visit our demo application on [vue-apollo.example.nhost.io](https://vue-apollo.example.nhost.io)
## Get started
1. Clone the repository
```sh
git clone https://github.com/nhost/nhost
cd nhost
```
2. Install and build dependencies
```sh
pnpm install
pnpm build
```
3. Go to the example folder
```sh
cd examples/vue-apollo
```
4. Terminal 1: Start Nhost
```sh
nhost dev
```
5. Terminal 2: Start the Vue application
```sh
pnpm run dev
```

View File

@@ -1,13 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Nhost with Vue and Apollo</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -18,7 +18,7 @@ auth:
blocked_email_domains: ''
blocked_emails: ''
allowed_redirect_urls: ''
anonymous_users_enabled: false
anonymous_users_enabled: true
client_url: http://localhost:3000
disable_new_users: false
email:

View File

@@ -2,6 +2,7 @@ table:
name: provider_requests
schema: auth
configuration:
column_config: {}
custom_column_names: {}
custom_name: authProviderRequests
custom_root_fields:

View File

@@ -2,6 +2,7 @@ table:
name: providers
schema: auth
configuration:
column_config: {}
custom_column_names: {}
custom_name: authProviders
custom_root_fields:

View File

@@ -2,6 +2,15 @@ table:
name: refresh_tokens
schema: auth
configuration:
column_config:
created_at:
custom_name: createdAt
expires_at:
custom_name: expiresAt
refresh_token:
custom_name: refreshToken
user_id:
custom_name: userId
custom_column_names:
created_at: createdAt
expires_at: expiresAt

View File

@@ -2,6 +2,7 @@ table:
name: roles
schema: auth
configuration:
column_config: {}
custom_column_names: {}
custom_name: authRoles
custom_root_fields:

View File

@@ -0,0 +1,30 @@
table:
name: user_authenticators
schema: auth
configuration:
column_config:
credential_id:
custom_name: credentialId
credential_public_key:
custom_name: credentialPublicKey
user_id:
custom_name: userId
custom_column_names:
credential_id: credentialId
credential_public_key: credentialPublicKey
user_id: userId
custom_name: authUserAuthenticators
custom_root_fields:
delete: deleteAuthUserAuthenticators
delete_by_pk: deleteAuthUserAuthenticator
insert: insertAuthUserAuthenticators
insert_one: insertAuthUserAuthenticator
select: authUserAuthenticators
select_aggregate: authUserAuthenticatorsAggregate
select_by_pk: authUserAuthenticator
update: updateAuthUserAuthenticators
update_by_pk: updateAuthUserAuthenticator
object_relationships:
- name: user
using:
foreign_key_constraint_on: user_id

View File

@@ -2,6 +2,21 @@ table:
name: user_providers
schema: auth
configuration:
column_config:
access_token:
custom_name: accessToken
created_at:
custom_name: createdAt
provider_id:
custom_name: providerId
provider_user_id:
custom_name: providerUserId
refresh_token:
custom_name: refreshToken
updated_at:
custom_name: updatedAt
user_id:
custom_name: userId
custom_column_names:
access_token: accessToken
created_at: createdAt

View File

@@ -2,6 +2,11 @@ table:
name: user_roles
schema: auth
configuration:
column_config:
created_at:
custom_name: createdAt
user_id:
custom_name: userId
custom_column_names:
created_at: createdAt
user_id: userId

View File

@@ -2,6 +2,45 @@ table:
name: users
schema: auth
configuration:
column_config:
active_mfa_type:
custom_name: activeMfaType
avatar_url:
custom_name: avatarUrl
created_at:
custom_name: createdAt
default_role:
custom_name: defaultRole
display_name:
custom_name: displayName
email_verified:
custom_name: emailVerified
is_anonymous:
custom_name: isAnonymous
last_seen:
custom_name: lastSeen
new_email:
custom_name: newEmail
otp_hash:
custom_name: otpHash
otp_hash_expires_at:
custom_name: otpHashExpiresAt
otp_method_last_used:
custom_name: otpMethodLastUsed
password_hash:
custom_name: passwordHash
phone_number:
custom_name: phoneNumber
phone_number_verified:
custom_name: phoneNumberVerified
ticket_expires_at:
custom_name: ticketExpiresAt
totp_secret:
custom_name: totpSecret
updated_at:
custom_name: updatedAt
webauthn_current_challenge:
custom_name: currentChallenge
custom_column_names:
active_mfa_type: activeMfaType
avatar_url: avatarUrl
@@ -21,6 +60,7 @@ configuration:
ticket_expires_at: ticketExpiresAt
totp_secret: totpSecret
updated_at: updatedAt
webauthn_current_challenge: currentChallenge
custom_name: users
custom_root_fields:
delete: deleteUsers
@@ -37,6 +77,13 @@ object_relationships:
using:
foreign_key_constraint_on: default_role
array_relationships:
- name: authenticators
using:
foreign_key_constraint_on:
column: user_id
table:
name: user_authenticators
schema: auth
- name: refreshTokens
using:
foreign_key_constraint_on:

View File

@@ -2,6 +2,23 @@ table:
name: buckets
schema: storage
configuration:
column_config:
cache_control:
custom_name: cacheControl
created_at:
custom_name: createdAt
download_expiration:
custom_name: downloadExpiration
id:
custom_name: id
max_upload_file_size:
custom_name: maxUploadFileSize
min_upload_file_size:
custom_name: minUploadFileSize
presigned_urls_enabled:
custom_name: presignedUrlsEnabled
updated_at:
custom_name: updatedAt
custom_column_names:
cache_control: cacheControl
created_at: createdAt

View File

@@ -2,6 +2,27 @@ table:
name: files
schema: storage
configuration:
column_config:
bucket_id:
custom_name: bucketId
created_at:
custom_name: createdAt
etag:
custom_name: etag
id:
custom_name: id
is_uploaded:
custom_name: isUploaded
mime_type:
custom_name: mimeType
name:
custom_name: name
size:
custom_name: size
updated_at:
custom_name: updatedAt
uploaded_by_user_id:
custom_name: uploadedByUserId
custom_column_names:
bucket_id: bucketId
created_at: createdAt

View File

@@ -2,6 +2,7 @@
- "!include auth_providers.yaml"
- "!include auth_refresh_tokens.yaml"
- "!include auth_roles.yaml"
- "!include auth_user_authenticators.yaml"
- "!include auth_user_providers.yaml"
- "!include auth_user_roles.yaml"
- "!include auth_users.yaml"

View File

@@ -14,16 +14,16 @@
"verify:fix": "run-p prettier:fix lint:fix"
},
"dependencies": {
"@apollo/client": "^3.6.2",
"@apollo/client": "^3.6.9",
"@mdi/font": "5.9.55",
"@nhost/apollo": "*",
"@nhost/vue": "*",
"@vue/apollo-composable": "^4.0.0-alpha.17",
"@vue/apollo-composable": "4.0.0-alpha.18",
"graphql": "15.7.2",
"graphql-tag": "^2.12.6",
"roboto-fontface": "*",
"vue": "^3.2.25",
"vue-router": "^4.0.3",
"vue": "^3.2.37",
"vue-router": "^4.1.2",
"vuetify": "^3.0.0-beta.0",
"webfontloader": "^1.0.0"
},
@@ -31,12 +31,12 @@
"@nhost/core": "*",
"@types/webfontloader": "^1.0.0",
"@vitejs/plugin-vue": "^2.3.1",
"@vuetify/vite-plugin": "^1.0.0-alpha.0",
"@vuetify/vite-plugin": "1.0.0-alpha.11",
"@xstate/inspect": "^0.6.2",
"sass": "1.32.0",
"typescript": "^4.5.4",
"typescript": "^4.7.4",
"vite": "^2.9.0",
"vue-tsc": "^0.29.8"
"vue-tsc": "^0.38.5"
},
"eslintConfig": {
"root": true,

View File

@@ -4,11 +4,13 @@
<template #prepend>
<v-app-bar-nav-icon @click.stop="drawer = !drawer" />
</template>
<v-app-bar-title>Nhost with Vue and Apollo</v-app-bar-title>
<template #append>
<v-btn icon="mdi-github" href="https://github.com/nhost/nhost/tree/main/examples/vue-apollo" target="_blank" />
<v-btn v-if="isAuthenticated" icon="mdi-exit-to-app" @click="signOutHandler" />
</template>
</v-app-bar>
<v-navigation-drawer v-model="drawer" permanent>
<v-navigation-drawer v-model="drawer" :permanent="mdAndUp">
<nav-bar />
</v-navigation-drawer>
<v-main class="my-4">
@@ -17,30 +19,22 @@
</v-app>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
<script lang="ts" setup>
import { useDisplay } from 'vuetify'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthenticated, useSignOut } from '@nhost/vue'
import NavBar from './components/NavBar.vue'
export default defineComponent({
components: { NavBar },
setup() {
const router = useRouter()
const isAuthenticated = useAuthenticated()
const { signOut } = useSignOut()
const drawer = ref(true)
const signOutHandler = async () => {
await signOut()
router.replace('/signout')
}
return {
drawer,
isAuthenticated,
signOutHandler
}
}
})
const { mdAndUp } = useDisplay()
const router = useRouter()
const isAuthenticated = useAuthenticated()
const { signOut } = useSignOut()
const drawer = ref()
const signOutHandler = async () => {
await signOut()
router.replace('/signout')
}
</script>

View File

@@ -1,25 +1,45 @@
<template>
<div>
<v-text-field v-model="email" placeholder="Email Address" autofocus />
<v-btn block color="primary" @click="signIn"> Continue with email </v-btn>
<form @submit="submit">
<v-text-field v-model="email" placeholder="Email Address" autofocus />
<v-btn block color="primary" type="submit" :disabled="isLoading" :loading="isLoading"> Continue with email
</v-btn>
</form>
<error-snack-bar :error="error" />
<v-dialog v-model="emailSentDialog">
<v-card>
<v-card-title>
<span class="text-h5">Verification email sent</span>
</v-card-title>
<v-card-text>
A email has been sent to {{ email }}. Please follow the link to sign in to the application.
</v-card-text>
<v-card-actions class="d-flex justify-center">
<v-btn text @click="emailSentDialog = false">
Close
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
<script lang="ts" setup>
import { ref } from 'vue'
import { useSignInEmailPasswordless } from '@nhost/vue'
export default defineComponent({
setup() {
const email = ref('')
const { signInEmailPasswordless, error } = useSignInEmailPasswordless({
redirectTo: '/#/profile'
})
const email = ref('')
const emailSentDialog = ref(false)
const signIn = () => signInEmailPasswordless(email)
return { email, signIn, error }
}
const { signInEmailPasswordless, error, isLoading } = useSignInEmailPasswordless({
redirectTo: '/profile'
})
const submit = async (e: Event) => {
e.preventDefault()
const { isSuccess } = await signInEmailPasswordless(email)
if (isSuccess) {
emailSentDialog.value = true
}
}
</script>

View File

@@ -1,5 +1,5 @@
<template>
<v-snackbar v-model="snack" :timeout="2_000">
<v-snackbar v-model="snack" :timeout="2_000" top>
{{ error?.message }}
<template #actions>
<v-btn color="blue" variant="text" @click="snack = false"> Close </v-btn>
@@ -7,24 +7,21 @@
</v-snackbar>
</template>
<script lang="ts">
import { defineComponent, PropType, ref, watchEffect } from 'vue'
<script lang="ts" setup>
import { PropType, ref, watchEffect } from 'vue'
import { ErrorPayload } from '@nhost/core'
export default defineComponent({
props: {
error: Object as PropType<ErrorPayload | null>
},
setup(props) {
const snack = ref(false)
watchEffect(() => {
if (props.error) {
snack.value = true
}
})
const props = defineProps({
error: Object as PropType<ErrorPayload | null>
})
const snack = ref(false)
return { snack }
watchEffect(() => {
if (props.error) {
snack.value = true
}
})
</script>

View File

@@ -4,33 +4,20 @@
<v-list-item title="Profile" to="/profile" value="profile" prepend-icon="mdi-account" />
<v-list-item title="Apollo" to="/apollo" value="apollo" prepend-icon="mdi-api" />
<v-list-item title="About" to="/about" value="about" prepend-icon="mdi-information" />
<v-list-item
v-if="authenticated"
title="Sign out"
prepend-icon="mdi-exit-to-app"
@click="signOutHandler"
/>
<v-list-item v-if="authenticated" title="Sign out" prepend-icon="mdi-exit-to-app" @click="signOutHandler" />
</v-list>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
<script lang="ts" setup>
import { useRouter } from 'vue-router'
import { useAuthenticated, useSignOut } from '@nhost/vue'
export default defineComponent({
setup() {
const router = useRouter()
const { signOut } = useSignOut()
const authenticated = useAuthenticated()
const signOutHandler = async () => {
await signOut()
router.push('/')
}
return {
authenticated,
signOutHandler
}
}
})
const router = useRouter()
const { signOut } = useSignOut()
const authenticated = useAuthenticated()
const signOutHandler = async () => {
await signOut()
router.push('/')
}
</script>

View File

@@ -1,48 +1,21 @@
<template>
<v-btn
class="my-1"
block
variant="contained-text"
prepend-icon="mdi-github"
color="white"
style="background-color: #333"
:href="github"
>
<v-btn class="my-1" block variant="contained-text" prepend-icon="mdi-github" color="white"
style="background-color: #333" :href="github">
Continue with GitHub
</v-btn>
<v-btn
class="my-1"
block
variant="contained-text"
prepend-icon="mdi-google"
color="white"
style="background-color: #de5246"
:href="google"
>
<v-btn class="my-1" block variant="contained-text" prepend-icon="mdi-google" color="white"
style="background-color: #de5246" :href="google">
Continue with Google
</v-btn>
<v-btn
class="my-1"
block
variant="contained-text"
prepend-icon="mdi-facebook"
color="white"
style="background-color: #3b5998"
:href="facebook"
>
<v-btn class="my-1" block variant="contained-text" prepend-icon="mdi-facebook" color="white"
style="background-color: #3b5998" :href="facebook">
Continue with Facebook
</v-btn>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
<script lang="ts" setup>
import { useProviderLink } from '@nhost/vue'
export default defineComponent({
setup() {
const { github, google, facebook } = useProviderLink({ redirectTo: window.location.origin })
return { github, google, facebook }
}
})
const { github, google, facebook } = useProviderLink({ redirectTo: '/' })
</script>

View File

@@ -0,0 +1,22 @@
<template>
<v-dialog v-model="modelValue">
<v-card>
<v-card-title>
<span class="text-h5">Verification email sent</span>
</v-card-title>
<v-card-text>
A email has been sent to {{ email }}. Please follow the link to verify your email address and to
complete your registration.
</v-card-text>
<v-card-actions class="d-flex justify-center">
<v-btn text @click="$emit('update:modelValue', false)">
Close
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script lang="ts" setup>
defineProps(['modelValue', 'email'])
</script>

View File

@@ -1,5 +1,5 @@
import { createApp } from 'vue'
import { createRouter, createWebHashHistory } from 'vue-router'
import { createRouter, createWebHistory } from 'vue-router'
import { createVuetify, ThemeDefinition } from 'vuetify'
import { createApolloClient } from '@nhost/apollo'
@@ -13,6 +13,7 @@ import 'vuetify/styles'
import EmailPasswordless from './components/EmailPasswordlessForm.vue'
import ErrorSnackBar from './components/ErrorSnackBar.vue'
import OauthLinks from './components/OAuthLinks.vue'
import VerificationEmailDialog from './components/VerificationEmailDialog.vue'
import App from './App.vue'
import { routes } from './routes'
@@ -68,7 +69,7 @@ nhost.auth.onAuthStateChanged((d) => {
})
const router = createRouter({
history: createWebHashHistory(),
history: createWebHistory(),
routes
})
@@ -88,4 +89,5 @@ createApp(App)
.component('ErrorSnackBar', ErrorSnackBar)
.component('EmailPasswordless', EmailPasswordless)
.component('OauthLinks', OauthLinks)
.component('VerificationEmailDialog', VerificationEmailDialog)
.mount('#app')

View File

@@ -1,7 +1,29 @@
<template>
<div className="d-flex align-center flex-column">
<v-card width="400">
<v-card-text> About </v-card-text>
<v-card-title>About this example</v-card-title>
<v-card-text>This application demonstrates the available features of the Nhost stack.
<p class="pt-2">Nhost cloud leverages the
following services in the backend:
<ul class="px-8">
<li>Hasura</li>
<li>Hasura Auth</li>
<li>Hasura Storage</li>
<li>Lambda Functions</li>
</ul>
</p>
<p class="py-2">
This frontend is built with the following technologies:
<ul class="px-8">
<li>Vue 3</li>
<li>Vue-router</li>
<li>Vuetify 3</li>
<li>and of course, the Nhost Vue client</li>
</ul>
</p>
Now let's go to the <router-link to="/">Home page</router-link>.
</v-card-text>
</v-card>
</div>
</template>

View File

@@ -1,19 +1,21 @@
<template>
<div className="d-flex align-center flex-column">
<v-card width="400" tile>
<v-card-text> Apollo </v-card-text>
<v-list density="compact" v-if="result">
<v-list-subheader>Books</v-list-subheader>
<v-list-item v-for="(item, i) in result.books" :key="i" :value="item.id">
<v-list-item-title v-text="item.title"></v-list-item-title>
</v-list-item>
</v-list>
<v-card-title> Apollo </v-card-title>
<v-card-text>
<v-list density="compact" v-if="result">
<v-list-subheader>Books</v-list-subheader>
<v-list-item v-for="(item, i) in result.books" :key="i" :value="item.id">
<v-list-item-title v-text="item.title"></v-list-item-title>
</v-list-item>
</v-list>
</v-card-text>
</v-card>
</div>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue'
<script lang="ts" setup>
import { computed } from 'vue'
import { gql } from '@apollo/client/core'
import { useAuthenticated } from '@nhost/vue'
@@ -28,20 +30,15 @@ const GET_BOOKS = gql`
}
`
export default defineComponent({
setup() {
const isAuthenticated = useAuthenticated()
// TODO check if the query always runs with the headers
const { result } = useQuery(
GET_BOOKS,
null,
computed(() => ({
pollInterval: 5000,
fetchPolicy: 'cache-and-network',
enabled: isAuthenticated.value
}))
)
return { result }
}
})
const isAuthenticated = useAuthenticated()
// TODO check if the query always runs with the headers
const { result } = useQuery(
GET_BOOKS,
null,
computed(() => ({
pollInterval: 5000,
fetchPolicy: 'cache-and-network',
enabled: isAuthenticated.value
}))
)
</script>

View File

@@ -6,14 +6,8 @@
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
<script lang="ts" setup>
import { useRouter } from 'vue-router'
export default defineComponent({
setup() {
const router = useRouter()
setTimeout(() => router.replace('/'), 2_500)
return {}
}
})
const router = useRouter()
setTimeout(() => router.replace('/'), 2_500)
</script>

View File

@@ -1,39 +1,37 @@
<template>
<v-text-field v-model="email" label="Email" />
<v-text-field v-model="password" label="Password" type="password" />
<v-btn block color="primary" class="my-1" @click="signIn"> Sign in </v-btn>
<form @submit="handleSignIn">
<v-text-field v-model="email" label="Email" />
<v-text-field v-model="password" label="Password" type="password" />
<v-btn block color="primary" class="my-1" type="submit" :disabled="isLoading" :loading="isLoading"> Sign in </v-btn>
</form>
<v-btn class="my-1" block variant="text" color="primary" to="/signin">
&#8592; Other Login Options
&#8592; Other Sign-in Options
</v-btn>
<error-snack-bar :error="error" />
<verification-email-dialog v-model="emailVerificationDialog" :email="email" />
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
<script lang="ts" setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useSignInEmailPassword } from '@nhost/vue'
export default defineComponent({
setup() {
const email = ref('')
const password = ref('')
const email = ref('')
const password = ref('')
const emailVerificationDialog = ref(false)
const router = useRouter()
const { signInEmailPassword, error } = useSignInEmailPassword()
const signIn = async () => {
const { isSuccess } = await signInEmailPassword(email, password)
if (isSuccess) {
router.replace('/')
}
}
const router = useRouter()
const { signInEmailPassword, error, isLoading } = useSignInEmailPassword()
return {
email,
error,
password,
signIn
}
const handleSignIn = async (e: Event) => {
e.preventDefault()
const { isSuccess, needsEmailVerification } = await signInEmailPassword(email, password)
if (isSuccess) {
router.replace('/')
}
})
if (needsEmailVerification) {
emailVerificationDialog.value = true
}
}
</script>

View File

@@ -1,6 +1,6 @@
<template>
<email-passwordless />
<v-btn class="my-1" block variant="text" color="primary" to="/signin">
&#8592; Other Login Options
&#8592; Other Sign-in Options
</v-btn>
</template>

View File

@@ -1,24 +1,7 @@
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { useSignInAnonymous } from '@nhost/vue'
const { signInAnonymous } = useSignInAnonymous()
const router = useRouter()
const handleSignInAnonymous = async (e: Event) => {
e.preventDefault()
const { isSuccess, error } = await signInAnonymous()
if (isSuccess) {
router.push('/profile')
} else {
console.log(error)
}
}
</script>
<template>
<div className="d-flex align-center flex-column">
<v-card width="400">
<v-card-title>Log in to the Application</v-card-title>
<v-card-title>Sign in to the Application</v-card-title>
<v-card-text>
<router-view />
</v-card-text>
@@ -26,7 +9,24 @@ const handleSignInAnonymous = async (e: Event) => {
<v-divider class="my-4" style="min-width: 90%" />
<div>
Don&lsquo;t have an account? <router-link to="/signup"> Sign up </router-link> or
<a href="#" @click="handleSignInAnonymous">sign in anonymously</a>
<a v-if="!isLoading" href="#" @click="handleSignInAnonymous">sign in anonymously</a>
<v-progress-circular v-else indeterminate />
</div>
</div>
<error-snack-bar :error="error" />
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { useSignInAnonymous } from '@nhost/vue'
const { signInAnonymous, error, isLoading } = useSignInAnonymous()
const router = useRouter()
const handleSignInAnonymous = async (e: Event) => {
e.preventDefault()
const { isSuccess } = await signInAnonymous()
if (isSuccess) {
router.push('/profile')
}
}
</script>

View File

@@ -1,38 +1,33 @@
<template>
<v-text-field v-model="email" label="Email" />
<v-text-field v-model="password" label="Password" type="password" />
<v-btn block color="primary" class="my-1" @click="signUp"> Sign up </v-btn>
<form @submit="handleSignUp">
<v-text-field v-model="email" label="Email" />
<v-text-field v-model="password" label="Password" type="password" />
<v-btn block color="primary" class="my-1" type="submit" :disabled="isLoading" :loading="isLoading"> Sign up </v-btn>
</form>
<v-btn class="my-1" block variant="text" color="primary" to="/signup">
&#8592; Other registration Options!
</v-btn>
<error-snack-bar :error="error" />
<verification-email-dialog v-model="emailVerificationDialog" :email="email" />
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
<script lang="ts" setup>
import { ref } from 'vue'
import { useSignUpEmailPassword } from '@nhost/vue'
export default defineComponent({
setup() {
const email = ref('')
const password = ref('')
const { signUpEmailPassword, error } = useSignUpEmailPassword({
redirectTo: window.location.origin
})
const signUp = async () => {
const result = await signUpEmailPassword(email, password)
if (result.error) {
}
}
return {
email,
error,
password,
signUp
}
}
const emailVerificationDialog = ref(false)
const email = ref('')
const password = ref('')
const { signUpEmailPassword, error, isLoading } = useSignUpEmailPassword({
redirectTo: '/'
})
const handleSignUp = async (e: Event) => {
e.preventDefault()
const result = await signUpEmailPassword(email, password)
if (result.needsEmailVerification) {
emailVerificationDialog.value = true
}
}
</script>

View File

@@ -50,5 +50,5 @@ export const routes: RouteRecordRaw[] = [
}
]
},
{ path: '/apollo', component: ApolloPage }
{ path: '/apollo', component: ApolloPage, meta: { auth: true } }
]

View File

@@ -17,7 +17,7 @@
"noEmit": true,
"jsx": "preserve",
"declaration": false,
"declarationMap": false
"declarationMap": false,
},
"include": ["src"]
}

View File

@@ -1,5 +1,5 @@
import { defineConfig } from 'vite'
import path from 'path'
import vue from '@vitejs/plugin-vue'
import vuetify from '@vuetify/vite-plugin'

View File

@@ -47,6 +47,8 @@ declare global {
const refDefault: typeof import('@vueuse/core')['refDefault']
const refThrottled: typeof import('@vueuse/core')['refThrottled']
const refWithControl: typeof import('@vueuse/core')['refWithControl']
const resolveRef: typeof import('@vueuse/core')['resolveRef']
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
const syncRef: typeof import('@vueuse/core')['syncRef']
const syncRefs: typeof import('@vueuse/core')['syncRefs']
const templateRef: typeof import('@vueuse/core')['templateRef']
@@ -65,6 +67,7 @@ declare global {
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
const useBase64: typeof import('@vueuse/core')['useBase64']
const useBattery: typeof import('@vueuse/core')['useBattery']
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
@@ -89,6 +92,7 @@ declare global {
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
const useDraggable: typeof import('@vueuse/core')['useDraggable']
const useDropZone: typeof import('@vueuse/core')['useDropZone']
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint']
const useElementHover: typeof import('@vueuse/core')['useElementHover']
@@ -100,6 +104,7 @@ declare global {
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
const useFavicon: typeof import('@vueuse/core')['useFavicon']
const useFetch: typeof import('@vueuse/core')['useFetch']
const useFileDialog: typeof import('@vueuse/core')['useFileDialog']
const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
const useFocus: typeof import('@vueuse/core')['useFocus']
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
@@ -108,6 +113,7 @@ declare global {
const useGamepad: typeof import('@vueuse/core')['useGamepad']
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
const useIdle: typeof import('@vueuse/core')['useIdle']
const useImage: typeof import('@vueuse/core')['useImage']
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
const useInterval: typeof import('@vueuse/core')['useInterval']
@@ -129,6 +135,7 @@ declare global {
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
const useNetwork: typeof import('@vueuse/core')['useNetwork']
const useNow: typeof import('@vueuse/core')['useNow']
const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
const useOnline: typeof import('@vueuse/core')['useOnline']
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
@@ -153,11 +160,13 @@ declare global {
const useShare: typeof import('@vueuse/core')['useShare']
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
const useStepper: typeof import('@vueuse/core')['useStepper']
const useStorage: typeof import('@vueuse/core')['useStorage']
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
const useSwipe: typeof import('@vueuse/core')['useSwipe']
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
const useThrottle: typeof import('@vueuse/core')['useThrottle']
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
@@ -184,12 +193,14 @@ declare global {
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
const watchArray: typeof import('@vueuse/core')['watchArray']
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
const watchOnce: typeof import('@vueuse/core')['watchOnce']
const watchPausable: typeof import('@vueuse/core')['watchPausable']
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
const whenever: typeof import('@vueuse/core')['whenever']
}

View File

@@ -10,15 +10,15 @@
"test": "vitest"
},
"dependencies": {
"@apollo/client": "^3.6.2",
"@apollo/client": "^3.6.9",
"@nhost/apollo": "*",
"@nhost/vue": "*",
"@vue/apollo-composable": "^4.0.0-alpha.17",
"@vueuse/core": "^8.4.2",
"@vue/apollo-composable": "4.0.0-alpha.18",
"@vueuse/core": "^8.9.2",
"graphql": "^15.7.2",
"graphql-tag": "^2.12.6",
"vue": "^3.2.33",
"vue-router": "^4.0.15"
"vue": "^3.2.37",
"vue-router": "^4.1.2"
},
"devDependencies": {
"@antfu/eslint-config": "^0.23.0",
@@ -26,7 +26,7 @@
"@types/node": "^17.0.32",
"@unocss/reset": "^0.33.2",
"@vitejs/plugin-vue": "^2.3.2",
"@vue/test-utils": "^2.0.0-rc.21",
"@vue/test-utils": "^2.0.2",
"eslint": "^8.15.0",
"jsdom": "^19.0.0",
"pnpm": "^7.0.1",
@@ -37,6 +37,6 @@
"vite": "^2.9.8",
"vite-plugin-pages": "^0.23.0",
"vitest": "^0.12.4",
"vue-tsc": "^0.34.12"
"vue-tsc": "^0.38.5"
}
}

View File

@@ -1,5 +1,11 @@
# @nhost/apollo
## 0.5.23
### Patch Changes
- f30d6779: Bump @apollo/client to v3.6.9
## 0.5.22
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/apollo",
"version": "0.5.22",
"version": "0.5.23",
"description": "Nhost Apollo Client library",
"license": "MIT",
"keywords": [
@@ -66,6 +66,6 @@
"graphql-ws": "^5.7.0"
},
"devDependencies": {
"@apollo/client": "^3.6.2"
"@apollo/client": "^3.6.9"
}
}

View File

@@ -1,5 +1,13 @@
# @nhost/react-apollo
## 4.6.3
### Patch Changes
- f30d6779: Bump @apollo/client to v3.6.9
- Updated dependencies [f30d6779]
- @nhost/apollo@0.5.23
## 4.6.2
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/react-apollo",
"version": "4.6.2",
"version": "4.6.3",
"description": "Nhost React Apollo client",
"license": "MIT",
"keywords": [
@@ -69,7 +69,7 @@
"react-dom": "^17.0.0 || ^18.0.0"
},
"devDependencies": {
"@apollo/client": "^3.6.2",
"@apollo/client": "^3.6.9",
"@nhost/react": "workspace:*",
"@types/react": "^18.0.8",
"graphql": "16",

View File

@@ -1,5 +1,12 @@
# @nhost/vue
## 0.3.7
### Patch Changes
- bc657251: Fix the deletion of refresh tokens in the URL when autoSignIn is enabled.
This feature only work when using the HTML5 history mode. A warning will appear when using the Hash mode and when in development mode.
## 0.3.6
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/vue",
"version": "0.3.6",
"version": "0.3.7",
"description": "Nhost Vue library",
"license": "MIT",
"keywords": [
@@ -65,7 +65,7 @@
"dependencies": {
"@nhost/core": "workspace:*",
"@nhost/nhost-js": "workspace:*",
"@vueuse/core": "^8.3.1",
"@vueuse/core": "^8.9.2",
"@xstate/vue": "^1.0.0",
"immer": "^9.0.12",
"jwt-decode": "^3.1.2"
@@ -75,8 +75,8 @@
"@nhost/hasura-auth-js": "workspace:*",
"@vitejs/plugin-vue": "^2.3.1",
"@xstate/inspect": "^0.6.2",
"vue": "^3.2.31",
"vue-router": "^4.0.14",
"vue": "^3.2.37",
"vue-router": "^4.1.2",
"ws": "^8.4.2",
"xstate": "^4.32.1"
}

View File

@@ -1,6 +1,6 @@
import { App, getCurrentInstance } from 'vue'
import { Router } from 'vue-router'
import { App, warn } from 'vue'
import { removeParameterFromWindow } from '@nhost/core'
import {
BackendUrl,
NhostAuthConstructorParams,
@@ -16,8 +16,10 @@ export interface NhostVueClientConstructorParams
Omit<NhostAuthConstructorParams, 'url' | 'start' | 'client'> {}
export class NhostClient extends VanillaClient {
private autoSignIn: boolean
constructor(params: NhostVueClientConstructorParams) {
super({ ...params, start: true })
this.autoSignIn = params.autoSignIn ?? true
}
/**
@@ -25,40 +27,25 @@ export class NhostClient extends VanillaClient {
* This method transforms the NhostClient class into a Vue plugin
*/
install(app: App) {
const autoSignIn = this.autoSignIn
app.provide(DefaultNhostClient, this)
// * Remove the refreshToken & type from the hash when using Vue Router
app.mixin({
created() {
const instance = getCurrentInstance()
// * On creation, check if we are in the root component.
if (instance?.uid === instance?.root.uid) {
const router: Router | undefined = this.$router
// * If Vue router is used, remove the refeshToken & type from the hash and query after each routing event
router?.afterEach((to) => {
if (to.hash.includes('refreshToken') || to.query['refreshToken']) {
delete to.query['refreshToken']
delete to.query['type']
const hash = new URLSearchParams(to.hash.slice(1))
if (hash.has('refreshToken')) {
hash.delete('refreshToken')
hash.delete('type')
}
let path = '/#' + to.path
if (Object.keys(to.params).length) {
path +=
'?' +
Object.entries(to.params)
.map(([key, value]) => `${key}=${value}`)
.join('&')
}
if (Array.from(hash).length) {
path += '#' + hash.toString()
}
window.history.pushState({}, '', path)
}
})
}
if (autoSignIn) {
const router = app.config.globalProperties.$router
if (!router) {
// * Vue-router is not set. Do nothing.
return
}
})
if (router.options.history.base) {
warn(
'[Nhost]: Vue-router is configured with a history Hash Mode. Refresh tokens will not be removed from the hash.'
)
return
}
router.afterEach(() => {
removeParameterFromWindow('refreshToken')
removeParameterFromWindow('type')
})
}
}
}

1006
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff