ad2app-lib 1.44.1 → 1.49.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.
@@ -33,6 +33,8 @@ export declare const AGENCY_ROUTE_PATHS: {
33
33
  readonly join: "/agency/join";
34
34
  /** A creator answering an access request (FR-016). */
35
35
  readonly requests: "/agency/requests";
36
+ /** A creator's campaign invitations and campaigns: the Agency tab of their Inbox (FR-023, FR-026). */
37
+ readonly creatorCampaigns: "/inbox/agency";
36
38
  };
37
39
  /**
38
40
  * The complete set of reads a grant may carry. A CEILING, not a starting point.
@@ -93,6 +95,12 @@ export interface I_AgencyMember {
93
95
  /** Null while an e-mail invitation is outstanding — nobody has accepted yet. */
94
96
  userId?: string;
95
97
  invitedEmail: string;
98
+ /**
99
+ * The account's own e-mail once the invitation is accepted (and always for
100
+ * the owner, who applied rather than being invited). The Team panel names a
101
+ * person by this, never by the organization's contact address (T063).
102
+ */
103
+ accountEmail?: string;
96
104
  role: AgencyMemberRole;
97
105
  invitedAt: string;
98
106
  /** "Who is in" is exactly `acceptedAt != null`. */
@@ -152,7 +160,27 @@ export interface I_CreatorAccessGrant {
152
160
  };
153
161
  /** Which platforms the grant reaches right now — derived at read time, never stored. */
154
162
  platformsReached: PublishPlatform[];
163
+ /**
164
+ * The agency's OPEN second ask on an active grant, for scopes the creator
165
+ * did not grant (FR-008). Kept apart from `requestedScopes`, so the consent
166
+ * screen names exactly this ask and nothing refused earlier (review H4).
167
+ * Absent when there is no open ask.
168
+ */
169
+ reaskedScopes?: I_AgencyGrantScope[];
170
+ /** When that open ask expires unanswered. */
171
+ reaskExpiresAt?: string;
155
172
  }
173
+ /**
174
+ * Outcomes of discovery and requests that are not refusals of access, sent as
175
+ * codes so the screen composes the sentence in the reader's language (FR-019,
176
+ * review M4). A 429 carries `{ code, limit, windowMinutes }`.
177
+ */
178
+ export declare const AGENCY_REQUEST_CODES: {
179
+ readonly RATE_LIMITED: "agency-rate-limited";
180
+ readonly REQUEST_EXPIRED: "agency-request-expired";
181
+ readonly REQUEST_ANSWERED: "agency-request-answered";
182
+ };
183
+ export type AgencyRequestCode = (typeof AGENCY_REQUEST_CODES)[keyof typeof AGENCY_REQUEST_CODES];
156
184
  /**
157
185
  * Roster row states, DECLARED IN PRECEDENCE ORDER (data-model §Derived).
158
186
  *
@@ -160,7 +188,7 @@ export interface I_CreatorAccessGrant {
160
188
  * first. `revoked` is deliberately absent — a revoked creator leaves the roster
161
189
  * entirely for the settings history (FR-011).
162
190
  */
163
- export declare const AGENCY_ROSTER_ROW_STATES: readonly ["requested", "lapsed", "disconnected", "stale", "active"];
191
+ export declare const AGENCY_ROSTER_ROW_STATES: readonly ["requested", "lapsed", "disconnected", "syncing", "stale", "active"];
164
192
  export type AgencyRosterRowState = (typeof AGENCY_ROSTER_ROW_STATES)[number];
165
193
  /**
166
194
  * What "needs attention" means — ONE home for the KPI tile, the roster segment
@@ -171,6 +199,32 @@ export type AgencyRosterRowState = (typeof AGENCY_ROSTER_ROW_STATES)[number];
171
199
  */
172
200
  export declare const AGENCY_ROSTER_NEEDS_ATTENTION_STATES: readonly ["lapsed", "disconnected", "stale"];
173
201
  export type AgencyRosterNeedsAttentionState = (typeof AGENCY_ROSTER_NEEDS_ATTENTION_STATES)[number];
