@jsm-mit/sultana-core-motoko-package 0.3.0 → 0.5.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.
@@ -75,6 +75,17 @@ type UpdateSalonServiceArgs =
75
75
  serviceTypeIds: vec ServiceTypeId;
76
76
  workerIds: vec principal;
77
77
  };
78
+ type UpdatePromoArgs =
79
+ record {
80
+ assetId: AssetId;
81
+ chips: vec text;
82
+ cta: PromoCta;
83
+ name: text;
84
+ promoId: PromoId;
85
+ salonId: SalonId;
86
+ target: PromoTarget;
87
+ textLines: vec text;
88
+ };
78
89
  type UpdateProfileArgs =
79
90
  record {
80
91
  avatarUrl: opt text;
@@ -127,6 +138,12 @@ type SetReportThresholdArgs =
127
138
  opType: OperationType;
128
139
  thresholdMs: nat;
129
140
  };
141
+ type SetPromoActiveArgs =
142
+ record {
143
+ active: bool;
144
+ promoId: PromoId;
145
+ salonId: SalonId;
146
+ };
130
147
  type SetLanguageActiveArgs =
131
148
  record {
132
149
  active: bool;
@@ -497,6 +514,7 @@ type PromoTarget =
497
514
  };
498
515
  type PromoItem =
