New Release (#371)

* Custom Checkout Progress

* Updates to Checkout

* Custom Checkout Progress

* Adding tabs

* Adding Collapse

* Adding Collapse

* Improving Sidebar Scroll

* Modif footer

* Changes

* More design updates

* sidebar cart

* More design updates

* More design updates

* More design updates

* More design updates

* Types

* Types

* Design Updates

* More changes

* More changes

* More changes

* Changes

* Changes

* Changes

* New tailwind required changes

* Sidebar Styling issues with Mobile

* Latest changes - Normalizing cart

* Styling Fixes

* New changes

* Changes

* latest

* Refactor and Renaming some UI Props

* Adding Quantity Component

* Adding Rating Component

* Rating Component

* More updates

* User Select disabled, plus hidding horizontal scroll bars

* Changes

* Adding ProductOptions Component and more helpers

* Styling updates

* Styling updates

* Fix for slim tags

* Missmatch with RightArrow

* Footer updates and some styles

* Latest Updates

* Latest Updates

* Latest Updates

* Removing Portal, since it's not needed. We might add it later I'd rather not to.

* Removing Portal, since it's not needed. We might add it later I'd rather not to.

* Sam backdrop filter

* General UI Improvements

* General UI Improvements

* Search now with Geist Colors

* Now with Geist Colors

* Changes

* Scroll for Mobile on IOs devises

* LoadingDots Working (:

* Changes

* More Changes

* Perf changes

* More perf changes

* Fade to the Nametags in the ProductCard

* changes

* Search issue ui

* Search issue ui

* Make sure to only refresh navbar and modals when required

* Index revalidate

* Fixed image issue

* hide album scroll on windows

* Fix scrollbar

* Changing

* Adding 404 with Layout

* Removing Toast

* Adding Assets

* Adding Assets

* Progress with LocalProvider

* New productTag

* Only images for the drop

* changes

* Empty SWRhooks

* Adding Local Provider

* Working local

* Working view of a LocalProvider

* More updates

* Changes

* Removed react-ticker

* default to local if no env available

* default to local if no env available

* add missing `@` to css import

* rewrite search rewrites to multiple pages

* allow requests in getStaticProps to execute in parallel

* make type import explicit

* add a tsconfig.js file

* use local provider in tsconfig.js

* avoid a circular dependency

* Saleor was not in the providers list

* avoid circular dependency in bigcommerce

* Adding more to the Local Provider (#366)

* Adding more data

* Adding more data

* optimize assets (#370)

* Optimize assets (#372)

* optimize assets

* remove assets

* remove assets

* cart enabled

* Adding saleor

* Changes with Webpack

* Changes

Co-authored-by: Luis Alvarez <luis@vercel.com>
Co-authored-by: Tobias Koppers <tobias.koppers@googlemail.com>
Co-authored-by: Shu Ding <g@shud.in>
This commit is contained in:
B
2021-06-15 20:23:17 -03:00
committed by GitHub
parent 3c9b90f453
commit 78cc378a72
202 changed files with 15406 additions and 2201 deletions

View File

@@ -4,8 +4,8 @@ import {
CommerceAPIConfig,
getCommerceApi as commerceApi,
} from '@commerce/api'
import fetchGraphqlApi from './utils/fetch-graphql-api'
import fetchStoreApi from './utils/fetch-store-api'
import createFetchGraphqlApi from './utils/fetch-graphql-api'
import createFetchStoreApi from './utils/fetch-store-api'
import type { CartAPI } from './endpoints/cart'
import type { CustomerAPI } from './endpoints/customer'
@@ -68,14 +68,14 @@ const config: BigcommerceConfig = {
customerCookie: 'SHOP_TOKEN',
cartCookie: process.env.BIGCOMMERCE_CART_COOKIE ?? 'bc_cartId',
cartCookieMaxAge: ONE_DAY * 30,
fetch: fetchGraphqlApi,
fetch: createFetchGraphqlApi(() => getCommerceApi().getConfig()),
applyLocale: true,
// REST API only
storeApiUrl: STORE_API_URL,
storeApiToken: STORE_API_TOKEN,
storeApiClientId: STORE_API_CLIENT_ID,
storeChannelId: STORE_CHANNEL_ID,
storeApiFetch: fetchStoreApi,
storeApiFetch: createFetchStoreApi(() => getCommerceApi().getConfig()),
}
const operations = {

View File

@@ -1,38 +1,36 @@
import { FetcherError } from '@commerce/utils/errors'
import type { GraphQLFetcher } from '@commerce/api'
import { provider } from '..'
import type { BigcommerceConfig } from '../index'
import fetch from './fetch'
const fetchGraphqlApi: GraphQLFetcher = async (
query: string,
{ variables, preview } = {},
fetchOptions
) => {
// log.warn(query)
const { config } = provider
const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
...fetchOptions,
method: 'POST',
headers: {
Authorization: `Bearer ${config.apiToken}`,
...fetchOptions?.headers,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables,
}),
})
const json = await res.json()
if (json.errors) {
throw new FetcherError({
errors: json.errors ?? [{ message: 'Failed to fetch Bigcommerce API' }],
status: res.status,
const fetchGraphqlApi: (getConfig: () => BigcommerceConfig) => GraphQLFetcher =
(getConfig) =>
async (query: string, { variables, preview } = {}, fetchOptions) => {
// log.warn(query)
const config = getConfig()
const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
...fetchOptions,
method: 'POST',
headers: {
Authorization: `Bearer ${config.apiToken}`,
...fetchOptions?.headers,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables,
}),
})
const json = await res.json()
if (json.errors) {
throw new FetcherError({
errors: json.errors ?? [{ message: 'Failed to fetch Bigcommerce API' }],
status: res.status,
})
}
return { data: json.data, res }
}
return { data: json.data, res }
}
export default fetchGraphqlApi

View File

@@ -1,56 +1,56 @@
import type { RequestInit, Response } from '@vercel/fetch'
import { provider } from '..'
import type { BigcommerceConfig } from '../index'
import { BigcommerceApiError, BigcommerceNetworkError } from './errors'
import fetch from './fetch'
export default async function fetchStoreApi<T>(
endpoint: string,
options?: RequestInit
): Promise<T> {
const { config } = provider
let res: Response
const fetchStoreApi =
<T>(getConfig: () => BigcommerceConfig) =>
async (endpoint: string, options?: RequestInit): Promise<T> => {
const config = getConfig()
let res: Response
try {
res = await fetch(config.storeApiUrl + endpoint, {
...options,
headers: {
...options?.headers,
'Content-Type': 'application/json',
'X-Auth-Token': config.storeApiToken,
'X-Auth-Client': config.storeApiClientId,
},
})
} catch (error) {
throw new BigcommerceNetworkError(
`Fetch to Bigcommerce failed: ${error.message}`
)
try {
res = await fetch(config.storeApiUrl + endpoint, {
...options,
headers: {
...options?.headers,
'Content-Type': 'application/json',
'X-Auth-Token': config.storeApiToken,
'X-Auth-Client': config.storeApiClientId,
},
})
} catch (error) {
throw new BigcommerceNetworkError(
`Fetch to Bigcommerce failed: ${error.message}`
)
}
const contentType = res.headers.get('Content-Type')
const isJSON = contentType?.includes('application/json')
if (!res.ok) {
const data = isJSON ? await res.json() : await getTextOrNull(res)
const headers = getRawHeaders(res)
const msg = `Big Commerce API error (${
res.status
}) \nHeaders: ${JSON.stringify(headers, null, 2)}\n${
typeof data === 'string' ? data : JSON.stringify(data, null, 2)
}`
throw new BigcommerceApiError(msg, res, data)
}
if (res.status !== 204 && !isJSON) {
throw new BigcommerceApiError(
`Fetch to Bigcommerce API failed, expected JSON content but found: ${contentType}`,
res
)
}
// If something was removed, the response will be empty
return res.status === 204 ? null : await res.json()
}
const contentType = res.headers.get('Content-Type')
const isJSON = contentType?.includes('application/json')
if (!res.ok) {
const data = isJSON ? await res.json() : await getTextOrNull(res)
const headers = getRawHeaders(res)
const msg = `Big Commerce API error (${
res.status
}) \nHeaders: ${JSON.stringify(headers, null, 2)}\n${
typeof data === 'string' ? data : JSON.stringify(data, null, 2)
}`
throw new BigcommerceApiError(msg, res, data)
}
if (res.status !== 204 && !isJSON) {
throw new BigcommerceApiError(
`Fetch to Bigcommerce API failed, expected JSON content but found: ${contentType}`,
res
)
}
// If something was removed, the response will be empty
return res.status === 204 ? null : await res.json()
}
export default fetchStoreApi
function getRawHeaders(res: Response) {
const headers: { [key: string]: string } = {}

View File

@@ -4,7 +4,8 @@ import {
CommerceProvider as CoreCommerceProvider,
useCommerce as useCoreCommerce,
} from '@commerce'
import { bigcommerceProvider, BigcommerceProvider } from './provider'
import { bigcommerceProvider } from './provider'
import type { BigcommerceProvider } from './provider'
export { bigcommerceProvider }
export type { BigcommerceProvider }

View File

@@ -72,9 +72,8 @@ export type APIProvider = {
operations: APIOperations<any>
}
export type CommerceAPI<
P extends APIProvider = APIProvider
> = CommerceAPICore<P> & AllOperations<P>
export type CommerceAPI<P extends APIProvider = APIProvider> =
CommerceAPICore<P> & AllOperations<P>
export class CommerceAPICore<P extends APIProvider = APIProvider> {
constructor(readonly provider: P) {}
@@ -134,17 +133,17 @@ export function getEndpoint<
}
}
export const createEndpoint = <API extends GetAPISchema<any, any>>(
endpoint: API['endpoint']
) => <P extends APIProvider>(
commerce: CommerceAPI<P>,
context?: Partial<API['endpoint']> & {
config?: P['config']
options?: API['schema']['endpoint']['options']
export const createEndpoint =
<API extends GetAPISchema<any, any>>(endpoint: API['endpoint']) =>
<P extends APIProvider>(
commerce: CommerceAPI<P>,
context?: Partial<API['endpoint']> & {
config?: P['config']
options?: API['schema']['endpoint']['options']
}
): NextApiHandler => {
return getEndpoint(commerce, { ...endpoint, ...context })
}
): NextApiHandler => {
return getEndpoint(commerce, { ...endpoint, ...context })
}
export interface CommerceAPIConfig {
locale?: string

View File

@@ -7,7 +7,14 @@ const fs = require('fs')
const merge = require('deepmerge')
const prettier = require('prettier')
const PROVIDERS = ['bigcommerce', 'shopify', 'swell', 'vendure', 'saleor']
const PROVIDERS = [
'bigcommerce',
'saleor',
'shopify',
'swell',
'vendure',
'local',
]
function getProviderName() {
return (
@@ -18,9 +25,7 @@ function getProviderName() {
? 'shopify'
: process.env.NEXT_PUBLIC_SWELL_STORE_ID
? 'swell'
: process.env.NEXT_PUBLIC_SALEOR_API_URL
? 'saleor'
: null)
: 'local')
)
}
@@ -52,27 +57,11 @@ function withCommerceConfig(nextConfig = {}) {
// Update paths in `tsconfig.json` to point to the selected provider
if (config.commerce.updateTSConfig !== false) {
const tsconfigPath = path.join(process.cwd(), 'tsconfig.json')
const tsconfig = require(tsconfigPath)
tsconfig.compilerOptions.paths['@framework'] = [`framework/${name}`]
tsconfig.compilerOptions.paths['@framework/*'] = [`framework/${name}/*`]
// When running for production it may be useful to exclude the other providers
// from TS checking
if (process.env.VERCEL) {
const exclude = tsconfig.exclude.filter(
(item) => !item.startsWith('framework/')
)
tsconfig.exclude = PROVIDERS.reduce((exclude, current) => {
if (current !== name) exclude.push(`framework/${current}`)
return exclude
}, exclude)
}
const staticTsconfigPath = path.join(process.cwd(), 'tsconfig.json')
const tsconfig = require('../../tsconfig.js')
fs.writeFileSync(
tsconfigPath,
staticTsconfigPath,
prettier.format(JSON.stringify(tsconfig), { parser: 'json' })
)
}

View File

@@ -58,7 +58,8 @@ import {
CommerceProvider as CoreCommerceProvider,
useCommerce as useCoreCommerce,
} from '@commerce'
import { bigcommerceProvider, BigcommerceProvider } from './provider'
import { bigcommerceProvider } from './provider'
import type { BigcommerceProvider } from './provider'
export { bigcommerceProvider }
export type { BigcommerceProvider }

View File

@@ -165,15 +165,13 @@ export type AddItemHandler<T extends CartTypes = CartTypes> = AddItemHook<T> & {
body: { cartId: string }
}
export type UpdateItemHandler<
T extends CartTypes = CartTypes
> = UpdateItemHook<T> & {
data: T['cart']
body: { cartId: string }
}
export type UpdateItemHandler<T extends CartTypes = CartTypes> =
UpdateItemHook<T> & {
data: T['cart']
body: { cartId: string }
}
export type RemoveItemHandler<
T extends CartTypes = CartTypes
> = RemoveItemHook<T> & {
body: { cartId: string }
}
export type RemoveItemHandler<T extends CartTypes = CartTypes> =
RemoveItemHook<T> & {
body: { cartId: string }
}

View File

@@ -5,7 +5,7 @@ export type ProductImage = {
export type ProductPrice = {
value: number
currencyCode?: 'USD' | 'ARS' | string
currencyCode?: 'USD' | 'EUR' | 'ARS' | string
retailPrice?: number
salePrice?: number
listPrice?: number

View File

@@ -0,0 +1 @@
COMMERCE_PROVIDER=local

View File

@@ -0,0 +1 @@
# Next.js Local Provider

View File

@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1,42 @@
import type { CommerceAPI, CommerceAPIConfig } from '@commerce/api'
import { getCommerceApi as commerceApi } from '@commerce/api'
import createFetcher from './utils/fetch-local'
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 LocalConfig extends CommerceAPIConfig {}
const config: LocalConfig = {
commerceUrl: '',
apiToken: '',
cartCookie: '',
customerCookie: '',
cartCookieMaxAge: 2592000,
fetch: createFetcher(() => getCommerceApi().getConfig()),
}
const operations = {
getAllPages,
getPage,
getSiteInfo,
getCustomerWishlist,
getAllProductPaths,
getAllProducts,
getProduct,
}
export const provider = { config, operations }
export type Provider = typeof provider
export type LocalAPI<P extends Provider = Provider> = CommerceAPI<P | any>
export function getCommerceApi<P extends Provider>(
customProvider: P = provider as any
): LocalAPI<P> {
return commerceApi(customProvider as any)
}

View File

@@ -0,0 +1,19 @@
export type Page = { url: string }
export type GetAllPagesResult = { pages: Page[] }
import type { LocalConfig } from '../index'
export default function getAllPagesOperation() {
function getAllPages({
config,
preview,
}: {
url?: string
config?: Partial<LocalConfig>
preview?: boolean
}): Promise<GetAllPagesResult> {
return Promise.resolve({
pages: [],
})
}
return getAllPages
}

View File

@@ -0,0 +1,15 @@
import data from '../../data.json'
export type GetAllProductPathsResult = {
products: Array<{ path: string }>
}
export default function getAllProductPathsOperation() {
function getAllProductPaths(): Promise<GetAllProductPathsResult> {
return Promise.resolve({
products: data.products.map(({ path }) => ({ path })),
})
}
return getAllProductPaths
}

View File

@@ -0,0 +1,25 @@
import { Product } from '@commerce/types/product'
import { GetAllProductsOperation } from '@commerce/types/product'
import type { OperationContext } from '@commerce/api/operations'
import type { LocalConfig, Provider } from '../index'
import data from '../../data.json'
export default function getAllProductsOperation({
commerce,
}: OperationContext<any>) {
async function getAllProducts<T extends GetAllProductsOperation>({
query = '',
variables,
config,
}: {
query?: string
variables?: T['variables']
config?: Partial<LocalConfig>
preview?: boolean
} = {}): Promise<{ products: Product[] | any[] }> {
return {
products: data.products,
}
}
return getAllProducts
}

View File

@@ -0,0 +1,6 @@
export default function getCustomerWishlistOperation() {
function getCustomerWishlist(): any {
return { wishlist: {} }
}
return getCustomerWishlist
}

View File

@@ -0,0 +1,13 @@
export type Page = any
export type GetPageResult = { page?: Page }
export type PageVariables = {
id: number
}
export default function getPageOperation() {
function getPage(): Promise<GetPageResult> {
return Promise.resolve({})
}
return getPage
}

View File

@@ -0,0 +1,26 @@
import type { LocalConfig } from '../index'
import { Product } from '@commerce/types/product'
import { GetProductOperation } from '@commerce/types/product'
import data from '../../data.json'
import type { OperationContext } from '@commerce/api/operations'
export default function getProductOperation({
commerce,
}: OperationContext<any>) {
async function getProduct<T extends GetProductOperation>({
query = '',
variables,
config,
}: {
query?: string
variables?: T['variables']
config?: Partial<LocalConfig>
preview?: boolean
} = {}): Promise<Product | {} | any> {
return {
product: data.products.find(({ slug }) => slug === variables!.slug),
}
}
return getProduct
}

View File

@@ -0,0 +1,43 @@
import { OperationContext } from '@commerce/api/operations'
import { Category } from '@commerce/types/site'
import { LocalConfig } from '../index'
export type GetSiteInfoResult<
T extends { categories: any[]; brands: any[] } = {
categories: Category[]
brands: any[]
}
> = T
export default function getSiteInfoOperation({}: OperationContext<any>) {
function getSiteInfo({
query,
variables,
config: cfg,
}: {
query?: string
variables?: any
config?: Partial<LocalConfig>
preview?: boolean
} = {}): Promise<GetSiteInfoResult> {
return Promise.resolve({
categories: [
{
id: 'new-arrivals',
name: 'New Arrivals',
slug: 'new-arrivals',
path: '/new-arrivals',
},
{
id: 'featured',
name: 'Featured',
slug: 'featured',
path: '/featured',
},
],
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

@@ -0,0 +1,34 @@
import { FetcherError } from '@commerce/utils/errors'
import type { GraphQLFetcher } from '@commerce/api'
import type { LocalConfig } from '../index'
import fetch from './fetch'
const fetchGraphqlApi: (getConfig: () => LocalConfig) => GraphQLFetcher =
(getConfig) =>
async (query: string, { variables, preview } = {}, fetchOptions) => {
const config = getConfig()
const res = await fetch(config.commerceUrl, {
...fetchOptions,
method: 'POST',
headers: {
...fetchOptions?.headers,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables,
}),
})
const json = await res.json()
if (json.errors) {
throw new FetcherError({
errors: json.errors ?? [{ message: 'Failed to fetch for API' }],
status: res.status,
})
}
return { data: json.data, res }
}
export default fetchGraphqlApi

View File

@@ -0,0 +1,3 @@
import zeitFetch from '@vercel/fetch'
export default zeitFetch()

View File

@@ -0,0 +1,3 @@
export { default as useLogin } from './use-login'
export { default as useLogout } from './use-logout'
export { default as useSignup } from './use-signup'

View File

@@ -0,0 +1,16 @@
import { MutationHook } from '@commerce/utils/types'
import useLogin, { UseLogin } from '@commerce/auth/use-login'
export default useLogin as UseLogin<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher() {
return null
},
useHook: () => () => {
return async function () {}
},
}

View File

@@ -0,0 +1,17 @@
import { MutationHook } from '@commerce/utils/types'
import useLogout, { UseLogout } from '@commerce/auth/use-logout'
export default useLogout as UseLogout<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher() {
return null
},
useHook:
({ fetch }) =>
() =>
async () => {},
}

View File

@@ -0,0 +1,19 @@
import { useCallback } from 'react'
import useCustomer from '../customer/use-customer'
import { MutationHook } from '@commerce/utils/types'
import useSignup, { UseSignup } from '@commerce/auth/use-signup'
export default useSignup as UseSignup<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher() {
return null
},
useHook:
({ fetch }) =>
() =>
() => {},
}

View File

@@ -0,0 +1,4 @@
export { default as useCart } from './use-cart'
export { default as useAddItem } from './use-add-item'
export { default as useRemoveItem } from './use-remove-item'
export { default as useUpdateItem } from './use-update-item'

View File

@@ -0,0 +1,17 @@
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
import { MutationHook } from '@commerce/utils/types'
export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input, options, fetch }) {},
useHook:
({ fetch }) =>
() => {
return async function addItem() {
return {}
}
},
}

View File

@@ -0,0 +1,42 @@
import { useMemo } from 'react'
import { SWRHook } from '@commerce/utils/types'
import useCart, { UseCart } from '@commerce/cart/use-cart'
export default useCart as UseCart<typeof handler>
export const handler: SWRHook<any> = {
fetchOptions: {
query: '',
},
async fetcher() {
return {
id: '',
createdAt: '',
currency: { code: '' },
taxesIncluded: '',
lineItems: [],
lineItemsSubtotalPrice: '',
subtotalPrice: 0,
totalPrice: 0,
}
},
useHook:
({ useData }) =>
(input) => {
return useMemo(
() =>
Object.create(
{},
{
isEmpty: {
get() {
return true
},
enumerable: true,
},
}
),
[]
)
},
}

View File

@@ -0,0 +1,18 @@
import { MutationHook } from '@commerce/utils/types'
import useRemoveItem, { UseRemoveItem } from '@commerce/cart/use-remove-item'
export default useRemoveItem as UseRemoveItem<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input, options, fetch }) {},
useHook:
({ fetch }) =>
() => {
return async function removeItem(input) {
return {}
}
},
}

View File

@@ -0,0 +1,18 @@
import { MutationHook } from '@commerce/utils/types'
import useUpdateItem, { UseUpdateItem } from '@commerce/cart/use-update-item'
export default useUpdateItem as UseUpdateItem<any>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input, options, fetch }) {},
useHook:
({ fetch }) =>
() => {
return async function addItem() {
return {}
}
},
}

