@lodashventure/medusa-campaign 1.4.1 → 1.4.3
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/.medusa/server/src/admin/index.js +741 -306
- package/.medusa/server/src/admin/index.mjs +742 -307
- package/.medusa/server/src/api/admin/buy-x-get-y/[id]/route.js +2 -6
- package/.medusa/server/src/api/admin/coupons/[id]/route.js +76 -0
- package/.medusa/server/src/api/admin/coupons/route.js +88 -0
- package/.medusa/server/src/api/middlewares.js +32 -1
- package/.medusa/server/src/api/store/campaigns/route.js +78 -7
- package/.medusa/server/src/api/store/coupons/public/route.js +110 -0
- package/.medusa/server/src/api/store/customers/me/coupons/route.js +148 -0
- package/.medusa/server/src/modules/custom-campaigns/migrations/Migration20251024000000.js +2 -2
- package/.medusa/server/src/modules/custom-campaigns/migrations/Migration20251025000000.js +2 -2
- package/.medusa/server/src/modules/custom-campaigns/migrations/Migration20251101000000.js +16 -0
- package/.medusa/server/src/modules/custom-campaigns/types/campaign-type.enum.js +2 -1
- package/.medusa/server/src/workflows/custom-campaign/createCouponCampaignWorkflow.js +105 -0
- package/.medusa/server/src/workflows/custom-campaign/updateCouponCampaignWorkflow.js +59 -0
- package/.medusa/server/src/workflows/index.js +6 -2
- package/package.json +15 -30
- package/src/admin/components/BuyXGetYForm.tsx +24 -13
- package/src/admin/components/CouponForm.tsx +352 -0
- package/src/admin/components/CouponPage.tsx +104 -0
- package/src/admin/components/ProductSelector.tsx +22 -11
- package/src/admin/hooks/useCouponById.ts +36 -0
- package/src/admin/hooks/useCoupons.ts +46 -0
- package/src/admin/hooks/useFlashSaleById.ts +36 -10
- package/src/admin/hooks/useFlashSales.ts +36 -10
- package/src/admin/routes/coupons/[id]/page.tsx +147 -0
- package/src/admin/routes/coupons/create/page.tsx +49 -0
- package/src/admin/routes/coupons/page.tsx +15 -0
- package/src/admin/routes/flash-sales/[id]/page.tsx +2 -11
- package/src/admin/routes/flash-sales/create/page.tsx +0 -6
- package/src/admin/widgets/campaign-detail-widget.tsx +33 -26
- package/src/api/admin/buy-x-get-y/[id]/route.ts +11 -15
- package/src/api/admin/coupons/[id]/route.ts +98 -0
- package/src/api/admin/coupons/route.ts +109 -0
- package/src/api/middlewares.ts +34 -0
- package/src/api/store/campaigns/route.ts +107 -24
- package/src/api/store/coupons/public/route.ts +165 -0
- package/src/api/store/customers/me/coupons/route.ts +244 -0
- package/src/modules/custom-campaigns/migrations/Migration20251024000000.ts +1 -1
- package/src/modules/custom-campaigns/migrations/Migration20251025000000.ts +1 -1
- package/src/modules/custom-campaigns/migrations/Migration20251101000000.ts +21 -0
- package/src/modules/custom-campaigns/types/campaign-type.enum.ts +1 -0
- package/src/workflows/custom-campaign/createCouponCampaignWorkflow.ts +176 -0
- package/src/workflows/custom-campaign/updateCouponCampaignWorkflow.ts +105 -0
- package/src/workflows/index.ts +3 -1
- package/src/admin/widgets/campaign-stats-widget.tsx +0 -238
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
import { defineWidgetConfig } from "@medusajs/admin-sdk";
|
|
2
|
-
import { Container, Heading, Text, Badge } from "@medusajs/ui";
|
|
3
|
-
import {
|
|
4
|
-
Sparkles,
|
|
5
|
-
PhotoSolid,
|
|
6
|
-
CheckCircleSolid,
|
|
7
|
-
ClockSolid,
|
|
8
|
-
} from "@medusajs/icons";
|
|
9
|
-
import { useState, useEffect } from "react";
|
|
10
|
-
|
|
11
|
-
interface CampaignStats {
|
|
12
|
-
total: number;
|
|
13
|
-
active: number;
|
|
14
|
-
inactive: number;
|
|
15
|
-
with_images: number;
|
|
16
|
-
with_content: number;
|
|
17
|
-
complete: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const CampaignStatsWidget = () => {
|
|
21
|
-
const [stats, setStats] = useState<CampaignStats>({
|
|
22
|
-
total: 0,
|
|
23
|
-
active: 0,
|
|
24
|
-
inactive: 0,
|
|
25
|
-
with_images: 0,
|
|
26
|
-
with_content: 0,
|
|
27
|
-
complete: 0,
|
|
28
|
-
});
|
|
29
|
-
const [loading, setLoading] = useState(true);
|
|
30
|
-
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
fetchStats();
|
|
33
|
-
}, []);
|
|
34
|
-
|
|
35
|
-
const fetchStats = async () => {
|
|
36
|
-
setLoading(true);
|
|
37
|
-
try {
|
|
38
|
-
const response = await fetch("/admin/flash-sales?limit=100", {
|
|
39
|
-
credentials: "include",
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
if (response.ok) {
|
|
43
|
-
const data = await response.json();
|
|
44
|
-
const campaigns = data.campaigns || [];
|
|
45
|
-
|
|
46
|
-
// Calculate stats
|
|
47
|
-
const now = new Date();
|
|
48
|
-
let active = 0;
|
|
49
|
-
let inactive = 0;
|
|
50
|
-
let withImages = 0;
|
|
51
|
-
let withContent = 0;
|
|
52
|
-
let complete = 0;
|
|
53
|
-
|
|
54
|
-
campaigns.forEach((campaign: any) => {
|
|
55
|
-
// Count active/inactive
|
|
56
|
-
if (campaign.ends_at && new Date(campaign.ends_at) > now) {
|
|
57
|
-
active++;
|
|
58
|
-
} else {
|
|
59
|
-
inactive++;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Count campaigns with details
|
|
63
|
-
if (campaign.campaign_detail) {
|
|
64
|
-
if (
|
|
65
|
-
campaign.campaign_detail.image_url ||
|
|
66
|
-
campaign.campaign_detail.thumbnail_url
|
|
67
|
-
) {
|
|
68
|
-
withImages++;
|
|
69
|
-
}
|
|
70
|
-
if (campaign.campaign_detail.detail_content) {
|
|
71
|
-
withContent++;
|
|
72
|
-
}
|
|
73
|
-
// Complete = has both images and content
|
|
74
|
-
if (
|
|
75
|
-
(campaign.campaign_detail.image_url ||
|
|
76
|
-
campaign.campaign_detail.thumbnail_url) &&
|
|
77
|
-
campaign.campaign_detail.detail_content
|
|
78
|
-
) {
|
|
79
|
-
complete++;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
setStats({
|
|
85
|
-
total: campaigns.length,
|
|
86
|
-
active,
|
|
87
|
-
inactive,
|
|
88
|
-
with_images: withImages,
|
|
89
|
-
with_content: withContent,
|
|
90
|
-
complete,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
} catch (err) {
|
|
94
|
-
console.error("Error fetching campaign stats:", err);
|
|
95
|
-
} finally {
|
|
96
|
-
setLoading(false);
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
if (loading) {
|
|
101
|
-
return (
|
|
102
|
-
<Container className="px-6 py-6">
|
|
103
|
-
<div className="flex items-center gap-3 mb-4">
|
|
104
|
-
<Sparkles className="text-ui-fg-subtle" />
|
|
105
|
-
<Heading level="h2">Campaign Overview</Heading>
|
|
106
|
-
</div>
|
|
107
|
-
<Text className="text-ui-fg-subtle">Loading stats...</Text>
|
|
108
|
-
</Container>
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const completionRate =
|
|
113
|
-
stats.total > 0 ? Math.round((stats.complete / stats.total) * 100) : 0;
|
|
114
|
-
|
|
115
|
-
return (
|
|
116
|
-
<Container className="px-6 py-6">
|
|
117
|
-
<div className="flex items-center gap-3 mb-6">
|
|
118
|
-
<Sparkles className="text-ui-fg-subtle" />
|
|
119
|
-
<Heading level="h2">Campaign Overview</Heading>
|
|
120
|
-
</div>
|
|
121
|
-
|
|
122
|
-
{/* Main Stats Grid */}
|
|
123
|
-
<div className="grid grid-cols-3 gap-4 mb-6">
|
|
124
|
-
{/* Total Campaigns */}
|
|
125
|
-
<div className="p-4 rounded-lg border bg-ui-bg-subtle">
|
|
126
|
-
<div className="flex items-center justify-between mb-2">
|
|
127
|
-
<Text className="text-xs text-ui-fg-subtle">Total</Text>
|
|
128
|
-
<Sparkles className="h-4 w-4 text-ui-fg-subtle" />
|
|
129
|
-
</div>
|
|
130
|
-
<Text className="text-2xl font-bold">{stats.total}</Text>
|
|
131
|
-
</div>
|
|
132
|
-
|
|
133
|
-
{/* Active Campaigns */}
|
|
134
|
-
<div className="p-4 rounded-lg border bg-green-50">
|
|
135
|
-
<div className="flex items-center justify-between mb-2">
|
|
136
|
-
<Text className="text-xs text-green-700">Active</Text>
|
|
137
|
-
<CheckCircleSolid className="h-4 w-4 text-green-600" />
|
|
138
|
-
</div>
|
|
139
|
-
<Text className="text-2xl font-bold text-green-700">
|
|
140
|
-
{stats.active}
|
|
141
|
-
</Text>
|
|
142
|
-
</div>
|
|
143
|
-
|
|
144
|
-
{/* Inactive Campaigns */}
|
|
145
|
-
<div className="p-4 rounded-lg border bg-gray-50">
|
|
146
|
-
<div className="flex items-center justify-between mb-2">
|
|
147
|
-
<Text className="text-xs text-gray-600">Inactive</Text>
|
|
148
|
-
<ClockSolid className="h-4 w-4 text-gray-500" />
|
|
149
|
-
</div>
|
|
150
|
-
<Text className="text-2xl font-bold text-gray-600">
|
|
151
|
-
{stats.inactive}
|
|
152
|
-
</Text>
|
|
153
|
-
</div>
|
|
154
|
-
</div>
|
|
155
|
-
|
|
156
|
-
{/* Detail Stats */}
|
|
157
|
-
<div className="space-y-3 mb-6">
|
|
158
|
-
<div className="flex items-center justify-between p-3 rounded-lg bg-ui-bg-subtle">
|
|
159
|
-
<div className="flex items-center gap-2">
|
|
160
|
-
<PhotoSolid className="h-4 w-4 text-blue-600" />
|
|
161
|
-
<Text className="text-sm">With Images</Text>
|
|
162
|
-
</div>
|
|
163
|
-
<div className="flex items-center gap-2">
|
|
164
|
-
<Text className="text-sm font-medium">{stats.with_images}</Text>
|
|
165
|
-
<Badge size="xsmall" color="blue">
|
|
166
|
-
{stats.total > 0
|
|
167
|
-
? Math.round((stats.with_images / stats.total) * 100)
|
|
168
|
-
: 0}
|
|
169
|
-
%
|
|
170
|
-
</Badge>
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
173
|
-
|
|
174
|
-
<div className="flex items-center justify-between p-3 rounded-lg bg-ui-bg-subtle">
|
|
175
|
-
<div className="flex items-center gap-2">
|
|
176
|
-
<CheckCircleSolid className="h-4 w-4 text-green-600" />
|
|
177
|
-
<Text className="text-sm">With Content</Text>
|
|
178
|
-
</div>
|
|
179
|
-
<div className="flex items-center gap-2">
|
|
180
|
-
<Text className="text-sm font-medium">{stats.with_content}</Text>
|
|
181
|
-
<Badge size="xsmall" color="green">
|
|
182
|
-
{stats.total > 0
|
|
183
|
-
? Math.round((stats.with_content / stats.total) * 100)
|
|
184
|
-
: 0}
|
|
185
|
-
%
|
|
186
|
-
</Badge>
|
|
187
|
-
</div>
|
|
188
|
-
</div>
|
|
189
|
-
|
|
190
|
-
<div className="flex items-center justify-between p-3 rounded-lg bg-ui-bg-subtle">
|
|
191
|
-
<div className="flex items-center gap-2">
|
|
192
|
-
<Sparkles className="h-4 w-4 text-purple-600" />
|
|
193
|
-
<Text className="text-sm">Complete Details</Text>
|
|
194
|
-
</div>
|
|
195
|
-
<div className="flex items-center gap-2">
|
|
196
|
-
<Text className="text-sm font-medium">{stats.complete}</Text>
|
|
197
|
-
<Badge size="xsmall" color="purple">
|
|
198
|
-
{completionRate}%
|
|
199
|
-
</Badge>
|
|
200
|
-
</div>
|
|
201
|
-
</div>
|
|
202
|
-
</div>
|
|
203
|
-
|
|
204
|
-
{/* Completion Progress Bar */}
|
|
205
|
-
<div className="pt-4 border-t">
|
|
206
|
-
<div className="flex items-center justify-between mb-2">
|
|
207
|
-
<Text className="text-xs text-ui-fg-subtle">Campaign Completion</Text>
|
|
208
|
-
<Text className="text-xs font-medium">{completionRate}%</Text>
|
|
209
|
-
</div>
|
|
210
|
-
<div className="h-2 bg-ui-bg-subtle rounded-full overflow-hidden">
|
|
211
|
-
<div
|
|
212
|
-
className="h-full bg-gradient-to-r from-purple-500 to-blue-500 transition-all duration-300"
|
|
213
|
-
style={{ width: `${completionRate}%` }}
|
|
214
|
-
/>
|
|
215
|
-
</div>
|
|
216
|
-
<Text className="text-xs text-ui-fg-muted mt-2">
|
|
217
|
-
{stats.complete} of {stats.total} campaigns have complete details
|
|
218
|
-
</Text>
|
|
219
|
-
</div>
|
|
220
|
-
|
|
221
|
-
{/* Quick Tip */}
|
|
222
|
-
{completionRate < 50 && stats.total > 0 && (
|
|
223
|
-
<div className="mt-4 p-3 rounded-lg bg-blue-50 border border-blue-200">
|
|
224
|
-
<Text className="text-xs text-blue-800">
|
|
225
|
-
💡 Tip: Add images and content to campaigns to improve customer
|
|
226
|
-
engagement!
|
|
227
|
-
</Text>
|
|
228
|
-
</div>
|
|
229
|
-
)}
|
|
230
|
-
</Container>
|
|
231
|
-
);
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
export const config = defineWidgetConfig({
|
|
235
|
-
zone: "campaign.list.before",
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
export default CampaignStatsWidget;
|