Agnostic UI (#199)

* changes

* Progress

* Normalized Products output

* Progress

* Restored Index Agnostic

* Progress

* Reordering

* Moved normalizer to BC function

* Removed Futures

* More Types

* More Types

* More Types

* Fix useCallback

* Progress, Changes types, readme and restoring functionality

* Changes

* TS Issues

* Changes

* Normalizer

* Normalizing more operations

* Normalizing more operations

* changes

* Merge Issues

* Cleanup

* change

* changes

* index.ts broke my tree shaking

* slug

* Normalized Options and Swatches

* Restored Add to cart

* Correct Variant Added to Cart

* Normalizing Cart Responses

* Changes

* changes breaking

* Adding immutable normalizer for Product

* Cart Normalized

* changes

* Progress

* More updates

* Removed some comments

* Add loading state for data hooks

* Bug fix

* Changed the way isEmpty works

* Improve navbar performance

* Added useResponse hook

* added useResponse to useWhishlist

* Added husky and lint-staged

* Ran prettier fix

* Added more cart types

* Moved types.d.ts to the commerce folder

* Minor changes

* Moved normalizer to happen after fetch

* updated useCart types

* Updated normalizer for useData

* Added new normalizer for the cart to the UI

* More corrections for useCart

* Updated cart update hooks

* Removed import

* Progress

* Switch away from global types

* Making multiple changes

* Improved types for operations

* Moved and updated cart types

* Updated the useAddItem and useRemoveItem hooks

* Minor life improvement

* Minor change

* Implement Shopify Provider

* Update README.md

* Update README.md

* normalizations & missing files

* Update index.ts

* fixes

* Update normalize.ts

* fix: cart error on first load

* shopify checkout redirect & api handler

* Update get-checkout-id.ts

* userAvatar

* Fix: color option

* Update normalize.ts

* changes

* Update next.config.js

* start customer auth & signup

* Update config.ts

* Login, Sign Up, Log Out, and checkout & customer association

* Automatic login after sign-up

* Update handle-login.ts

* MOving stuff around and adding temporal new files

* changes

* Replace use-cart with the new hook

* Removed old hook

* Improved HookHandler type

* Moved types

* Simplified useData types

* Updated Fetcher type

* Moved SwrOptions type

* Removed duplicated fetcher

* Moved provider to its own file

* Added proper type for fetch input

* Revert "Merge branch 'agnostic' of https://github.com/vercel/commerce into agnostic"

This reverts commit 23c8ed7c2d, reversing
changes made to bf50965a39.

* change readme

* Revert "Merge branch 'master' of https://github.com/vercel/commerce into agnostic"

This reverts commit bf50965a39, reversing
changes made to 0dad4ddedb.

* Revert "Revert "Merge branch 'agnostic' of https://github.com/vercel/commerce into agnostic""

This reverts commit c9a43f1bce.

* align with upstream changes

* Updated how the hook input is handled

* Add more options to the hook handler

* Final touches to the hook handler type

* Moved useWishlist to use new handler

* Move useCustomer to the new hook

* Added a default fetcher

* query all products for vendors & paths, improve search

* Update use-search.tsx

* fix cart after upstream changes

* Shopify Provider (#186)

* Start of Shopify provider

* add missing comment to documentation

* add missing env vars to documentation

* update reference to types file

* Moved useSearch to the new hook

* Removed old use-data lib

* Removed generics for result and body

* Removed normalizr

* Wishlist

* New changes and initial Features API

* Fixed some types

* Fixed more types

* fixes after upstream changes

* Fixed product types

* Fixed another product type

* Updated type

* Fixed remaining issues with types

* Added a MutationHandler

* Moved the handlers to each hook

* Moved the fetcher to its own file

* Moved handler to each hook

* Added initial version of useAddItem

* Added better mutation types, and moved some hooks

* Removed use-cart-actions

* Added initial version of useAddItem

* Updated types

* Update use-add-item.tsx

* changes

* Changes

* Reordering and changes

* Adding Features APO

* Adding wishlist api

* Implementing FeaturesAPI with Wishlist

* Removing bug with touchstart

* Adding tyni typing

* moved use-remove-item

* Removed MutationHandler type

* Moved more hooks and updated types to make them smaller

* Moved data hooks to new format

* Removed no longer required types

* Removed useResponse helper

* Updated useData type

* Moved wishlist use-add-item

* Moved wishlist use-remove-item to provider

* Moved use-login and use-logout

* Update use-signup

* Removed use-action helper

* Moved auth & cart hooks + several fixes

* Updated cart item, fixed deprecations

* Update next.config.js

* Updates to wishlist feature

* Moved the features to be environment variable only

* More changes for wishlist config

* Disable wishlist

* Removed useWishlistActions

* Updated readme

* updates

* typos

* Updated the way the provider config is set

* Removed features.ts

* Removed bootstrap.js

* Aligned with upstream changes

* Updates

* shopify: changes

* shopify: changes

* Update next.config.js

* Shopify Provider Updates (#209)

* Implement Shopify Provider

* Update README.md

* Update README.md

* normalizations & missing files

* Update index.ts

* fixes

* Update normalize.ts

* fix: cart error on first load

* shopify checkout redirect & api handler

* Update get-checkout-id.ts

* Fix: color option

* Update normalize.ts

* changes

* Update next.config.js

* start customer auth & signup

* Update config.ts

* Login, Sign Up, Log Out, and checkout & customer association

* Automatic login after sign-up

* Update handle-login.ts

* changes

* Revert "Merge branch 'agnostic' of https://github.com/vercel/commerce into agnostic"

This reverts commit 23c8ed7c2d, reversing
changes made to bf50965a39.

* change readme

* Revert "Merge branch 'master' of https://github.com/vercel/commerce into agnostic"

This reverts commit bf50965a39, reversing
changes made to 0dad4ddedb.

* Revert "Revert "Merge branch 'agnostic' of https://github.com/vercel/commerce into agnostic""

This reverts commit c9a43f1bce.

* align with upstream changes

* query all products for vendors & paths, improve search

* Update use-search.tsx

* fix cart after upstream changes

* fixes after upstream changes

* Moved handler to each hook

* Added initial version of useAddItem

* Updated types

* Update use-add-item.tsx

* Moved auth & cart hooks + several fixes

* Updated cart item, fixed deprecations

* Update next.config.js

* Aligned with upstream changes

* Updates

* Update next.config.js

* Updated the commerce config structure

* Removed @framework imports within framework providers

* Fixed types

* changes

* Adding extra config

* Adding shopify commit

* Adding env templates to the providers

* Ignore some types

* Adding link for Cart

* Adding customCheckout

* multiple changes to fix the wishlist

* Shopify Provier Updates (#212)

* changes

* Adding shopify commit

* Changed to query page by id

* Fixed page query, Changed use-search GraphQl query

* Update use-search.tsx

* remove unused util

* Changed cookie expiration

* Update tsconfig.json

Co-authored-by: okbel <curciobel@gmail.com>

* Bump and adding dependency

* Adding color checks

* Now colors work with lighter colors

* Stable commerce.config.json

* Updated main readme

* Readme changes

* Default to bigcommerce

Co-authored-by: bc <bc@bcs-MacBook-Pro.fibertel.com.ar>
Co-authored-by: Luis Alvarez <luis@vercel.com>
Co-authored-by: cond0r <pinte_catalin@yahoo.com>
Co-authored-by: Peter Mekhaeil <4616064+petermekhaeil@users.noreply.github.com>
This commit is contained in:
B
2021-03-04 07:57:25 -03:00
committed by GitHub
parent b121078151
commit 9b71bd77fc
232 changed files with 20545 additions and 1895 deletions

View File

@@ -1,43 +0,0 @@
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import { BigcommerceConfig, getConfig } from '..'
import { definitions } from '../definitions/store-content'
export type Page = definitions['page_Full']
export type GetAllPagesResult<
T extends { pages: any[] } = { pages: Page[] }
> = T
async function getAllPages(opts?: {
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetAllPagesResult>
async function getAllPages<T extends { pages: any[] }>(opts: {
url: string
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetAllPagesResult<T>>
async function getAllPages({
config,
preview,
}: {
url?: string
config?: BigcommerceConfig
preview?: boolean
} = {}): Promise<GetAllPagesResult> {
config = getConfig(config)
// RecursivePartial forces the method to check for every prop in the data, which is
// required in case there's a custom `url`
const { data } = await config.storeApiFetch<
RecursivePartial<{ data: Page[] }>
>('/v3/content/pages')
const pages = (data as RecursiveRequired<typeof data>) ?? []
return {
pages: preview ? pages : pages.filter((p) => p.is_visible),
}
}
export default getAllPages

View File

@@ -1,71 +0,0 @@
import type {
GetAllProductPathsQuery,
GetAllProductPathsQueryVariables,
} from '../../schema'
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import filterEdges from '../utils/filter-edges'
import { BigcommerceConfig, getConfig } from '..'
export const getAllProductPathsQuery = /* GraphQL */ `
query getAllProductPaths($first: Int = 100) {
site {
products(first: $first) {
edges {
node {
path
}
}
}
}
}
`
export type ProductPath = NonNullable<
NonNullable<GetAllProductPathsQuery['site']['products']['edges']>[0]
>
export type ProductPaths = ProductPath[]
export type { GetAllProductPathsQueryVariables }
export type GetAllProductPathsResult<
T extends { products: any[] } = { products: ProductPaths }
> = T
async function getAllProductPaths(opts?: {
variables?: GetAllProductPathsQueryVariables
config?: BigcommerceConfig
}): Promise<GetAllProductPathsResult>
async function getAllProductPaths<
T extends { products: any[] },
V = any
>(opts: {
query: string
variables?: V
config?: BigcommerceConfig
}): Promise<GetAllProductPathsResult<T>>
async function getAllProductPaths({
query = getAllProductPathsQuery,
variables,
config,
}: {
query?: string
variables?: GetAllProductPathsQueryVariables
config?: BigcommerceConfig
} = {}): Promise<GetAllProductPathsResult> {
config = getConfig(config)
// RecursivePartial forces the method to check for every prop in the data, which is
// required in case there's a custom `query`
const { data } = await config.fetch<
RecursivePartial<GetAllProductPathsQuery>
>(query, { variables })
const products = data.site?.products?.edges
return {
products: filterEdges(products as RecursiveRequired<typeof products>),
}
}
export default getAllProductPaths

View File

@@ -1,132 +0,0 @@
import type {
GetAllProductsQuery,
GetAllProductsQueryVariables,
} from '../../schema'
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import filterEdges from '../utils/filter-edges'
import setProductLocaleMeta from '../utils/set-product-locale-meta'
import { productConnectionFragment } from '../fragments/product'
import { BigcommerceConfig, getConfig } from '..'
export const getAllProductsQuery = /* GraphQL */ `
query getAllProducts(
$hasLocale: Boolean = false
$locale: String = "null"
$entityIds: [Int!]
$first: Int = 10
$products: Boolean = false
$featuredProducts: Boolean = false
$bestSellingProducts: Boolean = false
$newestProducts: Boolean = false
) {
site {
products(first: $first, entityIds: $entityIds) @include(if: $products) {
...productConnnection
}
featuredProducts(first: $first) @include(if: $featuredProducts) {
...productConnnection
}
bestSellingProducts(first: $first) @include(if: $bestSellingProducts) {
...productConnnection
}
newestProducts(first: $first) @include(if: $newestProducts) {
...productConnnection
}
}
}
${productConnectionFragment}
`
export type ProductEdge = NonNullable<
NonNullable<GetAllProductsQuery['site']['products']['edges']>[0]
>
export type ProductNode = ProductEdge['node']
export type GetAllProductsResult<
T extends Record<keyof GetAllProductsResult, any[]> = {
products: ProductEdge[]
}
> = T
const FIELDS = [
'products',
'featuredProducts',
'bestSellingProducts',
'newestProducts',
]
export type ProductTypes =
| 'products'
| 'featuredProducts'
| 'bestSellingProducts'
| 'newestProducts'
export type ProductVariables = { field?: ProductTypes } & Omit<
GetAllProductsQueryVariables,
ProductTypes | 'hasLocale'
>
async function getAllProducts(opts?: {
variables?: ProductVariables
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetAllProductsResult>
async function getAllProducts<
T extends Record<keyof GetAllProductsResult, any[]>,
V = any
>(opts: {
query: string
variables?: V
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetAllProductsResult<T>>
async function getAllProducts({
query = getAllProductsQuery,
variables: { field = 'products', ...vars } = {},
config,
}: {
query?: string
variables?: ProductVariables
config?: BigcommerceConfig
preview?: boolean
} = {}): Promise<GetAllProductsResult> {
config = getConfig(config)
const locale = vars.locale || config.locale
const variables: GetAllProductsQueryVariables = {
...vars,
locale,
hasLocale: !!locale,
}
if (!FIELDS.includes(field)) {
throw new Error(
`The field variable has to match one of ${FIELDS.join(', ')}`
)
}
variables[field] = true
// RecursivePartial forces the method to check for every prop in the data, which is
// required in case there's a custom `query`
const { data } = await config.fetch<RecursivePartial<GetAllProductsQuery>>(
query,
{ variables }
)
const edges = data.site?.[field]?.edges
const products = filterEdges(edges as RecursiveRequired<typeof edges>)
if (locale && config.applyLocale) {
products.forEach((product: RecursivePartial<ProductEdge>) => {
if (product.node) setProductLocaleMeta(product.node)
})
}
return { products }
}
export default getAllProducts

View File

@@ -1,34 +0,0 @@
import { GetCustomerIdQuery } from '../../schema'
import { BigcommerceConfig, getConfig } from '..'
export const getCustomerIdQuery = /* GraphQL */ `
query getCustomerId {
customer {
entityId
}
}
`
async function getCustomerId({
customerToken,
config,
}: {
customerToken: string
config?: BigcommerceConfig
}): Promise<number | undefined> {
config = getConfig(config)
const { data } = await config.fetch<GetCustomerIdQuery>(
getCustomerIdQuery,
undefined,
{
headers: {
cookie: `${config.customerCookie}=${customerToken}`,
},
}
)
return data?.customer?.entityId
}
export default getCustomerId

View File

@@ -1,87 +0,0 @@
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import { definitions } from '../definitions/wishlist'
import { BigcommerceConfig, getConfig } from '..'
import getAllProducts, { ProductEdge } from './get-all-products'
export type Wishlist = Omit<definitions['wishlist_Full'], 'items'> & {
items?: WishlistItem[]
}
export type WishlistItem = NonNullable<
definitions['wishlist_Full']['items']
>[0] & {
product?: ProductEdge['node']
}
export type GetCustomerWishlistResult<
T extends { wishlist?: any } = { wishlist?: Wishlist }
> = T
export type GetCustomerWishlistVariables = {
customerId: number
}
async function getCustomerWishlist(opts: {
variables: GetCustomerWishlistVariables
config?: BigcommerceConfig
includeProducts?: boolean
}): Promise<GetCustomerWishlistResult>
async function getCustomerWishlist<
T extends { wishlist?: any },
V = any
>(opts: {
url: string
variables: V
config?: BigcommerceConfig
includeProducts?: boolean
}): Promise<GetCustomerWishlistResult<T>>
async function getCustomerWishlist({
config,
variables,
includeProducts,
}: {
url?: string
variables: GetCustomerWishlistVariables
config?: BigcommerceConfig
includeProducts?: boolean
}): Promise<GetCustomerWishlistResult> {
config = getConfig(config)
const { data = [] } = await config.storeApiFetch<
RecursivePartial<{ data: Wishlist[] }>
>(`/v3/wishlists?customer_id=${variables.customerId}`)
const wishlist = data[0]
if (includeProducts && wishlist?.items?.length) {
const entityIds = wishlist.items
?.map((item) => item?.product_id)
.filter((id): id is number => !!id)
if (entityIds?.length) {
const graphqlData = await getAllProducts({
variables: { first: 100, entityIds },
config,
})
// Put the products in an object that we can use to get them by id
const productsById = graphqlData.products.reduce<{
[k: number]: ProductEdge
}>((prods, p) => {
prods[p.node.entityId] = p
return prods
}, {})
// Populate the wishlist items with the graphql products
wishlist.items.forEach((item) => {
const product = item && productsById[item.product_id!]
if (item && product) {
item.product = product.node
}
})
}
}
return { wishlist: wishlist as RecursiveRequired<typeof wishlist> }
}
export default getCustomerWishlist

View File

@@ -1,53 +0,0 @@
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import { BigcommerceConfig, getConfig } from '..'
import { definitions } from '../definitions/store-content'
export type Page = definitions['page_Full']
export type GetPageResult<T extends { page?: any } = { page?: Page }> = T
export type PageVariables = {
id: number
}
async function getPage(opts: {
url?: string
variables: PageVariables
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetPageResult>
async function getPage<T extends { page?: any }, V = any>(opts: {
url: string
variables: V
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetPageResult<T>>
async function getPage({
url,
variables,
config,
preview,
}: {
url?: string
variables: PageVariables
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetPageResult> {
config = getConfig(config)
// RecursivePartial forces the method to check for every prop in the data, which is
// required in case there's a custom `url`
const { data } = await config.storeApiFetch<RecursivePartial<{ data: Page[] }>>(
url || `/v3/content/pages?id=${variables.id}&include=body`
)
const firstPage = data?.[0]
const page = firstPage as RecursiveRequired<typeof firstPage>
if (preview || page?.is_visible) {
return { page }
}
return {}
}
export default getPage

View File

@@ -1,118 +0,0 @@
import type { GetProductQuery, GetProductQueryVariables } from '../../schema'
import setProductLocaleMeta from '../utils/set-product-locale-meta'
import { productInfoFragment } from '../fragments/product'
import { BigcommerceConfig, getConfig } from '..'
export const getProductQuery = /* GraphQL */ `
query getProduct(
$hasLocale: Boolean = false
$locale: String = "null"
$path: String!
) {
site {
route(path: $path) {
node {
__typename
... on Product {
...productInfo
variants {
edges {
node {
entityId
defaultImage {
urlOriginal
altText
isDefault
}
prices {
...productPrices
}
inventory {
aggregated {
availableToSell
warningLevel
}
isInStock
}
productOptions {
edges {
node {
__typename
entityId
displayName
...multipleChoiceOption
}
}
}
}
}
}
}
}
}
}
}
${productInfoFragment}
`
export type ProductNode = Extract<
GetProductQuery['site']['route']['node'],
{ __typename: 'Product' }
>
export type GetProductResult<
T extends { product?: any } = { product?: ProductNode }
> = T
export type ProductVariables = { locale?: string } & (
| { path: string; slug?: never }
| { path?: never; slug: string }
)
async function getProduct(opts: {
variables: ProductVariables
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetProductResult>
async function getProduct<T extends { product?: any }, V = any>(opts: {
query: string
variables: V
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetProductResult<T>>
async function getProduct({
query = getProductQuery,
variables: { slug, ...vars },
config,
}: {
query?: string
variables: ProductVariables
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetProductResult> {
config = getConfig(config)
const locale = vars.locale || config.locale
const variables: GetProductQueryVariables = {
...vars,
locale,
hasLocale: !!locale,
path: slug ? `/${slug}/` : vars.path!,
}
const { data } = await config.fetch<GetProductQuery>(query, { variables })
const product = data.site?.route?.node
if (product?.__typename === 'Product') {
if (locale && config.applyLocale) {
setProductLocaleMeta(product)
}
return { product }
}
return {}
}
export default getProduct

View File

@@ -1,106 +0,0 @@
import type { GetSiteInfoQuery, GetSiteInfoQueryVariables } from '../../schema'
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import filterEdges from '../utils/filter-edges'
import { BigcommerceConfig, getConfig } from '..'
import { categoryTreeItemFragment } from '../fragments/category-tree'
// Get 3 levels of categories
export const getSiteInfoQuery = /* GraphQL */ `
query getSiteInfo {
site {
categoryTree {
...categoryTreeItem
children {
...categoryTreeItem
children {
...categoryTreeItem
}
}
}
brands {
pageInfo {
startCursor
endCursor
}
edges {
cursor
node {
entityId
name
defaultImage {
urlOriginal
altText
}
pageTitle
metaDesc
metaKeywords
searchKeywords
path
}
}
}
}
}
${categoryTreeItemFragment}
`
export type CategoriesTree = NonNullable<
GetSiteInfoQuery['site']['categoryTree']
>
export type BrandEdge = NonNullable<
NonNullable<GetSiteInfoQuery['site']['brands']['edges']>[0]
>
export type Brands = BrandEdge[]
export type GetSiteInfoResult<
T extends { categories: any[]; brands: any[] } = {
categories: CategoriesTree
brands: Brands
}
> = T
async function getSiteInfo(opts?: {
variables?: GetSiteInfoQueryVariables
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetSiteInfoResult>
async function getSiteInfo<
T extends { categories: any[]; brands: any[] },
V = any
>(opts: {
query: string
variables?: V
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetSiteInfoResult<T>>
async function getSiteInfo({
query = getSiteInfoQuery,
variables,
config,
}: {
query?: string
variables?: GetSiteInfoQueryVariables
config?: BigcommerceConfig
preview?: boolean
} = {}): Promise<GetSiteInfoResult> {
config = getConfig(config)
// RecursivePartial forces the method to check for every prop in the data, which is
// required in case there's a custom `query`
const { data } = await config.fetch<RecursivePartial<GetSiteInfoQuery>>(
query,
{ variables }
)
const categories = data.site?.categoryTree
const brands = data.site?.brands?.edges
return {
categories: (categories as RecursiveRequired<typeof categories>) ?? [],
brands: filterEdges(brands as RecursiveRequired<typeof brands>),
}
}
export default getSiteInfo

View File

@@ -1,73 +0,0 @@
import type { ServerResponse } from 'http'
import type { LoginMutation, LoginMutationVariables } from '../../schema'
import type { RecursivePartial } from '../utils/types'
import concatHeader from '../utils/concat-cookie'
import { BigcommerceConfig, getConfig } from '..'
export const loginMutation = /* GraphQL */ `
mutation login($email: String!, $password: String!) {
login(email: $email, password: $password) {
result
}
}
`
export type LoginResult<T extends { result?: any } = { result?: string }> = T
export type LoginVariables = LoginMutationVariables
async function login(opts: {
variables: LoginVariables
config?: BigcommerceConfig
res: ServerResponse
}): Promise<LoginResult>
async function login<T extends { result?: any }, V = any>(opts: {
query: string
variables: V
res: ServerResponse
config?: BigcommerceConfig
}): Promise<LoginResult<T>>
async function login({
query = loginMutation,
variables,
res: response,
config,
}: {
query?: string
variables: LoginVariables
res: ServerResponse
config?: BigcommerceConfig
}): Promise<LoginResult> {
config = getConfig(config)
const { data, res } = await config.fetch<RecursivePartial<LoginMutation>>(
query,
{ variables }
)
// Bigcommerce returns a Set-Cookie header with the auth cookie
let cookie = res.headers.get('Set-Cookie')
if (cookie && typeof cookie === 'string') {
// In development, don't set a secure cookie or the browser will ignore it
if (process.env.NODE_ENV !== 'production') {
cookie = cookie.replace('; Secure', '')
// SameSite=none can't be set unless the cookie is Secure
// bc seems to sometimes send back SameSite=None rather than none so make
// this case insensitive
cookie = cookie.replace(/; SameSite=none/gi, '; SameSite=lax')
}
response.setHeader(
'Set-Cookie',
concatHeader(response.getHeader('Set-Cookie'), cookie)!
)
}
return {
result: data.login?.result,
}
}
export default login