@datlv-trustshop/shopify-inapp-components 0.1.21 → 0.1.22
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/dist/components/FloatingCard.d.ts +41 -0
- package/dist/components/FloatingCard.js +63 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useFloatingCards.d.ts +32 -0
- package/dist/hooks/useFloatingCards.js +167 -0
- package/dist/hooks/usePartnerIntegration.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface FloatingCardData {
|
|
3
|
+
id: number;
|
|
4
|
+
key: string;
|
|
5
|
+
type: string;
|
|
6
|
+
priority: number;
|
|
7
|
+
title: string;
|
|
8
|
+
description: string;
|
|
9
|
+
date: string;
|
|
10
|
+
tag?: {
|
|
11
|
+
label: string;
|
|
12
|
+
tone: "success" | "info" | "warning" | "critical" | "new";
|
|
13
|
+
};
|
|
14
|
+
img_url: string;
|
|
15
|
+
image_alt?: string | null;
|
|
16
|
+
image_type: string;
|
|
17
|
+
primary_action?: {
|
|
18
|
+
label: string;
|
|
19
|
+
url: string;
|
|
20
|
+
external: boolean;
|
|
21
|
+
variant: "primary" | "secondary";
|
|
22
|
+
};
|
|
23
|
+
secondary_action?: {
|
|
24
|
+
label: string;
|
|
25
|
+
url: string;
|
|
26
|
+
external: boolean;
|
|
27
|
+
variant: "primary" | "secondary";
|
|
28
|
+
};
|
|
29
|
+
dismissible: boolean;
|
|
30
|
+
auto_close_after?: number | null;
|
|
31
|
+
reappear_interval?: number | null;
|
|
32
|
+
display_pages?: string[];
|
|
33
|
+
}
|
|
34
|
+
interface FloatingCardProps {
|
|
35
|
+
data: FloatingCardData;
|
|
36
|
+
onDismiss?: () => void;
|
|
37
|
+
onPrimaryAction?: (data: FloatingCardData) => void;
|
|
38
|
+
onSecondaryAction?: (data: FloatingCardData) => void;
|
|
39
|
+
}
|
|
40
|
+
export declare const FloatingCard: React.FC<FloatingCardProps>;
|
|
41
|
+
export default FloatingCard;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Card, BlockStack, InlineStack, Text, Badge, Button, Box, } from "@shopify/polaris";
|
|
3
|
+
import { XIcon } from "@shopify/polaris-icons";
|
|
4
|
+
export const FloatingCard = ({ data, onDismiss, onPrimaryAction, onSecondaryAction, }) => {
|
|
5
|
+
const handlePrimaryAction = () => {
|
|
6
|
+
if (data.primary_action) {
|
|
7
|
+
const url = data.primary_action.url;
|
|
8
|
+
// Check if it's an external URL
|
|
9
|
+
if (data.primary_action.external) {
|
|
10
|
+
window.open(url, "_blank");
|
|
11
|
+
}
|
|
12
|
+
// Check if it's a modal trigger
|
|
13
|
+
else if (url === "modal" || url === "/pricing") {
|
|
14
|
+
// Pass the full card data to the handler for modal handling
|
|
15
|
+
onPrimaryAction?.(data);
|
|
16
|
+
}
|
|
17
|
+
// Check if it's an internal route
|
|
18
|
+
else if (url?.startsWith("/")) {
|
|
19
|
+
// Pass the full card data for internal navigation
|
|
20
|
+
onPrimaryAction?.(data);
|
|
21
|
+
}
|
|
22
|
+
// Default: just call the handler
|
|
23
|
+
else {
|
|
24
|
+
onPrimaryAction?.(data);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const handleSecondaryAction = () => {
|
|
29
|
+
if (data.secondary_action) {
|
|
30
|
+
const url = data.secondary_action.url;
|
|
31
|
+
if (data.secondary_action.external) {
|
|
32
|
+
window.open(url, "_blank");
|
|
33
|
+
}
|
|
34
|
+
else if (url?.startsWith("/") || url === "modal") {
|
|
35
|
+
// Pass the full card data for internal handling
|
|
36
|
+
onSecondaryAction?.(data);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
onSecondaryAction?.(data);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
return (_jsx("div", { className: "floating-card", children: _jsxs(Card, { padding: "0", roundedAbove: "sm", children: [data.dismissible && (_jsx("button", { className: "floating-card-close", onClick: onDismiss, "aria-label": "Close", children: _jsx(XIcon, {}) })), _jsxs(BlockStack, { gap: "0", children: [data.img_url && (_jsx("div", { className: "floating-card-image-container", children: _jsx("img", { src: data.img_url, alt: data.image_alt || data.title, style: {
|
|
44
|
+
width: "100%",
|
|
45
|
+
height: "100%",
|
|
46
|
+
objectFit: "cover",
|
|
47
|
+
}, onError: (e) => {
|
|
48
|
+
console.error("Image failed to load:", data.img_url);
|
|
49
|
+
e.target.style.display = "none";
|
|
50
|
+
} }) })), _jsx(Box, { padding: "400", children: _jsxs(BlockStack, { gap: "500", children: [_jsxs(BlockStack, { gap: "300", children: [_jsxs("div", { style: {
|
|
51
|
+
display: "flex",
|
|
52
|
+
flexDirection: "column",
|
|
53
|
+
gap: "6px",
|
|
54
|
+
}, children: [_jsxs("div", { style: {
|
|
55
|
+
display: "flex",
|
|
56
|
+
flexDirection: "row",
|
|
57
|
+
gap: "6px",
|
|
58
|
+
alignItems: "start"
|
|
59
|
+
}, children: [_jsx(Text, { as: "h2", variant: "bodyLg", fontWeight: "semibold", children: data.title }), data.tag && (_jsx(Badge, { tone: data.tag.tone || "success", children: data.tag.label }))] }), _jsx(Text, { as: "p", variant: "bodySm", children: data.date })] }), _jsx(Text, { as: "p", variant: "bodyMd", children: data.description })] }), _jsxs(InlineStack, { gap: "200", children: [data.primary_action && (_jsx(Button, { variant: data.primary_action.variant === "primary"
|
|
60
|
+
? "primary"
|
|
61
|
+
: "plain", onClick: handlePrimaryAction, children: data.primary_action.label })), data.secondary_action && (_jsx(Button, { variant: "tertiary", onClick: handleSecondaryAction, children: data.secondary_action.label }))] })] }) })] })] }) }));
|
|
62
|
+
};
|
|
63
|
+
export default FloatingCard;
|
package/dist/components/index.js
CHANGED
package/dist/hooks/index.d.ts
CHANGED
package/dist/hooks/index.js
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { FloatingCardData } from "../components/FloatingCard";
|
|
2
|
+
export type { FloatingCardData } from "../components/FloatingCard";
|
|
3
|
+
interface UseFloatingCardsOptions {
|
|
4
|
+
shopId?: string;
|
|
5
|
+
autoFetch?: boolean;
|
|
6
|
+
currentRoute?: string;
|
|
7
|
+
locale?: string;
|
|
8
|
+
}
|
|
9
|
+
interface UseFloatingCardsReturn {
|
|
10
|
+
cards: FloatingCardData[];
|
|
11
|
+
loading: boolean;
|
|
12
|
+
error: Error | null;
|
|
13
|
+
dismissedCards: Set<number>;
|
|
14
|
+
dismissCard: (cardId: number) => void;
|
|
15
|
+
resetDismissedCards: () => void;
|
|
16
|
+
fetchCards: () => Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Hook to fetch and manage floating cards (campaigns)
|
|
20
|
+
*/
|
|
21
|
+
export declare function useFloatingCards(options?: UseFloatingCardsOptions): UseFloatingCardsReturn;
|
|
22
|
+
/**
|
|
23
|
+
* Get visible (non-dismissed) cards
|
|
24
|
+
*/
|
|
25
|
+
export declare function useVisibleFloatingCards(options?: UseFloatingCardsOptions): {
|
|
26
|
+
visibleCards: FloatingCardData[];
|
|
27
|
+
loading: boolean;
|
|
28
|
+
error: Error | null;
|
|
29
|
+
dismissCard: (cardId: number) => void;
|
|
30
|
+
resetDismissedCards: () => void;
|
|
31
|
+
hasDismissedCards: boolean;
|
|
32
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from "react";
|
|
2
|
+
import { useDashboardContext } from "../provider/DashboardProvider";
|
|
3
|
+
/**
|
|
4
|
+
* Hook to fetch and manage floating cards (campaigns)
|
|
5
|
+
*/
|
|
6
|
+
export function useFloatingCards(options = {}) {
|
|
7
|
+
const { shopId, autoFetch = true, locale: propsLocale } = options;
|
|
8
|
+
// Get locale from DashboardProvider context
|
|
9
|
+
const dashboardContext = useDashboardContext();
|
|
10
|
+
const contextLocale = dashboardContext?.locale;
|
|
11
|
+
// Use props locale if provided, otherwise use context locale, fallback to 'en'
|
|
12
|
+
const locale = propsLocale || contextLocale || 'en';
|
|
13
|
+
const [cards, setCards] = useState([]);
|
|
14
|
+
const [loading, setLoading] = useState(false);
|
|
15
|
+
const [error, setError] = useState(null);
|
|
16
|
+
const [dismissedCards, setDismissedCards] = useState(new Set());
|
|
17
|
+
const fetchCards = useCallback(async () => {
|
|
18
|
+
if (!shopId) {
|
|
19
|
+
setError(new Error("Shop ID is required"));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
setLoading(true);
|
|
23
|
+
setError(null);
|
|
24
|
+
try {
|
|
25
|
+
const id = shopId;
|
|
26
|
+
// Use proxy in development
|
|
27
|
+
const apiUrl = typeof window !== "undefined" &&
|
|
28
|
+
window.location.hostname === "localhost"
|
|
29
|
+
? `/api/campaigns?shop_id=${id}&locale=${locale}`
|
|
30
|
+
: `https://ops.trustshop.io/api/campaigns?shop_id=${id}&locale=${locale}`;
|
|
31
|
+
const response = await fetch(apiUrl, {
|
|
32
|
+
method: "GET",
|
|
33
|
+
headers: {
|
|
34
|
+
Accept: "application/json",
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
},
|
|
37
|
+
credentials: "include",
|
|
38
|
+
});
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw new Error(`Failed to fetch campaigns: ${response.statusText}`);
|
|
41
|
+
}
|
|
42
|
+
const data = await response.json();
|
|
43
|
+
if (data.success && Array.isArray(data.data)) {
|
|
44
|
+
// Transform the API response to match our FloatingCardData interface
|
|
45
|
+
const transformedCards = data.data.map((card) => ({
|
|
46
|
+
...card,
|
|
47
|
+
// Ensure date format
|
|
48
|
+
date: card.date || `Updated: ${new Date().toLocaleDateString()}`,
|
|
49
|
+
// Transform tag tone if needed
|
|
50
|
+
tag: card.tag
|
|
51
|
+
? {
|
|
52
|
+
...card.tag,
|
|
53
|
+
tone: card.tag.tone || "info",
|
|
54
|
+
}
|
|
55
|
+
: undefined,
|
|
56
|
+
}));
|
|
57
|
+
setCards(transformedCards);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
setCards([]);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
setError(err instanceof Error ? err : new Error("Unknown error"));
|
|
65
|
+
setCards([]);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
setLoading(false);
|
|
69
|
+
}
|
|
70
|
+
}, [shopId, locale]);
|
|
71
|
+
const dismissCard = useCallback((cardId) => {
|
|
72
|
+
setDismissedCards((prev) => {
|
|
73
|
+
const newSet = new Set(prev);
|
|
74
|
+
newSet.add(cardId);
|
|
75
|
+
// Store in localStorage for persistence
|
|
76
|
+
try {
|
|
77
|
+
localStorage.setItem("trustshop_dismissed_cards", JSON.stringify(Array.from(newSet)));
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
console.error("Failed to save dismissed cards:", e);
|
|
81
|
+
}
|
|
82
|
+
return newSet;
|
|
83
|
+
});
|
|
84
|
+
}, []);
|
|
85
|
+
const resetDismissedCards = useCallback(() => {
|
|
86
|
+
setDismissedCards(new Set());
|
|
87
|
+
try {
|
|
88
|
+
localStorage.removeItem("trustshop_dismissed_cards");
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
console.error("Failed to clear dismissed cards:", e);
|
|
92
|
+
}
|
|
93
|
+
}, []);
|
|
94
|
+
// Load dismissed cards from localStorage on mount
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
try {
|
|
97
|
+
const stored = localStorage.getItem("trustshop_dismissed_cards");
|
|
98
|
+
if (stored) {
|
|
99
|
+
const parsed = JSON.parse(stored);
|
|
100
|
+
if (Array.isArray(parsed)) {
|
|
101
|
+
setDismissedCards(new Set(parsed));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
console.error("Failed to load dismissed cards:", e);
|
|
107
|
+
}
|
|
108
|
+
}, []);
|
|
109
|
+
// Auto-fetch on mount or when shopId/locale changes
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (autoFetch && shopId) {
|
|
112
|
+
fetchCards();
|
|
113
|
+
}
|
|
114
|
+
}, [autoFetch, shopId, fetchCards]); // fetchCards already depends on locale
|
|
115
|
+
return {
|
|
116
|
+
cards,
|
|
117
|
+
loading,
|
|
118
|
+
error,
|
|
119
|
+
dismissedCards,
|
|
120
|
+
dismissCard,
|
|
121
|
+
resetDismissedCards,
|
|
122
|
+
fetchCards,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get visible (non-dismissed) cards
|
|
127
|
+
*/
|
|
128
|
+
export function useVisibleFloatingCards(options = {}) {
|
|
129
|
+
const { currentRoute, ...restOptions } = options;
|
|
130
|
+
// Note: locale is passed through in restOptions
|
|
131
|
+
const { cards, loading, error, dismissedCards, dismissCard, resetDismissedCards, } = useFloatingCards(restOptions);
|
|
132
|
+
// Filter cards based on:
|
|
133
|
+
// 1. Not dismissed
|
|
134
|
+
// 2. Route matching (if display_pages is set)
|
|
135
|
+
const visibleCards = cards.filter((card) => {
|
|
136
|
+
// Check if card is dismissed
|
|
137
|
+
if (dismissedCards.has(card.id)) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
// Check display_pages logic
|
|
141
|
+
if (card.display_pages !== undefined) {
|
|
142
|
+
// If display_pages is empty array, show on all pages
|
|
143
|
+
if (card.display_pages.length === 0) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
// If display_pages has values, check if current route matches
|
|
147
|
+
if (currentRoute) {
|
|
148
|
+
// Extract the page key from the route (e.g., "/settings" -> "settings")
|
|
149
|
+
const currentPage = currentRoute.replace(/^\//, '').split('/')[0];
|
|
150
|
+
return card.display_pages.includes(currentPage);
|
|
151
|
+
}
|
|
152
|
+
// If no current route provided, don't show the card
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
// If display_pages is not defined, show on all pages (backward compatibility)
|
|
156
|
+
return true;
|
|
157
|
+
});
|
|
158
|
+
const hasDismissedCards = dismissedCards.size > 0;
|
|
159
|
+
return {
|
|
160
|
+
visibleCards,
|
|
161
|
+
loading,
|
|
162
|
+
error,
|
|
163
|
+
dismissCard,
|
|
164
|
+
resetDismissedCards,
|
|
165
|
+
hasDismissedCards,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
@@ -9,7 +9,7 @@ export function usePartnerIntegrations() {
|
|
|
9
9
|
const integrations = state.data?.integrations || [];
|
|
10
10
|
const groupsMap = [];
|
|
11
11
|
// Group by category_key
|
|
12
|
-
const reviewSources = integrations.filter((item) => item.category_key === "review_sources");
|
|
12
|
+
const reviewSources = integrations.filter((item) => item.category_key === "review_sources" && item.key !== "google_reviews");
|
|
13
13
|
if (reviewSources.length > 0) {
|
|
14
14
|
groupsMap.push({
|
|
15
15
|
title: translations.partnerIntegration?.reviewSourcesTitle ||
|