View File

@@ -0,0 +1,6 @@
{
"provider": "local",
"features": {
"wishlist": false
}
}

View File

@@ -0,0 +1 @@
export { default as useCustomer } from './use-customer'

View File

@@ -0,0 +1,15 @@
import { SWRHook } from '@commerce/utils/types'
import useCustomer, { UseCustomer } from '@commerce/customer/use-customer'
export default useCustomer as UseCustomer<typeof handler>
export const handler: SWRHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input, options, fetch }) {},
useHook: () => () => {
return async function addItem() {
return {}
}
},
}

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

@@ -0,0 +1,235 @@
{
"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" },
"descriptionHtml": "<p><span>Show off your love for Next.js and Vercel with this unique,&nbsp;</span><strong>limited edition</strong><span>&nbsp;t-shirt. This design is part of a limited run, numbered drop at the June 2021 Next.js Conf. It features a unique, handcrafted triangle design. Get it while supplies last only 200 of these shirts will be made!&nbsp;</span><strong>All proceeds will be donated to charity.</strong></p>",
"images": [
{
"url": "/assets/drop-shirt-0.png",
"altText": "Shirt",
"width": 1000,
"height": 1000
},
{
"url": "/assets/drop-shirt-1.png",
"altText": "Shirt",
"width": 1000,
"height": 1000
},
{
"url": "/assets/drop-shirt-2.png",
"altText": "Shirt",
"width": 1000,
"height": 1000
}
],
"variants": [
{
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0LzU0NDczMjUwMjQ0MjAss=",
"options": [
{
"__typename": "MultipleChoiceOption",
"id": "asd",
"displayName": "Size",
"values": [
{
"label": "XL"
}
]
}
]
}
],
"options": [
{
"id": "option-color",
"displayName": "Color",
"values": [
{
"label": "color",
"hexColors": ["#222"]
}
]
},
{
"id": "option-size",
"displayName": "Size",
"values": [
{
"label": "S"
},
{
"label": "M"
},
{
"label": "L"
}
]
}
]
},
{
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9ksdWN0LzU0NDczMjUwMjQ0MjA=",
"name": "Lightweight Jacket",
"vendor": "Next.js",
"path": "/lightweight-jacket",
"slug": "lightweight-jacket",
"price": { "value": 249.99, "currencyCode": "USD" },
"descriptionHtml": "<p><span>Show off your love for Next.js and Vercel with this unique,&nbsp;</span><strong>limited edition</strong><span>&nbsp;t-shirt. This design is part of a limited run, numbered drop at the June 2021 Next.js Conf. It features a unique, handcrafted triangle design. Get it while supplies last only 200 of these shirts will be made!&nbsp;</span><strong>All proceeds will be donated to charity.</strong></p>",
"images": [
{
"url": "/assets/lightweight-jacket-0.png",
"altText": "Lightweight Jacket",
"width": 1000,
"height": 1000
},
{
"url": "/assets/lightweight-jacket-1.png",
"altText": "Lightweight Jacket",
"width": 1000,
"height": 1000
},
{
"url": "/assets/lightweight-jacket-2.png",
"altText": "Lightweight Jacket",
"width": 1000,
"height": 1000
}
],
"variants": [
{
"id": "Z2lkOid8vc2hvcGlmeS9Qcm9kdWN0LzU0NDczMjUwMjQ0MjAss=",
"options": [
{
"__typename": "MultipleChoiceOption",
"id": "asd",
"displayName": "Size",
"values": [
{
"label": "XL"
}
]
}
]
}
],
"options": [
{
"id": "option-color",
"displayName": "Color",
"values": [
{
"label": "color",
"hexColors": ["#222"]
}
]
},
{
"id": "option-size",
"displayName": "Size",
"values": [
{
"label": "S"
},
{
"label": "M"
},
{
"label": "L"
}
]
}
]
},
{
"id": "Z2lkOis8vc2hvcGlmsddeS9Qcm9kdWN0LzU0NDczMjUwMjQ0MjA=",
"name": "Shirt",
"vendor": "Next.js",
"path": "/shirt",
"slug": "shirt",
"price": { "value": 25, "currencyCode": "USD" },
"descriptionHtml": "<p><span>Show off your love for Next.js and Vercel with this unique,&nbsp;</span><strong>limited edition</strong><span>&nbsp;t-shirt. This design is part of a limited run, numbered drop at the June 2021 Next.js Conf. It features a unique, handcrafted triangle design. Get it while supplies last only 200 of these shirts will be made!&nbsp;</span><strong>All proceeds will be donated to charity.</strong></p>",
"images": [
{
"url": "/assets/t-shirt-0.png",
"altText": "Shirt",
"width": 1000,
"height": 1000
},
{
"url": "/assets/t-shirt-1.png",
"altText": "Shirt",
"width": 1000,
"height": 1000
},
{
"url": "/assets/t-shirt-2.png",
"altText": "Shirt",
"width": 1000,
"height": 1000
},
{
"url": "/assets/t-shirt-3.png",
"altText": "Shirt",
"width": 1000,
"height": 1000
},
{
"url": "/assets/t-shirt-4.png",
"altText": "Shirt",
"width": 1000,
"height": 1000
}
],
"variants": [
{
"id": "Z2lkOi8vc2hvcGlmeS9Qcms9kdWN0LzU0NDczMjUwMjQ0MjAss=",
"options": [
{
"__typename": "MultipleChoiceOption",
"id": "asd",
"displayName": "Size",
"values": [
{
"label": "XL"
}
]
}
]
}
],
"options": [
{
"id": "option-color",
"displayName": "Color",
"values": [
{
"label": "color",
"hexColors": ["#222"]
}
]
},
{
"id": "option-size",
"displayName": "Size",
"values": [
{
"label": "S"
},
{
"label": "M"
},
{
"label": "L"
}
]
}
]
}
]
}

