Adding Local Provider

This commit is contained in:
Bel Curcio 2021-06-10 18:09:22 -03:00
parent 5ce1ee7aba
commit 23b69ec442
16 changed files with 75 additions and 7550 deletions

View File

@ -1,33 +1 @@
# Vendure Storefront Data Hooks
UI hooks and data fetching methods built from the ground up for e-commerce applications written in React, that use [Vendure](http://vendure.io/) as a headless e-commerce platform.
## Usage
1. Clone this repo and install its dependencies with `yarn install` or `npm install`
2. Set the Vendure provider and API URL in your `.env.local` file:
```
COMMERCE_PROVIDER=vendure
NEXT_PUBLIC_VENDURE_SHOP_API_URL=https://demo.vendure.io/shop-api
NEXT_PUBLIC_VENDURE_LOCAL_URL=/vendure-shop-api
```
3. With the Vendure server running, start this project using `yarn dev` or `npm run dev`.
## Known Limitations
1. Vendure does not ship with built-in wishlist functionality.
2. Nor does it come with any kind of blog/page-building feature. Both of these can be created as Vendure plugins, however.
3. The entire Vendure customer flow is carried out via its GraphQL API. This means that there is no external, pre-existing checkout flow. The checkout flow must be created as part of the Next.js app. See https://github.com/vercel/commerce/issues/64 for further discusion.
4. By default, the sign-up flow in Vendure uses email verification. This means that using the existing "sign up" flow from this project will not grant a new user the ability to authenticate, since the new account must first be verified. Again, the necessary parts to support this flow can be created as part of the Next.js app.
## Code generation
This provider makes use of GraphQL code generation. The [schema.graphql](./schema.graphql) and [schema.d.ts](./schema.d.ts) files contain the generated types & schema introspection results.
When developing the provider, changes to any GraphQL operations should be followed by re-generation of the types and schema files:
From the project root dir, run
```sh
graphql-codegen --config ./framework/vendure/codegen.json
```
# Next.js Local Provider

View File

@ -1,18 +1,19 @@
import type { CommerceAPIConfig } from '@commerce/api'
import fetchGraphqlApi from './utils/fetch-graphql-api'
import type { APIProvider, CommerceAPIConfig } from '@commerce/api'
import { CommerceAPI, getCommerceApi as commerceApi } from '@commerce/api'
import fetchGraphqlApi from './utils/fetch-graphql-api'
import getAllPages from './operations/get-all-pages'
import getPage from './operations/get-page'
import getSiteInfo from './operations/get-site-info'
import getCustomerWishlist from './operations/get-customer-wishlist'
import getAllProductPaths from './operations/get-all-product-paths'
import getAllProducts from './operations/get-all-products'
import getProduct from './operations/get-product'
export interface VendureConfig extends CommerceAPIConfig {}
export interface LocalConfig extends CommerceAPIConfig {}
const ONE_DAY = 60 * 60 * 24
const config: VendureConfig = {
const config: LocalConfig = {
commerceUrl: '',
apiToken: '',
cartCookie: '',
@ -25,6 +26,7 @@ const operations = {
getAllPages,
getPage,
getSiteInfo,
getCustomerWishlist,
getAllProductPaths,
getAllProducts,
getProduct,
@ -33,9 +35,10 @@ const operations = {
export const provider = { config, operations }
export type Provider = typeof provider
export type LocalAPI<P extends Provider = Provider> = CommerceAPI<P>
export function getCommerceApi<P extends Provider>(
customProvider: P = provider as any
): CommerceAPI<P> {
): LocalAPI<P> {
return commerceApi(customProvider)
}

View File

@ -2,10 +2,10 @@ export type Page = any
export type GetAllPagesResult = { pages: Page[] }
export default function getAllPagesOperation() {
function getAllPages(): GetAllPagesResult {
return {
function getAllPages(): Promise<GetAllPagesResult> {
return Promise.resolve({
pages: [],
}
})
}
return getAllPages
}

View File

@ -3,10 +3,10 @@ export type GetAllProductPathsResult = {
}
export default function getAllProductPathsOperation() {
function getAllProductPaths(): GetAllProductPathsResult {
return {
function getAllProductPaths(): Promise<GetAllProductPathsResult> {
return Promise.resolve({
products: [].map((p) => ({ path: `/hello` })),
}
})
}
return getAllProductPaths

View File

@ -1,13 +1,17 @@
import { Product } from '@commerce/types/product'
import { OperationContext } from '@commerce/api/operations'
import type { OperationContext } from '@commerce/api/operations'
import type { Provider } from '../index'
export type ProductVariables = { first?: number }
export default function getAllProductsOperation() {
function getAllProducts(): { products: Product[] | any[] } {
return {
export default function getAllProductsOperation({
commerce,
}: OperationContext<Provider>) {
async function getAllProducts(): Promise<{ products: Product[] | any[] }> {
return Promise.resolve({
products: [],
}
})
}
return getAllProducts
}

View File

@ -5,8 +5,8 @@ export type PageVariables = {
}
export default function getPageOperation() {
function getPage(): GetPageResult {
return {}
function getPage(): Promise<GetPageResult> {
return Promise.resolve({})
}
return getPage
}

View File

@ -9,11 +9,11 @@ export type GetSiteInfoResult<
> = T
export default function getSiteInfoOperation({}: OperationContext<any>) {
function getSiteInfo(): GetSiteInfoResult {
return {
function getSiteInfo(): Promise<GetSiteInfoResult> {
return Promise.resolve({
categories: [],
brands: [],
}
})
}
return getSiteInfo

View File

@ -0,0 +1,6 @@
export { default as getPage } from './get-page'
export { default as getSiteInfo } from './get-site-info'
export { default as getAllPages } from './get-all-pages'
export { default as getProduct } from './get-product'
export { default as getAllProducts } from './get-all-products'
export { default as getAllProductPaths } from './get-all-product-paths'

View File

@ -1,28 +0,0 @@
{
"schema": {
"http://localhost:3001/shop-api": {}
},
"documents": [
{
"./framework/vendure/**/*.{ts,tsx}": {
"noRequire": true
}
}
],
"generates": {
"./framework/vendure/schema.d.ts": {
"plugins": ["typescript", "typescript-operations"],
"config": {
"scalars": {
"ID": "string"
}
}
},
"./framework/vendure/schema.graphql": {
"plugins": ["schema-ast"]
}
},
"hooks": {
"afterAllFileWrite": ["prettier --write"]
}
}

22
framework/local/data.json Normal file
View File

@ -0,0 +1,22 @@
{
"products": [
{
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0LzU0NDczMjUwMjQ0MjA=",
"name": "New Short Sleeve T-Shirt",
"vendor": "Next.js",
"path": "/new-short-sleeve-t-shirt",
"slug": "new-short-sleeve-t-shirt",
"price": { "value": 25, "currencyCode": "USD" },
"images": [
{
"url": "/assets/drop-shirt-0",
"altText": null,
"width": 1000,
"height": 1000
}
],
"variants": [],
"options": []
}
]
}

View File

@ -1,51 +1,19 @@
import { Fetcher } from '@commerce/utils/types'
import { FetcherError } from '@commerce/utils/errors'
async function getText(res: Response) {
try {
return (await res.text()) || res.statusText
} catch (error) {
return res.statusText
}
}
async function getError(res: Response) {
if (res.headers.get('Content-Type')?.includes('application/json')) {
const data = await res.json()
return new FetcherError({ errors: data.errors, status: res.status })
}
return new FetcherError({ message: await getText(res), status: res.status })
}
const shopApiUrl = '/data.json'
export const fetcher: Fetcher = async ({
url,
method = 'POST',
method = 'GET',
variables,
query,
body: bodyObj,
}) => {
const shopApiUrl =
process.env.NEXT_PUBLIC_VENDURE_LOCAL_URL ||
process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL
if (!shopApiUrl) {
throw new Error(
'The Vendure Shop API url has not been provided. Please define NEXT_PUBLIC_VENDURE_SHOP_API_URL in .env.local'
)
}
const hasBody = Boolean(variables || query)
const body = hasBody ? JSON.stringify({ query, variables }) : undefined
const headers = hasBody ? { 'Content-Type': 'application/json' } : undefined
const res = await fetch(shopApiUrl, {
method,
body,
headers,
credentials: 'include',
})
const res = await fetch(shopApiUrl)
if (res.ok) {
const { data } = await res.json()
return data
}
throw await getError(res)
throw res
}

View File

@ -1,29 +1,28 @@
import * as React from 'react'
import { ReactNode } from 'react'
import { localProvider } from './provider'
import {
CommerceConfig,
CommerceProvider as CoreCommerceProvider,
useCommerce as useCoreCommerce,
} from '@commerce'
import { vendureProvider } from './provider'
export const vendureConfig: CommerceConfig = {
export const localConfig: CommerceConfig = {
locale: 'en-us',
cartCookie: 'session',
}
export type VendureConfig = Partial<CommerceConfig>
export type VendureProps = {
export function CommerceProvider({
children,
...config
}: {
children?: ReactNode
locale: string
} & VendureConfig
export function CommerceProvider({ children, ...config }: VendureProps) {
} & Partial<CommerceConfig>) {
return (
<CoreCommerceProvider
provider={vendureProvider}
config={{ ...vendureConfig, ...config }}
provider={localProvider}
config={{ ...localConfig, ...config }}
>
{children}
</CoreCommerceProvider>

View File

@ -3,6 +3,6 @@ const commerce = require('./commerce.config.json')
module.exports = {
commerce,
images: {
domains: ['localhost', 'demo.vendure.io'],
domains: ['localhost'],
},
}

View File

@ -10,10 +10,12 @@ import { handler as useLogout } from './auth/use-logout'
import { handler as useSignup } from './auth/use-signup'
import { fetcher } from './fetcher'
export const vendureProvider: Provider = {
export type Provider = typeof localProvider
export const localProvider: Provider = {
locale: 'en-us',
cartCookie: 'session',
fetcher: (e) => e,
fetcher: fetcher,
cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
customer: { useCustomer },
products: { useSearch },

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff