wip: Add subscription endpoint for BC

This commit is contained in:
Florian-crg 2021-09-29 13:58:55 +02:00
parent f9644fecef
commit 132012cc8d
13 changed files with 233 additions and 3 deletions

View File

@ -8,6 +8,7 @@ import { Github, Vercel } from '@components/icons'
import { Logo, Container } from '@components/ui'
import { I18nWidget } from '@components/common'
import s from './Footer.module.css'
import Subscribe from '../Subscribe'
interface Props {
className?: string
@ -64,6 +65,7 @@ const Footer: FC<Props> = ({ className, pages }) => {
</a>
<I18nWidget />
</div>
<Subscribe />
</div>
</div>
<div className="pt-6 pb-10 flex flex-col md:flex-row justify-between items-center space-y-4 text-accent-6 text-sm">

View File

@ -0,0 +1,3 @@
.root {
@apply relative text-sm bg-accent-0 text-base w-full transition-colors duration-150 border border-accent-2;
}

View File

@ -0,0 +1,68 @@
import { FC, useCallback, useEffect, useState } from 'react'
import cn from 'classnames'
import s from './Subscribe.module.css'
import { Button } from '@components/ui'
import { validate } from 'email-validator'
import useSubscribe from '@framework/subscriptions/use-subscribe'
interface Props {
className?: string
}
const Subscribe: FC<Props> = ({ className }) => {
const [email, setEmail] = useState('')
const [dirty, setDirty] = useState(false)
const [disabled, setDisabled] = useState(false)
const [loading, setLoading] = useState(false)
const subscribe = useSubscribe()
const handleValidation = useCallback(() => {
// Unable to send form unless fields are valid.
if (dirty) {
setDisabled(!validate(email))
}
}, [email, dirty])
useEffect(() => {
handleValidation()
}, [handleValidation])
const handleSubscribe = async (e: React.SyntheticEvent<EventTarget>) => {
e.preventDefault()
if (!dirty && !disabled) {
setDirty(true)
handleValidation()
}
setLoading(true)
try {
await subscribe({ email })
setLoading(false)
} catch (error) {
console.error(error);
setLoading(false)
setDisabled(false)
}
}
return (
<form onSubmit={handleSubscribe} className={cn(s.root, className)}>
<label className="hidden">
Subscribe
</label>
<input
className={s.input}
placeholder="Your email"
/>
<Button
variant="naked"
type="submit"
loading={loading}
disabled={disabled}
>I subscribe </Button>
</form>
)
}
export default Subscribe

View File

@ -0,0 +1 @@
export { default } from './Subscribe'

View File

@ -0,0 +1,18 @@
import { GetAPISchema, createEndpoint } from '@commerce/api'
import subscriptionsEndpoint from '@commerce/api/endpoints/subscribe'
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

View File

@ -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

View File

@ -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>

View File

@ -0,0 +1 @@
export { default as useSubscribe } from './use-subscribe'

View File

@ -0,0 +1,38 @@
import { useCallback } from 'react'
// import type { MutationHook } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors'
import useSubscriptions from '@commerce/subscriptions/use-subscriptions'
// import type { SubscriptionsHook } from '../types/subscriptions'
export default useSubscriptions as any
export const handler: any = {
fetchOptions: {
url: '/api/subscribe',
method: 'POST',
},
async fetcher({ input: { email, firstName, lastName, source }, options, fetch }: any) {
if (!(email)) {
throw new CommerceError({
message:
'An email is required to subscribe',
})
}
return fetch({
...options,
body: { email, firstName, lastName, source },
})
},
useHook: ({ fetch }: any) => () => {
return useCallback(
async function subscribe(input) {
const data = await fetch({ input })
return data
},
[fetch]
)
},
}

View 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,
any
>['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

View File

@ -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<any>
}
}
export type CommerceConfig = {

View File

@ -0,0 +1,21 @@
import { useHook, useMutationHook } from '../utils/use-hook'
import { mutationFetcher } from '../utils/default-fetcher'
import type { HookFetcherFn, MutationHook } 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

4
pages/api/subscribe.ts Normal file
View File

@ -0,0 +1,4 @@
import subscriptionsApi from '@framework/api/endpoints/subscriptions'
import commerce from '@lib/api/commerce'
export default subscriptionsApi(commerce)