diff --git a/src/embedded/components/IterableEmbeddedNotification.tsx b/src/embedded/components/IterableEmbeddedNotification.tsx deleted file mode 100644 index 686ea01e6..000000000 --- a/src/embedded/components/IterableEmbeddedNotification.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { View, Text } from 'react-native'; - -import type { IterableEmbeddedComponentProps } from '../types/IterableEmbeddedComponentProps'; - -export const IterableEmbeddedNotification = ({ - config, - message, - onButtonClick = () => {}, -}: IterableEmbeddedComponentProps) => { - console.log(`🚀 > IterableEmbeddedNotification > config:`, config); - console.log(`🚀 > IterableEmbeddedNotification > message:`, message); - console.log( - `🚀 > IterableEmbeddedNotification > onButtonClick:`, - onButtonClick - ); - - return ( - - IterableEmbeddedNotification - - ); -}; diff --git a/src/embedded/components/IterableEmbeddedNotification/IterableEmbeddedNotification.styles.ts b/src/embedded/components/IterableEmbeddedNotification/IterableEmbeddedNotification.styles.ts new file mode 100644 index 000000000..923df66fc --- /dev/null +++ b/src/embedded/components/IterableEmbeddedNotification/IterableEmbeddedNotification.styles.ts @@ -0,0 +1,54 @@ +import { StyleSheet } from 'react-native'; + +export const styles = StyleSheet.create({ + body: { + alignSelf: 'stretch', + fontSize: 14, + fontWeight: '400', + lineHeight: 20, + }, + bodyContainer: { + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + flexShrink: 1, + gap: 4, + width: '100%', + }, + button: { + borderRadius: 32, + gap: 8, + paddingHorizontal: 12, + paddingVertical: 8, + }, + buttonContainer: { + alignItems: 'flex-start', + alignSelf: 'stretch', + display: 'flex', + flexDirection: 'row', + gap: 12, + width: '100%', + }, + buttonText: { + fontSize: 14, + fontWeight: '700', + lineHeight: 20, + }, + container: { + alignItems: 'flex-start', + borderStyle: 'solid', + boxShadow: + '0 1px 1px 0 rgba(0, 0, 0, 0.06), 0 0 2px 0 rgba(0, 0, 0, 0.06), 0 0 1px 0 rgba(0, 0, 0, 0.08)', + display: 'flex', + flexDirection: 'column', + gap: 8, + justifyContent: 'center', + padding: 16, + width: '100%', + }, + title: { + fontSize: 16, + fontWeight: '700', + lineHeight: 24, + }, +}); diff --git a/src/embedded/components/IterableEmbeddedNotification/IterableEmbeddedNotification.tsx b/src/embedded/components/IterableEmbeddedNotification/IterableEmbeddedNotification.tsx new file mode 100644 index 000000000..f0909cfc5 --- /dev/null +++ b/src/embedded/components/IterableEmbeddedNotification/IterableEmbeddedNotification.tsx @@ -0,0 +1,96 @@ +import { + Text, + TouchableOpacity, + View, + type TextStyle, + type ViewStyle, + Pressable, +} from 'react-native'; + +import { IterableEmbeddedViewType } from '../../enums/IterableEmbeddedViewType'; +import { useEmbeddedView } from '../../hooks/useEmbeddedView'; +import type { IterableEmbeddedComponentProps } from '../../types/IterableEmbeddedComponentProps'; +import { styles } from './IterableEmbeddedNotification.styles'; + +export const IterableEmbeddedNotification = ({ + config, + message, + onButtonClick = () => {}, + onMessageClick = () => {}, +}: IterableEmbeddedComponentProps) => { + const { parsedStyles, handleButtonClick, handleMessageClick } = + useEmbeddedView(IterableEmbeddedViewType.Notification, { + message, + config, + onButtonClick, + onMessageClick, + }); + + const buttons = message.elements?.buttons ?? []; + + return ( + handleMessageClick()}> + + {} + + + {message.elements?.title} + + + {message.elements?.body} + + + {buttons.length > 0 && ( + + {buttons.map((button, index) => { + const backgroundColor = + index === 0 + ? parsedStyles.primaryBtnBackgroundColor + : parsedStyles.secondaryBtnBackgroundColor; + const textColor = + index === 0 + ? parsedStyles.primaryBtnTextColor + : parsedStyles.secondaryBtnTextColor; + return ( + handleButtonClick(button)} + key={button.id} + > + + {button.title} + + + ); + })} + + )} + + + ); +}; diff --git a/src/embedded/components/IterableEmbeddedNotification/index.ts b/src/embedded/components/IterableEmbeddedNotification/index.ts new file mode 100644 index 000000000..3a25fd8ee --- /dev/null +++ b/src/embedded/components/IterableEmbeddedNotification/index.ts @@ -0,0 +1,2 @@ +export * from './IterableEmbeddedNotification'; +export { IterableEmbeddedNotification as default } from './IterableEmbeddedNotification'; diff --git a/src/embedded/hooks/index.ts b/src/embedded/hooks/index.ts new file mode 100644 index 000000000..cbca753d9 --- /dev/null +++ b/src/embedded/hooks/index.ts @@ -0,0 +1 @@ +export * from './useEmbeddedView'; diff --git a/src/embedded/hooks/useEmbeddedView/embeddedViewDefaults.ts b/src/embedded/hooks/useEmbeddedView/embeddedViewDefaults.ts new file mode 100644 index 000000000..f20879388 --- /dev/null +++ b/src/embedded/hooks/useEmbeddedView/embeddedViewDefaults.ts @@ -0,0 +1,85 @@ +export const embeddedBackgroundColors = { + notification: '#ffffff', + card: '#ffffff', + banner: '#ffffff', +}; + +export const embeddedBorderColors = { + notification: '#E0DEDF', + card: '#E0DEDF', + banner: '#E0DEDF', +}; + +export const embeddedPrimaryBtnBackgroundColors = { + notification: '#6A266D', + card: 'transparent', + banner: '#6A266D', +}; + +export const embeddedPrimaryBtnTextColors = { + notification: '#ffffff', + card: '#79347F', + banner: '#ffffff', +}; + +export const embeddedSecondaryBtnBackgroundColors = { + notification: 'transparent', + card: 'transparent', + banner: 'transparent', +}; + +export const embeddedSecondaryBtnTextColors = { + notification: '#79347F', + card: '#79347F', + banner: '#79347F', +}; + +export const embeddedTitleTextColors = { + notification: '#3D3A3B', + card: '#3D3A3B', + banner: '#3D3A3B', +}; + +export const embeddedBodyTextColors = { + notification: '#787174', + card: '#787174', + banner: '#787174', +}; + +export const embeddedBorderRadius = { + notification: 8, + card: 6, + banner: 8, +}; + +export const embeddedBorderWidth = { + notification: 1, + card: 1, + banner: 1, +}; + +export const embeddedMediaImageBorderColors = { + notification: '#E0DEDF', + card: '#E0DEDF', + banner: '#E0DEDF', +}; + +export const embeddedMediaImageBackgroundColors = { + notification: '#F5F4F4', + card: '#F5F4F4', + banner: '#F5F4F4', +}; + +export const embeddedStyles = { + backgroundColor: embeddedBackgroundColors, + bodyText: embeddedBodyTextColors, + borderColor: embeddedBorderColors, + borderCornerRadius: embeddedBorderRadius, + borderWidth: embeddedBorderWidth, + mediaImageBorder: embeddedMediaImageBorderColors, + primaryBtnBackgroundColor: embeddedPrimaryBtnBackgroundColors, + primaryBtnTextColor: embeddedPrimaryBtnTextColors, + secondaryBtnBackground: embeddedSecondaryBtnBackgroundColors, + secondaryBtnTextColor: embeddedSecondaryBtnTextColors, + titleText: embeddedTitleTextColors, +}; diff --git a/src/embedded/hooks/useEmbeddedView/getMedia.ts b/src/embedded/hooks/useEmbeddedView/getMedia.ts new file mode 100644 index 000000000..de0dcb666 --- /dev/null +++ b/src/embedded/hooks/useEmbeddedView/getMedia.ts @@ -0,0 +1,29 @@ +import type { IterableEmbeddedMessage } from '../../types/IterableEmbeddedMessage'; +import { IterableEmbeddedViewType } from '../../enums'; + +/** + * This function is used to get the media to render for a given embedded view + * type and message. + * + * @param viewType - The type of view to render. + * @param message - The message to render. + * @returns The media to render. + * + * @example + * const media = getMedia(IterableEmbeddedViewType.Notification, message); + * console.log(media); + */ +export const getMedia = ( + /** The type of view to render. */ + viewType: IterableEmbeddedViewType, + /** The message to render. */ + message: IterableEmbeddedMessage +) => { + if (viewType === IterableEmbeddedViewType.Notification) { + return { url: null, caption: null, shouldShow: false }; + } + const url = message.elements?.mediaUrl ?? null; + const caption = message.elements?.mediaUrlCaption ?? null; + const shouldShow = !!url && url.length > 0; + return { url, caption, shouldShow }; +}; diff --git a/src/embedded/hooks/useEmbeddedView/getStyles.ts b/src/embedded/hooks/useEmbeddedView/getStyles.ts new file mode 100644 index 000000000..102367de5 --- /dev/null +++ b/src/embedded/hooks/useEmbeddedView/getStyles.ts @@ -0,0 +1,81 @@ +import type { IterableEmbeddedViewConfig } from '../../types/IterableEmbeddedViewConfig'; +import { embeddedStyles } from './embeddedViewDefaults'; +import { IterableEmbeddedViewType } from '../../enums'; + +/** + * Get the default style for the embedded view type. + * + * @param viewType - The type of view to render. + * @param colors - The colors to use for the default style. + * @returns The default style. + */ +const getDefaultStyle = ( + viewType: IterableEmbeddedViewType, + colors: { + banner: number | string; + card: number | string; + notification: number | string; + } +) => { + switch (viewType) { + case IterableEmbeddedViewType.Notification: + return colors.notification; + case IterableEmbeddedViewType.Card: + return colors.card; + default: + return colors.banner; + } +}; + +/** + * Get the style for the embedded view type. + * + * If a style is provided in the config, it will take precedence over the default style. + * + * @param viewType - The type of view to render. + * @param c - The config to use for the styles. + * @returns The styles. + * + * @example + * const styles = getStyles(IterableEmbeddedViewType.Notification, { + * backgroundColor: '#000000', + * borderColor: '#000000', + * borderWidth: 1, + * borderCornerRadius: 10, + * primaryBtnBackgroundColor: '#000000', + * primaryBtnTextColor: '#000000', + * }); + */ +export const getStyles = ( + viewType: IterableEmbeddedViewType, + c?: IterableEmbeddedViewConfig | null +) => { + return { + backgroundColor: + c?.backgroundColor ?? + getDefaultStyle(viewType, embeddedStyles.backgroundColor), + borderColor: + c?.borderColor ?? getDefaultStyle(viewType, embeddedStyles.borderColor), + borderWidth: + c?.borderWidth ?? getDefaultStyle(viewType, embeddedStyles.borderWidth), + borderCornerRadius: + c?.borderCornerRadius ?? + getDefaultStyle(viewType, embeddedStyles.borderCornerRadius), + primaryBtnBackgroundColor: + c?.primaryBtnBackgroundColor ?? + getDefaultStyle(viewType, embeddedStyles.primaryBtnBackgroundColor), + primaryBtnTextColor: + c?.primaryBtnTextColor ?? + getDefaultStyle(viewType, embeddedStyles.primaryBtnTextColor), + secondaryBtnBackgroundColor: + c?.secondaryBtnBackgroundColor ?? + getDefaultStyle(viewType, embeddedStyles.secondaryBtnBackground), + secondaryBtnTextColor: + c?.secondaryBtnTextColor ?? + getDefaultStyle(viewType, embeddedStyles.secondaryBtnTextColor), + titleTextColor: + c?.titleTextColor ?? getDefaultStyle(viewType, embeddedStyles.titleText), + bodyTextColor: + c?.bodyTextColor ?? getDefaultStyle(viewType, embeddedStyles.bodyText), + }; +}; diff --git a/src/embedded/hooks/useEmbeddedView/index.ts b/src/embedded/hooks/useEmbeddedView/index.ts new file mode 100644 index 000000000..bf1a77d44 --- /dev/null +++ b/src/embedded/hooks/useEmbeddedView/index.ts @@ -0,0 +1,2 @@ +export * from './useEmbeddedView'; +export { useEmbeddedView as default } from './useEmbeddedView'; diff --git a/src/embedded/hooks/useEmbeddedView/useEmbeddedView.ts b/src/embedded/hooks/useEmbeddedView/useEmbeddedView.ts new file mode 100644 index 000000000..cfbd9fc4f --- /dev/null +++ b/src/embedded/hooks/useEmbeddedView/useEmbeddedView.ts @@ -0,0 +1,74 @@ +import { useCallback, useMemo } from 'react'; +import { Iterable } from '../../../core/classes/Iterable'; +import { IterableEmbeddedViewType } from '../../enums'; +import type { IterableEmbeddedComponentProps } from '../../types/IterableEmbeddedComponentProps'; +import type { IterableEmbeddedMessageElementsButton } from '../../types/IterableEmbeddedMessageElementsButton'; +import { getMedia } from './getMedia'; +import { getStyles } from './getStyles'; + +/** + * This hook is used to manage the lifecycle of an embedded view. + * + * @param viewType - The type of view to render. + * @param props - The props for the embedded view. + * @returns The embedded view. + * + * @example + * const { handleButtonClick, handleMessageClick, media, parsedStyles } = useEmbeddedView(IterableEmbeddedViewType.Notification, { + * message, + * config, + * onButtonClick, + * onMessageClick, + * }); + * + * return ( + * + * {media.url} + * {media.caption} + * {parsedStyles.backgroundColor} + * + * ); + */ +export const useEmbeddedView = ( + /** The type of view to render. */ + viewType: IterableEmbeddedViewType, + /** The props for the embedded view. */ + { + message, + config, + onButtonClick = () => {}, + onMessageClick = () => {}, + }: IterableEmbeddedComponentProps +) => { + const parsedStyles = useMemo(() => { + return getStyles(viewType, config); + }, [viewType, config]); + const media = useMemo(() => { + return getMedia(viewType, message); + }, [viewType, message]); + + + const handleButtonClick = useCallback( + (button: IterableEmbeddedMessageElementsButton) => { + onButtonClick(button); + Iterable.embeddedManager.handleClick(message, button.id, button.action); + }, + [onButtonClick, message] + ); + + const handleMessageClick = useCallback(() => { + onMessageClick(); + Iterable.embeddedManager.handleClick( + message, + null, + message.elements?.defaultAction + ); + }, [message, onMessageClick]); + + return { + handleButtonClick, + handleMessageClick, + media, + parsedStyles, + }; +}; diff --git a/src/embedded/index.ts b/src/embedded/index.ts index 967e49dbe..107bb59fe 100644 --- a/src/embedded/index.ts +++ b/src/embedded/index.ts @@ -1,4 +1,6 @@ export * from './classes'; export * from './components'; export * from './enums'; +export * from './hooks'; export * from './types'; + diff --git a/src/embedded/types/IterableEmbeddedComponentProps.ts b/src/embedded/types/IterableEmbeddedComponentProps.ts index 9f2b17670..f59e2772e 100644 --- a/src/embedded/types/IterableEmbeddedComponentProps.ts +++ b/src/embedded/types/IterableEmbeddedComponentProps.ts @@ -3,7 +3,12 @@ import type { IterableEmbeddedMessageElementsButton } from './IterableEmbeddedMe import type { IterableEmbeddedViewConfig } from './IterableEmbeddedViewConfig'; export interface IterableEmbeddedComponentProps { + /** The message to render. */ message: IterableEmbeddedMessage; + /** The config for the embedded view. */ config?: IterableEmbeddedViewConfig | null; + /** The function to call when a button is clicked. */ onButtonClick?: (button: IterableEmbeddedMessageElementsButton) => void; + /** The function to call when the message is clicked. */ + onMessageClick?: () => void; }