@btst/plugin-affiliate 0.0.0-stage → 1.0.0-beta.0
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/LICENSE.md +17 -0
- package/dist/api/getters.cjs +182 -0
- package/dist/api/getters.mjs +166 -0
- package/dist/api/index.cjs +49 -0
- package/dist/api/index.d.cts +975 -0
- package/dist/api/index.d.mts +975 -0
- package/dist/api/index.d.ts +975 -0
- package/dist/api/index.mjs +7 -0
- package/dist/api/mutations.cjs +362 -0
- package/dist/api/mutations.mjs +352 -0
- package/dist/api/operations.cjs +328 -0
- package/dist/api/operations.mjs +326 -0
- package/dist/api/plugin.cjs +146 -0
- package/dist/api/plugin.mjs +144 -0
- package/dist/attribution.cjs +92 -0
- package/dist/attribution.d.cts +20 -0
- package/dist/attribution.d.mts +20 -0
- package/dist/attribution.d.ts +20 -0
- package/dist/attribution.mjs +82 -0
- package/dist/client/components/index.cjs +12 -0
- package/dist/client/components/index.d.cts +13 -0
- package/dist/client/components/index.d.mts +13 -0
- package/dist/client/components/index.d.ts +13 -0
- package/dist/client/components/index.mjs +1 -0
- package/dist/client/components/pages/affiliate-apply-page.internal.cjs +59 -0
- package/dist/client/components/pages/affiliate-apply-page.internal.mjs +57 -0
- package/dist/client/components/pages/affiliate-detail-page.internal.cjs +112 -0
- package/dist/client/components/pages/affiliate-detail-page.internal.mjs +110 -0
- package/dist/client/components/pages/affiliate-portal-page.internal.cjs +252 -0
- package/dist/client/components/pages/affiliate-portal-page.internal.mjs +250 -0
- package/dist/client/components/pages/affiliates-page.internal.cjs +160 -0
- package/dist/client/components/pages/affiliates-page.internal.mjs +158 -0
- package/dist/client/components/pages/commissions-page.internal.cjs +151 -0
- package/dist/client/components/pages/commissions-page.internal.mjs +149 -0
- package/dist/client/components/pages/payouts-page.internal.cjs +230 -0
- package/dist/client/components/pages/payouts-page.internal.mjs +228 -0
- package/dist/client/components/pages.cjs +145 -0
- package/dist/client/components/pages.mjs +138 -0
- package/dist/client/components/shared.cjs +331 -0
- package/dist/client/components/shared.mjs +316 -0
- package/dist/client/constants.cjs +5 -0
- package/dist/client/constants.mjs +3 -0
- package/dist/client/hooks/index.cjs +77 -0
- package/dist/client/hooks/index.d.cts +39 -0
- package/dist/client/hooks/index.d.mts +39 -0
- package/dist/client/hooks/index.d.ts +39 -0
- package/dist/client/hooks/index.mjs +61 -0
- package/dist/client/index.cjs +7 -0
- package/dist/client/index.d.cts +110 -0
- package/dist/client/index.d.mts +110 -0
- package/dist/client/index.d.ts +110 -0
- package/dist/client/index.mjs +1 -0
- package/dist/client/plugin.cjs +77 -0
- package/dist/client/plugin.mjs +75 -0
- package/dist/client.css +311 -0
- package/dist/commission.cjs +24 -0
- package/dist/commission.mjs +22 -0
- package/dist/db.cjs +74 -0
- package/dist/db.mjs +72 -0
- package/dist/permissions.cjs +53 -0
- package/dist/permissions.d.cts +78 -0
- package/dist/permissions.d.mts +78 -0
- package/dist/permissions.d.ts +78 -0
- package/dist/permissions.mjs +51 -0
- package/dist/query-keys.cjs +152 -0
- package/dist/query-keys.d.cts +394 -0
- package/dist/query-keys.d.mts +394 -0
- package/dist/query-keys.d.ts +394 -0
- package/dist/query-keys.mjs +149 -0
- package/dist/schemas.cjs +95 -0
- package/dist/schemas.mjs +77 -0
- package/dist/style.css +313 -0
- package/dist/types.cjs +2 -0
- package/dist/types.d.cts +176 -0
- package/dist/types.d.mts +176 -0
- package/dist/types.d.ts +176 -0
- package/dist/types.mjs +1 -0
- package/package.json +159 -3
- package/README.md +0 -3
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
const react = require('react');
|
|
6
|
+
const context = require('@btst/stack/context');
|
|
7
|
+
const permissions = require('../../../permissions.cjs');
|
|
8
|
+
const client_hooks_index = require('../../hooks/index.cjs');
|
|
9
|
+
const constants = require('../../constants.cjs');
|
|
10
|
+
const shared = require('../shared.cjs');
|
|
11
|
+
|
|
12
|
+
function PayoutsPage() {
|
|
13
|
+
const [status, setStatus] = react.useState("");
|
|
14
|
+
const [offset, setOffset] = react.useState(0);
|
|
15
|
+
const [affiliateId, setAffiliateId] = react.useState("");
|
|
16
|
+
const [currency, setCurrency] = react.useState("USD");
|
|
17
|
+
const [recording, setRecording] = react.useState(null);
|
|
18
|
+
const { data } = client_hooks_index.usePayouts({
|
|
19
|
+
status: status || void 0,
|
|
20
|
+
offset,
|
|
21
|
+
limit: 25
|
|
22
|
+
});
|
|
23
|
+
const exportCSV = client_hooks_index.usePayoutExport({ status: status || void 0 });
|
|
24
|
+
const create = client_hooks_index.useCreatePayout();
|
|
25
|
+
const update = client_hooks_index.useUpdatePayout();
|
|
26
|
+
const action = shared.useAffiliateAction();
|
|
27
|
+
const { Link, resolve } = context.usePluginSiteNavigation(constants.AFFILIATE_PLUGIN_ID);
|
|
28
|
+
const { localization } = context.usePluginOverrides(constants.AFFILIATE_PLUGIN_ID);
|
|
29
|
+
const save = (event) => {
|
|
30
|
+
event.preventDefault();
|
|
31
|
+
void action.run(async () => {
|
|
32
|
+
await create.mutateAsync({ affiliateId, currency });
|
|
33
|
+
}, "Payout batch created. Arrange the payment, then record its result below.");
|
|
34
|
+
};
|
|
35
|
+
const record = (event) => {
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
if (!recording) return;
|
|
38
|
+
const fields = new FormData(event.currentTarget);
|
|
39
|
+
const payout = recording;
|
|
40
|
+
void action.run(async () => {
|
|
41
|
+
await update.mutateAsync({
|
|
42
|
+
id: payout.id,
|
|
43
|
+
status: String(fields.get("status")),
|
|
44
|
+
reference: String(fields.get("reference") ?? "")
|
|
45
|
+
});
|
|
46
|
+
setRecording(null);
|
|
47
|
+
}, "Payout result recorded");
|
|
48
|
+
};
|
|
49
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
50
|
+
shared.AffiliatePage,
|
|
51
|
+
{
|
|
52
|
+
title: localization?.payouts ?? "Payouts",
|
|
53
|
+
description: "Group approved commissions, arrange payment, and record the result.",
|
|
54
|
+
action: /* @__PURE__ */ jsxRuntime.jsx(context.PermissionAccess, { permission: permissions.affiliatePermissions.payout.export(), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
55
|
+
"button",
|
|
56
|
+
{
|
|
57
|
+
type: "button",
|
|
58
|
+
disabled: action.pending || exportCSV.isFetching,
|
|
59
|
+
onClick: () => void action.run(async () => {
|
|
60
|
+
const result = await exportCSV.refetch();
|
|
61
|
+
if (result.error) throw result.error;
|
|
62
|
+
if (!result.data)
|
|
63
|
+
throw new Error(
|
|
64
|
+
"The payout CSV could not be exported. Try again."
|
|
65
|
+
);
|
|
66
|
+
const url = URL.createObjectURL(
|
|
67
|
+
new Blob([result.data.csv], {
|
|
68
|
+
type: "text/csv;charset=utf-8"
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
const anchor = document.createElement("a");
|
|
72
|
+
anchor.href = url;
|
|
73
|
+
anchor.download = "affiliate-payouts.csv";
|
|
74
|
+
anchor.click();
|
|
75
|
+
URL.revokeObjectURL(url);
|
|
76
|
+
}, "Payouts exported"),
|
|
77
|
+
children: "Export CSV"
|
|
78
|
+
}
|
|
79
|
+
) }),
|
|
80
|
+
children: [
|
|
81
|
+
/* @__PURE__ */ jsxRuntime.jsx(shared.ActionNotice, { ...action }),
|
|
82
|
+
/* @__PURE__ */ jsxRuntime.jsxs("form", { className: "affiliate-panel affiliate-stack", onSubmit: save, children: [
|
|
83
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Create payout batch" }),
|
|
84
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "affiliate-muted", children: "Includes all approved, unpaid commissions for one affiliate and currency. Payment is arranged manually." }),
|
|
85
|
+
/* @__PURE__ */ jsxRuntime.jsxs("fieldset", { disabled: action.pending, children: [
|
|
86
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "affiliate-form-grid", children: [
|
|
87
|
+
/* @__PURE__ */ jsxRuntime.jsx(shared.Field, { label: "Affiliate ID", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
88
|
+
"input",
|
|
89
|
+
{
|
|
90
|
+
value: affiliateId,
|
|
91
|
+
onChange: (event) => setAffiliateId(event.target.value),
|
|
92
|
+
required: true
|
|
93
|
+
}
|
|
94
|
+
) }),
|
|
95
|
+
/* @__PURE__ */ jsxRuntime.jsx(shared.Field, { label: "Currency", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
96
|
+
"input",
|
|
97
|
+
{
|
|
98
|
+
value: currency,
|
|
99
|
+
onChange: (event) => setCurrency(event.target.value.toUpperCase()),
|
|
100
|
+
pattern: "[A-Z]{3}",
|
|
101
|
+
maxLength: 3,
|
|
102
|
+
minLength: 3,
|
|
103
|
+
required: true
|
|
104
|
+
}
|
|
105
|
+
) })
|
|
106
|
+
] }),
|
|
107
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
108
|
+
context.PermissionAccess,
|
|
109
|
+
{
|
|
110
|
+
permission: permissions.affiliatePermissions.payout.create({
|
|
111
|
+
affiliateId,
|
|
112
|
+
currency
|
|
113
|
+
}),
|
|
114
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("button", { className: "affiliate-primary", type: "submit", children: create.isPending ? "Creating\u2026" : "Create payout batch" })
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
] })
|
|
118
|
+
] }),
|
|
119
|
+
recording && /* @__PURE__ */ jsxRuntime.jsxs("form", { className: "affiliate-panel affiliate-stack", onSubmit: record, children: [
|
|
120
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Record payout result" }),
|
|
121
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
122
|
+
shared.money(recording.amount, recording.currency),
|
|
123
|
+
" for affiliate",
|
|
124
|
+
" ",
|
|
125
|
+
recording.affiliateId
|
|
126
|
+
] }),
|
|
127
|
+
/* @__PURE__ */ jsxRuntime.jsxs("fieldset", { disabled: action.pending, children: [
|
|
128
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "affiliate-form-grid", children: [
|
|
129
|
+
/* @__PURE__ */ jsxRuntime.jsx(shared.Field, { label: "Payment result", children: /* @__PURE__ */ jsxRuntime.jsxs("select", { name: "status", defaultValue: "sent", children: [
|
|
130
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "sent", children: "Sent" }),
|
|
131
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "failed", children: "Failed" })
|
|
132
|
+
] }) }),
|
|
133
|
+
/* @__PURE__ */ jsxRuntime.jsx(shared.Field, { label: "Payment reference", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
134
|
+
"input",
|
|
135
|
+
{
|
|
136
|
+
name: "reference",
|
|
137
|
+
maxLength: 1e3,
|
|
138
|
+
placeholder: "Bank transfer or accounting reference"
|
|
139
|
+
}
|
|
140
|
+
) })
|
|
141
|
+
] }),
|
|
142
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "affiliate-actions", children: [
|
|
143
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { className: "affiliate-primary", type: "submit", children: "Save payout result" }),
|
|
144
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => setRecording(null), children: "Cancel" })
|
|
145
|
+
] })
|
|
146
|
+
] })
|
|
147
|
+
] }),
|
|
148
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "affiliate-toolbar", children: /* @__PURE__ */ jsxRuntime.jsx(shared.Field, { label: "Filter by status", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
149
|
+
"select",
|
|
150
|
+
{
|
|
151
|
+
value: status,
|
|
152
|
+
onChange: (event) => {
|
|
153
|
+
setStatus(event.target.value);
|
|
154
|
+
setOffset(0);
|
|
155
|
+
},
|
|
156
|
+
children: [
|
|
157
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "All statuses" }),
|
|
158
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "pending", children: "Pending" }),
|
|
159
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "sent", children: "Sent" }),
|
|
160
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "failed", children: "Failed" })
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
) }) }),
|
|
164
|
+
data.items.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "affiliate-empty", children: "No payouts match. Create a batch when an affiliate has approved commissions." }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
165
|
+
"div",
|
|
166
|
+
{
|
|
167
|
+
className: "affiliate-table-scroll",
|
|
168
|
+
role: "region",
|
|
169
|
+
"aria-label": "Payout history",
|
|
170
|
+
tabIndex: 0,
|
|
171
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("table", { children: [
|
|
172
|
+
/* @__PURE__ */ jsxRuntime.jsx("caption", { className: "affiliate-sr-only", children: "Manual affiliate payouts" }),
|
|
173
|
+
/* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
174
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { children: "Affiliate" }),
|
|
175
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "affiliate-number", children: "Amount" }),
|
|
176
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { children: "Status" }),
|
|
177
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { children: "Reference / date" }),
|
|
178
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { children: "Actions" })
|
|
179
|
+
] }) }),
|
|
180
|
+
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: data.items.map((payout) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
181
|
+
/* @__PURE__ */ jsxRuntime.jsxs("td", { children: [
|
|
182
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
183
|
+
Link,
|
|
184
|
+
{
|
|
185
|
+
href: resolve(
|
|
186
|
+
"affiliate/admin/affiliates",
|
|
187
|
+
encodeURIComponent(payout.affiliateId)
|
|
188
|
+
).href,
|
|
189
|
+
children: payout.affiliateId
|
|
190
|
+
}
|
|
191
|
+
),
|
|
192
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "affiliate-small affiliate-muted", children: payout.id })
|
|
193
|
+
] }),
|
|
194
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "affiliate-number", children: shared.money(payout.amount, payout.currency) }),
|
|
195
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { children: /* @__PURE__ */ jsxRuntime.jsx(shared.Status, { value: payout.status }) }),
|
|
196
|
+
/* @__PURE__ */ jsxRuntime.jsxs("td", { children: [
|
|
197
|
+
payout.reference || "No reference",
|
|
198
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "affiliate-small affiliate-muted", children: shared.date(payout.paidAt ?? payout.createdAt) })
|
|
199
|
+
] }),
|
|
200
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { children: payout.status === "pending" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
201
|
+
context.PermissionAccess,
|
|
202
|
+
{
|
|
203
|
+
permission: permissions.affiliatePermissions.payout.update({
|
|
204
|
+
payoutId: payout.id,
|
|
205
|
+
affiliateId: payout.affiliateId,
|
|
206
|
+
status: payout.status
|
|
207
|
+
}),
|
|
208
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
209
|
+
"button",
|
|
210
|
+
{
|
|
211
|
+
type: "button",
|
|
212
|
+
disabled: action.pending,
|
|
213
|
+
"aria-label": `Record payout result ${payout.id}`,
|
|
214
|
+
onClick: () => setRecording(payout),
|
|
215
|
+
children: "Record result"
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
) })
|
|
220
|
+
] }, payout.id)) })
|
|
221
|
+
] })
|
|
222
|
+
}
|
|
223
|
+
),
|
|
224
|
+
/* @__PURE__ */ jsxRuntime.jsx(shared.Pagination, { offset, total: data.total, onChange: setOffset })
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
exports.PayoutsPage = PayoutsPage;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { usePluginSiteNavigation, usePluginOverrides, PermissionAccess } from '@btst/stack/context';
|
|
5
|
+
import { affiliatePermissions } from '../../../permissions.mjs';
|
|
6
|
+
import { usePayouts, usePayoutExport, useCreatePayout, useUpdatePayout } from '../../hooks/index.mjs';
|
|
7
|
+
import { AFFILIATE_PLUGIN_ID } from '../../constants.mjs';
|
|
8
|
+
import { useAffiliateAction, AffiliatePage, ActionNotice, Field, money, Status, date, Pagination } from '../shared.mjs';
|
|
9
|
+
|
|
10
|
+
function PayoutsPage() {
|
|
11
|
+
const [status, setStatus] = useState("");
|
|
12
|
+
const [offset, setOffset] = useState(0);
|
|
13
|
+
const [affiliateId, setAffiliateId] = useState("");
|
|
14
|
+
const [currency, setCurrency] = useState("USD");
|
|
15
|
+
const [recording, setRecording] = useState(null);
|
|
16
|
+
const { data } = usePayouts({
|
|
17
|
+
status: status || void 0,
|
|
18
|
+
offset,
|
|
19
|
+
limit: 25
|
|
20
|
+
});
|
|
21
|
+
const exportCSV = usePayoutExport({ status: status || void 0 });
|
|
22
|
+
const create = useCreatePayout();
|
|
23
|
+
const update = useUpdatePayout();
|
|
24
|
+
const action = useAffiliateAction();
|
|
25
|
+
const { Link, resolve } = usePluginSiteNavigation(AFFILIATE_PLUGIN_ID);
|
|
26
|
+
const { localization } = usePluginOverrides(AFFILIATE_PLUGIN_ID);
|
|
27
|
+
const save = (event) => {
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
void action.run(async () => {
|
|
30
|
+
await create.mutateAsync({ affiliateId, currency });
|
|
31
|
+
}, "Payout batch created. Arrange the payment, then record its result below.");
|
|
32
|
+
};
|
|
33
|
+
const record = (event) => {
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
if (!recording) return;
|
|
36
|
+
const fields = new FormData(event.currentTarget);
|
|
37
|
+
const payout = recording;
|
|
38
|
+
void action.run(async () => {
|
|
39
|
+
await update.mutateAsync({
|
|
40
|
+
id: payout.id,
|
|
41
|
+
status: String(fields.get("status")),
|
|
42
|
+
reference: String(fields.get("reference") ?? "")
|
|
43
|
+
});
|
|
44
|
+
setRecording(null);
|
|
45
|
+
}, "Payout result recorded");
|
|
46
|
+
};
|
|
47
|
+
return /* @__PURE__ */ jsxs(
|
|
48
|
+
AffiliatePage,
|
|
49
|
+
{
|
|
50
|
+
title: localization?.payouts ?? "Payouts",
|
|
51
|
+
description: "Group approved commissions, arrange payment, and record the result.",
|
|
52
|
+
action: /* @__PURE__ */ jsx(PermissionAccess, { permission: affiliatePermissions.payout.export(), children: /* @__PURE__ */ jsx(
|
|
53
|
+
"button",
|
|
54
|
+
{
|
|
55
|
+
type: "button",
|
|
56
|
+
disabled: action.pending || exportCSV.isFetching,
|
|
57
|
+
onClick: () => void action.run(async () => {
|
|
58
|
+
const result = await exportCSV.refetch();
|
|
59
|
+
if (result.error) throw result.error;
|
|
60
|
+
if (!result.data)
|
|
61
|
+
throw new Error(
|
|
62
|
+
"The payout CSV could not be exported. Try again."
|
|
63
|
+
);
|
|
64
|
+
const url = URL.createObjectURL(
|
|
65
|
+
new Blob([result.data.csv], {
|
|
66
|
+
type: "text/csv;charset=utf-8"
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
const anchor = document.createElement("a");
|
|
70
|
+
anchor.href = url;
|
|
71
|
+
anchor.download = "affiliate-payouts.csv";
|
|
72
|
+
anchor.click();
|
|
73
|
+
URL.revokeObjectURL(url);
|
|
74
|
+
}, "Payouts exported"),
|
|
75
|
+
children: "Export CSV"
|
|
76
|
+
}
|
|
77
|
+
) }),
|
|
78
|
+
children: [
|
|
79
|
+
/* @__PURE__ */ jsx(ActionNotice, { ...action }),
|
|
80
|
+
/* @__PURE__ */ jsxs("form", { className: "affiliate-panel affiliate-stack", onSubmit: save, children: [
|
|
81
|
+
/* @__PURE__ */ jsx("h2", { children: "Create payout batch" }),
|
|
82
|
+
/* @__PURE__ */ jsx("p", { className: "affiliate-muted", children: "Includes all approved, unpaid commissions for one affiliate and currency. Payment is arranged manually." }),
|
|
83
|
+
/* @__PURE__ */ jsxs("fieldset", { disabled: action.pending, children: [
|
|
84
|
+
/* @__PURE__ */ jsxs("div", { className: "affiliate-form-grid", children: [
|
|
85
|
+
/* @__PURE__ */ jsx(Field, { label: "Affiliate ID", children: /* @__PURE__ */ jsx(
|
|
86
|
+
"input",
|
|
87
|
+
{
|
|
88
|
+
value: affiliateId,
|
|
89
|
+
onChange: (event) => setAffiliateId(event.target.value),
|
|
90
|
+
required: true
|
|
91
|
+
}
|
|
92
|
+
) }),
|
|
93
|
+
/* @__PURE__ */ jsx(Field, { label: "Currency", children: /* @__PURE__ */ jsx(
|
|
94
|
+
"input",
|
|
95
|
+
{
|
|
96
|
+
value: currency,
|
|
97
|
+
onChange: (event) => setCurrency(event.target.value.toUpperCase()),
|
|
98
|
+
pattern: "[A-Z]{3}",
|
|
99
|
+
maxLength: 3,
|
|
100
|
+
minLength: 3,
|
|
101
|
+
required: true
|
|
102
|
+
}
|
|
103
|
+
) })
|
|
104
|
+
] }),
|
|
105
|
+
/* @__PURE__ */ jsx(
|
|
106
|
+
PermissionAccess,
|
|
107
|
+
{
|
|
108
|
+
permission: affiliatePermissions.payout.create({
|
|
109
|
+
affiliateId,
|
|
110
|
+
currency
|
|
111
|
+
}),
|
|
112
|
+
children: /* @__PURE__ */ jsx("button", { className: "affiliate-primary", type: "submit", children: create.isPending ? "Creating\u2026" : "Create payout batch" })
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
] })
|
|
116
|
+
] }),
|
|
117
|
+
recording && /* @__PURE__ */ jsxs("form", { className: "affiliate-panel affiliate-stack", onSubmit: record, children: [
|
|
118
|
+
/* @__PURE__ */ jsx("h2", { children: "Record payout result" }),
|
|
119
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
120
|
+
money(recording.amount, recording.currency),
|
|
121
|
+
" for affiliate",
|
|
122
|
+
" ",
|
|
123
|
+
recording.affiliateId
|
|
124
|
+
] }),
|
|
125
|
+
/* @__PURE__ */ jsxs("fieldset", { disabled: action.pending, children: [
|
|
126
|
+
/* @__PURE__ */ jsxs("div", { className: "affiliate-form-grid", children: [
|
|
127
|
+
/* @__PURE__ */ jsx(Field, { label: "Payment result", children: /* @__PURE__ */ jsxs("select", { name: "status", defaultValue: "sent", children: [
|
|
128
|
+
/* @__PURE__ */ jsx("option", { value: "sent", children: "Sent" }),
|
|
129
|
+
/* @__PURE__ */ jsx("option", { value: "failed", children: "Failed" })
|
|
130
|
+
] }) }),
|
|
131
|
+
/* @__PURE__ */ jsx(Field, { label: "Payment reference", children: /* @__PURE__ */ jsx(
|
|
132
|
+
"input",
|
|
133
|
+
{
|
|
134
|
+
name: "reference",
|
|
135
|
+
maxLength: 1e3,
|
|
136
|
+
placeholder: "Bank transfer or accounting reference"
|
|
137
|
+
}
|
|
138
|
+
) })
|
|
139
|
+
] }),
|
|
140
|
+
/* @__PURE__ */ jsxs("div", { className: "affiliate-actions", children: [
|
|
141
|
+
/* @__PURE__ */ jsx("button", { className: "affiliate-primary", type: "submit", children: "Save payout result" }),
|
|
142
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setRecording(null), children: "Cancel" })
|
|
143
|
+
] })
|
|
144
|
+
] })
|
|
145
|
+
] }),
|
|
146
|
+
/* @__PURE__ */ jsx("div", { className: "affiliate-toolbar", children: /* @__PURE__ */ jsx(Field, { label: "Filter by status", children: /* @__PURE__ */ jsxs(
|
|
147
|
+
"select",
|
|
148
|
+
{
|
|
149
|
+
value: status,
|
|
150
|
+
onChange: (event) => {
|
|
151
|
+
setStatus(event.target.value);
|
|
152
|
+
setOffset(0);
|
|
153
|
+
},
|
|
154
|
+
children: [
|
|
155
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "All statuses" }),
|
|
156
|
+
/* @__PURE__ */ jsx("option", { value: "pending", children: "Pending" }),
|
|
157
|
+
/* @__PURE__ */ jsx("option", { value: "sent", children: "Sent" }),
|
|
158
|
+
/* @__PURE__ */ jsx("option", { value: "failed", children: "Failed" })
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
) }) }),
|
|
162
|
+
data.items.length === 0 ? /* @__PURE__ */ jsx("p", { className: "affiliate-empty", children: "No payouts match. Create a batch when an affiliate has approved commissions." }) : /* @__PURE__ */ jsx(
|
|
163
|
+
"div",
|
|
164
|
+
{
|
|
165
|
+
className: "affiliate-table-scroll",
|
|
166
|
+
role: "region",
|
|
167
|
+
"aria-label": "Payout history",
|
|
168
|
+
tabIndex: 0,
|
|
169
|
+
children: /* @__PURE__ */ jsxs("table", { children: [
|
|
170
|
+
/* @__PURE__ */ jsx("caption", { className: "affiliate-sr-only", children: "Manual affiliate payouts" }),
|
|
171
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
172
|
+
/* @__PURE__ */ jsx("th", { children: "Affiliate" }),
|
|
173
|
+
/* @__PURE__ */ jsx("th", { className: "affiliate-number", children: "Amount" }),
|
|
174
|
+
/* @__PURE__ */ jsx("th", { children: "Status" }),
|
|
175
|
+
/* @__PURE__ */ jsx("th", { children: "Reference / date" }),
|
|
176
|
+
/* @__PURE__ */ jsx("th", { children: "Actions" })
|
|
177
|
+
] }) }),
|
|
178
|
+
/* @__PURE__ */ jsx("tbody", { children: data.items.map((payout) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
179
|
+
/* @__PURE__ */ jsxs("td", { children: [
|
|
180
|
+
/* @__PURE__ */ jsx(
|
|
181
|
+
Link,
|
|
182
|
+
{
|
|
183
|
+
href: resolve(
|
|
184
|
+
"affiliate/admin/affiliates",
|
|
185
|
+
encodeURIComponent(payout.affiliateId)
|
|
186
|
+
).href,
|
|
187
|
+
children: payout.affiliateId
|
|
188
|
+
}
|
|
189
|
+
),
|
|
190
|
+
/* @__PURE__ */ jsx("p", { className: "affiliate-small affiliate-muted", children: payout.id })
|
|
191
|
+
] }),
|
|
192
|
+
/* @__PURE__ */ jsx("td", { className: "affiliate-number", children: money(payout.amount, payout.currency) }),
|
|
193
|
+
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(Status, { value: payout.status }) }),
|
|
194
|
+
/* @__PURE__ */ jsxs("td", { children: [
|
|
195
|
+
payout.reference || "No reference",
|
|
196
|
+
/* @__PURE__ */ jsx("p", { className: "affiliate-small affiliate-muted", children: date(payout.paidAt ?? payout.createdAt) })
|
|
197
|
+
] }),
|
|
198
|
+
/* @__PURE__ */ jsx("td", { children: payout.status === "pending" && /* @__PURE__ */ jsx(
|
|
199
|
+
PermissionAccess,
|
|
200
|
+
{
|
|
201
|
+
permission: affiliatePermissions.payout.update({
|
|
202
|
+
payoutId: payout.id,
|
|
203
|
+
affiliateId: payout.affiliateId,
|
|
204
|
+
status: payout.status
|
|
205
|
+
}),
|
|
206
|
+
children: /* @__PURE__ */ jsx(
|
|
207
|
+
"button",
|
|
208
|
+
{
|
|
209
|
+
type: "button",
|
|
210
|
+
disabled: action.pending,
|
|
211
|
+
"aria-label": `Record payout result ${payout.id}`,
|
|
212
|
+
onClick: () => setRecording(payout),
|
|
213
|
+
children: "Record result"
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
) })
|
|
218
|
+
] }, payout.id)) })
|
|
219
|
+
] })
|
|
220
|
+
}
|
|
221
|
+
),
|
|
222
|
+
/* @__PURE__ */ jsx(Pagination, { offset, total: data.total, onChange: setOffset })
|
|
223
|
+
]
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export { PayoutsPage };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
const react = require('react');
|
|
6
|
+
const components = require('@btst/stack/client/components');
|
|
7
|
+
const permissions = require('../../permissions.cjs');
|
|
8
|
+
const shared = require('./shared.cjs');
|
|
9
|
+
|
|
10
|
+
const Affiliates = react.lazy(
|
|
11
|
+
() => import('./pages/affiliates-page.internal.cjs').then((m) => ({
|
|
12
|
+
default: m.AffiliatesPage
|
|
13
|
+
}))
|
|
14
|
+
);
|
|
15
|
+
const Detail = react.lazy(
|
|
16
|
+
() => import('./pages/affiliate-detail-page.internal.cjs').then((m) => ({
|
|
17
|
+
default: m.AffiliateDetailPage
|
|
18
|
+
}))
|
|
19
|
+
);
|
|
20
|
+
const Commissions = react.lazy(
|
|
21
|
+
() => import('./pages/commissions-page.internal.cjs').then((m) => ({
|
|
22
|
+
default: m.CommissionsPage
|
|
23
|
+
}))
|
|
24
|
+
);
|
|
25
|
+
const Payouts = react.lazy(
|
|
26
|
+
() => import('./pages/payouts-page.internal.cjs').then((m) => ({
|
|
27
|
+
default: m.PayoutsPage
|
|
28
|
+
}))
|
|
29
|
+
);
|
|
30
|
+
const Apply = react.lazy(
|
|
31
|
+
() => import('./pages/affiliate-apply-page.internal.cjs').then((m) => ({
|
|
32
|
+
default: m.AffiliateApplyPage
|
|
33
|
+
}))
|
|
34
|
+
);
|
|
35
|
+
const Portal = react.lazy(
|
|
36
|
+
() => import('./pages/affiliate-portal-page.internal.cjs').then((m) => ({
|
|
37
|
+
default: m.AffiliatePortalPage
|
|
38
|
+
}))
|
|
39
|
+
);
|
|
40
|
+
function BrowserPage({ children }) {
|
|
41
|
+
const [mounted, setMounted] = react.useState(false);
|
|
42
|
+
react.useEffect(() => setMounted(true), []);
|
|
43
|
+
return mounted ? children : /* @__PURE__ */ jsxRuntime.jsx(shared.AffiliateLoading, {});
|
|
44
|
+
}
|
|
45
|
+
const boundary = {
|
|
46
|
+
ErrorComponent: shared.AffiliateError,
|
|
47
|
+
LoadingComponent: shared.AffiliateLoading,
|
|
48
|
+
NotFoundComponent: shared.AffiliateNotFound,
|
|
49
|
+
onError: (error) => console.error("[btst/affiliate]", error)
|
|
50
|
+
};
|
|
51
|
+
function AffiliateList() {
|
|
52
|
+
return /* @__PURE__ */ jsxRuntime.jsx(BrowserPage, { children: /* @__PURE__ */ jsxRuntime.jsx(Affiliates, {}) });
|
|
53
|
+
}
|
|
54
|
+
function AffiliateDetail(props) {
|
|
55
|
+
return /* @__PURE__ */ jsxRuntime.jsx(BrowserPage, { children: /* @__PURE__ */ jsxRuntime.jsx(Detail, { ...props }) });
|
|
56
|
+
}
|
|
57
|
+
function CommissionList() {
|
|
58
|
+
return /* @__PURE__ */ jsxRuntime.jsx(BrowserPage, { children: /* @__PURE__ */ jsxRuntime.jsx(Commissions, {}) });
|
|
59
|
+
}
|
|
60
|
+
function PayoutList() {
|
|
61
|
+
return /* @__PURE__ */ jsxRuntime.jsx(BrowserPage, { children: /* @__PURE__ */ jsxRuntime.jsx(Payouts, {}) });
|
|
62
|
+
}
|
|
63
|
+
function Application() {
|
|
64
|
+
return /* @__PURE__ */ jsxRuntime.jsx(BrowserPage, { children: /* @__PURE__ */ jsxRuntime.jsx(Apply, {}) });
|
|
65
|
+
}
|
|
66
|
+
function PortalDashboard() {
|
|
67
|
+
return /* @__PURE__ */ jsxRuntime.jsx(BrowserPage, { children: /* @__PURE__ */ jsxRuntime.jsx(Portal, {}) });
|
|
68
|
+
}
|
|
69
|
+
function AffiliatesPageComponent() {
|
|
70
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
71
|
+
components.ComposedRoute,
|
|
72
|
+
{
|
|
73
|
+
...boundary,
|
|
74
|
+
path: "/affiliate/admin/affiliates",
|
|
75
|
+
permission: permissions.affiliatePermissions.affiliate.read({ scope: "collection" }),
|
|
76
|
+
PageComponent: AffiliateList
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
function AffiliateDetailPageComponent({ id }) {
|
|
81
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
82
|
+
components.ComposedRoute,
|
|
83
|
+
{
|
|
84
|
+
...boundary,
|
|
85
|
+
path: `/affiliate/admin/affiliates/${id}`,
|
|
86
|
+
permission: permissions.affiliatePermissions.affiliate.read({
|
|
87
|
+
scope: "record",
|
|
88
|
+
affiliateId: id
|
|
89
|
+
}),
|
|
90
|
+
PageComponent: AffiliateDetail,
|
|
91
|
+
props: { id }
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
function CommissionsPageComponent() {
|
|
96
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
97
|
+
components.ComposedRoute,
|
|
98
|
+
{
|
|
99
|
+
...boundary,
|
|
100
|
+
path: "/affiliate/admin/commissions",
|
|
101
|
+
permission: permissions.affiliatePermissions.commission.read(),
|
|
102
|
+
PageComponent: CommissionList
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
function PayoutsPageComponent() {
|
|
107
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
108
|
+
components.ComposedRoute,
|
|
109
|
+
{
|
|
110
|
+
...boundary,
|
|
111
|
+
path: "/affiliate/admin/payouts",
|
|
112
|
+
permission: permissions.affiliatePermissions.payout.read(),
|
|
113
|
+
PageComponent: PayoutList
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
function AffiliateApplyPageComponent() {
|
|
118
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
119
|
+
components.ComposedRoute,
|
|
120
|
+
{
|
|
121
|
+
...boundary,
|
|
122
|
+
path: "/affiliate/apply",
|
|
123
|
+
permission: permissions.affiliatePermissions.application.create(),
|
|
124
|
+
PageComponent: Application
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
function AffiliatePortalPageComponent() {
|
|
129
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
130
|
+
components.ComposedRoute,
|
|
131
|
+
{
|
|
132
|
+
...boundary,
|
|
133
|
+
path: "/affiliate/portal",
|
|
134
|
+
permission: permissions.affiliatePermissions.portal.read(),
|
|
135
|
+
PageComponent: PortalDashboard
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
exports.AffiliateApplyPageComponent = AffiliateApplyPageComponent;
|
|
141
|
+
exports.AffiliateDetailPageComponent = AffiliateDetailPageComponent;
|
|
142
|
+
exports.AffiliatePortalPageComponent = AffiliatePortalPageComponent;
|
|
143
|
+
exports.AffiliatesPageComponent = AffiliatesPageComponent;
|
|
144
|
+
exports.CommissionsPageComponent = CommissionsPageComponent;
|
|
145
|
+
exports.PayoutsPageComponent = PayoutsPageComponent;
|