Update api endpoints, types & fixes

This commit is contained in:
cond0r 2021-05-27 17:08:25 +03:00
parent 9a126164b4
commit 12f0d90e91
46 changed files with 174 additions and 148 deletions

View File

@ -5,7 +5,7 @@ import Link from 'next/link'
import s from './CartItem.module.css'
import { Trash, Plus, Minus } from '@components/icons'
import { useUI } from '@components/ui/context'
import type { LineItem } from '@framework/types/cart'
import type { LineItem } from '@commerce/types/cart'
import usePrice from '@framework/product/use-price'
import useUpdateItem from '@framework/cart/use-update-item'
import useRemoveItem from '@framework/cart/use-remove-item'

View File

@ -2,7 +2,7 @@ import { FC } from 'react'
import cn from 'classnames'
import Link from 'next/link'
import { useRouter } from 'next/router'
import type { Page } from '@framework/types/page'
import type { Page } from '@commerce/types/page'
import getSlug from '@lib/get-slug'
import { Github, Vercel } from '@components/icons'
import { Logo, Container } from '@components/ui'

View File

@ -1,6 +1,6 @@
import { FC } from 'react'
import Link from 'next/link'
import type { Product } from '@framework/types/product'
import type { Product } from '@commerce/types/product'
import { Grid } from '@components/ui'
import { ProductCard } from '@components/product'
import s from './HomeAllProductsGrid.module.css'

View File

