@growflowstudio/growflowbilling-admin-ui 2.0.1 → 2.1.1
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/index.d.mts +90 -2
- package/dist/index.d.ts +90 -2
- package/dist/index.js +1368 -932
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1337 -911
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,44 @@
|
|
|
1
1
|
// src/provider.tsx
|
|
2
|
-
import
|
|
2
|
+
import React2, { useMemo } from "react";
|
|
3
3
|
import {
|
|
4
4
|
BillingAdminClientContext,
|
|
5
5
|
createBillingAdminClient
|
|
6
6
|
} from "@growflowstudio/growflowbilling-admin-core";
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
// src/components/shared/ErrorBoundary.tsx
|
|
9
|
+
import { Component } from "react";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
var ErrorBoundary = class extends Component {
|
|
12
|
+
constructor(props) {
|
|
13
|
+
super(props);
|
|
14
|
+
this.state = { hasError: false, error: null };
|
|
15
|
+
}
|
|
16
|
+
static getDerivedStateFromError(error) {
|
|
17
|
+
return { hasError: true, error };
|
|
18
|
+
}
|
|
19
|
+
componentDidCatch(error, errorInfo) {
|
|
20
|
+
console.error("[BillingSDK] Error caught by boundary:", error, errorInfo);
|
|
21
|
+
}
|
|
22
|
+
render() {
|
|
23
|
+
if (this.state.hasError) {
|
|
24
|
+
return this.props.fallback ?? /* @__PURE__ */ jsxs("div", { style: { padding: "1rem", textAlign: "center", color: "#666" }, children: [
|
|
25
|
+
/* @__PURE__ */ jsx("p", { children: "Something went wrong in the billing module." }),
|
|
26
|
+
/* @__PURE__ */ jsx(
|
|
27
|
+
"button",
|
|
28
|
+
{
|
|
29
|
+
onClick: () => this.setState({ hasError: false, error: null }),
|
|
30
|
+
style: { marginTop: "0.5rem", cursor: "pointer" },
|
|
31
|
+
children: "Try again"
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
] });
|
|
35
|
+
}
|
|
36
|
+
return this.props.children;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/provider.tsx
|
|
41
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
8
42
|
var DEFAULT_MODULES = {
|
|
9
43
|
plans: true,
|
|
10
44
|
subscriptions: true,
|
|
@@ -13,9 +47,9 @@ var DEFAULT_MODULES = {
|
|
|
13
47
|
invoices: true,
|
|
14
48
|
features: true
|
|
15
49
|
};
|
|
16
|
-
var BillingAdminConfigContext =
|
|
50
|
+
var BillingAdminConfigContext = React2.createContext(null);
|
|
17
51
|
function useBillingAdminConfig() {
|
|
18
|
-
const config =
|
|
52
|
+
const config = React2.useContext(BillingAdminConfigContext);
|
|
19
53
|
if (!config) {
|
|
20
54
|
throw new Error(
|
|
21
55
|
"useBillingAdminConfig must be used within a BillingAdminProvider."
|
|
@@ -35,7 +69,7 @@ function BillingAdminProvider({ config, children }) {
|
|
|
35
69
|
}),
|
|
36
70
|
[config.basePath, config.fetcher]
|
|
37
71
|
);
|
|
38
|
-
return /* @__PURE__ */
|
|
72
|
+
return /* @__PURE__ */ jsx2(BillingAdminConfigContext.Provider, { value: config, children: /* @__PURE__ */ jsx2(BillingAdminClientContext.Provider, { value: client, children: /* @__PURE__ */ jsx2(ErrorBoundary, { children }) }) });
|
|
39
73
|
}
|
|
40
74
|
|
|
41
75
|
// src/pages/PlansPage.tsx
|
|
@@ -48,7 +82,7 @@ import {
|
|
|
48
82
|
useUpdatePlan,
|
|
49
83
|
useDeletePlan,
|
|
50
84
|
useSyncPlanToStripe,
|
|
51
|
-
|
|
85
|
+
useDialogState,
|
|
52
86
|
validateRequired
|
|
53
87
|
} from "@growflowstudio/growflowbilling-admin-core";
|
|
54
88
|
|
|
@@ -80,9 +114,9 @@ function cn(...inputs) {
|
|
|
80
114
|
}
|
|
81
115
|
|
|
82
116
|
// src/components/shared/StatusBadge.tsx
|
|
83
|
-
import { jsx as
|
|
117
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
84
118
|
function StatusBadge({ label, colorClass, className }) {
|
|
85
|
-
return /* @__PURE__ */
|
|
119
|
+
return /* @__PURE__ */ jsx3(
|
|
86
120
|
"span",
|
|
87
121
|
{
|
|
88
122
|
className: cn(
|
|
@@ -96,13 +130,13 @@ function StatusBadge({ label, colorClass, className }) {
|
|
|
96
130
|
}
|
|
97
131
|
|
|
98
132
|
// src/components/shared/SkeletonRows.tsx
|
|
99
|
-
import { Fragment, jsx as
|
|
133
|
+
import { Fragment, jsx as jsx4 } from "react/jsx-runtime";
|
|
100
134
|
function SkeletonRows({ rows = 5, columns = 1 }) {
|
|
101
|
-
return /* @__PURE__ */
|
|
135
|
+
return /* @__PURE__ */ jsx4(Fragment, { children: Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ jsx4("tr", { children: /* @__PURE__ */ jsx4("td", { colSpan: columns, className: "p-4", children: /* @__PURE__ */ jsx4("div", { className: "h-10 w-full animate-pulse rounded-md bg-muted" }) }) }, i)) });
|
|
102
136
|
}
|
|
103
137
|
|
|
104
138
|
// src/components/plans/PlansTable.tsx
|
|
105
|
-
import { jsx as
|
|
139
|
+
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
106
140
|
var pricingModelLabels = {
|
|
107
141
|
flat: "Fisso",
|
|
108
142
|
tiered: "A Scaglioni",
|
|
@@ -117,55 +151,55 @@ function PlansTable({
|
|
|
117
151
|
onSync,
|
|
118
152
|
isSyncing
|
|
119
153
|
}) {
|
|
120
|
-
return /* @__PURE__ */
|
|
121
|
-
/* @__PURE__ */
|
|
122
|
-
/* @__PURE__ */
|
|
123
|
-
/* @__PURE__ */
|
|
124
|
-
/* @__PURE__ */
|
|
125
|
-
/* @__PURE__ */
|
|
126
|
-
/* @__PURE__ */
|
|
127
|
-
/* @__PURE__ */
|
|
128
|
-
/* @__PURE__ */
|
|
129
|
-
/* @__PURE__ */
|
|
130
|
-
/* @__PURE__ */
|
|
131
|
-
/* @__PURE__ */
|
|
154
|
+
return /* @__PURE__ */ jsx5("div", { className: "relative w-full overflow-auto", children: /* @__PURE__ */ jsxs2("table", { className: "w-full caption-bottom text-sm", children: [
|
|
155
|
+
/* @__PURE__ */ jsx5("thead", { className: "[&_tr]:border-b", children: /* @__PURE__ */ jsxs2("tr", { className: "border-b transition-colors hover:bg-muted/50", children: [
|
|
156
|
+
/* @__PURE__ */ jsx5("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Slug" }),
|
|
157
|
+
/* @__PURE__ */ jsx5("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Nome" }),
|
|
158
|
+
/* @__PURE__ */ jsx5("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Colore" }),
|
|
159
|
+
/* @__PURE__ */ jsx5("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Modello" }),
|
|
160
|
+
/* @__PURE__ */ jsx5("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Prezzo Mensile" }),
|
|
161
|
+
/* @__PURE__ */ jsx5("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Prezzo Annuale" }),
|
|
162
|
+
/* @__PURE__ */ jsx5("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Trial" }),
|
|
163
|
+
/* @__PURE__ */ jsx5("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Attivo" }),
|
|
164
|
+
/* @__PURE__ */ jsx5("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Stripe" }),
|
|
165
|
+
/* @__PURE__ */ jsx5("th", { className: "h-12 px-4 text-right align-middle font-medium text-muted-foreground", children: "Azioni" })
|
|
132
166
|
] }) }),
|
|
133
|
-
/* @__PURE__ */
|
|
134
|
-
/* @__PURE__ */
|
|
135
|
-
/* @__PURE__ */
|
|
136
|
-
/* @__PURE__ */
|
|
137
|
-
/* @__PURE__ */
|
|
138
|
-
/* @__PURE__ */
|
|
139
|
-
/* @__PURE__ */
|
|
140
|
-
/* @__PURE__ */
|
|
167
|
+
/* @__PURE__ */ jsx5("tbody", { className: "[&_tr:last-child]:border-0", children: isLoading ? /* @__PURE__ */ jsx5(SkeletonRows, { rows: 3, columns: 10 }) : plans.length === 0 ? /* @__PURE__ */ jsx5("tr", { children: /* @__PURE__ */ jsx5("td", { colSpan: 10, className: "p-4 text-center py-8 text-muted-foreground", children: "Nessun piano configurato" }) }) : plans.map((plan) => /* @__PURE__ */ jsxs2("tr", { className: "border-b transition-colors hover:bg-muted/50", children: [
|
|
168
|
+
/* @__PURE__ */ jsx5("td", { className: "p-4 align-middle", children: /* @__PURE__ */ jsx5("code", { className: "text-sm bg-muted px-2 py-0.5 rounded", children: plan.slug }) }),
|
|
169
|
+
/* @__PURE__ */ jsx5("td", { className: "p-4 align-middle font-medium", children: plan.name }),
|
|
170
|
+
/* @__PURE__ */ jsx5("td", { className: "p-4 align-middle", children: /* @__PURE__ */ jsx5(StatusBadge, { label: plan.color, colorClass: getPlanColorClass(plan.color) }) }),
|
|
171
|
+
/* @__PURE__ */ jsx5("td", { className: "p-4 align-middle", children: /* @__PURE__ */ jsx5(StatusBadge, { label: pricingModelLabels[plan.pricing_model], colorClass: "border" }) }),
|
|
172
|
+
/* @__PURE__ */ jsx5("td", { className: "p-4 align-middle", children: plan.contact_sales ? /* @__PURE__ */ jsx5("span", { className: "text-muted-foreground", children: "Contattaci" }) : formatCurrency(plan.price_monthly ?? 0, plan.currency) }),
|
|
173
|
+
/* @__PURE__ */ jsx5("td", { className: "p-4 align-middle", children: plan.contact_sales ? /* @__PURE__ */ jsx5("span", { className: "text-muted-foreground", children: "Contattaci" }) : formatCurrency(plan.price_yearly ?? 0, plan.currency) }),
|
|
174
|
+
/* @__PURE__ */ jsxs2("td", { className: "p-4 align-middle", children: [
|
|
141
175
|
plan.trial_days,
|
|
142
176
|
" giorni"
|
|
143
177
|
] }),
|
|
144
|
-
/* @__PURE__ */
|
|
145
|
-
/* @__PURE__ */
|
|
178
|
+
/* @__PURE__ */ jsx5("td", { className: "p-4 align-middle", children: plan.is_active ? /* @__PURE__ */ jsx5(Check, { className: "w-4 h-4 text-green-600" }) : /* @__PURE__ */ jsx5(X, { className: "w-4 h-4 text-muted-foreground" }) }),
|
|
179
|
+
/* @__PURE__ */ jsx5("td", { className: "p-4 align-middle", children: plan.stripe_price_id_monthly ? /* @__PURE__ */ jsx5(StatusBadge, { label: "Synced", colorClass: "bg-green-50 text-green-700 border-green-200" }) : /* @__PURE__ */ jsx5(
|
|
146
180
|
"button",
|
|
147
181
|
{
|
|
148
182
|
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-9 w-9 hover:bg-accent hover:text-accent-foreground",
|
|
149
183
|
onClick: () => onSync(plan.id),
|
|
150
184
|
disabled: isSyncing,
|
|
151
|
-
children: /* @__PURE__ */
|
|
185
|
+
children: /* @__PURE__ */ jsx5(Upload, { className: "w-4 h-4" })
|
|
152
186
|
}
|
|
153
187
|
) }),
|
|
154
|
-
/* @__PURE__ */
|
|
155
|
-
/* @__PURE__ */
|
|
188
|
+
/* @__PURE__ */ jsx5("td", { className: "p-4 align-middle text-right", children: /* @__PURE__ */ jsxs2("div", { className: "flex justify-end gap-1", children: [
|
|
189
|
+
/* @__PURE__ */ jsx5(
|
|
156
190
|
"button",
|
|
157
191
|
{
|
|
158
192
|
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-9 w-9 hover:bg-accent hover:text-accent-foreground",
|
|
159
193
|
onClick: () => onEdit(plan),
|
|
160
|
-
children: /* @__PURE__ */
|
|
194
|
+
children: /* @__PURE__ */ jsx5(Pencil, { className: "w-4 h-4" })
|
|
161
195
|
}
|
|
162
196
|
),
|
|
163
|
-
/* @__PURE__ */
|
|
197
|
+
/* @__PURE__ */ jsx5(
|
|
164
198
|
"button",
|
|
165
199
|
{
|
|
166
200
|
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-9 w-9 text-destructive hover:bg-accent hover:text-destructive",
|
|
167
201
|
onClick: () => onDelete(plan),
|
|
168
|
-
children: /* @__PURE__ */
|
|
202
|
+
children: /* @__PURE__ */ jsx5(Trash2, { className: "w-4 h-4" })
|
|
169
203
|
}
|
|
170
204
|
)
|
|
171
205
|
] }) })
|
|
@@ -174,12 +208,200 @@ function PlansTable({
|
|
|
174
208
|
}
|
|
175
209
|
|
|
176
210
|
// src/components/plans/PlanFormDialog.tsx
|
|
177
|
-
import {
|
|
211
|
+
import { useMemo as useMemo3 } from "react";
|
|
178
212
|
import { useQuery } from "@tanstack/react-query";
|
|
179
|
-
import { Loader2
|
|
213
|
+
import { Loader2 } from "lucide-react";
|
|
214
|
+
|
|
215
|
+
// src/components/shared/ToggleSwitch.tsx
|
|
216
|
+
import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
217
|
+
function ToggleSwitch({ label, checked, onChange }) {
|
|
218
|
+
return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
|
|
219
|
+
/* @__PURE__ */ jsx6(
|
|
220
|
+
"button",
|
|
221
|
+
{
|
|
222
|
+
type: "button",
|
|
223
|
+
role: "switch",
|
|
224
|
+
"aria-checked": checked,
|
|
225
|
+
onClick: () => onChange(!checked),
|
|
226
|
+
className: `peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors ${checked ? "bg-primary" : "bg-input"}`,
|
|
227
|
+
children: /* @__PURE__ */ jsx6(
|
|
228
|
+
"span",
|
|
229
|
+
{
|
|
230
|
+
className: `pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform ${checked ? "translate-x-4" : "translate-x-0"}`
|
|
231
|
+
}
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
),
|
|
235
|
+
/* @__PURE__ */ jsx6("span", { className: "text-sm font-medium leading-none", children: label })
|
|
236
|
+
] });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/components/plans/PlanDetailsSection.tsx
|
|
240
|
+
import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
241
|
+
function PlanDetailsSection({
|
|
242
|
+
formData,
|
|
243
|
+
formErrors,
|
|
244
|
+
onFormDataChange,
|
|
245
|
+
onNameChange
|
|
246
|
+
}) {
|
|
247
|
+
return /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
248
|
+
/* @__PURE__ */ jsxs4("div", { className: "space-y-2", children: [
|
|
249
|
+
/* @__PURE__ */ jsx7("label", { className: "text-sm font-medium leading-none", htmlFor: "name", children: "Nome *" }),
|
|
250
|
+
/* @__PURE__ */ jsx7(
|
|
251
|
+
"input",
|
|
252
|
+
{
|
|
253
|
+
id: "name",
|
|
254
|
+
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
255
|
+
value: formData.name,
|
|
256
|
+
onChange: (e) => onNameChange(e.target.value),
|
|
257
|
+
placeholder: "es. Piano Pro"
|
|
258
|
+
}
|
|
259
|
+
),
|
|
260
|
+
formErrors.name && /* @__PURE__ */ jsx7("p", { className: "text-xs text-destructive", children: formErrors.name })
|
|
261
|
+
] }),
|
|
262
|
+
/* @__PURE__ */ jsxs4("div", { className: "space-y-2", children: [
|
|
263
|
+
/* @__PURE__ */ jsx7("label", { className: "text-sm font-medium leading-none", htmlFor: "description", children: "Descrizione" }),
|
|
264
|
+
/* @__PURE__ */ jsx7(
|
|
265
|
+
"textarea",
|
|
266
|
+
{
|
|
267
|
+
id: "description",
|
|
268
|
+
className: "flex min-h-[60px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
269
|
+
value: formData.description,
|
|
270
|
+
onChange: (e) => onFormDataChange({ description: e.target.value }),
|
|
271
|
+
placeholder: "Descrizione del piano...",
|
|
272
|
+
rows: 2
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
] }),
|
|
276
|
+
/* @__PURE__ */ jsxs4("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
277
|
+
/* @__PURE__ */ jsxs4("div", { className: "space-y-2", children: [
|
|
278
|
+
/* @__PURE__ */ jsx7("label", { className: "text-sm font-medium leading-none", children: "Colore Badge" }),
|
|
279
|
+
/* @__PURE__ */ jsx7(
|
|
280
|
+
"select",
|
|
281
|
+
{
|
|
282
|
+
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
283
|
+
value: formData.color,
|
|
284
|
+
onChange: (e) => onFormDataChange({ color: e.target.value }),
|
|
285
|
+
children: PLAN_COLORS.map((color) => /* @__PURE__ */ jsx7("option", { value: color.value, children: color.label }, color.value))
|
|
286
|
+
}
|
|
287
|
+
),
|
|
288
|
+
/* @__PURE__ */ jsxs4("p", { className: "text-xs text-muted-foreground", children: [
|
|
289
|
+
"Anteprima:",
|
|
290
|
+
" ",
|
|
291
|
+
/* @__PURE__ */ jsx7("span", { className: `inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold ${getPlanColorClass(formData.color)}`, children: formData.name || "Piano" })
|
|
292
|
+
] })
|
|
293
|
+
] }),
|
|
294
|
+
/* @__PURE__ */ jsxs4("div", { className: "space-y-4 pt-6", children: [
|
|
295
|
+
/* @__PURE__ */ jsx7(ToggleSwitch, { label: "Piano Attivo", checked: formData.is_active, onChange: (v) => onFormDataChange({ is_active: v }) }),
|
|
296
|
+
/* @__PURE__ */ jsx7(ToggleSwitch, { label: "Contattaci per Prezzi", checked: formData.contact_sales, onChange: (v) => onFormDataChange({ contact_sales: v }) }),
|
|
297
|
+
/* @__PURE__ */ jsx7(ToggleSwitch, { label: "Visibile in Homepage", checked: formData.is_public, onChange: (v) => onFormDataChange({ is_public: v }) })
|
|
298
|
+
] })
|
|
299
|
+
] })
|
|
300
|
+
] });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// src/components/plans/PlanPricingSection.tsx
|
|
304
|
+
import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
305
|
+
function PlanPricingSection({ formData, onFormDataChange }) {
|
|
306
|
+
return /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
307
|
+
/* @__PURE__ */ jsxs5("div", { className: "grid grid-cols-3 gap-4", children: [
|
|
308
|
+
/* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
|
|
309
|
+
/* @__PURE__ */ jsx8("label", { className: "text-sm font-medium leading-none", children: "Modello Pricing" }),
|
|
310
|
+
/* @__PURE__ */ jsxs5(
|
|
311
|
+
"select",
|
|
312
|
+
{
|
|
313
|
+
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
314
|
+
value: formData.pricing_model,
|
|
315
|
+
onChange: (e) => onFormDataChange({ pricing_model: e.target.value }),
|
|
316
|
+
children: [
|
|
317
|
+
/* @__PURE__ */ jsx8("option", { value: "flat", children: "Fisso" }),
|
|
318
|
+
/* @__PURE__ */ jsx8("option", { value: "tiered", children: "A Scaglioni" }),
|
|
319
|
+
/* @__PURE__ */ jsx8("option", { value: "per_unit", children: "Per Unit\xE0" }),
|
|
320
|
+
/* @__PURE__ */ jsx8("option", { value: "usage_based", children: "A Consumo" })
|
|
321
|
+
]
|
|
322
|
+
}
|
|
323
|
+
)
|
|
324
|
+
] }),
|
|
325
|
+
/* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
|
|
326
|
+
/* @__PURE__ */ jsx8("label", { className: "text-sm font-medium leading-none", htmlFor: "price_monthly", children: "Prezzo Mensile" }),
|
|
327
|
+
/* @__PURE__ */ jsx8(
|
|
328
|
+
"input",
|
|
329
|
+
{
|
|
330
|
+
id: "price_monthly",
|
|
331
|
+
type: "number",
|
|
332
|
+
step: "0.01",
|
|
333
|
+
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
334
|
+
value: formData.price_monthly,
|
|
335
|
+
onChange: (e) => onFormDataChange({ price_monthly: e.target.value }),
|
|
336
|
+
placeholder: "0.00"
|
|
337
|
+
}
|
|
338
|
+
)
|
|
339
|
+
] }),
|
|
340
|
+
/* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
|
|
341
|
+
/* @__PURE__ */ jsx8("label", { className: "text-sm font-medium leading-none", htmlFor: "price_yearly", children: "Prezzo Annuale" }),
|
|
342
|
+
/* @__PURE__ */ jsx8(
|
|
343
|
+
"input",
|
|
344
|
+
{
|
|
345
|
+
id: "price_yearly",
|
|
346
|
+
type: "number",
|
|
347
|
+
step: "0.01",
|
|
348
|
+
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
349
|
+
value: formData.price_yearly,
|
|
350
|
+
onChange: (e) => onFormDataChange({ price_yearly: e.target.value }),
|
|
351
|
+
placeholder: "0.00"
|
|
352
|
+
}
|
|
353
|
+
)
|
|
354
|
+
] })
|
|
355
|
+
] }),
|
|
356
|
+
/* @__PURE__ */ jsxs5("div", { className: "grid grid-cols-3 gap-4", children: [
|
|
357
|
+
/* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
|
|
358
|
+
/* @__PURE__ */ jsx8("label", { className: "text-sm font-medium leading-none", children: "Valuta" }),
|
|
359
|
+
/* @__PURE__ */ jsxs5(
|
|
360
|
+
"select",
|
|
361
|
+
{
|
|
362
|
+
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
363
|
+
value: formData.currency,
|
|
364
|
+
onChange: (e) => onFormDataChange({ currency: e.target.value }),
|
|
365
|
+
children: [
|
|
366
|
+
/* @__PURE__ */ jsx8("option", { value: "eur", children: "EUR" }),
|
|
367
|
+
/* @__PURE__ */ jsx8("option", { value: "usd", children: "USD" }),
|
|
368
|
+
/* @__PURE__ */ jsx8("option", { value: "gbp", children: "GBP" })
|
|
369
|
+
]
|
|
370
|
+
}
|
|
371
|
+
)
|
|
372
|
+
] }),
|
|
373
|
+
/* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
|
|
374
|
+
/* @__PURE__ */ jsx8("label", { className: "text-sm font-medium leading-none", htmlFor: "trial_days", children: "Giorni Trial" }),
|
|
375
|
+
/* @__PURE__ */ jsx8(
|
|
376
|
+
"input",
|
|
377
|
+
{
|
|
378
|
+
id: "trial_days",
|
|
379
|
+
type: "number",
|
|
380
|
+
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
381
|
+
value: formData.trial_days,
|
|
382
|
+
onChange: (e) => onFormDataChange({ trial_days: parseInt(e.target.value) || 0 })
|
|
383
|
+
}
|
|
384
|
+
)
|
|
385
|
+
] }),
|
|
386
|
+
/* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
|
|
387
|
+
/* @__PURE__ */ jsx8("label", { className: "text-sm font-medium leading-none", htmlFor: "sort_order", children: "Ordine" }),
|
|
388
|
+
/* @__PURE__ */ jsx8(
|
|
389
|
+
"input",
|
|
390
|
+
{
|
|
391
|
+
id: "sort_order",
|
|
392
|
+
type: "number",
|
|
393
|
+
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
394
|
+
value: formData.sort_order,
|
|
395
|
+
onChange: (e) => onFormDataChange({ sort_order: parseInt(e.target.value) || 0 })
|
|
396
|
+
}
|
|
397
|
+
)
|
|
398
|
+
] })
|
|
399
|
+
] })
|
|
400
|
+
] });
|
|
401
|
+
}
|
|
180
402
|
|
|
181
403
|
// src/components/plans/LimitInput.tsx
|
|
182
|
-
import { jsx as
|
|
404
|
+
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
183
405
|
function LimitInput({
|
|
184
406
|
id,
|
|
185
407
|
label,
|
|
@@ -189,11 +411,11 @@ function LimitInput({
|
|
|
189
411
|
onUnlimitedChange,
|
|
190
412
|
min = 1
|
|
191
413
|
}) {
|
|
192
|
-
return /* @__PURE__ */
|
|
193
|
-
/* @__PURE__ */
|
|
194
|
-
/* @__PURE__ */
|
|
195
|
-
/* @__PURE__ */
|
|
196
|
-
/* @__PURE__ */
|
|
414
|
+
return /* @__PURE__ */ jsxs6("div", { className: "space-y-2 p-3 border rounded-lg", children: [
|
|
415
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between", children: [
|
|
416
|
+
/* @__PURE__ */ jsx9("label", { htmlFor: id, className: "text-sm font-medium leading-none", children: label }),
|
|
417
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
|
|
418
|
+
/* @__PURE__ */ jsx9(
|
|
197
419
|
"button",
|
|
198
420
|
{
|
|
199
421
|
type: "button",
|
|
@@ -201,7 +423,7 @@ function LimitInput({
|
|
|
201
423
|
"aria-checked": isUnlimited,
|
|
202
424
|
onClick: () => onUnlimitedChange(!isUnlimited),
|
|
203
425
|
className: `peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors ${isUnlimited ? "bg-primary" : "bg-input"}`,
|
|
204
|
-
children: /* @__PURE__ */
|
|
426
|
+
children: /* @__PURE__ */ jsx9(
|
|
205
427
|
"span",
|
|
206
428
|
{
|
|
207
429
|
className: `pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform ${isUnlimited ? "translate-x-4" : "translate-x-0"}`
|
|
@@ -209,10 +431,10 @@ function LimitInput({
|
|
|
209
431
|
)
|
|
210
432
|
}
|
|
211
433
|
),
|
|
212
|
-
/* @__PURE__ */
|
|
434
|
+
/* @__PURE__ */ jsx9("span", { className: "text-xs text-muted-foreground", children: "Illimitati" })
|
|
213
435
|
] })
|
|
214
436
|
] }),
|
|
215
|
-
/* @__PURE__ */
|
|
437
|
+
/* @__PURE__ */ jsx9(
|
|
216
438
|
"input",
|
|
217
439
|
{
|
|
218
440
|
id,
|
|
@@ -227,8 +449,275 @@ function LimitInput({
|
|
|
227
449
|
] });
|
|
228
450
|
}
|
|
229
451
|
|
|
452
|
+
// src/components/plans/PlanLimitsSection.tsx
|
|
453
|
+
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
454
|
+
function PlanLimitsSection({ limitFields, formData, onFormDataChange }) {
|
|
455
|
+
if (limitFields.length === 0) return null;
|
|
456
|
+
return /* @__PURE__ */ jsxs7("div", { className: "space-y-3", children: [
|
|
457
|
+
/* @__PURE__ */ jsx10("label", { className: "text-sm font-medium leading-none", children: "Limiti Piano" }),
|
|
458
|
+
/* @__PURE__ */ jsx10("div", { className: "grid grid-cols-2 gap-3", children: limitFields.map((field) => /* @__PURE__ */ jsx10(
|
|
459
|
+
LimitInput,
|
|
460
|
+
{
|
|
461
|
+
id: `limit_${field.key}`,
|
|
462
|
+
label: field.label,
|
|
463
|
+
value: formData.limits[field.key] ?? field.defaultValue ?? 1,
|
|
464
|
+
isUnlimited: formData.limits_unlimited[field.key] ?? false,
|
|
465
|
+
onValueChange: (v) => onFormDataChange({ limits: { ...formData.limits, [field.key]: v } }),
|
|
466
|
+
onUnlimitedChange: (v) => onFormDataChange({ limits_unlimited: { ...formData.limits_unlimited, [field.key]: v } })
|
|
467
|
+
},
|
|
468
|
+
field.key
|
|
469
|
+
)) })
|
|
470
|
+
] });
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// src/components/plans/PlanFeaturesSection.tsx
|
|
474
|
+
import { useMemo as useMemo2, useState } from "react";
|
|
475
|
+
|
|
476
|
+
// src/lib/plan-feature-state.ts
|
|
477
|
+
var FEATURE_STATE_DESCRIPTIONS = {
|
|
478
|
+
off: "Non disponibile per gli utenti di questo piano (default).",
|
|
479
|
+
teaser: "Mostrata in sidebar come locked con CTA upgrade. Non attivata funzionalmente.",
|
|
480
|
+
on: "Inclusa nel piano e pienamente accessibile.",
|
|
481
|
+
sunset: "Tecnicamente nel piano ma nascosta. Uso raro (deprecazioni / incidenti)."
|
|
482
|
+
};
|
|
483
|
+
function featureStateFromPlan(slug, features, uiVisibility) {
|
|
484
|
+
const inPlan = (features ?? []).includes(slug);
|
|
485
|
+
const ui = (uiVisibility ?? {})[slug];
|
|
486
|
+
if (inPlan) {
|
|
487
|
+
if (ui === "hidden") return "sunset";
|
|
488
|
+
return "on";
|
|
489
|
+
}
|
|
490
|
+
if (ui === "teaser") return "teaser";
|
|
491
|
+
return "off";
|
|
492
|
+
}
|
|
493
|
+
function applyFeatureState(slug, state, features, uiVisibility) {
|
|
494
|
+
const nextFeaturesSet = new Set(features ?? []);
|
|
495
|
+
const nextUi = { ...uiVisibility ?? {} };
|
|
496
|
+
switch (state) {
|
|
497
|
+
case "off":
|
|
498
|
+
nextFeaturesSet.delete(slug);
|
|
499
|
+
delete nextUi[slug];
|
|
500
|
+
break;
|
|
501
|
+
case "teaser":
|
|
502
|
+
nextFeaturesSet.delete(slug);
|
|
503
|
+
nextUi[slug] = "teaser";
|
|
504
|
+
break;
|
|
505
|
+
case "on":
|
|
506
|
+
nextFeaturesSet.add(slug);
|
|
507
|
+
delete nextUi[slug];
|
|
508
|
+
break;
|
|
509
|
+
case "sunset":
|
|
510
|
+
nextFeaturesSet.add(slug);
|
|
511
|
+
nextUi[slug] = "hidden";
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
return {
|
|
515
|
+
features: Array.from(nextFeaturesSet),
|
|
516
|
+
uiVisibility: nextUi
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function summarizeFeatureStates(features, uiVisibility) {
|
|
520
|
+
const counts = {
|
|
521
|
+
off: 0,
|
|
522
|
+
teaser: 0,
|
|
523
|
+
on: 0,
|
|
524
|
+
sunset: 0
|
|
525
|
+
};
|
|
526
|
+
const slugs = /* @__PURE__ */ new Set([
|
|
527
|
+
...features ?? [],
|
|
528
|
+
...Object.keys(uiVisibility ?? {})
|
|
529
|
+
]);
|
|
530
|
+
for (const slug of slugs) {
|
|
531
|
+
const state = featureStateFromPlan(slug, features, uiVisibility);
|
|
532
|
+
counts[state] += 1;
|
|
533
|
+
}
|
|
534
|
+
return counts;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// src/components/plans/PlanFeatureRow.tsx
|
|
538
|
+
import { Check as Check2, CircleSlash, Lock, Archive } from "lucide-react";
|
|
539
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
540
|
+
var STATE_VISUAL = {
|
|
541
|
+
off: {
|
|
542
|
+
label: "Non inclusa",
|
|
543
|
+
Icon: CircleSlash,
|
|
544
|
+
classes: "bg-muted text-muted-foreground border-muted-foreground/20"
|
|
545
|
+
},
|
|
546
|
+
teaser: {
|
|
547
|
+
label: "Teaser",
|
|
548
|
+
Icon: Lock,
|
|
549
|
+
classes: "bg-amber-100 text-amber-800 border-amber-300 dark:bg-amber-900/30 dark:text-amber-400 dark:border-amber-700"
|
|
550
|
+
},
|
|
551
|
+
on: {
|
|
552
|
+
label: "Inclusa",
|
|
553
|
+
Icon: Check2,
|
|
554
|
+
classes: "bg-green-100 text-green-800 border-green-300 dark:bg-green-900/30 dark:text-green-400 dark:border-green-700"
|
|
555
|
+
},
|
|
556
|
+
sunset: {
|
|
557
|
+
label: "Sunset",
|
|
558
|
+
Icon: Archive,
|
|
559
|
+
classes: "bg-rose-100 text-rose-800 border-rose-300 dark:bg-rose-900/30 dark:text-rose-400 dark:border-rose-700"
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
function PlanFeatureRow({
|
|
563
|
+
slug,
|
|
564
|
+
name,
|
|
565
|
+
description,
|
|
566
|
+
state,
|
|
567
|
+
showSunset = false,
|
|
568
|
+
onChange,
|
|
569
|
+
onRemove
|
|
570
|
+
}) {
|
|
571
|
+
const visual = STATE_VISUAL[state];
|
|
572
|
+
const Icon = visual.Icon;
|
|
573
|
+
return /* @__PURE__ */ jsxs8("div", { className: "flex items-start gap-3 p-2 rounded-md hover:bg-muted/50 transition-colors", children: [
|
|
574
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex-1 min-w-0", children: [
|
|
575
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2 flex-wrap", children: [
|
|
576
|
+
/* @__PURE__ */ jsx11("span", { className: "text-sm font-medium truncate", children: name || slug }),
|
|
577
|
+
/* @__PURE__ */ jsxs8(
|
|
578
|
+
"span",
|
|
579
|
+
{
|
|
580
|
+
className: `inline-flex items-center h-5 text-[10px] px-1.5 gap-1 rounded-md border ${visual.classes}`,
|
|
581
|
+
children: [
|
|
582
|
+
/* @__PURE__ */ jsx11(Icon, { className: "h-3 w-3 mr-1" }),
|
|
583
|
+
visual.label
|
|
584
|
+
]
|
|
585
|
+
}
|
|
586
|
+
)
|
|
587
|
+
] }),
|
|
588
|
+
description && /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground mt-0.5 line-clamp-1", children: description }),
|
|
589
|
+
/* @__PURE__ */ jsx11("code", { className: "text-[10px] text-muted-foreground bg-muted px-1.5 py-0.5 rounded mt-1 inline-block", children: slug })
|
|
590
|
+
] }),
|
|
591
|
+
/* @__PURE__ */ jsxs8(
|
|
592
|
+
"select",
|
|
593
|
+
{
|
|
594
|
+
value: state,
|
|
595
|
+
onChange: (e) => onChange(slug, e.target.value),
|
|
596
|
+
"aria-label": `Stato per ${slug}`,
|
|
597
|
+
className: "h-8 w-[140px] text-xs flex-shrink-0 rounded-md border border-input bg-background px-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
598
|
+
children: [
|
|
599
|
+
/* @__PURE__ */ jsx11("option", { value: "off", title: FEATURE_STATE_DESCRIPTIONS.off, children: "Non inclusa" }),
|
|
600
|
+
/* @__PURE__ */ jsx11("option", { value: "teaser", title: FEATURE_STATE_DESCRIPTIONS.teaser, children: "Teaser" }),
|
|
601
|
+
/* @__PURE__ */ jsx11("option", { value: "on", title: FEATURE_STATE_DESCRIPTIONS.on, children: "Inclusa" }),
|
|
602
|
+
showSunset && /* @__PURE__ */ jsx11("option", { value: "sunset", title: FEATURE_STATE_DESCRIPTIONS.sunset, children: "Sunset (admin)" })
|
|
603
|
+
]
|
|
604
|
+
}
|
|
605
|
+
),
|
|
606
|
+
onRemove && /* @__PURE__ */ jsx11(
|
|
607
|
+
"button",
|
|
608
|
+
{
|
|
609
|
+
type: "button",
|
|
610
|
+
onClick: onRemove,
|
|
611
|
+
className: "text-muted-foreground hover:text-destructive text-xs mt-1",
|
|
612
|
+
"aria-label": `Rimuovi override ${slug}`,
|
|
613
|
+
children: "\u2715"
|
|
614
|
+
}
|
|
615
|
+
)
|
|
616
|
+
] });
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// src/components/plans/PlanFeaturesSection.tsx
|
|
620
|
+
import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
621
|
+
function PlanFeaturesSection({
|
|
622
|
+
availableFeatures,
|
|
623
|
+
selectedFeatureSlugs,
|
|
624
|
+
uiVisibility,
|
|
625
|
+
onFeatureStateChange
|
|
626
|
+
}) {
|
|
627
|
+
const [showSunset, setShowSunset] = useState(false);
|
|
628
|
+
const rowSlugs = useMemo2(() => {
|
|
629
|
+
const out = new Set(availableFeatures.map((f) => f.slug));
|
|
630
|
+
for (const slug of selectedFeatureSlugs) out.add(slug);
|
|
631
|
+
for (const slug of Object.keys(uiVisibility)) out.add(slug);
|
|
632
|
+
return Array.from(out).sort();
|
|
633
|
+
}, [availableFeatures, selectedFeatureSlugs, uiVisibility]);
|
|
634
|
+
const stateCounts = useMemo2(
|
|
635
|
+
() => summarizeFeatureStates(selectedFeatureSlugs, uiVisibility),
|
|
636
|
+
[selectedFeatureSlugs, uiVisibility]
|
|
637
|
+
);
|
|
638
|
+
const handleChange = (slug, next) => {
|
|
639
|
+
const result = applyFeatureState(
|
|
640
|
+
slug,
|
|
641
|
+
next,
|
|
642
|
+
selectedFeatureSlugs,
|
|
643
|
+
uiVisibility
|
|
644
|
+
);
|
|
645
|
+
onFeatureStateChange(result);
|
|
646
|
+
};
|
|
647
|
+
return /* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
|
|
648
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between flex-wrap gap-2", children: [
|
|
649
|
+
/* @__PURE__ */ jsx12("label", { className: "text-sm font-medium leading-none", children: "Funzionalit\xE0 del piano" }),
|
|
650
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1 text-xs", children: [
|
|
651
|
+
stateCounts.on > 0 && /* @__PURE__ */ jsxs9("span", { className: "inline-flex items-center rounded-md border px-1.5 py-0.5 bg-green-100 text-green-800 border-green-300 dark:bg-green-900/30 dark:text-green-400 dark:border-green-700", children: [
|
|
652
|
+
stateCounts.on,
|
|
653
|
+
" inclusa",
|
|
654
|
+
stateCounts.on !== 1 ? "e" : ""
|
|
655
|
+
] }),
|
|
656
|
+
stateCounts.teaser > 0 && /* @__PURE__ */ jsxs9("span", { className: "inline-flex items-center rounded-md border px-1.5 py-0.5 bg-amber-100 text-amber-800 border-amber-300 dark:bg-amber-900/30 dark:text-amber-400 dark:border-amber-700", children: [
|
|
657
|
+
stateCounts.teaser,
|
|
658
|
+
" teaser"
|
|
659
|
+
] }),
|
|
660
|
+
stateCounts.sunset > 0 && /* @__PURE__ */ jsxs9("span", { className: "inline-flex items-center rounded-md border px-1.5 py-0.5 bg-rose-100 text-rose-800 border-rose-300 dark:bg-rose-900/30 dark:text-rose-400 dark:border-rose-700", children: [
|
|
661
|
+
stateCounts.sunset,
|
|
662
|
+
" sunset"
|
|
663
|
+
] })
|
|
664
|
+
] })
|
|
665
|
+
] }),
|
|
666
|
+
/* @__PURE__ */ jsxs9("p", { className: "text-xs text-muted-foreground", children: [
|
|
667
|
+
"Per ogni funzionalit\xE0 scegli lo stato: ",
|
|
668
|
+
/* @__PURE__ */ jsx12("strong", { children: "Non inclusa" }),
|
|
669
|
+
" ",
|
|
670
|
+
"(default), ",
|
|
671
|
+
/* @__PURE__ */ jsx12("strong", { children: "Teaser" }),
|
|
672
|
+
" (mostrata come locked con CTA upgrade), o ",
|
|
673
|
+
/* @__PURE__ */ jsx12("strong", { children: "Inclusa" }),
|
|
674
|
+
" (attiva). Lo stato scrive automaticamente le feature del piano e la visibility UI."
|
|
675
|
+
] }),
|
|
676
|
+
/* @__PURE__ */ jsx12("div", { className: "border rounded-lg p-2 space-y-1 max-h-[320px] overflow-y-auto", children: rowSlugs.length === 0 ? /* @__PURE__ */ jsx12("p", { className: "text-sm text-muted-foreground text-center py-4", children: "Nessuna funzionalit\xE0 disponibile nel catalogo." }) : rowSlugs.map((slug) => {
|
|
677
|
+
const feature = availableFeatures.find((f) => f.slug === slug);
|
|
678
|
+
const state = featureStateFromPlan(
|
|
679
|
+
slug,
|
|
680
|
+
selectedFeatureSlugs,
|
|
681
|
+
uiVisibility
|
|
682
|
+
);
|
|
683
|
+
return /* @__PURE__ */ jsx12(
|
|
684
|
+
PlanFeatureRow,
|
|
685
|
+
{
|
|
686
|
+
slug,
|
|
687
|
+
name: feature?.name,
|
|
688
|
+
description: feature?.description,
|
|
689
|
+
state,
|
|
690
|
+
showSunset,
|
|
691
|
+
onChange: handleChange
|
|
692
|
+
},
|
|
693
|
+
slug
|
|
694
|
+
);
|
|
695
|
+
}) }),
|
|
696
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2 pt-1", children: [
|
|
697
|
+
/* @__PURE__ */ jsx12(
|
|
698
|
+
"input",
|
|
699
|
+
{
|
|
700
|
+
id: "show-advanced-state",
|
|
701
|
+
type: "checkbox",
|
|
702
|
+
checked: showSunset,
|
|
703
|
+
onChange: (e) => setShowSunset(e.target.checked),
|
|
704
|
+
className: "h-4 w-4 rounded border-input cursor-pointer"
|
|
705
|
+
}
|
|
706
|
+
),
|
|
707
|
+
/* @__PURE__ */ jsx12(
|
|
708
|
+
"label",
|
|
709
|
+
{
|
|
710
|
+
htmlFor: "show-advanced-state",
|
|
711
|
+
className: "text-xs text-muted-foreground cursor-pointer",
|
|
712
|
+
children: 'Mostra stato avanzato "Sunset" (feature deprecate)'
|
|
713
|
+
}
|
|
714
|
+
)
|
|
715
|
+
] })
|
|
716
|
+
] });
|
|
717
|
+
}
|
|
718
|
+
|
|
230
719
|
// src/components/plans/PlanFormDialog.tsx
|
|
231
|
-
import { jsx as
|
|
720
|
+
import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
232
721
|
function createDefaultPlanFormData(limitFields = []) {
|
|
233
722
|
const limits = {};
|
|
234
723
|
const limitsUnlimited = {};
|
|
@@ -252,9 +741,13 @@ function createDefaultPlanFormData(limitFields = []) {
|
|
|
252
741
|
limits,
|
|
253
742
|
limits_unlimited: limitsUnlimited,
|
|
254
743
|
features: "",
|
|
744
|
+
ui_visibility: {},
|
|
255
745
|
sort_order: 0
|
|
256
746
|
};
|
|
257
747
|
}
|
|
748
|
+
function generateSlug(name) {
|
|
749
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
750
|
+
}
|
|
258
751
|
function PlanFormDialog({
|
|
259
752
|
open,
|
|
260
753
|
onOpenChange,
|
|
@@ -268,221 +761,74 @@ function PlanFormDialog({
|
|
|
268
761
|
const config = useBillingAdminConfig();
|
|
269
762
|
const limitFields = config.plans?.limitFields || [];
|
|
270
763
|
const featuresConfig = config.plans?.features;
|
|
764
|
+
const isCreating = !selectedPlan;
|
|
271
765
|
const { data: featuresData } = useQuery({
|
|
272
766
|
queryKey: ["billing-admin-available-features"],
|
|
273
767
|
queryFn: () => featuresConfig?.fetchFn?.() ?? Promise.resolve({ features: [] }),
|
|
274
768
|
enabled: !!featuresConfig?.enabled && !!featuresConfig?.fetchFn
|
|
275
769
|
});
|
|
276
770
|
const availableFeatures = featuresData?.features || [];
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
771
|
+
const selectedFeatureSlugs = useMemo3(
|
|
772
|
+
() => formData.features ? formData.features.split("\n").map((line) => line.trim()).filter(Boolean) : [],
|
|
773
|
+
[formData.features]
|
|
774
|
+
);
|
|
775
|
+
const uiVisibility = useMemo3(
|
|
776
|
+
() => formData.ui_visibility ?? {},
|
|
777
|
+
[formData.ui_visibility]
|
|
778
|
+
);
|
|
779
|
+
const handleNameChange = (name) => {
|
|
780
|
+
if (isCreating) {
|
|
781
|
+
onFormDataChange({ name, slug: generateSlug(name) });
|
|
782
|
+
} else {
|
|
783
|
+
onFormDataChange({ name });
|
|
282
784
|
}
|
|
283
|
-
}
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
785
|
+
};
|
|
786
|
+
const handleFeatureStateChange = (next) => {
|
|
787
|
+
onFormDataChange({
|
|
788
|
+
features: next.features.join("\n"),
|
|
789
|
+
ui_visibility: next.uiVisibility
|
|
790
|
+
});
|
|
288
791
|
};
|
|
289
792
|
if (!open) return null;
|
|
290
|
-
return /* @__PURE__ */
|
|
291
|
-
/* @__PURE__ */
|
|
292
|
-
/* @__PURE__ */
|
|
293
|
-
/* @__PURE__ */
|
|
294
|
-
/* @__PURE__ */
|
|
295
|
-
/* @__PURE__ */
|
|
793
|
+
return /* @__PURE__ */ jsxs10("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
794
|
+
/* @__PURE__ */ jsx13("div", { className: "fixed inset-0 bg-black/80", onClick: () => onOpenChange(false) }),
|
|
795
|
+
/* @__PURE__ */ jsxs10("div", { className: "relative z-50 w-full max-w-2xl max-h-[90vh] overflow-y-auto rounded-lg border bg-background p-6 shadow-lg", children: [
|
|
796
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex flex-col space-y-1.5 pb-4", children: [
|
|
797
|
+
/* @__PURE__ */ jsx13("h2", { className: "text-lg font-semibold leading-none tracking-tight", children: selectedPlan ? "Modifica Piano" : "Nuovo Piano" }),
|
|
798
|
+
/* @__PURE__ */ jsx13("p", { className: "text-sm text-muted-foreground", children: selectedPlan ? "Modifica le impostazioni del piano selezionato" : "Crea un nuovo piano di abbonamento" })
|
|
296
799
|
] }),
|
|
297
|
-
/* @__PURE__ */
|
|
298
|
-
/* @__PURE__ */
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
formErrors.name && /* @__PURE__ */ jsx6("p", { className: "text-xs text-destructive", children: formErrors.name })
|
|
327
|
-
] })
|
|
328
|
-
] }),
|
|
329
|
-
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
330
|
-
/* @__PURE__ */ jsx6("label", { className: "text-sm font-medium leading-none", htmlFor: "description", children: "Descrizione" }),
|
|
331
|
-
/* @__PURE__ */ jsx6(
|
|
332
|
-
"textarea",
|
|
333
|
-
{
|
|
334
|
-
id: "description",
|
|
335
|
-
className: "flex min-h-[60px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
336
|
-
value: formData.description,
|
|
337
|
-
onChange: (e) => onFormDataChange({ description: e.target.value }),
|
|
338
|
-
placeholder: "Descrizione del piano...",
|
|
339
|
-
rows: 2
|
|
340
|
-
}
|
|
341
|
-
)
|
|
342
|
-
] }),
|
|
343
|
-
/* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-3 gap-4", children: [
|
|
344
|
-
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
345
|
-
/* @__PURE__ */ jsx6("label", { className: "text-sm font-medium leading-none", children: "Modello Pricing" }),
|
|
346
|
-
/* @__PURE__ */ jsxs3(
|
|
347
|
-
"select",
|
|
348
|
-
{
|
|
349
|
-
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
350
|
-
value: formData.pricing_model,
|
|
351
|
-
onChange: (e) => onFormDataChange({ pricing_model: e.target.value }),
|
|
352
|
-
children: [
|
|
353
|
-
/* @__PURE__ */ jsx6("option", { value: "flat", children: "Fisso" }),
|
|
354
|
-
/* @__PURE__ */ jsx6("option", { value: "tiered", children: "A Scaglioni" }),
|
|
355
|
-
/* @__PURE__ */ jsx6("option", { value: "per_unit", children: "Per Unit\xE0" }),
|
|
356
|
-
/* @__PURE__ */ jsx6("option", { value: "usage_based", children: "A Consumo" })
|
|
357
|
-
]
|
|
358
|
-
}
|
|
359
|
-
)
|
|
360
|
-
] }),
|
|
361
|
-
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
362
|
-
/* @__PURE__ */ jsx6("label", { className: "text-sm font-medium leading-none", htmlFor: "price_monthly", children: "Prezzo Mensile" }),
|
|
363
|
-
/* @__PURE__ */ jsx6(
|
|
364
|
-
"input",
|
|
365
|
-
{
|
|
366
|
-
id: "price_monthly",
|
|
367
|
-
type: "number",
|
|
368
|
-
step: "0.01",
|
|
369
|
-
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
370
|
-
value: formData.price_monthly,
|
|
371
|
-
onChange: (e) => onFormDataChange({ price_monthly: e.target.value }),
|
|
372
|
-
placeholder: "0.00"
|
|
373
|
-
}
|
|
374
|
-
)
|
|
375
|
-
] }),
|
|
376
|
-
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
377
|
-
/* @__PURE__ */ jsx6("label", { className: "text-sm font-medium leading-none", htmlFor: "price_yearly", children: "Prezzo Annuale" }),
|
|
378
|
-
/* @__PURE__ */ jsx6(
|
|
379
|
-
"input",
|
|
380
|
-
{
|
|
381
|
-
id: "price_yearly",
|
|
382
|
-
type: "number",
|
|
383
|
-
step: "0.01",
|
|
384
|
-
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
385
|
-
value: formData.price_yearly,
|
|
386
|
-
onChange: (e) => onFormDataChange({ price_yearly: e.target.value }),
|
|
387
|
-
placeholder: "0.00"
|
|
388
|
-
}
|
|
389
|
-
)
|
|
390
|
-
] })
|
|
391
|
-
] }),
|
|
392
|
-
/* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-3 gap-4", children: [
|
|
393
|
-
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
394
|
-
/* @__PURE__ */ jsx6("label", { className: "text-sm font-medium leading-none", children: "Valuta" }),
|
|
395
|
-
/* @__PURE__ */ jsxs3(
|
|
396
|
-
"select",
|
|
397
|
-
{
|
|
398
|
-
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
399
|
-
value: formData.currency,
|
|
400
|
-
onChange: (e) => onFormDataChange({ currency: e.target.value }),
|
|
401
|
-
children: [
|
|
402
|
-
/* @__PURE__ */ jsx6("option", { value: "eur", children: "EUR" }),
|
|
403
|
-
/* @__PURE__ */ jsx6("option", { value: "usd", children: "USD" }),
|
|
404
|
-
/* @__PURE__ */ jsx6("option", { value: "gbp", children: "GBP" })
|
|
405
|
-
]
|
|
406
|
-
}
|
|
407
|
-
)
|
|
408
|
-
] }),
|
|
409
|
-
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
410
|
-
/* @__PURE__ */ jsx6("label", { className: "text-sm font-medium leading-none", htmlFor: "trial_days", children: "Giorni Trial" }),
|
|
411
|
-
/* @__PURE__ */ jsx6("input", { id: "trial_days", type: "number", className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: formData.trial_days, onChange: (e) => onFormDataChange({ trial_days: parseInt(e.target.value) || 0 }) })
|
|
412
|
-
] }),
|
|
413
|
-
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
414
|
-
/* @__PURE__ */ jsx6("label", { className: "text-sm font-medium leading-none", htmlFor: "sort_order", children: "Ordine" }),
|
|
415
|
-
/* @__PURE__ */ jsx6("input", { id: "sort_order", type: "number", className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: formData.sort_order, onChange: (e) => onFormDataChange({ sort_order: parseInt(e.target.value) || 0 }) })
|
|
416
|
-
] })
|
|
417
|
-
] }),
|
|
418
|
-
/* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
419
|
-
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
420
|
-
/* @__PURE__ */ jsx6("label", { className: "text-sm font-medium leading-none", children: "Colore Badge" }),
|
|
421
|
-
/* @__PURE__ */ jsx6(
|
|
422
|
-
"select",
|
|
423
|
-
{
|
|
424
|
-
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
425
|
-
value: formData.color,
|
|
426
|
-
onChange: (e) => onFormDataChange({ color: e.target.value }),
|
|
427
|
-
children: PLAN_COLORS.map((color) => /* @__PURE__ */ jsx6("option", { value: color.value, children: color.label }, color.value))
|
|
428
|
-
}
|
|
429
|
-
),
|
|
430
|
-
/* @__PURE__ */ jsxs3("p", { className: "text-xs text-muted-foreground", children: [
|
|
431
|
-
"Anteprima: ",
|
|
432
|
-
/* @__PURE__ */ jsx6("span", { className: `inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold ${getPlanColorClass(formData.color)}`, children: formData.name || "Piano" })
|
|
433
|
-
] })
|
|
434
|
-
] }),
|
|
435
|
-
/* @__PURE__ */ jsxs3("div", { className: "space-y-4 pt-6", children: [
|
|
436
|
-
/* @__PURE__ */ jsx6(ToggleSwitch, { label: "Piano Attivo", checked: formData.is_active, onChange: (v) => onFormDataChange({ is_active: v }) }),
|
|
437
|
-
/* @__PURE__ */ jsx6(ToggleSwitch, { label: "Contattaci per Prezzi", checked: formData.contact_sales, onChange: (v) => onFormDataChange({ contact_sales: v }) }),
|
|
438
|
-
/* @__PURE__ */ jsx6(ToggleSwitch, { label: "Visibile in Homepage", checked: formData.is_public, onChange: (v) => onFormDataChange({ is_public: v }) })
|
|
439
|
-
] })
|
|
440
|
-
] }),
|
|
441
|
-
limitFields.length > 0 && /* @__PURE__ */ jsxs3("div", { className: "space-y-3", children: [
|
|
442
|
-
/* @__PURE__ */ jsx6("label", { className: "text-sm font-medium leading-none", children: "Limiti Piano" }),
|
|
443
|
-
/* @__PURE__ */ jsx6("div", { className: "grid grid-cols-2 gap-3", children: limitFields.map((field) => /* @__PURE__ */ jsx6(
|
|
444
|
-
LimitInput,
|
|
445
|
-
{
|
|
446
|
-
id: `limit_${field.key}`,
|
|
447
|
-
label: field.label,
|
|
448
|
-
value: formData.limits[field.key] ?? field.defaultValue ?? 1,
|
|
449
|
-
isUnlimited: formData.limits_unlimited[field.key] ?? false,
|
|
450
|
-
onValueChange: (v) => onFormDataChange({ limits: { ...formData.limits, [field.key]: v } }),
|
|
451
|
-
onUnlimitedChange: (v) => onFormDataChange({ limits_unlimited: { ...formData.limits_unlimited, [field.key]: v } })
|
|
452
|
-
},
|
|
453
|
-
field.key
|
|
454
|
-
)) })
|
|
455
|
-
] }),
|
|
456
|
-
featuresConfig?.enabled && availableFeatures.length > 0 && /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
457
|
-
/* @__PURE__ */ jsx6("label", { className: "text-sm font-medium leading-none", children: "Features Incluse nel Piano" }),
|
|
458
|
-
/* @__PURE__ */ jsx6("div", { className: "border rounded-lg p-3 space-y-2 max-h-64 overflow-y-auto", children: availableFeatures.map((feature) => /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 p-2 hover:bg-muted/50 rounded", children: [
|
|
459
|
-
/* @__PURE__ */ jsx6(
|
|
460
|
-
"input",
|
|
461
|
-
{
|
|
462
|
-
type: "checkbox",
|
|
463
|
-
id: `feature-${feature.slug}`,
|
|
464
|
-
checked: selectedFeatureSlugs.includes(feature.slug),
|
|
465
|
-
onChange: (e) => handleFeatureToggle(feature.slug, e.target.checked),
|
|
466
|
-
className: "h-4 w-4 rounded border-input"
|
|
467
|
-
}
|
|
468
|
-
),
|
|
469
|
-
/* @__PURE__ */ jsxs3("div", { className: "flex-1", children: [
|
|
470
|
-
/* @__PURE__ */ jsx6("label", { htmlFor: `feature-${feature.slug}`, className: "text-sm font-medium cursor-pointer", children: feature.name }),
|
|
471
|
-
feature.description && /* @__PURE__ */ jsx6("p", { className: "text-xs text-muted-foreground", children: feature.description })
|
|
472
|
-
] }),
|
|
473
|
-
/* @__PURE__ */ jsx6("code", { className: "text-xs text-muted-foreground bg-muted px-2 py-0.5 rounded", children: feature.slug })
|
|
474
|
-
] }, feature.slug)) }),
|
|
475
|
-
selectedFeatureSlugs.length > 0 && /* @__PURE__ */ jsx6("div", { className: "flex flex-wrap gap-2 pt-2", children: selectedFeatureSlugs.map((slug) => {
|
|
476
|
-
const feature = availableFeatures.find((f) => f.slug === slug);
|
|
477
|
-
return /* @__PURE__ */ jsxs3("span", { className: "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold bg-secondary text-secondary-foreground gap-1", children: [
|
|
478
|
-
feature?.name || slug,
|
|
479
|
-
/* @__PURE__ */ jsx6("button", { type: "button", onClick: () => handleFeatureToggle(slug, false), className: "ml-1 hover:bg-destructive/20 rounded-full", children: /* @__PURE__ */ jsx6(XIcon, { className: "h-3 w-3" }) })
|
|
480
|
-
] }, slug);
|
|
481
|
-
}) })
|
|
482
|
-
] })
|
|
800
|
+
/* @__PURE__ */ jsxs10("div", { className: "grid gap-4 py-4", children: [
|
|
801
|
+
/* @__PURE__ */ jsx13(
|
|
802
|
+
PlanDetailsSection,
|
|
803
|
+
{
|
|
804
|
+
formData,
|
|
805
|
+
formErrors,
|
|
806
|
+
isCreating,
|
|
807
|
+
onFormDataChange,
|
|
808
|
+
onNameChange: handleNameChange
|
|
809
|
+
}
|
|
810
|
+
),
|
|
811
|
+
/* @__PURE__ */ jsx13(PlanPricingSection, { formData, onFormDataChange }),
|
|
812
|
+
/* @__PURE__ */ jsx13(
|
|
813
|
+
PlanLimitsSection,
|
|
814
|
+
{
|
|
815
|
+
limitFields,
|
|
816
|
+
formData,
|
|
817
|
+
onFormDataChange
|
|
818
|
+
}
|
|
819
|
+
),
|
|
820
|
+
featuresConfig?.enabled && /* @__PURE__ */ jsx13(
|
|
821
|
+
PlanFeaturesSection,
|
|
822
|
+
{
|
|
823
|
+
availableFeatures,
|
|
824
|
+
selectedFeatureSlugs,
|
|
825
|
+
uiVisibility,
|
|
826
|
+
onFeatureStateChange: handleFeatureStateChange
|
|
827
|
+
}
|
|
828
|
+
)
|
|
483
829
|
] }),
|
|
484
|
-
/* @__PURE__ */
|
|
485
|
-
/* @__PURE__ */
|
|
830
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 pt-4", children: [
|
|
831
|
+
/* @__PURE__ */ jsx13(
|
|
486
832
|
"button",
|
|
487
833
|
{
|
|
488
834
|
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 border border-input bg-background hover:bg-accent hover:text-accent-foreground mt-2 sm:mt-0",
|
|
@@ -490,14 +836,14 @@ function PlanFormDialog({
|
|
|
490
836
|
children: "Annulla"
|
|
491
837
|
}
|
|
492
838
|
),
|
|
493
|
-
/* @__PURE__ */
|
|
839
|
+
/* @__PURE__ */ jsxs10(
|
|
494
840
|
"button",
|
|
495
841
|
{
|
|
496
842
|
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50",
|
|
497
843
|
onClick: onSubmit,
|
|
498
844
|
disabled: isPending,
|
|
499
845
|
children: [
|
|
500
|
-
isPending && /* @__PURE__ */
|
|
846
|
+
isPending && /* @__PURE__ */ jsx13(Loader2, { className: "w-4 h-4 animate-spin mr-2" }),
|
|
501
847
|
selectedPlan ? "Salva Modifiche" : "Crea Piano"
|
|
502
848
|
]
|
|
503
849
|
}
|
|
@@ -506,26 +852,10 @@ function PlanFormDialog({
|
|
|
506
852
|
] })
|
|
507
853
|
] });
|
|
508
854
|
}
|
|
509
|
-
function ToggleSwitch({ label, checked, onChange }) {
|
|
510
|
-
return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
|
|
511
|
-
/* @__PURE__ */ jsx6(
|
|
512
|
-
"button",
|
|
513
|
-
{
|
|
514
|
-
type: "button",
|
|
515
|
-
role: "switch",
|
|
516
|
-
"aria-checked": checked,
|
|
517
|
-
onClick: () => onChange(!checked),
|
|
518
|
-
className: `peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors ${checked ? "bg-primary" : "bg-input"}`,
|
|
519
|
-
children: /* @__PURE__ */ jsx6("span", { className: `pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform ${checked ? "translate-x-4" : "translate-x-0"}` })
|
|
520
|
-
}
|
|
521
|
-
),
|
|
522
|
-
/* @__PURE__ */ jsx6("span", { className: "text-sm font-medium leading-none", children: label })
|
|
523
|
-
] });
|
|
524
|
-
}
|
|
525
855
|
|
|
526
856
|
// src/components/shared/DeleteConfirmDialog.tsx
|
|
527
857
|
import { Loader2 as Loader22 } from "lucide-react";
|
|
528
|
-
import { jsx as
|
|
858
|
+
import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
529
859
|
function DeleteConfirmDialog({
|
|
530
860
|
isOpen,
|
|
531
861
|
onOpenChange,
|
|
@@ -537,21 +867,21 @@ function DeleteConfirmDialog({
|
|
|
537
867
|
cancelLabel = "Annulla"
|
|
538
868
|
}) {
|
|
539
869
|
if (!isOpen) return null;
|
|
540
|
-
return /* @__PURE__ */
|
|
541
|
-
/* @__PURE__ */
|
|
870
|
+
return /* @__PURE__ */ jsxs11("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
871
|
+
/* @__PURE__ */ jsx14(
|
|
542
872
|
"div",
|
|
543
873
|
{
|
|
544
874
|
className: "fixed inset-0 bg-black/80",
|
|
545
875
|
onClick: () => onOpenChange(false)
|
|
546
876
|
}
|
|
547
877
|
),
|
|
548
|
-
/* @__PURE__ */
|
|
549
|
-
/* @__PURE__ */
|
|
550
|
-
/* @__PURE__ */
|
|
551
|
-
/* @__PURE__ */
|
|
878
|
+
/* @__PURE__ */ jsxs11("div", { className: "relative z-50 w-full max-w-lg rounded-lg border bg-background p-6 shadow-lg", children: [
|
|
879
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex flex-col space-y-2 text-center sm:text-left", children: [
|
|
880
|
+
/* @__PURE__ */ jsx14("h2", { className: "text-lg font-semibold", children: title }),
|
|
881
|
+
/* @__PURE__ */ jsx14("div", { className: "text-sm text-muted-foreground", children: description })
|
|
552
882
|
] }),
|
|
553
|
-
/* @__PURE__ */
|
|
554
|
-
/* @__PURE__ */
|
|
883
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 mt-4", children: [
|
|
884
|
+
/* @__PURE__ */ jsx14(
|
|
555
885
|
"button",
|
|
556
886
|
{
|
|
557
887
|
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 border border-input bg-background hover:bg-accent hover:text-accent-foreground mt-2 sm:mt-0",
|
|
@@ -559,14 +889,14 @@ function DeleteConfirmDialog({
|
|
|
559
889
|
children: cancelLabel
|
|
560
890
|
}
|
|
561
891
|
),
|
|
562
|
-
/* @__PURE__ */
|
|
892
|
+
/* @__PURE__ */ jsxs11(
|
|
563
893
|
"button",
|
|
564
894
|
{
|
|
565
895
|
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
566
896
|
onClick: onConfirm,
|
|
567
897
|
disabled: isDeleting,
|
|
568
898
|
children: [
|
|
569
|
-
isDeleting && /* @__PURE__ */
|
|
899
|
+
isDeleting && /* @__PURE__ */ jsx14(Loader22, { className: "w-4 h-4 animate-spin mr-2" }),
|
|
570
900
|
confirmLabel
|
|
571
901
|
]
|
|
572
902
|
}
|
|
@@ -577,14 +907,13 @@ function DeleteConfirmDialog({
|
|
|
577
907
|
}
|
|
578
908
|
|
|
579
909
|
// src/pages/PlansPage.tsx
|
|
580
|
-
import { Fragment as
|
|
910
|
+
import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
581
911
|
function PlansPage({ wrapper: Wrapper, header }) {
|
|
582
912
|
const config = useBillingAdminConfig();
|
|
583
913
|
const limitFields = config.plans?.limitFields || [];
|
|
584
914
|
const publicPlansConfig = config.plans?.publicPlans;
|
|
585
|
-
const
|
|
586
|
-
const
|
|
587
|
-
const [selectedPlan, setSelectedPlan] = useState2(null);
|
|
915
|
+
const formDialog = useDialogState();
|
|
916
|
+
const deleteDialog = useDialogState();
|
|
588
917
|
const [formData, setFormData] = useState2(createDefaultPlanFormData(limitFields));
|
|
589
918
|
const [formErrors, setFormErrors] = useState2({});
|
|
590
919
|
const { data, isLoading, refetch } = useAdminPlans();
|
|
@@ -601,15 +930,13 @@ function PlansPage({ wrapper: Wrapper, header }) {
|
|
|
601
930
|
const plans = data?.items || [];
|
|
602
931
|
const resetForm = () => {
|
|
603
932
|
setFormData(createDefaultPlanFormData(limitFields));
|
|
604
|
-
setSelectedPlan(null);
|
|
605
933
|
setFormErrors({});
|
|
606
934
|
};
|
|
607
935
|
const openCreateForm = () => {
|
|
608
936
|
resetForm();
|
|
609
|
-
|
|
937
|
+
formDialog.open(void 0);
|
|
610
938
|
};
|
|
611
939
|
const openEditForm = (plan) => {
|
|
612
|
-
setSelectedPlan(plan);
|
|
613
940
|
const planLimits = plan.limits || {};
|
|
614
941
|
const limits = {};
|
|
615
942
|
const limitsUnlimited = {};
|
|
@@ -634,27 +961,28 @@ function PlansPage({ wrapper: Wrapper, header }) {
|
|
|
634
961
|
limits,
|
|
635
962
|
limits_unlimited: limitsUnlimited,
|
|
636
963
|
features: plan.features?.join("\n") || "",
|
|
964
|
+
ui_visibility: plan.ui_visibility ?? {},
|
|
637
965
|
sort_order: plan.sort_order
|
|
638
966
|
});
|
|
639
|
-
|
|
967
|
+
formDialog.open(plan);
|
|
640
968
|
};
|
|
641
969
|
const handleValidate = () => {
|
|
642
970
|
const errors = {};
|
|
643
|
-
const slugErr = validateSlug(formData.slug);
|
|
644
|
-
if (slugErr) errors.slug = slugErr;
|
|
645
971
|
const nameErr = validateRequired(formData.name, "Il nome");
|
|
646
972
|
if (nameErr) errors.name = nameErr;
|
|
647
973
|
setFormErrors(errors);
|
|
648
974
|
return Object.keys(errors).length === 0;
|
|
649
975
|
};
|
|
650
976
|
const handleSubmit = async () => {
|
|
977
|
+
const resolvedSlug = formData.slug.trim() || formData.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
978
|
+
const finalFormData = { ...formData, slug: resolvedSlug };
|
|
651
979
|
if (!handleValidate()) return;
|
|
652
980
|
const limits = {};
|
|
653
981
|
for (const field of limitFields) {
|
|
654
|
-
limits[field.key] =
|
|
982
|
+
limits[field.key] = finalFormData.limits_unlimited[field.key] ? -1 : finalFormData.limits[field.key] ?? 1;
|
|
655
983
|
}
|
|
656
984
|
const planData = {
|
|
657
|
-
slug:
|
|
985
|
+
slug: finalFormData.slug,
|
|
658
986
|
name: formData.name,
|
|
659
987
|
description: formData.description || void 0,
|
|
660
988
|
pricing_model: formData.pricing_model,
|
|
@@ -667,13 +995,16 @@ function PlansPage({ wrapper: Wrapper, header }) {
|
|
|
667
995
|
color: formData.color,
|
|
668
996
|
limits: Object.keys(limits).length > 0 ? limits : void 0,
|
|
669
997
|
features: formData.features.split("\n").filter((f) => f.trim()),
|
|
998
|
+
// Send the full map — empty ``{}`` is meaningful (clears any
|
|
999
|
+
// lingering overrides) so we never fall back to ``undefined``.
|
|
1000
|
+
ui_visibility: formData.ui_visibility ?? {},
|
|
670
1001
|
sort_order: formData.sort_order
|
|
671
1002
|
};
|
|
672
1003
|
if (publicPlansConfig?.enabled && publicPlansConfig.updateFn) {
|
|
673
|
-
const isCurrentlyPublic = publicPlanSlugs.includes(
|
|
674
|
-
if (
|
|
1004
|
+
const isCurrentlyPublic = publicPlanSlugs.includes(finalFormData.slug);
|
|
1005
|
+
if (finalFormData.is_public !== isCurrentlyPublic) {
|
|
675
1006
|
try {
|
|
676
|
-
const newSlugs =
|
|
1007
|
+
const newSlugs = finalFormData.is_public ? [...publicPlanSlugs, finalFormData.slug] : publicPlanSlugs.filter((s) => s !== finalFormData.slug);
|
|
677
1008
|
await publicPlansConfig.updateFn(newSlugs);
|
|
678
1009
|
setPublicPlanSlugs(newSlugs);
|
|
679
1010
|
} catch {
|
|
@@ -681,11 +1012,11 @@ function PlansPage({ wrapper: Wrapper, header }) {
|
|
|
681
1012
|
}
|
|
682
1013
|
}
|
|
683
1014
|
}
|
|
684
|
-
if (
|
|
685
|
-
updateMutation.mutate({ id:
|
|
1015
|
+
if (formDialog.data) {
|
|
1016
|
+
updateMutation.mutate({ id: formDialog.data.id, data: planData }, {
|
|
686
1017
|
onSuccess: () => {
|
|
687
1018
|
toast.success("Piano aggiornato con successo");
|
|
688
|
-
|
|
1019
|
+
formDialog.close();
|
|
689
1020
|
resetForm();
|
|
690
1021
|
},
|
|
691
1022
|
onError: (err) => toast.error(`Errore: ${err.message}`)
|
|
@@ -694,7 +1025,7 @@ function PlansPage({ wrapper: Wrapper, header }) {
|
|
|
694
1025
|
createMutation.mutate(planData, {
|
|
695
1026
|
onSuccess: () => {
|
|
696
1027
|
toast.success("Piano creato con successo");
|
|
697
|
-
|
|
1028
|
+
formDialog.close();
|
|
698
1029
|
resetForm();
|
|
699
1030
|
},
|
|
700
1031
|
onError: (err) => toast.error(`Errore: ${err.message}`)
|
|
@@ -702,49 +1033,45 @@ function PlansPage({ wrapper: Wrapper, header }) {
|
|
|
702
1033
|
}
|
|
703
1034
|
};
|
|
704
1035
|
const handleDelete = () => {
|
|
705
|
-
if (
|
|
706
|
-
deleteMutation.mutate(
|
|
1036
|
+
if (deleteDialog.data) {
|
|
1037
|
+
deleteMutation.mutate(deleteDialog.data.id, {
|
|
707
1038
|
onSuccess: () => {
|
|
708
1039
|
toast.success("Piano eliminato con successo");
|
|
709
|
-
|
|
710
|
-
setSelectedPlan(null);
|
|
1040
|
+
deleteDialog.close();
|
|
711
1041
|
},
|
|
712
1042
|
onError: (err) => toast.error(`Errore: ${err.message}`)
|
|
713
1043
|
});
|
|
714
1044
|
}
|
|
715
1045
|
};
|
|
716
|
-
const content = /* @__PURE__ */
|
|
1046
|
+
const content = /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
717
1047
|
header,
|
|
718
|
-
/* @__PURE__ */
|
|
719
|
-
/* @__PURE__ */
|
|
720
|
-
/* @__PURE__ */
|
|
721
|
-
/* @__PURE__ */
|
|
722
|
-
/* @__PURE__ */
|
|
1048
|
+
/* @__PURE__ */ jsxs12("div", { className: "space-y-4", children: [
|
|
1049
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between", children: [
|
|
1050
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
1051
|
+
/* @__PURE__ */ jsx15(LayoutGrid, { className: "w-4 h-4" }),
|
|
1052
|
+
/* @__PURE__ */ jsxs12("span", { children: [
|
|
723
1053
|
plans.length,
|
|
724
1054
|
" piani configurati"
|
|
725
1055
|
] })
|
|
726
1056
|
] }),
|
|
727
|
-
/* @__PURE__ */
|
|
728
|
-
/* @__PURE__ */
|
|
729
|
-
/* @__PURE__ */
|
|
1057
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex gap-2", children: [
|
|
1058
|
+
/* @__PURE__ */ jsxs12("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-3 border border-input bg-background hover:bg-accent", onClick: () => refetch(), children: [
|
|
1059
|
+
/* @__PURE__ */ jsx15(RefreshCw, { className: "w-4 h-4 mr-2" }),
|
|
730
1060
|
"Aggiorna"
|
|
731
1061
|
] }),
|
|
732
|
-
/* @__PURE__ */
|
|
733
|
-
/* @__PURE__ */
|
|
1062
|
+
/* @__PURE__ */ jsxs12("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-primary text-primary-foreground hover:bg-primary/90", onClick: openCreateForm, children: [
|
|
1063
|
+
/* @__PURE__ */ jsx15(Plus, { className: "w-4 h-4 mr-2" }),
|
|
734
1064
|
"Nuovo Piano"
|
|
735
1065
|
] })
|
|
736
1066
|
] })
|
|
737
1067
|
] }),
|
|
738
|
-
/* @__PURE__ */
|
|
1068
|
+
/* @__PURE__ */ jsx15("div", { className: "rounded-lg border bg-card text-card-foreground shadow-sm", children: /* @__PURE__ */ jsx15("div", { className: "p-0", children: /* @__PURE__ */ jsx15(
|
|
739
1069
|
PlansTable,
|
|
740
1070
|
{
|
|
741
1071
|
plans,
|
|
742
1072
|
isLoading,
|
|
743
1073
|
onEdit: openEditForm,
|
|
744
|
-
onDelete: (plan) =>
|
|
745
|
-
setSelectedPlan(plan);
|
|
746
|
-
setShowDeleteDialog(true);
|
|
747
|
-
},
|
|
1074
|
+
onDelete: (plan) => deleteDialog.open(plan),
|
|
748
1075
|
onSync: (id) => syncMutation.mutate(id, {
|
|
749
1076
|
onSuccess: () => toast.success("Piano sincronizzato con Stripe"),
|
|
750
1077
|
onError: (err) => toast.error(`Errore sync: ${err.message}`)
|
|
@@ -753,30 +1080,37 @@ function PlansPage({ wrapper: Wrapper, header }) {
|
|
|
753
1080
|
}
|
|
754
1081
|
) }) })
|
|
755
1082
|
] }),
|
|
756
|
-
/* @__PURE__ */
|
|
1083
|
+
/* @__PURE__ */ jsx15(
|
|
757
1084
|
PlanFormDialog,
|
|
758
1085
|
{
|
|
759
|
-
open:
|
|
760
|
-
onOpenChange:
|
|
1086
|
+
open: formDialog.isOpen,
|
|
1087
|
+
onOpenChange: (open) => {
|
|
1088
|
+
if (!open) {
|
|
1089
|
+
formDialog.close();
|
|
1090
|
+
resetForm();
|
|
1091
|
+
}
|
|
1092
|
+
},
|
|
761
1093
|
formData,
|
|
762
1094
|
onFormDataChange: (data2) => setFormData((prev) => ({ ...prev, ...data2 })),
|
|
763
1095
|
formErrors,
|
|
764
|
-
selectedPlan,
|
|
1096
|
+
selectedPlan: formDialog.data ?? null,
|
|
765
1097
|
onSubmit: handleSubmit,
|
|
766
1098
|
isPending: createMutation.isPending || updateMutation.isPending
|
|
767
1099
|
}
|
|
768
1100
|
),
|
|
769
|
-
/* @__PURE__ */
|
|
1101
|
+
/* @__PURE__ */ jsx15(
|
|
770
1102
|
DeleteConfirmDialog,
|
|
771
1103
|
{
|
|
772
|
-
isOpen:
|
|
773
|
-
onOpenChange:
|
|
1104
|
+
isOpen: deleteDialog.isOpen,
|
|
1105
|
+
onOpenChange: (open) => {
|
|
1106
|
+
if (!open) deleteDialog.close();
|
|
1107
|
+
},
|
|
774
1108
|
title: "Eliminare Piano?",
|
|
775
|
-
description: /* @__PURE__ */
|
|
1109
|
+
description: /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
776
1110
|
"Stai per eliminare il piano ",
|
|
777
|
-
/* @__PURE__ */
|
|
1111
|
+
/* @__PURE__ */ jsx15("strong", { children: deleteDialog.data?.name }),
|
|
778
1112
|
" (",
|
|
779
|
-
/* @__PURE__ */
|
|
1113
|
+
/* @__PURE__ */ jsx15("code", { children: deleteDialog.data?.slug }),
|
|
780
1114
|
"). Questa azione non pu\xF2 essere annullata."
|
|
781
1115
|
] }),
|
|
782
1116
|
onConfirm: handleDelete,
|
|
@@ -785,12 +1119,12 @@ function PlansPage({ wrapper: Wrapper, header }) {
|
|
|
785
1119
|
}
|
|
786
1120
|
)
|
|
787
1121
|
] });
|
|
788
|
-
return Wrapper ? /* @__PURE__ */
|
|
1122
|
+
return Wrapper ? /* @__PURE__ */ jsx15(Wrapper, { children: content }) : content;
|
|
789
1123
|
}
|
|
790
1124
|
|
|
791
1125
|
// src/pages/ProductsPage.tsx
|
|
792
1126
|
import { toast as toast2 } from "sonner";
|
|
793
|
-
import { Plus as Plus2, RefreshCw as RefreshCw2, Pencil as Pencil2, Trash2 as Trash22, Upload as Upload2, Package, Check as
|
|
1127
|
+
import { Plus as Plus2, RefreshCw as RefreshCw2, Pencil as Pencil2, Trash2 as Trash22, Upload as Upload2, Package, Check as Check3, X as X2 } from "lucide-react";
|
|
794
1128
|
import { useQuery as useQuery2 } from "@tanstack/react-query";
|
|
795
1129
|
import {
|
|
796
1130
|
useAdminProducts,
|
|
@@ -798,14 +1132,22 @@ import {
|
|
|
798
1132
|
useUpdateProduct,
|
|
799
1133
|
useDeleteProduct,
|
|
800
1134
|
useSyncProductToStripe,
|
|
801
|
-
useDialogState,
|
|
1135
|
+
useDialogState as useDialogState2,
|
|
802
1136
|
formatCurrencyFromCents
|
|
803
1137
|
} from "@growflowstudio/growflowbilling-admin-core";
|
|
804
1138
|
|
|
805
1139
|
// src/components/products/ProductFormDialog.tsx
|
|
806
|
-
import { useState as useState3, useEffect
|
|
1140
|
+
import { useState as useState3, useEffect } from "react";
|
|
807
1141
|
import { Loader2 as Loader23, Link as LinkIcon } from "lucide-react";
|
|
808
|
-
import { jsx as
|
|
1142
|
+
import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1143
|
+
var safeJsonParse = (str, fallback = {}) => {
|
|
1144
|
+
try {
|
|
1145
|
+
const parsed = JSON.parse(str);
|
|
1146
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : fallback;
|
|
1147
|
+
} catch {
|
|
1148
|
+
return fallback;
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
809
1151
|
var defaultProductFormData = {
|
|
810
1152
|
name: "",
|
|
811
1153
|
description: "",
|
|
@@ -829,7 +1171,7 @@ function ProductFormDialog({
|
|
|
829
1171
|
}) {
|
|
830
1172
|
const [formData, setFormData] = useState3(defaultProductFormData);
|
|
831
1173
|
const [formErrors, setFormErrors] = useState3({});
|
|
832
|
-
|
|
1174
|
+
useEffect(() => {
|
|
833
1175
|
if (isOpen) {
|
|
834
1176
|
if (mode === "edit" && product) {
|
|
835
1177
|
const featureSlug = product.extra_data?.feature_slug || "";
|
|
@@ -851,16 +1193,12 @@ function ProductFormDialog({
|
|
|
851
1193
|
setFormErrors({});
|
|
852
1194
|
}
|
|
853
1195
|
}, [isOpen, mode, product]);
|
|
854
|
-
|
|
1196
|
+
useEffect(() => {
|
|
855
1197
|
if (formData.feature_slug) {
|
|
856
1198
|
setFormData((prev) => {
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
return { ...prev, extra_data: JSON.stringify(updated, null, 2) };
|
|
861
|
-
} catch {
|
|
862
|
-
return { ...prev, extra_data: JSON.stringify({ feature_slug: formData.feature_slug }, null, 2) };
|
|
863
|
-
}
|
|
1199
|
+
const parsedData = safeJsonParse(prev.extra_data);
|
|
1200
|
+
const updated = { ...parsedData, feature_slug: formData.feature_slug };
|
|
1201
|
+
return { ...prev, extra_data: JSON.stringify(updated, null, 2) };
|
|
864
1202
|
});
|
|
865
1203
|
}
|
|
866
1204
|
}, [formData.feature_slug]);
|
|
@@ -880,72 +1218,72 @@ function ProductFormDialog({
|
|
|
880
1218
|
if (validateForm()) onSubmit(formData);
|
|
881
1219
|
};
|
|
882
1220
|
if (!isOpen) return null;
|
|
883
|
-
return /* @__PURE__ */
|
|
884
|
-
/* @__PURE__ */
|
|
885
|
-
/* @__PURE__ */
|
|
886
|
-
/* @__PURE__ */
|
|
887
|
-
/* @__PURE__ */
|
|
888
|
-
/* @__PURE__ */
|
|
1221
|
+
return /* @__PURE__ */ jsxs13("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
1222
|
+
/* @__PURE__ */ jsx16("div", { className: "fixed inset-0 bg-black/80", onClick: () => onOpenChange(false) }),
|
|
1223
|
+
/* @__PURE__ */ jsxs13("div", { className: "relative z-50 w-full max-w-xl max-h-[90vh] overflow-y-auto rounded-lg border bg-background p-6 shadow-lg", children: [
|
|
1224
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex flex-col space-y-1.5 pb-4", children: [
|
|
1225
|
+
/* @__PURE__ */ jsx16("h2", { className: "text-lg font-semibold", children: mode === "create" ? "Nuovo Prodotto" : "Modifica Prodotto" }),
|
|
1226
|
+
/* @__PURE__ */ jsx16("p", { className: "text-sm text-muted-foreground", children: mode === "create" ? "Crea un nuovo prodotto / feature acquistabile" : "Modifica le impostazioni del prodotto selezionato" })
|
|
889
1227
|
] }),
|
|
890
|
-
/* @__PURE__ */
|
|
891
|
-
/* @__PURE__ */
|
|
892
|
-
/* @__PURE__ */
|
|
893
|
-
/* @__PURE__ */
|
|
894
|
-
formErrors.name && /* @__PURE__ */
|
|
1228
|
+
/* @__PURE__ */ jsxs13("div", { className: "grid gap-4 py-4", children: [
|
|
1229
|
+
/* @__PURE__ */ jsxs13("div", { className: "space-y-2", children: [
|
|
1230
|
+
/* @__PURE__ */ jsx16("label", { className: "text-sm font-medium leading-none", children: "Nome *" }),
|
|
1231
|
+
/* @__PURE__ */ jsx16("input", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: formData.name, onChange: (e) => setFormData({ ...formData, name: e.target.value }), placeholder: "es. Ottimizza Dati Prodotti" }),
|
|
1232
|
+
formErrors.name && /* @__PURE__ */ jsx16("p", { className: "text-xs text-destructive", children: formErrors.name })
|
|
895
1233
|
] }),
|
|
896
|
-
/* @__PURE__ */
|
|
897
|
-
/* @__PURE__ */
|
|
898
|
-
/* @__PURE__ */
|
|
1234
|
+
/* @__PURE__ */ jsxs13("div", { className: "space-y-2", children: [
|
|
1235
|
+
/* @__PURE__ */ jsx16("label", { className: "text-sm font-medium leading-none", children: "Descrizione" }),
|
|
1236
|
+
/* @__PURE__ */ jsx16("textarea", { className: "flex min-h-[60px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: formData.description, onChange: (e) => setFormData({ ...formData, description: e.target.value }), rows: 2 })
|
|
899
1237
|
] }),
|
|
900
|
-
/* @__PURE__ */
|
|
901
|
-
/* @__PURE__ */
|
|
902
|
-
/* @__PURE__ */
|
|
903
|
-
/* @__PURE__ */
|
|
904
|
-
formErrors.price_cents && /* @__PURE__ */
|
|
1238
|
+
/* @__PURE__ */ jsxs13("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
1239
|
+
/* @__PURE__ */ jsxs13("div", { className: "space-y-2", children: [
|
|
1240
|
+
/* @__PURE__ */ jsx16("label", { className: "text-sm font-medium leading-none", children: "Prezzo" }),
|
|
1241
|
+
/* @__PURE__ */ jsx16("input", { type: "number", step: "0.01", className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: formData.price_cents, onChange: (e) => setFormData({ ...formData, price_cents: e.target.value }), placeholder: "49.00" }),
|
|
1242
|
+
formErrors.price_cents && /* @__PURE__ */ jsx16("p", { className: "text-xs text-destructive", children: formErrors.price_cents })
|
|
905
1243
|
] }),
|
|
906
|
-
/* @__PURE__ */
|
|
907
|
-
/* @__PURE__ */
|
|
908
|
-
/* @__PURE__ */
|
|
909
|
-
/* @__PURE__ */
|
|
910
|
-
/* @__PURE__ */
|
|
911
|
-
/* @__PURE__ */
|
|
1244
|
+
/* @__PURE__ */ jsxs13("div", { className: "space-y-2", children: [
|
|
1245
|
+
/* @__PURE__ */ jsx16("label", { className: "text-sm font-medium leading-none", children: "Valuta" }),
|
|
1246
|
+
/* @__PURE__ */ jsxs13("select", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: formData.currency, onChange: (e) => setFormData({ ...formData, currency: e.target.value }), children: [
|
|
1247
|
+
/* @__PURE__ */ jsx16("option", { value: "eur", children: "EUR" }),
|
|
1248
|
+
/* @__PURE__ */ jsx16("option", { value: "usd", children: "USD" }),
|
|
1249
|
+
/* @__PURE__ */ jsx16("option", { value: "gbp", children: "GBP" })
|
|
912
1250
|
] })
|
|
913
1251
|
] })
|
|
914
1252
|
] }),
|
|
915
|
-
/* @__PURE__ */
|
|
916
|
-
/* @__PURE__ */
|
|
917
|
-
/* @__PURE__ */
|
|
918
|
-
/* @__PURE__ */
|
|
919
|
-
/* @__PURE__ */
|
|
920
|
-
/* @__PURE__ */
|
|
921
|
-
/* @__PURE__ */
|
|
1253
|
+
/* @__PURE__ */ jsxs13("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
1254
|
+
/* @__PURE__ */ jsxs13("div", { className: "space-y-2", children: [
|
|
1255
|
+
/* @__PURE__ */ jsx16("label", { className: "text-sm font-medium leading-none", children: "Ricorrenza" }),
|
|
1256
|
+
/* @__PURE__ */ jsxs13("select", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: formData.recurring_interval || "one_time", onChange: (e) => setFormData({ ...formData, recurring_interval: e.target.value === "one_time" ? "" : e.target.value }), children: [
|
|
1257
|
+
/* @__PURE__ */ jsx16("option", { value: "one_time", children: "Una tantum" }),
|
|
1258
|
+
/* @__PURE__ */ jsx16("option", { value: "month", children: "Mensile" }),
|
|
1259
|
+
/* @__PURE__ */ jsx16("option", { value: "year", children: "Annuale" })
|
|
922
1260
|
] })
|
|
923
1261
|
] }),
|
|
924
|
-
/* @__PURE__ */
|
|
925
|
-
/* @__PURE__ */
|
|
926
|
-
/* @__PURE__ */
|
|
927
|
-
/* @__PURE__ */
|
|
928
|
-
/* @__PURE__ */
|
|
929
|
-
/* @__PURE__ */
|
|
930
|
-
/* @__PURE__ */
|
|
1262
|
+
/* @__PURE__ */ jsxs13("div", { className: "space-y-2", children: [
|
|
1263
|
+
/* @__PURE__ */ jsx16("label", { className: "text-sm font-medium leading-none", children: "Categoria" }),
|
|
1264
|
+
/* @__PURE__ */ jsxs13("select", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: formData.category, onChange: (e) => setFormData({ ...formData, category: e.target.value }), children: [
|
|
1265
|
+
/* @__PURE__ */ jsx16("option", { value: "feature", children: "Feature" }),
|
|
1266
|
+
/* @__PURE__ */ jsx16("option", { value: "addon", children: "Add-on" }),
|
|
1267
|
+
/* @__PURE__ */ jsx16("option", { value: "service", children: "Servizio" }),
|
|
1268
|
+
/* @__PURE__ */ jsx16("option", { value: "credits", children: "Crediti" })
|
|
931
1269
|
] })
|
|
932
1270
|
] })
|
|
933
1271
|
] }),
|
|
934
|
-
/* @__PURE__ */
|
|
935
|
-
/* @__PURE__ */
|
|
936
|
-
mode === "create" && /* @__PURE__ */
|
|
1272
|
+
/* @__PURE__ */ jsxs13("div", { className: "space-y-4", children: [
|
|
1273
|
+
/* @__PURE__ */ jsx16(ToggleRow, { label: "Prodotto Attivo", checked: formData.is_active, onChange: (v) => setFormData({ ...formData, is_active: v }) }),
|
|
1274
|
+
mode === "create" && /* @__PURE__ */ jsx16(ToggleRow, { label: "Sincronizza su Stripe", checked: formData.sync_stripe, onChange: (v) => setFormData({ ...formData, sync_stripe: v }) })
|
|
937
1275
|
] }),
|
|
938
|
-
features.length > 0 && /* @__PURE__ */
|
|
939
|
-
/* @__PURE__ */
|
|
940
|
-
/* @__PURE__ */
|
|
1276
|
+
features.length > 0 && /* @__PURE__ */ jsxs13("div", { className: "space-y-2", children: [
|
|
1277
|
+
/* @__PURE__ */ jsx16("label", { className: "text-sm font-medium leading-none", children: "Collega a Feature" }),
|
|
1278
|
+
/* @__PURE__ */ jsxs13(
|
|
941
1279
|
"select",
|
|
942
1280
|
{
|
|
943
1281
|
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
944
1282
|
value: formData.feature_slug || "none",
|
|
945
1283
|
onChange: (e) => setFormData({ ...formData, feature_slug: e.target.value === "none" ? "" : e.target.value }),
|
|
946
1284
|
children: [
|
|
947
|
-
/* @__PURE__ */
|
|
948
|
-
features.map((f) => /* @__PURE__ */
|
|
1285
|
+
/* @__PURE__ */ jsx16("option", { value: "none", children: "Nessuna feature" }),
|
|
1286
|
+
features.map((f) => /* @__PURE__ */ jsxs13("option", { value: f.slug, children: [
|
|
949
1287
|
f.name,
|
|
950
1288
|
" (",
|
|
951
1289
|
f.slug,
|
|
@@ -954,24 +1292,24 @@ function ProductFormDialog({
|
|
|
954
1292
|
]
|
|
955
1293
|
}
|
|
956
1294
|
),
|
|
957
|
-
formData.feature_slug && /* @__PURE__ */
|
|
958
|
-
/* @__PURE__ */
|
|
959
|
-
/* @__PURE__ */
|
|
1295
|
+
formData.feature_slug && /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2 p-2 bg-blue-50 border border-blue-200 rounded text-sm", children: [
|
|
1296
|
+
/* @__PURE__ */ jsx16(LinkIcon, { className: "w-4 h-4 text-blue-600" }),
|
|
1297
|
+
/* @__PURE__ */ jsxs13("span", { className: "text-blue-900", children: [
|
|
960
1298
|
"Collegato a: ",
|
|
961
|
-
/* @__PURE__ */
|
|
1299
|
+
/* @__PURE__ */ jsx16("strong", { children: formData.feature_slug })
|
|
962
1300
|
] })
|
|
963
1301
|
] })
|
|
964
1302
|
] }),
|
|
965
|
-
/* @__PURE__ */
|
|
966
|
-
/* @__PURE__ */
|
|
967
|
-
/* @__PURE__ */
|
|
968
|
-
formErrors.extra_data && /* @__PURE__ */
|
|
1303
|
+
/* @__PURE__ */ jsxs13("div", { className: "space-y-2", children: [
|
|
1304
|
+
/* @__PURE__ */ jsx16("label", { className: "text-sm font-medium leading-none", children: "Extra Data (JSON)" }),
|
|
1305
|
+
/* @__PURE__ */ jsx16("textarea", { className: "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm font-mono focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: formData.extra_data, onChange: (e) => setFormData({ ...formData, extra_data: e.target.value }), rows: 3 }),
|
|
1306
|
+
formErrors.extra_data && /* @__PURE__ */ jsx16("p", { className: "text-xs text-destructive", children: formErrors.extra_data })
|
|
969
1307
|
] })
|
|
970
1308
|
] }),
|
|
971
|
-
/* @__PURE__ */
|
|
972
|
-
/* @__PURE__ */
|
|
973
|
-
/* @__PURE__ */
|
|
974
|
-
isSubmitting && /* @__PURE__ */
|
|
1309
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 pt-4", children: [
|
|
1310
|
+
/* @__PURE__ */ jsx16("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 border border-input bg-background hover:bg-accent mt-2 sm:mt-0", onClick: () => onOpenChange(false), children: "Annulla" }),
|
|
1311
|
+
/* @__PURE__ */ jsxs13("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50", onClick: handleSubmit, disabled: isSubmitting, children: [
|
|
1312
|
+
isSubmitting && /* @__PURE__ */ jsx16(Loader23, { className: "w-4 h-4 animate-spin mr-2" }),
|
|
975
1313
|
mode === "create" ? "Crea Prodotto" : "Salva Modifiche"
|
|
976
1314
|
] })
|
|
977
1315
|
] })
|
|
@@ -979,8 +1317,8 @@ function ProductFormDialog({
|
|
|
979
1317
|
] });
|
|
980
1318
|
}
|
|
981
1319
|
function ToggleRow({ label, checked, onChange }) {
|
|
982
|
-
return /* @__PURE__ */
|
|
983
|
-
/* @__PURE__ */
|
|
1320
|
+
return /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2", children: [
|
|
1321
|
+
/* @__PURE__ */ jsx16(
|
|
984
1322
|
"button",
|
|
985
1323
|
{
|
|
986
1324
|
type: "button",
|
|
@@ -988,21 +1326,21 @@ function ToggleRow({ label, checked, onChange }) {
|
|
|
988
1326
|
"aria-checked": checked,
|
|
989
1327
|
onClick: () => onChange(!checked),
|
|
990
1328
|
className: `inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors ${checked ? "bg-primary" : "bg-input"}`,
|
|
991
|
-
children: /* @__PURE__ */
|
|
1329
|
+
children: /* @__PURE__ */ jsx16("span", { className: `pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg transition-transform ${checked ? "translate-x-4" : "translate-x-0"}` })
|
|
992
1330
|
}
|
|
993
1331
|
),
|
|
994
|
-
/* @__PURE__ */
|
|
1332
|
+
/* @__PURE__ */ jsx16("span", { className: "text-sm font-medium leading-none", children: label })
|
|
995
1333
|
] });
|
|
996
1334
|
}
|
|
997
1335
|
|
|
998
1336
|
// src/pages/ProductsPage.tsx
|
|
999
|
-
import { Fragment as
|
|
1337
|
+
import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1000
1338
|
function ProductsPage({ wrapper: Wrapper, header }) {
|
|
1001
1339
|
const config = useBillingAdminConfig();
|
|
1002
1340
|
const featuresConfig = config.products?.features;
|
|
1003
|
-
const createDialog =
|
|
1004
|
-
const editDialog =
|
|
1005
|
-
const deleteDialog =
|
|
1341
|
+
const createDialog = useDialogState2();
|
|
1342
|
+
const editDialog = useDialogState2();
|
|
1343
|
+
const deleteDialog = useDialogState2();
|
|
1006
1344
|
const { data, isLoading, refetch } = useAdminProducts({ is_active: true });
|
|
1007
1345
|
const createMutation = useCreateProduct();
|
|
1008
1346
|
const updateMutation = useUpdateProduct();
|
|
@@ -1068,70 +1406,70 @@ function ProductsPage({ wrapper: Wrapper, header }) {
|
|
|
1068
1406
|
});
|
|
1069
1407
|
}
|
|
1070
1408
|
};
|
|
1071
|
-
const content = /* @__PURE__ */
|
|
1409
|
+
const content = /* @__PURE__ */ jsxs14(Fragment5, { children: [
|
|
1072
1410
|
header,
|
|
1073
|
-
/* @__PURE__ */
|
|
1074
|
-
/* @__PURE__ */
|
|
1075
|
-
/* @__PURE__ */
|
|
1076
|
-
/* @__PURE__ */
|
|
1077
|
-
/* @__PURE__ */
|
|
1411
|
+
/* @__PURE__ */ jsxs14("div", { className: "space-y-4", children: [
|
|
1412
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex items-center justify-between", children: [
|
|
1413
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
1414
|
+
/* @__PURE__ */ jsx17(Package, { className: "w-4 h-4" }),
|
|
1415
|
+
/* @__PURE__ */ jsxs14("span", { children: [
|
|
1078
1416
|
products.length,
|
|
1079
1417
|
" prodotti configurati"
|
|
1080
1418
|
] })
|
|
1081
1419
|
] }),
|
|
1082
|
-
/* @__PURE__ */
|
|
1083
|
-
/* @__PURE__ */
|
|
1084
|
-
/* @__PURE__ */
|
|
1420
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex gap-2", children: [
|
|
1421
|
+
/* @__PURE__ */ jsxs14("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-3 border border-input bg-background hover:bg-accent", onClick: () => refetch(), children: [
|
|
1422
|
+
/* @__PURE__ */ jsx17(RefreshCw2, { className: "w-4 h-4 mr-2" }),
|
|
1085
1423
|
"Aggiorna"
|
|
1086
1424
|
] }),
|
|
1087
|
-
/* @__PURE__ */
|
|
1088
|
-
/* @__PURE__ */
|
|
1425
|
+
/* @__PURE__ */ jsxs14("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-primary text-primary-foreground hover:bg-primary/90", onClick: () => createDialog.open(), children: [
|
|
1426
|
+
/* @__PURE__ */ jsx17(Plus2, { className: "w-4 h-4 mr-2" }),
|
|
1089
1427
|
"Nuovo Prodotto"
|
|
1090
1428
|
] })
|
|
1091
1429
|
] })
|
|
1092
1430
|
] }),
|
|
1093
|
-
/* @__PURE__ */
|
|
1094
|
-
/* @__PURE__ */
|
|
1095
|
-
/* @__PURE__ */
|
|
1096
|
-
/* @__PURE__ */
|
|
1097
|
-
/* @__PURE__ */
|
|
1098
|
-
/* @__PURE__ */
|
|
1099
|
-
/* @__PURE__ */
|
|
1100
|
-
/* @__PURE__ */
|
|
1101
|
-
/* @__PURE__ */
|
|
1431
|
+
/* @__PURE__ */ jsx17("div", { className: "rounded-lg border bg-card shadow-sm", children: /* @__PURE__ */ jsx17("div", { className: "relative w-full overflow-auto", children: /* @__PURE__ */ jsxs14("table", { className: "w-full caption-bottom text-sm", children: [
|
|
1432
|
+
/* @__PURE__ */ jsx17("thead", { className: "[&_tr]:border-b", children: /* @__PURE__ */ jsxs14("tr", { className: "border-b hover:bg-muted/50", children: [
|
|
1433
|
+
/* @__PURE__ */ jsx17("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Nome" }),
|
|
1434
|
+
/* @__PURE__ */ jsx17("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Categoria" }),
|
|
1435
|
+
/* @__PURE__ */ jsx17("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Prezzo" }),
|
|
1436
|
+
/* @__PURE__ */ jsx17("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Ricorrenza" }),
|
|
1437
|
+
/* @__PURE__ */ jsx17("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Attivo" }),
|
|
1438
|
+
/* @__PURE__ */ jsx17("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Stripe" }),
|
|
1439
|
+
/* @__PURE__ */ jsx17("th", { className: "h-12 px-4 text-right font-medium text-muted-foreground", children: "Azioni" })
|
|
1102
1440
|
] }) }),
|
|
1103
|
-
/* @__PURE__ */
|
|
1441
|
+
/* @__PURE__ */ jsx17("tbody", { children: isLoading ? /* @__PURE__ */ jsx17(SkeletonRows, { rows: 3, columns: 7 }) : products.length === 0 ? /* @__PURE__ */ jsx17("tr", { children: /* @__PURE__ */ jsx17("td", { colSpan: 7, className: "p-4 text-center py-8 text-muted-foreground", children: "Nessun prodotto configurato" }) }) : products.map((product) => {
|
|
1104
1442
|
const hasFeature = !!product.extra_data?.feature_slug;
|
|
1105
|
-
return /* @__PURE__ */
|
|
1106
|
-
/* @__PURE__ */
|
|
1107
|
-
/* @__PURE__ */
|
|
1108
|
-
/* @__PURE__ */
|
|
1109
|
-
hasFeature && /* @__PURE__ */
|
|
1443
|
+
return /* @__PURE__ */ jsxs14("tr", { className: "border-b hover:bg-muted/50", children: [
|
|
1444
|
+
/* @__PURE__ */ jsx17("td", { className: "p-4", children: /* @__PURE__ */ jsxs14("div", { children: [
|
|
1445
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2", children: [
|
|
1446
|
+
/* @__PURE__ */ jsx17("span", { className: "font-medium", children: product.name }),
|
|
1447
|
+
hasFeature && /* @__PURE__ */ jsx17(StatusBadge, { label: "Feature", colorClass: "bg-blue-50 text-blue-700 border-blue-200" })
|
|
1110
1448
|
] }),
|
|
1111
|
-
product.description && /* @__PURE__ */
|
|
1449
|
+
product.description && /* @__PURE__ */ jsx17("div", { className: "text-sm text-muted-foreground truncate max-w-xs", children: product.description })
|
|
1112
1450
|
] }) }),
|
|
1113
|
-
/* @__PURE__ */
|
|
1114
|
-
/* @__PURE__ */
|
|
1115
|
-
/* @__PURE__ */
|
|
1116
|
-
/* @__PURE__ */
|
|
1117
|
-
/* @__PURE__ */
|
|
1118
|
-
/* @__PURE__ */
|
|
1119
|
-
/* @__PURE__ */
|
|
1120
|
-
/* @__PURE__ */
|
|
1451
|
+
/* @__PURE__ */ jsx17("td", { className: "p-4", children: /* @__PURE__ */ jsx17(StatusBadge, { label: product.category || "feature", colorClass: "border" }) }),
|
|
1452
|
+
/* @__PURE__ */ jsx17("td", { className: "p-4", children: formatCurrencyFromCents(product.price_cents, product.currency) }),
|
|
1453
|
+
/* @__PURE__ */ jsx17("td", { className: "p-4", children: product.recurring_interval ? /* @__PURE__ */ jsx17(StatusBadge, { label: product.recurring_interval === "month" ? "Mensile" : product.recurring_interval === "year" ? "Annuale" : product.recurring_interval, colorClass: "bg-secondary text-secondary-foreground border-transparent" }) : /* @__PURE__ */ jsx17("span", { className: "text-muted-foreground", children: "Una tantum" }) }),
|
|
1454
|
+
/* @__PURE__ */ jsx17("td", { className: "p-4", children: product.is_active ? /* @__PURE__ */ jsx17(Check3, { className: "w-4 h-4 text-green-600" }) : /* @__PURE__ */ jsx17(X2, { className: "w-4 h-4 text-muted-foreground" }) }),
|
|
1455
|
+
/* @__PURE__ */ jsx17("td", { className: "p-4", children: product.stripe_product_id ? /* @__PURE__ */ jsx17(StatusBadge, { label: "Synced", colorClass: "bg-green-50 text-green-700 border-green-200" }) : /* @__PURE__ */ jsx17("button", { className: "inline-flex items-center justify-center rounded-md h-9 w-9 hover:bg-accent", onClick: () => syncMutation.mutate(product.id, { onSuccess: () => toast2.success("Sincronizzato"), onError: (err) => toast2.error(`Errore: ${err.message}`) }), disabled: syncMutation.isPending, children: /* @__PURE__ */ jsx17(Upload2, { className: "w-4 h-4" }) }) }),
|
|
1456
|
+
/* @__PURE__ */ jsx17("td", { className: "p-4 text-right", children: /* @__PURE__ */ jsxs14("div", { className: "flex justify-end gap-1", children: [
|
|
1457
|
+
/* @__PURE__ */ jsx17("button", { className: "inline-flex items-center justify-center rounded-md h-9 w-9 hover:bg-accent", onClick: () => editDialog.open(product), children: /* @__PURE__ */ jsx17(Pencil2, { className: "w-4 h-4" }) }),
|
|
1458
|
+
/* @__PURE__ */ jsx17("button", { className: "inline-flex items-center justify-center rounded-md h-9 w-9 text-destructive hover:bg-accent", onClick: () => deleteDialog.open(product), children: /* @__PURE__ */ jsx17(Trash22, { className: "w-4 h-4" }) })
|
|
1121
1459
|
] }) })
|
|
1122
1460
|
] }, product.id);
|
|
1123
1461
|
}) })
|
|
1124
1462
|
] }) }) })
|
|
1125
1463
|
] }),
|
|
1126
|
-
/* @__PURE__ */
|
|
1127
|
-
/* @__PURE__ */
|
|
1128
|
-
/* @__PURE__ */
|
|
1464
|
+
/* @__PURE__ */ jsx17(ProductFormDialog, { isOpen: createDialog.isOpen, onOpenChange: (open) => open ? createDialog.open() : createDialog.close(), mode: "create", features, onSubmit: handleCreateSubmit, isSubmitting: createMutation.isPending }),
|
|
1465
|
+
/* @__PURE__ */ jsx17(ProductFormDialog, { isOpen: editDialog.isOpen, onOpenChange: (open) => open ? editDialog.open(editDialog.data) : editDialog.close(), mode: "edit", product: editDialog.data, features, onSubmit: handleEditSubmit, isSubmitting: updateMutation.isPending }),
|
|
1466
|
+
/* @__PURE__ */ jsx17(DeleteConfirmDialog, { isOpen: deleteDialog.isOpen, onOpenChange: (open) => !open && deleteDialog.close(), title: "Disattivare Prodotto?", description: /* @__PURE__ */ jsxs14(Fragment5, { children: [
|
|
1129
1467
|
"Stai per disattivare il prodotto ",
|
|
1130
|
-
/* @__PURE__ */
|
|
1468
|
+
/* @__PURE__ */ jsx17("strong", { children: deleteDialog.data?.name }),
|
|
1131
1469
|
". Il prodotto non sar\xE0 pi\xF9 acquistabile."
|
|
1132
1470
|
] }), onConfirm: handleDeleteConfirm, isDeleting: deleteMutation.isPending, confirmLabel: "Disattiva Prodotto" })
|
|
1133
1471
|
] });
|
|
1134
|
-
return Wrapper ? /* @__PURE__ */
|
|
1472
|
+
return Wrapper ? /* @__PURE__ */ jsx17(Wrapper, { children: content }) : content;
|
|
1135
1473
|
}
|
|
1136
1474
|
|
|
1137
1475
|
// src/pages/SubscriptionsPage.tsx
|
|
@@ -1148,7 +1486,7 @@ import {
|
|
|
1148
1486
|
|
|
1149
1487
|
// src/components/shared/FilterBar.tsx
|
|
1150
1488
|
import { Search, RefreshCw as RefreshCw3 } from "lucide-react";
|
|
1151
|
-
import { jsx as
|
|
1489
|
+
import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1152
1490
|
function FilterBar({
|
|
1153
1491
|
searchPlaceholder = "Cerca...",
|
|
1154
1492
|
searchValue,
|
|
@@ -1158,10 +1496,10 @@ function FilterBar({
|
|
|
1158
1496
|
statusOptions,
|
|
1159
1497
|
onRefresh
|
|
1160
1498
|
}) {
|
|
1161
|
-
return /* @__PURE__ */
|
|
1162
|
-
/* @__PURE__ */
|
|
1163
|
-
/* @__PURE__ */
|
|
1164
|
-
/* @__PURE__ */
|
|
1499
|
+
return /* @__PURE__ */ jsx18("div", { className: "rounded-lg border bg-card text-card-foreground shadow-sm", children: /* @__PURE__ */ jsx18("div", { className: "p-6 pt-6", children: /* @__PURE__ */ jsxs15("div", { className: "flex flex-col sm:flex-row gap-4", children: [
|
|
1500
|
+
/* @__PURE__ */ jsxs15("div", { className: "relative flex-1", children: [
|
|
1501
|
+
/* @__PURE__ */ jsx18(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" }),
|
|
1502
|
+
/* @__PURE__ */ jsx18(
|
|
1165
1503
|
"input",
|
|
1166
1504
|
{
|
|
1167
1505
|
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 pl-9",
|
|
@@ -1171,22 +1509,22 @@ function FilterBar({
|
|
|
1171
1509
|
}
|
|
1172
1510
|
)
|
|
1173
1511
|
] }),
|
|
1174
|
-
statusOptions && onStatusFilterChange && /* @__PURE__ */
|
|
1512
|
+
statusOptions && onStatusFilterChange && /* @__PURE__ */ jsx18(
|
|
1175
1513
|
"select",
|
|
1176
1514
|
{
|
|
1177
1515
|
className: "flex h-10 w-[180px] rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
1178
1516
|
value: statusFilter || "all",
|
|
1179
1517
|
onChange: (e) => onStatusFilterChange(e.target.value),
|
|
1180
|
-
children: statusOptions.map((opt) => /* @__PURE__ */
|
|
1518
|
+
children: statusOptions.map((opt) => /* @__PURE__ */ jsx18("option", { value: opt.value, children: opt.label }, opt.value))
|
|
1181
1519
|
}
|
|
1182
1520
|
),
|
|
1183
|
-
/* @__PURE__ */
|
|
1521
|
+
/* @__PURE__ */ jsxs15(
|
|
1184
1522
|
"button",
|
|
1185
1523
|
{
|
|
1186
1524
|
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
1187
1525
|
onClick: onRefresh,
|
|
1188
1526
|
children: [
|
|
1189
|
-
/* @__PURE__ */
|
|
1527
|
+
/* @__PURE__ */ jsx18(RefreshCw3, { className: "w-4 h-4 mr-2" }),
|
|
1190
1528
|
"Aggiorna"
|
|
1191
1529
|
]
|
|
1192
1530
|
}
|
|
@@ -1195,7 +1533,7 @@ function FilterBar({
|
|
|
1195
1533
|
}
|
|
1196
1534
|
|
|
1197
1535
|
// src/pages/SubscriptionsPage.tsx
|
|
1198
|
-
import { Fragment as
|
|
1536
|
+
import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1199
1537
|
var statusFilterOptions = [
|
|
1200
1538
|
{ value: "all", label: "Tutti gli stati" },
|
|
1201
1539
|
{ value: "active", label: "Attivi" },
|
|
@@ -1231,10 +1569,10 @@ function SubscriptionsPage({ wrapper: Wrapper, header }) {
|
|
|
1231
1569
|
});
|
|
1232
1570
|
}
|
|
1233
1571
|
};
|
|
1234
|
-
const content = /* @__PURE__ */
|
|
1572
|
+
const content = /* @__PURE__ */ jsxs16(Fragment6, { children: [
|
|
1235
1573
|
header,
|
|
1236
|
-
/* @__PURE__ */
|
|
1237
|
-
/* @__PURE__ */
|
|
1574
|
+
/* @__PURE__ */ jsxs16("div", { className: "space-y-4", children: [
|
|
1575
|
+
/* @__PURE__ */ jsx19(
|
|
1238
1576
|
FilterBar,
|
|
1239
1577
|
{
|
|
1240
1578
|
searchPlaceholder: "Cerca per email o ID esterno...",
|
|
@@ -1246,156 +1584,156 @@ function SubscriptionsPage({ wrapper: Wrapper, header }) {
|
|
|
1246
1584
|
onRefresh: refetch
|
|
1247
1585
|
}
|
|
1248
1586
|
),
|
|
1249
|
-
/* @__PURE__ */
|
|
1250
|
-
/* @__PURE__ */
|
|
1251
|
-
/* @__PURE__ */
|
|
1587
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
1588
|
+
/* @__PURE__ */ jsx19(CreditCard, { className: "w-4 h-4" }),
|
|
1589
|
+
/* @__PURE__ */ jsxs16("span", { children: [
|
|
1252
1590
|
total,
|
|
1253
1591
|
" abbonament",
|
|
1254
1592
|
total === 1 ? "o" : "i",
|
|
1255
1593
|
" trovati"
|
|
1256
1594
|
] })
|
|
1257
1595
|
] }),
|
|
1258
|
-
/* @__PURE__ */
|
|
1259
|
-
/* @__PURE__ */
|
|
1260
|
-
/* @__PURE__ */
|
|
1261
|
-
/* @__PURE__ */
|
|
1262
|
-
/* @__PURE__ */
|
|
1263
|
-
/* @__PURE__ */
|
|
1264
|
-
/* @__PURE__ */
|
|
1265
|
-
/* @__PURE__ */
|
|
1266
|
-
/* @__PURE__ */
|
|
1267
|
-
/* @__PURE__ */
|
|
1596
|
+
/* @__PURE__ */ jsx19("div", { className: "rounded-lg border bg-card shadow-sm", children: /* @__PURE__ */ jsx19("div", { className: "relative w-full overflow-auto", children: /* @__PURE__ */ jsxs16("table", { className: "w-full caption-bottom text-sm", children: [
|
|
1597
|
+
/* @__PURE__ */ jsx19("thead", { className: "[&_tr]:border-b", children: /* @__PURE__ */ jsxs16("tr", { className: "border-b hover:bg-muted/50", children: [
|
|
1598
|
+
/* @__PURE__ */ jsx19("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Subscriber" }),
|
|
1599
|
+
/* @__PURE__ */ jsx19("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Piano" }),
|
|
1600
|
+
/* @__PURE__ */ jsx19("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Stato" }),
|
|
1601
|
+
/* @__PURE__ */ jsx19("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Ciclo" }),
|
|
1602
|
+
/* @__PURE__ */ jsx19("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Periodo" }),
|
|
1603
|
+
/* @__PURE__ */ jsx19("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Trial End" }),
|
|
1604
|
+
/* @__PURE__ */ jsx19("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Creato" }),
|
|
1605
|
+
/* @__PURE__ */ jsx19("th", { className: "h-12 px-4 text-right font-medium text-muted-foreground", children: "Azioni" })
|
|
1268
1606
|
] }) }),
|
|
1269
|
-
/* @__PURE__ */
|
|
1270
|
-
/* @__PURE__ */
|
|
1271
|
-
/* @__PURE__ */
|
|
1272
|
-
/* @__PURE__ */
|
|
1607
|
+
/* @__PURE__ */ jsx19("tbody", { children: isLoading ? /* @__PURE__ */ jsx19(SkeletonRows, { rows: 5, columns: 8 }) : subscriptions.length === 0 ? /* @__PURE__ */ jsx19("tr", { children: /* @__PURE__ */ jsx19("td", { colSpan: 8, className: "p-4 text-center py-8 text-muted-foreground", children: "Nessun abbonamento trovato" }) }) : subscriptions.map((sub) => /* @__PURE__ */ jsxs16("tr", { className: "border-b hover:bg-muted/50", children: [
|
|
1608
|
+
/* @__PURE__ */ jsx19("td", { className: "p-4", children: /* @__PURE__ */ jsxs16("div", { className: "flex flex-col", children: [
|
|
1609
|
+
/* @__PURE__ */ jsx19("span", { className: "font-medium", children: sub.subscriber_email }),
|
|
1610
|
+
/* @__PURE__ */ jsx19("span", { className: "text-xs text-muted-foreground", children: sub.subscriber_external_id })
|
|
1273
1611
|
] }) }),
|
|
1274
|
-
/* @__PURE__ */
|
|
1275
|
-
/* @__PURE__ */
|
|
1276
|
-
/* @__PURE__ */
|
|
1612
|
+
/* @__PURE__ */ jsxs16("td", { className: "p-4", children: [
|
|
1613
|
+
/* @__PURE__ */ jsx19("span", { className: "font-medium", children: sub.plan_name }),
|
|
1614
|
+
/* @__PURE__ */ jsxs16("span", { className: "text-xs text-muted-foreground ml-1", children: [
|
|
1277
1615
|
"(",
|
|
1278
1616
|
sub.plan_slug,
|
|
1279
1617
|
")"
|
|
1280
1618
|
] })
|
|
1281
1619
|
] }),
|
|
1282
|
-
/* @__PURE__ */
|
|
1283
|
-
/* @__PURE__ */
|
|
1284
|
-
/* @__PURE__ */
|
|
1620
|
+
/* @__PURE__ */ jsx19("td", { className: "p-4", children: /* @__PURE__ */ jsx19(StatusBadge, { label: subscriptionStatusLabels[sub.status], colorClass: subscriptionStatusColors[sub.status] }) }),
|
|
1621
|
+
/* @__PURE__ */ jsx19("td", { className: "p-4 capitalize", children: sub.billing_cycle }),
|
|
1622
|
+
/* @__PURE__ */ jsxs16("td", { className: "p-4 text-sm", children: [
|
|
1285
1623
|
formatDate(sub.current_period_start),
|
|
1286
1624
|
" - ",
|
|
1287
1625
|
formatDate(sub.current_period_end)
|
|
1288
1626
|
] }),
|
|
1289
|
-
/* @__PURE__ */
|
|
1290
|
-
/* @__PURE__ */
|
|
1291
|
-
/* @__PURE__ */
|
|
1292
|
-
/* @__PURE__ */
|
|
1627
|
+
/* @__PURE__ */ jsx19("td", { className: "p-4", children: formatDate(sub.trial_end) }),
|
|
1628
|
+
/* @__PURE__ */ jsx19("td", { className: "p-4", children: formatDate(sub.created_at) }),
|
|
1629
|
+
/* @__PURE__ */ jsx19("td", { className: "p-4 text-right", children: /* @__PURE__ */ jsxs16("div", { className: "flex justify-end gap-1", children: [
|
|
1630
|
+
/* @__PURE__ */ jsx19("button", { className: "inline-flex items-center justify-center rounded-md h-9 w-9 hover:bg-accent", onClick: () => {
|
|
1293
1631
|
setSelectedSub(sub);
|
|
1294
1632
|
setShowDetails(true);
|
|
1295
|
-
}, children: /* @__PURE__ */
|
|
1296
|
-
sub.status !== "canceled" && /* @__PURE__ */
|
|
1633
|
+
}, children: /* @__PURE__ */ jsx19(Eye, { className: "w-4 h-4" }) }),
|
|
1634
|
+
sub.status !== "canceled" && /* @__PURE__ */ jsx19("button", { className: "inline-flex items-center justify-center rounded-md h-9 w-9 text-destructive hover:bg-accent", onClick: () => {
|
|
1297
1635
|
setSelectedSub(sub);
|
|
1298
1636
|
setShowCancelDialog(true);
|
|
1299
|
-
}, children: /* @__PURE__ */
|
|
1637
|
+
}, children: /* @__PURE__ */ jsx19(XCircle, { className: "w-4 h-4" }) })
|
|
1300
1638
|
] }) })
|
|
1301
1639
|
] }, sub.id)) })
|
|
1302
1640
|
] }) }) })
|
|
1303
1641
|
] }),
|
|
1304
|
-
showDetails && selectedSub && /* @__PURE__ */
|
|
1305
|
-
/* @__PURE__ */
|
|
1306
|
-
/* @__PURE__ */
|
|
1307
|
-
/* @__PURE__ */
|
|
1308
|
-
/* @__PURE__ */
|
|
1309
|
-
/* @__PURE__ */
|
|
1310
|
-
/* @__PURE__ */
|
|
1311
|
-
/* @__PURE__ */
|
|
1312
|
-
/* @__PURE__ */
|
|
1313
|
-
/* @__PURE__ */
|
|
1314
|
-
/* @__PURE__ */
|
|
1315
|
-
/* @__PURE__ */
|
|
1642
|
+
showDetails && selectedSub && /* @__PURE__ */ jsxs16("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
1643
|
+
/* @__PURE__ */ jsx19("div", { className: "fixed inset-0 bg-black/80", onClick: () => setShowDetails(false) }),
|
|
1644
|
+
/* @__PURE__ */ jsxs16("div", { className: "relative z-50 w-full max-w-lg rounded-lg border bg-background p-6 shadow-lg", children: [
|
|
1645
|
+
/* @__PURE__ */ jsx19("h2", { className: "text-lg font-semibold mb-1", children: "Dettagli Abbonamento" }),
|
|
1646
|
+
/* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground mb-4", children: "Informazioni complete" }),
|
|
1647
|
+
/* @__PURE__ */ jsxs16("div", { className: "space-y-4", children: [
|
|
1648
|
+
/* @__PURE__ */ jsxs16("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
1649
|
+
/* @__PURE__ */ jsxs16("div", { children: [
|
|
1650
|
+
/* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: "Subscriber" }),
|
|
1651
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2 mt-1", children: [
|
|
1652
|
+
/* @__PURE__ */ jsx19(User, { className: "w-4 h-4 text-muted-foreground" }),
|
|
1653
|
+
/* @__PURE__ */ jsx19("span", { className: "font-medium", children: selectedSub.subscriber_email })
|
|
1316
1654
|
] }),
|
|
1317
|
-
/* @__PURE__ */
|
|
1655
|
+
/* @__PURE__ */ jsxs16("p", { className: "text-xs text-muted-foreground mt-1", children: [
|
|
1318
1656
|
"ID: ",
|
|
1319
1657
|
selectedSub.subscriber_external_id
|
|
1320
1658
|
] })
|
|
1321
1659
|
] }),
|
|
1322
|
-
/* @__PURE__ */
|
|
1323
|
-
/* @__PURE__ */
|
|
1324
|
-
/* @__PURE__ */
|
|
1325
|
-
/* @__PURE__ */
|
|
1326
|
-
/* @__PURE__ */
|
|
1660
|
+
/* @__PURE__ */ jsxs16("div", { children: [
|
|
1661
|
+
/* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: "Piano" }),
|
|
1662
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2 mt-1", children: [
|
|
1663
|
+
/* @__PURE__ */ jsx19(CreditCard, { className: "w-4 h-4 text-muted-foreground" }),
|
|
1664
|
+
/* @__PURE__ */ jsx19("span", { className: "font-medium", children: selectedSub.plan_name })
|
|
1327
1665
|
] })
|
|
1328
1666
|
] })
|
|
1329
1667
|
] }),
|
|
1330
|
-
/* @__PURE__ */
|
|
1331
|
-
/* @__PURE__ */
|
|
1332
|
-
/* @__PURE__ */
|
|
1333
|
-
/* @__PURE__ */
|
|
1668
|
+
/* @__PURE__ */ jsxs16("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
1669
|
+
/* @__PURE__ */ jsxs16("div", { children: [
|
|
1670
|
+
/* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: "Stato" }),
|
|
1671
|
+
/* @__PURE__ */ jsx19(StatusBadge, { label: subscriptionStatusLabels[selectedSub.status], colorClass: subscriptionStatusColors[selectedSub.status], className: "mt-1" })
|
|
1334
1672
|
] }),
|
|
1335
|
-
/* @__PURE__ */
|
|
1336
|
-
/* @__PURE__ */
|
|
1337
|
-
/* @__PURE__ */
|
|
1673
|
+
/* @__PURE__ */ jsxs16("div", { children: [
|
|
1674
|
+
/* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: "Ciclo" }),
|
|
1675
|
+
/* @__PURE__ */ jsx19("p", { className: "font-medium capitalize mt-1", children: selectedSub.billing_cycle })
|
|
1338
1676
|
] })
|
|
1339
1677
|
] }),
|
|
1340
|
-
/* @__PURE__ */
|
|
1341
|
-
/* @__PURE__ */
|
|
1342
|
-
/* @__PURE__ */
|
|
1343
|
-
/* @__PURE__ */
|
|
1344
|
-
/* @__PURE__ */
|
|
1345
|
-
/* @__PURE__ */
|
|
1678
|
+
/* @__PURE__ */ jsxs16("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
1679
|
+
/* @__PURE__ */ jsxs16("div", { children: [
|
|
1680
|
+
/* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: "Periodo" }),
|
|
1681
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2 mt-1", children: [
|
|
1682
|
+
/* @__PURE__ */ jsx19(Calendar, { className: "w-4 h-4 text-muted-foreground" }),
|
|
1683
|
+
/* @__PURE__ */ jsxs16("span", { children: [
|
|
1346
1684
|
formatDate(selectedSub.current_period_start),
|
|
1347
1685
|
" - ",
|
|
1348
1686
|
formatDate(selectedSub.current_period_end)
|
|
1349
1687
|
] })
|
|
1350
1688
|
] })
|
|
1351
1689
|
] }),
|
|
1352
|
-
/* @__PURE__ */
|
|
1353
|
-
/* @__PURE__ */
|
|
1354
|
-
/* @__PURE__ */
|
|
1690
|
+
/* @__PURE__ */ jsxs16("div", { children: [
|
|
1691
|
+
/* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: "Fine Trial" }),
|
|
1692
|
+
/* @__PURE__ */ jsx19("p", { className: "mt-1", children: formatDate(selectedSub.trial_end) })
|
|
1355
1693
|
] })
|
|
1356
1694
|
] }),
|
|
1357
|
-
selectedSub.canceled_at && /* @__PURE__ */
|
|
1358
|
-
/* @__PURE__ */
|
|
1695
|
+
selectedSub.canceled_at && /* @__PURE__ */ jsxs16("div", { className: "p-3 bg-destructive/10 rounded-lg", children: [
|
|
1696
|
+
/* @__PURE__ */ jsxs16("p", { className: "text-sm text-destructive font-medium", children: [
|
|
1359
1697
|
"Cancellato il ",
|
|
1360
1698
|
formatDate(selectedSub.canceled_at)
|
|
1361
1699
|
] }),
|
|
1362
|
-
selectedSub.cancel_at && /* @__PURE__ */
|
|
1700
|
+
selectedSub.cancel_at && /* @__PURE__ */ jsxs16("p", { className: "text-xs text-destructive/80 mt-1", children: [
|
|
1363
1701
|
"Termina il ",
|
|
1364
1702
|
formatDate(selectedSub.cancel_at)
|
|
1365
1703
|
] })
|
|
1366
1704
|
] }),
|
|
1367
|
-
selectedSub.stripe_subscription_id && /* @__PURE__ */
|
|
1368
|
-
/* @__PURE__ */
|
|
1369
|
-
/* @__PURE__ */
|
|
1705
|
+
selectedSub.stripe_subscription_id && /* @__PURE__ */ jsxs16("div", { children: [
|
|
1706
|
+
/* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: "Stripe ID" }),
|
|
1707
|
+
/* @__PURE__ */ jsx19("code", { className: "text-xs bg-muted px-2 py-1 rounded mt-1 inline-block", children: selectedSub.stripe_subscription_id })
|
|
1370
1708
|
] })
|
|
1371
1709
|
] }),
|
|
1372
|
-
/* @__PURE__ */
|
|
1710
|
+
/* @__PURE__ */ jsx19("div", { className: "flex justify-end pt-4", children: /* @__PURE__ */ jsx19("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 border border-input bg-background hover:bg-accent", onClick: () => setShowDetails(false), children: "Chiudi" }) })
|
|
1373
1711
|
] })
|
|
1374
1712
|
] }),
|
|
1375
|
-
showCancelDialog && selectedSub && /* @__PURE__ */
|
|
1376
|
-
/* @__PURE__ */
|
|
1377
|
-
/* @__PURE__ */
|
|
1378
|
-
/* @__PURE__ */
|
|
1379
|
-
/* @__PURE__ */
|
|
1713
|
+
showCancelDialog && selectedSub && /* @__PURE__ */ jsxs16("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
1714
|
+
/* @__PURE__ */ jsx19("div", { className: "fixed inset-0 bg-black/80", onClick: () => setShowCancelDialog(false) }),
|
|
1715
|
+
/* @__PURE__ */ jsxs16("div", { className: "relative z-50 w-full max-w-lg rounded-lg border bg-background p-6 shadow-lg", children: [
|
|
1716
|
+
/* @__PURE__ */ jsx19("h2", { className: "text-lg font-semibold", children: "Cancellare Abbonamento?" }),
|
|
1717
|
+
/* @__PURE__ */ jsxs16("p", { className: "text-sm text-muted-foreground mt-1", children: [
|
|
1380
1718
|
"Stai per cancellare l'abbonamento di ",
|
|
1381
|
-
/* @__PURE__ */
|
|
1719
|
+
/* @__PURE__ */ jsx19("strong", { children: selectedSub.subscriber_email }),
|
|
1382
1720
|
"."
|
|
1383
1721
|
] }),
|
|
1384
|
-
/* @__PURE__ */
|
|
1385
|
-
/* @__PURE__ */
|
|
1386
|
-
/* @__PURE__ */
|
|
1387
|
-
/* @__PURE__ */
|
|
1722
|
+
/* @__PURE__ */ jsxs16("div", { className: "py-4", children: [
|
|
1723
|
+
/* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-2 cursor-pointer", children: [
|
|
1724
|
+
/* @__PURE__ */ jsx19("input", { type: "checkbox", checked: cancelAtPeriodEnd, onChange: (e) => setCancelAtPeriodEnd(e.target.checked), className: "rounded border-input" }),
|
|
1725
|
+
/* @__PURE__ */ jsx19("span", { className: "text-sm", children: "Cancella alla fine del periodo corrente (consigliato)" })
|
|
1388
1726
|
] }),
|
|
1389
|
-
/* @__PURE__ */
|
|
1727
|
+
/* @__PURE__ */ jsx19("p", { className: "text-xs text-muted-foreground mt-2", children: cancelAtPeriodEnd ? "L'abbonamento rimarr\xE0 attivo fino alla fine del periodo." : "L'abbonamento verr\xE0 cancellato immediatamente." })
|
|
1390
1728
|
] }),
|
|
1391
|
-
/* @__PURE__ */
|
|
1392
|
-
/* @__PURE__ */
|
|
1393
|
-
/* @__PURE__ */
|
|
1729
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex justify-end gap-2", children: [
|
|
1730
|
+
/* @__PURE__ */ jsx19("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 border border-input bg-background hover:bg-accent", onClick: () => setShowCancelDialog(false), children: "Annulla" }),
|
|
1731
|
+
/* @__PURE__ */ jsx19("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-destructive text-destructive-foreground hover:bg-destructive/90 disabled:opacity-50", onClick: handleCancel, disabled: cancelMutation.isPending, children: cancelMutation.isPending ? "Cancellazione..." : "Conferma Cancellazione" })
|
|
1394
1732
|
] })
|
|
1395
1733
|
] })
|
|
1396
1734
|
] })
|
|
1397
1735
|
] });
|
|
1398
|
-
return Wrapper ? /* @__PURE__ */
|
|
1736
|
+
return Wrapper ? /* @__PURE__ */ jsx19(Wrapper, { children: content }) : content;
|
|
1399
1737
|
}
|
|
1400
1738
|
|
|
1401
1739
|
// src/pages/PaymentsPage.tsx
|
|
@@ -1405,6 +1743,7 @@ import { RotateCcw, Receipt } from "lucide-react";
|
|
|
1405
1743
|
import {
|
|
1406
1744
|
useAdminPayments,
|
|
1407
1745
|
useRefundPayment,
|
|
1746
|
+
useCursorPagination,
|
|
1408
1747
|
formatCurrencyFromCents as formatCurrencyFromCents2,
|
|
1409
1748
|
formatTimestamp,
|
|
1410
1749
|
truncateId,
|
|
@@ -1414,7 +1753,7 @@ import {
|
|
|
1414
1753
|
|
|
1415
1754
|
// src/components/shared/CursorPagination.tsx
|
|
1416
1755
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
1417
|
-
import { jsx as
|
|
1756
|
+
import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1418
1757
|
function CursorPagination({
|
|
1419
1758
|
cursor,
|
|
1420
1759
|
hasMore,
|
|
@@ -1422,20 +1761,20 @@ function CursorPagination({
|
|
|
1422
1761
|
onFirstPage,
|
|
1423
1762
|
onNextPage
|
|
1424
1763
|
}) {
|
|
1425
|
-
return /* @__PURE__ */
|
|
1426
|
-
/* @__PURE__ */
|
|
1764
|
+
return /* @__PURE__ */ jsxs17("div", { className: "flex justify-between items-center", children: [
|
|
1765
|
+
/* @__PURE__ */ jsxs17(
|
|
1427
1766
|
"button",
|
|
1428
1767
|
{
|
|
1429
1768
|
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-3 border border-input bg-background hover:bg-accent hover:text-accent-foreground disabled:opacity-50 disabled:pointer-events-none",
|
|
1430
1769
|
onClick: onFirstPage,
|
|
1431
1770
|
disabled: !cursor,
|
|
1432
1771
|
children: [
|
|
1433
|
-
/* @__PURE__ */
|
|
1772
|
+
/* @__PURE__ */ jsx20(ChevronLeft, { className: "w-4 h-4 mr-1" }),
|
|
1434
1773
|
"Prima Pagina"
|
|
1435
1774
|
]
|
|
1436
1775
|
}
|
|
1437
1776
|
),
|
|
1438
|
-
/* @__PURE__ */
|
|
1777
|
+
/* @__PURE__ */ jsxs17(
|
|
1439
1778
|
"button",
|
|
1440
1779
|
{
|
|
1441
1780
|
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-3 border border-input bg-background hover:bg-accent hover:text-accent-foreground disabled:opacity-50 disabled:pointer-events-none",
|
|
@@ -1447,7 +1786,7 @@ function CursorPagination({
|
|
|
1447
1786
|
disabled: !hasMore,
|
|
1448
1787
|
children: [
|
|
1449
1788
|
"Prossima",
|
|
1450
|
-
/* @__PURE__ */
|
|
1789
|
+
/* @__PURE__ */ jsx20(ChevronRight, { className: "w-4 h-4 ml-1" })
|
|
1451
1790
|
]
|
|
1452
1791
|
}
|
|
1453
1792
|
)
|
|
@@ -1455,7 +1794,7 @@ function CursorPagination({
|
|
|
1455
1794
|
}
|
|
1456
1795
|
|
|
1457
1796
|
// src/pages/PaymentsPage.tsx
|
|
1458
|
-
import { Fragment as
|
|
1797
|
+
import { Fragment as Fragment7, jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1459
1798
|
var statusFilterOptions2 = [
|
|
1460
1799
|
{ value: "all", label: "Tutti gli stati" },
|
|
1461
1800
|
{ value: "succeeded", label: "Completati" },
|
|
@@ -1466,7 +1805,7 @@ var statusFilterOptions2 = [
|
|
|
1466
1805
|
function PaymentsPage({ wrapper: Wrapper, header }) {
|
|
1467
1806
|
const [search, setSearch] = useState5("");
|
|
1468
1807
|
const [statusFilter, setStatusFilter] = useState5("all");
|
|
1469
|
-
const
|
|
1808
|
+
const { cursor, goToNext, goToFirst } = useCursorPagination();
|
|
1470
1809
|
const [selectedPayment, setSelectedPayment] = useState5(null);
|
|
1471
1810
|
const [showRefundDialog, setShowRefundDialog] = useState5(false);
|
|
1472
1811
|
const [refundAmount, setRefundAmount] = useState5("");
|
|
@@ -1498,96 +1837,96 @@ function PaymentsPage({ wrapper: Wrapper, header }) {
|
|
|
1498
1837
|
setRefundAmount((payment.amount / 100).toFixed(2));
|
|
1499
1838
|
setShowRefundDialog(true);
|
|
1500
1839
|
};
|
|
1501
|
-
const content = /* @__PURE__ */
|
|
1840
|
+
const content = /* @__PURE__ */ jsxs18(Fragment7, { children: [
|
|
1502
1841
|
header,
|
|
1503
|
-
/* @__PURE__ */
|
|
1504
|
-
/* @__PURE__ */
|
|
1842
|
+
/* @__PURE__ */ jsxs18("div", { className: "space-y-4", children: [
|
|
1843
|
+
/* @__PURE__ */ jsx21(
|
|
1505
1844
|
FilterBar,
|
|
1506
1845
|
{
|
|
1507
1846
|
searchPlaceholder: "Cerca per email cliente...",
|
|
1508
1847
|
searchValue: search,
|
|
1509
1848
|
onSearchChange: (v) => {
|
|
1510
1849
|
setSearch(v);
|
|
1511
|
-
|
|
1850
|
+
goToFirst();
|
|
1512
1851
|
},
|
|
1513
1852
|
statusFilter,
|
|
1514
1853
|
onStatusFilterChange: (v) => {
|
|
1515
1854
|
setStatusFilter(v);
|
|
1516
|
-
|
|
1855
|
+
goToFirst();
|
|
1517
1856
|
},
|
|
1518
1857
|
statusOptions: statusFilterOptions2,
|
|
1519
1858
|
onRefresh: refetch
|
|
1520
1859
|
}
|
|
1521
1860
|
),
|
|
1522
|
-
/* @__PURE__ */
|
|
1523
|
-
/* @__PURE__ */
|
|
1524
|
-
/* @__PURE__ */
|
|
1861
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
1862
|
+
/* @__PURE__ */ jsx21(Receipt, { className: "w-4 h-4" }),
|
|
1863
|
+
/* @__PURE__ */ jsxs18("span", { children: [
|
|
1525
1864
|
payments.length,
|
|
1526
1865
|
" pagamenti visualizzati"
|
|
1527
1866
|
] })
|
|
1528
1867
|
] }),
|
|
1529
|
-
/* @__PURE__ */
|
|
1530
|
-
/* @__PURE__ */
|
|
1531
|
-
/* @__PURE__ */
|
|
1532
|
-
/* @__PURE__ */
|
|
1533
|
-
/* @__PURE__ */
|
|
1534
|
-
/* @__PURE__ */
|
|
1535
|
-
/* @__PURE__ */
|
|
1536
|
-
/* @__PURE__ */
|
|
1537
|
-
/* @__PURE__ */
|
|
1868
|
+
/* @__PURE__ */ jsx21("div", { className: "rounded-lg border bg-card shadow-sm", children: /* @__PURE__ */ jsx21("div", { className: "relative w-full overflow-auto", children: /* @__PURE__ */ jsxs18("table", { className: "w-full caption-bottom text-sm", children: [
|
|
1869
|
+
/* @__PURE__ */ jsx21("thead", { className: "[&_tr]:border-b", children: /* @__PURE__ */ jsxs18("tr", { className: "border-b hover:bg-muted/50", children: [
|
|
1870
|
+
/* @__PURE__ */ jsx21("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "ID Pagamento" }),
|
|
1871
|
+
/* @__PURE__ */ jsx21("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Importo" }),
|
|
1872
|
+
/* @__PURE__ */ jsx21("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Stato" }),
|
|
1873
|
+
/* @__PURE__ */ jsx21("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Cliente" }),
|
|
1874
|
+
/* @__PURE__ */ jsx21("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Descrizione" }),
|
|
1875
|
+
/* @__PURE__ */ jsx21("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Data" }),
|
|
1876
|
+
/* @__PURE__ */ jsx21("th", { className: "h-12 px-4 text-right font-medium text-muted-foreground", children: "Azioni" })
|
|
1538
1877
|
] }) }),
|
|
1539
|
-
/* @__PURE__ */
|
|
1540
|
-
/* @__PURE__ */
|
|
1541
|
-
/* @__PURE__ */
|
|
1542
|
-
/* @__PURE__ */
|
|
1543
|
-
/* @__PURE__ */
|
|
1544
|
-
/* @__PURE__ */
|
|
1545
|
-
/* @__PURE__ */
|
|
1546
|
-
/* @__PURE__ */
|
|
1547
|
-
/* @__PURE__ */
|
|
1878
|
+
/* @__PURE__ */ jsx21("tbody", { children: isLoading ? /* @__PURE__ */ jsx21(SkeletonRows, { rows: 5, columns: 7 }) : payments.length === 0 ? /* @__PURE__ */ jsx21("tr", { children: /* @__PURE__ */ jsx21("td", { colSpan: 7, className: "p-4 text-center py-8 text-muted-foreground", children: "Nessun pagamento trovato" }) }) : payments.map((payment) => /* @__PURE__ */ jsxs18("tr", { className: "border-b hover:bg-muted/50", children: [
|
|
1879
|
+
/* @__PURE__ */ jsx21("td", { className: "p-4", children: /* @__PURE__ */ jsx21("code", { className: "text-xs bg-muted px-2 py-0.5 rounded", children: truncateId(payment.id) }) }),
|
|
1880
|
+
/* @__PURE__ */ jsx21("td", { className: "p-4 font-medium", children: formatCurrencyFromCents2(payment.amount, payment.currency) }),
|
|
1881
|
+
/* @__PURE__ */ jsx21("td", { className: "p-4", children: /* @__PURE__ */ jsx21(StatusBadge, { label: paymentStatusLabels[payment.status], colorClass: paymentStatusColors[payment.status] }) }),
|
|
1882
|
+
/* @__PURE__ */ jsx21("td", { className: "p-4", children: payment.customer_email || /* @__PURE__ */ jsx21("span", { className: "text-muted-foreground", children: "-" }) }),
|
|
1883
|
+
/* @__PURE__ */ jsx21("td", { className: "p-4 max-w-[200px] truncate", children: payment.description || /* @__PURE__ */ jsx21("span", { className: "text-muted-foreground", children: "-" }) }),
|
|
1884
|
+
/* @__PURE__ */ jsx21("td", { className: "p-4", children: formatTimestamp(payment.created, true) }),
|
|
1885
|
+
/* @__PURE__ */ jsx21("td", { className: "p-4 text-right", children: payment.status === "succeeded" && /* @__PURE__ */ jsxs18("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-3 hover:bg-accent", onClick: () => openRefundDialog(payment), children: [
|
|
1886
|
+
/* @__PURE__ */ jsx21(RotateCcw, { className: "w-4 h-4 mr-1" }),
|
|
1548
1887
|
"Rimborsa"
|
|
1549
1888
|
] }) })
|
|
1550
1889
|
] }, payment.id)) })
|
|
1551
1890
|
] }) }) }),
|
|
1552
|
-
/* @__PURE__ */
|
|
1891
|
+
/* @__PURE__ */ jsx21(CursorPagination, { cursor, hasMore, items: payments, onFirstPage: goToFirst, onNextPage: goToNext })
|
|
1553
1892
|
] }),
|
|
1554
|
-
showRefundDialog && selectedPayment && /* @__PURE__ */
|
|
1555
|
-
/* @__PURE__ */
|
|
1556
|
-
/* @__PURE__ */
|
|
1557
|
-
/* @__PURE__ */
|
|
1558
|
-
/* @__PURE__ */
|
|
1559
|
-
/* @__PURE__ */
|
|
1560
|
-
/* @__PURE__ */
|
|
1561
|
-
/* @__PURE__ */
|
|
1562
|
-
/* @__PURE__ */
|
|
1563
|
-
/* @__PURE__ */
|
|
1893
|
+
showRefundDialog && selectedPayment && /* @__PURE__ */ jsxs18("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
1894
|
+
/* @__PURE__ */ jsx21("div", { className: "fixed inset-0 bg-black/80", onClick: () => setShowRefundDialog(false) }),
|
|
1895
|
+
/* @__PURE__ */ jsxs18("div", { className: "relative z-50 w-full max-w-md rounded-lg border bg-background p-6 shadow-lg", children: [
|
|
1896
|
+
/* @__PURE__ */ jsx21("h2", { className: "text-lg font-semibold", children: "Rimborso Pagamento" }),
|
|
1897
|
+
/* @__PURE__ */ jsx21("p", { className: "text-sm text-muted-foreground mt-1", children: "Effettua un rimborso per il pagamento selezionato" }),
|
|
1898
|
+
/* @__PURE__ */ jsxs18("div", { className: "space-y-4 py-4", children: [
|
|
1899
|
+
/* @__PURE__ */ jsxs18("div", { className: "p-3 bg-muted rounded-lg", children: [
|
|
1900
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex justify-between items-center", children: [
|
|
1901
|
+
/* @__PURE__ */ jsx21("span", { className: "text-sm text-muted-foreground", children: "Pagamento originale" }),
|
|
1902
|
+
/* @__PURE__ */ jsx21("span", { className: "font-medium", children: formatCurrencyFromCents2(selectedPayment.amount, selectedPayment.currency) })
|
|
1564
1903
|
] }),
|
|
1565
|
-
selectedPayment.customer_email && /* @__PURE__ */
|
|
1904
|
+
selectedPayment.customer_email && /* @__PURE__ */ jsxs18("p", { className: "text-sm text-muted-foreground mt-1", children: [
|
|
1566
1905
|
"Cliente: ",
|
|
1567
1906
|
selectedPayment.customer_email
|
|
1568
1907
|
] })
|
|
1569
1908
|
] }),
|
|
1570
|
-
/* @__PURE__ */
|
|
1571
|
-
/* @__PURE__ */
|
|
1572
|
-
/* @__PURE__ */
|
|
1909
|
+
/* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
|
|
1910
|
+
/* @__PURE__ */ jsx21("label", { className: "text-sm font-medium leading-none", children: "Importo Rimborso" }),
|
|
1911
|
+
/* @__PURE__ */ jsx21("input", { type: "number", step: "0.01", className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: refundAmount, onChange: (e) => setRefundAmount(e.target.value), placeholder: "Lascia vuoto per rimborso totale" })
|
|
1573
1912
|
] }),
|
|
1574
|
-
/* @__PURE__ */
|
|
1575
|
-
/* @__PURE__ */
|
|
1576
|
-
/* @__PURE__ */
|
|
1577
|
-
/* @__PURE__ */
|
|
1578
|
-
/* @__PURE__ */
|
|
1579
|
-
/* @__PURE__ */
|
|
1913
|
+
/* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
|
|
1914
|
+
/* @__PURE__ */ jsx21("label", { className: "text-sm font-medium leading-none", children: "Motivo" }),
|
|
1915
|
+
/* @__PURE__ */ jsxs18("select", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: refundReason, onChange: (e) => setRefundReason(e.target.value), children: [
|
|
1916
|
+
/* @__PURE__ */ jsx21("option", { value: "requested_by_customer", children: "Richiesto dal cliente" }),
|
|
1917
|
+
/* @__PURE__ */ jsx21("option", { value: "duplicate", children: "Pagamento duplicato" }),
|
|
1918
|
+
/* @__PURE__ */ jsx21("option", { value: "fraudulent", children: "Fraudolento" })
|
|
1580
1919
|
] })
|
|
1581
1920
|
] })
|
|
1582
1921
|
] }),
|
|
1583
|
-
/* @__PURE__ */
|
|
1584
|
-
/* @__PURE__ */
|
|
1585
|
-
/* @__PURE__ */
|
|
1922
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex justify-end gap-2", children: [
|
|
1923
|
+
/* @__PURE__ */ jsx21("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 border border-input bg-background hover:bg-accent", onClick: () => setShowRefundDialog(false), children: "Annulla" }),
|
|
1924
|
+
/* @__PURE__ */ jsx21("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-destructive text-destructive-foreground hover:bg-destructive/90 disabled:opacity-50", onClick: handleRefund, disabled: refundMutation.isPending, children: refundMutation.isPending ? "Rimborso in corso..." : "Conferma Rimborso" })
|
|
1586
1925
|
] })
|
|
1587
1926
|
] })
|
|
1588
1927
|
] })
|
|
1589
1928
|
] });
|
|
1590
|
-
return Wrapper ? /* @__PURE__ */
|
|
1929
|
+
return Wrapper ? /* @__PURE__ */ jsx21(Wrapper, { children: content }) : content;
|
|
1591
1930
|
}
|
|
1592
1931
|
|
|
1593
1932
|
// src/pages/InvoicesPage.tsx
|
|
@@ -1598,7 +1937,8 @@ import {
|
|
|
1598
1937
|
useAdminInvoices,
|
|
1599
1938
|
useSendInvoice,
|
|
1600
1939
|
useVoidInvoice,
|
|
1601
|
-
useDialogState as
|
|
1940
|
+
useDialogState as useDialogState3,
|
|
1941
|
+
useCursorPagination as useCursorPagination2,
|
|
1602
1942
|
formatCurrencyFromCents as formatCurrencyFromCents4,
|
|
1603
1943
|
formatTimestamp as formatTimestamp3,
|
|
1604
1944
|
invoiceStatusColors as invoiceStatusColors2,
|
|
@@ -1608,78 +1948,78 @@ import {
|
|
|
1608
1948
|
// src/components/invoices/InvoiceDetailsDialog.tsx
|
|
1609
1949
|
import { ExternalLink } from "lucide-react";
|
|
1610
1950
|
import { formatCurrencyFromCents as formatCurrencyFromCents3, formatTimestamp as formatTimestamp2, invoiceStatusColors, invoiceStatusLabels } from "@growflowstudio/growflowbilling-admin-core";
|
|
1611
|
-
import { jsx as
|
|
1951
|
+
import { jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1612
1952
|
function InvoiceDetailsDialog({ isOpen, onOpenChange, invoice }) {
|
|
1613
1953
|
if (!isOpen || !invoice) return null;
|
|
1614
|
-
return /* @__PURE__ */
|
|
1615
|
-
/* @__PURE__ */
|
|
1616
|
-
/* @__PURE__ */
|
|
1617
|
-
/* @__PURE__ */
|
|
1618
|
-
/* @__PURE__ */
|
|
1619
|
-
/* @__PURE__ */
|
|
1954
|
+
return /* @__PURE__ */ jsxs19("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
1955
|
+
/* @__PURE__ */ jsx22("div", { className: "fixed inset-0 bg-black/80", onClick: () => onOpenChange(false) }),
|
|
1956
|
+
/* @__PURE__ */ jsxs19("div", { className: "relative z-50 w-full max-w-lg rounded-lg border bg-background p-6 shadow-lg", children: [
|
|
1957
|
+
/* @__PURE__ */ jsxs19("div", { className: "flex flex-col space-y-1.5 pb-4", children: [
|
|
1958
|
+
/* @__PURE__ */ jsx22("h2", { className: "text-lg font-semibold", children: "Dettagli Fattura" }),
|
|
1959
|
+
/* @__PURE__ */ jsx22("p", { className: "text-sm text-muted-foreground", children: invoice.number || "Fattura senza numero" })
|
|
1620
1960
|
] }),
|
|
1621
|
-
/* @__PURE__ */
|
|
1622
|
-
/* @__PURE__ */
|
|
1623
|
-
/* @__PURE__ */
|
|
1624
|
-
/* @__PURE__ */
|
|
1625
|
-
/* @__PURE__ */
|
|
1961
|
+
/* @__PURE__ */ jsxs19("div", { className: "space-y-4", children: [
|
|
1962
|
+
/* @__PURE__ */ jsxs19("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
1963
|
+
/* @__PURE__ */ jsxs19("div", { children: [
|
|
1964
|
+
/* @__PURE__ */ jsx22("p", { className: "text-sm text-muted-foreground", children: "Stato" }),
|
|
1965
|
+
/* @__PURE__ */ jsx22(StatusBadge, { label: invoiceStatusLabels[invoice.status], colorClass: invoiceStatusColors[invoice.status], className: "mt-1" })
|
|
1626
1966
|
] }),
|
|
1627
|
-
/* @__PURE__ */
|
|
1628
|
-
/* @__PURE__ */
|
|
1629
|
-
/* @__PURE__ */
|
|
1967
|
+
/* @__PURE__ */ jsxs19("div", { children: [
|
|
1968
|
+
/* @__PURE__ */ jsx22("p", { className: "text-sm text-muted-foreground", children: "Data Creazione" }),
|
|
1969
|
+
/* @__PURE__ */ jsx22("p", { className: "font-medium mt-1", children: formatTimestamp2(invoice.created) })
|
|
1630
1970
|
] })
|
|
1631
1971
|
] }),
|
|
1632
|
-
/* @__PURE__ */
|
|
1633
|
-
/* @__PURE__ */
|
|
1634
|
-
/* @__PURE__ */
|
|
1635
|
-
/* @__PURE__ */
|
|
1972
|
+
/* @__PURE__ */ jsxs19("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
1973
|
+
/* @__PURE__ */ jsxs19("div", { children: [
|
|
1974
|
+
/* @__PURE__ */ jsx22("p", { className: "text-sm text-muted-foreground", children: "Importo Dovuto" }),
|
|
1975
|
+
/* @__PURE__ */ jsx22("p", { className: "font-medium mt-1", children: formatCurrencyFromCents3(invoice.amount_due, invoice.currency) })
|
|
1636
1976
|
] }),
|
|
1637
|
-
/* @__PURE__ */
|
|
1638
|
-
/* @__PURE__ */
|
|
1639
|
-
/* @__PURE__ */
|
|
1977
|
+
/* @__PURE__ */ jsxs19("div", { children: [
|
|
1978
|
+
/* @__PURE__ */ jsx22("p", { className: "text-sm text-muted-foreground", children: "Importo Pagato" }),
|
|
1979
|
+
/* @__PURE__ */ jsx22("p", { className: "font-medium mt-1", children: formatCurrencyFromCents3(invoice.amount_paid, invoice.currency) })
|
|
1640
1980
|
] })
|
|
1641
1981
|
] }),
|
|
1642
|
-
invoice.amount_remaining > 0 && /* @__PURE__ */
|
|
1982
|
+
invoice.amount_remaining > 0 && /* @__PURE__ */ jsx22("div", { className: "p-3 bg-amber-50 dark:bg-amber-900/20 rounded-lg", children: /* @__PURE__ */ jsxs19("p", { className: "text-sm text-amber-700 dark:text-amber-400", children: [
|
|
1643
1983
|
"Importo rimanente: ",
|
|
1644
|
-
/* @__PURE__ */
|
|
1984
|
+
/* @__PURE__ */ jsx22("strong", { children: formatCurrencyFromCents3(invoice.amount_remaining, invoice.currency) })
|
|
1645
1985
|
] }) }),
|
|
1646
|
-
/* @__PURE__ */
|
|
1647
|
-
/* @__PURE__ */
|
|
1648
|
-
/* @__PURE__ */
|
|
1649
|
-
invoice.customer_email && /* @__PURE__ */
|
|
1986
|
+
/* @__PURE__ */ jsxs19("div", { children: [
|
|
1987
|
+
/* @__PURE__ */ jsx22("p", { className: "text-sm text-muted-foreground", children: "Cliente" }),
|
|
1988
|
+
/* @__PURE__ */ jsx22("p", { className: "font-medium mt-1", children: invoice.customer_name || "Non specificato" }),
|
|
1989
|
+
invoice.customer_email && /* @__PURE__ */ jsx22("p", { className: "text-sm text-muted-foreground", children: invoice.customer_email })
|
|
1650
1990
|
] }),
|
|
1651
|
-
invoice.lines && invoice.lines.length > 0 && /* @__PURE__ */
|
|
1652
|
-
/* @__PURE__ */
|
|
1653
|
-
/* @__PURE__ */
|
|
1654
|
-
/* @__PURE__ */
|
|
1655
|
-
/* @__PURE__ */
|
|
1656
|
-
/* @__PURE__ */
|
|
1991
|
+
invoice.lines && invoice.lines.length > 0 && /* @__PURE__ */ jsxs19("div", { children: [
|
|
1992
|
+
/* @__PURE__ */ jsx22("p", { className: "text-sm text-muted-foreground mb-2", children: "Voci Fattura" }),
|
|
1993
|
+
/* @__PURE__ */ jsx22("div", { className: "border rounded-lg divide-y", children: invoice.lines.map((line) => /* @__PURE__ */ jsxs19("div", { className: "p-3 flex justify-between items-center", children: [
|
|
1994
|
+
/* @__PURE__ */ jsxs19("div", { children: [
|
|
1995
|
+
/* @__PURE__ */ jsx22("p", { className: "text-sm font-medium", children: line.description }),
|
|
1996
|
+
/* @__PURE__ */ jsxs19("p", { className: "text-xs text-muted-foreground", children: [
|
|
1657
1997
|
"Qt\xE0: ",
|
|
1658
1998
|
line.quantity
|
|
1659
1999
|
] })
|
|
1660
2000
|
] }),
|
|
1661
|
-
/* @__PURE__ */
|
|
2001
|
+
/* @__PURE__ */ jsx22("p", { className: "font-medium", children: formatCurrencyFromCents3(line.amount, line.currency) })
|
|
1662
2002
|
] }, line.id)) })
|
|
1663
2003
|
] }),
|
|
1664
|
-
invoice.hosted_invoice_url && /* @__PURE__ */
|
|
2004
|
+
invoice.hosted_invoice_url && /* @__PURE__ */ jsxs19(
|
|
1665
2005
|
"button",
|
|
1666
2006
|
{
|
|
1667
2007
|
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 border border-input bg-background hover:bg-accent w-full",
|
|
1668
2008
|
onClick: () => window.open(invoice.hosted_invoice_url, "_blank"),
|
|
1669
2009
|
children: [
|
|
1670
|
-
/* @__PURE__ */
|
|
2010
|
+
/* @__PURE__ */ jsx22(ExternalLink, { className: "w-4 h-4 mr-2" }),
|
|
1671
2011
|
"Apri su Stripe"
|
|
1672
2012
|
]
|
|
1673
2013
|
}
|
|
1674
2014
|
)
|
|
1675
2015
|
] }),
|
|
1676
|
-
/* @__PURE__ */
|
|
2016
|
+
/* @__PURE__ */ jsx22("div", { className: "flex justify-end pt-4", children: /* @__PURE__ */ jsx22("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 border border-input bg-background hover:bg-accent", onClick: () => onOpenChange(false), children: "Chiudi" }) })
|
|
1677
2017
|
] })
|
|
1678
2018
|
] });
|
|
1679
2019
|
}
|
|
1680
2020
|
|
|
1681
2021
|
// src/pages/InvoicesPage.tsx
|
|
1682
|
-
import { Fragment as
|
|
2022
|
+
import { Fragment as Fragment8, jsx as jsx23, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1683
2023
|
var statusFilterOptions3 = [
|
|
1684
2024
|
{ value: "all", label: "Tutti gli stati" },
|
|
1685
2025
|
{ value: "draft", label: "Bozze" },
|
|
@@ -1691,9 +2031,9 @@ var statusFilterOptions3 = [
|
|
|
1691
2031
|
function InvoicesPage({ wrapper: Wrapper, header }) {
|
|
1692
2032
|
const [search, setSearch] = useState6("");
|
|
1693
2033
|
const [statusFilter, setStatusFilter] = useState6("all");
|
|
1694
|
-
const
|
|
1695
|
-
const detailsDialog =
|
|
1696
|
-
const voidDialog =
|
|
2034
|
+
const { cursor, goToNext, goToFirst } = useCursorPagination2();
|
|
2035
|
+
const detailsDialog = useDialogState3();
|
|
2036
|
+
const voidDialog = useDialogState3();
|
|
1697
2037
|
const { data, isLoading, refetch } = useAdminInvoices({
|
|
1698
2038
|
search: search || void 0,
|
|
1699
2039
|
status: statusFilter !== "all" ? statusFilter : void 0,
|
|
@@ -1719,76 +2059,76 @@ function InvoicesPage({ wrapper: Wrapper, header }) {
|
|
|
1719
2059
|
if (invoice.invoice_pdf) window.open(invoice.invoice_pdf, "_blank");
|
|
1720
2060
|
else if (invoice.hosted_invoice_url) window.open(invoice.hosted_invoice_url, "_blank");
|
|
1721
2061
|
};
|
|
1722
|
-
const content = /* @__PURE__ */
|
|
2062
|
+
const content = /* @__PURE__ */ jsxs20(Fragment8, { children: [
|
|
1723
2063
|
header,
|
|
1724
|
-
/* @__PURE__ */
|
|
1725
|
-
/* @__PURE__ */
|
|
2064
|
+
/* @__PURE__ */ jsxs20("div", { className: "space-y-4", children: [
|
|
2065
|
+
/* @__PURE__ */ jsx23(
|
|
1726
2066
|
FilterBar,
|
|
1727
2067
|
{
|
|
1728
2068
|
searchPlaceholder: "Cerca per email o numero fattura...",
|
|
1729
2069
|
searchValue: search,
|
|
1730
2070
|
onSearchChange: (v) => {
|
|
1731
2071
|
setSearch(v);
|
|
1732
|
-
|
|
2072
|
+
goToFirst();
|
|
1733
2073
|
},
|
|
1734
2074
|
statusFilter,
|
|
1735
2075
|
onStatusFilterChange: (v) => {
|
|
1736
2076
|
setStatusFilter(v);
|
|
1737
|
-
|
|
2077
|
+
goToFirst();
|
|
1738
2078
|
},
|
|
1739
2079
|
statusOptions: statusFilterOptions3,
|
|
1740
2080
|
onRefresh: refetch
|
|
1741
2081
|
}
|
|
1742
2082
|
),
|
|
1743
|
-
/* @__PURE__ */
|
|
1744
|
-
/* @__PURE__ */
|
|
1745
|
-
/* @__PURE__ */
|
|
2083
|
+
/* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
2084
|
+
/* @__PURE__ */ jsx23(FileText, { className: "w-4 h-4" }),
|
|
2085
|
+
/* @__PURE__ */ jsxs20("span", { children: [
|
|
1746
2086
|
invoices.length,
|
|
1747
2087
|
" fatture visualizzate"
|
|
1748
2088
|
] })
|
|
1749
2089
|
] }),
|
|
1750
|
-
/* @__PURE__ */
|
|
1751
|
-
/* @__PURE__ */
|
|
1752
|
-
/* @__PURE__ */
|
|
1753
|
-
/* @__PURE__ */
|
|
1754
|
-
/* @__PURE__ */
|
|
1755
|
-
/* @__PURE__ */
|
|
1756
|
-
/* @__PURE__ */
|
|
1757
|
-
/* @__PURE__ */
|
|
1758
|
-
/* @__PURE__ */
|
|
1759
|
-
/* @__PURE__ */
|
|
2090
|
+
/* @__PURE__ */ jsx23("div", { className: "rounded-lg border bg-card shadow-sm", children: /* @__PURE__ */ jsx23("div", { className: "relative w-full overflow-auto", children: /* @__PURE__ */ jsxs20("table", { className: "w-full caption-bottom text-sm", children: [
|
|
2091
|
+
/* @__PURE__ */ jsx23("thead", { className: "[&_tr]:border-b", children: /* @__PURE__ */ jsxs20("tr", { className: "border-b hover:bg-muted/50", children: [
|
|
2092
|
+
/* @__PURE__ */ jsx23("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Numero" }),
|
|
2093
|
+
/* @__PURE__ */ jsx23("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Stato" }),
|
|
2094
|
+
/* @__PURE__ */ jsx23("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Importo Dovuto" }),
|
|
2095
|
+
/* @__PURE__ */ jsx23("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Importo Pagato" }),
|
|
2096
|
+
/* @__PURE__ */ jsx23("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Cliente" }),
|
|
2097
|
+
/* @__PURE__ */ jsx23("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Data" }),
|
|
2098
|
+
/* @__PURE__ */ jsx23("th", { className: "h-12 px-4 text-left font-medium text-muted-foreground", children: "Scadenza" }),
|
|
2099
|
+
/* @__PURE__ */ jsx23("th", { className: "h-12 px-4 text-right font-medium text-muted-foreground", children: "Azioni" })
|
|
1760
2100
|
] }) }),
|
|
1761
|
-
/* @__PURE__ */
|
|
1762
|
-
/* @__PURE__ */
|
|
1763
|
-
/* @__PURE__ */
|
|
1764
|
-
/* @__PURE__ */
|
|
1765
|
-
/* @__PURE__ */
|
|
1766
|
-
/* @__PURE__ */
|
|
1767
|
-
/* @__PURE__ */
|
|
1768
|
-
inv.customer_email && /* @__PURE__ */
|
|
2101
|
+
/* @__PURE__ */ jsx23("tbody", { children: isLoading ? /* @__PURE__ */ jsx23(SkeletonRows, { rows: 5, columns: 8 }) : invoices.length === 0 ? /* @__PURE__ */ jsx23("tr", { children: /* @__PURE__ */ jsx23("td", { colSpan: 8, className: "p-4 text-center py-8 text-muted-foreground", children: "Nessuna fattura trovata" }) }) : invoices.map((inv) => /* @__PURE__ */ jsxs20("tr", { className: "border-b hover:bg-muted/50", children: [
|
|
2102
|
+
/* @__PURE__ */ jsx23("td", { className: "p-4 font-medium", children: inv.number || /* @__PURE__ */ jsx23("span", { className: "text-muted-foreground", children: "-" }) }),
|
|
2103
|
+
/* @__PURE__ */ jsx23("td", { className: "p-4", children: /* @__PURE__ */ jsx23(StatusBadge, { label: invoiceStatusLabels2[inv.status], colorClass: invoiceStatusColors2[inv.status] }) }),
|
|
2104
|
+
/* @__PURE__ */ jsx23("td", { className: "p-4 font-medium", children: formatCurrencyFromCents4(inv.amount_due, inv.currency) }),
|
|
2105
|
+
/* @__PURE__ */ jsx23("td", { className: "p-4", children: formatCurrencyFromCents4(inv.amount_paid, inv.currency) }),
|
|
2106
|
+
/* @__PURE__ */ jsx23("td", { className: "p-4", children: /* @__PURE__ */ jsxs20("div", { className: "flex flex-col", children: [
|
|
2107
|
+
/* @__PURE__ */ jsx23("span", { children: inv.customer_name || "-" }),
|
|
2108
|
+
inv.customer_email && /* @__PURE__ */ jsx23("span", { className: "text-xs text-muted-foreground", children: inv.customer_email })
|
|
1769
2109
|
] }) }),
|
|
1770
|
-
/* @__PURE__ */
|
|
1771
|
-
/* @__PURE__ */
|
|
1772
|
-
/* @__PURE__ */
|
|
1773
|
-
/* @__PURE__ */
|
|
1774
|
-
(inv.invoice_pdf || inv.hosted_invoice_url) && /* @__PURE__ */
|
|
1775
|
-
inv.status === "open" && /* @__PURE__ */
|
|
1776
|
-
(inv.status === "draft" || inv.status === "open") && /* @__PURE__ */
|
|
2110
|
+
/* @__PURE__ */ jsx23("td", { className: "p-4", children: formatTimestamp3(inv.created) }),
|
|
2111
|
+
/* @__PURE__ */ jsx23("td", { className: "p-4", children: formatTimestamp3(inv.due_date) }),
|
|
2112
|
+
/* @__PURE__ */ jsx23("td", { className: "p-4 text-right", children: /* @__PURE__ */ jsxs20("div", { className: "flex justify-end gap-1", children: [
|
|
2113
|
+
/* @__PURE__ */ jsx23("button", { className: "inline-flex items-center justify-center rounded-md h-9 w-9 hover:bg-accent", onClick: () => detailsDialog.open(inv), children: /* @__PURE__ */ jsx23(Eye2, { className: "w-4 h-4" }) }),
|
|
2114
|
+
(inv.invoice_pdf || inv.hosted_invoice_url) && /* @__PURE__ */ jsx23("button", { className: "inline-flex items-center justify-center rounded-md h-9 w-9 hover:bg-accent", onClick: () => openInvoicePdf(inv), children: /* @__PURE__ */ jsx23(Download, { className: "w-4 h-4" }) }),
|
|
2115
|
+
inv.status === "open" && /* @__PURE__ */ jsx23("button", { className: "inline-flex items-center justify-center rounded-md h-9 w-9 hover:bg-accent", onClick: () => sendMutation.mutate(inv.id, { onSuccess: () => toast5.success("Fattura inviata"), onError: (err) => toast5.error(`Errore: ${err.message}`) }), disabled: sendMutation.isPending, children: /* @__PURE__ */ jsx23(Send, { className: "w-4 h-4" }) }),
|
|
2116
|
+
(inv.status === "draft" || inv.status === "open") && /* @__PURE__ */ jsx23("button", { className: "inline-flex items-center justify-center rounded-md h-9 w-9 text-destructive hover:bg-accent", onClick: () => voidDialog.open(inv), children: /* @__PURE__ */ jsx23(XCircle2, { className: "w-4 h-4" }) })
|
|
1777
2117
|
] }) })
|
|
1778
2118
|
] }, inv.id)) })
|
|
1779
2119
|
] }) }) }),
|
|
1780
|
-
/* @__PURE__ */
|
|
2120
|
+
/* @__PURE__ */ jsx23(CursorPagination, { cursor, hasMore, items: invoices, onFirstPage: goToFirst, onNextPage: goToNext })
|
|
1781
2121
|
] }),
|
|
1782
|
-
/* @__PURE__ */
|
|
1783
|
-
/* @__PURE__ */
|
|
2122
|
+
/* @__PURE__ */ jsx23(InvoiceDetailsDialog, { isOpen: detailsDialog.isOpen, onOpenChange: (open) => !open && detailsDialog.close(), invoice: detailsDialog.data }),
|
|
2123
|
+
/* @__PURE__ */ jsx23(
|
|
1784
2124
|
DeleteConfirmDialog,
|
|
1785
2125
|
{
|
|
1786
2126
|
isOpen: voidDialog.isOpen,
|
|
1787
2127
|
onOpenChange: (open) => !open && voidDialog.close(),
|
|
1788
2128
|
title: "Annullare Fattura?",
|
|
1789
|
-
description: /* @__PURE__ */
|
|
2129
|
+
description: /* @__PURE__ */ jsxs20(Fragment8, { children: [
|
|
1790
2130
|
"Stai per annullare la fattura ",
|
|
1791
|
-
/* @__PURE__ */
|
|
2131
|
+
/* @__PURE__ */ jsx23("strong", { children: voidDialog.data?.number || voidDialog.data?.id }),
|
|
1792
2132
|
". Una fattura annullata non pu\xF2 essere riattivata."
|
|
1793
2133
|
] }),
|
|
1794
2134
|
onConfirm: handleVoidConfirm,
|
|
@@ -1797,284 +2137,363 @@ function InvoicesPage({ wrapper: Wrapper, header }) {
|
|
|
1797
2137
|
}
|
|
1798
2138
|
)
|
|
1799
2139
|
] });
|
|
1800
|
-
return Wrapper ? /* @__PURE__ */
|
|
2140
|
+
return Wrapper ? /* @__PURE__ */ jsx23(Wrapper, { children: content }) : content;
|
|
1801
2141
|
}
|
|
1802
2142
|
|
|
1803
2143
|
// src/pages/FeaturesPage.tsx
|
|
1804
2144
|
import { useState as useState7 } from "react";
|
|
2145
|
+
import { Plus as Plus3, Package as Package2, AlertCircle } from "lucide-react";
|
|
2146
|
+
import { useAdminFeatures, useDeleteFeature } from "@growflowstudio/growflowbilling-admin-core";
|
|
2147
|
+
import { toast as toast7 } from "sonner";
|
|
2148
|
+
|
|
2149
|
+
// src/components/features/FeatureFormModal.tsx
|
|
2150
|
+
import { useCreateFeature, useUpdateFeature } from "@growflowstudio/growflowbilling-admin-core";
|
|
1805
2151
|
import { toast as toast6 } from "sonner";
|
|
1806
|
-
import {
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
useCreateFeature,
|
|
1810
|
-
useUpdateFeature,
|
|
1811
|
-
useDeleteFeature
|
|
1812
|
-
} from "@growflowstudio/growflowbilling-admin-core";
|
|
1813
|
-
import { Fragment as Fragment7, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1814
|
-
function FeaturesPage({ wrapper: Wrapper, header }) {
|
|
1815
|
-
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState7(false);
|
|
1816
|
-
const [editingFeature, setEditingFeature] = useState7(null);
|
|
1817
|
-
const [deletingFeature, setDeletingFeature] = useState7(null);
|
|
1818
|
-
const { data, isLoading } = useAdminFeatures();
|
|
2152
|
+
import { Fragment as Fragment9, jsx as jsx24, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
2153
|
+
function FeatureFormModal({ feature, open, onClose, onSuccess }) {
|
|
2154
|
+
const isEditMode = feature !== null;
|
|
1819
2155
|
const createMutation = useCreateFeature();
|
|
1820
2156
|
const updateMutation = useUpdateFeature();
|
|
1821
|
-
const
|
|
1822
|
-
const
|
|
1823
|
-
const handleCreateFeature = (e) => {
|
|
1824
|
-
e.preventDefault();
|
|
1825
|
-
const formData = new FormData(e.currentTarget);
|
|
1826
|
-
const createData = {
|
|
1827
|
-
slug: formData.get("slug"),
|
|
1828
|
-
name: formData.get("name"),
|
|
1829
|
-
description: formData.get("description") || void 0,
|
|
1830
|
-
icon: formData.get("icon") || void 0,
|
|
1831
|
-
category: formData.get("category") || void 0,
|
|
1832
|
-
is_available: formData.get("is_available") === "on",
|
|
1833
|
-
billing_product_id: formData.get("billing_product_id") || void 0
|
|
1834
|
-
};
|
|
1835
|
-
createMutation.mutate(createData, {
|
|
1836
|
-
onSuccess: () => {
|
|
1837
|
-
toast6.success("Feature creata con successo");
|
|
1838
|
-
setIsCreateDialogOpen(false);
|
|
1839
|
-
},
|
|
1840
|
-
onError: (err) => toast6.error(`Errore: ${err.message}`)
|
|
1841
|
-
});
|
|
1842
|
-
};
|
|
1843
|
-
const handleUpdateFeature = (e) => {
|
|
2157
|
+
const isPending = createMutation.isPending || updateMutation.isPending;
|
|
2158
|
+
const handleSubmit = (e) => {
|
|
1844
2159
|
e.preventDefault();
|
|
1845
|
-
if (!editingFeature) return;
|
|
1846
2160
|
const formData = new FormData(e.currentTarget);
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
2161
|
+
if (isEditMode) {
|
|
2162
|
+
const updateData = {
|
|
2163
|
+
name: formData.get("name"),
|
|
2164
|
+
description: formData.get("description") || void 0,
|
|
2165
|
+
icon: formData.get("icon") || void 0,
|
|
2166
|
+
category: formData.get("category") || void 0,
|
|
2167
|
+
is_available: formData.get("is_available") === "on",
|
|
2168
|
+
billing_product_id: formData.get("billing_product_id") || void 0
|
|
2169
|
+
};
|
|
2170
|
+
updateMutation.mutate(
|
|
2171
|
+
{ id: feature.id, data: updateData },
|
|
2172
|
+
{
|
|
2173
|
+
onSuccess: () => {
|
|
2174
|
+
toast6.success("Feature aggiornata con successo");
|
|
2175
|
+
onSuccess();
|
|
2176
|
+
},
|
|
2177
|
+
onError: (err) => toast6.error(`Errore: ${err.message}`)
|
|
2178
|
+
}
|
|
2179
|
+
);
|
|
2180
|
+
} else {
|
|
2181
|
+
const createData = {
|
|
2182
|
+
slug: formData.get("slug"),
|
|
2183
|
+
name: formData.get("name"),
|
|
2184
|
+
description: formData.get("description") || void 0,
|
|
2185
|
+
icon: formData.get("icon") || void 0,
|
|
2186
|
+
category: formData.get("category") || void 0,
|
|
2187
|
+
is_available: formData.get("is_available") === "on",
|
|
2188
|
+
billing_product_id: formData.get("billing_product_id") || void 0
|
|
2189
|
+
};
|
|
2190
|
+
createMutation.mutate(createData, {
|
|
2191
|
+
onSuccess: () => {
|
|
2192
|
+
toast6.success("Feature creata con successo");
|
|
2193
|
+
onSuccess();
|
|
2194
|
+
},
|
|
2195
|
+
onError: (err) => toast6.error(`Errore: ${err.message}`)
|
|
2196
|
+
});
|
|
2197
|
+
}
|
|
1862
2198
|
};
|
|
2199
|
+
if (!open) return null;
|
|
2200
|
+
return /* @__PURE__ */ jsxs21("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
2201
|
+
/* @__PURE__ */ jsx24("div", { className: "fixed inset-0 bg-black/80", onClick: onClose }),
|
|
2202
|
+
/* @__PURE__ */ jsx24("div", { className: "relative z-50 w-full max-w-md rounded-lg border bg-background p-6 shadow-lg", children: /* @__PURE__ */ jsxs21("form", { onSubmit: handleSubmit, children: [
|
|
2203
|
+
/* @__PURE__ */ jsx24("h2", { className: "text-lg font-semibold", children: isEditMode ? "Modifica Feature" : "Crea Nuova Feature" }),
|
|
2204
|
+
/* @__PURE__ */ jsx24("p", { className: "text-sm text-muted-foreground mt-1", children: isEditMode ? `Aggiorna i dettagli della feature "${feature.name}".` : "Configura una nuova feature disponibile." }),
|
|
2205
|
+
/* @__PURE__ */ jsxs21("div", { className: "space-y-4 py-4", children: [
|
|
2206
|
+
/* @__PURE__ */ jsxs21("div", { className: "space-y-2", children: [
|
|
2207
|
+
/* @__PURE__ */ jsx24("label", { className: "text-sm font-medium leading-none", htmlFor: "slug", children: isEditMode ? "Slug (non modificabile)" : "Slug *" }),
|
|
2208
|
+
isEditMode ? /* @__PURE__ */ jsx24(
|
|
2209
|
+
"input",
|
|
2210
|
+
{
|
|
2211
|
+
value: feature.slug,
|
|
2212
|
+
disabled: true,
|
|
2213
|
+
className: "flex h-10 w-full rounded-md border border-input bg-muted px-3 py-2 text-sm"
|
|
2214
|
+
}
|
|
2215
|
+
) : /* @__PURE__ */ jsxs21(Fragment9, { children: [
|
|
2216
|
+
/* @__PURE__ */ jsx24(
|
|
2217
|
+
"input",
|
|
2218
|
+
{
|
|
2219
|
+
id: "slug",
|
|
2220
|
+
name: "slug",
|
|
2221
|
+
required: true,
|
|
2222
|
+
placeholder: "ottimizza-dati-prodotti",
|
|
2223
|
+
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
2224
|
+
}
|
|
2225
|
+
),
|
|
2226
|
+
/* @__PURE__ */ jsx24("p", { className: "text-xs text-muted-foreground", children: "Identificatore univoco (lowercase, trattini)" })
|
|
2227
|
+
] })
|
|
2228
|
+
] }),
|
|
2229
|
+
/* @__PURE__ */ jsxs21("div", { className: "space-y-2", children: [
|
|
2230
|
+
/* @__PURE__ */ jsx24("label", { className: "text-sm font-medium leading-none", htmlFor: "name", children: "Nome *" }),
|
|
2231
|
+
/* @__PURE__ */ jsx24(
|
|
2232
|
+
"input",
|
|
2233
|
+
{
|
|
2234
|
+
id: isEditMode ? "edit-name" : "name",
|
|
2235
|
+
name: "name",
|
|
2236
|
+
required: true,
|
|
2237
|
+
defaultValue: feature?.name ?? "",
|
|
2238
|
+
placeholder: "Ottimizza Dati Prodotti",
|
|
2239
|
+
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
2240
|
+
}
|
|
2241
|
+
)
|
|
2242
|
+
] }),
|
|
2243
|
+
/* @__PURE__ */ jsxs21("div", { className: "space-y-2", children: [
|
|
2244
|
+
/* @__PURE__ */ jsx24("label", { className: "text-sm font-medium leading-none", htmlFor: "description", children: "Descrizione" }),
|
|
2245
|
+
/* @__PURE__ */ jsx24(
|
|
2246
|
+
"textarea",
|
|
2247
|
+
{
|
|
2248
|
+
id: isEditMode ? "edit-description" : "description",
|
|
2249
|
+
name: "description",
|
|
2250
|
+
rows: 3,
|
|
2251
|
+
defaultValue: feature?.description ?? "",
|
|
2252
|
+
placeholder: "Descrizione della feature...",
|
|
2253
|
+
className: "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
2254
|
+
}
|
|
2255
|
+
)
|
|
2256
|
+
] }),
|
|
2257
|
+
/* @__PURE__ */ jsxs21("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2258
|
+
/* @__PURE__ */ jsxs21("div", { className: "space-y-2", children: [
|
|
2259
|
+
/* @__PURE__ */ jsx24("label", { className: "text-sm font-medium leading-none", htmlFor: "icon", children: "Icona Lucide" }),
|
|
2260
|
+
/* @__PURE__ */ jsx24(
|
|
2261
|
+
"input",
|
|
2262
|
+
{
|
|
2263
|
+
id: isEditMode ? "edit-icon" : "icon",
|
|
2264
|
+
name: "icon",
|
|
2265
|
+
defaultValue: feature?.icon ?? "",
|
|
2266
|
+
placeholder: "Package",
|
|
2267
|
+
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
2268
|
+
}
|
|
2269
|
+
)
|
|
2270
|
+
] }),
|
|
2271
|
+
/* @__PURE__ */ jsxs21("div", { className: "space-y-2", children: [
|
|
2272
|
+
/* @__PURE__ */ jsx24("label", { className: "text-sm font-medium leading-none", htmlFor: "category", children: "Categoria" }),
|
|
2273
|
+
/* @__PURE__ */ jsx24(
|
|
2274
|
+
"input",
|
|
2275
|
+
{
|
|
2276
|
+
id: isEditMode ? "edit-category" : "category",
|
|
2277
|
+
name: "category",
|
|
2278
|
+
defaultValue: feature?.category ?? "",
|
|
2279
|
+
placeholder: "ai, limits, integration...",
|
|
2280
|
+
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
2281
|
+
}
|
|
2282
|
+
)
|
|
2283
|
+
] })
|
|
2284
|
+
] }),
|
|
2285
|
+
/* @__PURE__ */ jsxs21("div", { className: "space-y-2", children: [
|
|
2286
|
+
/* @__PURE__ */ jsx24("label", { className: "text-sm font-medium leading-none", htmlFor: "billing_product_id", children: "Billing Product ID (opzionale)" }),
|
|
2287
|
+
/* @__PURE__ */ jsx24(
|
|
2288
|
+
"input",
|
|
2289
|
+
{
|
|
2290
|
+
id: isEditMode ? "edit-billing_product_id" : "billing_product_id",
|
|
2291
|
+
name: "billing_product_id",
|
|
2292
|
+
defaultValue: feature?.billing_product_id ?? "",
|
|
2293
|
+
placeholder: "UUID del prodotto billing",
|
|
2294
|
+
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
2295
|
+
}
|
|
2296
|
+
),
|
|
2297
|
+
!isEditMode && /* @__PURE__ */ jsx24("p", { className: "text-xs text-muted-foreground", children: "Se collegato a un prodotto, la feature viene attivata automaticamente" })
|
|
2298
|
+
] }),
|
|
2299
|
+
/* @__PURE__ */ jsxs21("div", { className: "flex items-center gap-2", children: [
|
|
2300
|
+
/* @__PURE__ */ jsx24(
|
|
2301
|
+
"input",
|
|
2302
|
+
{
|
|
2303
|
+
type: "checkbox",
|
|
2304
|
+
id: isEditMode ? "edit-is_available" : "is_available",
|
|
2305
|
+
name: "is_available",
|
|
2306
|
+
defaultChecked: feature?.is_available ?? true,
|
|
2307
|
+
className: "rounded border-input"
|
|
2308
|
+
}
|
|
2309
|
+
),
|
|
2310
|
+
/* @__PURE__ */ jsx24(
|
|
2311
|
+
"label",
|
|
2312
|
+
{
|
|
2313
|
+
className: "text-sm font-medium leading-none",
|
|
2314
|
+
htmlFor: isEditMode ? "edit-is_available" : "is_available",
|
|
2315
|
+
children: "Feature disponibile"
|
|
2316
|
+
}
|
|
2317
|
+
)
|
|
2318
|
+
] })
|
|
2319
|
+
] }),
|
|
2320
|
+
/* @__PURE__ */ jsxs21("div", { className: "flex justify-end gap-2", children: [
|
|
2321
|
+
/* @__PURE__ */ jsx24(
|
|
2322
|
+
"button",
|
|
2323
|
+
{
|
|
2324
|
+
type: "button",
|
|
2325
|
+
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 border border-input bg-background hover:bg-accent",
|
|
2326
|
+
onClick: onClose,
|
|
2327
|
+
children: "Annulla"
|
|
2328
|
+
}
|
|
2329
|
+
),
|
|
2330
|
+
/* @__PURE__ */ jsx24(
|
|
2331
|
+
"button",
|
|
2332
|
+
{
|
|
2333
|
+
type: "submit",
|
|
2334
|
+
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50",
|
|
2335
|
+
disabled: isPending,
|
|
2336
|
+
children: isPending ? isEditMode ? "Salvataggio..." : "Creazione..." : isEditMode ? "Salva Modifiche" : "Crea Feature"
|
|
2337
|
+
}
|
|
2338
|
+
)
|
|
2339
|
+
] })
|
|
2340
|
+
] }) })
|
|
2341
|
+
] });
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
// src/components/features/FeatureListItem.tsx
|
|
2345
|
+
import { Pencil as Pencil3, Trash2 as Trash23 } from "lucide-react";
|
|
2346
|
+
import { jsx as jsx25, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
2347
|
+
function FeatureListItem({ feature, onEdit, onDelete }) {
|
|
2348
|
+
return /* @__PURE__ */ jsx25("div", { className: "border rounded-lg p-4 hover:bg-muted/50 transition-colors", children: /* @__PURE__ */ jsxs22("div", { className: "flex items-start justify-between", children: [
|
|
2349
|
+
/* @__PURE__ */ jsxs22("div", { className: "flex-1", children: [
|
|
2350
|
+
/* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2 mb-2", children: [
|
|
2351
|
+
/* @__PURE__ */ jsx25("span", { className: "font-semibold", children: feature.name }),
|
|
2352
|
+
/* @__PURE__ */ jsx25("code", { className: "text-xs bg-muted px-2 py-0.5 rounded", children: feature.slug }),
|
|
2353
|
+
!feature.is_available && /* @__PURE__ */ jsx25(StatusBadge, { label: "Disattivata", colorClass: "bg-secondary text-secondary-foreground border-transparent" }),
|
|
2354
|
+
feature.billing_product_id && /* @__PURE__ */ jsx25(StatusBadge, { label: "Con Billing", colorClass: "border" })
|
|
2355
|
+
] }),
|
|
2356
|
+
feature.description && /* @__PURE__ */ jsx25("p", { className: "text-sm text-muted-foreground mb-2", children: feature.description }),
|
|
2357
|
+
/* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-4 text-xs text-muted-foreground", children: [
|
|
2358
|
+
feature.category && /* @__PURE__ */ jsxs22("span", { children: [
|
|
2359
|
+
"Categoria: ",
|
|
2360
|
+
feature.category
|
|
2361
|
+
] }),
|
|
2362
|
+
feature.icon && /* @__PURE__ */ jsxs22("span", { children: [
|
|
2363
|
+
"Icona: ",
|
|
2364
|
+
feature.icon
|
|
2365
|
+
] })
|
|
2366
|
+
] })
|
|
2367
|
+
] }),
|
|
2368
|
+
/* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-1", children: [
|
|
2369
|
+
/* @__PURE__ */ jsx25(
|
|
2370
|
+
"button",
|
|
2371
|
+
{
|
|
2372
|
+
className: "inline-flex items-center justify-center rounded-md h-9 w-9 hover:bg-accent",
|
|
2373
|
+
onClick: () => onEdit(feature),
|
|
2374
|
+
children: /* @__PURE__ */ jsx25(Pencil3, { className: "w-4 h-4" })
|
|
2375
|
+
}
|
|
2376
|
+
),
|
|
2377
|
+
/* @__PURE__ */ jsx25(
|
|
2378
|
+
"button",
|
|
2379
|
+
{
|
|
2380
|
+
className: "inline-flex items-center justify-center rounded-md h-9 w-9 text-destructive hover:bg-accent",
|
|
2381
|
+
onClick: () => onDelete(feature),
|
|
2382
|
+
children: /* @__PURE__ */ jsx25(Trash23, { className: "w-4 h-4" })
|
|
2383
|
+
}
|
|
2384
|
+
)
|
|
2385
|
+
] })
|
|
2386
|
+
] }) });
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
// src/pages/FeaturesPage.tsx
|
|
2390
|
+
import { Fragment as Fragment10, jsx as jsx26, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
2391
|
+
function FeaturesPage({ wrapper: Wrapper, header }) {
|
|
2392
|
+
const [modalFeature, setModalFeature] = useState7(void 0);
|
|
2393
|
+
const [deletingFeature, setDeletingFeature] = useState7(null);
|
|
2394
|
+
const isModalOpen = modalFeature !== void 0;
|
|
2395
|
+
const { data, isLoading, refetch } = useAdminFeatures();
|
|
2396
|
+
const deleteMutation = useDeleteFeature();
|
|
2397
|
+
const features = data?.features || [];
|
|
1863
2398
|
const handleDeleteConfirm = () => {
|
|
1864
2399
|
if (deletingFeature) {
|
|
1865
2400
|
deleteMutation.mutate(deletingFeature.id, {
|
|
1866
2401
|
onSuccess: () => {
|
|
1867
|
-
|
|
2402
|
+
toast7.success("Feature eliminata con successo");
|
|
1868
2403
|
setDeletingFeature(null);
|
|
1869
2404
|
},
|
|
1870
|
-
onError: (err) =>
|
|
2405
|
+
onError: (err) => toast7.error(`Errore: ${err.message}`)
|
|
1871
2406
|
});
|
|
1872
2407
|
}
|
|
1873
2408
|
};
|
|
1874
|
-
const
|
|
2409
|
+
const handleModalSuccess = () => {
|
|
2410
|
+
refetch();
|
|
2411
|
+
setModalFeature(void 0);
|
|
2412
|
+
};
|
|
2413
|
+
const content = /* @__PURE__ */ jsxs23(Fragment10, { children: [
|
|
1875
2414
|
header,
|
|
1876
|
-
/* @__PURE__ */
|
|
1877
|
-
/* @__PURE__ */
|
|
1878
|
-
/* @__PURE__ */
|
|
1879
|
-
/* @__PURE__ */
|
|
1880
|
-
/* @__PURE__ */
|
|
2415
|
+
/* @__PURE__ */ jsxs23("div", { className: "space-y-4", children: [
|
|
2416
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex items-center justify-between", children: [
|
|
2417
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
2418
|
+
/* @__PURE__ */ jsx26(Package2, { className: "w-4 h-4" }),
|
|
2419
|
+
/* @__PURE__ */ jsxs23("span", { children: [
|
|
1881
2420
|
features.length,
|
|
1882
2421
|
" feature",
|
|
1883
2422
|
features.length !== 1 ? "s" : "",
|
|
1884
2423
|
" configurate"
|
|
1885
2424
|
] })
|
|
1886
2425
|
] }),
|
|
1887
|
-
/* @__PURE__ */
|
|
2426
|
+
/* @__PURE__ */ jsxs23(
|
|
1888
2427
|
"button",
|
|
1889
2428
|
{
|
|
1890
2429
|
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-primary text-primary-foreground hover:bg-primary/90",
|
|
1891
|
-
onClick: () =>
|
|
2430
|
+
onClick: () => setModalFeature(null),
|
|
1892
2431
|
children: [
|
|
1893
|
-
/* @__PURE__ */
|
|
2432
|
+
/* @__PURE__ */ jsx26(Plus3, { className: "w-4 h-4 mr-2" }),
|
|
1894
2433
|
"Nuova Feature"
|
|
1895
2434
|
]
|
|
1896
2435
|
}
|
|
1897
2436
|
)
|
|
1898
2437
|
] }),
|
|
1899
|
-
/* @__PURE__ */
|
|
1900
|
-
/* @__PURE__ */
|
|
1901
|
-
/* @__PURE__ */
|
|
1902
|
-
/* @__PURE__ */
|
|
2438
|
+
/* @__PURE__ */ jsxs23("div", { className: "rounded-lg border bg-card shadow-sm", children: [
|
|
2439
|
+
/* @__PURE__ */ jsxs23("div", { className: "p-4 border-b", children: [
|
|
2440
|
+
/* @__PURE__ */ jsxs23("h3", { className: "text-base font-semibold flex items-center gap-2", children: [
|
|
2441
|
+
/* @__PURE__ */ jsx26(Package2, { className: "w-5 h-5" }),
|
|
1903
2442
|
"Features Disponibili"
|
|
1904
2443
|
] }),
|
|
1905
|
-
/* @__PURE__ */
|
|
2444
|
+
/* @__PURE__ */ jsx26("p", { className: "text-sm text-muted-foreground mt-1", children: "Le features possono essere collegate a prodotti billing o attivate manualmente." })
|
|
1906
2445
|
] }),
|
|
1907
|
-
/* @__PURE__ */
|
|
1908
|
-
isLoading ? /* @__PURE__ */
|
|
1909
|
-
/* @__PURE__ */
|
|
1910
|
-
/* @__PURE__ */
|
|
1911
|
-
/* @__PURE__ */
|
|
2446
|
+
/* @__PURE__ */ jsxs23("div", { className: "p-4", children: [
|
|
2447
|
+
isLoading ? /* @__PURE__ */ jsx26("div", { className: "space-y-3", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsx26("div", { className: "h-20 bg-muted animate-pulse rounded-lg" }, i)) }) : features.length === 0 ? /* @__PURE__ */ jsxs23("div", { className: "text-center py-12 text-muted-foreground", children: [
|
|
2448
|
+
/* @__PURE__ */ jsx26(Package2, { className: "mx-auto h-12 w-12 mb-4 opacity-50" }),
|
|
2449
|
+
/* @__PURE__ */ jsx26("p", { children: "Nessuna feature configurata" }),
|
|
2450
|
+
/* @__PURE__ */ jsx26(
|
|
1912
2451
|
"button",
|
|
1913
2452
|
{
|
|
1914
2453
|
className: "text-sm text-primary hover:underline mt-2",
|
|
1915
|
-
onClick: () =>
|
|
2454
|
+
onClick: () => setModalFeature(null),
|
|
1916
2455
|
children: "Crea la prima feature"
|
|
1917
2456
|
}
|
|
1918
2457
|
)
|
|
1919
|
-
] }) : /* @__PURE__ */
|
|
1920
|
-
|
|
2458
|
+
] }) : /* @__PURE__ */ jsx26("div", { className: "space-y-3", children: features.map((feature) => /* @__PURE__ */ jsx26(
|
|
2459
|
+
FeatureListItem,
|
|
1921
2460
|
{
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
/* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2 mb-2", children: [
|
|
1926
|
-
/* @__PURE__ */ jsx17("span", { className: "font-semibold", children: feature.name }),
|
|
1927
|
-
/* @__PURE__ */ jsx17("code", { className: "text-xs bg-muted px-2 py-0.5 rounded", children: feature.slug }),
|
|
1928
|
-
!feature.is_available && /* @__PURE__ */ jsx17(StatusBadge, { label: "Disattivata", colorClass: "bg-secondary text-secondary-foreground border-transparent" }),
|
|
1929
|
-
feature.billing_product_id && /* @__PURE__ */ jsx17(StatusBadge, { label: "Con Billing", colorClass: "border" })
|
|
1930
|
-
] }),
|
|
1931
|
-
feature.description && /* @__PURE__ */ jsx17("p", { className: "text-sm text-muted-foreground mb-2", children: feature.description }),
|
|
1932
|
-
/* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-4 text-xs text-muted-foreground", children: [
|
|
1933
|
-
feature.category && /* @__PURE__ */ jsxs14("span", { children: [
|
|
1934
|
-
"Categoria: ",
|
|
1935
|
-
feature.category
|
|
1936
|
-
] }),
|
|
1937
|
-
feature.icon && /* @__PURE__ */ jsxs14("span", { children: [
|
|
1938
|
-
"Icona: ",
|
|
1939
|
-
feature.icon
|
|
1940
|
-
] })
|
|
1941
|
-
] })
|
|
1942
|
-
] }),
|
|
1943
|
-
/* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-1", children: [
|
|
1944
|
-
/* @__PURE__ */ jsx17(
|
|
1945
|
-
"button",
|
|
1946
|
-
{
|
|
1947
|
-
className: "inline-flex items-center justify-center rounded-md h-9 w-9 hover:bg-accent",
|
|
1948
|
-
onClick: () => setEditingFeature(feature),
|
|
1949
|
-
children: /* @__PURE__ */ jsx17(Pencil3, { className: "w-4 h-4" })
|
|
1950
|
-
}
|
|
1951
|
-
),
|
|
1952
|
-
/* @__PURE__ */ jsx17(
|
|
1953
|
-
"button",
|
|
1954
|
-
{
|
|
1955
|
-
className: "inline-flex items-center justify-center rounded-md h-9 w-9 text-destructive hover:bg-accent",
|
|
1956
|
-
onClick: () => setDeletingFeature(feature),
|
|
1957
|
-
children: /* @__PURE__ */ jsx17(Trash23, { className: "w-4 h-4" })
|
|
1958
|
-
}
|
|
1959
|
-
)
|
|
1960
|
-
] })
|
|
1961
|
-
] })
|
|
2461
|
+
feature,
|
|
2462
|
+
onEdit: (f) => setModalFeature(f),
|
|
2463
|
+
onDelete: (f) => setDeletingFeature(f)
|
|
1962
2464
|
},
|
|
1963
2465
|
feature.id
|
|
1964
2466
|
)) }),
|
|
1965
|
-
/* @__PURE__ */
|
|
1966
|
-
/* @__PURE__ */
|
|
1967
|
-
/* @__PURE__ */
|
|
1968
|
-
/* @__PURE__ */
|
|
2467
|
+
/* @__PURE__ */ jsxs23("div", { className: "mt-6 flex gap-3 items-start rounded-lg border p-3 text-sm", children: [
|
|
2468
|
+
/* @__PURE__ */ jsx26(AlertCircle, { className: "w-4 h-4 mt-0.5 text-muted-foreground shrink-0" }),
|
|
2469
|
+
/* @__PURE__ */ jsxs23("p", { className: "text-muted-foreground text-xs", children: [
|
|
2470
|
+
/* @__PURE__ */ jsx26("strong", { children: "Nota:" }),
|
|
1969
2471
|
" Features con un billing_product_id vengono attivate automaticamente quando il cliente sottoscrive il piano corrispondente."
|
|
1970
2472
|
] })
|
|
1971
2473
|
] })
|
|
1972
2474
|
] })
|
|
1973
2475
|
] })
|
|
1974
2476
|
] }),
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
] }),
|
|
1986
|
-
/* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
|
|
1987
|
-
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium leading-none", htmlFor: "name", children: "Nome *" }),
|
|
1988
|
-
/* @__PURE__ */ jsx17("input", { id: "name", name: "name", required: true, placeholder: "Ottimizza Dati Prodotti", className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" })
|
|
1989
|
-
] }),
|
|
1990
|
-
/* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
|
|
1991
|
-
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium leading-none", htmlFor: "description", children: "Descrizione" }),
|
|
1992
|
-
/* @__PURE__ */ jsx17("textarea", { id: "description", name: "description", rows: 3, placeholder: "Descrizione della feature...", className: "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" })
|
|
1993
|
-
] }),
|
|
1994
|
-
/* @__PURE__ */ jsxs14("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
1995
|
-
/* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
|
|
1996
|
-
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium leading-none", htmlFor: "icon", children: "Icona Lucide" }),
|
|
1997
|
-
/* @__PURE__ */ jsx17("input", { id: "icon", name: "icon", placeholder: "Package", className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" })
|
|
1998
|
-
] }),
|
|
1999
|
-
/* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
|
|
2000
|
-
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium leading-none", htmlFor: "category", children: "Categoria" }),
|
|
2001
|
-
/* @__PURE__ */ jsx17("input", { id: "category", name: "category", placeholder: "ai, limits, integration...", className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" })
|
|
2002
|
-
] })
|
|
2003
|
-
] }),
|
|
2004
|
-
/* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
|
|
2005
|
-
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium leading-none", htmlFor: "billing_product_id", children: "Billing Product ID (opzionale)" }),
|
|
2006
|
-
/* @__PURE__ */ jsx17("input", { id: "billing_product_id", name: "billing_product_id", placeholder: "UUID del prodotto billing", className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" }),
|
|
2007
|
-
/* @__PURE__ */ jsx17("p", { className: "text-xs text-muted-foreground", children: "Se collegato a un prodotto, la feature viene attivata automaticamente" })
|
|
2008
|
-
] }),
|
|
2009
|
-
/* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2", children: [
|
|
2010
|
-
/* @__PURE__ */ jsx17("input", { type: "checkbox", id: "is_available", name: "is_available", defaultChecked: true, className: "rounded border-input" }),
|
|
2011
|
-
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium leading-none", htmlFor: "is_available", children: "Feature disponibile" })
|
|
2012
|
-
] })
|
|
2013
|
-
] }),
|
|
2014
|
-
/* @__PURE__ */ jsxs14("div", { className: "flex justify-end gap-2", children: [
|
|
2015
|
-
/* @__PURE__ */ jsx17("button", { type: "button", className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 border border-input bg-background hover:bg-accent", onClick: () => setIsCreateDialogOpen(false), children: "Annulla" }),
|
|
2016
|
-
/* @__PURE__ */ jsx17("button", { type: "submit", className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50", disabled: createMutation.isPending, children: createMutation.isPending ? "Creazione..." : "Crea Feature" })
|
|
2017
|
-
] })
|
|
2018
|
-
] }) })
|
|
2019
|
-
] }),
|
|
2020
|
-
editingFeature && /* @__PURE__ */ jsxs14("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
2021
|
-
/* @__PURE__ */ jsx17("div", { className: "fixed inset-0 bg-black/80", onClick: () => setEditingFeature(null) }),
|
|
2022
|
-
/* @__PURE__ */ jsx17("div", { className: "relative z-50 w-full max-w-md rounded-lg border bg-background p-6 shadow-lg", children: /* @__PURE__ */ jsxs14("form", { onSubmit: handleUpdateFeature, children: [
|
|
2023
|
-
/* @__PURE__ */ jsx17("h2", { className: "text-lg font-semibold", children: "Modifica Feature" }),
|
|
2024
|
-
/* @__PURE__ */ jsxs14("p", { className: "text-sm text-muted-foreground mt-1", children: [
|
|
2025
|
-
'Aggiorna i dettagli della feature "',
|
|
2026
|
-
editingFeature.name,
|
|
2027
|
-
'".'
|
|
2028
|
-
] }),
|
|
2029
|
-
/* @__PURE__ */ jsxs14("div", { className: "space-y-4 py-4", children: [
|
|
2030
|
-
/* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
|
|
2031
|
-
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium leading-none", children: "Slug (non modificabile)" }),
|
|
2032
|
-
/* @__PURE__ */ jsx17("input", { value: editingFeature.slug, disabled: true, className: "flex h-10 w-full rounded-md border border-input bg-muted px-3 py-2 text-sm" })
|
|
2033
|
-
] }),
|
|
2034
|
-
/* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
|
|
2035
|
-
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium leading-none", htmlFor: "edit-name", children: "Nome *" }),
|
|
2036
|
-
/* @__PURE__ */ jsx17("input", { id: "edit-name", name: "name", required: true, defaultValue: editingFeature.name, className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" })
|
|
2037
|
-
] }),
|
|
2038
|
-
/* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
|
|
2039
|
-
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium leading-none", htmlFor: "edit-description", children: "Descrizione" }),
|
|
2040
|
-
/* @__PURE__ */ jsx17("textarea", { id: "edit-description", name: "description", rows: 3, defaultValue: editingFeature.description || "", className: "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" })
|
|
2041
|
-
] }),
|
|
2042
|
-
/* @__PURE__ */ jsxs14("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2043
|
-
/* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
|
|
2044
|
-
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium leading-none", htmlFor: "edit-icon", children: "Icona Lucide" }),
|
|
2045
|
-
/* @__PURE__ */ jsx17("input", { id: "edit-icon", name: "icon", defaultValue: editingFeature.icon || "", className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" })
|
|
2046
|
-
] }),
|
|
2047
|
-
/* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
|
|
2048
|
-
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium leading-none", htmlFor: "edit-category", children: "Categoria" }),
|
|
2049
|
-
/* @__PURE__ */ jsx17("input", { id: "edit-category", name: "category", defaultValue: editingFeature.category || "", className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" })
|
|
2050
|
-
] })
|
|
2051
|
-
] }),
|
|
2052
|
-
/* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
|
|
2053
|
-
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium leading-none", htmlFor: "edit-billing_product_id", children: "Billing Product ID" }),
|
|
2054
|
-
/* @__PURE__ */ jsx17("input", { id: "edit-billing_product_id", name: "billing_product_id", defaultValue: editingFeature.billing_product_id || "", className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" })
|
|
2055
|
-
] }),
|
|
2056
|
-
/* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2", children: [
|
|
2057
|
-
/* @__PURE__ */ jsx17("input", { type: "checkbox", id: "edit-is_available", name: "is_available", defaultChecked: editingFeature.is_available, className: "rounded border-input" }),
|
|
2058
|
-
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium leading-none", htmlFor: "edit-is_available", children: "Feature disponibile" })
|
|
2059
|
-
] })
|
|
2060
|
-
] }),
|
|
2061
|
-
/* @__PURE__ */ jsxs14("div", { className: "flex justify-end gap-2", children: [
|
|
2062
|
-
/* @__PURE__ */ jsx17("button", { type: "button", className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 border border-input bg-background hover:bg-accent", onClick: () => setEditingFeature(null), children: "Annulla" }),
|
|
2063
|
-
/* @__PURE__ */ jsx17("button", { type: "submit", className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50", disabled: updateMutation.isPending, children: updateMutation.isPending ? "Salvataggio..." : "Salva Modifiche" })
|
|
2064
|
-
] })
|
|
2065
|
-
] }) })
|
|
2066
|
-
] }),
|
|
2067
|
-
/* @__PURE__ */ jsx17(
|
|
2477
|
+
/* @__PURE__ */ jsx26(
|
|
2478
|
+
FeatureFormModal,
|
|
2479
|
+
{
|
|
2480
|
+
feature: isModalOpen ? modalFeature ?? null : null,
|
|
2481
|
+
open: isModalOpen,
|
|
2482
|
+
onClose: () => setModalFeature(void 0),
|
|
2483
|
+
onSuccess: handleModalSuccess
|
|
2484
|
+
}
|
|
2485
|
+
),
|
|
2486
|
+
/* @__PURE__ */ jsx26(
|
|
2068
2487
|
DeleteConfirmDialog,
|
|
2069
2488
|
{
|
|
2070
2489
|
isOpen: !!deletingFeature,
|
|
2071
2490
|
onOpenChange: (open) => !open && setDeletingFeature(null),
|
|
2072
2491
|
title: "Eliminare Feature?",
|
|
2073
|
-
description: /* @__PURE__ */
|
|
2492
|
+
description: /* @__PURE__ */ jsxs23(Fragment10, { children: [
|
|
2074
2493
|
"Stai per eliminare la feature ",
|
|
2075
|
-
/* @__PURE__ */
|
|
2494
|
+
/* @__PURE__ */ jsx26("strong", { children: deletingFeature?.name }),
|
|
2076
2495
|
" (",
|
|
2077
|
-
/* @__PURE__ */
|
|
2496
|
+
/* @__PURE__ */ jsx26("code", { children: deletingFeature?.slug }),
|
|
2078
2497
|
"). Questa azione non pu\xF2 essere annullata."
|
|
2079
2498
|
] }),
|
|
2080
2499
|
onConfirm: handleDeleteConfirm,
|
|
@@ -2083,7 +2502,7 @@ function FeaturesPage({ wrapper: Wrapper, header }) {
|
|
|
2083
2502
|
}
|
|
2084
2503
|
)
|
|
2085
2504
|
] });
|
|
2086
|
-
return Wrapper ? /* @__PURE__ */
|
|
2505
|
+
return Wrapper ? /* @__PURE__ */ jsx26(Wrapper, { children: content }) : content;
|
|
2087
2506
|
}
|
|
2088
2507
|
|
|
2089
2508
|
// src/nav/billingNavItems.ts
|
|
@@ -2114,6 +2533,8 @@ export {
|
|
|
2114
2533
|
BillingAdminProvider,
|
|
2115
2534
|
CursorPagination,
|
|
2116
2535
|
DeleteConfirmDialog,
|
|
2536
|
+
FeatureFormModal,
|
|
2537
|
+
FeatureListItem,
|
|
2117
2538
|
FeaturesPage,
|
|
2118
2539
|
FilterBar,
|
|
2119
2540
|
InvoiceDetailsDialog,
|
|
@@ -2121,7 +2542,11 @@ export {
|
|
|
2121
2542
|
LimitInput,
|
|
2122
2543
|
PLAN_COLORS,
|
|
2123
2544
|
PaymentsPage,
|
|
2545
|
+
PlanDetailsSection,
|
|
2546
|
+
PlanFeaturesSection,
|
|
2124
2547
|
PlanFormDialog,
|
|
2548
|
+
PlanLimitsSection,
|
|
2549
|
+
PlanPricingSection,
|
|
2125
2550
|
PlansPage,
|
|
2126
2551
|
PlansTable,
|
|
2127
2552
|
ProductFormDialog,
|
|
@@ -2129,6 +2554,7 @@ export {
|
|
|
2129
2554
|
SkeletonRows,
|
|
2130
2555
|
StatusBadge,
|
|
2131
2556
|
SubscriptionsPage,
|
|
2557
|
+
ToggleSwitch,
|
|
2132
2558
|
createDefaultPlanFormData,
|
|
2133
2559
|
defaultProductFormData,
|
|
2134
2560
|
getBillingNavItems,
|