@datlv-trustshop/shopify-inapp-components 0.1.9
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/README.md +141 -0
- package/dist/components/AppList.d.ts +13 -0
- package/dist/components/AppList.js +64 -0
- package/dist/components/ArticleList.d.ts +20 -0
- package/dist/components/ArticleList.js +174 -0
- package/dist/components/ArticleSlide.d.ts +14 -0
- package/dist/components/ArticleSlide.js +151 -0
- package/dist/components/FooterBanner.d.ts +10 -0
- package/dist/components/FooterBanner.js +72 -0
- package/dist/components/GrowApps.d.ts +13 -0
- package/dist/components/GrowApps.js +213 -0
- package/dist/components/ImageLoading.d.ts +15 -0
- package/dist/components/ImageLoading.js +66 -0
- package/dist/components/PartnerList.d.ts +9 -0
- package/dist/components/PartnerList.js +102 -0
- package/dist/components/PopupBanner.d.ts +12 -0
- package/dist/components/PopupBanner.js +100 -0
- package/dist/components/TopBanner.d.ts +14 -0
- package/dist/components/TopBanner.js +31 -0
- package/dist/components/WhatsNew.d.ts +14 -0
- package/dist/components/WhatsNew.js +258 -0
- package/dist/components/index.d.ts +9 -0
- package/dist/components/index.js +9 -0
- package/dist/components/inlineStyles.d.ts +110 -0
- package/dist/components/inlineStyles.js +114 -0
- package/dist/components/styles.d.ts +152 -0
- package/dist/components/styles.js +158 -0
- package/dist/core/adapter.d.ts +6 -0
- package/dist/core/adapter.js +301 -0
- package/dist/core/engine.d.ts +33 -0
- package/dist/core/engine.js +176 -0
- package/dist/core/fetcher.d.ts +4 -0
- package/dist/core/fetcher.js +72 -0
- package/dist/core/global-manager.d.ts +99 -0
- package/dist/core/global-manager.js +315 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/useApps.d.ts +3 -0
- package/dist/hooks/useApps.js +18 -0
- package/dist/hooks/useArticles.d.ts +11 -0
- package/dist/hooks/useArticles.js +49 -0
- package/dist/hooks/useBanner.d.ts +5 -0
- package/dist/hooks/useBanner.js +22 -0
- package/dist/hooks/useDashboard.d.ts +11 -0
- package/dist/hooks/useDashboard.js +13 -0
- package/dist/hooks/useGrowApps.d.ts +10 -0
- package/dist/hooks/useGrowApps.js +14 -0
- package/dist/hooks/useTranslations.d.ts +3 -0
- package/dist/hooks/useTranslations.js +9 -0
- package/dist/hooks/useWhatsNew.d.ts +11 -0
- package/dist/hooks/useWhatsNew.js +34 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +16 -0
- package/dist/provider/DashboardProvider.d.ts +36 -0
- package/dist/provider/DashboardProvider.js +184 -0
- package/dist/translations/default.d.ts +2 -0
- package/dist/translations/default.js +27 -0
- package/dist/types/app.d.ts +14 -0
- package/dist/types/app.js +1 -0
- package/dist/types/article.d.ts +14 -0
- package/dist/types/article.js +1 -0
- package/dist/types/banner.d.ts +22 -0
- package/dist/types/banner.js +1 -0
- package/dist/types/dashboard.d.ts +42 -0
- package/dist/types/dashboard.js +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.js +6 -0
- package/dist/types/partner.d.ts +8 -0
- package/dist/types/partner.js +1 -0
- package/dist/types/product-update.d.ts +23 -0
- package/dist/types/product-update.js +1 -0
- package/dist/types/translations.d.ts +28 -0
- package/dist/types/translations.js +1 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# @trustshop/shopify-inapp-components
|
|
2
|
+
|
|
3
|
+
React TypeScript components for Shopify in-app dashboard content management.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @trustshop/shopify-inapp-components
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import {
|
|
15
|
+
DashboardProvider,
|
|
16
|
+
TopBanner,
|
|
17
|
+
useTopBanner,
|
|
18
|
+
} from "@trustshop/shopify-inapp-components";
|
|
19
|
+
|
|
20
|
+
function App() {
|
|
21
|
+
return (
|
|
22
|
+
<DashboardProvider
|
|
23
|
+
config={{
|
|
24
|
+
apiUrl: "https://your-api.com/dashboard",
|
|
25
|
+
cacheTime: 300000, // 5 minutes
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
<YourDashboard />
|
|
29
|
+
</DashboardProvider>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function YourDashboard() {
|
|
34
|
+
const topBanner = useTopBanner();
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<TopBanner
|
|
38
|
+
renderBanner={(banner, handlers) => (
|
|
39
|
+
<Banner onDismiss={handlers.onClose}>
|
|
40
|
+
<h2>{banner.title}</h2>
|
|
41
|
+
<p>{banner.description}</p>
|
|
42
|
+
<button onClick={handlers.onAction}>{banner.linkText}</button>
|
|
43
|
+
</Banner>
|
|
44
|
+
)}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Features
|
|
51
|
+
|
|
52
|
+
- 🎯 **Singleton Pattern**: Efficient data management with single instance
|
|
53
|
+
- 🔄 **Smart Caching**: Automatic cache management with TTL
|
|
54
|
+
- 🎨 **Flexible UI**: Render prop pattern for custom component styling
|
|
55
|
+
- 🔒 **Type Safe**: Full TypeScript support
|
|
56
|
+
- ⚛️ **React 18+**: Support for StrictMode and SSR
|
|
57
|
+
- 🚀 **Performance**: Optimized for production use
|
|
58
|
+
|
|
59
|
+
## Components
|
|
60
|
+
|
|
61
|
+
### DashboardProvider
|
|
62
|
+
|
|
63
|
+
Main provider component that manages dashboard state.
|
|
64
|
+
|
|
65
|
+
### Banner Components
|
|
66
|
+
|
|
67
|
+
- `TopBanner` - Top notification banner
|
|
68
|
+
- `PopupBanner` - Modal/popup banner
|
|
69
|
+
- `FooterBanner` - Footer sticky banner
|
|
70
|
+
|
|
71
|
+
### Content Components
|
|
72
|
+
|
|
73
|
+
- `AppList` - Display apps by category
|
|
74
|
+
- `ArticleList` - Display articles/blog posts
|
|
75
|
+
- `WhatsNewList` - Product updates
|
|
76
|
+
|
|
77
|
+
## Hooks
|
|
78
|
+
|
|
79
|
+
- `useDashboard()` - Access full dashboard context
|
|
80
|
+
- `useTopBanner()` - Get top banner data
|
|
81
|
+
- `usePopupBanner()` - Get popup banner data
|
|
82
|
+
- `useFooterBanner()` - Get footer banner data
|
|
83
|
+
- `useApps()` - Access apps data
|
|
84
|
+
- `useArticles()` - Access articles data
|
|
85
|
+
- `useWhatsNew()` - Access product updates
|
|
86
|
+
|
|
87
|
+
## Configuration
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
interface DashboardConfig {
|
|
91
|
+
apiUrl: string;
|
|
92
|
+
cacheTime?: number; // Cache TTL in ms (default: 5 minutes)
|
|
93
|
+
retryAttempts?: number; // Retry attempts on failure (default: 3)
|
|
94
|
+
retryDelay?: number; // Delay between retries in ms (default: 1000)
|
|
95
|
+
headers?: Record<string, string>;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## API Response Adapter
|
|
100
|
+
|
|
101
|
+
The SDK automatically adapts various API response formats to a consistent internal structure, supporting flexible field names and nested data.
|
|
102
|
+
|
|
103
|
+
## Development
|
|
104
|
+
|
|
105
|
+
### Local Testing
|
|
106
|
+
|
|
107
|
+
For UI testing and development, use the `sdk-test-app` folder:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
cd sdk-test-app
|
|
111
|
+
npm install
|
|
112
|
+
npm run dev
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
This will start a development server at `http://localhost:3002` with:
|
|
116
|
+
|
|
117
|
+
- Real API integration (proxied to avoid CORS)
|
|
118
|
+
- Production-ready WhatsNew component
|
|
119
|
+
- Clean UI identical to deployment environment
|
|
120
|
+
|
|
121
|
+
### Project Structure
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
shopify-inapp-components/
|
|
125
|
+
├── src/ # Component source code
|
|
126
|
+
├── dist/ # Built package
|
|
127
|
+
├── sdk-test-app/ # Development testing environment
|
|
128
|
+
└── examples/ # Usage examples
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT
|
|
134
|
+
|
|
135
|
+
## Author
|
|
136
|
+
|
|
137
|
+
Your Name
|
|
138
|
+
|
|
139
|
+
## Contributing
|
|
140
|
+
|
|
141
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { AppItem, AppGroup } from "../types";
|
|
3
|
+
export interface AppListProps {
|
|
4
|
+
group?: AppGroup;
|
|
5
|
+
className?: string;
|
|
6
|
+
onAppClick?: (app: AppItem) => void;
|
|
7
|
+
showRating?: boolean;
|
|
8
|
+
showInstalls?: boolean;
|
|
9
|
+
showPrice?: boolean;
|
|
10
|
+
limit?: number;
|
|
11
|
+
gridColumns?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare const AppList: React.FC<AppListProps>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useApps } from "../hooks/useApps";
|
|
3
|
+
export const AppList = ({ group, className = "", onAppClick, showRating = true, showInstalls = false, showPrice = false, limit, gridColumns = 3, }) => {
|
|
4
|
+
const apps = useApps(group);
|
|
5
|
+
const displayApps = limit ? apps.slice(0, limit) : apps;
|
|
6
|
+
const handleAppClick = (app) => {
|
|
7
|
+
if (app.link) {
|
|
8
|
+
window.open(app.link, "_blank");
|
|
9
|
+
}
|
|
10
|
+
onAppClick?.(app);
|
|
11
|
+
};
|
|
12
|
+
if (displayApps.length === 0) {
|
|
13
|
+
return (_jsx("div", { className: `app-list-empty ${className}`, children: _jsx("p", { style: { textAlign: "center", color: "#999" }, children: "No apps available" }) }));
|
|
14
|
+
}
|
|
15
|
+
return (_jsx("div", { className: `app-list ${className}`, style: {
|
|
16
|
+
display: "grid",
|
|
17
|
+
gridTemplateColumns: `repeat(${gridColumns}, 1fr)`,
|
|
18
|
+
gap: "20px",
|
|
19
|
+
}, children: displayApps.map((app) => (_jsxs("div", { className: "app-item", style: {
|
|
20
|
+
padding: "16px",
|
|
21
|
+
border: "1px solid #e0e0e0",
|
|
22
|
+
borderRadius: "8px",
|
|
23
|
+
cursor: "pointer",
|
|
24
|
+
transition: "box-shadow 0.2s",
|
|
25
|
+
backgroundColor: "#ffffff",
|
|
26
|
+
}, onClick: () => handleAppClick(app), onMouseEnter: (e) => {
|
|
27
|
+
e.currentTarget.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.1)";
|
|
28
|
+
}, onMouseLeave: (e) => {
|
|
29
|
+
e.currentTarget.style.boxShadow = "none";
|
|
30
|
+
}, children: [_jsxs("div", { style: {
|
|
31
|
+
display: "flex",
|
|
32
|
+
alignItems: "start",
|
|
33
|
+
marginBottom: "12px",
|
|
34
|
+
}, children: [app.icon && (_jsx("img", { src: app.icon, alt: app.title, style: {
|
|
35
|
+
width: "48px",
|
|
36
|
+
height: "48px",
|
|
37
|
+
borderRadius: "8px",
|
|
38
|
+
marginRight: "12px",
|
|
39
|
+
} })), _jsxs("div", { style: { flex: 1 }, children: [_jsx("h3", { style: {
|
|
40
|
+
margin: "0 0 4px 0",
|
|
41
|
+
fontSize: "16px",
|
|
42
|
+
fontWeight: "600",
|
|
43
|
+
color: "#333",
|
|
44
|
+
}, children: app.title }), app.featured && (_jsx("span", { style: {
|
|
45
|
+
display: "inline-block",
|
|
46
|
+
padding: "2px 6px",
|
|
47
|
+
backgroundColor: "#ffd700",
|
|
48
|
+
color: "#333",
|
|
49
|
+
fontSize: "10px",
|
|
50
|
+
fontWeight: "bold",
|
|
51
|
+
borderRadius: "3px",
|
|
52
|
+
}, children: "FEATURED" }))] })] }), _jsx("p", { style: {
|
|
53
|
+
margin: "0 0 12px 0",
|
|
54
|
+
fontSize: "14px",
|
|
55
|
+
color: "#666",
|
|
56
|
+
lineHeight: "1.4",
|
|
57
|
+
}, children: app.description }), _jsxs("div", { style: {
|
|
58
|
+
display: "flex",
|
|
59
|
+
alignItems: "center",
|
|
60
|
+
justifyContent: "space-between",
|
|
61
|
+
fontSize: "12px",
|
|
62
|
+
color: "#999",
|
|
63
|
+
}, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", gap: "12px" }, children: [showRating && app.rating && (_jsxs("div", { style: { display: "flex", alignItems: "center" }, children: [_jsx("span", { style: { color: "#ffa500", marginRight: "4px" }, children: "\u2605" }), _jsx("span", { children: app.rating.toFixed(1) })] })), showInstalls && app.installs && (_jsxs("div", { children: [app.installs.toLocaleString(), " installs"] }))] }), showPrice && (_jsx("div", { style: { fontWeight: "600", color: "#333" }, children: app.price || "Free" }))] })] }, app.id))) }));
|
|
64
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ArticleItem } from "../types";
|
|
3
|
+
export interface ArticleListProps {
|
|
4
|
+
limit?: number;
|
|
5
|
+
className?: string;
|
|
6
|
+
onArticleClick?: (article: ArticleItem) => void;
|
|
7
|
+
layout?: "slide" | "list" | "grid";
|
|
8
|
+
showThumbnail?: boolean;
|
|
9
|
+
showAuthor?: boolean;
|
|
10
|
+
showDate?: boolean;
|
|
11
|
+
currentIndex?: number;
|
|
12
|
+
showNavigation?: boolean;
|
|
13
|
+
autoPlay?: boolean;
|
|
14
|
+
autoPlayInterval?: number;
|
|
15
|
+
onSlideChange?: (index: number) => void;
|
|
16
|
+
renderButton?: (article: ArticleItem, handlers: {
|
|
17
|
+
onClick: () => void;
|
|
18
|
+
}) => React.ReactNode;
|
|
19
|
+
}
|
|
20
|
+
export declare const ArticleList: React.FC<ArticleListProps>;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
|
+
import { Button, Box } from "@shopify/polaris";
|
|
4
|
+
import { ExternalIcon } from "@shopify/polaris-icons";
|
|
5
|
+
import { useDashboard } from "../hooks/useDashboard";
|
|
6
|
+
export const ArticleList = ({ limit, className = "", onArticleClick, layout = "slide", showThumbnail = true, currentIndex: externalCurrentIndex, showNavigation = true, autoPlay = false, autoPlayInterval = 5000, onSlideChange, renderButton, }) => {
|
|
7
|
+
const { data } = useDashboard();
|
|
8
|
+
const articles = data?.articles || [];
|
|
9
|
+
const displayArticles = limit ? articles.slice(0, limit) : articles;
|
|
10
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
11
|
+
const [internalCurrentIndex, setInternalCurrentIndex] = useState(0);
|
|
12
|
+
const currentIndex = externalCurrentIndex !== undefined
|
|
13
|
+
? externalCurrentIndex
|
|
14
|
+
: internalCurrentIndex;
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const checkMobile = () => {
|
|
17
|
+
setIsMobile(window.innerWidth <= 768);
|
|
18
|
+
};
|
|
19
|
+
checkMobile();
|
|
20
|
+
window.addEventListener("resize", checkMobile);
|
|
21
|
+
return () => window.removeEventListener("resize", checkMobile);
|
|
22
|
+
}, []);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!autoPlay || layout !== "slide" || displayArticles.length <= 1)
|
|
25
|
+
return;
|
|
26
|
+
const interval = setInterval(() => {
|
|
27
|
+
const newIndex = (currentIndex + 1) % displayArticles.length;
|
|
28
|
+
if (externalCurrentIndex === undefined) {
|
|
29
|
+
setInternalCurrentIndex(newIndex);
|
|
30
|
+
}
|
|
31
|
+
onSlideChange?.(newIndex);
|
|
32
|
+
}, autoPlayInterval);
|
|
33
|
+
return () => clearInterval(interval);
|
|
34
|
+
}, [
|
|
35
|
+
autoPlay,
|
|
36
|
+
layout,
|
|
37
|
+
displayArticles.length,
|
|
38
|
+
currentIndex,
|
|
39
|
+
autoPlayInterval,
|
|
40
|
+
externalCurrentIndex,
|
|
41
|
+
onSlideChange,
|
|
42
|
+
]);
|
|
43
|
+
const handleSlideChange = (newIndex) => {
|
|
44
|
+
if (externalCurrentIndex === undefined) {
|
|
45
|
+
setInternalCurrentIndex(newIndex);
|
|
46
|
+
}
|
|
47
|
+
onSlideChange?.(newIndex);
|
|
48
|
+
};
|
|
49
|
+
const handlePrevious = () => {
|
|
50
|
+
const newIndex = currentIndex === 0 ? displayArticles.length - 1 : currentIndex - 1;
|
|
51
|
+
handleSlideChange(newIndex);
|
|
52
|
+
};
|
|
53
|
+
const handleNext = () => {
|
|
54
|
+
const newIndex = (currentIndex + 1) % displayArticles.length;
|
|
55
|
+
handleSlideChange(newIndex);
|
|
56
|
+
};
|
|
57
|
+
const handleArticleClick = (article) => {
|
|
58
|
+
if (article.link) {
|
|
59
|
+
window.open(article.link, "_blank");
|
|
60
|
+
}
|
|
61
|
+
onArticleClick?.(article);
|
|
62
|
+
};
|
|
63
|
+
if (displayArticles.length === 0) {
|
|
64
|
+
return (_jsx("div", { className: className, style: { width: "100%" }, children: _jsx("p", { style: { textAlign: "center", color: "#999" }, children: "No articles available" }) }));
|
|
65
|
+
}
|
|
66
|
+
if (layout === "slide") {
|
|
67
|
+
let slidePercentage;
|
|
68
|
+
if (isMobile) {
|
|
69
|
+
slidePercentage = currentIndex * 90;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const itemWidth = 100 / displayArticles.length;
|
|
73
|
+
slidePercentage = currentIndex * itemWidth * 1.5;
|
|
74
|
+
}
|
|
75
|
+
return (_jsxs("div", { className: className, style: {
|
|
76
|
+
position: "relative",
|
|
77
|
+
overflow: "hidden",
|
|
78
|
+
width: "100%",
|
|
79
|
+
}, children: [_jsxs("div", { style: {
|
|
80
|
+
overflow: "hidden",
|
|
81
|
+
position: "relative",
|
|
82
|
+
}, children: [_jsx("div", { style: {
|
|
83
|
+
display: "flex",
|
|
84
|
+
transition: "transform 0.5s ease-in-out",
|
|
85
|
+
gap: "20px",
|
|
86
|
+
willChange: "transform",
|
|
87
|
+
transform: `translateX(-${slidePercentage}%)`,
|
|
88
|
+
}, children: displayArticles.map((article) => (_jsx("div", { style: {
|
|
89
|
+
flex: "0 0 auto",
|
|
90
|
+
width: isMobile ? "90%" : "270px",
|
|
91
|
+
}, children: _jsxs("div", { style: {
|
|
92
|
+
display: "flex",
|
|
93
|
+
flexDirection: "column",
|
|
94
|
+
gap: "4px",
|
|
95
|
+
}, children: [_jsxs("div", { style: {
|
|
96
|
+
display: "flex",
|
|
97
|
+
flexDirection: "column",
|
|
98
|
+
gap: "10px",
|
|
99
|
+
}, children: [showThumbnail && article.imageUrl && (_jsx("div", { style: {
|
|
100
|
+
width: isMobile ? "100%" : "270px",
|
|
101
|
+
height: "152px",
|
|
102
|
+
overflow: "hidden",
|
|
103
|
+
borderRadius: "8px",
|
|
104
|
+
}, children: _jsx("img", { src: article.imageUrl ||
|
|
105
|
+
"https://asset.trustshop.io/dashboard/news-review-summary.png", alt: article.title, style: {
|
|
106
|
+
width: "100%",
|
|
107
|
+
height: "100%",
|
|
108
|
+
objectFit: "cover",
|
|
109
|
+
} }) })), article.category && (_jsx("div", { style: {
|
|
110
|
+
color: "var(--p-color-text-emphasis, #616161)",
|
|
111
|
+
}, children: _jsx("span", { style: { fontSize: "13px" }, children: article.category }) }))] }), _jsxs("div", { style: {
|
|
112
|
+
display: "flex",
|
|
113
|
+
flexDirection: "column",
|
|
114
|
+
gap: "6px",
|
|
115
|
+
}, children: [_jsx("h3", { style: {
|
|
116
|
+
margin: 0,
|
|
117
|
+
fontSize: "14px",
|
|
118
|
+
fontWeight: "bold",
|
|
119
|
+
color: "#303030",
|
|
120
|
+
lineHeight: "1.4",
|
|
121
|
+
}, children: article.title }), renderButton ? (renderButton(article, {
|
|
122
|
+
onClick: () => handleArticleClick(article),
|
|
123
|
+
})) : (_jsx(Box, { children: _jsx(Button, { icon: ExternalIcon, onClick: () => handleArticleClick(article), variant: "plain", children: article.link ? "Read article" : "Learn more" }) }))] })] }) }, article.id))) }), showNavigation && displayArticles.length > 1 && (_jsxs(_Fragment, { children: [_jsx("button", { onClick: handlePrevious, "aria-label": "Previous slide", style: {
|
|
124
|
+
position: "absolute",
|
|
125
|
+
top: "50%",
|
|
126
|
+
transform: "translateY(-50%)",
|
|
127
|
+
left: "10px",
|
|
128
|
+
background: "rgba(255, 255, 255, 0.9)",
|
|
129
|
+
border: "1px solid #e3e3e3",
|
|
130
|
+
borderRadius: "50%",
|
|
131
|
+
width: "32px",
|
|
132
|
+
height: "32px",
|
|
133
|
+
display: "flex",
|
|
134
|
+
alignItems: "center",
|
|
135
|
+
justifyContent: "center",
|
|
136
|
+
cursor: "pointer",
|
|
137
|
+
zIndex: 10,
|
|
138
|
+
transition: "background-color 0.2s",
|
|
139
|
+
padding: 0,
|
|
140
|
+
}, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "#303030", children: _jsx("path", { d: "M10.5 13L5.5 8l5-5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", fill: "none" }) }) }), _jsx("button", { onClick: handleNext, "aria-label": "Next slide", style: {
|
|
141
|
+
position: "absolute",
|
|
142
|
+
top: "50%",
|
|
143
|
+
transform: "translateY(-50%)",
|
|
144
|
+
right: "10px",
|
|
145
|
+
background: "rgba(255, 255, 255, 0.9)",
|
|
146
|
+
border: "1px solid #e3e3e3",
|
|
147
|
+
borderRadius: "50%",
|
|
148
|
+
width: "32px",
|
|
149
|
+
height: "32px",
|
|
150
|
+
display: "flex",
|
|
151
|
+
alignItems: "center",
|
|
152
|
+
justifyContent: "center",
|
|
153
|
+
cursor: "pointer",
|
|
154
|
+
zIndex: 10,
|
|
155
|
+
transition: "background-color 0.2s",
|
|
156
|
+
padding: 0,
|
|
157
|
+
}, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "#303030", children: _jsx("path", { d: "M5.5 13l5-5-5-5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", fill: "none" }) }) })] }))] }), showNavigation && displayArticles.length > 1 && (_jsx("div", { style: {
|
|
158
|
+
display: "flex",
|
|
159
|
+
justifyContent: "center",
|
|
160
|
+
gap: "8px",
|
|
161
|
+
marginTop: "16px",
|
|
162
|
+
}, children: displayArticles.map((_, index) => (_jsx("button", { onClick: () => handleSlideChange(index), "aria-label": `Go to slide ${index + 1}`, style: {
|
|
163
|
+
width: "8px",
|
|
164
|
+
height: "8px",
|
|
165
|
+
borderRadius: "50%",
|
|
166
|
+
backgroundColor: index === currentIndex ? "#303030" : "#e3e3e3",
|
|
167
|
+
cursor: "pointer",
|
|
168
|
+
transition: "background-color 0.3s",
|
|
169
|
+
border: "none",
|
|
170
|
+
padding: 0,
|
|
171
|
+
} }, index))) }))] }));
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ArticleItem } from "../types";
|
|
3
|
+
export interface ArticleSlideProps {
|
|
4
|
+
limit?: number;
|
|
5
|
+
className?: string;
|
|
6
|
+
onArticleClick?: (article: ArticleItem) => void;
|
|
7
|
+
autoPlay?: boolean;
|
|
8
|
+
autoPlayInterval?: number;
|
|
9
|
+
showNavigation?: boolean;
|
|
10
|
+
renderButton?: (article: ArticleItem, handlers: {
|
|
11
|
+
onClick: () => void;
|
|
12
|
+
}) => React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
export declare const ArticleSlide: React.FC<ArticleSlideProps>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
|
+
import { useDashboard } from "../hooks/useDashboard";
|
|
4
|
+
export const ArticleSlide = ({ limit, className = "", onArticleClick, autoPlay = false, autoPlayInterval = 5000, showNavigation = true, renderButton, }) => {
|
|
5
|
+
const { data } = useDashboard();
|
|
6
|
+
const articles = data?.articles || [];
|
|
7
|
+
const displayArticles = limit ? articles.slice(0, limit) : articles;
|
|
8
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
9
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const checkMobile = () => {
|
|
12
|
+
setIsMobile(window.innerWidth <= 768);
|
|
13
|
+
};
|
|
14
|
+
checkMobile();
|
|
15
|
+
window.addEventListener("resize", checkMobile);
|
|
16
|
+
return () => window.removeEventListener("resize", checkMobile);
|
|
17
|
+
}, []);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (!autoPlay || displayArticles.length <= 1)
|
|
20
|
+
return;
|
|
21
|
+
const interval = setInterval(() => {
|
|
22
|
+
setCurrentIndex((prev) => (prev + 1) % displayArticles.length);
|
|
23
|
+
}, autoPlayInterval);
|
|
24
|
+
return () => clearInterval(interval);
|
|
25
|
+
}, [autoPlay, autoPlayInterval, displayArticles.length]);
|
|
26
|
+
const handleArticleClick = (article) => {
|
|
27
|
+
if (article.link) {
|
|
28
|
+
window.open(article.link, "_blank");
|
|
29
|
+
}
|
|
30
|
+
onArticleClick?.(article);
|
|
31
|
+
};
|
|
32
|
+
const handlePrevious = () => {
|
|
33
|
+
setCurrentIndex((prev) => prev === 0 ? displayArticles.length - 1 : prev - 1);
|
|
34
|
+
};
|
|
35
|
+
const handleNext = () => {
|
|
36
|
+
setCurrentIndex((prev) => (prev + 1) % displayArticles.length);
|
|
37
|
+
};
|
|
38
|
+
if (displayArticles.length === 0) {
|
|
39
|
+
return (_jsx("div", { className: className, style: { width: "100%" }, children: _jsx("p", { style: { textAlign: "center", color: "#999" }, children: "No articles available" }) }));
|
|
40
|
+
}
|
|
41
|
+
let slidePercentage;
|
|
42
|
+
if (isMobile) {
|
|
43
|
+
slidePercentage = currentIndex * 90;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
const itemWidth = 100 / displayArticles.length;
|
|
47
|
+
slidePercentage = currentIndex * itemWidth * 1.5;
|
|
48
|
+
}
|
|
49
|
+
return (_jsxs("div", { className: className, style: {
|
|
50
|
+
position: "relative",
|
|
51
|
+
overflow: "hidden",
|
|
52
|
+
width: "100%",
|
|
53
|
+
}, children: [_jsxs("div", { style: {
|
|
54
|
+
overflow: "hidden",
|
|
55
|
+
position: "relative",
|
|
56
|
+
}, children: [_jsx("div", { style: {
|
|
57
|
+
display: "flex",
|
|
58
|
+
transition: "transform 0.5s ease-in-out",
|
|
59
|
+
gap: "20px",
|
|
60
|
+
willChange: "transform",
|
|
61
|
+
transform: `translateX(-${slidePercentage}%)`,
|
|
62
|
+
}, children: displayArticles.map((article) => (_jsx("div", { style: {
|
|
63
|
+
flex: "0 0 auto",
|
|
64
|
+
width: isMobile ? "90%" : "270px",
|
|
65
|
+
}, children: _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "4px" }, children: [_jsxs("div", { style: {
|
|
66
|
+
display: "flex",
|
|
67
|
+
flexDirection: "column",
|
|
68
|
+
gap: "10px",
|
|
69
|
+
}, children: [article.thumbnail && (_jsx("div", { style: {
|
|
70
|
+
width: isMobile ? "100%" : "270px",
|
|
71
|
+
height: "152px",
|
|
72
|
+
overflow: "hidden",
|
|
73
|
+
borderRadius: "8px",
|
|
74
|
+
}, children: _jsx("img", { src: article.thumbnail ||
|
|
75
|
+
"https://asset.trustshop.io/dashboard/news-review-summary.png", alt: article.title, style: {
|
|
76
|
+
width: "100%",
|
|
77
|
+
height: "100%",
|
|
78
|
+
objectFit: "cover",
|
|
79
|
+
} }) })), article.category && (_jsx("div", { style: { color: "var(--p-color-text-emphasis, #616161)" }, children: _jsx("span", { style: { fontSize: "13px" }, children: article.category }) }))] }), _jsxs("div", { style: {
|
|
80
|
+
display: "flex",
|
|
81
|
+
flexDirection: "column",
|
|
82
|
+
gap: "6px",
|
|
83
|
+
}, children: [_jsx("h3", { style: {
|
|
84
|
+
margin: 0,
|
|
85
|
+
fontSize: "14px",
|
|
86
|
+
fontWeight: "bold",
|
|
87
|
+
color: "#303030",
|
|
88
|
+
lineHeight: "1.4",
|
|
89
|
+
}, children: article.title }), renderButton ? (renderButton(article, {
|
|
90
|
+
onClick: () => handleArticleClick(article),
|
|
91
|
+
})) : (_jsxs("button", { onClick: () => handleArticleClick(article), style: {
|
|
92
|
+
background: "none",
|
|
93
|
+
border: "none",
|
|
94
|
+
color: "var(--p-color-text, #303030)",
|
|
95
|
+
cursor: "pointer",
|
|
96
|
+
padding: "4px 0",
|
|
97
|
+
fontSize: "14px",
|
|
98
|
+
textAlign: "left",
|
|
99
|
+
display: "inline-flex",
|
|
100
|
+
alignItems: "center",
|
|
101
|
+
gap: "4px",
|
|
102
|
+
}, children: [article.link ? "Read article" : "Learn more", _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "currentColor", children: [_jsx("path", { d: "M17 2a1 1 0 0 1 1 1v4a1 1 0 1 1-2 0V4.414l-7.293 7.293a1 1 0 0 1-1.414-1.414L14.586 3H12a1 1 0 1 1 0-2h5Z" }), _jsx("path", { d: "M3 6a1 1 0 0 1 1-1h5a1 1 0 1 1 0 2H5v10h10v-4a1 1 0 1 1 2 0v5a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V6Z" })] })] }))] })] }) }, article.id))) }), showNavigation && displayArticles.length > 1 && (_jsxs(_Fragment, { children: [_jsx("button", { onClick: handlePrevious, "aria-label": "Previous slide", style: {
|
|
103
|
+
position: "absolute",
|
|
104
|
+
top: "50%",
|
|
105
|
+
transform: "translateY(-50%)",
|
|
106
|
+
left: "10px",
|
|
107
|
+
background: "rgba(255, 255, 255, 0.9)",
|
|
108
|
+
border: "1px solid #e3e3e3",
|
|
109
|
+
borderRadius: "50%",
|
|
110
|
+
width: "32px",
|
|
111
|
+
height: "32px",
|
|
112
|
+
display: "flex",
|
|
113
|
+
alignItems: "center",
|
|
114
|
+
justifyContent: "center",
|
|
115
|
+
cursor: "pointer",
|
|
116
|
+
zIndex: 10,
|
|
117
|
+
transition: "background-color 0.2s",
|
|
118
|
+
padding: 0,
|
|
119
|
+
}, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "#303030", children: _jsx("path", { d: "M10.5 13L5.5 8l5-5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", fill: "none" }) }) }), _jsx("button", { onClick: handleNext, "aria-label": "Next slide", style: {
|
|
120
|
+
position: "absolute",
|
|
121
|
+
top: "50%",
|
|
122
|
+
transform: "translateY(-50%)",
|
|
123
|
+
right: "10px",
|
|
124
|
+
background: "rgba(255, 255, 255, 0.9)",
|
|
125
|
+
border: "1px solid #e3e3e3",
|
|
126
|
+
borderRadius: "50%",
|
|
127
|
+
width: "32px",
|
|
128
|
+
height: "32px",
|
|
129
|
+
display: "flex",
|
|
130
|
+
alignItems: "center",
|
|
131
|
+
justifyContent: "center",
|
|
132
|
+
cursor: "pointer",
|
|
133
|
+
zIndex: 10,
|
|
134
|
+
transition: "background-color 0.2s",
|
|
135
|
+
padding: 0,
|
|
136
|
+
}, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "#303030", children: _jsx("path", { d: "M5.5 13l5-5-5-5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", fill: "none" }) }) })] }))] }), showNavigation && displayArticles.length > 1 && (_jsx("div", { style: {
|
|
137
|
+
display: "flex",
|
|
138
|
+
justifyContent: "center",
|
|
139
|
+
gap: "8px",
|
|
140
|
+
marginTop: "16px",
|
|
141
|
+
}, children: displayArticles.map((_, index) => (_jsx("button", { onClick: () => setCurrentIndex(index), "aria-label": `Go to slide ${index + 1}`, style: {
|
|
142
|
+
width: "8px",
|
|
143
|
+
height: "8px",
|
|
144
|
+
borderRadius: "50%",
|
|
145
|
+
backgroundColor: index === currentIndex ? "#303030" : "#e3e3e3",
|
|
146
|
+
cursor: "pointer",
|
|
147
|
+
transition: "background-color 0.3s",
|
|
148
|
+
border: "none",
|
|
149
|
+
padding: 0,
|
|
150
|
+
} }, index))) }))] }));
|
|
151
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { BannerItem } from "../types";
|
|
3
|
+
export interface FooterBannerProps {
|
|
4
|
+
className?: string;
|
|
5
|
+
onClose?: () => void;
|
|
6
|
+
onAction?: (banner: BannerItem) => void;
|
|
7
|
+
closable?: boolean;
|
|
8
|
+
sticky?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare const FooterBanner: React.FC<FooterBannerProps>;
|