@btst/plugin-affiliate 0.0.0-stage → 1.0.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.
Files changed (79) hide show
  1. package/LICENSE.md +17 -0
  2. package/dist/api/getters.cjs +182 -0
  3. package/dist/api/getters.mjs +166 -0
  4. package/dist/api/index.cjs +49 -0
  5. package/dist/api/index.d.cts +975 -0
  6. package/dist/api/index.d.mts +975 -0
  7. package/dist/api/index.d.ts +975 -0
  8. package/dist/api/index.mjs +7 -0
  9. package/dist/api/mutations.cjs +362 -0
  10. package/dist/api/mutations.mjs +352 -0
  11. package/dist/api/operations.cjs +328 -0
  12. package/dist/api/operations.mjs +326 -0
  13. package/dist/api/plugin.cjs +146 -0
  14. package/dist/api/plugin.mjs +144 -0
  15. package/dist/attribution.cjs +92 -0
  16. package/dist/attribution.d.cts +20 -0
  17. package/dist/attribution.d.mts +20 -0
  18. package/dist/attribution.d.ts +20 -0
  19. package/dist/attribution.mjs +82 -0
  20. package/dist/client/components/index.cjs +12 -0
  21. package/dist/client/components/index.d.cts +13 -0
  22. package/dist/client/components/index.d.mts +13 -0
  23. package/dist/client/components/index.d.ts +13 -0
  24. package/dist/client/components/index.mjs +1 -0
  25. package/dist/client/components/pages/affiliate-apply-page.internal.cjs +59 -0
  26. package/dist/client/components/pages/affiliate-apply-page.internal.mjs +57 -0
  27. package/dist/client/components/pages/affiliate-detail-page.internal.cjs +112 -0
  28. package/dist/client/components/pages/affiliate-detail-page.internal.mjs +110 -0
  29. package/dist/client/components/pages/affiliate-portal-page.internal.cjs +252 -0
  30. package/dist/client/components/pages/affiliate-portal-page.internal.mjs +250 -0
  31. package/dist/client/components/pages/affiliates-page.internal.cjs +160 -0
  32. package/dist/client/components/pages/affiliates-page.internal.mjs +158 -0
  33. package/dist/client/components/pages/commissions-page.internal.cjs +151 -0
  34. package/dist/client/components/pages/commissions-page.internal.mjs +149 -0
  35. package/dist/client/components/pages/payouts-page.internal.cjs +230 -0
  36. package/dist/client/components/pages/payouts-page.internal.mjs +228 -0
  37. package/dist/client/components/pages.cjs +145 -0
  38. package/dist/client/components/pages.mjs +138 -0
  39. package/dist/client/components/shared.cjs +331 -0
  40. package/dist/client/components/shared.mjs +316 -0
  41. package/dist/client/constants.cjs +5 -0
  42. package/dist/client/constants.mjs +3 -0
  43. package/dist/client/hooks/index.cjs +77 -0
  44. package/dist/client/hooks/index.d.cts +39 -0
  45. package/dist/client/hooks/index.d.mts +39 -0
  46. package/dist/client/hooks/index.d.ts +39 -0
  47. package/dist/client/hooks/index.mjs +61 -0
  48. package/dist/client/index.cjs +7 -0
  49. package/dist/client/index.d.cts +110 -0
  50. package/dist/client/index.d.mts +110 -0
  51. package/dist/client/index.d.ts +110 -0
  52. package/dist/client/index.mjs +1 -0
  53. package/dist/client/plugin.cjs +77 -0
  54. package/dist/client/plugin.mjs +75 -0
  55. package/dist/client.css +311 -0
  56. package/dist/commission.cjs +24 -0
  57. package/dist/commission.mjs +22 -0
  58. package/dist/db.cjs +74 -0
  59. package/dist/db.mjs +72 -0
  60. package/dist/permissions.cjs +53 -0
  61. package/dist/permissions.d.cts +78 -0
  62. package/dist/permissions.d.mts +78 -0
  63. package/dist/permissions.d.ts +78 -0
  64. package/dist/permissions.mjs +51 -0
  65. package/dist/query-keys.cjs +152 -0
  66. package/dist/query-keys.d.cts +394 -0
  67. package/dist/query-keys.d.mts +394 -0
  68. package/dist/query-keys.d.ts +394 -0
  69. package/dist/query-keys.mjs +149 -0
  70. package/dist/schemas.cjs +95 -0
  71. package/dist/schemas.mjs +77 -0
  72. package/dist/style.css +313 -0
  73. package/dist/types.cjs +2 -0
  74. package/dist/types.d.cts +176 -0
  75. package/dist/types.d.mts +176 -0
  76. package/dist/types.d.ts +176 -0
  77. package/dist/types.mjs +1 -0
  78. package/package.json +159 -3
  79. package/README.md +0 -3
