From 1e52ecfee9de41b42d1958a3399637a290e1ca6a Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 13 Oct 2025 17:55:17 -0700 Subject: [PATCH] feat: introduce IterableEmbeddedCard component with styling and functionality for embedded messaging --- example/src/components/Embedded/Embedded.tsx | 2 +- src/core/classes/IterableApi.ts | 5 +- src/core/images/logo-grey.png | Bin 0 -> 3986 bytes .../components/IterableEmbeddedCard.tsx | 18 --- .../IterableEmbeddedCard.styles.ts | 88 ++++++++++++ .../IterableEmbeddedCard.tsx | 136 ++++++++++++++++++ .../components/IterableEmbeddedCard/index.ts | 1 + src/embedded/components/index.ts | 2 +- 8 files changed, 231 insertions(+), 21 deletions(-) create mode 100644 src/core/images/logo-grey.png delete mode 100644 src/embedded/components/IterableEmbeddedCard.tsx create mode 100644 src/embedded/components/IterableEmbeddedCard/IterableEmbeddedCard.styles.ts create mode 100644 src/embedded/components/IterableEmbeddedCard/IterableEmbeddedCard.tsx create mode 100644 src/embedded/components/IterableEmbeddedCard/index.ts diff --git a/example/src/components/Embedded/Embedded.tsx b/example/src/components/Embedded/Embedded.tsx index c603e210e..62cdc5fec 100644 --- a/example/src/components/Embedded/Embedded.tsx +++ b/example/src/components/Embedded/Embedded.tsx @@ -16,7 +16,7 @@ export const Embedded = () => { IterableEmbeddedMessage[] >([]); const [selectedViewType, setSelectedViewType] = - useState(IterableEmbeddedViewType.Banner); + useState(IterableEmbeddedViewType.Card); const syncEmbeddedMessages = useCallback(() => { Iterable.embeddedManager.syncMessages(); diff --git a/src/core/classes/IterableApi.ts b/src/core/classes/IterableApi.ts index 09d0dc44f..8aa5338b0 100644 --- a/src/core/classes/IterableApi.ts +++ b/src/core/classes/IterableApi.ts @@ -540,7 +540,10 @@ export class IterableApi { */ static startEmbeddedImpression(messageId: string, placementId: number) { IterableLogger.log('startEmbeddedImpression: ', messageId, placementId); - return RNIterableAPI.startEmbeddedImpression(messageId, placementId); + return RNIterableAPI.startEmbeddedImpression( + messageId, + Number(placementId) + ); } /** diff --git a/src/core/images/logo-grey.png b/src/core/images/logo-grey.png new file mode 100644 index 0000000000000000000000000000000000000000..5c0d56a926c317223c1d46fd3ed3c1e63f193fe5 GIT binary patch literal 3986 zcmV;D4{h*?P)Dg|00001b5ch_0Itp) z=>Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91Z=eGJ1ONa40RR91ZvX%Q0PW%XN&o;3{7FPXRCodHoo$F+M;XUw=G?p4 zZ8vG6-8PVyU6UeR1MNbv)R5o?3pRo@ZBgO}X^Mg$ih>}DA0!l^w4ZD#N~ua)`^67x zT5N3##cs{Ml-8OR#1={F&2B=rHpHzPO}cx}8UN3@cP{6ioO|zinVBI zYf1ESCc5;;_U+p@!TsPhfV;hbGMl9z939)abLq_D;-3r1Ddo|=gh${!#sQ9P)sSn0 zka2i)`}PkP&YpckgFI^@`mWlXy#4mEKzOf7bVzjPk`(;l^z`(UZn>u9^%cCmd-qna z-7xq!6X2Q|yo@$*9Btr|sSMXCSLhUGQdR@p(6OZg-czDZZ&JIMJYejq1z?b@|atJPLJgmL1s^Y9cnR1sg2lHFq^*QESUAmw=g zyy!AOr>ji`4!|k^cZU^l6ba{dQk?aAeZu7`)LD2@a3M4)Zwq}J|D@c?gMfx1P6Koj zUK|{zvZ$Ri-PX1JEVvLiH7_^|&^dT&?V_A|yc7sPMln$^ocv-+vPDv?_lW)Ue zdTc&rJf#9G1zuKgl#wV2Z^E-vbxQtBN_Z5kG=SFUHuHc}83}_TnkYV?&W-q-zbplN zC8KN+1i=lq=R&QPydvNzGqG5T62Yki8gbOt!NM#{sf+4iy#!h-c}2mgT=H00ebOYg z{^>H#et=SUO2w7Kw^{TkXdZe}Zo@Lsv*;NXn)gIic`1;=j{)xGsC*5_^-85O50i>Z8e}DkFk13VfuodA z2}FbaDy*Ri-mb*d^|~n8U(t?!7qb8-dj-vL-*^c$R`N`Oi)N${Vna|54hSZXpcJO+ zi6YVO22B1MD+hnT^QKK5jFmtm@N9uo5D6eAugB4DZNr@8&44({nH&x8bACNW_!n^e z0q5cjfc?pr{s%b!hXrs-MI!*U!?O)e<&FY~_N!|rCSU>BS_P+6)DBQ z``qLIQiVkJ6Fl;2XN=lpFayz^(*+R@7}%p0$^Sbkcqv}lELOK zftlg;6F3EBy#mYxuOGp!XMm~U^)tBj3NY35SQK1ERyO&<$8wCkS!D{^l5<|*y<6vX zJ2oaN;-*iX6V=AWZY7@L&7Z-wwY3T8J(w84&c$K8%rcA>4SJ#F5>`pbT1_n5P3L(_&?7XpTLiwptU=! zEn=Q;9w4U~XD{|z59|t$z+E#i_y`1j4Y6c0I{?JvoOzF%gqRnc$}wvIB>`hc0p@C8 zN}0G%wxg2$tTIq}q|<_KM|iaBg710HV|aT@TyENP2tUUJe;ms&+HEe2f>UWQ2@pTv zt=KE(dzc}h5|`8QvvuhooSU1w%C;u4!$SL^?%BR$`)iAfi)~iiSHYta*95n@K!ou}Zv(&;04%)A zkWO$)N!rmpJ$ke_?mFRxlwpUu)+-9)0cFY_&syl;+vErS+(T*g? zYL#{byb_5z^&#fD2GIR~zz{gnW6WP$drB$Psy4%Ip}6>^x?#v4#;>y4wAM;(_+tZ{ zN^jmOl)%A!@HX@izpi8%^2c_Lj=sLIuuyB2JYRUn3`G_fdzkw$3S#MARkjIEDVnte zh9wRg5XgXiQOPjrW4su$YQaNfhfPKpkv+@=yCn_4GQcT4(*jiBu<6cq-6t@VWDPzx zg{A&U2#x>ZG%1keR=O21A3qu?kk+Pb3SITd9(U$9$4co;7fm;p!^S2Psr?C2gRZ6(kp za7yPmz&JPxhdlh43U9;5yNVOhHqS)03Wg1EYW4-H3K;~aM8hgA5;eaZIWc~SYI-?@haxy{Jho^AoWG|| zqu|sv*8nPTnEDDAZv*_u;sB9{7nS}te301z;SU(k~8> zb7^JqS;dcq@u?7Ab4T;#)(yi4lJU82;8gTw1MCD&=@$ivlJ^&N-G=XR*?>opVUYQl zjYv`I-p9+3^Qw*O6=zuhdx2AWM*x0^v|Y7es={gZYg4nc^Q{hx7^pmo2#l9L3}%0k z7Mx13ZGdUPDg6{cykh-Zb4fr*D!0LXI+bVhC2}&P*;mSaS z4UtlOH4iwYkLePa7o5_MN+7lw`615RhC=GT4xc_;rR221!z{wG@cp|{ch^(^js8QZ zE*Fc+GX=0HIF+t&w?J&hqt12ucp>}SU{q5wTh*qzP&<2e`4ii=Jr6sU!??CtT`%ZA z)Yy8epp@q0x~ouK;99AJ*`AP`He21_x>oQ2J@f}>=4Kv_in$6N-BbXj@IH;xozVxf z9HRtd>?0n)mQcUT@?4s*x&Uei*Hj*zms3dp3__s-1BBpPjjOLwQHJj|7XP!N|zSCD=962P)cz81b-Wo%d=3*AqC( zsNs55gf5d)nTE4an~i@HlBUa^VDrz^dUOV4UZmn z9U$gWH=s9n1O9x{*Fl#X9Hk{wn-@>K%WWWm3-{m*}64()3Lq!(=Y6C|}(gu%& zl?kvXI8u;Yc_mOQI7&*P-X;l1a<+S+3Y;JKTQE;JjzrxIy+&Z$c#YBz23BWqyZ|$W zchLL1fGv$L!vD9rZ`jukj`TGGj|AHSNZ??Nb`Q4Z{~XNk$bO^+jPyuy2=^{_djAb3 z`DQHuGy;y&rM23OW|WO8w92Ymf}nm3zrb%G?os?zqdIgtM)>Pde;xq19hjtj55_|u zb_!eYx-oF1moa!G+$6xpveX*ALr<&yxf~!8@cr<$7c`DI4OCT)Q*bybe zq#u4*IQ=GfrGTUKmu8Fd_Sq$TI>0}wMIn{2doq>pm6U57eO@Jk0rI+YHOl;{Oe zNMXE_LV849z*yTxKaqP*fFpgJfJcgS1?Z*QA&}%4cFusKbUFi%l<5f2#}>!sJEy== zb~pu(6iNapc#!EO5|UHYA!DLzea?ZS^f?EQl!^oN@x>Hewh9oPq$Sb?&Pi~jn3M2G zu{c0X7~}OZI}WSyOU)GqXTecUI17)Iivq+uYuZ1oL2)EG3`=NJ1*gH0-cG|K1r@-m zz;cbPs#EZv_xg?x=~lKYWo|QTqG>Ml<0B&%@rpH>IZS4KK3jPHf>-VQ{hn;H%!V#y zRUvPZXf@*6!J$8*GjU5^8Qq`j{N0}J&y{o6`D!zoEmVaHzkV04El00wJNEcDlRmhl zO}=J9eFcxwKJ~^MHJ{0YIMfP|_BlCm;>2^B^Vg`jzN$8(IYD0UmOPaJ z@(=9WcNE)g-3R-Y$>{Y?#~6g;V^6_j*gfy*PGS1t`nrIpGCKU)rVx`DzTSn>xEbGX s7*qGqrp*KH>k?ZsH+S|`xBAxqFNL) {}, -}: IterableEmbeddedComponentProps) => { - console.log(`🚀 > IterableEmbeddedCard > config:`, config); - console.log(`🚀 > IterableEmbeddedCard > message:`, message); - console.log(`🚀 > IterableEmbeddedCard > onButtonClick:`, onButtonClick); - - return ( - - IterableEmbeddedCard - - ); -}; diff --git a/src/embedded/components/IterableEmbeddedCard/IterableEmbeddedCard.styles.ts b/src/embedded/components/IterableEmbeddedCard/IterableEmbeddedCard.styles.ts new file mode 100644 index 000000000..1a728c657 --- /dev/null +++ b/src/embedded/components/IterableEmbeddedCard/IterableEmbeddedCard.styles.ts @@ -0,0 +1,88 @@ +import { StyleSheet } from 'react-native'; +import { embeddedMediaImageBackgroundColors } from '../../constants/embeddedViewDefaults'; + +export const IMAGE_HEIGHT = 230; +export const PLACEHOLDER_IMAGE_HEIGHT = 56; +export const PLACEHOLDER_IMAGE_WIDTH = 56; + +export const styles = StyleSheet.create({ + body: { + alignSelf: 'stretch', + fontSize: 14, + fontWeight: '400', + lineHeight: 20, + }, + bodyContainer: { + alignItems: 'flex-start', + alignSelf: 'stretch', + display: 'flex', + flexDirection: 'column', + gap: 24, + paddingBottom: 16, + paddingHorizontal: 16, + paddingTop: 12, + }, + button: { + borderRadius: 32, + gap: 8, + }, + buttonContainer: { + alignItems: 'flex-start', + alignSelf: 'stretch', + display: 'flex', + flexDirection: 'row', + gap: 12, + width: '100%', + }, + buttonText: { + fontSize: 14, + fontWeight: '700', + lineHeight: 20, + }, + container: { + alignItems: 'center', + 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: 16, + justifyContent: 'center', + overflow: 'hidden', + width: '100%', + }, + mediaContainer: { + alignItems: 'flex-start', + alignSelf: 'stretch', + backgroundColor: embeddedMediaImageBackgroundColors.card, + display: 'flex', + flexDirection: 'row', + height: IMAGE_HEIGHT, + }, + mediaContainerNoImage: { + alignItems: 'center', + justifyContent: 'center', + }, + mediaImage: { + height: IMAGE_HEIGHT, + paddingHorizontal: 0, + paddingVertical: 0, + width: '100%', + }, + mediaImagePlaceholder: { + height: PLACEHOLDER_IMAGE_HEIGHT, + opacity: 0.25, + width: PLACEHOLDER_IMAGE_WIDTH, + }, + textContainer: { + alignItems: 'flex-start', + alignSelf: 'stretch', + display: 'flex', + flexDirection: 'column', + gap: 8, + }, + title: { + fontSize: 18, + fontWeight: '700', + }, +}); diff --git a/src/embedded/components/IterableEmbeddedCard/IterableEmbeddedCard.tsx b/src/embedded/components/IterableEmbeddedCard/IterableEmbeddedCard.tsx new file mode 100644 index 000000000..fcd04f5ac --- /dev/null +++ b/src/embedded/components/IterableEmbeddedCard/IterableEmbeddedCard.tsx @@ -0,0 +1,136 @@ +import { + Image, + PixelRatio, + Text, + TouchableOpacity, + View, + type TextStyle, + type ViewStyle, + Pressable, +} from 'react-native'; + +import { IterableEmbeddedViewType } from '../../enums'; +import { useEmbeddedView } from '../../hooks/useEmbeddedView'; +import type { IterableEmbeddedComponentProps } from '../../types/IterableEmbeddedComponentProps'; +import { IMAGE_HEIGHT, styles } from './IterableEmbeddedCard.styles'; + +/** + * TODO: Add default action click handler. See IterableEmbeddedView for functionality. + */ + +export const IterableEmbeddedCard = ({ + config, + message, + onButtonClick = () => {}, + onMessageClick = () => {}, +}: IterableEmbeddedComponentProps) => { + const { + componentRef, + handleButtonClick, + handleLayout, + handleMessageClick, + media, + parsedStyles, + } = useEmbeddedView(IterableEmbeddedViewType.Card, { + message, + config, + onButtonClick, + onMessageClick, + }); + const buttons = message?.elements?.buttons ?? []; + + return ( + handleMessageClick()}> + + + {media.caption + + + + + {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/IterableEmbeddedCard/index.ts b/src/embedded/components/IterableEmbeddedCard/index.ts new file mode 100644 index 000000000..748f2064f --- /dev/null +++ b/src/embedded/components/IterableEmbeddedCard/index.ts @@ -0,0 +1 @@ +export * from './IterableEmbeddedCard'; diff --git a/src/embedded/components/index.ts b/src/embedded/components/index.ts index 22725d80c..f51919c32 100644 --- a/src/embedded/components/index.ts +++ b/src/embedded/components/index.ts @@ -1,4 +1,4 @@ export * from './IterableEmbeddedBanner/IterableEmbeddedBanner'; -export * from './IterableEmbeddedCard'; +export * from './IterableEmbeddedCard/IterableEmbeddedCard'; export * from './IterableEmbeddedNotification/IterableEmbeddedNotification'; export * from './IterableEmbeddedView';