View File

@@ -0,0 +1,11 @@
import { Fetcher } from '@commerce/utils/types'
export const fetcher: Fetcher = async () => {
console.log('FETCHER')
const res = await fetch('./data.json')
if (res.ok) {
const { data } = await res.json()
return data
}
throw res
}

32
framework/local/index.tsx Normal file
View File

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

View File

@@ -0,0 +1,8 @@
const commerce = require('./commerce.config.json')
module.exports = {
commerce,
images: {
domains: ['localhost'],
},
}

View File

@@ -0,0 +1,2 @@
export { default as usePrice } from './use-price'
export { default as useSearch } from './use-search'

View File

@@ -0,0 +1,2 @@
export * from '@commerce/product/use-price'
export { default } from '@commerce/product/use-price'

View File

@@ -0,0 +1,17 @@
import { SWRHook } from '@commerce/utils/types'
import useSearch, { UseSearch } from '@commerce/product/use-search'
export default useSearch as UseSearch<typeof handler>
export const handler: SWRHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input, options, fetch }) {},
useHook: () => () => {
return {
data: {
products: [],
},
}
},
}

View File

@@ -0,0 +1,21 @@
import { fetcher } from './fetcher'
import { handler as useCart } from './cart/use-cart'
import { handler as useAddItem } from './cart/use-add-item'
import { handler as useUpdateItem } from './cart/use-update-item'
import { handler as useRemoveItem } from './cart/use-remove-item'
import { handler as useCustomer } from './customer/use-customer'
import { handler as useSearch } from './product/use-search'
import { handler as useLogin } from './auth/use-login'
import { handler as useLogout } from './auth/use-logout'
import { handler as useSignup } from './auth/use-signup'
export type Provider = typeof localProvider
export const localProvider = {
locale: 'en-us',
cartCookie: 'session',
fetcher: fetcher,
cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
customer: { useCustomer },
products: { useSearch },
auth: { useLogin, useLogout, useSignup },
}

