mirror of
https://github.com/vercel/commerce.git
synced 2025-05-20 00:16:59 +00:00
Merge branch 'master' of github.com:vercel/commerce into outgrow-reaction-commerce-provider
Signed-off-by: Loan Laux <loan@outgrow.io>
This commit is contained in:
commit
1332345da4
@ -1,5 +1,5 @@
|
|||||||
# Available providers: bigcommerce, shopify
|
# Available providers: bigcommerce, shopify, swell
|
||||||
COMMERCE_PROVIDER=bigcommerce
|
COMMERCE_PROVIDER=
|
||||||
|
|
||||||
BIGCOMMERCE_STOREFRONT_API_URL=
|
BIGCOMMERCE_STOREFRONT_API_URL=
|
||||||
BIGCOMMERCE_STOREFRONT_API_TOKEN=
|
BIGCOMMERCE_STOREFRONT_API_TOKEN=
|
||||||
@ -10,3 +10,6 @@ BIGCOMMERCE_CHANNEL_ID=
|
|||||||
|
|
||||||
NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN=
|
NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN=
|
||||||
NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN=
|
NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN=
|
||||||
|
|
||||||
|
NEXT_PUBLIC_SWELL_STORE_ID=
|
||||||
|
NEXT_PUBLIC_SWELL_PUBLIC_KEY=
|
||||||
|
22
README.md
22
README.md
@ -7,8 +7,9 @@ Start right now at [nextjs.org/commerce](https://nextjs.org/commerce)
|
|||||||
|
|
||||||
Demo live at: [demo.vercel.store](https://demo.vercel.store/)
|
Demo live at: [demo.vercel.store](https://demo.vercel.store/)
|
||||||
|
|
||||||
- Shopify Demo: https://shopify.demo.vercel.store/
|
- Shopify Demo: https://shopify.vercel.store/
|
||||||
- BigCommerce Demo: https://bigcommerce.demo.vercel.store/
|
- Swell Demo: https://swell.vercel.store/
|
||||||
|
- BigCommerce Demo: https://bigcommerce.vercel.store/
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -40,6 +41,23 @@ Next.js Commerce integrates out-of-the-box with BigCommerce and Shopify. We plan
|
|||||||
|
|
||||||
Open `.env.local` and change the value of `COMMERCE_PROVIDER` to the provider you would like to use, then set the environment variables for that provider (use `.env.template` as the base).
|
Open `.env.local` and change the value of `COMMERCE_PROVIDER` to the provider you would like to use, then set the environment variables for that provider (use `.env.template` as the base).
|
||||||
|
|
||||||
|
The setup for Shopify would look like this for example:
|
||||||
|
|
||||||
|
```
|
||||||
|
COMMERCE_PROVIDER=shopify
|
||||||
|
NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN=xxxxxxx.myshopify.com
|
||||||
|
```
|
||||||
|
|
||||||
|
And change the `tsconfig.json` to resolve to the chosen provider:
|
||||||
|
|
||||||
|
```
|
||||||
|
"@framework": ["framework/shopify"],
|
||||||
|
"@framework/*": ["framework/shopify/*"]
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it!
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
Every provider defines the features that it supports under `framework/{provider}/commerce.config.json`
|
Every provider defines the features that it supports under `framework/{provider}/commerce.config.json`
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
.root {
|
.root {
|
||||||
@apply text-center p-6 bg-primary text-sm flex-row justify-center items-center font-medium fixed bottom-0 w-full z-30 transition-all duration-300 ease-out;
|
@apply text-center p-6 bg-primary text-sm flex-row justify-center items-center font-medium fixed bottom-0 w-full z-30 transition-all duration-300 ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
@screen md {
|
@screen md {
|
||||||
|
.root {
|
||||||
@apply flex text-left;
|
@apply flex text-left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,20 +44,6 @@ const Footer: FC<Props> = ({ className, pages }) => {
|
|||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="py-3 md:py-0 md:pb-4">
|
|
||||||
<Link href="/">
|
|
||||||
<a className="text-primary hover:text-accents-6 transition ease-in-out duration-150">
|
|
||||||
Careers
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li className="py-3 md:py-0 md:pb-4">
|
|
||||||
<Link href="/blog">
|
|
||||||
<a className="text-primary hover:text-accents-6 transition ease-in-out duration-150">
|
|
||||||
Blog
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
{sitePages.map((page) => (
|
{sitePages.map((page) => (
|
||||||
<li key={page.url} className="py-3 md:py-0 md:pb-4">
|
<li key={page.url} className="py-3 md:py-0 md:pb-4">
|
||||||
<Link href={page.url!}>
|
<Link href={page.url!}>
|
||||||
|
@ -16,14 +16,16 @@
|
|||||||
|
|
||||||
.dropdownMenu {
|
.dropdownMenu {
|
||||||
@apply fixed right-0 top-12 mt-2 origin-top-right outline-none bg-primary z-40 w-full h-full;
|
@apply fixed right-0 top-12 mt-2 origin-top-right outline-none bg-primary z-40 w-full h-full;
|
||||||
|
}
|
||||||
|
|
||||||
@screen lg {
|
@screen lg {
|
||||||
|
.dropdownMenu {
|
||||||
@apply absolute border border-accents-1 shadow-lg w-56 h-auto;
|
@apply absolute border border-accents-1 shadow-lg w-56 h-auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.closeButton {
|
|
||||||
@screen md {
|
@screen md {
|
||||||
|
.closeButton {
|
||||||
@apply hidden;
|
@apply hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
.dropdownMenu {
|
.dropdownMenu {
|
||||||
@apply fixed right-0 mt-2 origin-top-right outline-none bg-primary z-40 w-full h-full;
|
@apply fixed right-0 mt-2 origin-top-right outline-none bg-primary z-40 w-full h-full;
|
||||||
|
}
|
||||||
|
|
||||||
@screen lg {
|
@screen lg {
|
||||||
|
.dropdownMenu {
|
||||||
@apply absolute top-10 border border-accents-1 shadow-lg w-56 h-auto;
|
@apply absolute top-10 border border-accents-1 shadow-lg w-56 h-auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,6 @@
|
|||||||
@apply grid grid-cols-1 gap-0;
|
@apply grid grid-cols-1 gap-0;
|
||||||
min-height: var(--row-height);
|
min-height: var(--row-height);
|
||||||
|
|
||||||
@screen lg {
|
|
||||||
@apply grid-cols-3 grid-rows-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
@apply row-span-1 bg-transparent box-border overflow-hidden;
|
@apply row-span-1 bg-transparent box-border overflow-hidden;
|
||||||
height: 500px;
|
height: 500px;
|
||||||
@ -19,6 +15,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@screen lg {
|
||||||
|
.root {
|
||||||
|
@apply grid-cols-3 grid-rows-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root & > * {
|
||||||
|
@apply col-span-1;
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.default {
|
.default {
|
||||||
& > * {
|
& > * {
|
||||||
@apply bg-transparent;
|
@apply bg-transparent;
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
.root {
|
.root {
|
||||||
@apply mx-auto grid grid-cols-1 py-32 gap-4;
|
@apply mx-auto grid grid-cols-1 py-32 gap-4;
|
||||||
|
}
|
||||||
|
|
||||||
@screen md {
|
@screen md {
|
||||||
|
.root {
|
||||||
@apply grid-cols-2;
|
@apply grid-cols-2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ const Hero: FC<Props> = ({ headline, description }) => {
|
|||||||
<p className="mt-5 text-xl leading-7 text-accent-2 text-white">
|
<p className="mt-5 text-xl leading-7 text-accent-2 text-white">
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
<Link href="/blog">
|
<Link href="/">
|
||||||
<a className="text-white pt-3 font-bold hover:underline flex flex-row cursor-pointer w-max-content">
|
<a className="text-white pt-3 font-bold hover:underline flex flex-row cursor-pointer w-max-content">
|
||||||
Read it here
|
Read it here
|
||||||
<RightArrow width="20" heigh="20" className="ml-1" />
|
<RightArrow width="20" heigh="20" className="ml-1" />
|
||||||
|
@ -7,7 +7,7 @@ const fs = require('fs')
|
|||||||
const merge = require('deepmerge')
|
const merge = require('deepmerge')
|
||||||
const prettier = require('prettier')
|
const prettier = require('prettier')
|
||||||
|
|
||||||
const PROVIDERS = ['bigcommerce', 'reactioncommerce', 'shopify']
|
const PROVIDERS = ['bigcommerce', 'shopify', 'swell', 'reactioncommerce']
|
||||||
|
|
||||||
function getProviderName() {
|
function getProviderName() {
|
||||||
return (
|
return (
|
||||||
@ -16,6 +16,8 @@ function getProviderName() {
|
|||||||
? 'bigcommerce'
|
? 'bigcommerce'
|
||||||
: process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN
|
: process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN
|
||||||
? 'shopify'
|
? 'shopify'
|
||||||
|
: process.env.NEXT_PUBLIC_SWELL_STORE_ID
|
||||||
|
? 'swell'
|
||||||
: null)
|
: null)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
5
framework/swell/.env.template
Normal file
5
framework/swell/.env.template
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
SWELL_STORE_DOMAIN=
|
||||||
|
SWELL_STOREFRONT_ACCESS_TOKEN=
|
||||||
|
|
||||||
|
NEXT_PUBLIC_SWELL_STORE_ID=
|
||||||
|
NEXT_PUBLIC_SWELL_PUBLIC_KEY=
|
1
framework/swell/api/cart/index.ts
Normal file
1
framework/swell/api/cart/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function () {}
|
1
framework/swell/api/catalog/index.ts
Normal file
1
framework/swell/api/catalog/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function () {}
|
1
framework/swell/api/catalog/products.ts
Normal file
1
framework/swell/api/catalog/products.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function () {}
|
20
framework/swell/api/checkout/index.ts
Normal file
20
framework/swell/api/checkout/index.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import createApiHandler, { SwellApiHandler } from '../utils/create-api-handler'
|
||||||
|
|
||||||
|
import { SWELL_CHECKOUT_URL_COOKIE } from '../../const'
|
||||||
|
|
||||||
|
import { getConfig } from '..'
|
||||||
|
|
||||||
|
const checkoutApi: SwellApiHandler<any> = async (req, res, config) => {
|
||||||
|
config = getConfig()
|
||||||
|
|
||||||
|
const { cookies } = req
|
||||||
|
const checkoutUrl = cookies[SWELL_CHECKOUT_URL_COOKIE]
|
||||||
|
|
||||||
|
if (checkoutUrl) {
|
||||||
|
res.redirect(checkoutUrl)
|
||||||
|
} else {
|
||||||
|
res.redirect('/cart')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createApiHandler(checkoutApi, {}, {})
|
1
framework/swell/api/customer.ts
Normal file
1
framework/swell/api/customer.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function () {}
|
1
framework/swell/api/customers/index.ts
Normal file
1
framework/swell/api/customers/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function () {}
|
1
framework/swell/api/customers/login.ts
Normal file
1
framework/swell/api/customers/login.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function () {}
|
1
framework/swell/api/customers/logout.ts
Normal file
1
framework/swell/api/customers/logout.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function () {}
|
1
framework/swell/api/customers/signup.ts
Normal file
1
framework/swell/api/customers/signup.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function () {}
|
50
framework/swell/api/index.ts
Normal file
50
framework/swell/api/index.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import type { CommerceAPIConfig } from '@commerce/api'
|
||||||
|
|
||||||
|
import {
|
||||||
|
SWELL_CHECKOUT_ID_COOKIE,
|
||||||
|
SWELL_CUSTOMER_TOKEN_COOKIE,
|
||||||
|
SWELL_COOKIE_EXPIRE,
|
||||||
|
} from '../const'
|
||||||
|
|
||||||
|
import fetchApi from './utils/fetch-swell-api'
|
||||||
|
|
||||||
|
export interface SwellConfig extends CommerceAPIConfig {
|
||||||
|
fetch: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Config {
|
||||||
|
private config: SwellConfig
|
||||||
|
|
||||||
|
constructor(config: SwellConfig) {
|
||||||
|
this.config = config
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfig(userConfig: Partial<SwellConfig> = {}) {
|
||||||
|
return Object.entries(userConfig).reduce<SwellConfig>(
|
||||||
|
(cfg, [key, value]) => Object.assign(cfg, { [key]: value }),
|
||||||
|
{ ...this.config }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfig(newConfig: Partial<SwellConfig>) {
|
||||||
|
Object.assign(this.config, newConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = new Config({
|
||||||
|
locale: 'en-US',
|
||||||
|
commerceUrl: '',
|
||||||
|
apiToken: ''!,
|
||||||
|
cartCookie: SWELL_CHECKOUT_ID_COOKIE,
|
||||||
|
cartCookieMaxAge: SWELL_COOKIE_EXPIRE,
|
||||||
|
fetch: fetchApi,
|
||||||
|
customerCookie: SWELL_CUSTOMER_TOKEN_COOKIE,
|
||||||
|
})
|
||||||
|
|
||||||
|
export function getConfig(userConfig?: Partial<SwellConfig>) {
|
||||||
|
return config.getConfig(userConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setConfig(newConfig: Partial<SwellConfig>) {
|
||||||
|
return config.setConfig(newConfig)
|
||||||
|
}
|
25
framework/swell/api/operations/get-page.ts
Normal file
25
framework/swell/api/operations/get-page.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Page } from '../../schema'
|
||||||
|
import { SwellConfig, getConfig } from '..'
|
||||||
|
|
||||||
|
export type GetPageResult<T extends { page?: any } = { page?: Page }> = T
|
||||||
|
|
||||||
|
export type PageVariables = {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPage({
|
||||||
|
url,
|
||||||
|
variables,
|
||||||
|
config,
|
||||||
|
preview,
|
||||||
|
}: {
|
||||||
|
url?: string
|
||||||
|
variables: PageVariables
|
||||||
|
config?: SwellConfig
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<GetPageResult> {
|
||||||
|
config = getConfig(config)
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getPage
|
58
framework/swell/api/utils/create-api-handler.ts
Normal file
58
framework/swell/api/utils/create-api-handler.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { SwellConfig, getConfig } from '..'
|
||||||
|
|
||||||
|
export type SwellApiHandler<
|
||||||
|
T = any,
|
||||||
|
H extends SwellHandlers = {},
|
||||||
|
Options extends {} = {}
|
||||||
|
> = (
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<SwellApiResponse<T>>,
|
||||||
|
config: SwellConfig,
|
||||||
|
handlers: H,
|
||||||
|
// Custom configs that may be used by a particular handler
|
||||||
|
options: Options
|
||||||
|
) => void | Promise<void>
|
||||||
|
|
||||||
|
export type SwellHandler<T = any, Body = null> = (options: {
|
||||||
|
req: NextApiRequest
|
||||||
|
res: NextApiResponse<SwellApiResponse<T>>
|
||||||
|
config: SwellConfig
|
||||||
|
body: Body
|
||||||
|
}) => void | Promise<void>
|
||||||
|
|
||||||
|
export type SwellHandlers<T = any> = {
|
||||||
|
[k: string]: SwellHandler<T, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SwellApiResponse<T> = {
|
||||||
|
data: T | null
|
||||||
|
errors?: { message: string; code?: string }[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function createApiHandler<
|
||||||
|
T = any,
|
||||||
|
H extends SwellHandlers = {},
|
||||||
|
Options extends {} = {}
|
||||||
|
>(
|
||||||
|
handler: SwellApiHandler<T, H, Options>,
|
||||||
|
handlers: H,
|
||||||
|
defaultOptions: Options
|
||||||
|
) {
|
||||||
|
return function getApiHandler({
|
||||||
|
config,
|
||||||
|
operations,
|
||||||
|
options,
|
||||||
|
}: {
|
||||||
|
config?: SwellConfig
|
||||||
|
operations?: Partial<H>
|
||||||
|
options?: Options extends {} ? Partial<Options> : never
|
||||||
|
} = {}): NextApiHandler {
|
||||||
|
const ops = { ...operations, ...handlers }
|
||||||
|
const opts = { ...defaultOptions, ...options }
|
||||||
|
|
||||||
|
return function apiHandler(req, res) {
|
||||||
|
return handler(req, res, getConfig(config), ops, opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
framework/swell/api/utils/fetch-swell-api.ts
Normal file
7
framework/swell/api/utils/fetch-swell-api.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { swellConfig } from '../..'
|
||||||
|
|
||||||
|
const fetchApi = async (query: string, method: string, variables: [] = []) => {
|
||||||
|
const { swell } = swellConfig
|
||||||
|
return swell[query][method](...variables)
|
||||||
|
}
|
||||||
|
export default fetchApi
|
2
framework/swell/api/utils/fetch.ts
Normal file
2
framework/swell/api/utils/fetch.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import zeitFetch from '@vercel/fetch'
|
||||||
|
export default zeitFetch()
|
28
framework/swell/api/utils/is-allowed-method.ts
Normal file
28
framework/swell/api/utils/is-allowed-method.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
|
export default function isAllowedMethod(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse,
|
||||||
|
allowedMethods: string[]
|
||||||
|
) {
|
||||||
|
const methods = allowedMethods.includes('OPTIONS')
|
||||||
|
? allowedMethods
|
||||||
|
: [...allowedMethods, 'OPTIONS']
|
||||||
|
|
||||||
|
if (!req.method || !methods.includes(req.method)) {
|
||||||
|
res.status(405)
|
||||||
|
res.setHeader('Allow', methods.join(', '))
|
||||||
|
res.end()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method === 'OPTIONS') {
|
||||||
|
res.status(200)
|
||||||
|
res.setHeader('Allow', methods.join(', '))
|
||||||
|
res.setHeader('Content-Length', '0')
|
||||||
|
res.end()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
2
framework/swell/api/wishlist/index.tsx
Normal file
2
framework/swell/api/wishlist/index.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export type WishlistItem = { product: any; id: number }
|
||||||
|
export default function () {}
|
76
framework/swell/auth/use-login.tsx
Normal file
76
framework/swell/auth/use-login.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
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 {
|
||||||
|
CustomerAccessTokenCreateInput,
|
||||||
|
CustomerUserError,
|
||||||
|
Mutation,
|
||||||
|
MutationCheckoutCreateArgs,
|
||||||
|
} from '../schema'
|
||||||
|
import useLogin, { UseLogin } from '@commerce/auth/use-login'
|
||||||
|
import { setCustomerToken } from '../utils'
|
||||||
|
|
||||||
|
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> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: 'account',
|
||||||
|
method: 'login',
|
||||||
|
},
|
||||||
|
async fetcher({ input: { email, password }, options, fetch }) {
|
||||||
|
if (!(email && password)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message:
|
||||||
|
'A first name, last name, email and password are required to login',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const { customerAccessTokenCreate } = await fetch<
|
||||||
|
Mutation,
|
||||||
|
MutationCheckoutCreateArgs
|
||||||
|
>({
|
||||||
|
...options,
|
||||||
|
variables: [email, password],
|
||||||
|
})
|
||||||
|
|
||||||
|
const errors = customerAccessTokenCreate?.customerUserErrors
|
||||||
|
|
||||||
|
if (errors && errors.length) {
|
||||||
|
throw new ValidationError({
|
||||||
|
message: getErrorMessage(errors[0]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const customerAccessToken = customerAccessTokenCreate?.customerAccessToken
|
||||||
|
const accessToken = customerAccessToken?.accessToken
|
||||||
|
|
||||||
|
if (accessToken) {
|
||||||
|
setCustomerToken(accessToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ fetch }) =>
|
||||||
|
() => {
|
||||||
|
const { revalidate } = useCustomer()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function login(input) {
|
||||||
|
const data = await fetch({ input })
|
||||||
|
await revalidate()
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, revalidate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
38
framework/swell/auth/use-logout.tsx
Normal file
38
framework/swell/auth/use-logout.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
|
import useLogout, { UseLogout } from '@commerce/auth/use-logout'
|
||||||
|
import useCustomer from '../customer/use-customer'
|
||||||
|
import { getCustomerToken, setCustomerToken } from '../utils/customer-token'
|
||||||
|
|
||||||
|
export default useLogout as UseLogout<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<null> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: 'account',
|
||||||
|
method: 'logout',
|
||||||
|
},
|
||||||
|
async fetcher({ options, fetch }) {
|
||||||
|
await fetch({
|
||||||
|
...options,
|
||||||
|
variables: {
|
||||||
|
customerAccessToken: getCustomerToken(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
setCustomerToken(null)
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ fetch }) =>
|
||||||
|
() => {
|
||||||
|
const { mutate } = useCustomer()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function logout() {
|
||||||
|
const data = await fetch()
|
||||||
|
await mutate(null, false)
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, mutate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
67
framework/swell/auth/use-signup.tsx
Normal file
67
framework/swell/auth/use-signup.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
|
import useSignup, { UseSignup } from '@commerce/auth/use-signup'
|
||||||
|
import useCustomer from '../customer/use-customer'
|
||||||
|
import { CustomerCreateInput } from '../schema'
|
||||||
|
|
||||||
|
import handleLogin from '../utils/handle-login'
|
||||||
|
|
||||||
|
export default useSignup as UseSignup<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<
|
||||||
|
null,
|
||||||
|
{},
|
||||||
|
CustomerCreateInput,
|
||||||
|
CustomerCreateInput
|
||||||
|
> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: 'account',
|
||||||
|
method: 'create',
|
||||||
|
},
|
||||||
|
async fetcher({
|
||||||
|
input: { firstName, lastName, email, password },
|
||||||
|
options,
|
||||||
|
fetch,
|
||||||
|
}) {
|
||||||
|
if (!(firstName && lastName && email && password)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message:
|
||||||
|
'A first name, last name, email and password are required to signup',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const data = await fetch({
|
||||||
|
...options,
|
||||||
|
variables: {
|
||||||
|
first_name: firstName,
|
||||||
|
last_name: lastName,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const loginData = await fetch({
|
||||||
|
query: 'account',
|
||||||
|
method: 'login',
|
||||||
|
variables: [email, password],
|
||||||
|
})
|
||||||
|
handleLogin(loginData)
|
||||||
|
} catch (error) {}
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ fetch }) =>
|
||||||
|
() => {
|
||||||
|
const { revalidate } = useCustomer()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function signup(input) {
|
||||||
|
const data = await fetch({ input })
|
||||||
|
await revalidate()
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, revalidate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
3
framework/swell/cart/index.ts
Normal file
3
framework/swell/cart/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { default as useCart } from './use-cart'
|
||||||
|
export { default as useAddItem } from './use-add-item'
|
||||||
|
export { default as useRemoveItem } from './use-remove-item'
|
61
framework/swell/cart/use-add-item.tsx
Normal file
61
framework/swell/cart/use-add-item.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
|
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
|
||||||
|
import useCart from './use-cart'
|
||||||
|
import { Cart, CartItemBody } from '../types'
|
||||||
|
import { checkoutToCart } from './utils'
|
||||||
|
import { getCheckoutId } from '../utils'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
export default useAddItem as UseAddItem<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<Cart, {}, CartItemBody> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: 'cart',
|
||||||
|
method: 'addItem',
|
||||||
|
},
|
||||||
|
async fetcher({ input: item, options, fetch }) {
|
||||||
|
if (
|
||||||
|
item.quantity &&
|
||||||
|
(!Number.isInteger(item.quantity) || item.quantity! < 1)
|
||||||
|
) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'The item quantity has to be a valid integer greater than 0',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const variables: {
|
||||||
|
product_id: string
|
||||||
|
variant_id?: string
|
||||||
|
checkoutId?: string
|
||||||
|
quantity?: number
|
||||||
|
} = {
|
||||||
|
checkoutId: getCheckoutId(),
|
||||||
|
product_id: item.productId,
|
||||||
|
quantity: item.quantity,
|
||||||
|
}
|
||||||
|
if (item.productId !== item.variantId) {
|
||||||
|
variables.variant_id = item.variantId
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch({
|
||||||
|
...options,
|
||||||
|
variables,
|
||||||
|
})
|
||||||
|
|
||||||
|
return checkoutToCart(response) as any
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ fetch }) =>
|
||||||
|
() => {
|
||||||
|
const { mutate } = useCart()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function addItem(input) {
|
||||||
|
const data = await fetch({ input })
|
||||||
|
await mutate(data, false)
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, mutate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
39
framework/swell/cart/use-cart.tsx
Normal file
39
framework/swell/cart/use-cart.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import useCart, { UseCart } from '@commerce/cart/use-cart'
|
||||||
|
import { Cart } from '@commerce/types'
|
||||||
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import { normalizeCart } from '../utils/normalize'
|
||||||
|
import { checkoutCreate, checkoutToCart } from './utils'
|
||||||
|
|
||||||
|
export default useCart as UseCart<typeof handler>
|
||||||
|
|
||||||
|
export const handler: SWRHook<Cart | null, {}, any, { isEmpty?: boolean }> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: 'cart',
|
||||||
|
method: 'get',
|
||||||
|
},
|
||||||
|
async fetcher({ fetch }) {
|
||||||
|
const cart = await checkoutCreate(fetch)
|
||||||
|
|
||||||
|
return cart ? normalizeCart(cart) : null
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ useData }) =>
|
||||||
|
(input) => {
|
||||||
|
const response = useData({
|
||||||
|
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
|
||||||
|
})
|
||||||
|
return useMemo(
|
||||||
|
() =>
|
||||||
|
Object.create(response, {
|
||||||
|
isEmpty: {
|
||||||
|
get() {
|
||||||
|
return (response.data?.lineItems.length ?? 0) <= 0
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[response]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
67
framework/swell/cart/use-remove-item.tsx
Normal file
67
framework/swell/cart/use-remove-item.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
import type {
|
||||||
|
MutationHookContext,
|
||||||
|
HookFetcherContext,
|
||||||
|
} from '@commerce/utils/types'
|
||||||
|
|
||||||
|
import { ValidationError } from '@commerce/utils/errors'
|
||||||
|
|
||||||
|
import useRemoveItem, {
|
||||||
|
RemoveItemInput as RemoveItemInputBase,
|
||||||
|
UseRemoveItem,
|
||||||
|
} from '@commerce/cart/use-remove-item'
|
||||||
|
|
||||||
|
import useCart from './use-cart'
|
||||||
|
import { checkoutToCart } from './utils'
|
||||||
|
import { Cart, LineItem } from '../types'
|
||||||
|
import { RemoveCartItemBody } from '@commerce/types'
|
||||||
|
|
||||||
|
export type RemoveItemFn<T = any> = T extends LineItem
|
||||||
|
? (input?: RemoveItemInput<T>) => Promise<Cart | null>
|
||||||
|
: (input: RemoveItemInput<T>) => Promise<Cart | null>
|
||||||
|
|
||||||
|
export type RemoveItemInput<T = any> = T extends LineItem
|
||||||
|
? Partial<RemoveItemInputBase>
|
||||||
|
: RemoveItemInputBase
|
||||||
|
|
||||||
|
export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||||
|
|
||||||
|
export const handler = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: 'cart',
|
||||||
|
method: 'removeItem',
|
||||||
|
},
|
||||||
|
async fetcher({
|
||||||
|
input: { itemId },
|
||||||
|
options,
|
||||||
|
fetch,
|
||||||
|
}: HookFetcherContext<RemoveCartItemBody>) {
|
||||||
|
const response = await fetch({
|
||||||
|
...options,
|
||||||
|
variables: [itemId],
|
||||||
|
})
|
||||||
|
return checkoutToCart(response)
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ fetch }: MutationHookContext<Cart | null, RemoveCartItemBody>) =>
|
||||||
|
<T extends LineItem | undefined = undefined>(ctx: { item?: T } = {}) => {
|
||||||
|
const { item } = ctx
|
||||||
|
const { mutate } = useCart()
|
||||||
|
const removeItem: RemoveItemFn<LineItem> = async (input) => {
|
||||||
|
const itemId = input?.id ?? item?.id
|
||||||
|
|
||||||
|
if (!itemId) {
|
||||||
|
throw new ValidationError({
|
||||||
|
message: 'Invalid input used for this operation',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await fetch({ input: { itemId } })
|
||||||
|
await mutate(data, false)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
return useCallback(removeItem as RemoveItemFn<T>, [fetch, mutate])
|
||||||
|
},
|
||||||
|
}
|
93
framework/swell/cart/use-update-item.tsx
Normal file
93
framework/swell/cart/use-update-item.tsx
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import debounce from 'lodash.debounce'
|
||||||
|
import type {
|
||||||
|
HookFetcherContext,
|
||||||
|
MutationHookContext,
|
||||||
|
} from '@commerce/utils/types'
|
||||||
|
import { ValidationError } from '@commerce/utils/errors'
|
||||||
|
import useUpdateItem, {
|
||||||
|
UpdateItemInput as UpdateItemInputBase,
|
||||||
|
UseUpdateItem,
|
||||||
|
} from '@commerce/cart/use-update-item'
|
||||||
|
|
||||||
|
import useCart from './use-cart'
|
||||||
|
import { handler as removeItemHandler } from './use-remove-item'
|
||||||
|
import type { Cart, LineItem, UpdateCartItemBody } from '../types'
|
||||||
|
import { checkoutToCart } from './utils'
|
||||||
|
|
||||||
|
export type UpdateItemInput<T = any> = T extends LineItem
|
||||||
|
? Partial<UpdateItemInputBase<LineItem>>
|
||||||
|
: UpdateItemInputBase<LineItem>
|
||||||
|
|
||||||
|
export default useUpdateItem as UseUpdateItem<typeof handler>
|
||||||
|
|
||||||
|
export const handler = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: 'cart',
|
||||||
|
method: 'updateItem',
|
||||||
|
},
|
||||||
|
async fetcher({
|
||||||
|
input: { itemId, item },
|
||||||
|
options,
|
||||||
|
fetch,
|
||||||
|
}: HookFetcherContext<UpdateCartItemBody>) {
|
||||||
|
if (Number.isInteger(item.quantity)) {
|
||||||
|
// Also allow the update hook to remove an item if the quantity is lower than 1
|
||||||
|
if (item.quantity! < 1) {
|
||||||
|
return removeItemHandler.fetcher({
|
||||||
|
options: removeItemHandler.fetchOptions,
|
||||||
|
input: { itemId },
|
||||||
|
fetch,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (item.quantity) {
|
||||||
|
throw new ValidationError({
|
||||||
|
message: 'The item quantity has to be a valid integer',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const response = await fetch({
|
||||||
|
...options,
|
||||||
|
variables: [itemId, { quantity: item.quantity }],
|
||||||
|
})
|
||||||
|
|
||||||
|
return checkoutToCart(response)
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ fetch }: MutationHookContext<Cart | null, UpdateCartItemBody>) =>
|
||||||
|
<T extends LineItem | undefined = undefined>(
|
||||||
|
ctx: {
|
||||||
|
item?: T
|
||||||
|
wait?: number
|
||||||
|
} = {}
|
||||||
|
) => {
|
||||||
|
const { item } = ctx
|
||||||
|
const { mutate, data: cartData } = useCart() as any
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
debounce(async (input: UpdateItemInput<T>) => {
|
||||||
|
const itemId = cartData.lineItems[0].id
|
||||||
|
const productId = cartData.lineItems[0].productId
|
||||||
|
const variantId = cartData.lineItems[0].variant.id
|
||||||
|
if (!itemId || !productId) {
|
||||||
|
throw new ValidationError({
|
||||||
|
message: 'Invalid input used for this operation',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await fetch({
|
||||||
|
input: {
|
||||||
|
item: {
|
||||||
|
productId,
|
||||||
|
variantId,
|
||||||
|
quantity: input.quantity,
|
||||||
|
},
|
||||||
|
itemId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await mutate(data, false)
|
||||||
|
return data
|
||||||
|
}, ctx.wait ?? 500),
|
||||||
|
[fetch, mutate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
28
framework/swell/cart/utils/checkout-create.ts
Normal file
28
framework/swell/cart/utils/checkout-create.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { SWELL_CHECKOUT_URL_COOKIE } from '../../const'
|
||||||
|
|
||||||
|
import Cookies from 'js-cookie'
|
||||||
|
|
||||||
|
export const checkoutCreate = async (fetch: any) => {
|
||||||
|
const cart = await fetch({
|
||||||
|
query: 'cart',
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!cart) {
|
||||||
|
const cart = await fetch({
|
||||||
|
query: 'cart',
|
||||||
|
method: 'setItems',
|
||||||
|
variables: [[]],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkoutUrl = cart?.checkout_url
|
||||||
|
|
||||||
|
if (checkoutUrl) {
|
||||||
|
Cookies.set(SWELL_CHECKOUT_URL_COOKIE, checkoutUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cart
|
||||||
|
}
|
||||||
|
|
||||||
|
export default checkoutCreate
|
26
framework/swell/cart/utils/checkout-to-cart.ts
Normal file
26
framework/swell/cart/utils/checkout-to-cart.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Cart } from '../../types'
|
||||||
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
|
|
||||||
|
import {
|
||||||
|
CheckoutLineItemsAddPayload,
|
||||||
|
CheckoutLineItemsRemovePayload,
|
||||||
|
CheckoutLineItemsUpdatePayload,
|
||||||
|
Maybe,
|
||||||
|
} from '../../schema'
|
||||||
|
import { normalizeCart } from '../../utils'
|
||||||
|
|
||||||
|
export type CheckoutPayload =
|
||||||
|
| CheckoutLineItemsAddPayload
|
||||||
|
| CheckoutLineItemsUpdatePayload
|
||||||
|
| CheckoutLineItemsRemovePayload
|
||||||
|
|
||||||
|
const checkoutToCart = (checkoutPayload?: Maybe<CheckoutPayload>): Cart => {
|
||||||
|
if (!checkoutPayload) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'Invalid response from Swell',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return normalizeCart(checkoutPayload as any)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default checkoutToCart
|
33
framework/swell/cart/utils/fetcher.ts
Normal file
33
framework/swell/cart/utils/fetcher.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { HookFetcherFn } from '@commerce/utils/types'
|
||||||
|
import { Cart } from '@commerce/types'
|
||||||
|
// import { checkoutCreate, checkoutToCart } from '.'
|
||||||
|
import { FetchCartInput } from '@commerce/cart/use-cart'
|
||||||
|
import { data } from 'autoprefixer'
|
||||||
|
import { normalizeCart } from '../../utils'
|
||||||
|
|
||||||
|
const fetcher: HookFetcherFn<Cart | null, FetchCartInput> = async ({
|
||||||
|
options,
|
||||||
|
// input: { cartId: checkoutId },
|
||||||
|
fetch,
|
||||||
|
}) => {
|
||||||
|
let checkout
|
||||||
|
|
||||||
|
// if (checkoutId) {
|
||||||
|
const data = await fetch({
|
||||||
|
query: 'cart',
|
||||||
|
method: 'get',
|
||||||
|
// variables: { category: categoryId },
|
||||||
|
})
|
||||||
|
// checkout = data.node
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (checkout?.completedAt || !checkoutId) {
|
||||||
|
// checkout = await checkoutCreate(fetch)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO: Fix this type
|
||||||
|
// return checkoutToCart({ checkout } as any)
|
||||||
|
return normalizeCart(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default fetcher
|
2
framework/swell/cart/utils/index.ts
Normal file
2
framework/swell/cart/utils/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { default as checkoutToCart } from './checkout-to-cart'
|
||||||
|
export { default as checkoutCreate } from './checkout-create'
|
6
framework/swell/commerce.config.json
Normal file
6
framework/swell/commerce.config.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"provider": "swell",
|
||||||
|
"features": {
|
||||||
|
"wishlist": false
|
||||||
|
}
|
||||||
|
}
|
37
framework/swell/common/get-all-pages.ts
Normal file
37
framework/swell/common/get-all-pages.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { getConfig, SwellConfig } from '../api'
|
||||||
|
|
||||||
|
type Variables = {
|
||||||
|
first?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReturnType = {
|
||||||
|
pages: Page[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Page = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
url: string
|
||||||
|
sort_order?: number
|
||||||
|
body: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAllPages = async (options?: {
|
||||||
|
variables?: Variables
|
||||||
|
config: SwellConfig
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<ReturnType> => {
|
||||||
|
let { config, variables = { first: 250 } } = options ?? {}
|
||||||
|
config = getConfig(config)
|
||||||
|
const { locale, fetch } = config
|
||||||
|
const data = await fetch('content', 'list', ['pages'])
|
||||||
|
const pages =
|
||||||
|
data?.results?.map(({ slug, ...rest }: { slug: string }) => ({
|
||||||
|
url: `/${locale}/${slug}`,
|
||||||
|
...rest,
|
||||||
|
})) ?? []
|
||||||
|
|
||||||
|
return { pages }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getAllPages
|
33
framework/swell/common/get-page.ts
Normal file
33
framework/swell/common/get-page.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { getConfig, SwellConfig } from '../api'
|
||||||
|
import { Page } from './get-all-pages'
|
||||||
|
|
||||||
|
type Variables = {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetPageResult<T extends { page?: any } = { page?: Page }> = T
|
||||||
|
|
||||||
|
const getPage = async (options: {
|
||||||
|
variables: Variables
|
||||||
|
config: SwellConfig
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<GetPageResult> => {
|
||||||
|
let { config, variables } = options ?? {}
|
||||||
|
|
||||||
|
config = getConfig(config)
|
||||||
|
const { locale } = config
|
||||||
|
const { id } = variables
|
||||||
|
const result = await config.fetch('content', 'get', ['pages', id])
|
||||||
|
const page = result
|
||||||
|
|
||||||
|
return {
|
||||||
|
page: page
|
||||||
|
? {
|
||||||
|
...page,
|
||||||
|
url: `/${locale}/${page.slug}`,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getPage
|
31
framework/swell/common/get-site-info.ts
Normal file
31
framework/swell/common/get-site-info.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import getCategories, { Category } from '../utils/get-categories'
|
||||||
|
import getVendors, { Brands } from '../utils/get-vendors'
|
||||||
|
|
||||||
|
import { getConfig, SwellConfig } from '../api'
|
||||||
|
|
||||||
|
export type GetSiteInfoResult<
|
||||||
|
T extends { categories: any[]; brands: any[] } = {
|
||||||
|
categories: Category[]
|
||||||
|
brands: Brands
|
||||||
|
}
|
||||||
|
> = T
|
||||||
|
|
||||||
|
const getSiteInfo = async (options?: {
|
||||||
|
variables?: any
|
||||||
|
config: SwellConfig
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<GetSiteInfoResult> => {
|
||||||
|
let { config } = options ?? {}
|
||||||
|
|
||||||
|
config = getConfig(config)
|
||||||
|
|
||||||
|
const categories = await getCategories(config)
|
||||||
|
const brands = await getVendors(config)
|
||||||
|
|
||||||
|
return {
|
||||||
|
categories,
|
||||||
|
brands,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getSiteInfo
|
13
framework/swell/const.ts
Normal file
13
framework/swell/const.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export const SWELL_CHECKOUT_ID_COOKIE = 'SWELL_checkoutId'
|
||||||
|
|
||||||
|
export const SWELL_CHECKOUT_URL_COOKIE = 'swell_checkoutUrl'
|
||||||
|
|
||||||
|
export const SWELL_CUSTOMER_TOKEN_COOKIE = 'swell_customerToken'
|
||||||
|
|
||||||
|
export const STORE_DOMAIN = process.env.NEXT_PUBLIC_SWELL_STORE_DOMAIN
|
||||||
|
|
||||||
|
export const SWELL_COOKIE_EXPIRE = 30
|
||||||
|
|
||||||
|
export const SWELL_STORE_ID = process.env.NEXT_PUBLIC_SWELL_STORE_ID
|
||||||
|
|
||||||
|
export const SWELL_PUBLIC_KEY = process.env.NEXT_PUBLIC_SWELL_PUBLIC_KEY
|
1
framework/swell/customer/index.ts
Normal file
1
framework/swell/customer/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as useCustomer } from './use-customer'
|
52
framework/swell/customer/use-customer.tsx
Normal file
52
framework/swell/customer/use-customer.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import useCustomer, { UseCustomer } from '@commerce/customer/use-customer'
|
||||||
|
import { Customer } from '@commerce/types'
|
||||||
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
|
import { normalizeCustomer } from '../utils/normalize'
|
||||||
|
|
||||||
|
export default useCustomer as UseCustomer<typeof handler>
|
||||||
|
|
||||||
|
export const handler: SWRHook<Customer | null> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: 'account',
|
||||||
|
method: 'get',
|
||||||
|
},
|
||||||
|
async fetcher({ options, fetch }) {
|
||||||
|
const data = await fetch<any | null>({
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
return data ? normalizeCustomer(data) : null
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ useData }) =>
|
||||||
|
(input) => {
|
||||||
|
return useData({
|
||||||
|
swrOptions: {
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
...input?.swrOptions,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// const handler = (): { data: Customer } => {
|
||||||
|
// const swell = getContext();
|
||||||
|
// const response = swell.account.get();
|
||||||
|
// const { firstName, lastName, email, company, customerGroupId, notes, phone,
|
||||||
|
// entityId, addressCount, attributeCount, storeCredit } = response;
|
||||||
|
// return {
|
||||||
|
// data: {
|
||||||
|
// firstName,
|
||||||
|
// lastName,
|
||||||
|
// email,
|
||||||
|
// company,
|
||||||
|
// customerGroupId,
|
||||||
|
// notes,
|
||||||
|
// phone,
|
||||||
|
// entityId,
|
||||||
|
// addressCount,
|
||||||
|
// attributeCount,
|
||||||
|
// storeCredit
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// export default handler;
|
28
framework/swell/fetcher.ts
Normal file
28
framework/swell/fetcher.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Fetcher } from '@commerce/utils/types'
|
||||||
|
import { handleFetchResponse } from './utils'
|
||||||
|
import { swellConfig } from './index'
|
||||||
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
|
|
||||||
|
const fetcher: Fetcher = async ({ method = 'get', variables, query }) => {
|
||||||
|
const { swell } = swellConfig
|
||||||
|
|
||||||
|
async function callSwell() {
|
||||||
|
if (Array.isArray(variables)) {
|
||||||
|
const arg1 = variables[0]
|
||||||
|
const arg2 = variables[1]
|
||||||
|
const response = await swell[query!][method](arg1, arg2)
|
||||||
|
return handleFetchResponse(response)
|
||||||
|
} else {
|
||||||
|
const response = await swell[query!][method](variables)
|
||||||
|
return handleFetchResponse(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query && query in swell) {
|
||||||
|
return await callSwell()
|
||||||
|
} else {
|
||||||
|
throw new CommerceError({ message: 'Invalid query argument!' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default fetcher
|
47
framework/swell/index.tsx
Normal file
47
framework/swell/index.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import swell from 'swell-js'
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
CommerceConfig,
|
||||||
|
CommerceProvider as CoreCommerceProvider,
|
||||||
|
useCommerce as useCoreCommerce,
|
||||||
|
} from '@commerce'
|
||||||
|
|
||||||
|
import { swellProvider, SwellProvider } from './provider'
|
||||||
|
import {
|
||||||
|
SWELL_CHECKOUT_ID_COOKIE,
|
||||||
|
SWELL_STORE_ID,
|
||||||
|
SWELL_PUBLIC_KEY,
|
||||||
|
} from './const'
|
||||||
|
swell.init(SWELL_STORE_ID, SWELL_PUBLIC_KEY)
|
||||||
|
|
||||||
|
export { swellProvider }
|
||||||
|
export type { SwellProvider }
|
||||||
|
|
||||||
|
export const swellConfig: any = {
|
||||||
|
locale: 'en-us',
|
||||||
|
cartCookie: SWELL_CHECKOUT_ID_COOKIE,
|
||||||
|
swell,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SwellConfig = Partial<CommerceConfig>
|
||||||
|
|
||||||
|
export type SwellProps = {
|
||||||
|
children?: ReactNode
|
||||||
|
locale: string
|
||||||
|
} & SwellConfig
|
||||||
|
|
||||||
|
export function CommerceProvider({ children, ...config }: SwellProps) {
|
||||||
|
return (
|
||||||
|
<CoreCommerceProvider
|
||||||
|
// TODO: Fix this type
|
||||||
|
provider={swellProvider as any}
|
||||||
|
config={{ ...swellConfig, ...config }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</CoreCommerceProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCommerce = () => useCoreCommerce()
|
8
framework/swell/next.config.js
Normal file
8
framework/swell/next.config.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
const commerce = require('./commerce.config.json')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
commerce,
|
||||||
|
images: {
|
||||||
|
domains: ['cdn.schema.io'],
|
||||||
|
},
|
||||||
|
}
|
28
framework/swell/product/get-all-collections.ts
Normal file
28
framework/swell/product/get-all-collections.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { CollectionEdge } from '../schema'
|
||||||
|
import { getConfig, SwellConfig } from '../api'
|
||||||
|
|
||||||
|
const getAllCollections = async (options?: {
|
||||||
|
variables?: any
|
||||||
|
config: SwellConfig
|
||||||
|
preview?: boolean
|
||||||
|
}) => {
|
||||||
|
let { config, variables = { limit: 25 } } = options ?? {}
|
||||||
|
config = getConfig(config)
|
||||||
|
|
||||||
|
const response = await config.fetch('categories', 'list', { variables })
|
||||||
|
const edges = response.results ?? []
|
||||||
|
|
||||||
|
const categories = edges.map(
|
||||||
|
({ node: { id: entityId, title: name, handle } }: CollectionEdge) => ({
|
||||||
|
entityId,
|
||||||
|
name,
|
||||||
|
path: `/${handle}`,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
categories,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getAllCollections
|
39
framework/swell/product/get-all-product-paths.ts
Normal file
39
framework/swell/product/get-all-product-paths.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { SwellProduct } from '../types'
|
||||||
|
import { getConfig, SwellConfig } from '../api'
|
||||||
|
|
||||||
|
type ProductPath = {
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProductPathNode = {
|
||||||
|
node: ProductPath
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReturnType = {
|
||||||
|
products: ProductPathNode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAllProductPaths = async (options?: {
|
||||||
|
variables?: any
|
||||||
|
config?: SwellConfig
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<ReturnType> => {
|
||||||
|
let { config, variables = [{ limit: 100 }] } = options ?? {}
|
||||||
|
config = getConfig(config)
|
||||||
|
|
||||||
|
const { results } = await config.fetch('products', 'list', [
|
||||||
|
{
|
||||||
|
limit: variables.first,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
return {
|
||||||
|
products: results?.map(({ slug: handle }: SwellProduct) => ({
|
||||||
|
node: {
|
||||||
|
path: `/${handle}`,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getAllProductPaths
|
36
framework/swell/product/get-all-products.ts
Normal file
36
framework/swell/product/get-all-products.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { getConfig, SwellConfig } from '../api'
|
||||||
|
import { normalizeProduct } from '../utils/normalize'
|
||||||
|
import { Product } from '@commerce/types'
|
||||||
|
import { SwellProduct } from '../types'
|
||||||
|
|
||||||
|
type Variables = {
|
||||||
|
first?: number
|
||||||
|
field?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReturnType = {
|
||||||
|
products: Product[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAllProducts = async (options: {
|
||||||
|
variables?: Variables
|
||||||
|
config?: SwellConfig
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<ReturnType> => {
|
||||||
|
let { config, variables = { first: 250 } } = options ?? {}
|
||||||
|
config = getConfig(config)
|
||||||
|
const { results } = await config.fetch('products', 'list', [
|
||||||
|
{
|
||||||
|
limit: variables.first,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
const products = results.map((product: SwellProduct) =>
|
||||||
|
normalizeProduct(product)
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
products,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getAllProducts
|
32
framework/swell/product/get-product.ts
Normal file
32
framework/swell/product/get-product.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { GraphQLFetcherResult } from '@commerce/api'
|
||||||
|
import { getConfig, SwellConfig } from '../api'
|
||||||
|
import { normalizeProduct } from '../utils'
|
||||||
|
|
||||||
|
type Variables = {
|
||||||
|
slug: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReturnType = {
|
||||||
|
product: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const getProduct = async (options: {
|
||||||
|
variables: Variables
|
||||||
|
config: SwellConfig
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<ReturnType> => {
|
||||||
|
let { config, variables } = options ?? {}
|
||||||
|
config = getConfig(config)
|
||||||
|
|
||||||
|
const product = await config.fetch('products', 'get', [variables.slug])
|
||||||
|
|
||||||
|
if (product && product.variants) {
|
||||||
|
product.variants = product.variants?.results
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
product: product ? normalizeProduct(product) : null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getProduct
|
2
framework/swell/product/use-price.tsx
Normal file
2
framework/swell/product/use-price.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from '@commerce/product/use-price'
|
||||||
|
export { default } from '@commerce/product/use-price'
|
73
framework/swell/product/use-search.tsx
Normal file
73
framework/swell/product/use-search.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
|
import useSearch, { UseSearch } from '@commerce/product/use-search'
|
||||||
|
|
||||||
|
import { normalizeProduct } from '../utils'
|
||||||
|
|
||||||
|
import { Product } from '@commerce/types'
|
||||||
|
|
||||||
|
import { SwellProduct } from '../types'
|
||||||
|
|
||||||
|
export default useSearch as UseSearch<typeof handler>
|
||||||
|
|
||||||
|
export type SearchProductsInput = {
|
||||||
|
search?: string
|
||||||
|
categoryId?: string
|
||||||
|
brandId?: string
|
||||||
|
sort?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SearchProductsData = {
|
||||||
|
products: Product[]
|
||||||
|
found: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handler: SWRHook<
|
||||||
|
SearchProductsData,
|
||||||
|
SearchProductsInput,
|
||||||
|
SearchProductsInput
|
||||||
|
> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: 'products', // String(Math.random()),
|
||||||
|
method: 'list',
|
||||||
|
},
|
||||||
|
async fetcher({ input, options, fetch }) {
|
||||||
|
const sortMap = new Map([
|
||||||
|
['latest-desc', ''],
|
||||||
|
['price-asc', 'price_asc'],
|
||||||
|
['price-desc', 'price_desc'],
|
||||||
|
['trending-desc', 'popularity'],
|
||||||
|
])
|
||||||
|
const { categoryId, search, sort = 'latest-desc' } = input
|
||||||
|
const mappedSort = sortMap.get(sort)
|
||||||
|
const { results, count: found } = await fetch({
|
||||||
|
query: 'products',
|
||||||
|
method: 'list',
|
||||||
|
variables: { category: categoryId, search, sort: mappedSort },
|
||||||
|
})
|
||||||
|
|
||||||
|
const products = results.map((product: SwellProduct) =>
|
||||||
|
normalizeProduct(product)
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
products,
|
||||||
|
found,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ useData }) =>
|
||||||
|
(input = {}) => {
|
||||||
|
return useData({
|
||||||
|
input: [
|
||||||
|
['search', input.search],
|
||||||
|
['categoryId', input.categoryId],
|
||||||
|
['brandId', input.brandId],
|
||||||
|
['sort', input.sort],
|
||||||
|
],
|
||||||
|
swrOptions: {
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
...input.swrOptions,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
31
framework/swell/provider.ts
Normal file
31
framework/swell/provider.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { SWELL_CHECKOUT_URL_COOKIE, STORE_DOMAIN } from './const'
|
||||||
|
|
||||||
|
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'
|
||||||
|
|
||||||
|
import fetcher from './fetcher'
|
||||||
|
|
||||||
|
export const swellProvider = {
|
||||||
|
locale: 'en-us',
|
||||||
|
cartCookie: SWELL_CHECKOUT_URL_COOKIE,
|
||||||
|
storeDomain: STORE_DOMAIN,
|
||||||
|
fetcher,
|
||||||
|
cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
|
||||||
|
customer: { useCustomer },
|
||||||
|
products: { useSearch },
|
||||||
|
auth: { useLogin, useLogout, useSignup },
|
||||||
|
features: {
|
||||||
|
wishlist: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SwellProvider = typeof swellProvider
|
5002
framework/swell/schema.d.ts
vendored
Normal file
5002
framework/swell/schema.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9631
framework/swell/schema.graphql
Normal file
9631
framework/swell/schema.graphql
Normal file
File diff suppressed because it is too large
Load Diff
1
framework/swell/swell-js.d.ts
vendored
Normal file
1
framework/swell/swell-js.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
declare module 'swell-js'
|
123
framework/swell/types.ts
Normal file
123
framework/swell/types.ts
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import * as Core from '@commerce/types'
|
||||||
|
import { CheckoutLineItem } from './schema'
|
||||||
|
|
||||||
|
export type SwellImage = {
|
||||||
|
file: {
|
||||||
|
url: String
|
||||||
|
height: Number
|
||||||
|
width: Number
|
||||||
|
}
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CartLineItem = {
|
||||||
|
id: string
|
||||||
|
product: SwellProduct
|
||||||
|
price: number
|
||||||
|
variant: {
|
||||||
|
name: string | null
|
||||||
|
sku: string | null
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
quantity: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SwellCart = {
|
||||||
|
id: string
|
||||||
|
account_id: number
|
||||||
|
currency: string
|
||||||
|
tax_included_total: number
|
||||||
|
sub_total: number
|
||||||
|
grand_total: number
|
||||||
|
discount_total: number
|
||||||
|
quantity: number
|
||||||
|
items: CartLineItem[]
|
||||||
|
date_created: string
|
||||||
|
discounts?: { id: number; amount: number }[] | null
|
||||||
|
// TODO: add missing fields
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SwellVariant = {
|
||||||
|
id: string
|
||||||
|
option_value_ids: string[]
|
||||||
|
name: string
|
||||||
|
price?: number
|
||||||
|
stock_status?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductOptionValue {
|
||||||
|
label: string
|
||||||
|
hexColors?: string[]
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProductOptions = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
variant: boolean
|
||||||
|
values: ProductOptionValue[]
|
||||||
|
required: boolean
|
||||||
|
active: boolean
|
||||||
|
attribute_id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SwellProduct {
|
||||||
|
id: string
|
||||||
|
description: string
|
||||||
|
name: string
|
||||||
|
slug: string
|
||||||
|
currency: string
|
||||||
|
price: number
|
||||||
|
images: any[]
|
||||||
|
options: any[]
|
||||||
|
variants: any[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SwellCustomer extends Core.Customer {
|
||||||
|
first_name: string
|
||||||
|
last_name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SwellCheckout = {
|
||||||
|
id: string
|
||||||
|
webUrl: string
|
||||||
|
lineItems: CheckoutLineItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Cart extends Core.Cart {
|
||||||
|
id: string
|
||||||
|
lineItems: LineItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LineItem extends Core.LineItem {
|
||||||
|
options?: any[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cart mutations
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type OptionSelections = {
|
||||||
|
option_id: number
|
||||||
|
option_value: number | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CartItemBody = Core.CartItemBody & {
|
||||||
|
productId: string // The product id is always required for BC
|
||||||
|
optionSelections?: OptionSelections
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetCartHandlerBody = Core.GetCartHandlerBody
|
||||||
|
|
||||||
|
export type AddCartItemBody = Core.AddCartItemBody<CartItemBody>
|
||||||
|
|
||||||
|
export type AddCartItemHandlerBody = Core.AddCartItemHandlerBody<CartItemBody>
|
||||||
|
|
||||||
|
export type UpdateCartItemBody = Core.UpdateCartItemBody<CartItemBody>
|
||||||
|
|
||||||
|
export type UpdateCartItemHandlerBody =
|
||||||
|
Core.UpdateCartItemHandlerBody<CartItemBody>
|
||||||
|
|
||||||
|
export type RemoveCartItemBody = Core.RemoveCartItemBody
|
||||||
|
|
||||||
|
export type RemoveCartItemHandlerBody = Core.RemoveCartItemHandlerBody
|
21
framework/swell/utils/customer-token.ts
Normal file
21
framework/swell/utils/customer-token.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import Cookies, { CookieAttributes } from 'js-cookie'
|
||||||
|
import { SWELL_COOKIE_EXPIRE, SWELL_CUSTOMER_TOKEN_COOKIE } from '../const'
|
||||||
|
|
||||||
|
export const getCustomerToken = () => Cookies.get(SWELL_CUSTOMER_TOKEN_COOKIE)
|
||||||
|
|
||||||
|
export const setCustomerToken = (
|
||||||
|
token: string | null,
|
||||||
|
options?: CookieAttributes
|
||||||
|
) => {
|
||||||
|
if (!token) {
|
||||||
|
Cookies.remove(SWELL_CUSTOMER_TOKEN_COOKIE)
|
||||||
|
} else {
|
||||||
|
Cookies.set(
|
||||||
|
SWELL_CUSTOMER_TOKEN_COOKIE,
|
||||||
|
token,
|
||||||
|
options ?? {
|
||||||
|
expires: SWELL_COOKIE_EXPIRE,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
20
framework/swell/utils/get-categories.ts
Normal file
20
framework/swell/utils/get-categories.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { SwellConfig } from '../api'
|
||||||
|
|
||||||
|
export type Category = {
|
||||||
|
entityId: string
|
||||||
|
name: string
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCategories = async (config: SwellConfig): Promise<Category[]> => {
|
||||||
|
const data = await config.fetch('categories', 'get')
|
||||||
|
return (
|
||||||
|
data.results.map(({ id: entityId, name, slug }: any) => ({
|
||||||
|
entityId,
|
||||||
|
name,
|
||||||
|
path: `/${slug}`,
|
||||||
|
})) ?? []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getCategories
|
8
framework/swell/utils/get-checkout-id.ts
Normal file
8
framework/swell/utils/get-checkout-id.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import Cookies from 'js-cookie'
|
||||||
|
import { SWELL_CHECKOUT_ID_COOKIE } from '../const'
|
||||||
|
|
||||||
|
const getCheckoutId = (id?: string) => {
|
||||||
|
return id ?? Cookies.get(SWELL_CHECKOUT_ID_COOKIE)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getCheckoutId
|
27
framework/swell/utils/get-search-variables.ts
Normal file
27
framework/swell/utils/get-search-variables.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import getSortVariables from './get-sort-variables'
|
||||||
|
import type { SearchProductsInput } from '../product/use-search'
|
||||||
|
|
||||||
|
export const getSearchVariables = ({
|
||||||
|
brandId,
|
||||||
|
search,
|
||||||
|
categoryId,
|
||||||
|
sort,
|
||||||
|
}: SearchProductsInput) => {
|
||||||
|
let query = ''
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
query += `product_type:${search} OR title:${search} OR tag:${search}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brandId) {
|
||||||
|
query += `${search ? ' AND ' : ''}vendor:${brandId}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
categoryId,
|
||||||
|
query,
|
||||||
|
...getSortVariables(sort, !!categoryId),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getSearchVariables
|
32
framework/swell/utils/get-sort-variables.ts
Normal file
32
framework/swell/utils/get-sort-variables.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
const getSortVariables = (sort?: string, isCategory = false) => {
|
||||||
|
let output = {}
|
||||||
|
switch (sort) {
|
||||||
|
case 'price-asc':
|
||||||
|
output = {
|
||||||
|
sortKey: 'PRICE',
|
||||||
|
reverse: false,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'price-desc':
|
||||||
|
output = {
|
||||||
|
sortKey: 'PRICE',
|
||||||
|
reverse: true,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'trending-desc':
|
||||||
|
output = {
|
||||||
|
sortKey: 'BEST_SELLING',
|
||||||
|
reverse: false,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'latest-desc':
|
||||||
|
output = {
|
||||||
|
sortKey: isCategory ? 'CREATED' : 'CREATED_AT',
|
||||||
|
reverse: true,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getSortVariables
|
27
framework/swell/utils/get-vendors.ts
Normal file
27
framework/swell/utils/get-vendors.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { SwellConfig } from '../api'
|
||||||
|
|
||||||
|
export type BrandNode = {
|
||||||
|
name: string
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BrandEdge = {
|
||||||
|
node: BrandNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Brands = BrandEdge[]
|
||||||
|
|
||||||
|
const getVendors = async (config: SwellConfig) => {
|
||||||
|
const vendors: [string] =
|
||||||
|
(await config.fetch('attributes', 'get', ['brand']))?.values ?? []
|
||||||
|
|
||||||
|
return [...new Set(vendors)].map((v) => ({
|
||||||
|
node: {
|
||||||
|
entityId: v,
|
||||||
|
name: v,
|
||||||
|
path: `brands/${v}`,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getVendors
|
19
framework/swell/utils/handle-fetch-response.ts
Normal file
19
framework/swell/utils/handle-fetch-response.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
|
|
||||||
|
type SwellFetchResponse = {
|
||||||
|
error: {
|
||||||
|
message: string
|
||||||
|
code?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFetchResponse = async (res: SwellFetchResponse) => {
|
||||||
|
if (res) {
|
||||||
|
if (res.error) {
|
||||||
|
throw new CommerceError(res.error)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default handleFetchResponse
|
39
framework/swell/utils/handle-login.ts
Normal file
39
framework/swell/utils/handle-login.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { ValidationError } from '@commerce/utils/errors'
|
||||||
|
import { setCustomerToken } from './customer-token'
|
||||||
|
|
||||||
|
const getErrorMessage = ({
|
||||||
|
code,
|
||||||
|
message,
|
||||||
|
}: {
|
||||||
|
code: string
|
||||||
|
message: string
|
||||||
|
}) => {
|
||||||
|
switch (code) {
|
||||||
|
case 'UNIDENTIFIED_CUSTOMER':
|
||||||
|
message = 'Cannot find an account that matches the provided credentials'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLogin = (data: any) => {
|
||||||
|
const response = data.customerAccessTokenCreate
|
||||||
|
const errors = response?.customerUserErrors
|
||||||
|
|
||||||
|
if (errors && errors.length) {
|
||||||
|
throw new ValidationError({
|
||||||
|
message: getErrorMessage(errors[0]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const customerAccessToken = response?.customerAccessToken
|
||||||
|
const accessToken = customerAccessToken?.accessToken
|
||||||
|
|
||||||
|
if (accessToken) {
|
||||||
|
setCustomerToken(accessToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
return customerAccessToken
|
||||||
|
}
|
||||||
|
|
||||||
|
export default handleLogin
|
9
framework/swell/utils/index.ts
Normal file
9
framework/swell/utils/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export { default as handleFetchResponse } from './handle-fetch-response'
|
||||||
|
export { default as getSearchVariables } from './get-search-variables'
|
||||||
|
export { default as getSortVariables } from './get-sort-variables'
|
||||||
|
export { default as getVendors } from './get-vendors'
|
||||||
|
export { default as getCategories } from './get-categories'
|
||||||
|
export { default as getCheckoutId } from './get-checkout-id'
|
||||||
|
|
||||||
|
export * from './normalize'
|
||||||
|
export * from './customer-token'
|
219
framework/swell/utils/normalize.ts
Normal file
219
framework/swell/utils/normalize.ts
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
import { Product, Customer } from '@commerce/types'
|
||||||
|
|
||||||
|
import { MoneyV2, ProductOption } from '../schema'
|
||||||
|
|
||||||
|
import type {
|
||||||
|
Cart,
|
||||||
|
CartLineItem,
|
||||||
|
SwellCustomer,
|
||||||
|
SwellProduct,
|
||||||
|
SwellImage,
|
||||||
|
SwellVariant,
|
||||||
|
ProductOptionValue,
|
||||||
|
SwellCart,
|
||||||
|
LineItem,
|
||||||
|
} from '../types'
|
||||||
|
|
||||||
|
const money = ({ amount, currencyCode }: MoneyV2) => {
|
||||||
|
return {
|
||||||
|
value: +amount,
|
||||||
|
currencyCode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type normalizedProductOption = {
|
||||||
|
__typename?: string
|
||||||
|
id: string
|
||||||
|
displayName: string
|
||||||
|
values: ProductOptionValue[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeProductOption = ({
|
||||||
|
id,
|
||||||
|
name: displayName = '',
|
||||||
|
values = [],
|
||||||
|
}: ProductOption) => {
|
||||||
|
let returnValues = values.map((value) => {
|
||||||
|
let output: any = {
|
||||||
|
label: value.name,
|
||||||
|
id: value?.id || id,
|
||||||
|
}
|
||||||
|
if (displayName.match(/colou?r/gi)) {
|
||||||
|
output = {
|
||||||
|
...output,
|
||||||
|
hexColors: [value.name],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
__typename: 'MultipleChoiceOption',
|
||||||
|
id,
|
||||||
|
displayName,
|
||||||
|
values: returnValues,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeProductImages = (images: SwellImage[]) => {
|
||||||
|
if (!images || images.length < 1) {
|
||||||
|
return [{ url: '/' }]
|
||||||
|
}
|
||||||
|
return images?.map(({ file, ...rest }: SwellImage) => ({
|
||||||
|
url: file?.url + '',
|
||||||
|
height: Number(file?.height),
|
||||||
|
width: Number(file?.width),
|
||||||
|
...rest,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeProductVariants = (
|
||||||
|
variants: SwellVariant[],
|
||||||
|
productOptions: normalizedProductOption[]
|
||||||
|
) => {
|
||||||
|
return variants?.map(
|
||||||
|
({ id, name, price, option_value_ids: optionValueIds = [] }) => {
|
||||||
|
const values = name
|
||||||
|
.split(',')
|
||||||
|
.map((i) => ({ name: i.trim(), label: i.trim() }))
|
||||||
|
|
||||||
|
const options = optionValueIds.map((id) => {
|
||||||
|
const matchingOption = productOptions.find((option) => {
|
||||||
|
return option.values.find(
|
||||||
|
(value: ProductOptionValue) => value.id == id
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return normalizeProductOption({
|
||||||
|
id,
|
||||||
|
name: matchingOption?.displayName ?? '',
|
||||||
|
values,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
// sku: sku ?? id,
|
||||||
|
price: price ?? null,
|
||||||
|
listPrice: price ?? null,
|
||||||
|
// requiresShipping: true,
|
||||||
|
options,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeProduct(swellProduct: SwellProduct): Product {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
images,
|
||||||
|
options,
|
||||||
|
slug,
|
||||||
|
variants,
|
||||||
|
price: value,
|
||||||
|
currency: currencyCode,
|
||||||
|
} = swellProduct
|
||||||
|
// ProductView accesses variants for each product
|
||||||
|
const emptyVariants = [{ options: [], id, name }]
|
||||||
|
const productOptions = options
|
||||||
|
? options.map((o) => normalizeProductOption(o))
|
||||||
|
: []
|
||||||
|
const productVariants = variants
|
||||||
|
? normalizeProductVariants(variants, productOptions)
|
||||||
|
: []
|
||||||
|
|
||||||
|
const productImages = normalizeProductImages(images)
|
||||||
|
const product = {
|
||||||
|
...swellProduct,
|
||||||
|
description,
|
||||||
|
id,
|
||||||
|
vendor: '',
|
||||||
|
path: `/${slug}`,
|
||||||
|
images: productImages,
|
||||||
|
variants:
|
||||||
|
productVariants && productVariants.length
|
||||||
|
? productVariants
|
||||||
|
: emptyVariants,
|
||||||
|
options: productOptions,
|
||||||
|
price: {
|
||||||
|
value,
|
||||||
|
currencyCode,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return product
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeCart({
|
||||||
|
id,
|
||||||
|
account_id,
|
||||||
|
date_created,
|
||||||
|
currency,
|
||||||
|
tax_included_total,
|
||||||
|
items,
|
||||||
|
sub_total,
|
||||||
|
grand_total,
|
||||||
|
discounts,
|
||||||
|
}: SwellCart) {
|
||||||
|
const cart: Cart = {
|
||||||
|
id: id,
|
||||||
|
customerId: account_id + '',
|
||||||
|
email: '',
|
||||||
|
createdAt: date_created,
|
||||||
|
currency: { code: currency },
|
||||||
|
taxesIncluded: tax_included_total > 0,
|
||||||
|
lineItems: items?.map(normalizeLineItem) ?? [],
|
||||||
|
lineItemsSubtotalPrice: +sub_total,
|
||||||
|
subtotalPrice: +sub_total,
|
||||||
|
totalPrice: grand_total,
|
||||||
|
discounts: discounts?.map((discount) => ({ value: discount.amount })),
|
||||||
|
}
|
||||||
|
return cart
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeCustomer(customer: SwellCustomer): Customer {
|
||||||
|
const { first_name: firstName, last_name: lastName } = customer
|
||||||
|
return {
|
||||||
|
...customer,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLineItem({
|
||||||
|
id,
|
||||||
|
product,
|
||||||
|
price,
|
||||||
|
variant,
|
||||||
|
quantity,
|
||||||
|
}: CartLineItem): LineItem {
|
||||||
|
const item = {
|
||||||
|
id,
|
||||||
|
variantId: variant?.id,
|
||||||
|
productId: product.id ?? '',
|
||||||
|
name: product?.name ?? '',
|
||||||
|
quantity,
|
||||||
|
variant: {
|
||||||
|
id: variant?.id ?? '',
|
||||||
|
sku: variant?.sku ?? '',
|
||||||
|
name: variant?.name!,
|
||||||
|
image: {
|
||||||
|
url:
|
||||||
|
product?.images && product.images.length > 0
|
||||||
|
? product?.images[0].file.url
|
||||||
|
: '/',
|
||||||
|
},
|
||||||
|
requiresShipping: false,
|
||||||
|
price: price,
|
||||||
|
listPrice: price,
|
||||||
|
},
|
||||||
|
path: '',
|
||||||
|
discounts: [],
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
value: variant?.name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}
|
13
framework/swell/utils/storage.ts
Normal file
13
framework/swell/utils/storage.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export const getCheckoutIdFromStorage = (token: string) => {
|
||||||
|
if (window && window.sessionStorage) {
|
||||||
|
return window.sessionStorage.getItem(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setCheckoutIdInStorage = (token: string, id: string | number) => {
|
||||||
|
if (window && window.sessionStorage) {
|
||||||
|
return window.sessionStorage.setItem(token, id + '')
|
||||||
|
}
|
||||||
|
}
|
13
framework/swell/wishlist/use-add-item.tsx
Normal file
13
framework/swell/wishlist/use-add-item.tsx
Normal 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
|
17
framework/swell/wishlist/use-remove-item.tsx
Normal file
17
framework/swell/wishlist/use-remove-item.tsx
Normal 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
|
46
framework/swell/wishlist/use-wishlist.tsx
Normal file
46
framework/swell/wishlist/use-wishlist.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// TODO: replace this hook and other wishlist hooks with a handler, or remove them if
|
||||||
|
// Swell doesn't have a wishlist
|
||||||
|
|
||||||
|
import { HookFetcher } from '@commerce/utils/types'
|
||||||
|
import { Product } from '../schema'
|
||||||
|
|
||||||
|
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)
|
@ -7,7 +7,7 @@ const {
|
|||||||
const provider = commerce.provider || getProviderName()
|
const provider = commerce.provider || getProviderName()
|
||||||
const isBC = provider === 'bigcommerce'
|
const isBC = provider === 'bigcommerce'
|
||||||
const isShopify = provider === 'shopify'
|
const isShopify = provider === 'shopify'
|
||||||
const isRC = provider === 'reactioncommerce'
|
const isSwell = provider === 'swell'
|
||||||
|
|
||||||
module.exports = withCommerceConfig({
|
module.exports = withCommerceConfig({
|
||||||
env: {
|
env: {
|
||||||
@ -20,7 +20,7 @@ module.exports = withCommerceConfig({
|
|||||||
},
|
},
|
||||||
rewrites() {
|
rewrites() {
|
||||||
return [
|
return [
|
||||||
(isBC || isShopify) && {
|
(isBC || isShopify || isSwell) && {
|
||||||
source: '/checkout',
|
source: '/checkout',
|
||||||
destination: '/api/bigcommerce/checkout',
|
destination: '/api/bigcommerce/checkout',
|
||||||
},
|
},
|
||||||
|
@ -36,16 +36,17 @@
|
|||||||
"next": "^10.0.9-canary.5",
|
"next": "^10.0.9-canary.5",
|
||||||
"next-seo": "^4.11.0",
|
"next-seo": "^4.11.0",
|
||||||
"next-themes": "^0.0.4",
|
"next-themes": "^0.0.4",
|
||||||
"postcss": "^8.2.6",
|
"postcss": "^8.2.8",
|
||||||
"postcss-nesting": "^7.0.1",
|
"postcss-nesting": "^7.0.1",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
"react-merge-refs": "^1.1.0",
|
"react-merge-refs": "^1.1.0",
|
||||||
"react-ticker": "^1.2.2",
|
"react-ticker": "^1.2.2",
|
||||||
"shopify-buy": "^2.11.0",
|
"shopify-buy": "^2.11.0",
|
||||||
|
"swell-js": "^4.0.0-next.0",
|
||||||
"swr": "^0.4.0",
|
"swr": "^0.4.0",
|
||||||
"tabbable": "^5.1.5",
|
"tabbable": "^5.1.5",
|
||||||
"tailwindcss": "^2.0.3"
|
"tailwindcss": "^2.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "^1.20.0",
|
"@graphql-codegen/cli": "^1.20.0",
|
||||||
@ -54,6 +55,7 @@
|
|||||||
"@graphql-codegen/typescript-operations": "^1.17.13",
|
"@graphql-codegen/typescript-operations": "^1.17.13",
|
||||||
"@manifoldco/swagger-to-ts": "^2.1.0",
|
"@manifoldco/swagger-to-ts": "^2.1.0",
|
||||||
"@next/bundle-analyzer": "^10.0.1",
|
"@next/bundle-analyzer": "^10.0.1",
|
||||||
|
"@tailwindcss/jit": "^0.1.3",
|
||||||
"@types/body-scroll-lock": "^2.6.1",
|
"@types/body-scroll-lock": "^2.6.1",
|
||||||
"@types/classnames": "^2.2.10",
|
"@types/classnames": "^2.2.10",
|
||||||
"@types/cookie": "^0.4.0",
|
"@types/cookie": "^0.4.0",
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
import type { GetStaticPropsContext } from 'next'
|
|
||||||
import { getConfig } from '@framework/api'
|
|
||||||
import getAllPages from '@framework/common/get-all-pages'
|
|
||||||
import { Layout } from '@components/common'
|
|
||||||
import { Container } from '@components/ui'
|
|
||||||
|
|
||||||
export async function getStaticProps({
|
|
||||||
preview,
|
|
||||||
locale,
|
|
||||||
}: GetStaticPropsContext) {
|
|
||||||
const config = getConfig({ locale })
|
|
||||||
const { pages } = await getAllPages({ config, preview })
|
|
||||||
return {
|
|
||||||
props: { pages },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Blog() {
|
|
||||||
return (
|
|
||||||
<div className="pb-20">
|
|
||||||
<div className="text-center pt-40 pb-56 bg-violet">
|
|
||||||
<Container>
|
|
||||||
<h2 className="text-4xl tracking-tight leading-10 font-extrabold text-white sm:text-5xl sm:leading-none md:text-6xl">
|
|
||||||
Welcome to Acme, the simplest way to start publishing with Next.js
|
|
||||||
</h2>
|
|
||||||
<p className="mt-3 max-w-md mx-auto text-gray-100 sm:text-lg md:mt-5 md:text-xl md:max-w-3xl">
|
|
||||||
The Yeezy BOOST 350 V2 lineup continues to grow. We recently had the
|
|
||||||
‘Carbon’ iteration, and now release details have been locked in for
|
|
||||||
this ‘Natural’ joint. Revealed by Yeezy Mafia earlier this year, the
|
|
||||||
shoe was originally called ‘Abez’, which translated to ‘Tin’ in
|
|
||||||
Hebrew. It’s now undergone a name change, and will be referred to as
|
|
||||||
‘Natura`
|
|
||||||
</p>
|
|
||||||
<div className="mt-5 max-w-md mx-auto sm:flex sm:justify-center md:mt-12">
|
|
||||||
<div className="flex">
|
|
||||||
<div className="flex-shrink-0 inline-flex rounded-full border-2 border-white">
|
|
||||||
<img
|
|
||||||
className="h-12 w-12 rounded-full"
|
|
||||||
src="https://vercel.com/api/www/avatar/61182a9f6bda512b4d9263c9c8a60aabe0402f4c?s=204"
|
|
||||||
alt="Avatar"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="ml-4">
|
|
||||||
<div className="leading-6 font-medium text-white">
|
|
||||||
José Rodriguez
|
|
||||||
</div>
|
|
||||||
<div className="leading-6 font-medium text-gray-200">
|
|
||||||
CEO, Acme
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Container>
|
|
||||||
</div>
|
|
||||||
<Container>
|
|
||||||
<div className="-mt-96 mx-auto">
|
|
||||||
<img src="/jacket.png" alt="Jacket" />
|
|
||||||
</div>
|
|
||||||
{/** Replace by HTML Content */}
|
|
||||||
<div className="text-lg leading-7 font-medium py-6 text-justify max-w-6xl mx-auto">
|
|
||||||
<p className="py-6">
|
|
||||||
Biscuit oat cake wafer icing ice cream tiramisu pudding cupcake.
|
|
||||||
Candy canes bonbon dragée jujubes chocolate bar. Cotton candy gummi
|
|
||||||
bears toffee cake muffin caramels. Gummi bears danish liquorice ice
|
|
||||||
cream pie chocolate cake lemon drops tootsie roll tart. Biscuit
|
|
||||||
gingerbread fruitcake cake powder pudding cotton candy chocolate
|
|
||||||
bar. Sweet donut marshmallow powder gummies jelly tart powder.
|
|
||||||
Cheesecake bonbon caramels cupcake jujubes halvah donut dessert
|
|
||||||
chocolate bar. Jelly gummies liquorice lollipop chocolate bar
|
|
||||||
chocolate cake sugar plum. Lollipop toffee dragée chocolate bar
|
|
||||||
jelly beans biscuit. Halvah danish cheesecake. Tiramisu donut
|
|
||||||
lollipop pie donut caramels tiramisu. Jujubes candy canes pudding
|
|
||||||
danish fruitcake chupa chups jujubes carrot cake bonbon. Halvah
|
|
||||||
donut jelly halvah bonbon.
|
|
||||||
</p>
|
|
||||||
<p className="py-6">
|
|
||||||
Biscuit sugar plum sweet chocolate cake sesame snaps soufflé
|
|
||||||
topping. Gummies topping bonbon chocolate pudding cookie. Wafer
|
|
||||||
icing cake pastry. Gummies candy dessert chupa chups lemon drops.
|
|
||||||
Soufflé marshmallow oat cake chocolate jelly-o caramels pie marzipan
|
|
||||||
jelly beans. Cheesecake liquorice donut jujubes halvah ice cream
|
|
||||||
cotton candy cupcake sugar plum. Ice cream ice cream sweet roll
|
|
||||||
fruitcake icing. Muffin candy canes bonbon croissant gummies lemon
|
|
||||||
drops pie danish. Oat cake chocolate toffee cake jelly tart
|
|
||||||
caramels. Sweet donut cheesecake pastry pie sweet. Bonbon lollipop
|
|
||||||
brownie. Soufflé pudding macaroon cotton candy gingerbread. Biscuit
|
|
||||||
macaroon gummi bears candy canes chocolate cake lemon drops
|
|
||||||
marshmallow. Chocolate cake cotton candy marshmallow cake sweet
|
|
||||||
tootsie roll bonbon carrot cake sugar plum.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Container>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Blog.Layout = Layout
|
|
@ -78,7 +78,7 @@ export default function Search({
|
|||||||
const { data } = useSearch({
|
const { data } = useSearch({
|
||||||
search: typeof q === 'string' ? q : '',
|
search: typeof q === 'string' ? q : '',
|
||||||
categoryId: activeCategory?.entityId,
|
categoryId: activeCategory?.entityId,
|
||||||
brandId: activeBrand?.entityId,
|
brandId: (activeBrand as any)?.entityId,
|
||||||
sort: typeof sort === 'string' ? sort : '',
|
sort: typeof sort === 'string' ? sort : '',
|
||||||
shopId,
|
shopId,
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user