@ -11,7 +11,7 @@ import CartSidebarView from '@components/cart/CartSidebarView'
import LoginView from '@components/auth/LoginView'
import { CommerceProvider } from '@framework'
import type { Page } from '@framework/types/page'
import type { Page } from '@commerce/types/page'
const Loading = () => (
<div className="w-80 h-80 flex items-center text-center justify-center p-3">

View File

@ -1,7 +1,7 @@
import { FC } from 'react'
import Link from 'next/link'
import cn from 'classnames'
import type { LineItem } from '@framework/types/cart'
import type { LineItem } from '@commerce/types/cart'
import useCart from '@framework/cart/use-cart'
import useCustomer from '@framework/customer/use-customer'
import { Avatar } from '@components/common'

View File

@ -1,7 +1,7 @@
import { FC } from 'react'
import cn from 'classnames'
import Link from 'next/link'
import type { Product } from '@framework/types/product'
import type { Product } from '@commerce/types/product'
import s from './ProductCard.module.css'
import Image, { ImageProps } from 'next/image'
import WishlistButton from '@components/wishlist/WishlistButton'

View File

@ -5,7 +5,7 @@ import { FC, useEffect, useState } from 'react'
import s from './ProductView.module.css'
import { Swatch, ProductSlider } from '@components/product'
import { Button, Container, Text, useUI } from '@components/ui'
import type { Product } from '@framework/types/product'
import type { Product } from '@commerce/types/product'
import usePrice from '@framework/product/use-price'
import { useAddItem } from '@framework/cart'
import { getVariant, SelectedOptions } from '../helpers'
@ -18,6 +18,8 @@ interface Props {
}
const ProductView: FC<Props> = ({ product }) => {
// TODO: fix this missing argument issue
/* @ts-ignore */
const addItem = useAddItem()
const { price } = usePrice({
amount: product.price.value,

View File

@ -1,8 +1,10 @@
.root {
composes: root from 'components/ui/Button/Button.module.css';
@apply h-12 w-12 bg-primary text-primary rounded-full mr-3 inline-flex
@apply h-12 py-0 px-3 bg-primary text-primary rounded-full mr-3 inline-flex
items-center justify-center cursor-pointer transition duration-150 ease-in-out
p-0 shadow-none border-gray-200 border box-border;
shadow-none border-gray-200 border box-border;
min-width: 3em;
& > span {
@apply absolute;

View File

@ -28,7 +28,7 @@ const Swatch: FC<Omit<ButtonProps, 'variant'> & Props> = ({
s.root,
{
[s.active]: active,
[s.size]: variant === 'size',
[s.size]: variant !== 'color',
[s.color]: color,
[s.dark]: color ? isDark(color) : false,
},
@ -40,6 +40,7 @@ const Swatch: FC<Omit<ButtonProps, 'variant'> & Props> = ({
className={rootClassName}
style={color ? { backgroundColor: color } : {}}
aria-label="Variant Swatch"
{...(variant === 'color' && { title: label })}
{...props}
>
{variant === 'color' && active && (

View File

@ -1,4 +1,4 @@
import type { Product } from '@framework/types/product'
import type { Product } from '@commerce/types/product'
export type SelectedOptions = Record<string, string | null>
export function getVariant(product: Product, opts: SelectedOptions) {

View File

@ -6,7 +6,7 @@ import useAddItem from '@framework/wishlist/use-add-item'
import useCustomer from '@framework/customer/use-customer'
import useWishlist from '@framework/wishlist/use-wishlist'
import useRemoveItem from '@framework/wishlist/use-remove-item'
import type { Product, ProductVariant } from '@framework/types/product'
import type { Product, ProductVariant } from '@commerce/types/product'
type Props = {
productId: Product['id']

View File

@ -7,7 +7,7 @@ import { Trash } from '@components/icons'
import { Button, Text } from '@components/ui'
import { useUI } from '@components/ui/context'
import type { Product } from '@framework/types/product'
import type { Product } from '@commerce/types/product'
import usePrice from '@framework/product/use-price'
import useAddItem from '@framework/cart/use-add-item'
import useRemoveItem from '@framework/wishlist/use-remove-item'
@ -26,6 +26,9 @@ const WishlistCard: FC<Props> = ({ product }) => {
const removeItem = useRemoveItem({ wishlist: { includeProducts: true } })
const [loading, setLoading] = useState(false)
const [removing, setRemoving] = useState(false)
// TODO: fix this missing argument issue
/* @ts-ignore */
const addItem = useAddItem()
const { openSidebar } = useUI()

View File

@ -5,6 +5,7 @@ import type {
import type { GetPageOperation, Page } from '../../types/page'
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import type { BigcommerceConfig, Provider } from '..'
import { normalizePage } from '../../lib/normalize'
export default function getPageOperation({
commerce,
@ -44,7 +45,7 @@ export default function getPageOperation({
const page = firstPage as RecursiveRequired<typeof firstPage>
if (preview || page?.is_visible) {
return { page }
return { page: normalizePage(page as any) }
}
return {}
}

View File

@ -2,7 +2,7 @@ import { useCallback } from 'react'
import type { MutationHook } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors'
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
import type { AddItemHook } from '../types/cart'
import type { AddItemHook } from '@commerce/types/cart'
import useCart from './use-cart'
export default useAddItem as UseAddItem<typeof handler>

View File

@ -1,7 +1,7 @@
import { useMemo } from 'react'
import { SWRHook } from '@commerce/utils/types'
import useCart, { UseCart } from '@commerce/cart/use-cart'
import type { GetCartHook } from '../types/cart'
import type { GetCartHook } from '@commerce/types/cart'
export default useCart as UseCart<typeof handler>

View File

@ -5,11 +5,11 @@ import type {
} from '@commerce/utils/types'
import { ValidationError } from '@commerce/utils/errors'
import useRemoveItem, { UseRemoveItem } from '@commerce/cart/use-remove-item'
import type { Cart, LineItem, RemoveItemHook } from '../types/cart'
import type { Cart, LineItem, RemoveItemHook } from '@commerce/types/cart'
import useCart from './use-cart'
export type RemoveItemFn<T = any> = T extends LineItem
? (input?: RemoveItemActionInput<T>) => Promise<Cart | null>
? (input?: RemoveItemActionInput<T>) => Promise<Cart | null | undefined>
: (input: RemoveItemActionInput<T>) => Promise<Cart | null>
export type RemoveItemActionInput<T = any> = T extends LineItem

View File

@ -6,7 +6,7 @@ import type {
} from '@commerce/utils/types'
import { ValidationError } from '@commerce/utils/errors'
import useUpdateItem, { UseUpdateItem } from '@commerce/cart/use-update-item'
import type { LineItem, UpdateItemHook } from '../types/cart'
import type { LineItem, UpdateItemHook } from '@commerce/types/cart'
import { handler as removeItemHandler } from './use-remove-item'
import useCart from './use-cart'

View File

@ -1,6 +1,8 @@
import type { Product } from '../types/product'
import type { Cart, BigcommerceCart, LineItem } from '../types/cart'
import type { Page } from '../types/page'
import update from './immutability'
import { definitions } from '../api/definitions/store-content'
function normalizeProductOption(productOption: any) {
const {
@ -69,6 +71,16 @@ export function normalizeProduct(productNode: any): Product {
})
}
export function normalizePage(page: definitions['page_Full']): Page {
return {
id: String(page.id),
name: page.name,
is_visible: page.is_visible,
sort_order: page.sort_order,
body: page.body,
}
}
export function normalizeCart(data: BigcommerceCart): Cart {
return {
id: data.id,

View File

@ -1,9 +1,7 @@
import * as Core from '@commerce/types/page'
import { definitions } from '../api/definitions/store-content'
export * from '@commerce/types/page'
export type Page = definitions['page_Full']
export type Page = Core.Page
export type PageTypes = {
page: Page

View File

@ -8,51 +8,42 @@ import {
} from 'react'
import type {
AddItemHook,
GetCartHook,
RemoveItemHook,
UpdateItemHook,
} from '@framework/types/cart'
Customer,
Wishlist,
Cart,
Product,
Signup,
Login,
Logout,
} from '@commerce/types'
import {
AddItemHook as WishlistAddItemHook,
GetWishlistHook,
RemoveItemHook as WishlistRemoveItemHook,
} from '@framework/types/wishlist'
import { CustomerHook } from '@framework/types/customer'
import { LoginHook } from '@framework/types/login'
import { LogoutHook } from '@framework/types/logout'
import { SearchProductsHook } from '@framework/types/product'
import { SignupHook } from '@framework/types/signup'
import { Fetcher, SWRHook, MutationHook } from './utils/types'
import type { Fetcher, SWRHook, MutationHook } from './utils/types'
const Commerce = createContext<CommerceContextValue<any> | {}>({})
export type Provider = CommerceConfig & {
fetcher: Fetcher
cart?: {
useCart?: SWRHook<GetCartHook>
useAddItem?: MutationHook<AddItemHook>
useUpdateItem?: MutationHook<UpdateItemHook>
useRemoveItem?: MutationHook<RemoveItemHook>
useCart?: SWRHook<Cart.GetCartHook>
useAddItem?: MutationHook<Cart.AddItemHook>
useUpdateItem?: MutationHook<Cart.UpdateItemHook>
useRemoveItem?: MutationHook<Cart.RemoveItemHook>
}
wishlist?: {
useWishlist?: SWRHook<GetWishlistHook>
useAddItem?: MutationHook<WishlistAddItemHook>
useRemoveItem?: MutationHook<WishlistRemoveItemHook>
useWishlist?: SWRHook<Wishlist.GetWishlistHook>
useAddItem?: MutationHook<Wishlist.AddItemHook>
useRemoveItem?: MutationHook<Wishlist.RemoveItemHook>
}
customer?: {
useCustomer?: SWRHook<CustomerHook>
useCustomer?: SWRHook<Customer.CustomerHook>
}
products?: {
useSearch?: SWRHook<SearchProductsHook>
useSearch?: SWRHook<Product.SearchProductsHook>
}
auth?: {
useSignup?: MutationHook<SignupHook>
useLogin?: MutationHook<LoginHook>
useLogout?: MutationHook<LogoutHook>
useSignup?: MutationHook<Signup.SignupHook>
useLogin?: MutationHook<Login.LoginHook>
useLogout?: MutationHook<Logout.LogoutHook>
}
}

View File

@ -1,5 +1,14 @@
import type { Discount, Measurement, Image } from './common'
export type SelectedOption = {
// The option's id.
id?: string
// The product options name.
name: string
/// The product options value.
value: string
}
export type LineItem = {
id: string
variantId: string
@ -10,6 +19,7 @@ export type LineItem = {
// A human-friendly unique string automatically generated from the products name
path: string
variant: ProductVariant
options?: SelectedOption[]
}
export type ProductVariant = {
@ -86,7 +96,7 @@ export type CartItemBody = {
*/
export type CartTypes = {
cart: Cart
cart?: Cart
item: LineItem
itemBody: CartItemBody
}
@ -117,7 +127,7 @@ export type UpdateItemHook<T extends CartTypes = CartTypes> = {
data: T['cart'] | null
input: { item?: T['item']; wait?: number }
fetchInput: { itemId: string; item: T['itemBody'] }
body: { itemId: string; item: T['itemBody'] }
body: { itemId: string; item?: T['itemBody'] }
actionInput: T['itemBody'] & { id: string }
}

View File

@ -1,5 +1,18 @@
// TODO: define this type
export type Page = any
export type Page = {
// ID of the Web page.
id: string
// Page name, as displayed on the storefront.
name: string
// Relative URL on the storefront for this page.
url?: string
// HTML or variable that populates this pages `<body>` element, in default/desktop view. Required in POST if page type is `raw`.
body: string
// If true, this page appears in the storefronts navigation menu.
is_visible?: boolean
// Order in which this page should display on the storefront. (Lower integers specify earlier display.)
sort_order?: number
}
export type PageTypes = {
page: Page

View File

@ -58,7 +58,10 @@ export type ProductTypes = {
}
export type SearchProductsHook<T extends ProductTypes = ProductTypes> = {
data: T['product'][]
data: {
products: T['product'][]
found: boolean
}
body: T['searchBody']
input: T['searchBody']
fetchInput: T['searchBody']

View File

@ -0,0 +1 @@
export default function (_commerce: any) {}

View File

@ -0,0 +1 @@
export default function (_commerce: any) {}

View File

@ -0,0 +1 @@
export default function (_commerce: any) {}

View File

@ -0,0 +1 @@
export default function (_commerce: any) {}

View File

@ -0,0 +1 @@
export default function (_commerce: any) {}

View File

@ -0,0 +1 @@
export default function (_commerce: any) {}

View File

@ -0,0 +1 @@
export default function (_commerce: any) {}

View File

@ -0,0 +1 @@
export default function (_commerce: any) {}

View File

@ -2,8 +2,13 @@ import type {
OperationContext,
OperationOptions,
} from '@commerce/api/operations'
import { normalizePage } from '@framework/utils'
import type { ShopifyConfig, Provider } from '..'
import { GetPageQuery, GetPageQueryVariables, Page } from '../../schema'
import {
GetPageQuery,
GetPageQueryVariables,
Page as ShopifyPage,
} from '../../schema'
import { GetPageOperation } from '../../types/page'
import getPageQuery from '../../utils/queries/get-page-query'
@ -28,14 +33,13 @@ export default function getPageOperation({
query = getPageQuery,
variables,
config,
preview,
}: {
query?: string
variables: T['variables']
config?: Partial<ShopifyConfig>
preview?: boolean
}): Promise<T['data']> {
const { fetch, locale } = commerce.getConfig(config)
const { fetch, locale = 'en-US' } = commerce.getConfig(config)
const {
data: { node: page },
@ -53,7 +57,7 @@ export default function getPageOperation({
}
)
return page ? { page } : {}
return page ? { page: normalizePage(page as ShopifyPage, locale) } : {}
}
return getPage

View File

@ -1,26 +1,40 @@
import type { OperationContext } from '@commerce/api/operations'
import type {
OperationContext,
OperationOptions,
} from '@commerce/api/operations'
import { GetProductOperation } from '../../types/product'
import { normalizeProduct, getProductQuery } from '../../utils'
import type { ShopifyConfig, Provider } from '..'
import {
GetProductBySlugQuery,
GetProductBySlugQueryVariables,
Product as ShopifyProduct,
} from '../../schema'
import { GetProductBySlugQuery, Product as ShopifyProduct } from '../../schema'
export default function getProductOperation({
commerce,
}: OperationContext<Provider>) {
async function getProduct<T extends GetProductOperation>(opts: {
variables: T['variables']
config?: Partial<ShopifyConfig>
preview?: boolean
}): Promise<T['data']>
async function getProduct<T extends GetProductOperation>(
opts: {
variables: T['variables']
config?: Partial<ShopifyConfig>
preview?: boolean
} & OperationOptions
): Promise<T['data']>
async function getProduct<T extends GetProductOperation>({
query = getProductQuery,
config,
variables,
config: cfg,
}: {
query?: string
config?: ShopifyConfig
variables?: GetProductBySlugQueryVariables
} = {}): Promise<T['data']> {
const { fetch, locale } = commerce.getConfig(config)
variables: T['variables']
config?: Partial<ShopifyConfig>
preview?: boolean
}): Promise<T['data']> {
const { fetch, locale } = commerce.getConfig(cfg)
const {
data: { productByHandle },

View File

@ -1,29 +1,20 @@
import { useCallback } from 'react'
import type { MutationHook } from '@commerce/utils/types'
import { CommerceError, ValidationError } from '@commerce/utils/errors'
import useCustomer from '../customer/use-customer'
import createCustomerAccessTokenMutation from '../utils/mutations/customer-access-token-create'
import {
CustomerAccessTokenCreateInput,
CustomerUserError,
Mutation,
MutationCheckoutCreateArgs,
} from '../schema'
import { CommerceError } from '@commerce/utils/errors'
import useLogin, { UseLogin } from '@commerce/auth/use-login'
import { setCustomerToken, throwUserErrors } from '../utils'
import type { LoginHook } from '../types/login'
import useCustomer from '../customer/use-customer'
import createCustomerAccessTokenMutation from '../utils/mutations/customer-access-token-create'
import { setCustomerToken, throwUserErrors } from '@framework/utils'
import {
Mutation,
MutationCustomerAccessTokenCreateArgs,
} from '@framework/schema'
export default useLogin as UseLogin<typeof handler>
const getErrorMessage = ({ code, message }: CustomerUserError) => {
switch (code) {
case 'UNIDENTIFIED_CUSTOMER':
message = 'Cannot find an account that matches the provided credentials'
break
}
return message
}
export const handler: MutationHook<null, {}, CustomerAccessTokenCreateInput> = {
export const handler: MutationHook<LoginHook> = {
fetchOptions: {
query: createCustomerAccessTokenMutation,
},
@ -37,7 +28,7 @@ export const handler: MutationHook<null, {}, CustomerAccessTokenCreateInput> = {
const { customerAccessTokenCreate } = await fetch<
Mutation,
MutationCheckoutCreateArgs
MutationCustomerAccessTokenCreateArgs
>({
...options,
variables: {

View File

@ -1,13 +1,14 @@
import { useCallback } from 'react'
import type { MutationHook } from '@commerce/utils/types'
import useLogout, { UseLogout } from '@commerce/auth/use-logout'
import type { LogoutHook } from '../types/logout'
import useCustomer from '../customer/use-customer'
import customerAccessTokenDeleteMutation from '../utils/mutations/customer-access-token-delete'
import { getCustomerToken, setCustomerToken } from '../utils/customer-token'
export default useLogout as UseLogout<typeof handler>
export const handler: MutationHook<null> = {
export const handler: MutationHook<LogoutHook> = {
fetchOptions: {
query: customerAccessTokenDeleteMutation,
},

View File

@ -1,25 +1,20 @@
import { useCallback } from 'react'
import type { MutationHook } from '@commerce/utils/types'
import { CommerceError, ValidationError } from '@commerce/utils/errors'
import { CommerceError } from '@commerce/utils/errors'
import useSignup, { UseSignup } from '@commerce/auth/use-signup'
import type { SignupHook } from '../types/signup'
import useCustomer from '../customer/use-customer'
import {
CustomerCreateInput,
Mutation,
MutationCustomerCreateArgs,
} from '../schema'
import { Mutation, MutationCustomerCreateArgs } from '../schema'
import { customerCreateMutation } from '../utils/mutations'
import { handleAutomaticLogin, throwUserErrors } from '../utils'
import {
handleAutomaticLogin,
throwUserErrors,
customerCreateMutation,
} from '@framework/utils'
export default useSignup as UseSignup<typeof handler>
export const handler: MutationHook<
null,
{},
CustomerCreateInput,
CustomerCreateInput
> = {
export const handler: MutationHook<SignupHook> = {
fetchOptions: {
query: customerCreateMutation,
},

View File

@ -9,7 +9,7 @@ import type { Cart, LineItem, RemoveItemHook } from '../types/cart'
import useCart from './use-cart'
export type RemoveItemFn<T = any> = T extends LineItem
? (input?: RemoveItemActionInput<T>) => Promise<Cart | null>
? (input?: RemoveItemActionInput<T>) => Promise<Cart | null | undefined>
: (input: RemoveItemActionInput<T>) => Promise<Cart | null>
export type RemoveItemActionInput<T = any> = T extends LineItem

View File

@ -1,27 +0,0 @@
import { getConfig, ShopifyConfig } from '../api'
import getCustomerIdQuery from '../utils/queries/get-customer-id-query'
import Cookies from 'js-cookie'
import { GetCustomerIdQuery } from '../schema'
async function getCustomerId({
customerToken: customerAccesToken,
config,
}: {
customerToken: string
config?: ShopifyConfig
}): Promise<string | undefined> {
config = getConfig(config)
const {
data: { customer },
} = await config.fetch<GetCustomerIdQuery>(getCustomerIdQuery, {
variables: {
customerAccesToken:
customerAccesToken || Cookies.get(config.customerCookie),
},
})
return customer?.id
}
export default getCustomerId

View File

@ -36,4 +36,4 @@ export function CommerceProvider({ children, ...config }: ShopifyProps) {
)
}
export const useCommerce = () => useCoreCommerce()
export const useCommerce = () => useCoreCommerce<ShopifyProvider>()

View File

@ -24,4 +24,4 @@ export const shopifyProvider = {
auth: { useLogin, useLogout, useSignup },
}
export type ShopifyProvider = any
export type ShopifyProvider = typeof shopifyProvider

View File

@ -1,9 +1,7 @@
import * as Core from '@commerce/types/page'
import { definitions } from '../api/definitions/store-content'
export * from '@commerce/types/page'
export type Page = definitions['page_Full']
export type Page = Core.Page
export type PageTypes = {
page: Page

View File

@ -1,4 +1,4 @@
import { Cart } from '../types'
import type { Cart } from '../types/cart'
import { CommerceError } from '@commerce/utils/errors'
import {

View File

@ -14,6 +14,7 @@ import {
Page as ShopifyPage,
PageEdge,
} from '../schema'
import { colorMap } from '@lib/colors'
const money = ({ amount, currencyCode }: MoneyV2) => {
return {
@ -30,7 +31,7 @@ const normalizeProductOption = ({
return {
__typename: 'MultipleChoiceOption',
id,
displayName,
displayName: displayName.toLowerCase(),
values: values.map((value) => {
let output: any = {
label: value,
@ -38,7 +39,7 @@ const normalizeProductOption = ({
if (displayName.match(/colou?r/gi)) {
output = {
...output,
hexColors: [value],
hexColors: [colorMap[value] || value],
}
}
return output
@ -152,9 +153,7 @@ function normalizeLineItem({
},
path: String(variant?.product?.handle),
discounts: [],
options:
// By default Shopify adds a default variant with default names, we're removing it. https://community.shopify.com/c/Shopify-APIs-SDKs/Adding-new-product-variant-is-automatically-adding-quot-Default/td-p/358095
variant?.title == 'Default Title' ? [] : variant?.selectedOptions,
options: variant?.title == 'Default Title' ? [] : variant?.selectedOptions,
}
}

View File

@ -30,6 +30,10 @@ export const checkoutDetailsFragment = /* GraphQL */ `
id
sku
title
selectedOptions {
name
value
}
image {
originalSrc
altText

View File

@ -42,7 +42,7 @@ function hexToRgb(hex: string = '') {
return [r, g, b]
}
const colorMap: Record<string, string> = {
export const colorMap: Record<string, string> = {
aliceblue: '#F0F8FF',
antiquewhite: '#FAEBD7',
aqua: '#00FFFF',
@ -56,6 +56,8 @@ const colorMap: Record<string, string> = {
blueviolet: '#8A2BE2',
brown: '#A52A2A',
burlywood: '#DEB887',
burgandy: '#800020',
burgundy: '#800020',
cadetblue: '#5F9EA0',
chartreuse: '#7FFF00',
chocolate: '#D2691E',

View File

@ -18,7 +18,8 @@ import {
getDesignerPath,
useSearchMeta,
} from '@lib/search'
import type { Product } from '@framework/types/product'
import type { Product } from '@commerce/types/product'
// TODO(bc) Remove this. This should come from the API
import getSlug from '@lib/get-slug'