View File

@@ -0,0 +1,13 @@
import { useCallback } from 'react'
export function emptyHook() {
const useEmptyHook = async (options = {}) => {
return useCallback(async function () {
return Promise.resolve()
}, [])
}
return useEmptyHook
}
export default emptyHook

View File

@@ -0,0 +1,17 @@
import { useCallback } from 'react'
type Options = {
includeProducts?: boolean
}
export function emptyHook(options?: Options) {
const useEmptyHook = async ({ id }: { id: string | number }) => {
return useCallback(async function () {
return Promise.resolve()
}, [])
}
return useEmptyHook
}
export default emptyHook

View File

@@ -0,0 +1,43 @@
import { HookFetcher } from '@commerce/utils/types'
import type { Product } from '@commerce/types/product'
const defaultOpts = {}
export type Wishlist = {
items: [
{
product_id: number
variant_id: number
id: number
product: Product
}
]
}
export interface UseWishlistOptions {
includeProducts?: boolean
}
export interface UseWishlistInput extends UseWishlistOptions {
customerId?: number
}
export const fetcher: HookFetcher<Wishlist | null, UseWishlistInput> = () => {
return null
}
export function extendHook(
customFetcher: typeof fetcher,
// swrOptions?: SwrOptions<Wishlist | null, UseWishlistInput>
swrOptions?: any
) {
const useWishlist = ({ includeProducts }: UseWishlistOptions = {}) => {
return { data: null }
}
useWishlist.extend = extendHook
return useWishlist
}
export default extendHook(fetcher)