@@ -0,0 +1,92 @@
1
+ 'use strict';
2
+
3
+ const AFFILIATE_COOKIE_NAME = "btst_affiliate";
4
+ function safeLandingPath(value = "/") {
5
+ let decoded;
6
+ try {
7
+ decoded = decodeURIComponent(value);
8
+ } catch {
9
+ throw new Error("Invalid landing path");
10
+ }
11
+ if (!decoded.startsWith("/") || decoded.startsWith("//") || /[\\\u0000-\u0020\u007f]/.test(decoded)) {
12
+ throw new Error("Landing path must be a local absolute path");
13
+ }
14
+ const parsed = new URL(value, "https://affiliate.invalid");
15
+ if (parsed.origin !== "https://affiliate.invalid" || parsed.pathname.startsWith("//") || decodeURIComponent(parsed.pathname).startsWith("//"))
16
+ throw new Error("Invalid landing path");
17
+ return parsed.pathname + parsed.search + parsed.hash;
18
+ }
19
+ function referralCodeFromURL(url, parameters = ["ref", "affiliate"]) {
20
+ const parsed = typeof url === "string" ? new URL(url, "https://affiliate.invalid") : url;
21
+ for (const name of parameters) {
22
+ const value = parsed.searchParams.get(name);
23
+ if (value) return value;
24
+ }
25
+ return null;
26
+ }
27
+ function isClickWithinWindow(click, days = 30, now = /* @__PURE__ */ new Date()) {
28
+ const age = now.getTime() - new Date(click.createdAt).getTime();
29
+ return Number.isFinite(age) && age >= 0 && age < days * 864e5;
30
+ }
31
+ function selectAttributionClick(previous, current, options = {}) {
32
+ return options.attributionMode === "first_touch" && previous && isClickWithinWindow(previous, options.attributionWindowDays) ? previous : current;
33
+ }
34
+ function createReferralLink(baseURL, code, landingPath = "/") {
35
+ const base = new URL(baseURL);
36
+ if (!["https:", "http:"].includes(base.protocol))
37
+ throw new Error("Referral links require HTTP or HTTPS");
38
+ const url = new URL(safeLandingPath(landingPath), base.origin);
39
+ url.searchParams.set("ref", code);
40
+ return url.href;
41
+ }
42
+ function attributionFromRequest(request) {
43
+ const code = referralCodeFromURL(request.url);
44
+ if (code) return { code };
45
+ for (const cookie of (request.headers.get("cookie") ?? "").split(";")) {
46
+ const separator = cookie.indexOf("=");
47
+ if (separator < 0 || cookie.slice(0, separator).trim() !== AFFILIATE_COOKIE_NAME)
48
+ continue;
49
+ try {
50
+ const clickId = decodeURIComponent(cookie.slice(separator + 1).trim());
51
+ return clickId ? { clickId } : void 0;
52
+ } catch {
53
+ return void 0;
54
+ }
55
+ }
56
+ return void 0;
57
+ }
58
+ function createTrackingLink(apiBaseURL, code, landingPath = "/") {
59
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(code) || code.length < 3 || code.length > 64)
60
+ throw new Error("Invalid affiliate code");
61
+ const base = new URL(apiBaseURL);
62
+ if (!["http:", "https:"].includes(base.protocol))
63
+ throw new Error("Tracking links require HTTP or HTTPS");
64
+ const url = new URL(
65
+ `${base.pathname.replace(/\/$/, "")}/affiliate/track/${encodeURIComponent(code)}`,
66
+ base.origin
67
+ );
68
+ url.searchParams.set("to", safeLandingPath(landingPath));
69
+ return url.href;
70
+ }
71
+ function getReferralRedirect(value, apiBaseURL) {
72
+ const url = new URL(value);
73
+ const code = referralCodeFromURL(url);
74
+ if (!code) return null;
75
+ url.searchParams.delete("ref");
76
+ url.searchParams.delete("affiliate");
77
+ return createTrackingLink(
78
+ apiBaseURL,
79
+ code,
80
+ url.pathname + url.search + url.hash
81
+ );
82
+ }
83
+
84
+ exports.AFFILIATE_COOKIE_NAME = AFFILIATE_COOKIE_NAME;
85
+ exports.attributionFromRequest = attributionFromRequest;
86
+ exports.createReferralLink = createReferralLink;
87
+ exports.createTrackingLink = createTrackingLink;
88
+ exports.getReferralRedirect = getReferralRedirect;
89
+ exports.isClickWithinWindow = isClickWithinWindow;
90
+ exports.referralCodeFromURL = referralCodeFromURL;
91
+ exports.safeLandingPath = safeLandingPath;
92
+ exports.selectAttributionClick = selectAttributionClick;
@@ -0,0 +1,20 @@
1
+ import { ReferralClick, AffiliateOptions } from './types.cjs';
2
+
3
+ declare const AFFILIATE_COOKIE_NAME = "btst_affiliate";
4
+ /** Only local absolute paths are accepted; URL parsing also catches encoded authority paths. */
5
+ declare function safeLandingPath(value?: string): string;
6
+ declare function referralCodeFromURL(url: string | URL, parameters?: string[]): string | null;
7
+ declare function isClickWithinWindow(click: Pick<ReferralClick, "createdAt">, days?: number, now?: Date): boolean;
8
+ /** The caller must load the previous opaque cookie click ID from the database first. */
9
+ declare function selectAttributionClick(previous: ReferralClick | null, current: ReferralClick, options?: AffiliateOptions): ReferralClick;
10
+ declare function createReferralLink(baseURL: string, code: string, landingPath?: string): string;
11
+ /** Checkout helpers resolve query codes or opaque click IDs; timestamps always come from the database. */
12
+ declare function attributionFromRequest(request: Request): {
13
+ code?: string;
14
+ clickId?: string;
15
+ } | undefined;
16
+ declare function createTrackingLink(apiBaseURL: string, code: string, landingPath?: string): string;
17
+ /** Redirect query-param referrals through tracking once, then return to the landing page without referral params. */
18
+ declare function getReferralRedirect(value: string | URL, apiBaseURL: string): string | null;
19
+
20
+ export { AFFILIATE_COOKIE_NAME, attributionFromRequest, createReferralLink, createTrackingLink, getReferralRedirect, isClickWithinWindow, referralCodeFromURL, safeLandingPath, selectAttributionClick };
@@ -0,0 +1,20 @@
1
+ import { ReferralClick, AffiliateOptions } from './types.mjs';
2
+
3
+ declare const AFFILIATE_COOKIE_NAME = "btst_affiliate";
4
+ /** Only local absolute paths are accepted; URL parsing also catches encoded authority paths. */
5
+ declare function safeLandingPath(value?: string): string;
6
+ declare function referralCodeFromURL(url: string | URL, parameters?: string[]): string | null;
7
+ declare function isClickWithinWindow(click: Pick<ReferralClick, "createdAt">, days?: number, now?: Date): boolean;
8
+ /** The caller must load the previous opaque cookie click ID from the database first. */
9
+ declare function selectAttributionClick(previous: ReferralClick | null, current: ReferralClick, options?: AffiliateOptions): ReferralClick;
10
+ declare function createReferralLink(baseURL: string, code: string, landingPath?: string): string;
11
+ /** Checkout helpers resolve query codes or opaque click IDs; timestamps always come from the database. */
12
+ declare function attributionFromRequest(request: Request): {
13
+ code?: string;
14
+ clickId?: string;
15
+ } | undefined;
16
+ declare function createTrackingLink(apiBaseURL: string, code: string, landingPath?: string): string;
17
+ /** Redirect query-param referrals through tracking once, then return to the landing page without referral params. */
18
+ declare function getReferralRedirect(value: string | URL, apiBaseURL: string): string | null;
19
+
20
+ export { AFFILIATE_COOKIE_NAME, attributionFromRequest, createReferralLink, createTrackingLink, getReferralRedirect, isClickWithinWindow, referralCodeFromURL, safeLandingPath, selectAttributionClick };
@@ -0,0 +1,20 @@
1
+ import { ReferralClick, AffiliateOptions } from './types.js';
2
+
3
+ declare const AFFILIATE_COOKIE_NAME = "btst_affiliate";
4
+ /** Only local absolute paths are accepted; URL parsing also catches encoded authority paths. */
5
+ declare function safeLandingPath(value?: string): string;
6
+ declare function referralCodeFromURL(url: string | URL, parameters?: string[]): string | null;
7
+ declare function isClickWithinWindow(click: Pick<ReferralClick, "createdAt">, days?: number, now?: Date): boolean;
8
+ /** The caller must load the previous opaque cookie click ID from the database first. */
9
+ declare function selectAttributionClick(previous: ReferralClick | null, current: ReferralClick, options?: AffiliateOptions): ReferralClick;
10
+ declare function createReferralLink(baseURL: string, code: string, landingPath?: string): string;
11
+ /** Checkout helpers resolve query codes or opaque click IDs; timestamps always come from the database. */
12
+ declare function attributionFromRequest(request: Request): {
13
+ code?: string;
14
+ clickId?: string;
15
+ } | undefined;
16
+ declare function createTrackingLink(apiBaseURL: string, code: string, landingPath?: string): string;
17
+ /** Redirect query-param referrals through tracking once, then return to the landing page without referral params. */
18
+ declare function getReferralRedirect(value: string | URL, apiBaseURL: string): string | null;
19
+
20
+ export { AFFILIATE_COOKIE_NAME, attributionFromRequest, createReferralLink, createTrackingLink, getReferralRedirect, isClickWithinWindow, referralCodeFromURL, safeLandingPath, selectAttributionClick };
@@ -0,0 +1,82 @@
1
+ const AFFILIATE_COOKIE_NAME = "btst_affiliate";
2
+ function safeLandingPath(value = "/") {
3
+ let decoded;
4
+ try {
5
+ decoded = decodeURIComponent(value);
6
+ } catch {
7
+ throw new Error("Invalid landing path");
8
+ }
9
+ if (!decoded.startsWith("/") || decoded.startsWith("//") || /[\\\u0000-\u0020\u007f]/.test(decoded)) {
10
+ throw new Error("Landing path must be a local absolute path");
11
+ }
12
+ const parsed = new URL(value, "https://affiliate.invalid");
13
+ if (parsed.origin !== "https://affiliate.invalid" || parsed.pathname.startsWith("//") || decodeURIComponent(parsed.pathname).startsWith("//"))
14
+ throw new Error("Invalid landing path");
15
+ return parsed.pathname + parsed.search + parsed.hash;
16
+ }
17
+ function referralCodeFromURL(url, parameters = ["ref", "affiliate"]) {
18
+ const parsed = typeof url === "string" ? new URL(url, "https://affiliate.invalid") : url;
19
+ for (const name of parameters) {
20
+ const value = parsed.searchParams.get(name);
21
+ if (value) return value;
22
+ }
23
+ return null;
24
+ }
25
+ function isClickWithinWindow(click, days = 30, now = /* @__PURE__ */ new Date()) {
26
+ const age = now.getTime() - new Date(click.createdAt).getTime();
27
+ return Number.isFinite(age) && age >= 0 && age < days * 864e5;
28
+ }
29
+ function selectAttributionClick(previous, current, options = {}) {
30
+ return options.attributionMode === "first_touch" && previous && isClickWithinWindow(previous, options.attributionWindowDays) ? previous : current;
31
+ }
32
+ function createReferralLink(baseURL, code, landingPath = "/") {
33
+ const base = new URL(baseURL);
34
+ if (!["https:", "http:"].includes(base.protocol))
35
+ throw new Error("Referral links require HTTP or HTTPS");
36
+ const url = new URL(safeLandingPath(landingPath), base.origin);
37
+ url.searchParams.set("ref", code);
38
+ return url.href;
39
+ }
40
+ function attributionFromRequest(request) {
41
+ const code = referralCodeFromURL(request.url);
42
+ if (code) return { code };
43
+ for (const cookie of (request.headers.get("cookie") ?? "").split(";")) {
44
+ const separator = cookie.indexOf("=");
45
+ if (separator < 0 || cookie.slice(0, separator).trim() !== AFFILIATE_COOKIE_NAME)
46
+ continue;
47
+ try {
48
+ const clickId = decodeURIComponent(cookie.slice(separator + 1).trim());
49
+ return clickId ? { clickId } : void 0;
50
+ } catch {
51
+ return void 0;
52
+ }
53
+ }
54
+ return void 0;
55
+ }
56
+ function createTrackingLink(apiBaseURL, code, landingPath = "/") {
57
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(code) || code.length < 3 || code.length > 64)
58
+ throw new Error("Invalid affiliate code");
59
+ const base = new URL(apiBaseURL);
60
+ if (!["http:", "https:"].includes(base.protocol))
61
+ throw new Error("Tracking links require HTTP or HTTPS");
62
+ const url = new URL(
63
+ `${base.pathname.replace(/\/$/, "")}/affiliate/track/${encodeURIComponent(code)}`,
64
+ base.origin
65
+ );
66
+ url.searchParams.set("to", safeLandingPath(landingPath));
67
+ return url.href;
68
+ }
69
+ function getReferralRedirect(value, apiBaseURL) {
70
+ const url = new URL(value);
71
+ const code = referralCodeFromURL(url);
72
+ if (!code) return null;
73
+ url.searchParams.delete("ref");
74
+ url.searchParams.delete("affiliate");
75
+ return createTrackingLink(
76
+ apiBaseURL,
77
+ code,
78
+ url.pathname + url.search + url.hash
79
+ );
80
+ }
81
+
82
+ export { AFFILIATE_COOKIE_NAME, attributionFromRequest, createReferralLink, createTrackingLink, getReferralRedirect, isClickWithinWindow, referralCodeFromURL, safeLandingPath, selectAttributionClick };
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ const pages = require('./pages.cjs');
4
+
5
+
6
+
7
+ exports.AffiliateApplyPageComponent = pages.AffiliateApplyPageComponent;
8
+ exports.AffiliateDetailPageComponent = pages.AffiliateDetailPageComponent;
9
+ exports.AffiliatePortalPageComponent = pages.AffiliatePortalPageComponent;
10
+ exports.AffiliatesPageComponent = pages.AffiliatesPageComponent;
11
+ exports.CommissionsPageComponent = pages.CommissionsPageComponent;
12
+ exports.PayoutsPageComponent = pages.PayoutsPageComponent;
@@ -0,0 +1,13 @@
1
+ 'use client';
2
+ import * as react from 'react';
3
+
4
+ declare function AffiliatesPageComponent(): react.JSX.Element;
5
+ declare function AffiliateDetailPageComponent({ id }: {
6
+ id: string;
7
+ }): react.JSX.Element;
8
+ declare function CommissionsPageComponent(): react.JSX.Element;
9
+ declare function PayoutsPageComponent(): react.JSX.Element;
10
+ declare function AffiliateApplyPageComponent(): react.JSX.Element;
11
+ declare function AffiliatePortalPageComponent(): react.JSX.Element;
12
+
13
+ export { AffiliateApplyPageComponent, AffiliateDetailPageComponent, AffiliatePortalPageComponent, AffiliatesPageComponent, CommissionsPageComponent, PayoutsPageComponent };
@@ -0,0 +1,13 @@
1
+ 'use client';
2
+ import * as react from 'react';
3
+
4
+ declare function AffiliatesPageComponent(): react.JSX.Element;
5
+ declare function AffiliateDetailPageComponent({ id }: {
6
+ id: string;
7
+ }): react.JSX.Element;
8
+ declare function CommissionsPageComponent(): react.JSX.Element;
9
+ declare function PayoutsPageComponent(): react.JSX.Element;
10
+ declare function AffiliateApplyPageComponent(): react.JSX.Element;
11
+ declare function AffiliatePortalPageComponent(): react.JSX.Element;
12
+
13
+ export { AffiliateApplyPageComponent, AffiliateDetailPageComponent, AffiliatePortalPageComponent, AffiliatesPageComponent, CommissionsPageComponent, PayoutsPageComponent };
@@ -0,0 +1,13 @@
1
+ 'use client';
2
+ import * as react from 'react';
3
+
4
+ declare function AffiliatesPageComponent(): react.JSX.Element;
5
+ declare function AffiliateDetailPageComponent({ id }: {
6
+ id: string;
7
+ }): react.JSX.Element;
8
+ declare function CommissionsPageComponent(): react.JSX.Element;
9
+ declare function PayoutsPageComponent(): react.JSX.Element;
10
+ declare function AffiliateApplyPageComponent(): react.JSX.Element;
11
+ declare function AffiliatePortalPageComponent(): react.JSX.Element;
12
+
13
+ export { AffiliateApplyPageComponent, AffiliateDetailPageComponent, AffiliatePortalPageComponent, AffiliatesPageComponent, CommissionsPageComponent, PayoutsPageComponent };
@@ -0,0 +1 @@
1
+ export { AffiliateApplyPageComponent, AffiliateDetailPageComponent, AffiliatePortalPageComponent, AffiliatesPageComponent, CommissionsPageComponent, PayoutsPageComponent } from './pages.mjs';
@@ -0,0 +1,59 @@
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 client_hooks_index = require('../../hooks/index.cjs');
8
+ const constants = require('../../constants.cjs');
9
+ const shared = require('../shared.cjs');
10
+
11
+ function AffiliateApplyPage() {
12
+ const { identity } = context.useIdentity();
13
+ const [applied, setApplied] = react.useState(false);
14
+ const apply = client_hooks_index.useApplyAffiliate();
15
+ const action = shared.useAffiliateAction();
16
+ const { Link, resolve } = context.usePluginSiteNavigation(constants.AFFILIATE_PLUGIN_ID);
17
+ const { localization } = context.usePluginOverrides(constants.AFFILIATE_PLUGIN_ID);
18
+ const save = (event) => {
19
+ event.preventDefault();
20
+ const fields = new FormData(event.currentTarget);
21
+ void action.run(async () => {
22
+ await apply.mutateAsync(shared.readProfile(fields));
23
+ setApplied(true);
24
+ }, "Application submitted");
25
+ };
26
+ return /* @__PURE__ */ jsxRuntime.jsx(
27
+ shared.AffiliatePage,
28
+ {
29
+ title: localization?.apply ?? "Become an affiliate",
30
+ description: "Apply for a referral code and earn commissions on attributed conversions after approval.",
31
+ admin: false,
32
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "affiliate-application", children: [
33
+ /* @__PURE__ */ jsxRuntime.jsx(shared.ActionNotice, { ...action }),
34
+ applied ? /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "affiliate-stack", children: [
35
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Your application is pending review" }),
36
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "You can view your status and update your payout details in the affiliate portal." }),
37
+ /* @__PURE__ */ jsxRuntime.jsx(
38
+ Link,
39
+ {
40
+ className: "affiliate-primary",
41
+ href: resolve("affiliate/portal").href,
42
+ children: "Open affiliate portal"
43
+ }
44
+ )
45
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("form", { className: "affiliate-stack", onSubmit: save, children: /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { disabled: action.pending, children: [
46
+ /* @__PURE__ */ jsxRuntime.jsx(
47
+ shared.ProfileFields,
48
+ {
49
+ defaults: { name: identity?.name, email: identity?.email }
50
+ }
51
+ ),
52
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "affiliate-primary", type: "submit", children: action.pending ? "Submitting\u2026" : "Submit application" })
53
+ ] }) })
54
+ ] })
55
+ }
56
+ );
57
+ }
58
+
59
+ exports.AffiliateApplyPage = AffiliateApplyPage;
@@ -0,0 +1,57 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { useState } from 'react';
4
+ import { useIdentity, usePluginSiteNavigation, usePluginOverrides } from '@btst/stack/context';
5
+ import { useApplyAffiliate } from '../../hooks/index.mjs';
6
+ import { AFFILIATE_PLUGIN_ID } from '../../constants.mjs';
7
+ import { useAffiliateAction, AffiliatePage, ActionNotice, ProfileFields, readProfile } from '../shared.mjs';
8
+
9
+ function AffiliateApplyPage() {
10
+ const { identity } = useIdentity();
11
+ const [applied, setApplied] = useState(false);
12
+ const apply = useApplyAffiliate();
13
+ const action = useAffiliateAction();
14
+ const { Link, resolve } = usePluginSiteNavigation(AFFILIATE_PLUGIN_ID);
15
+ const { localization } = usePluginOverrides(AFFILIATE_PLUGIN_ID);
16
+ const save = (event) => {
17
+ event.preventDefault();
18
+ const fields = new FormData(event.currentTarget);
19
+ void action.run(async () => {
20
+ await apply.mutateAsync(readProfile(fields));
21
+ setApplied(true);
22
+ }, "Application submitted");
23
+ };
24
+ return /* @__PURE__ */ jsx(
25
+ AffiliatePage,
26
+ {
27
+ title: localization?.apply ?? "Become an affiliate",
28
+ description: "Apply for a referral code and earn commissions on attributed conversions after approval.",
29
+ admin: false,
30
+ children: /* @__PURE__ */ jsxs("div", { className: "affiliate-application", children: [
31
+ /* @__PURE__ */ jsx(ActionNotice, { ...action }),
32
+ applied ? /* @__PURE__ */ jsxs("section", { className: "affiliate-stack", children: [
33
+ /* @__PURE__ */ jsx("h2", { children: "Your application is pending review" }),
34
+ /* @__PURE__ */ jsx("p", { children: "You can view your status and update your payout details in the affiliate portal." }),
35
+ /* @__PURE__ */ jsx(
36
+ Link,
37
+ {
38
+ className: "affiliate-primary",
39
+ href: resolve("affiliate/portal").href,
40
+ children: "Open affiliate portal"
41
+ }
42
+ )
43
+ ] }) : /* @__PURE__ */ jsx("form", { className: "affiliate-stack", onSubmit: save, children: /* @__PURE__ */ jsxs("fieldset", { disabled: action.pending, children: [
44
+ /* @__PURE__ */ jsx(
45
+ ProfileFields,
46
+ {
47
+ defaults: { name: identity?.name, email: identity?.email }
48
+ }
49
+ ),
50
+ /* @__PURE__ */ jsx("button", { className: "affiliate-primary", type: "submit", children: action.pending ? "Submitting\u2026" : "Submit application" })
51
+ ] }) })
52
+ ] })
53
+ }
54
+ );
55
+ }
56
+
57
+ export { AffiliateApplyPage };
@@ -0,0 +1,112 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ const jsxRuntime = require('react/jsx-runtime');
5
+ const context = require('@btst/stack/context');
6
+ const permissions = require('../../../permissions.cjs');
7
+ const client_hooks_index = require('../../hooks/index.cjs');
8
+ const constants = require('../../constants.cjs');
9
+ const shared = require('../shared.cjs');
10
+
11
+ function AffiliateDetailPage({ id }) {
12
+ const { data } = client_hooks_index.useAffiliate(id);
13
+ const { affiliate, stats } = data;
14
+ const update = client_hooks_index.useUpdateAffiliate();
15
+ const remove = client_hooks_index.useDeleteAffiliate();
16
+ const action = shared.useAffiliateAction();
17
+ const { Link, resolve, navigate } = context.usePluginSiteNavigation(constants.AFFILIATE_PLUGIN_ID);
18
+ const facts = {
19
+ affiliateId: affiliate.id,
20
+ userId: affiliate.userId,
21
+ status: affiliate.status
22
+ };
23
+ const save = (event) => {
24
+ event.preventDefault();
25
+ const fields = new FormData(event.currentTarget);
26
+ const commissionType = String(fields.get("commissionType") ?? "");
27
+ const commissionValue = String(fields.get("commissionValue") ?? "");
28
+ void action.run(async () => {
29
+ if (commissionType && !commissionValue.trim())
30
+ throw new Error(
31
+ "Enter a commission value or select the program default."
32
+ );
33
+ await update.mutateAsync({
34
+ id,
35
+ data: {
36
+ ...shared.readProfile(fields),
37
+ status: String(fields.get("status")),
38
+ notes: String(fields.get("notes") ?? ""),
39
+ commissionType: commissionType ? commissionType : null,
40
+ commissionValue: commissionType ? Number(commissionValue) : null
41
+ }
42
+ });
43
+ }, "Affiliate profile saved");
44
+ };
45
+ return /* @__PURE__ */ jsxRuntime.jsxs(
46
+ shared.AffiliatePage,
47
+ {
48
+ title: affiliate.name,
49
+ description: affiliate.email,
50
+ action: /* @__PURE__ */ jsxRuntime.jsx(shared.Status, { value: affiliate.status }),
51
+ children: [
52
+ /* @__PURE__ */ jsxRuntime.jsx(Link, { href: resolve("affiliate/admin/affiliates").href, children: "Back to affiliates" }),
53
+ /* @__PURE__ */ jsxRuntime.jsx(shared.Stats, { stats }),
54
+ /* @__PURE__ */ jsxRuntime.jsx(shared.ActionNotice, { ...action }),
55
+ /* @__PURE__ */ jsxRuntime.jsx(
56
+ context.PermissionAccess,
57
+ {
58
+ permission: permissions.affiliatePermissions.affiliate.update(facts),
59
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
60
+ "form",
61
+ {
62
+ className: "affiliate-stack",
63
+ onSubmit: save,
64
+ children: [
65
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Profile and commission rule" }),
66
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "affiliate-small affiliate-muted", children: [
67
+ "Account: ",
68
+ affiliate.userId
69
+ ] }),
70
+ /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { disabled: action.pending, children: [
71
+ /* @__PURE__ */ jsxRuntime.jsx(shared.ProfileFields, { affiliate, admin: true }),
72
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "affiliate-actions", children: /* @__PURE__ */ jsxRuntime.jsx("button", { className: "affiliate-primary", type: "submit", children: action.pending ? "Saving\u2026" : "Save profile" }) })
73
+ ] })
74
+ ]
75
+ },
76
+ affiliate.updatedAt
77
+ )
78
+ }
79
+ ),
80
+ /* @__PURE__ */ jsxRuntime.jsx(
81
+ context.PermissionAccess,
82
+ {
83
+ permission: permissions.affiliatePermissions.affiliate.delete(facts),
84
+ children: /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "affiliate-section affiliate-stack", children: [
85
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Remove affiliate" }),
86
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "affiliate-muted", children: "Only affiliates without conversions, commissions, or payouts can be deleted. Suspend a partner to stop new referrals and preserve their history." }),
87
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(
88
+ "button",
89
+ {
90
+ type: "button",
91
+ disabled: action.pending,
92
+ onClick: () => {
93
+ if (window.confirm(
94
+ `Delete ${affiliate.name}? This removes their affiliate profile.`
95
+ ))
96
+ void action.run(async () => {
97
+ await remove.mutateAsync(id);
98
+ await navigate("affiliate/admin/affiliates");
99
+ });
100
+ },
101
+ children: "Delete affiliate"
102
+ }
103
+ ) })
104
+ ] })
105
+ }
106
+ )
107
+ ]
108
+ }
109
+ );
110
+ }
111
+
112
+ exports.AffiliateDetailPage = AffiliateDetailPage;
@@ -0,0 +1,110 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { usePluginSiteNavigation, PermissionAccess } from '@btst/stack/context';
4
+ import { affiliatePermissions } from '../../../permissions.mjs';
5
+ import { useAffiliate, useUpdateAffiliate, useDeleteAffiliate } from '../../hooks/index.mjs';
6
+ import { AFFILIATE_PLUGIN_ID } from '../../constants.mjs';
7
+ import { useAffiliateAction, AffiliatePage, Stats, ActionNotice, ProfileFields, Status, readProfile } from '../shared.mjs';
8
+
9
+ function AffiliateDetailPage({ id }) {
10
+ const { data } = useAffiliate(id);
11
+ const { affiliate, stats } = data;
12
+ const update = useUpdateAffiliate();
13
+ const remove = useDeleteAffiliate();
14
+ const action = useAffiliateAction();
15
+ const { Link, resolve, navigate } = usePluginSiteNavigation(AFFILIATE_PLUGIN_ID);
16
+ const facts = {
17
+ affiliateId: affiliate.id,
18
+ userId: affiliate.userId,
19
+ status: affiliate.status
20
+ };
21
+ const save = (event) => {
22
+ event.preventDefault();
23
+ const fields = new FormData(event.currentTarget);
24
+ const commissionType = String(fields.get("commissionType") ?? "");
25
+ const commissionValue = String(fields.get("commissionValue") ?? "");
26
+ void action.run(async () => {
27
+ if (commissionType && !commissionValue.trim())
28
+ throw new Error(
29
+ "Enter a commission value or select the program default."
30
+ );
31
+ await update.mutateAsync({
32
+ id,
33
+ data: {
34
+ ...readProfile(fields),
35
+ status: String(fields.get("status")),
36
+ notes: String(fields.get("notes") ?? ""),
37
+ commissionType: commissionType ? commissionType : null,
38
+ commissionValue: commissionType ? Number(commissionValue) : null
39
+ }
40
+ });
41
+ }, "Affiliate profile saved");
42
+ };
43
+ return /* @__PURE__ */ jsxs(
44
+ AffiliatePage,
45
+ {
46
+ title: affiliate.name,
47
+ description: affiliate.email,
48
+ action: /* @__PURE__ */ jsx(Status, { value: affiliate.status }),
49
+ children: [
50
+ /* @__PURE__ */ jsx(Link, { href: resolve("affiliate/admin/affiliates").href, children: "Back to affiliates" }),
51
+ /* @__PURE__ */ jsx(Stats, { stats }),
52
+ /* @__PURE__ */ jsx(ActionNotice, { ...action }),
53
+ /* @__PURE__ */ jsx(
54
+ PermissionAccess,
55
+ {
56
+ permission: affiliatePermissions.affiliate.update(facts),
57
+ children: /* @__PURE__ */ jsxs(
58
+ "form",
59
+ {
60
+ className: "affiliate-stack",
61
+ onSubmit: save,
62
+ children: [
63
+ /* @__PURE__ */ jsx("h2", { children: "Profile and commission rule" }),
64
+ /* @__PURE__ */ jsxs("p", { className: "affiliate-small affiliate-muted", children: [
65
+ "Account: ",
66
+ affiliate.userId
67
+ ] }),
68
+ /* @__PURE__ */ jsxs("fieldset", { disabled: action.pending, children: [
69
+ /* @__PURE__ */ jsx(ProfileFields, { affiliate, admin: true }),
70
+ /* @__PURE__ */ jsx("div", { className: "affiliate-actions", children: /* @__PURE__ */ jsx("button", { className: "affiliate-primary", type: "submit", children: action.pending ? "Saving\u2026" : "Save profile" }) })
71
+ ] })
72
+ ]
73
+ },
74
+ affiliate.updatedAt
75
+ )
76
+ }
77
+ ),
78
+ /* @__PURE__ */ jsx(
79
+ PermissionAccess,
80
+ {
81
+ permission: affiliatePermissions.affiliate.delete(facts),
82
+ children: /* @__PURE__ */ jsxs("section", { className: "affiliate-section affiliate-stack", children: [
83
+ /* @__PURE__ */ jsx("h2", { children: "Remove affiliate" }),
84
+ /* @__PURE__ */ jsx("p", { className: "affiliate-muted", children: "Only affiliates without conversions, commissions, or payouts can be deleted. Suspend a partner to stop new referrals and preserve their history." }),
85
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
86
+ "button",
87
+ {
88
+ type: "button",
89
+ disabled: action.pending,
90
+ onClick: () => {
91
+ if (window.confirm(
92
+ `Delete ${affiliate.name}? This removes their affiliate profile.`
93
+ ))
94
+ void action.run(async () => {
95
+ await remove.mutateAsync(id);
96
+ await navigate("affiliate/admin/affiliates");
97
+ });
98
+ },
99
+ children: "Delete affiliate"
100
+ }
101
+ ) })
102
+ ] })
103
+ }
104
+ )
105
+ ]
106
+ }
107
+ );
108
+ }
109
+
110
+ export { AffiliateDetailPage };