@leisuresaas/react-native 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +90 -0
- package/package.json +49 -0
- package/src/ads/ad-slot.tsx +131 -0
- package/src/ads/ad.tsx +91 -0
- package/src/ads/bundle-id.ts +9 -0
- package/src/ads/constants.ts +92 -0
- package/src/ads/context.tsx +76 -0
- package/src/ads/creative.tsx +105 -0
- package/src/ads/default-theme.ts +284 -0
- package/src/ads/hooks.ts +156 -0
- package/src/ads/index.ts +32 -0
- package/src/ads/layouts/image.tsx +101 -0
- package/src/ads/layouts/index.tsx +28 -0
- package/src/ads/layouts/shared.tsx +80 -0
- package/src/ads/layouts/text.tsx +77 -0
- package/src/ads/lineup-id.ts +6 -0
- package/src/ads/open-click.ts +61 -0
- package/src/ads/rotation/indicators-context.tsx +31 -0
- package/src/ads/rotation/indicators.tsx +22 -0
- package/src/ads/rotation/shell.tsx +98 -0
- package/src/ads/session-id.ts +18 -0
- package/src/ads/theme.ts +105 -0
- package/src/ads-ui.tsx +40 -0
- package/src/ads.ts +16 -0
- package/src/app-update/context.tsx +380 -0
- package/src/auth-errors.ts +50 -0
- package/src/auth-session.ts +129 -0
- package/src/auth-storage.ts +62 -0
- package/src/auth.tsx +249 -0
- package/src/dev.ts +16 -0
- package/src/drive/types.ts +62 -0
- package/src/drive.ts +355 -0
- package/src/errors.ts +11 -0
- package/src/gateway-url.ts +7 -0
- package/src/hosted-ui-link.ts +265 -0
- package/src/index.ts +161 -0
- package/src/platform-api-base.ts +72 -0
- package/src/platform.ts +16 -0
- package/src/public-ads.ts +105 -0
- package/src/public-app-config.ts +106 -0
- package/src/public-auth.ts +252 -0
- package/src/publishable-key.ts +19 -0
- package/src/push.ts +89 -0
- package/src/types.ts +123 -0
- package/src/userinfo.ts +34 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { Text, View } from "react-native";
|
|
3
|
+
|
|
4
|
+
import type { TextLayout } from "../constants";
|
|
5
|
+
import type { AdCreativeStyles, AdLayoutRenderContext } from "../theme";
|
|
6
|
+
import { AdBadge, AdBody, AdLinkCta, AdTitle, LayoutShell } from "./shared";
|
|
7
|
+
|
|
8
|
+
export type AdLayoutRendererProps = {
|
|
9
|
+
ctx: AdLayoutRenderContext;
|
|
10
|
+
styles: AdCreativeStyles;
|
|
11
|
+
contentStyle?: import("react-native").StyleProp<import("react-native").ViewStyle>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function TextInlineLayout({ ctx, styles, contentStyle }: AdLayoutRendererProps) {
|
|
15
|
+
return (
|
|
16
|
+
<LayoutShell ad={ctx.ad} onPress={ctx.onPress} styles={styles} contentStyle={contentStyle}>
|
|
17
|
+
<View style={styles.content}>
|
|
18
|
+
<AdTitle text={ctx.ad.title} styles={styles} />
|
|
19
|
+
<AdBody text={ctx.ad.body_text} styles={styles} />
|
|
20
|
+
<AdLinkCta styles={styles} />
|
|
21
|
+
</View>
|
|
22
|
+
</LayoutShell>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function TextCalloutLayout({ ctx, styles, contentStyle }: AdLayoutRendererProps) {
|
|
27
|
+
return (
|
|
28
|
+
<LayoutShell ad={ctx.ad} onPress={ctx.onPress} styles={styles} contentStyle={contentStyle}>
|
|
29
|
+
<AdBadge styles={styles} />
|
|
30
|
+
<AdTitle text={ctx.ad.title} styles={styles} />
|
|
31
|
+
<AdBody text={ctx.ad.body_text} styles={styles} />
|
|
32
|
+
<AdLinkCta styles={styles} label="View details" />
|
|
33
|
+
</LayoutShell>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function TextFooterLayout({ ctx, styles, contentStyle }: AdLayoutRendererProps) {
|
|
38
|
+
const line = [ctx.ad.title, ctx.ad.body_text].filter(Boolean).join(" · ");
|
|
39
|
+
return (
|
|
40
|
+
<LayoutShell ad={ctx.ad} onPress={ctx.onPress} styles={styles} contentStyle={contentStyle}>
|
|
41
|
+
<Text style={styles.title} numberOfLines={2}>{line || "Platform ad"}</Text>
|
|
42
|
+
</LayoutShell>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function TextSidebarLayout({ ctx, styles, contentStyle }: AdLayoutRendererProps) {
|
|
47
|
+
return (
|
|
48
|
+
<LayoutShell ad={ctx.ad} onPress={ctx.onPress} styles={styles} contentStyle={contentStyle}>
|
|
49
|
+
<View style={styles.content}>
|
|
50
|
+
<AdTitle text={ctx.ad.title} styles={styles} />
|
|
51
|
+
<AdBody text={ctx.ad.body_text} styles={styles} numberOfLines={3} />
|
|
52
|
+
</View>
|
|
53
|
+
</LayoutShell>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function TextMinimalLayout({ ctx, styles, contentStyle }: AdLayoutRendererProps) {
|
|
58
|
+
const label = ctx.ad.title || ctx.ad.body_text || "Learn more";
|
|
59
|
+
return (
|
|
60
|
+
<LayoutShell ad={ctx.ad} onPress={ctx.onPress} styles={styles} contentStyle={contentStyle}>
|
|
61
|
+
<Text style={styles.link}>{label} ›</Text>
|
|
62
|
+
</LayoutShell>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const textLayoutMap: Record<TextLayout, (props: AdLayoutRendererProps) => ReactElement> = {
|
|
67
|
+
text_inline: TextInlineLayout,
|
|
68
|
+
text_callout: TextCalloutLayout,
|
|
69
|
+
text_footer: TextFooterLayout,
|
|
70
|
+
text_sidebar: TextSidebarLayout,
|
|
71
|
+
text_minimal: TextMinimalLayout,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export function renderTextLayout(layout: TextLayout, props: AdLayoutRendererProps): ReactElement {
|
|
75
|
+
const Layout = textLayoutMap[layout] ?? TextInlineLayout;
|
|
76
|
+
return <Layout {...props} />;
|
|
77
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as WebBrowser from "expo-web-browser";
|
|
2
|
+
import { Linking } from "react-native";
|
|
3
|
+
|
|
4
|
+
export type InAppNavigate = (path: string) => void;
|
|
5
|
+
|
|
6
|
+
type OpenAdClickOptions = {
|
|
7
|
+
onInAppNavigate?: InAppNavigate;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
async function openExternal(url: string): Promise<void> {
|
|
11
|
+
try {
|
|
12
|
+
await WebBrowser.openBrowserAsync(url);
|
|
13
|
+
} catch {
|
|
14
|
+
await Linking.openURL(url);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Opens feed click_url. Sends X-Ads-Click: resolve so the gateway can return
|
|
20
|
+
* an in-app path instead of a browser redirect. Click is recorded by that GET.
|
|
21
|
+
*/
|
|
22
|
+
export async function openAdClickUrl(url: string, options?: OpenAdClickOptions): Promise<void> {
|
|
23
|
+
if (!url) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetch(url, {
|
|
28
|
+
method: "GET",
|
|
29
|
+
headers: { Accept: "application/json", "X-Ads-Click": "resolve" },
|
|
30
|
+
});
|
|
31
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
32
|
+
if (res.ok && contentType.includes("application/json")) {
|
|
33
|
+
const data = (await res.json()) as { kind?: string; path?: string; url?: string };
|
|
34
|
+
if (data.kind === "in_app" && data.path?.startsWith("/")) {
|
|
35
|
+
if (options?.onInAppNavigate) {
|
|
36
|
+
options.onInAppNavigate(data.path);
|
|
37
|
+
} else {
|
|
38
|
+
console.warn("Ad: in-app click ignored; pass AdsProvider onInAppNavigate");
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (data.kind === "external" && data.url?.startsWith("https://")) {
|
|
43
|
+
await openExternal(data.url);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
console.warn("Ad: unexpected click resolve body");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (res.url && res.url !== url && res.url.startsWith("https://")) {
|
|
50
|
+
await openExternal(res.url);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
console.warn("Ad: click resolve failed", res.status);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.warn("Ad: click resolve failed", err);
|
|
59
|
+
}
|
|
60
|
+
await openExternal(url);
|
|
61
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
|
|
3
|
+
import type { AdFeedItem } from "../../types";
|
|
4
|
+
import type { AdRotationStyles } from "../theme";
|
|
5
|
+
|
|
6
|
+
export type AdRotationIndicatorState = {
|
|
7
|
+
ads: AdFeedItem[];
|
|
8
|
+
index: number;
|
|
9
|
+
onIndexChange: (index: number) => void;
|
|
10
|
+
styles: AdRotationStyles;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const AdRotationIndicatorsContext = createContext<AdRotationIndicatorState | null>(null);
|
|
14
|
+
|
|
15
|
+
export function AdRotationIndicatorsProvider({
|
|
16
|
+
value,
|
|
17
|
+
children,
|
|
18
|
+
}: {
|
|
19
|
+
value: AdRotationIndicatorState | null;
|
|
20
|
+
children: React.ReactNode;
|
|
21
|
+
}) {
|
|
22
|
+
return (
|
|
23
|
+
<AdRotationIndicatorsContext.Provider value={value}>
|
|
24
|
+
{children}
|
|
25
|
+
</AdRotationIndicatorsContext.Provider>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function useAdRotationIndicators() {
|
|
30
|
+
return useContext(AdRotationIndicatorsContext);
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Pressable, View } from "react-native";
|
|
2
|
+
|
|
3
|
+
import type { AdRotationIndicatorState } from "./indicators-context";
|
|
4
|
+
|
|
5
|
+
type AdRotationIndicatorsProps = {
|
|
6
|
+
state: AdRotationIndicatorState;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/** Rendered once inside the ad root View — avoids absolute positioning bugs under Pressable. */
|
|
10
|
+
export function AdRotationIndicators({ state }: AdRotationIndicatorsProps) {
|
|
11
|
+
const { ads, index, onIndexChange, styles } = state;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<View style={styles.indicators} pointerEvents="box-none">
|
|
15
|
+
{ads.map((item, i) => (
|
|
16
|
+
<Pressable key={item.id} onPress={() => onIndexChange(i)} hitSlop={8}>
|
|
17
|
+
<View style={[styles.dot, i === index && styles.dotActive]} />
|
|
18
|
+
</Pressable>
|
|
19
|
+
))}
|
|
20
|
+
</View>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useEffect, useRef, type ReactNode } from "react";
|
|
2
|
+
import { Animated, View } from "react-native";
|
|
3
|
+
|
|
4
|
+
import type { AdFeedItem, AdFeedRotation } from "../../types";
|
|
5
|
+
import { ROTATION_ANIM_MS, effectiveRotationMode, type AdRotationMode } from "../constants";
|
|
6
|
+
import type { AdRotationStyles } from "../theme";
|
|
7
|
+
import { AdRotationIndicatorsProvider, type AdRotationIndicatorState } from "./indicators-context";
|
|
8
|
+
|
|
9
|
+
export type AdRotationShellProps = {
|
|
10
|
+
ads: AdFeedItem[];
|
|
11
|
+
rotation?: AdFeedRotation;
|
|
12
|
+
index: number;
|
|
13
|
+
onIndexChange: (index: number) => void;
|
|
14
|
+
styles: AdRotationStyles;
|
|
15
|
+
showIndicators: boolean;
|
|
16
|
+
renderSlide: (ad: AdFeedItem, index: number) => ReactNode;
|
|
17
|
+
rootStyle?: import("react-native").StyleProp<import("react-native").ViewStyle>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function AnimatedSlide({
|
|
21
|
+
mode, slideKey, children, slideStyle,
|
|
22
|
+
}: {
|
|
23
|
+
mode: AdRotationMode;
|
|
24
|
+
slideKey: string;
|
|
25
|
+
children: ReactNode;
|
|
26
|
+
slideStyle?: import("react-native").StyleProp<import("react-native").ViewStyle>;
|
|
27
|
+
}) {
|
|
28
|
+
const opacity = useRef(new Animated.Value(1)).current;
|
|
29
|
+
const translateX = useRef(new Animated.Value(0)).current;
|
|
30
|
+
const translateY = useRef(new Animated.Value(0)).current;
|
|
31
|
+
const scale = useRef(new Animated.Value(1)).current;
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (mode === "none") {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
opacity.setValue(mode === "fade" ? 0 : 1);
|
|
38
|
+
translateX.setValue(mode === "slide" ? 24 : 0);
|
|
39
|
+
translateY.setValue(mode === "stack" ? 10 : 0);
|
|
40
|
+
scale.setValue(mode === "stack" ? 0.96 : 1);
|
|
41
|
+
|
|
42
|
+
const anims: Animated.CompositeAnimation[] = [];
|
|
43
|
+
if (mode === "fade") {
|
|
44
|
+
anims.push(Animated.timing(opacity, { toValue: 1, duration: ROTATION_ANIM_MS, useNativeDriver: true }));
|
|
45
|
+
}
|
|
46
|
+
if (mode === "slide") {
|
|
47
|
+
anims.push(Animated.timing(translateX, { toValue: 0, duration: ROTATION_ANIM_MS, useNativeDriver: true }));
|
|
48
|
+
}
|
|
49
|
+
if (mode === "stack") {
|
|
50
|
+
anims.push(
|
|
51
|
+
Animated.timing(translateY, { toValue: 0, duration: ROTATION_ANIM_MS, useNativeDriver: true }),
|
|
52
|
+
Animated.timing(scale, { toValue: 1, duration: ROTATION_ANIM_MS, useNativeDriver: true }),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
Animated.parallel(anims).start();
|
|
56
|
+
}, [slideKey, mode, opacity, translateX, translateY, scale]);
|
|
57
|
+
|
|
58
|
+
if (mode === "none") {
|
|
59
|
+
return <View style={slideStyle}>{children}</View>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Animated.View
|
|
64
|
+
style={[
|
|
65
|
+
slideStyle,
|
|
66
|
+
mode === "fade" && { opacity },
|
|
67
|
+
mode === "slide" && { transform: [{ translateX }] },
|
|
68
|
+
mode === "stack" && { transform: [{ translateY }, { scale }] },
|
|
69
|
+
]}
|
|
70
|
+
>
|
|
71
|
+
{children}
|
|
72
|
+
</Animated.View>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function AdRotationShell({
|
|
77
|
+
ads, rotation, index, onIndexChange, styles, showIndicators, renderSlide, rootStyle,
|
|
78
|
+
}: AdRotationShellProps) {
|
|
79
|
+
const mode = effectiveRotationMode(rotation, ads.length);
|
|
80
|
+
const ad = ads[index] ?? ads[0];
|
|
81
|
+
if (!ad) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const indicatorState: AdRotationIndicatorState | null = showIndicators && ads.length > 1
|
|
86
|
+
? { ads, index, onIndexChange, styles }
|
|
87
|
+
: null;
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<View style={[styles.root, rootStyle]}>
|
|
91
|
+
<AdRotationIndicatorsProvider value={indicatorState}>
|
|
92
|
+
<AnimatedSlide mode={mode} slideKey={ad.id} slideStyle={styles.slide}>
|
|
93
|
+
{renderSlide(ad, index)}
|
|
94
|
+
</AnimatedSlide>
|
|
95
|
+
</AdRotationIndicatorsProvider>
|
|
96
|
+
</View>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as SecureStore from "expo-secure-store";
|
|
2
|
+
|
|
3
|
+
const STORAGE_KEY = "leisuresaas_ads_session_id";
|
|
4
|
+
|
|
5
|
+
function randomSessionId(): string {
|
|
6
|
+
const hex = () => Math.floor(Math.random() * 0xffffffff).toString(16).padStart(8, "0");
|
|
7
|
+
return `${hex()}${hex()}-${hex().slice(0, 4)}-4${hex().slice(1, 4)}-a${hex().slice(1, 4)}-${hex()}${hex()}${hex()}`.slice(0, 36);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function getOrCreateAdsSessionId(): Promise<string> {
|
|
11
|
+
const existing = await SecureStore.getItemAsync(STORAGE_KEY);
|
|
12
|
+
if (existing?.trim()) {
|
|
13
|
+
return existing.trim();
|
|
14
|
+
}
|
|
15
|
+
const id = randomSessionId();
|
|
16
|
+
await SecureStore.setItemAsync(STORAGE_KEY, id);
|
|
17
|
+
return id;
|
|
18
|
+
}
|
package/src/ads/theme.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { ImageStyle, TextStyle, ViewStyle } from "react-native";
|
|
3
|
+
|
|
4
|
+
import type { AdFeedItem, AdFeedRotation } from "../types";
|
|
5
|
+
import type { AdLayout, AdType } from "./constants";
|
|
6
|
+
|
|
7
|
+
export type AdCreativeStyles = {
|
|
8
|
+
root?: ViewStyle;
|
|
9
|
+
pressed?: ViewStyle;
|
|
10
|
+
image?: ImageStyle;
|
|
11
|
+
title?: TextStyle;
|
|
12
|
+
body?: TextStyle;
|
|
13
|
+
link?: TextStyle;
|
|
14
|
+
badge?: TextStyle;
|
|
15
|
+
caption?: ViewStyle;
|
|
16
|
+
overlay?: ViewStyle;
|
|
17
|
+
media?: ViewStyle;
|
|
18
|
+
content?: ViewStyle;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type AdRotationStyles = {
|
|
22
|
+
root?: ViewStyle;
|
|
23
|
+
slide?: ViewStyle;
|
|
24
|
+
indicators?: ViewStyle;
|
|
25
|
+
dot?: ViewStyle;
|
|
26
|
+
dotActive?: ViewStyle;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type TextLayoutStyles = Partial<Record<import("./constants").TextLayout, AdCreativeStyles>>;
|
|
30
|
+
export type ImageLayoutStyles = Partial<Record<import("./constants").ImageLayout, AdCreativeStyles>>;
|
|
31
|
+
|
|
32
|
+
export type AdLayoutStyles = {
|
|
33
|
+
text?: TextLayoutStyles;
|
|
34
|
+
image?: ImageLayoutStyles;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type AdsStateStyles = {
|
|
38
|
+
loading?: ViewStyle;
|
|
39
|
+
empty?: TextStyle;
|
|
40
|
+
error?: TextStyle;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type AdsTheme = {
|
|
44
|
+
creative?: AdCreativeStyles;
|
|
45
|
+
rotation?: AdRotationStyles;
|
|
46
|
+
layouts?: AdLayoutStyles;
|
|
47
|
+
states?: AdsStateStyles;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type ResolvedCreativeStyles = Required<Pick<AdCreativeStyles, never>> & AdCreativeStyles;
|
|
51
|
+
|
|
52
|
+
export type ResolvedAdsTheme = {
|
|
53
|
+
creative: AdCreativeStyles;
|
|
54
|
+
rotation: AdRotationStyles;
|
|
55
|
+
layouts: {
|
|
56
|
+
text: Record<import("./constants").TextLayout, AdCreativeStyles>;
|
|
57
|
+
image: Record<import("./constants").ImageLayout, AdCreativeStyles>;
|
|
58
|
+
};
|
|
59
|
+
states: AdsStateStyles;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type AdRenderContext = {
|
|
63
|
+
ad: AdFeedItem;
|
|
64
|
+
adType: AdType;
|
|
65
|
+
layout: AdLayout;
|
|
66
|
+
placement: string;
|
|
67
|
+
index: number;
|
|
68
|
+
count: number;
|
|
69
|
+
onPress: () => void;
|
|
70
|
+
theme: ResolvedAdsTheme;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type AdLayoutRenderContext = AdRenderContext & {
|
|
74
|
+
styles: AdCreativeStyles;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type AdProps = {
|
|
78
|
+
placement?: string;
|
|
79
|
+
style?: ViewStyle;
|
|
80
|
+
contentStyle?: ViewStyle;
|
|
81
|
+
theme?: AdsTheme;
|
|
82
|
+
styles?: AdLayoutStyles;
|
|
83
|
+
onError?: (error: Error) => void;
|
|
84
|
+
onPress?: (ad: AdFeedItem) => void;
|
|
85
|
+
showEmpty?: boolean;
|
|
86
|
+
showIndicators?: boolean;
|
|
87
|
+
renderAd?: (ctx: AdRenderContext) => ReactNode;
|
|
88
|
+
renderLayout?: (ctx: AdLayoutRenderContext) => ReactNode;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export type UseAdsFeedOptions = {
|
|
92
|
+
placement?: string;
|
|
93
|
+
enabled?: boolean;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type UseAdsFeedResult = {
|
|
97
|
+
feed: import("../types").AdsFeedResponse | null;
|
|
98
|
+
ad: AdFeedItem | null;
|
|
99
|
+
ads: AdFeedItem[];
|
|
100
|
+
layout?: string;
|
|
101
|
+
rotation?: AdFeedRotation;
|
|
102
|
+
loading: boolean;
|
|
103
|
+
error: Error | null;
|
|
104
|
+
refresh: () => Promise<void>;
|
|
105
|
+
};
|
package/src/ads-ui.tsx
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export {
|
|
2
|
+
Ad,
|
|
3
|
+
AdBanner,
|
|
4
|
+
AdFooter,
|
|
5
|
+
AdInline,
|
|
6
|
+
AdPreview,
|
|
7
|
+
AdSidebar,
|
|
8
|
+
AdsProvider,
|
|
9
|
+
defaultAdsTheme,
|
|
10
|
+
effectiveRotationMode,
|
|
11
|
+
layoutsForType,
|
|
12
|
+
defaultLayoutForType,
|
|
13
|
+
normalizeAdType,
|
|
14
|
+
normalizeLayout,
|
|
15
|
+
normalizeRotationMode,
|
|
16
|
+
useAdsFeed,
|
|
17
|
+
useAdRotation,
|
|
18
|
+
TEXT_LAYOUTS,
|
|
19
|
+
IMAGE_LAYOUTS,
|
|
20
|
+
AD_TYPES,
|
|
21
|
+
} from "./ads/index";
|
|
22
|
+
export type {
|
|
23
|
+
AdCreativeStyles,
|
|
24
|
+
AdLayoutRenderContext,
|
|
25
|
+
AdLayoutStyles,
|
|
26
|
+
AdPreviewProps,
|
|
27
|
+
AdProps,
|
|
28
|
+
AdRenderContext,
|
|
29
|
+
AdRotationStyles,
|
|
30
|
+
AdSlotViewProps,
|
|
31
|
+
AdsProviderProps,
|
|
32
|
+
AdsTheme,
|
|
33
|
+
InAppNavigate,
|
|
34
|
+
UseAdsFeedOptions,
|
|
35
|
+
UseAdsFeedResult,
|
|
36
|
+
UseAdRotationResult,
|
|
37
|
+
TextLayoutStyles,
|
|
38
|
+
ImageLayoutStyles,
|
|
39
|
+
} from "./ads/index";
|
|
40
|
+
export type { AdType, AdLayout, TextLayout, ImageLayout, AdRotationMode } from "./ads/constants";
|
package/src/ads.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated Use click_url from GET /ads/feed; the platform returns signed tracking URLs.
|
|
3
|
+
*/
|
|
4
|
+
export function buildAdClickUrl(clickBaseUrl: string, adId: string, productId?: string): string {
|
|
5
|
+
const base = clickBaseUrl.replace(/\/$/, "");
|
|
6
|
+
const id = adId.trim();
|
|
7
|
+
if (!base || !id) {
|
|
8
|
+
return "";
|
|
9
|
+
}
|
|
10
|
+
let url = `${base}/v1/ads/click/${encodeURIComponent(id)}`;
|
|
11
|
+
const product = productId?.trim();
|
|
12
|
+
if (product) {
|
|
13
|
+
url += `?product_id=${encodeURIComponent(product)}`;
|
|
14
|
+
}
|
|
15
|
+
return url;
|
|
16
|
+
}
|