View File

@@ -4,7 +4,7 @@ import fetch from './fetch'
import { API_URL } from '../../const'
import { getError } from '../../utils/handle-fetch-response'
import { getCommerceApi } from '..'
import { getToken } from '@framework/utils'
import { getToken } from '../../utils/index'
const fetchGraphqlApi: GraphQLFetcher = async (query: string, { variables } = {}, fetchOptions) => {
const config = getCommerceApi().getConfig()

View File

@@ -35,9 +35,9 @@ export default function getSiteInfoOperation({
} = {}): Promise<T['data']> {
const cfg = commerce.getConfig(config)
const categories = await getCategories(cfg)
const brands = await getBrands(cfg)
/*
const categoriesPromise = getCategories(cfg)
const brandsPromise = getBrands(cfg)
/*
const { fetch, locale } = cfg
const { data } = await fetch<GetSiteInfoQuery, GetSiteInfoQueryVariables>(
query,
@@ -53,8 +53,8 @@ export default function getSiteInfoOperation({
*/
return {
categories,
brands,
categories: await categoriesPromise,
brands: await brandsPromise,
}
}

View File

@@ -9,26 +9,37 @@ const fetchGraphqlApi: GraphQLFetcher = async (
{ variables } = {},
fetchOptions
) => {
const res = await fetch(API_URL, {
...fetchOptions,
method: 'POST',
headers: {
'X-Shopify-Storefront-Access-Token': API_TOKEN!,
...fetchOptions?.headers,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables,
}),
})
try {
const res = await fetch(API_URL, {
...fetchOptions,
method: 'POST',
headers: {
'X-Shopify-Storefront-Access-Token': API_TOKEN!,
...fetchOptions?.headers,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables,
}),
})
const { data, errors, status } = await res.json()
const { data, errors, status } = await res.json()
if (errors) {
throw getError(errors, status)
if (errors) {
throw getError(errors, status)
}
return { data, res }
} catch (err) {
throw getError(
[
{
message: `${err} \n Most likely related to an unexpected output. e.g the store might be protected with password or not available.`,
},
],
500
)
}
return { data, res }
}
export default fetchGraphqlApi