499
516
  record {
517
+ active: bool;
500
518
  assetId: AssetId;
501
519
  chips: vec text;
502
520
  createdAt: int;
@@ -505,6 +523,7 @@ type PromoItem =
505
523
  id: PromoId;
506
524
  kind: AssetKind;
507
525
  location: Coordinate;
526
+ name: text;
508
527
  salonId: SalonId;
509
528
  target: PromoTarget;
510
529
  textLines: vec text;
@@ -815,6 +834,7 @@ type AddPromoArgs =
815
834
  assetId: AssetId;
816
835
  chips: vec text;
817
836
  cta: PromoCta;
837
+ name: text;
818
838
  salonId: SalonId;
819
839
  target: PromoTarget;
820
840
  textLines: vec text;
@@ -856,8 +876,9 @@ service : {
856
876
  addDiscountCode: (args: AddDiscountCodeArgs) -> (Result_1);
857
877
  /// Superadmin: adds a supported language (active by default; languages are never deleted)
858
878
  addLanguage: (code: LangCode) -> (Result_1);
859
- /// Owner attaches a promo (max 3 per salon): #Ready media from the salon's library,
860
- /// target = the salon or one of its services, CTA = button label / show-delay / discount code
879
+ /// Owner adds a promo to the salon's library (max 15): #Ready media from the salon's
880
+ /// library, target = the salon or one of its services, CTA = button label / discount code.
881
+ /// The promo is created INACTIVE — setPromoActive is what puts it in the feed
861
882
  addPromo: (args: AddPromoArgs) -> (Result_37);
862
883
  /// Adds a new service to a specific salon
863
884
  addSalonService: (args: AddSalonServiceArgs) -> (Result_36);
@@ -942,7 +963,7 @@ service : {
942
963
  /// The salon's outstanding invite, or null when there is none or it has expired (Owner only)
943
964
  getSalonInvite: (salonId: SalonId) -> (Result_20) query;
944
965
  getSalonInviteTtl: () -> (Result_3) query;
945
- /// Public: the salon's current promos
966
+ /// The salon's promos: the whole library for its owner, the active ones for everyone else
946
967
  getSalonPromos: (salonId: SalonId) -> (Result_19) query;
947
968
  /// Returns the list of salon services by salon ID (owner view)
948
969
  getSalonServicesBySalonId: (salonId: SalonId) -> (Result_6) query;
@@ -983,7 +1004,7 @@ service : {
983
1004
  removeDailySchedule: (args: RemoveDailyScheduleArgs) -> (Result_1);
984
1005
  /// Owner removes a code (blocked while a promo of the salon still broadcasts it)
985
1006
  removeDiscountCode: (args: RemoveDiscountCodeArgs) -> (Result_1);
986
- /// Owner removes a promo (this is also the "deactivate"); the library asset stays
1007
+ /// Owner deletes a promo for good (to only hide it, use setPromoActive); the asset stays
987
1008
  removePromo: (args: RemovePromoArgs) -> (Result_1);
988
1009
  /// Permanently removes a salon service and cleans up search indexes
989
1010
  removeSalonServiceBySalonServiceId: (salonServiceId: SalonServiceId) ->
@@ -1015,6 +1036,9 @@ service : {
1015
1036
  /// Superadmin only, ONE-WAY: flips isProd to true — the test-only endpoints
1016
1037
  /// (purgeFailedAssetsNow, triggerAssetsSweep) trap from then on. There is no way back.
1017
1038
  setProdMode: () -> (Result_1);
1039
+ /// Owner puts a promo in the feed or takes it out (max 3 active per salon); the promo
1040
+ /// itself stays in the library either way
1041
+ setPromoActive: (args: SetPromoActiveArgs) -> (Result_1);
1018
1042
  setReportBatchSize: (size: nat) -> (Result_3);
1019
1043
  setReportThreshold: (args: SetReportThresholdArgs) -> (Result_3);
1020
1044
  setSalonInviteTtl: (newTtlSeconds: nat) -> (Result_3);
@@ -1031,6 +1055,8 @@ service : {
1031
1055
  /// sweep now, with a TTL override — same code path its hourly timer drives
1032
1056
  triggerAssetsSweep: (ttlSeconds: nat) -> (Result_3);
1033
1057
  updateMyProfile: (args: UpdateProfileArgs) -> (Result_2);
1058
+ /// Owner edits a promo in place; it keeps its id, its createdAt and its place in the feed
1059
+ updatePromo: (args: UpdatePromoArgs) -> (Result_1);
1034
1060
  /// Updates an existing service with new details and synchronizes the index
1035
1061
  updateSalonService: (args: UpdateSalonServiceArgs) -> (Result_1);
1036
1062
  updateVisit: (args: UpdateVisitArgs) -> (Result);
@@ -31,6 +31,7 @@ export interface AddPromoArgs {
31
31
  'textLines' : Array<string>,
32
32
  'assetId' : AssetId,
33
33
  'chips' : Array<string>,
34
+ 'name' : string,
34
35
  'target' : PromoTarget,
35
36
  'salonId' : SalonId,
36
37
  }
@@ -265,9 +266,11 @@ export interface PromoItem {
265
266
  'cta' : PromoCta,
266
267
  'textLines' : Array<string>,
267
268
  'thumbId' : AssetId,
269
+ 'active' : boolean,
268
270
  'assetId' : AssetId,
269
271
  'chips' : Array<string>,
270
272
  'kind' : AssetKind,
273
+ 'name' : string,
271
274
  'createdAt' : bigint,
272
275
  'target' : PromoTarget,
273
276
  'location' : Coordinate,
@@ -479,6 +482,11 @@ export interface SetDailyScheduleArgs {
479
482
  'mask' : TimeMask,
480
483
  }
481
484
  export interface SetLanguageActiveArgs { 'active' : boolean, 'code' : LangCode }
485
+ export interface SetPromoActiveArgs {
486
+ 'active' : boolean,
487
+ 'salonId' : SalonId,
488
+ 'promoId' : PromoId,
489
+ }
482
490
  export interface SetReportThresholdArgs {
483
491
  'opType' : OperationType,
484
492
  'thresholdMs' : bigint,
@@ -516,6 +524,16 @@ export interface UpdateProfileArgs {
516
524
  'name' : string,
517
525
  'avatarUrl' : [] | [string],
518
526
  }
527
+ export interface UpdatePromoArgs {
528
+ 'cta' : PromoCta,
529
+ 'textLines' : Array<string>,
530
+ 'assetId' : AssetId,
531
+ 'chips' : Array<string>,
532
+ 'name' : string,
533
+ 'target' : PromoTarget,
534
+ 'salonId' : SalonId,
535
+ 'promoId' : PromoId,
536
+ }
519
537
  export interface UpdateSalonServiceArgs {
520
538
  'duration' : bigint,
521
539
  'active' : boolean,
@@ -596,8 +614,9 @@ export interface _SERVICE {
596
614
  */
597
615
  'addLanguage' : ActorMethod<[LangCode], Result_1>,
598
616
  /**
599
- * / Owner attaches a promo (max 3 per salon): #Ready media from the salon's library,
600
- * / target = the salon or one of its services, CTA = button label / show-delay / discount code
617
+ * / Owner adds a promo to the salon's library (max 15): #Ready media from the salon's
618
+ * / library, target = the salon or one of its services, CTA = button label / discount code.
619
+ * / The promo is created INACTIVE — setPromoActive is what puts it in the feed
601
620
  */
602
621
  'addPromo' : ActorMethod<[AddPromoArgs], Result_37>,
603
622
  /**
@@ -759,7 +778,7 @@ export interface _SERVICE {
759
778
  'getSalonInvite' : ActorMethod<[SalonId], Result_20>,
760
779
  'getSalonInviteTtl' : ActorMethod<[], Result_3>,
761
780
  /**
762
- * / Public: the salon's current promos
781
+ * / The salon's promos: the whole library for its owner, the active ones for everyone else
763
782
  */
764
783
  'getSalonPromos' : ActorMethod<[SalonId], Result_19>,
765
784
  /**
@@ -828,7 +847,7 @@ export interface _SERVICE {
828
847
  */
829
848
  'removeDiscountCode' : ActorMethod<[RemoveDiscountCodeArgs], Result_1>,
830
849
  /**
831
- * / Owner removes a promo (this is also the "deactivate"); the library asset stays
850
+ * / Owner deletes a promo for good (to only hide it, use setPromoActive); the asset stays
832
851
  */
833
852
  'removePromo' : ActorMethod<[RemovePromoArgs], Result_1>,
834
853
  /**
@@ -888,6 +907,11 @@ export interface _SERVICE {
888
907
  * / (purgeFailedAssetsNow, triggerAssetsSweep) trap from then on. There is no way back.
889
908
  */
890
909
  'setProdMode' : ActorMethod<[], Result_1>,
910
+ /**
911
+ * / Owner puts a promo in the feed or takes it out (max 3 active per salon); the promo
912
+ * / itself stays in the library either way
913
+ */
914
+ 'setPromoActive' : ActorMethod<[SetPromoActiveArgs], Result_1>,
891
915
  'setReportBatchSize' : ActorMethod<[bigint], Result_3>,
892
916
  'setReportThreshold' : ActorMethod<[SetReportThresholdArgs], Result_3>,
893
917
  'setSalonInviteTtl' : ActorMethod<[bigint], Result_3>,
@@ -910,6 +934,10 @@ export interface _SERVICE {
910
934
  */
911
935
  'triggerAssetsSweep' : ActorMethod<[bigint], Result_3>,
912
936
  'updateMyProfile' : ActorMethod<[UpdateProfileArgs], Result_2>,
937
+ /**
938
+ * / Owner edits a promo in place; it keeps its id, its createdAt and its place in the feed
939
+ */
940
+ 'updatePromo' : ActorMethod<[UpdatePromoArgs], Result_1>,
913
941
  /**
914
942
  * / Updates an existing service with new details and synchronizes the index
915
943
  */
@@ -45,6 +45,7 @@ export const idlFactory = ({ IDL }) => {
45
45
  'textLines' : IDL.Vec(IDL.Text),
46
46
  'assetId' : AssetId,
47
47
  'chips' : IDL.Vec(IDL.Text),
48
+ 'name' : IDL.Text,
48
49
  'target' : PromoTarget,
49
50
  'salonId' : SalonId,
50
51
  });
@@ -397,9 +398,11 @@ export const idlFactory = ({ IDL }) => {
397
398
  'cta' : PromoCta,
398
399
  'textLines' : IDL.Vec(IDL.Text),
399
400
  'thumbId' : AssetId,
401
+ 'active' : IDL.Bool,
400
402
  'assetId' : AssetId,
401
403
  'chips' : IDL.Vec(IDL.Text),
402
404
  'kind' : AssetKind,
405
+ 'name' : IDL.Text,
403
406
  'createdAt' : IDL.Int,
404
407
  'target' : PromoTarget,
405
408
  'location' : Coordinate,
@@ -591,6 +594,11 @@ export const idlFactory = ({ IDL }) => {
591
594
  'active' : IDL.Bool,
592
595
  'code' : LangCode,
593
596
  });
597
+ const SetPromoActiveArgs = IDL.Record({
598
+ 'active' : IDL.Bool,
599
+ 'salonId' : SalonId,
600
+ 'promoId' : PromoId,
601
+ });
594
602
  const SetReportThresholdArgs = IDL.Record({
595
603
  'opType' : OperationType,
596
604
  'thresholdMs' : IDL.Nat,
@@ -606,6 +614,16 @@ export const idlFactory = ({ IDL }) => {
606
614
  'name' : IDL.Text,
607
615
  'avatarUrl' : IDL.Opt(IDL.Text),
608
616
  });
617
+ const UpdatePromoArgs = IDL.Record({
618
+ 'cta' : PromoCta,
619
+ 'textLines' : IDL.Vec(IDL.Text),
620
+ 'assetId' : AssetId,
621
+ 'chips' : IDL.Vec(IDL.Text),
622
+ 'name' : IDL.Text,
623
+ 'target' : PromoTarget,
624
+ 'salonId' : SalonId,
625
+ 'promoId' : PromoId,
626
+ });
609
627
  const UpdateSalonServiceArgs = IDL.Record({
610
628
  'duration' : IDL.Nat,
611
629
  'active' : IDL.Bool,
@@ -809,6 +827,7 @@ export const idlFactory = ({ IDL }) => {
809
827
  'setLanguageActive' : IDL.Func([SetLanguageActiveArgs], [Result_1], []),
810
828
  'setLoggingEnabled' : IDL.Func([IDL.Bool], [Result_1], []),
811
829
  'setProdMode' : IDL.Func([], [Result_1], []),
830
+ 'setPromoActive' : IDL.Func([SetPromoActiveArgs], [Result_1], []),
812
831
  'setReportBatchSize' : IDL.Func([IDL.Nat], [Result_3], []),
813
832
  'setReportThreshold' : IDL.Func([SetReportThresholdArgs], [Result_3], []),
814
833
  'setSalonInviteTtl' : IDL.Func([IDL.Nat], [Result_3], []),
@@ -820,6 +839,7 @@ export const idlFactory = ({ IDL }) => {
820
839
  'setWeeklyTemplate' : IDL.Func([SetWeeklyTemplateArgs], [Result_1], []),
821
840
  'triggerAssetsSweep' : IDL.Func([IDL.Nat], [Result_3], []),
822
841
  'updateMyProfile' : IDL.Func([UpdateProfileArgs], [Result_2], []),
842
+ 'updatePromo' : IDL.Func([UpdatePromoArgs], [Result_1], []),
823
843
  'updateSalonService' : IDL.Func([UpdateSalonServiceArgs], [Result_1], []),
824
844
  'updateVisit' : IDL.Func([UpdateVisitArgs], [Result], []),
825
845
  });
@@ -1,6 +1,10 @@
1
1
  import { type Identity } from "@icp-sdk/core/agent";
2
2
  import { ActorBase } from "../actor-base.js";
3
3
  import type { PromoItem } from "../../declarations/sultana-core-motoko/sultana-core-motoko.did.js";
4
+ /** How many promos a salon may KEEP — the size of its library, drafts included (PromoRepo). */
5
+ export declare const MAX_PROMOS_PER_SALON = 15;
6
+ /** How many of them may be IN THE FEED at once (PromoRepo). */
7
+ export declare const MAX_ACTIVE_PROMOS_PER_SALON = 3;
4
8
  /** Friendly promo-content input — every field optional; omitted = empty / frontend default. */
5
9
  export interface PromoContentInput {
6
10
  /** Up to 2 overlay text lines rendered over the media. */
@@ -13,46 +17,87 @@ export interface PromoContentInput {
13
17
  discountCode?: string;
14
18
  }
15
19
  /**
16
- * Wraps the promo endpoints on sultana-core-motoko. Promos belong to a SALON (max 3 per
17
- * salon) and are managed by its current owner; the media must be a #Ready asset from the
18
- * salon's library. getSalonPromos and getPromoFeed are public queries — the feed follows
19
- * the search pattern: the frontend expands its radius into geohash6 cells and adapts the
20
- * set to the response size, core only answers exact-key lookups.
20
+ * Wraps the promo endpoints on sultana-core-motoko. A salon keeps a LIBRARY of up to
21
+ * MAX_PROMOS_PER_SALON promos, of which at most MAX_ACTIVE_PROMOS_PER_SALON may be active —
22
+ * only the active ones reach the geo feed. Promos are managed by the salon's current owner
23
+ * and the media must be a #Ready asset from the salon's library.
24
+ *
25
+ * getSalonPromos and getPromoFeed are public queries. The feed follows the search pattern: the
26
+ * caller expands its radius into geohash6 cells and adapts the set to the response size, core
27
+ * only answers exact-key lookups.
21
28
  */
22
29
  export declare class PromoActor extends ActorBase {
23
30
  constructor(canisterId: string, identity?: Identity);
24
31
  /**
25
- * Attaches a promo to the salon (owner only, max 3 per salon). The media must be a
26
- * #Ready asset from the salon's library (image or video — kind and thumb/poster are
27
- * taken from the library entry).
32
+ * Adds a promo to the salon's library (owner only, max MAX_PROMOS_PER_SALON). The media
33
+ * must be a #Ready asset from the salon's library (image or video — kind and thumb/poster
34
+ * are taken from the library entry).
35
+ *
36
+ * The promo is created INACTIVE: adding never pushes a running campaign out of the feed,
37
+ * so call setPromoActiveAsyncUnsafe to put it there.
28
38
  * @param salonId - The salon the promo belongs to (and, for a salon-target promo, links to).
29
- * @param assetId - A #Ready asset from the salon's library; one asset can back only one promo.
39
+ * @param name - The owner's label for the campaign (required, max 60). Not feed content —
40
+ * it is how promos are told apart in a library of fifteen — but it does ride in the
41
+ * public PromoItem, so it is readable by anyone.
42
+ * @param assetId - A #Ready asset from the salon's library; one asset may back several promos.
30
43
  * @param content - Overlay text lines (max 2), chips (max 2), button label and discount
31
44
  * code (all optional — see PromoContentInput).
32
45
  * @param targetSalonServiceId - Optional: make the promo tap through to this service of the
33
46
  * salon; omitted -> the promo targets the salon itself.
34
47
  * @returns A Promise that resolves to the new PromoId.
35
48
  * @throws Error `CanisterError` on a Framework.Result #err (not the owner, asset not in the
36
- * library or not #Ready, promo limit reached, duplicate asset, bad target, too many
37
- * lines/chips, ...).
49
+ * library or not #Ready, library full, a missing or over-long name, bad target, too
50
+ * many lines/chips, a discount code that is not this salon's or whose scope does not
51
+ * fit the target, ...).
38
52
  * @throws Error `CriticalCanisterError` / `CallRefusedAtInspectionStage` as usual.
39
53
  */
40
- addPromoAsyncUnsafe(salonId: string, assetId: string, content?: PromoContentInput, targetSalonServiceId?: string): Promise<string>;
54
+ addPromoAsyncUnsafe(salonId: string, name: string, assetId: string, content?: PromoContentInput, targetSalonServiceId?: string): Promise<string>;
41
55
  /**
42
- * Removes a promo from the salon (owner only). This is also the "deactivate" — there is
43
- * no separate active flag; the library asset stays untouched.
56
+ * Edits a promo in place (owner only). The promo keeps its id, its createdAt and its place
57
+ * in or out of the feed, so correcting a running campaign does not restart it. Every
58
+ * content field is replaced by what you pass — an omitted `content` field clears it.
59
+ * @param salonId - The salon that owns the promo.
60
+ * @param promoId - The promo to edit.
61
+ * @param name - The owner's label for the campaign (required, max 60).
62
+ * @param assetId - A #Ready asset from the salon's library; may differ from the current one
63
+ * (kind and thumb/poster are re-read from the new entry).
64
+ * @param content - The new overlay text lines, chips, button label and discount code.
65
+ * @param targetSalonServiceId - Optional new tap-through target; omitted -> the salon itself.
66
+ * @throws Error `CanisterError` on a Framework.Result #err (not the owner, promo not found,
67
+ * promo of a different salon, plus every validation addPromo does).
68
+ */
69
+ updatePromoAsyncUnsafe(salonId: string, promoId: string, name: string, assetId: string, content?: PromoContentInput, targetSalonServiceId?: string): Promise<boolean>;
70
+ /**
71
+ * Puts a promo in the geo feed (`active = true`) or takes it out (owner only). The promo
72
+ * stays in the library either way — this is the "deactivate"; removePromo is a real delete.
73
+ *
74
+ * Setting the flag to what it already is succeeds and changes nothing. Activating one promo
75
+ * too many FAILS rather than dropping a running one: which campaign comes down is the
76
+ * owner's decision, so deactivate one first.
77
+ * @throws Error `CanisterError` on a Framework.Result #err (not the owner, promo not found,
78
+ * promo of a different salon, MAX_ACTIVE_PROMOS_PER_SALON already active).
79
+ */
80
+ setPromoActiveAsyncUnsafe(salonId: string, promoId: string, active: boolean): Promise<boolean>;
81
+ /**
82
+ * Deletes a promo from the salon's library (owner only) — content, id and all. To only take
83
+ * it off the feed use setPromoActiveAsyncUnsafe. The library asset stays untouched.
44
84
  * @throws Error `CanisterError` on a Framework.Result #err (not the owner, promo not
45
85
  * found, promo of a different salon).
46
86
  */
47
87
  removePromoAsyncUnsafe(salonId: string, promoId: string): Promise<boolean>;
48
88
  /**
49
- * Returns the salon's current promos — public query (promo content is public by nature).
89
+ * The salon's promos — a public query, but the answer depends on who asks: the salon's
90
+ * owner gets the whole library, everyone else gets the ACTIVE promos only. Drafts stay
91
+ * private because one may already carry a discount code the owner has not broadcast yet.
92
+ *
93
+ * So call this with the owner's identity to manage the library, and with any identity to
94
+ * read what the public sees.
50
95
  * @throws Error `CriticalCanisterError` on transport/canister failure.
51
96
  */
52
97
  getSalonPromosAsyncUnsafe(salonId: string): Promise<PromoItem[]>;
53
98
  /**
54
- * Public geo feed: promos in the given geohash6 cells, deduplicated by promo id. The
55
- * caller expands its radius into cells and should adapt the set to the response size
99
+ * Public geo feed: the ACTIVE promos in the given geohash6 cells, deduplicated by promo id.
100
+ * The caller expands its radius into cells and should adapt the set to the response size
56
101
  * (there is no server-side pagination; core soft-caps very large responses).
57
102
  * @param geohashes6 - The geohash6 cells covering the viewer's radius.
58
103
  * @throws Error `CriticalCanisterError` on transport/canister failure.
@@ -1 +1 @@
1
- {"version":3,"file":"promo-actor.d.ts","sourceRoot":"","sources":["../../src/actors/promo-actor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAY,SAAS,EAAe,MAAM,mEAAmE,CAAC;AAE1H,+FAA+F;AAC/F,MAAM,WAAW,iBAAiB;IAC9B,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6FAA6F;IAC7F,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAaD;;;;;;GAMG;AACH,qBAAa,UAAW,SAAQ,SAAS;gBAEzB,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ;IAInD;;;;;;;;;;;;;;;OAeG;IACU,mBAAmB,CAC5B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,iBAAsB,EAC/B,oBAAoB,CAAC,EAAE,MAAM,GAC9B,OAAO,CAAC,MAAM,CAAC;IAalB;;;;;OAKG;IACU,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAMvF;;;OAGG;IACU,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAM7E;;;;;;OAMG;IACU,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;CAKnF"}
1
+ {"version":3,"file":"promo-actor.d.ts","sourceRoot":"","sources":["../../src/actors/promo-actor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAY,SAAS,EAAe,MAAM,mEAAmE,CAAC;AAE1H,+FAA+F;AAC/F,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,+DAA+D;AAC/D,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C,+FAA+F;AAC/F,MAAM,WAAW,iBAAiB;IAC9B,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6FAA6F;IAC7F,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAaD;;;;;;;;;GASG;AACH,qBAAa,UAAW,SAAQ,SAAS;gBAEzB,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ;IAInD;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACU,mBAAmB,CAC5B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,iBAAsB,EAC/B,oBAAoB,CAAC,EAAE,MAAM,GAC9B,OAAO,CAAC,MAAM,CAAC;IAclB;;;;;;;;;;;;;OAaG;IACU,sBAAsB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,iBAAsB,EAC/B,oBAAoB,CAAC,EAAE,MAAM,GAC9B,OAAO,CAAC,OAAO,CAAC;IAenB;;;;;;;;;OASG;IACU,yBAAyB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAM3G;;;;;OAKG;IACU,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAMvF;;;;;;;;OAQG;IACU,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAM7E;;;;;;OAMG;IACU,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;CAKnF"}
@@ -1,5 +1,9 @@
1
1
  import {} from "@icp-sdk/core/agent";
2
2
  import { ActorBase } from "../actor-base.js";
3
+ /** How many promos a salon may KEEP — the size of its library, drafts included (PromoRepo). */
4
+ export const MAX_PROMOS_PER_SALON = 15;
5
+ /** How many of them may be IN THE FEED at once (PromoRepo). */
6
+ export const MAX_ACTIVE_PROMOS_PER_SALON = 3;
3
7
  function toCandidCta(content) {
4
8
  return {
5
9
  buttonLabel: content.buttonLabel === undefined ? [] : [content.buttonLabel],
@@ -10,35 +14,46 @@ function toCandidTarget(targetSalonServiceId) {
10
14
  return targetSalonServiceId ? { SalonService: targetSalonServiceId } : { Salon: null };
11
15
  }
12
16
  /**
13
- * Wraps the promo endpoints on sultana-core-motoko. Promos belong to a SALON (max 3 per
14
- * salon) and are managed by its current owner; the media must be a #Ready asset from the
15
- * salon's library. getSalonPromos and getPromoFeed are public queries — the feed follows
16
- * the search pattern: the frontend expands its radius into geohash6 cells and adapts the
17
- * set to the response size, core only answers exact-key lookups.
17
+ * Wraps the promo endpoints on sultana-core-motoko. A salon keeps a LIBRARY of up to
18
+ * MAX_PROMOS_PER_SALON promos, of which at most MAX_ACTIVE_PROMOS_PER_SALON may be active —
19
+ * only the active ones reach the geo feed. Promos are managed by the salon's current owner
20
+ * and the media must be a #Ready asset from the salon's library.
21
+ *
22
+ * getSalonPromos and getPromoFeed are public queries. The feed follows the search pattern: the
23
+ * caller expands its radius into geohash6 cells and adapts the set to the response size, core
24
+ * only answers exact-key lookups.
18
25
  */
19
26
  export class PromoActor extends ActorBase {
20
27
  constructor(canisterId, identity) {
21
28
  super(canisterId, identity);
22
29
  }
23
30
  /**
24
- * Attaches a promo to the salon (owner only, max 3 per salon). The media must be a
25
- * #Ready asset from the salon's library (image or video — kind and thumb/poster are
26
- * taken from the library entry).
31
+ * Adds a promo to the salon's library (owner only, max MAX_PROMOS_PER_SALON). The media
32
+ * must be a #Ready asset from the salon's library (image or video — kind and thumb/poster
33
+ * are taken from the library entry).
34
+ *
35
+ * The promo is created INACTIVE: adding never pushes a running campaign out of the feed,
36
+ * so call setPromoActiveAsyncUnsafe to put it there.
27
37
  * @param salonId - The salon the promo belongs to (and, for a salon-target promo, links to).
28
- * @param assetId - A #Ready asset from the salon's library; one asset can back only one promo.
38
+ * @param name - The owner's label for the campaign (required, max 60). Not feed content —
39
+ * it is how promos are told apart in a library of fifteen — but it does ride in the
40
+ * public PromoItem, so it is readable by anyone.
41
+ * @param assetId - A #Ready asset from the salon's library; one asset may back several promos.
29
42
  * @param content - Overlay text lines (max 2), chips (max 2), button label and discount
30
43
  * code (all optional — see PromoContentInput).
31
44
  * @param targetSalonServiceId - Optional: make the promo tap through to this service of the
32
45
  * salon; omitted -> the promo targets the salon itself.
33
46
  * @returns A Promise that resolves to the new PromoId.
34
47
  * @throws Error `CanisterError` on a Framework.Result #err (not the owner, asset not in the
35
- * library or not #Ready, promo limit reached, duplicate asset, bad target, too many
36
- * lines/chips, ...).
48
+ * library or not #Ready, library full, a missing or over-long name, bad target, too
49
+ * many lines/chips, a discount code that is not this salon's or whose scope does not
50
+ * fit the target, ...).
37
51
  * @throws Error `CriticalCanisterError` / `CallRefusedAtInspectionStage` as usual.
38
52
  */
39
- async addPromoAsyncUnsafe(salonId, assetId, content = {}, targetSalonServiceId) {
53
+ async addPromoAsyncUnsafe(salonId, name, assetId, content = {}, targetSalonServiceId) {
40
54
  return this.executeFunctionAsyncUnsafe(() => this.actor.addPromo({
41
55
  salonId,
56
+ name,
42
57
  assetId,
43
58
  target: toCandidTarget(targetSalonServiceId),
44
59
  textLines: content.textLines ?? [],
@@ -47,8 +62,47 @@ export class PromoActor extends ActorBase {
47
62
  }));
48
63
  }
49
64
  /**
50
- * Removes a promo from the salon (owner only). This is also the "deactivate" — there is
51
- * no separate active flag; the library asset stays untouched.
65
+ * Edits a promo in place (owner only). The promo keeps its id, its createdAt and its place
66
+ * in or out of the feed, so correcting a running campaign does not restart it. Every
67
+ * content field is replaced by what you pass — an omitted `content` field clears it.
68
+ * @param salonId - The salon that owns the promo.
69
+ * @param promoId - The promo to edit.
70
+ * @param name - The owner's label for the campaign (required, max 60).
71
+ * @param assetId - A #Ready asset from the salon's library; may differ from the current one
72
+ * (kind and thumb/poster are re-read from the new entry).
73
+ * @param content - The new overlay text lines, chips, button label and discount code.
74
+ * @param targetSalonServiceId - Optional new tap-through target; omitted -> the salon itself.
75
+ * @throws Error `CanisterError` on a Framework.Result #err (not the owner, promo not found,
76
+ * promo of a different salon, plus every validation addPromo does).
77
+ */
78
+ async updatePromoAsyncUnsafe(salonId, promoId, name, assetId, content = {}, targetSalonServiceId) {
79
+ return this.executeFunctionAsyncUnsafe(() => this.actor.updatePromo({
80
+ salonId,
81
+ promoId,
82
+ name,
83
+ assetId,
84
+ target: toCandidTarget(targetSalonServiceId),
85
+ textLines: content.textLines ?? [],
86
+ chips: content.chips ?? [],
87
+ cta: toCandidCta(content),
88
+ }));
89
+ }
90
+ /**
91
+ * Puts a promo in the geo feed (`active = true`) or takes it out (owner only). The promo
92
+ * stays in the library either way — this is the "deactivate"; removePromo is a real delete.
93
+ *
94
+ * Setting the flag to what it already is succeeds and changes nothing. Activating one promo
95
+ * too many FAILS rather than dropping a running one: which campaign comes down is the
96
+ * owner's decision, so deactivate one first.
97
+ * @throws Error `CanisterError` on a Framework.Result #err (not the owner, promo not found,
98
+ * promo of a different salon, MAX_ACTIVE_PROMOS_PER_SALON already active).
99
+ */
100
+ async setPromoActiveAsyncUnsafe(salonId, promoId, active) {
101
+ return this.executeFunctionAsyncUnsafe(() => this.actor.setPromoActive({ salonId, promoId, active }));
102
+ }
103
+ /**
104
+ * Deletes a promo from the salon's library (owner only) — content, id and all. To only take
105
+ * it off the feed use setPromoActiveAsyncUnsafe. The library asset stays untouched.
52
106
  * @throws Error `CanisterError` on a Framework.Result #err (not the owner, promo not
53
107
  * found, promo of a different salon).
54
108
  */
@@ -56,15 +110,20 @@ export class PromoActor extends ActorBase {
56
110
  return this.executeFunctionAsyncUnsafe(() => this.actor.removePromo({ salonId, promoId }));
57
111
  }
58
112
  /**
59
- * Returns the salon's current promos — public query (promo content is public by nature).
113
+ * The salon's promos — a public query, but the answer depends on who asks: the salon's
114
+ * owner gets the whole library, everyone else gets the ACTIVE promos only. Drafts stay
115
+ * private because one may already carry a discount code the owner has not broadcast yet.
116
+ *
117
+ * So call this with the owner's identity to manage the library, and with any identity to
118
+ * read what the public sees.
60
119
  * @throws Error `CriticalCanisterError` on transport/canister failure.
61
120
  */
62
121
  async getSalonPromosAsyncUnsafe(salonId) {
63
122
  return this.executeFunctionAsyncUnsafe(() => this.actor.getSalonPromos(salonId));
64
123
  }
65
124
  /**
66
- * Public geo feed: promos in the given geohash6 cells, deduplicated by promo id. The
67
- * caller expands its radius into cells and should adapt the set to the response size
125
+ * Public geo feed: the ACTIVE promos in the given geohash6 cells, deduplicated by promo id.
126
+ * The caller expands its radius into cells and should adapt the set to the response size
68
127
  * (there is no server-side pagination; core soft-caps very large responses).
69
128
  * @param geohashes6 - The geohash6 cells covering the viewer's radius.
70
129
  * @throws Error `CriticalCanisterError` on transport/canister failure.
package/dist/index.d.ts CHANGED
@@ -16,6 +16,7 @@ export { PromoActor } from "./actors/promo-actor.js";
16
16
  export { DiscountCodesActor } from "./actors/discount-codes-actor.js";
17
17
  export type { DiscountValueInput } from "./actors/discount-codes-actor.js";
18
18
  export type { PromoContentInput } from "./actors/promo-actor.js";
19
+ export { MAX_PROMOS_PER_SALON, MAX_ACTIVE_PROMOS_PER_SALON } from "./actors/promo-actor.js";
19
20
  export type { PromoItem, PromoCta, PromoTarget, PromoId, DiscountCode, DiscountScope, DiscountValue, SupportedLanguage, ServiceTypeLabel, ServiceTypeTranslations, SetTranslationArgs, SalonPublicInfo } from "../declarations/sultana-core-motoko/sultana-core-motoko.did.js";
20
21
  export { SalonServicesActor } from "./actors/salon-services-actor.js";
21
22
  export { SalonsActor } from "./actors/salons-actor.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,+BAA+B,EAAE,mCAAmC,EAAE,uCAAuC,EAAE,4BAA4B,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACjP,YAAY,EAAE,oBAAoB,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAClH,YAAY,EACR,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,GACtB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAClF,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,YAAY,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gEAAgE,CAAC;AAC9G,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,YAAY,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAC3E,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAC5F,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,gEAAgE,CAAC;AAC/Q,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACvG,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAC1G,YAAY,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAClH,YAAY,EACR,KAAK,EACL,SAAS,EACT,YAAY,EACZ,OAAO,EACP,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,GAClB,MAAM,gEAAgE,CAAC;AACxE,YAAY,EACR,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,UAAU,EACV,oBAAoB,EACpB,eAAe,EACf,kBAAkB,EAClB,4BAA4B,EAC5B,aAAa,EACb,mBAAmB,EACnB,uBAAuB,EACvB,iBAAiB,EACjB,YAAY,EACZ,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,EACjB,sBAAsB,EACzB,MAAM,gEAAgE,CAAC"}
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ export { HrActor } from "./actors/hr-actor.js";
8
8
  export { MigrationActor } from "./actors/migration-actor.js";
9
9
  export { PromoActor } from "./actors/promo-actor.js";
10
10
  export { DiscountCodesActor } from "./actors/discount-codes-actor.js";
11
+ export { MAX_PROMOS_PER_SALON, MAX_ACTIVE_PROMOS_PER_SALON } from "./actors/promo-actor.js";
11
12
  export { SalonServicesActor } from "./actors/salon-services-actor.js";
12
13
  export { SalonsActor } from "./actors/salons-actor.js";
13
14
  export { SearchActor } from "./actors/search-actor.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsm-mit/sultana-core-motoko-package",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "A TypeScript library for interacting with the Sultana Core Motoko actor on the Internet Computer (IC) platform.",
5
5
  "homepage": "https://github.com/JSM-Sultana/sultana-core-motoko-package#readme",
6
6
  "bugs": {