From e22afd02bea2ff4c008be9a11e4be7f75fd76f0a Mon Sep 17 00:00:00 2001 From: Riley Love Date: Mon, 13 May 2024 13:51:07 -0500 Subject: [PATCH] feat(add courierjs): add courierjs and fix typings for useCourier --- package.json | 1 + .../components/src/components/CourierSdk.tsx | 15 +++- packages/components/src/index.tsx | 10 +++ .../src/components/Inbox/index.tsx | 7 +- .../src/components/Messages2.0/Header.tsx | 76 ++++++++++--------- .../src/hooks/use-local-storage-messages.ts | 11 ++- .../src/components/DigestSchedule.tsx | 2 +- .../src/components/PreferenceTemplate.tsx | 7 +- .../src/components/PreferencesV4.tsx | 13 ++-- .../src/components/Status.tsx | 3 +- packages/react-preferences/src/types.ts | 36 +-------- .../src/utils/format_digest.ts | 2 +- .../src/hooks/use-courier-actions.ts | 28 ++++++- .../react-provider/src/hooks/use-courier.ts | 5 +- packages/react-provider/src/index.tsx | 9 ++- packages/react-provider/src/types.ts | 50 +++++++++++- .../src/components/Toast/index.tsx | 11 +-- packages/react-toast/src/hooks/index.ts | 14 ++-- yarn.lock | 5 ++ 19 files changed, 190 insertions(+), 115 deletions(-) diff --git a/package.json b/package.json index 2df1802e..e7cdec26 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ ] }, "dependencies": { + "@trycourier/courier-js": "^1.4.2", "pkg-dir": "^7.0.0" } } diff --git a/packages/components/src/components/CourierSdk.tsx b/packages/components/src/components/CourierSdk.tsx index ed37f665..a94d4c18 100644 --- a/packages/components/src/components/CourierSdk.tsx +++ b/packages/components/src/components/CourierSdk.tsx @@ -1,6 +1,7 @@ import React, { useEffect } from "react"; import { useCourier } from "@trycourier/react-provider"; -import { useInbox, usePreferences } from "@trycourier/react-hooks"; +import { IInbox, useInbox, usePreferences } from "@trycourier/react-hooks"; +import { ToastProps } from "@trycourier/react-toast"; export const CourierSdk: React.FunctionComponent<{ activeComponents: { @@ -9,7 +10,11 @@ export const CourierSdk: React.FunctionComponent<{ preferences: boolean; }; }> = ({ children }) => { - const courier = useCourier(); + const courier = + useCourier<{ + inbox: IInbox; + toast: ToastProps; + }>(); const inbox = useInbox(); const preferences = usePreferences(); @@ -40,8 +45,12 @@ export const CourierSdk: React.FunctionComponent<{ ...courier.toast, }, brand: courier.brand, - transport: courier.transport, + identify: courier.identify, renewSession: courier.renewSession, + subscribe: courier.subscribe, + track: courier.track, + transport: courier.transport, + unsubscribe: courier.unsubscribe, }; }, [courier]); diff --git a/packages/components/src/index.tsx b/packages/components/src/index.tsx index 04d6671f..6aaedc5d 100644 --- a/packages/components/src/index.tsx +++ b/packages/components/src/index.tsx @@ -52,6 +52,16 @@ declare global { transport?: any; brand?: Brand; renewSession?: (token: string) => void; + identify?: ( + userId: string, + payload: Record + ) => Promise; + subscribe?: (userId: string, listId: string) => Promise; + track?: ( + event: string, + properties?: Record + ) => Promise; + unsubscribe?: (userId: string, listId: string) => Promise; init: (config: ICourierConfig) => void; on: (action: string, cb: () => void) => void; }; diff --git a/packages/react-inbox/src/components/Inbox/index.tsx b/packages/react-inbox/src/components/Inbox/index.tsx index b736276f..ef556f47 100644 --- a/packages/react-inbox/src/components/Inbox/index.tsx +++ b/packages/react-inbox/src/components/Inbox/index.tsx @@ -87,7 +87,7 @@ const StyledTippy = styled(LazyTippy)<{ const Inbox: React.FunctionComponent = (props) => { const ref = useRef(null); - const courierContext = useCourier(); + const courierContext = useCourier<{ inbox: InboxProps }>(); if (!courierContext) { throw new Error("Missing Courier Provider"); @@ -143,10 +143,7 @@ const Inbox: React.FunctionComponent = (props) => { } } - const localStorageState = useLocalStorageMessages( - courierContext.clientKey, - courierContext.userId - ); + const localStorageState = useLocalStorageMessages(courierContext); useEffect(() => { init({ diff --git a/packages/react-inbox/src/components/Messages2.0/Header.tsx b/packages/react-inbox/src/components/Messages2.0/Header.tsx index 4d1e23ca..d7f1cd59 100644 --- a/packages/react-inbox/src/components/Messages2.0/Header.tsx +++ b/packages/react-inbox/src/components/Messages2.0/Header.tsx @@ -231,39 +231,40 @@ const Header: React.FunctionComponent = ({ }; const options = useMemo(() => { - const viewOptions = views?.map((v, index) => ({ - id: v.id, - Component: ({ - active, - disabled, - onClick, - selected, - showDropdown, - }: { - active?: boolean; - disabled?: boolean; - onClick?: React.MouseEventHandler; - selected?: boolean; - showDropdown?: boolean; - }) => ( - - - {onClick && !disabled && } - - ), - })); - - return [ - ...(viewOptions ?? []), - brand?.preferenceTemplates?.length && { + const viewOptions = views + ?.map((v, index) => ({ + id: v.id, + Component: ({ + active, + disabled, + onClick, + selected, + showDropdown, + }: { + active?: boolean; + disabled?: boolean; + onClick?: React.MouseEventHandler; + selected?: boolean; + showDropdown?: boolean; + }) => ( + + + {onClick && !disabled && } + + ), + })) + .filter(Boolean); + + if (brand?.preferenceTemplates?.length) { + viewOptions?.push({ id: "preferences", Component: ({ active, @@ -286,12 +287,13 @@ const Header: React.FunctionComponent = ({ {onClick && } ), - }, - ].filter(Boolean); + }); + } + return viewOptions; }, [brand?.preferenceTemplates?.length, title, unreadMessageCount]); const ActiveOption = options?.find((o) => o.id === view)?.Component; - const hasDropdownOptions = options?.length > 1; + const hasDropdownOptions = Boolean(options?.length); return ( @@ -310,7 +312,7 @@ const Header: React.FunctionComponent = ({ {showDropdown && ( {options - .map((o) => { + ?.map((o) => { return ; }) .filter(Boolean)} diff --git a/packages/react-inbox/src/hooks/use-local-storage-messages.ts b/packages/react-inbox/src/hooks/use-local-storage-messages.ts index ef32f114..69b85a1d 100644 --- a/packages/react-inbox/src/hooks/use-local-storage-messages.ts +++ b/packages/react-inbox/src/hooks/use-local-storage-messages.ts @@ -2,7 +2,13 @@ import { useEffect, useMemo } from "react"; import { useInbox } from "@trycourier/react-hooks"; import { useCourier } from "@trycourier/react-provider"; -const useLocalStorageMessages = (clientKey: string, userId: string) => { +const useLocalStorageMessages = ({ + clientKey, + userId, +}: { + clientKey?: string; + userId?: string; +}) => { const { localStorage } = useCourier(); const { lastMessagesFetched, @@ -35,7 +41,8 @@ const useLocalStorageMessages = (clientKey: string, userId: string) => { } try { - return JSON.parse(localStorage.getItem(localStorageKey)); + const item = localStorage.getItem(localStorageKey); + return item ? JSON.parse(item) : {}; } catch { // do nothing } diff --git a/packages/react-preferences/src/components/DigestSchedule.tsx b/packages/react-preferences/src/components/DigestSchedule.tsx index a8c25a5a..170bff95 100644 --- a/packages/react-preferences/src/components/DigestSchedule.tsx +++ b/packages/react-preferences/src/components/DigestSchedule.tsx @@ -1,7 +1,7 @@ import { usePreferences } from "@trycourier/react-hooks"; +import { DigestSchedule } from "@trycourier/react-provider"; import React, { useEffect, useState } from "react"; import styled from "styled-components"; -import { DigestSchedule } from "~/types"; import formatDigest, { toUpperCaseFirstLetter } from "~/utils/format_digest"; const DigestScheduleContainer = styled.div` diff --git a/packages/react-preferences/src/components/PreferenceTemplate.tsx b/packages/react-preferences/src/components/PreferenceTemplate.tsx index 12dfca53..b8a57301 100644 --- a/packages/react-preferences/src/components/PreferenceTemplate.tsx +++ b/packages/react-preferences/src/components/PreferenceTemplate.tsx @@ -3,11 +3,8 @@ import styled from "styled-components"; import { usePreferences } from "@trycourier/react-hooks"; import { StatusPreference } from "./Status"; -import { - ChannelClassification, - IPreferenceTemplate, - IRecipientPreference, -} from "../types"; +import { ChannelClassification, IRecipientPreference } from "../types"; +import { IPreferenceTemplate } from "@trycourier/react-provider"; const StyledItem = styled.div` border-bottom: 1px solid #dadce0; diff --git a/packages/react-preferences/src/components/PreferencesV4.tsx b/packages/react-preferences/src/components/PreferencesV4.tsx index 9273df16..d7d85619 100644 --- a/packages/react-preferences/src/components/PreferencesV4.tsx +++ b/packages/react-preferences/src/components/PreferencesV4.tsx @@ -1,17 +1,16 @@ import React, { Fragment, useEffect, useMemo, useState } from "react"; import styled from "styled-components"; import { usePreferences } from "@trycourier/react-hooks"; -import { - ChannelClassification, - IPreferenceTemplate, - IRecipientPreference, - PreferenceStatus, -} from "~/types"; +import { ChannelClassification, IRecipientPreference } from "~/types"; import { StyledToggle } from "./StyledToggle"; import Toggle from "react-toggle"; import { PreferenceSection } from "@trycourier/react-hooks"; import DigestSchedules from "./DigestSchedule"; -import { useCourier } from "@trycourier/react-provider"; +import { + IPreferenceTemplate, + PreferenceStatus, + useCourier, +} from "@trycourier/react-provider"; export const ChannelOption = styled.div` display: flex; diff --git a/packages/react-preferences/src/components/Status.tsx b/packages/react-preferences/src/components/Status.tsx index 378c187b..54e62f0b 100644 --- a/packages/react-preferences/src/components/Status.tsx +++ b/packages/react-preferences/src/components/Status.tsx @@ -1,8 +1,9 @@ import React, { useState } from "react"; import Toggle from "react-toggle"; -import { PreferenceItemComponentFn, PreferenceStatus } from "../types"; +import { PreferenceItemComponentFn } from "../types"; import { ChannelPreferences } from "./ChannelPreferences"; import { StyledToggle } from "./StyledToggle"; +import { PreferenceStatus } from "@trycourier/react-provider"; export const StatusPreference: PreferenceItemComponentFn = ({ //label, diff --git a/packages/react-preferences/src/types.ts b/packages/react-preferences/src/types.ts index 6bc13033..21709c0d 100644 --- a/packages/react-preferences/src/types.ts +++ b/packages/react-preferences/src/types.ts @@ -1,3 +1,5 @@ +import { PreferenceStatus } from "@trycourier/react-provider"; + export type Preference = "channel_preferences" | "status" | "snooze"; export type ChannelClassification = @@ -18,8 +20,6 @@ export type PreferenceItemComponentFn = React.FunctionComponent<{ customizeDeliveryChannel?: boolean; }>; -export type PreferenceStatus = "OPTED_IN" | "OPTED_OUT" | "REQUIRED"; - export type SnoozePreference = { start?: string; until: string; @@ -31,28 +31,6 @@ export interface IPreference { channel_preferences?: Array; } -export interface DigestSchedule { - period: string; - repetition: string; - scheduleId: string; - default?: boolean; - start: string; - recurrence: string; - repeat?: { - frequency: number; - interval: "day" | "week" | "month" | "year"; - on?: string | RepeatOn; - }; - end?: number | string; -} - -export interface IPreferenceTemplate { - templateName: string; - templateId: string; - defaultStatus: PreferenceStatus; - digestSchedules?: DigestSchedule[]; -} - export interface IRecipientPreference { templateId: string; status: PreferenceStatus | null; @@ -60,13 +38,3 @@ export interface IRecipientPreference { routingPreferences: Array; digestSchedule: string; } - -export type RepeatOn = { - sunday?: boolean; - monday?: boolean; - tuesday?: boolean; - wednesday?: boolean; - thursday?: boolean; - friday?: boolean; - saturday?: boolean; -}; diff --git a/packages/react-preferences/src/utils/format_digest.ts b/packages/react-preferences/src/utils/format_digest.ts index baa4b3f5..ea9a91cf 100644 --- a/packages/react-preferences/src/utils/format_digest.ts +++ b/packages/react-preferences/src/utils/format_digest.ts @@ -1,5 +1,5 @@ +import { DigestSchedule, RepeatOn } from "@trycourier/react-provider"; import format from "date-fns/format"; -import { DigestSchedule, RepeatOn } from "~/types"; export const toUpperCaseFirstLetter = (str: string) => { return str.charAt(0).toUpperCase() + str.slice(1); diff --git a/packages/react-provider/src/hooks/use-courier-actions.ts b/packages/react-provider/src/hooks/use-courier-actions.ts index 97ade6e0..cf659e0a 100644 --- a/packages/react-provider/src/hooks/use-courier-actions.ts +++ b/packages/react-provider/src/hooks/use-courier-actions.ts @@ -6,9 +6,11 @@ import { Events, } from "@trycourier/client-graphql"; import { Brand, CourierTransport } from ".."; +import courier from "@trycourier/courier-js"; + import { ICourierContext } from "~/types"; -const useCourierActions = (state, dispatch) => { +const useCourierActions = (state, dispatch): ICourierContext => { return useMemo(() => { const courierClient = createCourierClient({ apiUrl: state.apiUrl, @@ -19,10 +21,34 @@ const useCourierActions = (state, dispatch) => { userSignature: state.userSignature, }); + courier.init({ + baseUrl: state.apiUrl, + authorization: state.authorization, + clientKey: state.clientKey, + userId: state.userId, + userSignature: state.userSignature, + }); + const brands = Brands({ client: courierClient }); const events = Events({ client: courierClient }); return { + dispatch, + async track( + event: string, + properties?: Record | undefined + ) { + await courier.track(event, properties); + }, + async identify(userId: string, payload: Record) { + await courier.identify(userId, payload); + }, + async subscribe(userId: string, listId: string) { + await courier.subscribe(userId, listId); + }, + async unsubscribe(userId: string, listId: string) { + await courier.unsubscribe(userId, listId); + }, init: async (payload: Partial) => { dispatch({ type: "root/INIT", diff --git a/packages/react-provider/src/hooks/use-courier.ts b/packages/react-provider/src/hooks/use-courier.ts index 189c7051..1fb8e617 100644 --- a/packages/react-provider/src/hooks/use-courier.ts +++ b/packages/react-provider/src/hooks/use-courier.ts @@ -2,8 +2,9 @@ import { useContext } from "react"; import { CourierContext } from "../"; import { ICourierContext } from "../types"; -function useCourier(): ICourierContext & T { - return useContext(CourierContext) as ICourierContext & T; +function useCourier(): ICourierContext & T { + const context = useContext(CourierContext) as ICourierContext & T; + return context; } export default useCourier; diff --git a/packages/react-provider/src/index.tsx b/packages/react-provider/src/index.tsx index e6bdf53f..cae8c4b0 100644 --- a/packages/react-provider/src/index.tsx +++ b/packages/react-provider/src/index.tsx @@ -15,12 +15,16 @@ import createReducer from "react-use/lib/factory/createReducer"; import { Brand, + DigestSchedule, EventType, ICourierContext, ICourierProviderProps, + IPreferenceTemplate, OnEvent, PinDetails, + PreferenceStatus, ProviderTheme, + RepeatOn, ThemeVariables, WSOptions, } from "./types"; @@ -54,17 +58,21 @@ export const registerMiddleware = _registerMiddleware; export type { Brand, + DigestSchedule, EventType, IActionElemental, ICourierContext, ICourierEventMessage, IInboxMessagePreview, Interceptor, + IPreferenceTemplate, ITextElemental, Middleware, OnEvent, PinDetails, + PreferenceStatus, ProviderTheme, + RepeatOn, WSOptions, }; @@ -300,7 +308,6 @@ export const CourierProvider: React.FunctionComponent< ...state, ...actions, clientSourceId, - dispatch, }} > diff --git a/packages/react-provider/src/types.ts b/packages/react-provider/src/types.ts index 5f1ccaee..dddbc880 100644 --- a/packages/react-provider/src/types.ts +++ b/packages/react-provider/src/types.ts @@ -6,6 +6,40 @@ export { IInboxMessagePreview } from "@trycourier/client-graphql"; export { Interceptor } from "./transports/types"; export type ErrorEventHandler = (event: ErrorEvent) => void; +export type PreferenceStatus = "OPTED_IN" | "OPTED_OUT" | "REQUIRED"; + +export type RepeatOn = { + sunday?: boolean; + monday?: boolean; + tuesday?: boolean; + wednesday?: boolean; + thursday?: boolean; + friday?: boolean; + saturday?: boolean; +}; + +export interface DigestSchedule { + period: string; + repetition: string; + scheduleId: string; + default?: boolean; + start: string; + recurrence: string; + repeat?: { + frequency: number; + interval: "day" | "week" | "month" | "year"; + on?: string | RepeatOn; + }; + end?: number | string; +} + +export interface IPreferenceTemplate { + templateName: string; + templateId: string; + defaultStatus: PreferenceStatus; + digestSchedules?: DigestSchedule[]; +} + export type WSOptions = { url?: string; onError?: ErrorEventHandler; @@ -57,6 +91,7 @@ export interface Brand { }; renderActionsAsButtons?: boolean; }; + preferenceTemplates?: Array; colors?: { primary?: string; secondary?: string; @@ -108,6 +143,19 @@ export interface ICourierProviderProps { wsOptions?: WSOptions; } export interface ICourierContext extends ICourierProviderProps { + clientSourceId?: string; dispatch: (action: { type: string; payload?: any; meta?: any }) => void; - clientSourceId: string; + getBrand: (brandId?: string) => void; + createTrackEvent: (trackingId: string) => void; + identify?: ( + userId: string, + payload: Record + ) => Promise; + subscribe: (userId: string, listId: string) => Promise; + track: (event: string, properties?: Record) => Promise; + unsubscribe: (userId: string, listId: string) => Promise; + renewSession: (token: string) => void; + pageVisible: () => void; + init: (payload: Partial) => Promise; + wsReconnected: () => void; } diff --git a/packages/react-toast/src/components/Toast/index.tsx b/packages/react-toast/src/components/Toast/index.tsx index b20f524b..5e09e7f1 100644 --- a/packages/react-toast/src/components/Toast/index.tsx +++ b/packages/react-toast/src/components/Toast/index.tsx @@ -28,19 +28,14 @@ export const Toast: React.FunctionComponent = (props) => { throw new Error("Missing Courier Provider"); } - const { - clientKey, - transport, - dispatch, - brand: courierBrand, - } = courierContext; + const { transport, dispatch, brand: courierBrand } = courierContext; const brand = props?.brand ?? courierBrand; const theme = useMemo(() => { return { ...props.theme, - brand: deepExtend({}, themeDefaults, brand), + brand: deepExtend({}, themeDefaults, brand ?? {}), }; }, [props.theme, brand]); @@ -87,7 +82,7 @@ export const Toast: React.FunctionComponent = (props) => { }); }, [props, dispatch, handleToast]); - useListenForTransportEvent(clientKey, transport, handleToast); + useListenForTransportEvent({ transport, handleToast }); const autoClose = props?.autoClose ?? brand?.inapp?.toast?.timerAutoClose; diff --git a/packages/react-toast/src/hooks/index.ts b/packages/react-toast/src/hooks/index.ts index ffbc5401..ed7a3b08 100644 --- a/packages/react-toast/src/hooks/index.ts +++ b/packages/react-toast/src/hooks/index.ts @@ -25,11 +25,13 @@ export const useToast: UseToast = () => { ]; }; -export const useListenForTransportEvent = ( - clientKey: string, - transport: ICourierContext["transport"], - handleToast -) => { +export const useListenForTransportEvent = ({ + transport, + handleToast, +}: { + transport: ICourierContext["transport"]; + handleToast; +}) => { const { createTrackEvent } = useCourier(); useEffect(() => { @@ -50,5 +52,5 @@ export const useListenForTransportEvent = ( handleToast(courierEvent?.data); }, }); - }, [clientKey, handleToast, transport]); + }, [handleToast, transport]); }; diff --git a/yarn.lock b/yarn.lock index 2977cb98..9121f677 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4658,6 +4658,11 @@ rimraf "^3.0.2" urql "^2.0.1" +"@trycourier/courier-js@^1.4.2": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@trycourier/courier-js/-/courier-js-1.4.2.tgz#93c15a2ac95af928796f50763164d64cae32ccbf" + integrity sha512-OYVPxSfjxsfBDVSVAYX0EX/ABh0OhEW/pi/0vU07LBuCwSun/tVoxqQsqTz6T3M+86iDSxsfWUMNfnO9MLdLHA== + "@trycourier/courier@^1.3.0": version "1.7.3" resolved "https://registry.yarnpkg.com/@trycourier/courier/-/courier-1.7.3.tgz#d3116c59e3ce4b551a616a3108e5b44b9a5a2b9d"