202
+ /**
203
+ * The rows that carry numbers — ONE home for "which creators' numbers may the
204
+ * agency see on the roster" (decided 2026-09-24, review M1/M2).
205
+ *
206
+ * Only a row whose card OPENS carries `metrics` and counts in the followers,
207
+ * followers-delta and comments totals: `active`, and `stale` (the card opens;
208
+ * the numbers are as old as `lastSyncAt` says). A `lapsed` creator's reads are
209
+ * refused on the card, so their numbers must not reach the agency through the
210
+ * roster instead; a `disconnected` creator has nothing live to count; a
211
+ * `requested` creator has shared nothing yet.
212
+ */
213
+ export declare const AGENCY_ROSTER_METRICS_STATES: readonly ["syncing", "stale", "active"];
214
+ export type AgencyRosterMetricsState = (typeof AGENCY_ROSTER_METRICS_STATES)[number];
215
+ /**
216
+ * One creator's numbers on the roster (FR-011a: sortable metric columns), read
217
+ * from the daily-synced tables, never a vendor call. `null` is a REASON, never
218
+ * a zero: the creator did not grant that scope, or nothing has been synced yet.
219
+ */
220
+ export interface I_AgencyRosterRowMetrics {
221
+ /** Latest followers over the creator's CONNECTED platforms; null without `analytics` or before the first sync. */
222
+ followers: number | null;
223
+ /** Against the snapshot seven days earlier, per connected platform; null without `analytics` or without a week-old point. */
224
+ followersDelta7d: number | null;
225
+ /** The current Monday-to-Sunday week, organization timezone; null without `comments`. */
226
+ commentsThisWeek: number | null;
227
+ }
174
228
  export interface I_AgencyRosterRow {
175
229
  grantId: string;
176
230
  creator: {
@@ -180,22 +234,72 @@ export interface I_AgencyRosterRow {
180
234
  platforms: PublishPlatform[];
181
235
  state: AgencyRosterRowState;
182
236
  grantedScopes: I_AgencyGrantScope[];
237
+ /**
238
+ * The newest successful daily sync. Absent on a `requested` row (nothing is
239
+ * shared yet, so there is nothing to have synced for the agency) and on a
240
+ * creator never synced. `platforms` stays on a requested row: it is what the
241
+ * consent screen names, a fact about the account rather than shared data.
242
+ */
183
243
  lastSyncAt?: string;
244
+ /**
245
+ * Present ONLY on rows in AGENCY_ROSTER_METRICS_STATES (`active`, `stale`):
246
+ * the rows whose card opens. Absent on `lapsed`, `disconnected` and
247
+ * `requested` rows — no numbers, not null numbers.
248
+ */
249
+ metrics?: I_AgencyRosterRowMetrics;
250
+ /**
251
+ * The agency's own last "Ask again" (FR-008), while its one-per-TTL clock
252
+ * runs (audit F19): when it was sent and when another is possible. It says
253
+ * nothing about the ANSWER, because the clock runs whatever the creator did.
254
+ * The roster uses it to say "asked on …, again after …" instead of offering
255
+ * an ask that would not be sent. Absent once the clock has run out.
256
+ */
257
+ reaskedAt?: string;
258
+ reaskAvailableAt?: string;
259
+ }
260
+ /**
261
+ * The platforms a creator has connected, as the agency side sees them
262
+ * (discovery, the grant's "reaches"). `GET users/me/connected-platforms`,
263
+ * answered to every creator, subscribed or not.
264
+ */
265
+ export interface I_ConnectedPlatforms {
266
+ platforms: PublishPlatform[];
184
267
  }
185
268
  export interface I_AgencyRoster {
186
269
  totals: {
187
270
  creators: number;
188
271
  active: number;
189
- /** Summed only over creators holding `analytics`; disconnected platforms excluded. */
190
- followers: number;
191
- followersDelta7d: number;
192
- /** Calendar week, Mon-Sun, organization timezone, creators holding `comments`. */
193
- commentsThisWeek: number;
272
+ /**
273
+ * Summed only over rows in AGENCY_ROSTER_METRICS_STATES whose creator holds
274
+ * `analytics`; disconnected platforms excluded. `null` when no counted
275
+ * creator has a synced number yet, never a zero standing in (T067).
276
+ */
277
+ followers: number | null;
278
+ /**
279
+ * Same rows as `followers`, against the snapshot seven days earlier. `null`
280
+ * when no counted creator has a week-old point — unknown, never a zero.
281
+ */
282
+ followersDelta7d: number | null;
283
+ /**
284
+ * Calendar week, Mon-Sun, organization timezone, over rows in
285
+ * AGENCY_ROSTER_METRICS_STATES whose creator holds `comments`. Like
286
+ * `followers`, `null` when no counted creator has a synced number yet:
287
+ * unknown, never a zero (SC-008, T067).
288
+ */
289
+ commentsThisWeek: number | null;
194
290
  /** Count of rows in AGENCY_ROSTER_NEEDS_ATTENTION_STATES. */
195
291
  needsAttention: number;
196
292
  };
197
293
  items: I_AgencyRosterRow[];
198
294
  }
295
+ /**
296
+ * A coverage that ENDED recently, so the creator meeting the paywall again is
297
+ * told why (FR-014: "an honest message, never a silent loss of access").
298
+ */
299
+ export interface I_SponsorshipEnded {
300
+ organizationName: string;
301
+ endedAt: string;
302
+ }
199
303
  /** What a sponsored creator sees on their OWN subscription screen (FR-014). */
200
304
  export interface I_SponsoredBy {
201
305
  organizationName: string;
@@ -203,6 +307,106 @@ export interface I_SponsoredBy {
203
307
  /** Present once the sponsorship is ending: the date their plan runs to. */
204
308
  until?: string;
205
309
  }
310
+ /** The card on file, as Stripe describes it. Never more than these four facts. */
311
+ export interface I_AgencyPaymentMethod {
312
+ brand: string;
313
+ last4: string;
314
+ expMonth: number;
315
+ expYear: number;
316
+ }
317
+ /**
318
+ * One invoice. `amount` is Stripe's minor unit verbatim; the display edge
319
+ * formats it once, like every other price in the app.
320
+ */
321
+ export interface I_AgencyInvoice {
322
+ id: string;
323
+ number: string | null;
324
+ date: string;
325
+ amount: number;
326
+ currency: string;
327
+ status: string;
328
+ pdfUrl?: string;
329
+ hostedUrl?: string;
330
+ }
331
+ /**
332
+ * One creator on an ACTIVE grant, with whether the agency covers their plan
333
+ * (FR-013b). `tier: null` is "not covered". `until` is set while a coverage is
334
+ * ending: it runs to the close of the period already paid for.
335
+ */
336
+ export interface I_AgencyCoveredCreator {
337
+ grantId: string;
338
+ creator: {
339
+ id: string;
340
+ handle: string;
341
+ };
342
+ tier: SponsoredTier | null;
343
+ since?: string;
344
+ until?: string;
345
+ }
346
+ /**
347
+ * An ended relationship, for the past-creators history (FR-013b). `creator.id`
348
+ * is null once the creator's account was purged; the handle is the snapshot
349
+ * frozen at grant time.
350
+ */
351
+ export interface I_AgencyPastCreator {
352
+ grantId: string;
353
+ creator: {
354
+ id: string | null;
355
+ handle: string;
356
+ displayName?: string;
357
+ };
358
+ endedAt: string;
359
+ endedBy: CreatorAccessGrantRevokedBy;
360
+ coveredUntil?: string;
361
+ }
362
+ /**
363
+ * Everything the owner's billing section and panel show (FR-013, FR-013b).
364
+ *
365
+ * `seats` is the ONE home of the billed quantity: the count of active grants.
366
+ * Covering a creator never changes it (clarify 2026-09-17), so nothing here
367
+ * derives a price from `covered`.
368
+ *
369
+ * `price` is null when the seat product is not configured in this deployment
370
+ * (T031, the money gate); the screen says so rather than inventing a number.
371
+ * `nextInvoice` is Stripe's own preview, so a coupon is already applied to it.
372
+ */
373
+ export interface I_AgencyBilling {
374
+ subscriptionStatus: AgencySubscriptionStatus;
375
+ /** Creators with an active grant. */
376
+ seats: number;
377
+ /**
378
+ * What Stripe bills: `max(1, seats)`. An agency with no creators yet still
379
+ * pays for one seat, and the screen must say the number Stripe charges.
380
+ */
381
+ billedSeats: number;
382
+ price: {
383
+ amount: number;
384
+ currency: string;
385
+ interval: string;
386
+ } | null;
387
+ currentPeriodEnd?: string;
388
+ cancelAtPeriodEnd?: boolean;
389
+ nextInvoice?: {
390
+ amount: number;
391
+ currency: string;
392
+ date: string;
393
+ };
394
+ discount?: {
395
+ name?: string;
396
+ percentOff?: number;
397
+ amountOff?: number;
398
+ };
399
+ paymentMethod?: I_AgencyPaymentMethod;
400
+ billingDetails?: {
401
+ name?: string;
402
+ email?: string;
403
+ country?: string;
404
+ taxId?: string;
405
+ };
406
+ invoices: I_AgencyInvoice[];
407
+ covered: I_AgencyCoveredCreator[];
408
+ pastCreators: I_AgencyPastCreator[];
409
+ }
206
410
  /**
207
411
  * Every DTO below carries class-validator decorators and a constructor that
208
412
  * survives being called with nothing.
@@ -264,10 +468,50 @@ export declare class AgencyDiscoverQueryDto {
264
468
  constructor(data?: Partial<AgencyDiscoverQueryDto>);
265
469
  }
266
470
  export declare class AgencyAccessRequestDto {
471
+ /**
472
+ * A user id: a UUID or a Firebase uid (letters, digits, hyphens). It was
473
+ * `@IsUUID()` until US5 was built, which refused most real creators — their
474
+ * ids are Firebase uids.
475
+ */
267
476
  userId: string;
268
477
  scopes: I_AgencyGrantScope[];
269
478
  constructor(data?: Partial<AgencyAccessRequestDto>);
270
479
  }
480
+ /** `PATCH /users/me/agency-discoverable` — "Let agencies find me" (FR-015). */
481
+ export declare class UpdateAgencyDiscoverableDto {
482
+ discoverable: boolean;
483
+ /**
484
+ * NO default: a required boolean that defaults to false lets an empty body
485
+ * through validation and reach the database as `undefined` (review M7).
486
+ */
487
+ constructor(data?: Partial<UpdateAgencyDiscoverableDto>);
488
+ }
489
+ /**
490
+ * One discover hit: the public handle and the platforms, NEVER a metric and
491
+ * never a grant flag, so the payload cannot tell a caller whether it already
492
+ * holds the creator (FR-015). `userId` is what the request is sent for.
493
+ */
494
+ export interface I_AgencyDiscoverHit {
495
+ userId: string;
496
+ handle: string;
497
+ platforms: PublishPlatform[];
498
+ /**
499
+ * The platform whose username matched. Absent on an e-mail match. Two hits
500
+ * with the same handle are two different people (@mark on YouTube, another
501
+ * @mark on Instagram), and this is what tells them apart (T068).
502
+ */
503
+ matchedPlatform?: PublishPlatform;
504
+ }
505
+ /**
506
+ * `POST /agency/roster/discover` — the SAME shape and status for every case:
507
+ * discoverable exact matches, a non-discoverable account, a partial match and
508
+ * no account at all (SC-009). A miss is `{ results: [] }`, never a 404. An
509
+ * exact handle held by several findable creators returns each of them, one
510
+ * row per person (Maciej, 2026-09-26, T068); an e-mail matches one person.
511
+ */
512
+ export interface I_AgencyDiscoverResult {
513
+ results: I_AgencyDiscoverHit[];
514
+ }
271
515
  export declare class AdminSetOrganizationStatusDto {
272
516
  status: Extract<AgencyOrganizationStatus, 'approved' | 'rejected'>;
273
517
  /** Required on reject — a rejected applicant is shown this (FR-002). The
@@ -9,7 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.AdminSetOrganizationStatusDto = exports.AgencyAccessRequestDto = exports.AgencyDiscoverQueryDto = exports.SponsorCreatorDto = exports.UpdateGrantScopesDto = exports.AcceptGrantDto = exports.AcceptAgencyMemberInviteDto = exports.RedeemAgencyInviteDto = exports.InviteAgencyMemberDto = exports.CreateAgencyOrganizationDto = exports.AGENCY_ROSTER_NEEDS_ATTENTION_STATES = exports.AGENCY_ROSTER_ROW_STATES = exports.CREATOR_ACCESS_GRANT_STATES = exports.AGENCY_ORGANIZATION_STATUSES = exports.isAgencyGrantScope = exports.AGENCY_GRANT_SCOPES = exports.AGENCY_ROUTE_PATHS = void 0;
12
+ exports.AdminSetOrganizationStatusDto = exports.UpdateAgencyDiscoverableDto = exports.AgencyAccessRequestDto = exports.AgencyDiscoverQueryDto = exports.SponsorCreatorDto = exports.UpdateGrantScopesDto = exports.AcceptGrantDto = exports.AcceptAgencyMemberInviteDto = exports.RedeemAgencyInviteDto = exports.InviteAgencyMemberDto = exports.CreateAgencyOrganizationDto = exports.AGENCY_ROSTER_METRICS_STATES = exports.AGENCY_ROSTER_NEEDS_ATTENTION_STATES = exports.AGENCY_ROSTER_ROW_STATES = exports.AGENCY_REQUEST_CODES = exports.CREATOR_ACCESS_GRANT_STATES = exports.AGENCY_ORGANIZATION_STATUSES = exports.isAgencyGrantScope = exports.AGENCY_GRANT_SCOPES = exports.AGENCY_ROUTE_PATHS = void 0;
13
13
  const class_validator_1 = require("class-validator");
14
14
  // ── Cross-repo route constants ───────────────────────────────────────────────
15
15
  /**
@@ -32,6 +32,8 @@ exports.AGENCY_ROUTE_PATHS = {
32
32
  join: '/agency/join',
33
33
  /** A creator answering an access request (FR-016). */
34
34
  requests: '/agency/requests',
35
+ /** A creator's campaign invitations and campaigns: the Agency tab of their Inbox (FR-023, FR-026). */
36
+ creatorCampaigns: '/inbox/agency',
35
37
  };
36
38
  // ── Scopes ───────────────────────────────────────────────────────────────────
37
39
  /**
@@ -89,6 +91,16 @@ exports.CREATOR_ACCESS_GRANT_STATES = [
89
91
  'expired',
90
92
  'revoked',
91
93
  ];
94
+ /**
95
+ * Outcomes of discovery and requests that are not refusals of access, sent as
96
+ * codes so the screen composes the sentence in the reader's language (FR-019,
97
+ * review M4). A 429 carries `{ code, limit, windowMinutes }`.
98
+ */
99
+ exports.AGENCY_REQUEST_CODES = {
100
+ RATE_LIMITED: 'agency-rate-limited',
101
+ REQUEST_EXPIRED: 'agency-request-expired',
102
+ REQUEST_ANSWERED: 'agency-request-answered',
103
+ };
92
104
  // ── Roster ───────────────────────────────────────────────────────────────────
93
105
  /**
94
106
  * Roster row states, DECLARED IN PRECEDENCE ORDER (data-model §Derived).
@@ -104,6 +116,13 @@ exports.AGENCY_ROSTER_ROW_STATES = [
104
116
  'lapsed',
105
117
  /** the creator has no live platform connection */
106
118
  'disconnected',
119
+ /**
120
+ * shared within the last 24 hours and not synced yet: the first daily sync
121
+ * has not run. Not stale, and not something the agency can act on (T067,
122
+ * the 2026-09-26 UI run: a creator who agreed a minute ago read as
123
+ * "needs attention").
124
+ */
125
+ 'syncing',
107
126
  /** no successful daily sync in 24 hours (FR-011, Jan 2026-09-21) */
108
127
  'stale',
109
128
  'active',
@@ -120,6 +139,22 @@ exports.AGENCY_ROSTER_NEEDS_ATTENTION_STATES = [
120
139
  'disconnected',
121
140
  'stale',
122
141
  ];
142
+ /**
143
+ * The rows that carry numbers — ONE home for "which creators' numbers may the
144
+ * agency see on the roster" (decided 2026-09-24, review M1/M2).
145
+ *
146
+ * Only a row whose card OPENS carries `metrics` and counts in the followers,
147
+ * followers-delta and comments totals: `active`, and `stale` (the card opens;
148
+ * the numbers are as old as `lastSyncAt` says). A `lapsed` creator's reads are
149
+ * refused on the card, so their numbers must not reach the agency through the
150
+ * roster instead; a `disconnected` creator has nothing live to count; a
151
+ * `requested` creator has shared nothing yet.
152
+ */
153
+ exports.AGENCY_ROSTER_METRICS_STATES = [
154
+ 'syncing',
155
+ 'stale',
156
+ 'active',
157
+ ];
123
158
  // ── DTOs ─────────────────────────────────────────────────────────────────────
124
159
  /**
125
160
  * Every DTO below carries class-validator decorators and a constructor that
@@ -272,7 +307,8 @@ class AgencyAccessRequestDto {
272
307
  }
273
308
  exports.AgencyAccessRequestDto = AgencyAccessRequestDto;
274
309
  __decorate([
275
- (0, class_validator_1.IsUUID)(),
310
+ (0, class_validator_1.IsString)(),
311
+ (0, class_validator_1.Matches)(/^[A-Za-z0-9-]{1,128}$/),
276
312
  __metadata("design:type", String)
277
313
  ], AgencyAccessRequestDto.prototype, "userId", void 0);
278
314
  __decorate([
@@ -281,6 +317,22 @@ __decorate([
281
317
  (0, class_validator_1.IsIn)(exports.AGENCY_GRANT_SCOPES, { each: true }),
282
318
  __metadata("design:type", Array)
283
319
  ], AgencyAccessRequestDto.prototype, "scopes", void 0);
320
+ /** `PATCH /users/me/agency-discoverable` — "Let agencies find me" (FR-015). */
321
+ class UpdateAgencyDiscoverableDto {
322
+ /**
323
+ * NO default: a required boolean that defaults to false lets an empty body
324
+ * through validation and reach the database as `undefined` (review M7).
325
+ */
326
+ constructor(data) {
327
+ if (data?.discoverable !== undefined)
328
+ this.discoverable = data.discoverable;
329
+ }
330
+ }
331
+ exports.UpdateAgencyDiscoverableDto = UpdateAgencyDiscoverableDto;
332
+ __decorate([
333
+ (0, class_validator_1.IsBoolean)(),
334
+ __metadata("design:type", Boolean)
335
+ ], UpdateAgencyDiscoverableDto.prototype, "discoverable", void 0);
284
336
  class AdminSetOrganizationStatusDto {
285
337
  constructor(data) {
286
338
  this.status = data?.status ?? 'approved';
@@ -38,4 +38,6 @@ export * from "./I_SM_Platform";
38
38
  export * from "./scheduling";
39
39
  export * from "./agent";
40
40
  export * from "./agency";
41
+ export * from "./agency-campaigns";
42
+ export * from "./agency-threads";
41
43
  export * from "./I_AccessDenial";
@@ -57,5 +57,7 @@ __exportStar(require("./scheduling"), exports);
57
57
  __exportStar(require("./agent"), exports);
58
58
  // ── Agency domain (organizations, memberships, creator access grants, spec 175)
59
59
  __exportStar(require("./agency"), exports);
60
+ __exportStar(require("./agency-campaigns"), exports);
61
+ __exportStar(require("./agency-threads"), exports);
60
62
  // ── Access control ────────────────────────────────────────────────────────────
61
63
  __exportStar(require("./I_AccessDenial"), exports);
@@ -4,13 +4,14 @@
4
4
  * Covers subscription tier state, Stripe checkout flows,
5
5
  * and the customer portal redirect.
6
6
  */
7
+ import type { I_SponsoredBy, I_SponsorshipEnded } from '../agency';
7
8
  export type SchedulingSubscriptionTier = 'free' | 'starter' | 'pro';
8
9
  export type SchedulingSubscriptionStatus = 'active' | 'canceled' | 'expired';
9
10
  /**
10
11
  * Constructor input. Takes `maxAccounts`, or the deprecated `maxPlatforms` so
11
12
  * code written against 1.40.0 keeps compiling; either way both fields are set.
12
13
  */
13
- export type SchedulingSubscriptionInfoInput = Pick<SchedulingSubscriptionInfoDTO, 'tier' | 'status' | 'expiresAt'> & ({
14
+ export type SchedulingSubscriptionInfoInput = Pick<SchedulingSubscriptionInfoDTO, 'tier' | 'status' | 'expiresAt'> & Partial<Pick<SchedulingSubscriptionInfoDTO, 'sponsoredBy' | 'sponsorshipEnded'>> & ({
14
15
  maxAccounts: number;
15
16
  maxPlatforms?: number;
16
17
  } | {
@@ -32,6 +33,14 @@ export declare class SchedulingSubscriptionInfoDTO {
32
33
  * Read `maxAccounts`. Always equal to it; removed in a later release.
33
34
  */
34
35
  maxPlatforms: number;
36
+ /**
37
+ * Present only while an agency covers this creator's plan (spec 175 FR-014).
38
+ * `until` appears once the sponsorship is ending: the date the plan runs to.
39
+ * Absent — never null — for everyone else, so "not sponsored" has one shape.
40
+ */
41
+ sponsoredBy?: I_SponsoredBy;
42
+ /** Present only when a coverage ended recently and nothing replaced it. Absent otherwise. */
43
+ sponsorshipEnded?: I_SponsorshipEnded;
35
44
  constructor(data: SchedulingSubscriptionInfoInput);
36
45
  }
37
46
  /**
@@ -19,6 +19,10 @@ class SchedulingSubscriptionInfoDTO {
19
19
  this.expiresAt = data.expiresAt;
20
20
  this.maxAccounts = maxAccounts;
21
21
  this.maxPlatforms = maxAccounts;
22
+ if (data.sponsoredBy)
23
+ this.sponsoredBy = data.sponsoredBy;
24
+ if (data.sponsorshipEnded)
25
+ this.sponsorshipEnded = data.sponsorshipEnded;
22
26
  }
23
27
  }
24
28
  exports.SchedulingSubscriptionInfoDTO = SchedulingSubscriptionInfoDTO;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ad2app-lib",
3
- "version": "1.44.1",
3
+ "version": "1.49.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "commonjs",
@@ -51,7 +51,7 @@
51
51
  "prepare": "npm run build"
52
52
  },
53
53
  "keywords": [],
54
- "author": "Maciej G\u00f3rski@ad2.app",
54
+ "author": "Maciej Górski@ad2.app",
55
55
  "license": "ISC",
56
56
  "description": "Package to share types and utils across the ad2app projects",
57
57
  "dependencies": {
@@ -196,11 +196,51 @@ const EVENT_PROPERTY_WITNESS: { [E in keyof EventProperties]: EventProperties[E]
196
196
  actor_role: "member",
197
197
  origin: "agency_request",
198
198
  },
199
+ [EVENTS.AGENCY_CAMPAIGN_CREATED]: { organization_id: "org-1", actor_role: "member", campaign_id: "c-1" },
200
+ [EVENTS.AGENCY_CAMPAIGN_CREATOR_INVITED]: {
201
+ organization_id: "org-1",
202
+ actor_role: "member",
203
+ campaign_id: "c-1",
204
+ creator_user_id: "u-1",
205
+ with_access_request: true,
206
+ },
207
+ [EVENTS.AGENCY_CAMPAIGN_JOINED]: { organization_id: "org-1", actor_role: "creator", campaign_id: "c-1" },
208
+ [EVENTS.AGENCY_CAMPAIGN_DECLINED]: { organization_id: "org-1", actor_role: "creator", campaign_id: "c-1" },
209
+ [EVENTS.AGENCY_CAMPAIGN_LEFT]: { organization_id: "org-1", actor_role: "creator", campaign_id: "c-1" },
210
+ [EVENTS.AGENCY_CAMPAIGN_CREATOR_REMOVED]: {
211
+ organization_id: "org-1",
212
+ actor_role: "owner",
213
+ campaign_id: "c-1",
214
+ creator_user_id: "u-1",
215
+ },
216
+ [EVENTS.AGENCY_CAMPAIGN_OFFER_SENT]: { organization_id: "org-1", actor_role: "member", campaign_id: "c-1" },
217
+ [EVENTS.AGENCY_CAMPAIGN_OFFER_ACTED]: {
218
+ organization_id: "org-1",
219
+ actor_role: "creator",
220
+ campaign_id: "c-1",
221
+ action: "counter",
222
+ },
223
+ [EVENTS.AGENCY_CAMPAIGN_MATERIAL_UPLOADED]: {
224
+ organization_id: "org-1",
225
+ actor_role: "creator",
226
+ campaign_id: "c-1",
227
+ version: 2,
228
+ },
229
+ [EVENTS.AGENCY_CAMPAIGN_MATERIAL_REVIEWED]: {
230
+ organization_id: "org-1",
231
+ actor_role: "member",
232
+ campaign_id: "c-1",
233
+ action: "request_changes",
234
+ },
235
+ [EVENTS.AGENCY_CAMPAIGN_PUBLISHED]: { organization_id: "org-1", actor_role: "member", campaign_id: "c-1" },
236
+ [EVENTS.AGENCY_CAMPAIGN_MILESTONE_CONFIRMED]: { organization_id: "org-1", actor_role: "owner", campaign_id: "c-1" },
237
+ [EVENTS.AGENCY_THREAD_STARTED]: { organization_id: "org-1", actor_role: "member", thread_id: "t-1" },
238
+ [EVENTS.AGENCY_THREAD_MESSAGE_SENT]: { organization_id: "org-1", actor_role: "creator", thread_id: "t-1" },
199
239
  };
200
240
 
201
241
  test("no agency event may carry the discover query — only whether it matched (FR-015)", () => {
202
242
  const agencyEvents = Object.values(EVENTS).filter((name) => name.startsWith("agency_"));
203
- assert.equal(agencyEvents.length, 15, "the 175 set is 15 events");
243
+ assert.equal(agencyEvents.length, 29, "the 175 set is 15 roster events, 12 campaign-loop events (US7) and 2 thread events (US8)");
204
244
  for (const name of agencyEvents) {
205
245
  const props = EVENT_PROPERTY_WITNESS[name as keyof typeof EVENT_PROPERTY_WITNESS];
206
246
  for (const key of Object.keys(props)) {
@@ -191,6 +191,23 @@ export const EVENTS = {
191
191
  // creators an agency went looking for.
192
192
  AGENCY_DISCOVER_SEARCHED: 'agency_discover_searched',
193
193
  AGENCY_ACCESS_REQUESTED: 'agency_access_requested',
194
+ // The campaign loop (175 US7). Server-owned: each is captured in the act's
195
+ // own transaction path, under the ACTOR's distinct id.
196
+ AGENCY_CAMPAIGN_CREATED: 'agency_campaign_created',
197
+ AGENCY_CAMPAIGN_CREATOR_INVITED: 'agency_campaign_creator_invited',
198
+ AGENCY_CAMPAIGN_JOINED: 'agency_campaign_joined',
199
+ AGENCY_CAMPAIGN_DECLINED: 'agency_campaign_declined',
200
+ AGENCY_CAMPAIGN_LEFT: 'agency_campaign_left',
201
+ AGENCY_CAMPAIGN_CREATOR_REMOVED: 'agency_campaign_creator_removed',
202
+ AGENCY_CAMPAIGN_OFFER_SENT: 'agency_campaign_offer_sent',
203
+ AGENCY_CAMPAIGN_OFFER_ACTED: 'agency_campaign_offer_acted',
204
+ AGENCY_CAMPAIGN_MATERIAL_UPLOADED: 'agency_campaign_material_uploaded',
205
+ AGENCY_CAMPAIGN_MATERIAL_REVIEWED: 'agency_campaign_material_reviewed',
206
+ AGENCY_CAMPAIGN_PUBLISHED: 'agency_campaign_published',
207
+ AGENCY_CAMPAIGN_MILESTONE_CONFIRMED: 'agency_campaign_milestone_confirmed',
208
+ // Threads (175 US8). Never the message body: a conversation is not analytics.
209
+ AGENCY_THREAD_STARTED: 'agency_thread_started',
210
+ AGENCY_THREAD_MESSAGE_SENT: 'agency_thread_message_sent',
194
211
  } as const;
195
212
 
196
213
  export type EventName = (typeof EVENTS)[keyof typeof EVENTS];
@@ -427,6 +444,11 @@ export interface AgencyEventProperties {
427
444
  creator_user_id?: string;
428
445
  }
429
446
 
447
+ /** A campaign-loop event: the agency event plus the campaign it concerns. */
448
+ export interface AgencyCampaignEventProperties extends AgencyEventProperties {
449
+ campaign_id: string;
450
+ }
451
+
430
452
  export interface EventProperties {
431
453
  [EVENTS.LANDING_CTA_CLICKED]: {
432
454
  location:
@@ -675,6 +697,23 @@ export interface EventProperties {
675
697
  // `matched` only. The query itself is never sent (FR-015).
676
698
  [EVENTS.AGENCY_DISCOVER_SEARCHED]: AgencyEventProperties & { matched: boolean };
677
699
  [EVENTS.AGENCY_ACCESS_REQUESTED]: AgencyEventProperties;
700
+ // The campaign loop (175 US7): which campaign, and for an act, which one.
701
+ // Never a price: an amount in analytics is a creator's rate in a dashboard.
702
+ [EVENTS.AGENCY_CAMPAIGN_CREATED]: AgencyCampaignEventProperties;
703
+ [EVENTS.AGENCY_CAMPAIGN_CREATOR_INVITED]: AgencyCampaignEventProperties & { with_access_request: boolean };
704
+ [EVENTS.AGENCY_CAMPAIGN_JOINED]: AgencyCampaignEventProperties;
705
+ [EVENTS.AGENCY_CAMPAIGN_DECLINED]: AgencyCampaignEventProperties;
706
+ [EVENTS.AGENCY_CAMPAIGN_LEFT]: AgencyCampaignEventProperties;
707
+ [EVENTS.AGENCY_CAMPAIGN_CREATOR_REMOVED]: AgencyCampaignEventProperties;
708
+ [EVENTS.AGENCY_CAMPAIGN_OFFER_SENT]: AgencyCampaignEventProperties;
709
+ [EVENTS.AGENCY_CAMPAIGN_OFFER_ACTED]: AgencyCampaignEventProperties & { action: 'accept' | 'counter' | 'confirm' | 'reject' };
710
+ [EVENTS.AGENCY_CAMPAIGN_MATERIAL_UPLOADED]: AgencyCampaignEventProperties & { version: number };
711
+ [EVENTS.AGENCY_CAMPAIGN_MATERIAL_REVIEWED]: AgencyCampaignEventProperties & { action: 'approve' | 'request_changes' | 'reject' };
712
+ [EVENTS.AGENCY_CAMPAIGN_PUBLISHED]: AgencyCampaignEventProperties;
713
+ [EVENTS.AGENCY_CAMPAIGN_MILESTONE_CONFIRMED]: AgencyCampaignEventProperties;
714
+ // Threads (175 US8): which thread, never its content.
715
+ [EVENTS.AGENCY_THREAD_STARTED]: AgencyEventProperties & { thread_id: string };
716
+ [EVENTS.AGENCY_THREAD_MESSAGE_SENT]: AgencyEventProperties & { thread_id: string };
678
717
  }
679
718
 
680
719
  /** Canonical person property keys (set via identify / $set). */
@@ -82,6 +82,25 @@ export const ACCESS_DENIAL_CODES = {
82
82
  * per person, FR-004). Names the case and NOTHING else about that account.
83
83
  */
84
84
  AGENCY_ALREADY_IN_ORGANIZATION: "agency-already-in-organization",
85
+ /**
86
+ * The owner invited an address that is ALREADY on this organization's team,
87
+ * themselves included (T063). A different case from the one above: the owner
88
+ * is told this person is already in, not that they belong elsewhere.
89
+ */
90
+ AGENCY_ALREADY_MEMBER: "agency-already-member",
91
+ /**
92
+ * The owner tried to cover a creator who pays for their own plan. Covering
93
+ * is for a creator without a paid plan (US4 scenario 3); covering a payer
94
+ * would leave them paying for what the agency also pays for (T066).
95
+ */
96
+ AGENCY_CREATOR_PAYS_OWN_PLAN: "agency-creator-pays-own-plan",
97
+ /**
98
+ * A creator whose plan an agency covers asked for the billing portal, and
99
+ * has no Stripe customer of their own to open it for (FR-014). The body
100
+ * names the covering agency; the client hides the portal button instead of
101
+ * showing a Stripe error.
102
+ */
103
+ SPONSORED_NO_PORTAL: "sponsored-no-portal",
85
104
  } as const;
86
105
 
87
106
  export type AccessDenialCode =
@@ -41,6 +41,8 @@ export enum I_OfferStatus {
41
41
  AGENCY_NEGOTIATED = 'agencyNegotiated',
42
42
  CONFIRMED = 'confirmed',
43
43
  ALTERNATIVE_SUGGESTED = 'alternativeSuggested',
44
+ /** The creator left the campaign before the offer was confirmed (spec 175 US7). Added, never renamed: Postgres enums only grow. */
45
+ WITHDRAWN = 'withdrawn',
44
46
  }
45
47
 
46
48
  export declare class I_OfferGetDTO {
@@ -87,7 +87,7 @@ test('every AGENCY code is namespaced, so a new domain cannot shadow an old wall
87
87
  key.startsWith('AGENCY_'),
88
88
  );
89
89
 
90
- assert.equal(agencyCodes.length, 12);
90
+ assert.equal(agencyCodes.length, 14);
91
91
  for (const [, value] of agencyCodes) {
92
92
  assert.match(value, /^agency-/, `un-namespaced agency code: ${value}`);
93
93
  }