mirror of
https://github.com/vercel/commerce.git
synced 2025-07-23 04:36:49 +00:00
Add subscription endpoint for BC
This commit is contained in:
@@ -6,7 +6,7 @@ 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'
|
||||
import { I18nWidget } from '@components/common'
|
||||
import { I18nWidget, Subscribe } from '@components/common'
|
||||
import s from './Footer.module.css'
|
||||
|
||||
interface Props {
|
||||
@@ -70,6 +70,7 @@ const Footer: FC<Props> = ({ className, pages }) => {
|
||||
<div>
|
||||
<span>© 2020 ACME, Inc. All rights reserved.</span>
|
||||
</div>
|
||||
<Subscribe />
|
||||
<div className="flex items-center text-primary text-sm">
|
||||
<span className="text-primary">Created by</span>
|
||||
<a
|
||||
|
1
components/common/Subscribe/index.ts
Normal file
1
components/common/Subscribe/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './subscribe'
|
51
components/common/Subscribe/subscribe.jsx
Normal file
51
components/common/Subscribe/subscribe.jsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { validate } from 'email-validator'
|
||||
import { Button } from '@components/ui'
|
||||
import useSubscribe from '@framework/subscriptions/use-subscribe'
|
||||
|
||||
const Subscribe = () => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [email, setEmail] = useState('')
|
||||
|
||||
const subscribe = useSubscribe()
|
||||
|
||||
const handleChange = (event) => {
|
||||
setEmail(event.target.value)
|
||||
}
|
||||
|
||||
const handleSubscribe = async (event) => {
|
||||
event.preventDefault()
|
||||
setLoading(true)
|
||||
try {
|
||||
await subscribe({
|
||||
email: email,
|
||||
})
|
||||
setLoading(false)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubscribe} class="w-full max-w-sm">
|
||||
<div class="flex items-center border-b border-teal-500 py-2">
|
||||
<input
|
||||
class="appearance-none bg-transparent border-none w-full text-gray-700 mr-3 py-1 px-2 leading-tight focus:outline-none"
|
||||
type="email"
|
||||
placeholder="janedoe@gmail.com"
|
||||
aria-label="Email"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Button variant="slim" className="mt-1 h-8 " loading={loading}>
|
||||
S'INSCRIRE
|
||||
</Button>
|
||||
{/* <button class="flex-shrink-0 bg-teal-500 hover:bg-teal-700 border-teal-500 hover:border-teal-700 text-sm border-4 text-white py-1 px-2 rounded" type="button">
|
||||
Sign Up
|
||||
</button> */}
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default Subscribe
|
@@ -7,3 +7,4 @@ export { default as Searchbar } from './Searchbar'
|
||||
export { default as UserNav } from './UserNav'
|
||||
export { default as Head } from './Head'
|
||||
export { default as I18nWidget } from './I18nWidget'
|
||||
export { default as Subscribe } from './Subscribe'
|
||||
|
18
framework/bigcommerce/api/endpoints/subscriptions/index.ts
Normal file
18
framework/bigcommerce/api/endpoints/subscriptions/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
||||
import subscriptionsEndpoint from '@commerce/api/endpoints/subscriptions'
|
||||
import type { SubscriptionsSchema } from '../../../types/subscriptions'
|
||||
import type { BigcommerceAPI } from '../..'
|
||||
import subscriptions from './subscriptions'
|
||||
|
||||
export type SubscriptionsAPI = GetAPISchema<BigcommerceAPI, SubscriptionsSchema>
|
||||
|
||||
export type SubscriptionsEndpoint = SubscriptionsAPI['endpoint']
|
||||
|
||||
export const handlers: SubscriptionsEndpoint['handlers'] = { subscriptions }
|
||||
|
||||
const subscriptionsApi = createEndpoint<SubscriptionsAPI>({
|
||||
handler: subscriptionsEndpoint,
|
||||
handlers,
|
||||
})
|
||||
|
||||
export default subscriptionsApi
|
@@ -0,0 +1,33 @@
|
||||
import type { SubscriptionsEndpoint } from '.'
|
||||
|
||||
const subscriptions: SubscriptionsEndpoint['handlers']['subscriptions'] = async ({
|
||||
res,
|
||||
body: { email, firstName, lastName, source },
|
||||
config,
|
||||
}) => {
|
||||
try {
|
||||
const { data } = await config.storeApiFetch('/v3/customers/subscribers', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
email,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
source
|
||||
}
|
||||
})
|
||||
res.status(200).json({ data: data })
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
message: 'Error',
|
||||
code: 'Error SubscriptionsEndpoint',
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default subscriptions
|
@@ -14,6 +14,7 @@ import type { LogoutAPI } from './endpoints/logout'
|
||||
import type { SignupAPI } from './endpoints/signup'
|
||||
import type { ProductsAPI } from './endpoints/catalog/products'
|
||||
import type { WishlistAPI } from './endpoints/wishlist'
|
||||
import type { SubscriptionsAPI } from './endpoints/subscriptions'
|
||||
|
||||
import login from './operations/login'
|
||||
import getAllPages from './operations/get-all-pages'
|
||||
@@ -34,7 +35,7 @@ export interface BigcommerceConfig extends CommerceAPIConfig {
|
||||
storeChannelId?: string
|
||||
storeUrl?: string
|
||||
storeApiClientSecret?: string
|
||||
storeHash?:string
|
||||
storeHash?: string
|
||||
storeApiFetch<T>(endpoint: string, options?: RequestInit): Promise<T>
|
||||
}
|
||||
|
||||
@@ -81,8 +82,8 @@ const config: BigcommerceConfig = {
|
||||
storeApiToken: STORE_API_TOKEN,
|
||||
storeApiClientId: STORE_API_CLIENT_ID,
|
||||
storeChannelId: STORE_CHANNEL_ID,
|
||||
storeUrl:STORE_URL,
|
||||
storeApiClientSecret:CLIENT_SECRET,
|
||||
storeUrl: STORE_URL,
|
||||
storeApiClientSecret: CLIENT_SECRET,
|
||||
storeHash: STOREFRONT_HASH,
|
||||
storeApiFetch: createFetchStoreApi(() => getCommerceApi().getConfig()),
|
||||
}
|
||||
@@ -110,6 +111,7 @@ export type APIs =
|
||||
| SignupAPI
|
||||
| ProductsAPI
|
||||
| WishlistAPI
|
||||
| SubscriptionsAPI
|
||||
|
||||
export type BigcommerceAPI<P extends Provider = Provider> = CommerceAPI<P>
|
||||
|
||||
|
@@ -14,6 +14,8 @@ 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 { handler as useSubscriptions } from './subscriptions/use-subscribe'
|
||||
|
||||
import fetcher from './fetcher'
|
||||
|
||||
export const bigcommerceProvider = {
|
||||
@@ -29,6 +31,7 @@ export const bigcommerceProvider = {
|
||||
customer: { useCustomer },
|
||||
products: { useSearch },
|
||||
auth: { useLogin, useLogout, useSignup },
|
||||
subscriptions: { useSubscriptions },
|
||||
}
|
||||
|
||||
export type BigcommerceProvider = typeof bigcommerceProvider
|
||||
|
1
framework/bigcommerce/subscriptions/index.ts
Normal file
1
framework/bigcommerce/subscriptions/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as useSubscribe } from './use-subscribe'
|
38
framework/bigcommerce/subscriptions/use-subscribe.tsx
Normal file
38
framework/bigcommerce/subscriptions/use-subscribe.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { useCallback } from 'react'
|
||||
import type { MutationHook } from '@commerce/utils/types'
|
||||
import { CommerceError } from '@commerce/utils/errors'
|
||||
import useSubscribe, { UseSubscribe } from '@commerce/subscriptions/use-subscribe'
|
||||
import type { SubscriptionsHook } from '../types/subscriptions'
|
||||
|
||||
|
||||
export default useSubscribe as UseSubscribe<typeof handler>
|
||||
|
||||
export const handler: MutationHook<SubscriptionsHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/subscribe',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({ input: { email, firstName, lastName, source }, options, fetch }) {
|
||||
if (!(email)) {
|
||||
throw new CommerceError({
|
||||
message:
|
||||
'An email is required to subscribe',
|
||||
})
|
||||
}
|
||||
|
||||
return fetch({
|
||||
...options,
|
||||
body: { email, firstName, lastName, source },
|
||||
})
|
||||
},
|
||||
useHook: ({ fetch }) => () => {
|
||||
|
||||
return useCallback(
|
||||
async function subscribe(input) {
|
||||
const data = await fetch({ input })
|
||||
return data
|
||||
},
|
||||
[fetch]
|
||||
)
|
||||
},
|
||||
}
|
1
framework/bigcommerce/types/subscriptions.ts
Normal file
1
framework/bigcommerce/types/subscriptions.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from '@commerce/types/subscriptions'
|
35
framework/commerce/api/endpoints/subscriptions.ts
Normal file
35
framework/commerce/api/endpoints/subscriptions.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { SubscriptionsSchema } from '../../types/subscriptions'
|
||||
import { CommerceAPIError } from '../utils/errors'
|
||||
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||
import type { GetAPISchema } from '..'
|
||||
|
||||
const subscriptionsEndpoint: GetAPISchema<
|
||||
any,
|
||||
SubscriptionsSchema
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
const { req, res, handlers } = ctx
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
POST: handlers['subscriptions'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const body = { ...req.body }
|
||||
return await handlers['subscriptions']({ ...ctx, body })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
}
|
||||
|
||||
export default subscriptionsEndpoint
|
@@ -11,6 +11,8 @@ import type { WishlistSchema } from '../types/wishlist'
|
||||
import type { CheckoutSchema } from '../types/checkout'
|
||||
import type { CustomerCardSchema } from '../types/customer/card'
|
||||
import type { CustomerAddressSchema } from '../types/customer/address'
|
||||
import type { SubscriptionsSchema } from '../types/subscriptions'
|
||||
|
||||
import {
|
||||
defaultOperations,
|
||||
OPERATIONS,
|
||||
@@ -29,22 +31,23 @@ export type APISchemas =
|
||||
| CheckoutSchema
|
||||
| CustomerCardSchema
|
||||
| CustomerAddressSchema
|
||||
| SubscriptionsSchema
|
||||
|
||||
export type GetAPISchema<
|
||||
C extends CommerceAPI<any>,
|
||||
S extends APISchemas = APISchemas
|
||||
> = {
|
||||
schema: S
|
||||
endpoint: EndpointContext<C, S['endpoint']>
|
||||
}
|
||||
> = {
|
||||
schema: S
|
||||
endpoint: EndpointContext<C, S['endpoint']>
|
||||
}
|
||||
|
||||
export type EndpointContext<
|
||||
C extends CommerceAPI,
|
||||
E extends EndpointSchemaBase
|
||||
> = {
|
||||
handler: Endpoint<C, E>
|
||||
handlers: EndpointHandlers<C, E>
|
||||
}
|
||||
> = {
|
||||
handler: Endpoint<C, E>
|
||||
handlers: EndpointHandlers<C, E>
|
||||
}
|
||||
|
||||
export type EndpointSchemaBase = {
|
||||
options: {}
|
||||
@@ -56,20 +59,20 @@ export type EndpointSchemaBase = {
|
||||
export type Endpoint<
|
||||
C extends CommerceAPI,
|
||||
E extends EndpointSchemaBase
|
||||
> = APIEndpoint<C, EndpointHandlers<C, E>, any, E['options']>
|
||||
> = APIEndpoint<C, EndpointHandlers<C, E>, any, E['options']>
|
||||
|
||||
export type EndpointHandlers<
|
||||
C extends CommerceAPI,
|
||||
E extends EndpointSchemaBase
|
||||
> = {
|
||||
[H in keyof E['handlers']]: APIHandler<
|
||||
C,
|
||||
EndpointHandlers<C, E>,
|
||||
E['handlers'][H]['data'],
|
||||
E['handlers'][H]['body'],
|
||||
E['options']
|
||||
>
|
||||
}
|
||||
> = {
|
||||
[H in keyof E['handlers']]: APIHandler<
|
||||
C,
|
||||
EndpointHandlers<C, E>,
|
||||
E['handlers'][H]['data'],
|
||||
E['handlers'][H]['body'],
|
||||
E['options']
|
||||
>
|
||||
}
|
||||
|
||||
export type APIProvider = {
|
||||
config: CommerceAPIConfig
|
||||
@@ -80,7 +83,7 @@ export type CommerceAPI<P extends APIProvider = APIProvider> =
|
||||
CommerceAPICore<P> & AllOperations<P>
|
||||
|
||||
export class CommerceAPICore<P extends APIProvider = APIProvider> {
|
||||
constructor(readonly provider: P) {}
|
||||
constructor(readonly provider: P) { }
|
||||
|
||||
getConfig(userConfig: Partial<P['config']> = {}): P['config'] {
|
||||
return Object.entries(userConfig).reduce(
|
||||
@@ -139,15 +142,15 @@ export function getEndpoint<
|
||||
|
||||
export const createEndpoint =
|
||||
<API extends GetAPISchema<any, any>>(endpoint: API['endpoint']) =>
|
||||
<P extends APIProvider>(
|
||||
commerce: CommerceAPI<P>,
|
||||
context?: Partial<API['endpoint']> & {
|
||||
config?: P['config']
|
||||
options?: API['schema']['endpoint']['options']
|
||||
<P extends APIProvider>(
|
||||
commerce: CommerceAPI<P>,
|
||||
context?: Partial<API['endpoint']> & {
|
||||
config?: P['config']
|
||||
options?: API['schema']['endpoint']['options']
|
||||
}
|
||||
): NextApiHandler => {
|
||||
return getEndpoint(commerce, { ...endpoint, ...context })
|
||||
}
|
||||
): NextApiHandler => {
|
||||
return getEndpoint(commerce, { ...endpoint, ...context })
|
||||
}
|
||||
|
||||
export interface CommerceAPIConfig {
|
||||
locale?: string
|
||||
@@ -167,11 +170,11 @@ export interface CommerceAPIConfig {
|
||||
export type GraphQLFetcher<
|
||||
Data extends GraphQLFetcherResult = GraphQLFetcherResult,
|
||||
Variables = any
|
||||
> = (
|
||||
query: string,
|
||||
queryData?: CommerceAPIFetchOptions<Variables>,
|
||||
fetchOptions?: RequestInit
|
||||
) => Promise<Data>
|
||||
> = (
|
||||
query: string,
|
||||
queryData?: CommerceAPIFetchOptions<Variables>,
|
||||
fetchOptions?: RequestInit
|
||||
) => Promise<Data>
|
||||
|
||||
export interface GraphQLFetcherResult<Data = any> {
|
||||
data: Data
|
||||
|
@@ -16,6 +16,7 @@ import type {
|
||||
Login,
|
||||
Logout,
|
||||
Checkout,
|
||||
Subscriptions
|
||||
} from '@commerce/types'
|
||||
|
||||
import type { Fetcher, SWRHook, MutationHook } from './utils/types'
|
||||
@@ -62,6 +63,9 @@ export type Provider = CommerceConfig & {
|
||||
useLogin?: MutationHook<Login.LoginHook>
|
||||
useLogout?: MutationHook<Logout.LogoutHook>
|
||||
}
|
||||
subscriptions?: {
|
||||
useSubscriptions?: MutationHook<Subscriptions.SubscriptionsHook>
|
||||
}
|
||||
}
|
||||
|
||||
export type CommerceConfig = {
|
||||
|
21
framework/commerce/subscriptions/use-subscribe.tsx
Normal file
21
framework/commerce/subscriptions/use-subscribe.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { useHook, useMutationHook } from '../utils/use-hook'
|
||||
import { mutationFetcher } from '../utils/default-fetcher'
|
||||
import type { MutationHook, HookFetcherFn } from '../utils/types'
|
||||
import type { SubscriptionsHook } from '../types/subscriptions'
|
||||
import type { Provider } from '..'
|
||||
|
||||
export type UseSubscribe<
|
||||
H extends MutationHook<any> = MutationHook<any>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<any> = mutationFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider.subscriptions?.useSubscriptions!
|
||||
|
||||
const useSubscribe: any = (...args: any) => {
|
||||
const hook = useHook(fn)
|
||||
return useMutationHook({ fetcher, ...hook })(...args)
|
||||
}
|
||||
|
||||
export default useSubscribe
|
||||
|
@@ -9,6 +9,7 @@ import * as Product from './product'
|
||||
import * as Signup from './signup'
|
||||
import * as Site from './site'
|
||||
import * as Wishlist from './wishlist'
|
||||
import * as Subscriptions from './subscriptions'
|
||||
|
||||
export type {
|
||||
Cart,
|
||||
@@ -22,4 +23,5 @@ export type {
|
||||
Signup,
|
||||
Site,
|
||||
Wishlist,
|
||||
Subscriptions
|
||||
}
|
||||
|
35
framework/commerce/types/subscriptions.ts
Normal file
35
framework/commerce/types/subscriptions.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
export type SubscriptionsBody = {
|
||||
email: string
|
||||
firstName: string
|
||||
lastName: string
|
||||
source: string
|
||||
}
|
||||
|
||||
export type SubscriptionsTypes = {
|
||||
body: SubscriptionsBody
|
||||
}
|
||||
|
||||
// export type SubscriptionsHook<T extends SubscriptionsTypes = SubscriptionsTypes> = {
|
||||
// data: null
|
||||
// body: T['body']
|
||||
// actionInput: T['body']
|
||||
// fetcherInput: T['body']
|
||||
// }
|
||||
|
||||
export type SubscriptionsHook<T extends SubscriptionsTypes = SubscriptionsTypes> = {
|
||||
data: any
|
||||
input?: any
|
||||
fetcherInput: any
|
||||
body: { item: any }
|
||||
actionInput: any
|
||||
}
|
||||
|
||||
export type SubscriptionsSchema<T extends SubscriptionsTypes = SubscriptionsTypes> = {
|
||||
endpoint: {
|
||||
options: {}
|
||||
handlers: {
|
||||
subscriptions: SubscriptionsHook<T>
|
||||
}
|
||||
}
|
||||
}
|
4
pages/api/subscribe.ts
Normal file
4
pages/api/subscribe.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import subscriptionsApi from '@framework/api/endpoints/subscriptions'
|
||||
import commerce from '@lib/api/commerce'
|
||||
|
||||
export default subscriptionsApi(commerce)
|
Reference in New Issue
Block a user