@datlv-trustshop/shopify-inapp-components 0.1.20 → 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.
@@ -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;
@@ -113,7 +113,7 @@ export const PartnerIntegration = ({ className = "", onManage, onInstall, onOpen
113
113
  }, title: status.upgradeBadgeTooltip, children: [status.upgradeBadgeIcon && (_jsx("span", { style: {
114
114
  display: "inline-flex",
115
115
  alignItems: "center",
116
- }, children: status.upgradeBadgeIcon })), _jsx("span", { children: status.upgradeBadgeText || translations.partnerIntegration?.upgradeBadge?.upgrade || "Upgrade" })] }))] }), item.content && (_jsx(Text, { as: "p", variant: "bodyMd", tone: "subdued", children: item.content }))] }), _jsx(InlineStack, { gap: "200", children: status ? (_jsxs(_Fragment, { children: [item.button_install_text && (_jsx(Button, { icon: ExternalIcon, onClick: () => handleInstall(item), children: item.button_install_text })), item.button_manage_text && (_jsx(Button, { onClick: () => handleManage(item), disabled: item.key === "after_ship" ? (status.showUpgradeBadge === true) : status.showUpgradeBadge === true, children: item.button_manage_text }))] })) : (_jsxs(_Fragment, { children: [item.button_manage_text && (_jsx(Button, { onClick: () => handleManage(item), children: item.button_manage_text })), item.button_install_text && (_jsx(Button, { icon: ExternalIcon, onClick: () => handleInstall(item), children: item.button_install_text }))] })) })] }) })] }) }) }, `integration--${item.id || item.key}`));
116
+ }, children: status.upgradeBadgeIcon })), _jsx("span", { children: status.upgradeBadgeText || translations.partnerIntegration?.upgradeBadge?.upgrade || "Upgrade" })] }))] }), item.content && (_jsx(Text, { as: "p", variant: "bodyMd", children: item.content }))] }), _jsx(InlineStack, { gap: "200", children: status ? (_jsxs(_Fragment, { children: [item.button_install_text && (_jsx(Button, { icon: ExternalIcon, onClick: () => handleInstall(item), children: item.button_install_text })), item.button_manage_text && (_jsx(Button, { onClick: () => handleManage(item), disabled: item.key === "after_ship" ? (status.showUpgradeBadge === true) : status.showUpgradeBadge === true, children: item.button_manage_text }))] })) : (_jsxs(_Fragment, { children: [item.button_manage_text && (_jsx(Button, { onClick: () => handleManage(item), children: item.button_manage_text })), item.button_install_text && (_jsx(Button, { icon: ExternalIcon, onClick: () => handleInstall(item), children: item.button_install_text }))] })) })] }) })] }) }) }, `integration--${item.id || item.key}`));
117
117
  };
118
118
  // Loading state
119
119
  if (loading) {
@@ -8,3 +8,4 @@ export * from "./WhatsNew";
8
8
  export * from "./GrowApps";
9
9
  export * from "./PartnerList";
10
10
  export * from "./PartnerIntegration";
11
+ export * from "./FloatingCard";
@@ -8,3 +8,4 @@ export * from "./WhatsNew";
8
8
  export * from "./GrowApps";
9
9
  export * from "./PartnerList";
10
10
  export * from "./PartnerIntegration";
11
+ export * from "./FloatingCard";
@@ -5,3 +5,4 @@ export * from "./useArticles";
5
5
  export * from "./useWhatsNew";
6
6
  export * from "./useGrowApps";
7
7
  export * from "./usePartnerIntegration";
8
+ export * from "./useFloatingCards";
@@ -5,3 +5,4 @@ export * from "./useArticles";
5
5
  export * from "./useWhatsNew";
6
6
  export * from "./useGrowApps";
7
7
  export * from "./usePartnerIntegration";
8
+ export * from "./useFloatingCards";
@@ -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 ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datlv-trustshop/shopify-inapp-components",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "private": false,
5
5
  "description": "React TypeScript components for Shopify in-app dashboard content",
6
6
  "main": "dist/index.js",