View File

@@ -7,7 +7,8 @@ import {
useCommerce as useCoreCommerce,
} from '@commerce'
import { shopifyProvider, ShopifyProvider } from './provider'
import { shopifyProvider } from './provider'
import type { ShopifyProvider } from './provider'
import { SHOPIFY_CHECKOUT_ID_COOKIE } from './const'
export { shopifyProvider }

View File

@@ -1,6 +1,6 @@
import { FetcherError } from '@commerce/utils/errors'
export function getError(errors: any[], status: number) {
export function getError(errors: any[] | null, status: number) {
errors = errors ?? [{ message: 'Failed to fetch Shopify API' }]
return new FetcherError({ errors, status })
}

View File

@@ -86,6 +86,7 @@ const normalizeProductVariants = ({ edges }: ProductVariantConnection) => {
name,
values: [value],
})
return options
}),
}

View File

@@ -1,8 +1,8 @@
import getCategories from '../../utils/get-categories'
import getVendors, { Brands } from '../../utils/get-vendors'
import { Provider, SwellConfig } from '../'
import { OperationContext } from '@commerce/api/operations'
import { Category } from '@commerce/types/site'
import type { OperationContext } from '@commerce/api/operations'
import type { Category } from '@commerce/types/site'
export type GetSiteInfoResult<
T extends { categories: any[]; brands: any[] } = {

View File

@@ -18,16 +18,18 @@ export const handler: MutationHook<LogoutHook> = {
})
return null
},
useHook: ({ fetch }) => () => {
const { mutate } = useCustomer()
useHook:
({ fetch }) =>
() => {
const { mutate } = useCustomer()
return useCallback(
async function logout() {
const data = await fetch()
await mutate(null, false)
return data
},
[fetch, mutate]
)
},
return useCallback(
async function logout() {
const data = await fetch()
await mutate(null, false)
return data
},
[fetch, mutate]
)
},
}