@tryvitris/website-admin-contracts 0.1.0-alpha.2 → 1.1.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 +21 -21
- package/README.md +3 -3
- package/dist/billing.d.ts +117 -0
- package/dist/billing.js +7 -0
- package/dist/client-onboarding.d.ts +69 -0
- package/dist/client-onboarding.js +35 -0
- package/dist/client-portfolio.d.ts +64 -0
- package/dist/client-portfolio.js +9 -0
- package/dist/index.d.ts +72 -1
- package/dist/index.js +24 -3
- package/dist/stripe-billing.d.ts +103 -0
- package/dist/stripe-billing.js +0 -0
- package/dist/webmaster.d.ts +25 -0
- package/dist/webmaster.js +9 -0
- package/package.json +2 -2
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Vitris contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vitris contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
# Website Admin Contracts
|
|
2
|
-
|
|
3
|
-
Validated installation manifests, roles, permissions and module contracts for `@tryvitris/website-admin-harness`. Node.js 24+. MIT licensed.
|
|
1
|
+
# Website Admin Contracts
|
|
2
|
+
|
|
3
|
+
Validated installation manifests, roles, permissions and module contracts for `@tryvitris/website-admin-harness`. Node.js 24+. MIT licensed.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/** Local preview contracts. These are not a production Stripe integration. */
|
|
2
|
+
export declare const billingPermissions: readonly ["billing:read", "billing:customers:write", "billing:catalog:read", "billing:subscriptions:write", "billing:invoices:write", "billing:collect", "billing:connection:manage", "account-billing:read", "account-billing:manage"];
|
|
3
|
+
export interface BillingCustomer {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
contact: string;
|
|
7
|
+
email: string;
|
|
8
|
+
initials: string;
|
|
9
|
+
color: string;
|
|
10
|
+
websites: string[];
|
|
11
|
+
note: string;
|
|
12
|
+
}
|
|
13
|
+
export interface BillingPlan {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
amount: number;
|
|
18
|
+
interval: 'month' | 'year';
|
|
19
|
+
features: string[];
|
|
20
|
+
selfService: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface BillingSubscription {
|
|
23
|
+
id: string;
|
|
24
|
+
customerId: string;
|
|
25
|
+
planId: string;
|
|
26
|
+
status: 'active' | 'past_due' | 'trialing' | 'canceled';
|
|
27
|
+
renewal: string;
|
|
28
|
+
cancelAtPeriodEnd: boolean;
|
|
29
|
+
scheduledPlanId: string | null;
|
|
30
|
+
}
|
|
31
|
+
export interface BillingLine {
|
|
32
|
+
description: string;
|
|
33
|
+
quantity: number;
|
|
34
|
+
unitAmount: number;
|
|
35
|
+
}
|
|
36
|
+
export interface BillingInvoice {
|
|
37
|
+
id: string;
|
|
38
|
+
customerId: string;
|
|
39
|
+
number: string;
|
|
40
|
+
description: string;
|
|
41
|
+
lines: BillingLine[];
|
|
42
|
+
status: 'draft' | 'open' | 'paid' | 'void';
|
|
43
|
+
issued: string;
|
|
44
|
+
due: string;
|
|
45
|
+
paidAt: string | null;
|
|
46
|
+
kind: 'subscription' | 'one_off';
|
|
47
|
+
}
|
|
48
|
+
export interface BillingPayment {
|
|
49
|
+
id: string;
|
|
50
|
+
customerId: string;
|
|
51
|
+
invoiceId: string;
|
|
52
|
+
amount: number;
|
|
53
|
+
status: 'succeeded' | 'failed' | 'refunded';
|
|
54
|
+
date: string;
|
|
55
|
+
}
|
|
56
|
+
export interface BillingActivity {
|
|
57
|
+
id: string;
|
|
58
|
+
customerId: string | null;
|
|
59
|
+
text: string;
|
|
60
|
+
at: string;
|
|
61
|
+
}
|
|
62
|
+
export interface BillingState {
|
|
63
|
+
customers: BillingCustomer[];
|
|
64
|
+
plans: BillingPlan[];
|
|
65
|
+
subscriptions: BillingSubscription[];
|
|
66
|
+
invoices: BillingInvoice[];
|
|
67
|
+
payments: BillingPayment[];
|
|
68
|
+
activity: BillingActivity[];
|
|
69
|
+
connection: boolean;
|
|
70
|
+
card: {
|
|
71
|
+
brand: string;
|
|
72
|
+
last4: string;
|
|
73
|
+
expiry: string;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export interface BillingResponse extends BillingState {
|
|
77
|
+
revision: number;
|
|
78
|
+
simulated: true;
|
|
79
|
+
audience: 'seller' | 'customer';
|
|
80
|
+
customerId: string | null;
|
|
81
|
+
supplier: string;
|
|
82
|
+
asOf: string;
|
|
83
|
+
currency: 'USD';
|
|
84
|
+
}
|
|
85
|
+
export type BillingCommand = {
|
|
86
|
+
action: 'plan';
|
|
87
|
+
subscriptionId: string;
|
|
88
|
+
planId: string;
|
|
89
|
+
timing: 'renewal' | 'now';
|
|
90
|
+
reviewed: boolean;
|
|
91
|
+
} | {
|
|
92
|
+
action: 'cancel' | 'resume';
|
|
93
|
+
subscriptionId: string;
|
|
94
|
+
reviewed: boolean;
|
|
95
|
+
} | {
|
|
96
|
+
action: 'draft';
|
|
97
|
+
invoiceId?: string;
|
|
98
|
+
customerId: string;
|
|
99
|
+
description: string;
|
|
100
|
+
due: string;
|
|
101
|
+
lines: BillingLine[];
|
|
102
|
+
} | {
|
|
103
|
+
action: 'send' | 'void' | 'pay';
|
|
104
|
+
invoiceId: string;
|
|
105
|
+
reviewed: boolean;
|
|
106
|
+
} | {
|
|
107
|
+
action: 'card';
|
|
108
|
+
reviewed: boolean;
|
|
109
|
+
} | {
|
|
110
|
+
action: 'connection';
|
|
111
|
+
connected: boolean;
|
|
112
|
+
};
|
|
113
|
+
export type BillingMutation = BillingCommand & {
|
|
114
|
+
revision: number;
|
|
115
|
+
idempotencyKey: string;
|
|
116
|
+
};
|
|
117
|
+
export declare const invoiceTotal: (invoice: Pick<BillingInvoice, "lines">) => number;
|
package/dist/billing.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// packages/contracts/src/billing.ts
|
|
2
|
+
var billingPermissions = ["billing:read", "billing:customers:write", "billing:catalog:read", "billing:subscriptions:write", "billing:invoices:write", "billing:collect", "billing:connection:manage", "account-billing:read", "account-billing:manage"];
|
|
3
|
+
var invoiceTotal = (invoice) => invoice.lines.reduce((sum, line) => sum + line.quantity * line.unitAmount, 0);
|
|
4
|
+
export {
|
|
5
|
+
billingPermissions,
|
|
6
|
+
invoiceTotal
|
|
7
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export declare const clientOnboardingPermissions: readonly ["prospects:read", "prospects:write"];
|
|
2
|
+
export interface OnboardingField {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
required: boolean;
|
|
6
|
+
value: string;
|
|
7
|
+
kind?: 'text' | 'textarea' | 'tel' | 'url';
|
|
8
|
+
section?: 'business' | 'website';
|
|
9
|
+
help?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface OnboardingTemplate {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
businessType: string;
|
|
15
|
+
title: string;
|
|
16
|
+
intro: string;
|
|
17
|
+
message: string;
|
|
18
|
+
fields: OnboardingField[];
|
|
19
|
+
requiresPayment: boolean;
|
|
20
|
+
revision: number;
|
|
21
|
+
updatedAt: string | null;
|
|
22
|
+
source: 'starter' | 'saved';
|
|
23
|
+
}
|
|
24
|
+
export interface OnboardingConfig {
|
|
25
|
+
templateId?: string;
|
|
26
|
+
templateName?: string;
|
|
27
|
+
templateRevision?: number;
|
|
28
|
+
title: string;
|
|
29
|
+
intro: string;
|
|
30
|
+
}
|
|
31
|
+
export interface OnboardingDraft {
|
|
32
|
+
name?: string;
|
|
33
|
+
fields?: OnboardingField[];
|
|
34
|
+
step?: 'business' | 'website' | 'account';
|
|
35
|
+
savedAt?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface Prospect {
|
|
38
|
+
id: string;
|
|
39
|
+
business: string;
|
|
40
|
+
name: string;
|
|
41
|
+
email: string;
|
|
42
|
+
previewUrl: string;
|
|
43
|
+
message: string;
|
|
44
|
+
status: 'draft' | 'invited' | 'expired' | 'active' | 'revoked';
|
|
45
|
+
createdAt: string;
|
|
46
|
+
invitedAt: string | null;
|
|
47
|
+
expiresAt: string | null;
|
|
48
|
+
activatedAt: string | null;
|
|
49
|
+
feedback: string;
|
|
50
|
+
feedbackAt: string | null;
|
|
51
|
+
fields: OnboardingField[];
|
|
52
|
+
requiresPayment: boolean;
|
|
53
|
+
paymentStatus: 'not_requested' | 'pending' | 'demo_complete';
|
|
54
|
+
onboarding?: OnboardingConfig;
|
|
55
|
+
}
|
|
56
|
+
export interface ProspectWorkspace {
|
|
57
|
+
rows: Prospect[];
|
|
58
|
+
templates: OnboardingTemplate[];
|
|
59
|
+
localOnly: true;
|
|
60
|
+
delivery: 'copy-link';
|
|
61
|
+
}
|
|
62
|
+
export type ProspectPortal = Pick<Prospect, 'business' | 'name' | 'email' | 'previewUrl' | 'message' | 'feedback' | 'feedbackAt' | 'fields' | 'requiresPayment' | 'paymentStatus'> & {
|
|
63
|
+
onboarding?: Pick<OnboardingConfig, 'title' | 'intro'>;
|
|
64
|
+
draft?: OnboardingDraft;
|
|
65
|
+
inviteId?: string;
|
|
66
|
+
};
|
|
67
|
+
export declare function normalizeOnboardingFields(value: unknown): OnboardingField[] | null;
|
|
68
|
+
export declare function onboardingFieldError(field: OnboardingField): string | null;
|
|
69
|
+
export declare function onboardingAnswersMatch(answers: OnboardingField[], original: OnboardingField[], complete?: boolean): boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// packages/contracts/src/client-onboarding.ts
|
|
2
|
+
var clientOnboardingPermissions = ["prospects:read", "prospects:write"];
|
|
3
|
+
function normalizeOnboardingFields(value) {
|
|
4
|
+
if (!Array.isArray(value) || value.length > 10) return null;
|
|
5
|
+
const result = [];
|
|
6
|
+
for (const item of value) {
|
|
7
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return null;
|
|
8
|
+
const f = item;
|
|
9
|
+
if (typeof f.id !== "string" || !/^[\w-]{1,60}$/.test(f.id) || result.some((v) => v.id === f.id) || typeof f.label !== "string" || !f.label.trim() || f.label.trim().length > 80 || typeof f.required !== "boolean" || typeof f.value !== "string" || f.value.trim().length > 1e3 || f.kind !== void 0 && !["text", "textarea", "tel", "url"].includes(String(f.kind)) || f.section !== void 0 && !["business", "website"].includes(String(f.section)) || f.help !== void 0 && (typeof f.help !== "string" || f.help.length > 180)) return null;
|
|
10
|
+
result.push({ id: f.id, label: f.label.trim(), required: f.required, value: f.value.trim(), ...f.kind ? { kind: f.kind } : {}, ...f.section ? { section: f.section } : {}, ...f.help ? { help: String(f.help).trim() } : {} });
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
function onboardingFieldError(field) {
|
|
15
|
+
if (!field.value.trim()) return field.required ? "Please complete this field." : null;
|
|
16
|
+
if (field.kind === "tel" && field.value.replace(/[^0-9]/g, "").length < 5) return "Enter a phone number.";
|
|
17
|
+
if (field.kind === "url") {
|
|
18
|
+
try {
|
|
19
|
+
const u = new URL(field.value);
|
|
20
|
+
if (!["http:", "https:"].includes(u.protocol) || !u.hostname.includes(".") || u.username || u.password) return "Enter a complete website URL.";
|
|
21
|
+
} catch {
|
|
22
|
+
return "Enter a complete website URL, including https://.";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
function onboardingAnswersMatch(answers, original, complete = false) {
|
|
28
|
+
return answers.length === original.length && original.every((f) => answers.some((a) => a.id === f.id && a.label === f.label && a.required === f.required && (a.kind || "text") === (f.kind || "text") && (a.section || "business") === (f.section || "business") && (a.help || "") === (f.help || "") && (!complete || !onboardingFieldError(a))));
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
clientOnboardingPermissions,
|
|
32
|
+
normalizeOnboardingFields,
|
|
33
|
+
onboardingAnswersMatch,
|
|
34
|
+
onboardingFieldError
|
|
35
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { AnalyticsRow, AnalyticsResponse } from './index.ts';
|
|
2
|
+
export declare const clientPortfolioPermissions: readonly ["clients:read", "client-changes:request", "client-releases:publish"];
|
|
3
|
+
export type PortfolioStatus = 'requested' | 'in_progress' | 'preview_ready' | 'queued' | 'published' | 'failed' | 'canceled';
|
|
4
|
+
export interface PortfolioClient {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
contact: string;
|
|
8
|
+
sector: string;
|
|
9
|
+
}
|
|
10
|
+
export interface PortfolioWebsite {
|
|
11
|
+
id: string;
|
|
12
|
+
clientId: string;
|
|
13
|
+
name: string;
|
|
14
|
+
origin: string;
|
|
15
|
+
initials: string;
|
|
16
|
+
color: string;
|
|
17
|
+
connection: 'healthy' | 'unavailable' | 'not_configured';
|
|
18
|
+
lastSuccess: string | null;
|
|
19
|
+
rows: AnalyticsRow[];
|
|
20
|
+
previousRows: AnalyticsRow[];
|
|
21
|
+
source?: 'synthetic' | 'connected' | 'not-connected';
|
|
22
|
+
message?: string;
|
|
23
|
+
windows?: Partial<Record<'14' | '30', AnalyticsResponse>>;
|
|
24
|
+
/** Verified collector activation, independent of padded/reporting-window rows. */
|
|
25
|
+
trackingStartedAt?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface PortfolioChange {
|
|
28
|
+
id: string;
|
|
29
|
+
websiteId: string;
|
|
30
|
+
title: string;
|
|
31
|
+
request: string;
|
|
32
|
+
status: PortfolioStatus;
|
|
33
|
+
candidate: string | null;
|
|
34
|
+
createdAt: string;
|
|
35
|
+
updatedAt: string;
|
|
36
|
+
idempotencyKey: string;
|
|
37
|
+
history: {
|
|
38
|
+
label: string;
|
|
39
|
+
at: string;
|
|
40
|
+
}[];
|
|
41
|
+
checks: {
|
|
42
|
+
label: string;
|
|
43
|
+
result: 'simulated' | 'failed';
|
|
44
|
+
}[];
|
|
45
|
+
receipt: {
|
|
46
|
+
id: string;
|
|
47
|
+
candidate: string;
|
|
48
|
+
publishedAt: string;
|
|
49
|
+
simulated: true;
|
|
50
|
+
} | null;
|
|
51
|
+
}
|
|
52
|
+
export interface PortfolioState {
|
|
53
|
+
clients: PortfolioClient[];
|
|
54
|
+
websites: PortfolioWebsite[];
|
|
55
|
+
changes: PortfolioChange[];
|
|
56
|
+
runnerOnline: boolean;
|
|
57
|
+
}
|
|
58
|
+
export interface PortfolioResponse extends PortfolioState {
|
|
59
|
+
revision: number;
|
|
60
|
+
simulated: true;
|
|
61
|
+
analyticsMode?: 'live';
|
|
62
|
+
}
|
|
63
|
+
export type PortfolioAction = 'start' | 'prepare' | 'fail' | 'retry' | 'cancel' | 'queue' | 'publish';
|
|
64
|
+
export declare function isTrackingTimestamp(value: unknown): value is string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// packages/contracts/src/client-portfolio.ts
|
|
2
|
+
var clientPortfolioPermissions = ["clients:read", "client-changes:request", "client-releases:publish"];
|
|
3
|
+
function isTrackingTimestamp(value) {
|
|
4
|
+
return typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value) && Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value;
|
|
5
|
+
}
|
|
6
|
+
export {
|
|
7
|
+
clientPortfolioPermissions,
|
|
8
|
+
isTrackingTimestamp
|
|
9
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
export * from './billing.js';
|
|
2
|
+
export * from './stripe-billing.js';
|
|
3
|
+
export * from './client-portfolio.js';
|
|
4
|
+
export * from './client-onboarding.js';
|
|
1
5
|
export * from './scheduling.js';
|
|
6
|
+
export * from './webmaster.js';
|
|
2
7
|
export declare const contractVersion = 1;
|
|
3
8
|
export declare const coreSections: readonly ["overview", "leads", "analytics", "settings", "access", "activity"];
|
|
4
|
-
export declare const permissions: readonly ["leads:read", "leads:write", "analytics:read", "settings:write", "access:read", "activity:read", "tasks:read", "tasks:write", "appointments:read", "appointments:write", "availability:write", "scheduling:configure", "calendar-connections:manage"];
|
|
9
|
+
export declare const permissions: readonly ["leads:read", "leads:write", "analytics:read", "settings:write", "access:read", "access:write", "activity:read", "tasks:read", "tasks:write", "appointments:read", "appointments:write", "availability:write", "scheduling:configure", "calendar-connections:manage", "clients:read", "client-changes:request", "client-releases:publish", "prospects:read", "prospects:write", "billing:read", "billing:customers:write", "billing:catalog:read", "billing:subscriptions:write", "billing:invoices:write", "billing:collect", "billing:connection:manage", "account-billing:read", "account-billing:manage"];
|
|
5
10
|
export type Permission = typeof permissions[number];
|
|
6
11
|
export type Role = 'owner' | 'administrator' | 'editor' | 'analyst';
|
|
7
12
|
export declare const roles: Record<Role, readonly Permission[]>;
|
|
@@ -32,6 +37,7 @@ export type WebsiteTheme = Partial<Record<typeof websiteColorKeys[number], strin
|
|
|
32
37
|
export declare function isThemeColor(value: unknown): value is string;
|
|
33
38
|
export declare function isWebsiteTheme(value: unknown): value is WebsiteTheme;
|
|
34
39
|
export interface ModuleDefinition {
|
|
40
|
+
ui?: 'stripe-billing' | 'stripe-account';
|
|
35
41
|
id: string;
|
|
36
42
|
version: number;
|
|
37
43
|
label: string;
|
|
@@ -42,7 +48,14 @@ export interface User {
|
|
|
42
48
|
id: string;
|
|
43
49
|
email: string;
|
|
44
50
|
role: Role;
|
|
51
|
+
webmaster?: {
|
|
52
|
+
organizationId: string;
|
|
53
|
+
organizationName: string;
|
|
54
|
+
permissions: Permission[];
|
|
55
|
+
revision: number;
|
|
56
|
+
};
|
|
45
57
|
}
|
|
58
|
+
export declare function userCan(user: User, permission: Permission): boolean;
|
|
46
59
|
export interface BootstrapResponse {
|
|
47
60
|
manifest: AdminManifest;
|
|
48
61
|
user: User | null;
|
|
@@ -53,6 +66,10 @@ export interface BootstrapResponse {
|
|
|
53
66
|
capabilities: string[];
|
|
54
67
|
theme?: WebsiteTheme | null;
|
|
55
68
|
themeStatus?: 'website' | 'installation' | 'unavailable' | 'default';
|
|
69
|
+
accountAuth?: AccountCapabilities;
|
|
70
|
+
emailLoginConfigured?: boolean;
|
|
71
|
+
privateLocalSignIn?: boolean;
|
|
72
|
+
webmasterEnabled?: boolean;
|
|
56
73
|
}
|
|
57
74
|
export type DataSource = 'synthetic' | 'connected' | 'not-connected';
|
|
58
75
|
export interface LeadRow {
|
|
@@ -68,7 +85,28 @@ export interface LeadRow {
|
|
|
68
85
|
notification_policy?: string | null;
|
|
69
86
|
notification_status?: string | null;
|
|
70
87
|
notification_error?: string | null;
|
|
88
|
+
followUp?: LeadFollowUp;
|
|
89
|
+
}
|
|
90
|
+
export interface LeadFollowUp {
|
|
91
|
+
assignedTo: string | null;
|
|
92
|
+
dueDate: string | null;
|
|
93
|
+
notes: string;
|
|
94
|
+
completed: boolean;
|
|
95
|
+
revision: number;
|
|
96
|
+
updatedAt: string | null;
|
|
97
|
+
}
|
|
98
|
+
export interface LeadPagination {
|
|
99
|
+
version: 1;
|
|
100
|
+
total: number;
|
|
101
|
+
newCount: number;
|
|
102
|
+
nextCursor: string | null;
|
|
103
|
+
}
|
|
104
|
+
export interface LeadTeamMember {
|
|
105
|
+
id: string;
|
|
106
|
+
email: string;
|
|
71
107
|
}
|
|
108
|
+
export declare function isLeadFollowUp(value: unknown): value is LeadFollowUp;
|
|
109
|
+
export declare function isLeadPagination(value: unknown): value is LeadPagination;
|
|
72
110
|
export declare const analyticsMetricKeys: readonly ["sessions", "bounces", "engaged", "duration_ms", "leave_count", "scroll_25", "scroll_50", "scroll_75", "scroll_100", "converts_form", "converts_tel", "converts_mailto", "converts_maps", "engage_nav", "engage_btn", "engage_img", "engage_carousel"];
|
|
73
111
|
export declare const analyticsBreakdownKeys: readonly ["top_paths_json", "top_refs_json", "channels_json", "devices_json", "hours_json", "top_engage_json"];
|
|
74
112
|
export type AnalyticsMetric = 'views' | 'visitors' | 'enquiries' | typeof analyticsMetricKeys[number];
|
|
@@ -89,6 +127,9 @@ export interface LeadsResponse {
|
|
|
89
127
|
writable?: boolean;
|
|
90
128
|
message?: string;
|
|
91
129
|
mode?: string;
|
|
130
|
+
followUpsEnabled?: boolean;
|
|
131
|
+
pagination?: LeadPagination;
|
|
132
|
+
truncated?: boolean;
|
|
92
133
|
}
|
|
93
134
|
export interface AnalyticsResponse {
|
|
94
135
|
rows: AnalyticsRow[];
|
|
@@ -105,11 +146,41 @@ export interface SettingsResponse {
|
|
|
105
146
|
export interface AccessRow extends User {
|
|
106
147
|
active: number;
|
|
107
148
|
}
|
|
149
|
+
export interface AccountCapabilities {
|
|
150
|
+
enabled: boolean;
|
|
151
|
+
emailReady: boolean;
|
|
152
|
+
setupAvailable: boolean;
|
|
153
|
+
emailVerified: boolean;
|
|
154
|
+
canInvite: boolean;
|
|
155
|
+
}
|
|
156
|
+
export interface AccountAction {
|
|
157
|
+
purpose: 'owner' | 'invite' | 'verify' | 'reset';
|
|
158
|
+
email: string;
|
|
159
|
+
expiresAt: number;
|
|
160
|
+
business: string;
|
|
161
|
+
}
|
|
162
|
+
export interface TeamInvitation {
|
|
163
|
+
id: string;
|
|
164
|
+
email: string;
|
|
165
|
+
role: string;
|
|
166
|
+
expiresAt: number;
|
|
167
|
+
status: 'accepted' | 'canceled' | 'expired' | 'pending';
|
|
168
|
+
delivery: 'pending' | 'sending' | 'sent' | 'failed' | 'superseded';
|
|
169
|
+
}
|
|
170
|
+
export interface AccessResponse extends RowsResponse<AccessRow & {
|
|
171
|
+
email_verified_at?: number | null;
|
|
172
|
+
}> {
|
|
173
|
+
invitations?: TeamInvitation[];
|
|
174
|
+
accountAuth?: AccountCapabilities;
|
|
175
|
+
}
|
|
108
176
|
export interface ActivityRow {
|
|
109
177
|
id: string;
|
|
110
178
|
actor: string;
|
|
111
179
|
action: string;
|
|
112
180
|
created_at: string;
|
|
181
|
+
person_id?: string | null;
|
|
182
|
+
organization_id?: string | null;
|
|
183
|
+
organization_name?: string | null;
|
|
113
184
|
}
|
|
114
185
|
export interface TaskRow {
|
|
115
186
|
id: string;
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { schedulingPermissions } from "./scheduling.js";
|
|
2
|
+
import { clientPortfolioPermissions } from "./client-portfolio.js";
|
|
3
|
+
import { clientOnboardingPermissions } from "./client-onboarding.js";
|
|
4
|
+
import { billingPermissions } from "./billing.js";
|
|
5
|
+
export * from "./billing.js";
|
|
6
|
+
export * from "./stripe-billing.js";
|
|
7
|
+
export * from "./client-portfolio.js";
|
|
8
|
+
export * from "./client-onboarding.js";
|
|
2
9
|
export * from "./scheduling.js";
|
|
10
|
+
export * from "./webmaster.js";
|
|
3
11
|
const contractVersion = 1;
|
|
4
12
|
const coreSections = ["overview", "leads", "analytics", "settings", "access", "activity"];
|
|
5
|
-
const permissions = ["leads:read", "leads:write", "analytics:read", "settings:write", "access:read", "activity:read", "tasks:read", "tasks:write", ...schedulingPermissions];
|
|
13
|
+
const permissions = ["leads:read", "leads:write", "analytics:read", "settings:write", "access:read", "access:write", "activity:read", "tasks:read", "tasks:write", ...schedulingPermissions, ...clientPortfolioPermissions, ...clientOnboardingPermissions, ...billingPermissions];
|
|
6
14
|
const roles = {
|
|
7
15
|
owner: permissions,
|
|
8
|
-
administrator: permissions,
|
|
16
|
+
administrator: permissions.filter((p) => p !== "access:write" && !clientPortfolioPermissions.includes(p) && !clientOnboardingPermissions.includes(p) && !billingPermissions.includes(p)),
|
|
9
17
|
editor: ["leads:read", "leads:write", "activity:read", "tasks:read", "tasks:write"],
|
|
10
18
|
analyst: ["analytics:read", "activity:read"]
|
|
11
19
|
};
|
|
@@ -27,6 +35,16 @@ function isThemeColor(value) {
|
|
|
27
35
|
function isWebsiteTheme(value) {
|
|
28
36
|
return isObject(value) && Object.keys(value).length > 0 && Object.entries(value).every(([key, color]) => websiteColorKeys.includes(key) && isThemeColor(color));
|
|
29
37
|
}
|
|
38
|
+
function userCan(user, permission) {
|
|
39
|
+
return user.webmaster ? user.webmaster.permissions.includes(permission) : can(user.role, permission);
|
|
40
|
+
}
|
|
41
|
+
function isLeadFollowUp(value) {
|
|
42
|
+
if (!isObject(value)) return false;
|
|
43
|
+
return (value.assignedTo === null || typeof value.assignedTo === "string" && value.assignedTo.length > 0 && value.assignedTo.length <= 100) && (value.dueDate === null || typeof value.dueDate === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value.dueDate) && Number.isFinite(Date.parse(value.dueDate + "T00:00:00Z")) && (/* @__PURE__ */ new Date(value.dueDate + "T00:00:00Z")).toISOString().slice(0, 10) === value.dueDate) && typeof value.notes === "string" && value.notes.length <= 3e3 && typeof value.completed === "boolean" && Number.isSafeInteger(value.revision) && Number(value.revision) >= 0 && (value.updatedAt === null || typeof value.updatedAt === "string");
|
|
44
|
+
}
|
|
45
|
+
function isLeadPagination(value) {
|
|
46
|
+
return isObject(value) && value.version === 1 && [value.total, value.newCount].every((n) => Number.isSafeInteger(n) && Number(n) >= 0) && (value.nextCursor === null || typeof value.nextCursor === "string" && value.nextCursor.length <= 4096);
|
|
47
|
+
}
|
|
30
48
|
const analyticsMetricKeys = ["sessions", "bounces", "engaged", "duration_ms", "leave_count", "scroll_25", "scroll_50", "scroll_75", "scroll_100", "converts_form", "converts_tel", "converts_mailto", "converts_maps", "engage_nav", "engage_btn", "engage_img", "engage_carousel"];
|
|
31
49
|
const analyticsBreakdownKeys = ["top_paths_json", "top_refs_json", "channels_json", "devices_json", "hours_json", "top_engage_json"];
|
|
32
50
|
function isObject(value) {
|
|
@@ -34,7 +52,7 @@ function isObject(value) {
|
|
|
34
52
|
}
|
|
35
53
|
function isLeadRow(value) {
|
|
36
54
|
if (!isObject(value)) return false;
|
|
37
|
-
return ["id", "name", "email", "message", "status", "created_at"].every((key) => typeof value[key] === "string") && ["source", "phone", "fields_json", "notification_policy", "notification_status", "notification_error"].every((key) => value[key] === void 0 || value[key] === null || typeof value[key] === "string");
|
|
55
|
+
return ["id", "name", "email", "message", "status", "created_at"].every((key) => typeof value[key] === "string") && ["source", "phone", "fields_json", "notification_policy", "notification_status", "notification_error"].every((key) => value[key] === void 0 || value[key] === null || typeof value[key] === "string") && (value.followUp === void 0 || isLeadFollowUp(value.followUp));
|
|
38
56
|
}
|
|
39
57
|
function isAnalyticsRow(value) {
|
|
40
58
|
if (!isObject(value) || typeof value.day !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value.day)) return false;
|
|
@@ -128,6 +146,8 @@ export {
|
|
|
128
146
|
contractVersion,
|
|
129
147
|
coreSections,
|
|
130
148
|
isAnalyticsRow,
|
|
149
|
+
isLeadFollowUp,
|
|
150
|
+
isLeadPagination,
|
|
131
151
|
isLeadRow,
|
|
132
152
|
isLoopback,
|
|
133
153
|
isObject,
|
|
@@ -136,6 +156,7 @@ export {
|
|
|
136
156
|
parseManifest,
|
|
137
157
|
permissions,
|
|
138
158
|
roles,
|
|
159
|
+
userCan,
|
|
139
160
|
validateModules,
|
|
140
161
|
websiteColorKeys,
|
|
141
162
|
workQueueModule
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/** Installation choices, supplied by trusted source rather than browser input. */
|
|
2
|
+
export interface StripeBillingConfig {
|
|
3
|
+
accountId: string;
|
|
4
|
+
mode: 'test' | 'live';
|
|
5
|
+
currency: string;
|
|
6
|
+
prices: string[];
|
|
7
|
+
portalConfigurationId?: string;
|
|
8
|
+
allowLiveWrites?: boolean;
|
|
9
|
+
grants?: BillingAccountGrant[];
|
|
10
|
+
}
|
|
11
|
+
export interface BillingAccountGrant {
|
|
12
|
+
id: string;
|
|
13
|
+
tokenHash: string;
|
|
14
|
+
customerId: string;
|
|
15
|
+
installationId: string;
|
|
16
|
+
siteId: string;
|
|
17
|
+
adminOrigin: string;
|
|
18
|
+
manage: boolean;
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface BillingAccountConfig {
|
|
22
|
+
sellerOrigin: string;
|
|
23
|
+
sellerInstallationId: string;
|
|
24
|
+
sellerSiteId: string;
|
|
25
|
+
grantId: string;
|
|
26
|
+
customerId: string;
|
|
27
|
+
mode: 'test' | 'live';
|
|
28
|
+
}
|
|
29
|
+
export interface StripeCustomer {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
email: string;
|
|
33
|
+
stripeId: string;
|
|
34
|
+
createdAt: number;
|
|
35
|
+
}
|
|
36
|
+
export interface StripePrice {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
amount: number;
|
|
40
|
+
currency: string;
|
|
41
|
+
interval: 'month' | 'year' | null;
|
|
42
|
+
intervalCount: number;
|
|
43
|
+
}
|
|
44
|
+
export interface StripeRecord {
|
|
45
|
+
id: string;
|
|
46
|
+
customerId: string;
|
|
47
|
+
kind: 'invoice' | 'subscription' | 'checkout' | 'payment';
|
|
48
|
+
status: string;
|
|
49
|
+
amount: number;
|
|
50
|
+
paid: number;
|
|
51
|
+
remaining: number;
|
|
52
|
+
currency: string;
|
|
53
|
+
label: string;
|
|
54
|
+
date: number | null;
|
|
55
|
+
cancelAtPeriodEnd: boolean;
|
|
56
|
+
cancelAt?: number | null;
|
|
57
|
+
priceIds: string[];
|
|
58
|
+
}
|
|
59
|
+
export interface StripeInvoiceDraft {
|
|
60
|
+
id: string;
|
|
61
|
+
customerId: string;
|
|
62
|
+
description: string;
|
|
63
|
+
lines: {
|
|
64
|
+
description: string;
|
|
65
|
+
quantity: number;
|
|
66
|
+
unitAmount: number;
|
|
67
|
+
}[];
|
|
68
|
+
dueDays: number;
|
|
69
|
+
revision: number;
|
|
70
|
+
state: 'draft' | 'issuing' | 'issued';
|
|
71
|
+
stripeId: string | null;
|
|
72
|
+
}
|
|
73
|
+
export interface StripeBillingView {
|
|
74
|
+
provider: 'stripe';
|
|
75
|
+
simulated: false;
|
|
76
|
+
audience: 'seller' | 'customer';
|
|
77
|
+
installationId: string;
|
|
78
|
+
siteId: string;
|
|
79
|
+
mode: 'test' | 'live';
|
|
80
|
+
currency: string;
|
|
81
|
+
business: string;
|
|
82
|
+
setup: {
|
|
83
|
+
configured: boolean;
|
|
84
|
+
verified: boolean;
|
|
85
|
+
webhookConfigured: boolean;
|
|
86
|
+
liveWritesAllowed: boolean;
|
|
87
|
+
verifiedAt: number | null;
|
|
88
|
+
};
|
|
89
|
+
customers: StripeCustomer[];
|
|
90
|
+
records: StripeRecord[];
|
|
91
|
+
drafts: StripeInvoiceDraft[];
|
|
92
|
+
prices: StripePrice[];
|
|
93
|
+
operations: {
|
|
94
|
+
id: string;
|
|
95
|
+
kind: string;
|
|
96
|
+
state: string;
|
|
97
|
+
createdAt: number;
|
|
98
|
+
}[];
|
|
99
|
+
sync: {
|
|
100
|
+
pending: number;
|
|
101
|
+
failed: number;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Operational access never includes ownership, access administration or billing. */
|
|
2
|
+
export declare const webmasterScopes: readonly ["analytics:read", "settings:write", "tasks:read", "tasks:write", "leads:read", "leads:write"];
|
|
3
|
+
export type WebmasterScope = typeof webmasterScopes[number];
|
|
4
|
+
export interface WebmasterMember {
|
|
5
|
+
userId: string;
|
|
6
|
+
permissions: WebmasterScope[];
|
|
7
|
+
}
|
|
8
|
+
export interface WebmasterConnection {
|
|
9
|
+
organizationId: string;
|
|
10
|
+
organizationName: string;
|
|
11
|
+
billingProvider: boolean;
|
|
12
|
+
status: 'connected' | 'disconnected';
|
|
13
|
+
revision: number;
|
|
14
|
+
members: WebmasterMember[];
|
|
15
|
+
permissions: WebmasterScope[];
|
|
16
|
+
}
|
|
17
|
+
export interface WebmasterResponse {
|
|
18
|
+
connection: WebmasterConnection | null;
|
|
19
|
+
canManage: boolean;
|
|
20
|
+
eligibleUsers: Array<{
|
|
21
|
+
id: string;
|
|
22
|
+
email: string;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
export declare function validWebmasterScopes(value: unknown): value is WebmasterScope[];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// packages/contracts/src/webmaster.ts
|
|
2
|
+
var webmasterScopes = ["analytics:read", "settings:write", "tasks:read", "tasks:write", "leads:read", "leads:write"];
|
|
3
|
+
function validWebmasterScopes(value) {
|
|
4
|
+
return Array.isArray(value) && value.length <= webmasterScopes.length && new Set(value).size === value.length && value.every((p) => webmasterScopes.includes(p)) && (!value.includes("leads:write") || value.includes("leads:read")) && (!value.includes("tasks:write") || value.includes("tasks:read"));
|
|
5
|
+
}
|
|
6
|
+
export {
|
|
7
|
+
validWebmasterScopes,
|
|
8
|
+
webmasterScopes
|
|
9
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tryvitris/website-admin-contracts",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Validated installation and module contracts for Cloudflare business admin apps",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public",
|
|
22
|
-
"tag": "
|
|
22
|
+
"tag": "latest"
|
|
23
23
|
},
|
|
24
24
|
"engines": {
|
|
25
25
|
"node": ">=24"
|