Skip to content

Commit

Permalink
React/Core: added Whatsapp Catalog component to Botonic (#2954)
Browse files Browse the repository at this point in the history
<!-- _Set as [Draft
PR](https://github.blog/2019-02-14-introducing-draft-pull-requests/) if
it's not ready to be merged_.

[PR best practices
Reference](https://blog.codeminer42.com/on-writing-a-great-pull-request-37c60ce6f31d/)
-->

## Description
Added [Whatsapp
Catalog](https://developers.facebook.com/docs/whatsapp/cloud-api/guides/sell-products-and-services/share-products/)
component to Botonic
<!--
- Must be clear and concise (2-3 lines).
- Don't make reviewers think. The description should explain what has
been implemented or what it's used for. If a pull request is not
descriptive, people will be lazy or not willing to spend much time on
it.
- Be explicit with the names (don't abbreviate and don't use acronyms
that can lead to misleading understanding).
- If you consider it appropriate, include the steps to try the new
features.
-->

## Context
Component used to send the Whatsapp Catalog linked to the bot's phone
number.
<!--
- What problem is trying to solve this pull request?
- What are the reasons or business goals of this implementation?
- Can I provide visual resources or links to understand better the
situation?
-->

## Approach taken / Explain the design

<!--
- Explain what the code does.
- If it's a complex solution, try to provide a sketch.
-->

## To document / Usage example

```
  render() {
    return (
      <WhatsappCatalog
        body='Catalog sent from the bot'
        thumbnailProductId='p6ng7fghmh'
        footer="Catalog footer!!!"
      />
    )
  }
```
![Captura de pantalla de 2025-01-14
09-40-59](https://github.com/user-attachments/assets/be565dcb-5eee-41fc-896a-c0cf11b55024)

<!--
- How this is used?
- If possible, provide a snippet of code with a usage example.
-->

## Testing

The pull request...

- has unit tests
  • Loading branch information
AlbertGom authored Feb 7, 2025
1 parent d422e9b commit 00f6628
Show file tree
Hide file tree
Showing 20 changed files with 769 additions and 0 deletions.
57 changes: 57 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions packages/botonic-core/src/models/legacy-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ export enum INPUT {
WHATSAPP_BUTTON_LIST = 'whatsapp-button-list',
WHATSAPP_CTA_URL_BUTTON = 'whatsapp-cta-url-button',
EVENT_AGENT_MESSAGE_CREATED = 'case_event_agent_message_created',
WHATSAPP_CATALOG = 'whatsapp-catalog',
WHATSAPP_PRODUCT = 'whatsapp-product',
WHATSAPP_PRODUCT_LIST = 'whatsapp-product-list',
WHATSAPP_PRODUCT_CAROUSEL = 'whatsapp-product-carousel',
WHATSAPP_MEDIA_CAROUSEL = 'whatsapp-media-carousel',
WHATSAPP_ORDER = 'whatsapp_order',
}

export interface Locales {
Expand Down Expand Up @@ -114,6 +120,12 @@ export type InputType =
| INPUT.WHATSAPP_BUTTON_LIST
| INPUT.WHATSAPP_CTA_URL_BUTTON
| INPUT.EVENT_AGENT_MESSAGE_CREATED
| INPUT.WHATSAPP_CATALOG
| INPUT.WHATSAPP_PRODUCT
| INPUT.WHATSAPP_PRODUCT_LIST
| INPUT.WHATSAPP_PRODUCT_CAROUSEL
| INPUT.WHATSAPP_MEDIA_CAROUSEL
| INPUT.WHATSAPP_ORDER

export interface IntentResult {
intent: string
Expand Down Expand Up @@ -157,6 +169,15 @@ export interface Input extends Partial<NluResult> {
type: string
data: string
}
catalog_id?: string
product_items?: ProductItem[]
}

interface ProductItem {
product_retailer_id: string
quantity: number
item_price: number
currency: string
}

export interface Campaign {
Expand Down
16 changes: 16 additions & 0 deletions packages/botonic-react/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,24 @@ export {
WhatsappButtonListRowProps,
WhatsappButtonListSectionProps,
} from './whatsapp-button-list'
export { WhatsappCatalog, WhatsappCatalogProps } from './whatsapp-catalog'
export {
WhatsappCTAUrlButton,
WhatsappCTAUrlButtonProps,
} from './whatsapp-cta-url-button'
export {
WhatsappMediaCarousel,
WhatsappMediaCarouselProps,
} from './whatsapp-media-carousel'
export { WhatsappProduct } from './whatsapp-product'
export {
WhatsappProductCarousel,
WhatsappProductCarouselProps,
} from './whatsapp-product-carousel'
export {
ProductItem,
WhatsappProductList,
WhatsappProductListProps,
WhatsappProductListSection,
} from './whatsapp-product-list'
export { WhatsappTemplate } from './whatsapp-template'
42 changes: 42 additions & 0 deletions packages/botonic-react/src/components/whatsapp-catalog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { INPUT } from '@botonic/core'
import React from 'react'

import { renderComponent } from '../util/react'
import { Message } from './message'

export interface WhatsappCatalogProps {
body: string
footer?: string
thumbnailProductId?: string
}

const serialize = (message: string) => {
return { text: message }
}

export const WhatsappCatalog = (props: WhatsappCatalogProps) => {
const renderBrowser = () => {
// Return a dummy message for browser
const message = `WhatsApp Catalog would be sent to the user.`
return (
<Message json={serialize(message)} {...props} type={INPUT.TEXT}>
{message}
</Message>
)
}

const renderNode = () => {
return (
// @ts-ignore Property 'message' does not exist on type 'JSX.IntrinsicElements'.
<message
{...props}
body={props.body}
footer={props.footer}
thumbnailProductId={props.thumbnailProductId}
type={INPUT.WHATSAPP_CATALOG}
/>
)
}

return renderComponent({ renderBrowser, renderNode })
}
104 changes: 104 additions & 0 deletions packages/botonic-react/src/components/whatsapp-media-carousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { INPUT } from '@botonic/core'
import React from 'react'

import { toSnakeCaseKeys } from '../util/functional'
import { renderComponent } from '../util/react'
import { Message } from './message'

type Parameters = TextParameter | CurrencyParameter | DateTimeParameter

interface TextParameter {
type: 'text'
text: string
}

interface CurrencyParameter {
type: 'currency'
currency: {
fallbackValue: string
code: string
amount1000: number
}
}

interface DateTimeParameter {
type: 'date_time'
dateTime: { fallbackValue: string }
}

type CardButton = QuickReplyButton | UrlButton

interface Button {
type: 'quick_reply' | 'url'
buttonIndex?: number
}

interface QuickReplyButton extends Button {
payload: string
}

interface UrlButton extends Button {
urlVariable: string
}

interface Card {
fileType: 'image' | 'video'
fileId: string
cardIndex?: number
bodyParameters?: Parameters[]
buttons?: CardButton[]
extraComponents?: Record<string, any>[]
}

export interface WhatsappMediaCarouselProps {
templateName: string
templateLanguage: string
cards: Card[]
bodyParameters?: Parameters[]
}

const serialize = (message: string) => {
return { text: message }
}

export const WhatsappMediaCarousel = (props: WhatsappMediaCarouselProps) => {
const renderBrowser = () => {
// Return a dummy message for browser
const message = `WhatsApp Media Carousel would be sent to the user.`
return (
<Message json={serialize(message)} {...props} type={INPUT.TEXT}>
{message}
</Message>
)
}

const getCards = (cards: Card[]) => {
cards.forEach((card, index) => {
if (!card.cardIndex) {
card.cardIndex = index
}
card.buttons?.forEach((button, index) => {
if (!button.buttonIndex) {
button.buttonIndex = index
}
})
})
return toSnakeCaseKeys(cards)
}

const renderNode = () => {
return (
// @ts-ignore Property 'message' does not exist on type 'JSX.IntrinsicElements'.
<message
{...props}
bodyParameters={JSON.stringify(toSnakeCaseKeys(props.bodyParameters))}
cards={JSON.stringify(getCards(props.cards))}
templateName={props.templateName}
templateLanguage={props.templateLanguage}
type={INPUT.WHATSAPP_MEDIA_CAROUSEL}
/>
)
}

return renderComponent({ renderBrowser, renderNode })
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { INPUT } from '@botonic/core'
import React from 'react'

import { toSnakeCaseKeys } from '../util/functional'
import { renderComponent } from '../util/react'
import { Message } from './message'

type Parameters = TextParameter | CurrencyParameter | DateTimeParameter

interface TextParameter {
type: 'text'
text: string
}

interface CurrencyParameter {
type: 'currency'
currency: {
fallbackValue: string
code: string
amount1000: number
}
}

interface DateTimeParameter {
type: 'date_time'
dateTime: { fallbackValue: string }
}

interface Card {
productRetailerId: string
catalogId: string
cardIndex?: number
}

export interface WhatsappProductCarouselProps {
templateName: string
templateLanguage: string
cards: Card[]
bodyParameters?: Parameters[]
}

const serialize = (message: string) => {
return { text: message }
}

export const WhatsappProductCarousel = (
props: WhatsappProductCarouselProps
) => {
const renderBrowser = () => {
// Return a dummy message for browser
const message = `WhatsApp Product Carousel would be sent to the user.`
return (
<Message json={serialize(message)} {...props} type={INPUT.TEXT}>
{message}
</Message>
)
}

const getCards = (cards: Card[]) => {
cards.forEach((card, index) => {
if (!card.cardIndex) {
card.cardIndex = index
}
})
return toSnakeCaseKeys(cards)
}

const renderNode = () => {
return (
// @ts-ignore Property 'message' does not exist on type 'JSX.IntrinsicElements'.
<message
{...props}
bodyParameters={JSON.stringify(toSnakeCaseKeys(props.bodyParameters))}
cards={JSON.stringify(getCards(props.cards))}
templateName={props.templateName}
templateLanguage={props.templateLanguage}
type={INPUT.WHATSAPP_PRODUCT_CAROUSEL}
/>
)
}

return renderComponent({ renderBrowser, renderNode })
}
Loading

0 comments on commit 00f6628

Please sign in to comment.