From 3e5f54da16c9f29a3b9ede65c72090dfbc04e58b Mon Sep 17 00:00:00 2001 From: Florian-crg Date: Sat, 9 Oct 2021 11:40:39 +0200 Subject: [PATCH] Add subscription endpoint for BC --- components/common/Footer/Footer.tsx | 3 +- components/common/Subscribe/index.ts | 1 + components/common/Subscribe/subscribe.jsx | 51 ++++++++++++++ components/common/index.ts | 1 + .../api/endpoints/subscriptions/index.ts | 18 +++++ .../endpoints/subscriptions/subscriptions.ts | 33 +++++++++ framework/bigcommerce/api/index.ts | 8 ++- framework/bigcommerce/provider.ts | 3 + framework/bigcommerce/subscriptions/index.ts | 1 + .../subscriptions/use-subscribe.tsx | 38 +++++++++++ framework/bigcommerce/types/subscriptions.ts | 1 + .../commerce/api/endpoints/subscriptions.ts | 35 ++++++++++ framework/commerce/api/index.ts | 67 ++++++++++--------- framework/commerce/index.tsx | 4 ++ .../commerce/subscriptions/use-subscribe.tsx | 21 ++++++ framework/commerce/types/index.ts | 2 + framework/commerce/types/subscriptions.ts | 35 ++++++++++ pages/api/subscribe.ts | 4 ++ 18 files changed, 290 insertions(+), 36 deletions(-) create mode 100644 components/common/Subscribe/index.ts create mode 100644 components/common/Subscribe/subscribe.jsx create mode 100644 framework/bigcommerce/api/endpoints/subscriptions/index.ts create mode 100644 framework/bigcommerce/api/endpoints/subscriptions/subscriptions.ts create mode 100644 framework/bigcommerce/subscriptions/index.ts create mode 100644 framework/bigcommerce/subscriptions/use-subscribe.tsx create mode 100644 framework/bigcommerce/types/subscriptions.ts create mode 100644 framework/commerce/api/endpoints/subscriptions.ts create mode 100644 framework/commerce/subscriptions/use-subscribe.tsx create mode 100644 framework/commerce/types/subscriptions.ts create mode 100644 pages/api/subscribe.ts diff --git a/components/common/Footer/Footer.tsx b/components/common/Footer/Footer.tsx index 8cfcc9666..a43cfe67b 100644 --- a/components/common/Footer/Footer.tsx +++ b/components/common/Footer/Footer.tsx @@ -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 = ({ className, pages }) => {
© 2020 ACME, Inc. All rights reserved.
+
Created by { + 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 ( +
+
+ + + {/* */} +
+
+ ) +} + +export default Subscribe diff --git a/components/common/index.ts b/components/common/index.ts index 98dd3394b..8a021bcb4 100644 --- a/components/common/index.ts +++ b/components/common/index.ts @@ -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' diff --git a/framework/bigcommerce/api/endpoints/subscriptions/index.ts b/framework/bigcommerce/api/endpoints/subscriptions/index.ts new file mode 100644 index 000000000..520efb241 --- /dev/null +++ b/framework/bigcommerce/api/endpoints/subscriptions/index.ts @@ -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 + +export type SubscriptionsEndpoint = SubscriptionsAPI['endpoint'] + +export const handlers: SubscriptionsEndpoint['handlers'] = { subscriptions } + +const subscriptionsApi = createEndpoint({ + handler: subscriptionsEndpoint, + handlers, +}) + +export default subscriptionsApi diff --git a/framework/bigcommerce/api/endpoints/subscriptions/subscriptions.ts b/framework/bigcommerce/api/endpoints/subscriptions/subscriptions.ts new file mode 100644 index 000000000..415896926 --- /dev/null +++ b/framework/bigcommerce/api/endpoints/subscriptions/subscriptions.ts @@ -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 diff --git a/framework/bigcommerce/api/index.ts b/framework/bigcommerce/api/index.ts index 9dbe400f9..bc62318be 100644 --- a/framework/bigcommerce/api/index.ts +++ b/framework/bigcommerce/api/index.ts @@ -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(endpoint: string, options?: RequestInit): Promise } @@ -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

= CommerceAPI

diff --git a/framework/bigcommerce/provider.ts b/framework/bigcommerce/provider.ts index 196855438..e93394062 100644 --- a/framework/bigcommerce/provider.ts +++ b/framework/bigcommerce/provider.ts @@ -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 diff --git a/framework/bigcommerce/subscriptions/index.ts b/framework/bigcommerce/subscriptions/index.ts new file mode 100644 index 000000000..14c9d2232 --- /dev/null +++ b/framework/bigcommerce/subscriptions/index.ts @@ -0,0 +1 @@ +export { default as useSubscribe } from './use-subscribe' diff --git a/framework/bigcommerce/subscriptions/use-subscribe.tsx b/framework/bigcommerce/subscriptions/use-subscribe.tsx new file mode 100644 index 000000000..b4c7b52c7 --- /dev/null +++ b/framework/bigcommerce/subscriptions/use-subscribe.tsx @@ -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 + +export const handler: MutationHook = { + 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] + ) + }, +} \ No newline at end of file diff --git a/framework/bigcommerce/types/subscriptions.ts b/framework/bigcommerce/types/subscriptions.ts new file mode 100644 index 000000000..bdbdf5bef --- /dev/null +++ b/framework/bigcommerce/types/subscriptions.ts @@ -0,0 +1 @@ +export * from '@commerce/types/subscriptions' diff --git a/framework/commerce/api/endpoints/subscriptions.ts b/framework/commerce/api/endpoints/subscriptions.ts new file mode 100644 index 000000000..dfcdb017f --- /dev/null +++ b/framework/commerce/api/endpoints/subscriptions.ts @@ -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 diff --git a/framework/commerce/api/index.ts b/framework/commerce/api/index.ts index 716c11ed5..53c9853d7 100644 --- a/framework/commerce/api/index.ts +++ b/framework/commerce/api/index.ts @@ -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, S extends APISchemas = APISchemas -> = { - schema: S - endpoint: EndpointContext -} + > = { + schema: S + endpoint: EndpointContext + } export type EndpointContext< C extends CommerceAPI, E extends EndpointSchemaBase -> = { - handler: Endpoint - handlers: EndpointHandlers -} + > = { + handler: Endpoint + handlers: EndpointHandlers + } export type EndpointSchemaBase = { options: {} @@ -56,20 +59,20 @@ export type EndpointSchemaBase = { export type Endpoint< C extends CommerceAPI, E extends EndpointSchemaBase -> = APIEndpoint, any, E['options']> + > = APIEndpoint, any, E['options']> export type EndpointHandlers< C extends CommerceAPI, E extends EndpointSchemaBase -> = { - [H in keyof E['handlers']]: APIHandler< - C, - EndpointHandlers, - E['handlers'][H]['data'], - E['handlers'][H]['body'], - E['options'] - > -} + > = { + [H in keyof E['handlers']]: APIHandler< + C, + EndpointHandlers, + E['handlers'][H]['data'], + E['handlers'][H]['body'], + E['options'] + > + } export type APIProvider = { config: CommerceAPIConfig @@ -80,7 +83,7 @@ export type CommerceAPI

= CommerceAPICore

& AllOperations

export class CommerceAPICore

{ - constructor(readonly provider: P) {} + constructor(readonly provider: P) { } getConfig(userConfig: Partial = {}): P['config'] { return Object.entries(userConfig).reduce( @@ -139,15 +142,15 @@ export function getEndpoint< export const createEndpoint = >(endpoint: API['endpoint']) => -

( - commerce: CommerceAPI

, - context?: Partial & { - config?: P['config'] - options?: API['schema']['endpoint']['options'] +

( + commerce: CommerceAPI

, + context?: Partial & { + 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, - fetchOptions?: RequestInit -) => Promise + > = ( + query: string, + queryData?: CommerceAPIFetchOptions, + fetchOptions?: RequestInit + ) => Promise export interface GraphQLFetcherResult { data: Data diff --git a/framework/commerce/index.tsx b/framework/commerce/index.tsx index eaa878a9e..6ba428386 100644 --- a/framework/commerce/index.tsx +++ b/framework/commerce/index.tsx @@ -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 useLogout?: MutationHook } + subscriptions?: { + useSubscriptions?: MutationHook + } } export type CommerceConfig = { diff --git a/framework/commerce/subscriptions/use-subscribe.tsx b/framework/commerce/subscriptions/use-subscribe.tsx new file mode 100644 index 000000000..06ae724fa --- /dev/null +++ b/framework/commerce/subscriptions/use-subscribe.tsx @@ -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 = MutationHook + > = ReturnType + +export const fetcher: HookFetcherFn = 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 + diff --git a/framework/commerce/types/index.ts b/framework/commerce/types/index.ts index 7ab0b7f64..36408c141 100644 --- a/framework/commerce/types/index.ts +++ b/framework/commerce/types/index.ts @@ -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 } diff --git a/framework/commerce/types/subscriptions.ts b/framework/commerce/types/subscriptions.ts new file mode 100644 index 000000000..04e41496e --- /dev/null +++ b/framework/commerce/types/subscriptions.ts @@ -0,0 +1,35 @@ + +export type SubscriptionsBody = { + email: string + firstName: string + lastName: string + source: string +} + +export type SubscriptionsTypes = { + body: SubscriptionsBody +} + +// export type SubscriptionsHook = { +// data: null +// body: T['body'] +// actionInput: T['body'] +// fetcherInput: T['body'] +// } + +export type SubscriptionsHook = { + data: any + input?: any + fetcherInput: any + body: { item: any } + actionInput: any +} + +export type SubscriptionsSchema = { + endpoint: { + options: {} + handlers: { + subscriptions: SubscriptionsHook + } + } +} diff --git a/pages/api/subscribe.ts b/pages/api/subscribe.ts new file mode 100644 index 000000000..8cf6fe947 --- /dev/null +++ b/pages/api/subscribe.ts @@ -0,0 +1,4 @@ +import subscriptionsApi from '@framework/api/endpoints/subscriptions' +import commerce from '@lib/api/commerce' + +export default subscriptionsApi(commerce)