@fonderie/vue-admin-screens 0.2.0 → 0.4.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/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/screens/AdminShell.ts
2
2
  import { ConfigEditorScreen, ConfigListScreen } from "@fonderie/vue-config-admin-screens";
3
3
  import { TemplateEditorScreen, TemplateListScreen } from "@fonderie/vue-courier-admin-screens";
4
- import { computed as computed2, defineComponent as defineComponent9, h as h10, ref as ref2 } from "vue";
4
+ import { computed as computed2, defineComponent as defineComponent12, h as h13, ref as ref4 } from "vue";
5
5
 
6
6
  // src/styles.ts
7
7
  var styles = {
@@ -561,6 +561,312 @@ var UsersScreen = defineComponent8({
561
561
  }
562
562
  });
563
563
 
564
+ // src/screens/CatalogScreen.ts
565
+ import { useAdminCatalog } from "@fonderie/vue-admin";
566
+ import { defineComponent as defineComponent9, h as h10 } from "vue";
567
+ var CatalogScreen = defineComponent9({
568
+ name: "FonderieCatalogScreen",
569
+ props: { client: { type: Object, required: true } },
570
+ setup(props) {
571
+ const { catalog, isLoading, error, refresh, deletePlan } = useAdminCatalog(props.client);
572
+ const money = (v) => v == null ? "\u2014" : (v / 100).toFixed(2);
573
+ return () => {
574
+ const c = catalog.value;
575
+ return h10("div", { style: styles.container }, [
576
+ h10("div", { style: styles.toolbar }, [
577
+ h10("h1", { style: { ...styles.title, marginBottom: 0 } }, "Catalog"),
578
+ refreshButton("Refresh", isLoading.value, () => void refresh())
579
+ ]),
580
+ error.value ? h10("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
581
+ isLoading.value && !c ? h10("p", { style: styles.status }, "Loading\u2026") : null,
582
+ c ? [
583
+ h10("h2", { style: styles.subtitle }, "Configured (code)"),
584
+ h10(
585
+ "pre",
586
+ {
587
+ style: {
588
+ ...styles.mono,
589
+ background: "#f9fafb",
590
+ padding: "12px",
591
+ overflowX: "auto"
592
+ }
593
+ },
594
+ JSON.stringify(c.configured, null, 2)
595
+ ),
596
+ h10("h2", { style: styles.subtitle }, "Stored (database)"),
597
+ c.stored.length === 0 ? h10("p", { style: styles.muted }, "No stored plans.") : table(
598
+ ["Plan", "Tier", "Seats", "Monthly", "Yearly", "Trial", ""],
599
+ c.stored.map(
600
+ (p) => h10("tr", { key: p.id }, [
601
+ td([
602
+ h10("strong", p.name),
603
+ p.description ? h10("div", { style: styles.muted }, p.description) : ""
604
+ ]),
605
+ td(String(p.tier)),
606
+ td(p.seats == null ? "\u221E" : String(p.seats)),
607
+ td(`${money(p.pricing?.monthly)} ${p.pricing?.currency ?? ""}`),
608
+ td(money(p.pricing?.yearly)),
609
+ td(p.trialDays ? `${p.trialDays} d` : "\u2014"),
610
+ td(
611
+ h10(
612
+ "button",
613
+ {
614
+ type: "button",
615
+ style: styles.button,
616
+ onClick: () => {
617
+ if (window.confirm(`Delete stored plan "${p.name}"?`))
618
+ void deletePlan(p.id).catch(() => {
619
+ });
620
+ }
621
+ },
622
+ "Delete"
623
+ )
624
+ )
625
+ ])
626
+ )
627
+ )
628
+ ] : null
629
+ ]);
630
+ };
631
+ }
632
+ });
633
+
634
+ // src/screens/SubscriberScreen.ts
635
+ import { useAdminSubscriber } from "@fonderie/vue-admin";
636
+ import { defineComponent as defineComponent10, h as h11, ref as ref2 } from "vue";
637
+ var SubscriberScreen = defineComponent10({
638
+ name: "FonderieSubscriberScreen",
639
+ props: { client: { type: Object, required: true } },
640
+ setup(props) {
641
+ const type = ref2("user");
642
+ const idInput = ref2("");
643
+ const subscriber = ref2(null);
644
+ const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = useAdminSubscriber(props.client, subscriber, { limit: 20 });
645
+ const amount = ref2("");
646
+ const note = ref2("");
647
+ const granted = ref2(null);
648
+ const input = (model, placeholder, extra = {}) => h11("input", {
649
+ value: model.value,
650
+ onInput: (e) => model.value = e.target.value,
651
+ placeholder,
652
+ style: { ...styles.button, cursor: "text", ...extra },
653
+ "aria-label": placeholder
654
+ });
655
+ return () => {
656
+ const s = subscription.value;
657
+ const w = wallet.value;
658
+ return h11("div", { style: styles.container }, [
659
+ h11("h1", { style: styles.title }, "Subscriber"),
660
+ h11(
661
+ "form",
662
+ {
663
+ style: styles.toolbar,
664
+ onSubmit: (e) => {
665
+ e.preventDefault();
666
+ granted.value = null;
667
+ subscriber.value = idInput.value.trim() ? { type: type.value, id: idInput.value.trim() } : null;
668
+ }
669
+ },
670
+ [
671
+ h11(
672
+ "select",
673
+ {
674
+ value: type.value,
675
+ onChange: (e) => type.value = e.target.value,
676
+ style: styles.button,
677
+ "aria-label": "Type"
678
+ },
679
+ [
680
+ h11("option", { value: "user" }, "user"),
681
+ h11("option", { value: "workspace" }, "workspace")
682
+ ]
683
+ ),
684
+ input(idInput, "subscriber id", { minWidth: "280px" }),
685
+ h11(
686
+ "button",
687
+ { type: "submit", style: styles.button, disabled: isLoading.value },
688
+ "Look up"
689
+ )
690
+ ]
691
+ ),
692
+ error.value ? h11("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
693
+ subscriber.value && !isLoading.value ? [
694
+ h11("h2", { style: styles.subtitle }, "Subscription"),
695
+ s ? h11("table", { style: styles.table }, [
696
+ h11("tbody", [
697
+ h11("tr", [td("plan"), td([h11("strong", s.plan), ` \xB7 ${s.interval}`])]),
698
+ h11("tr", [
699
+ td("status"),
700
+ td(`${s.status}${s.cancelAtPeriodEnd ? " \xB7 cancels at period end" : ""}`)
701
+ ]),
702
+ h11("tr", [
703
+ td("period"),
704
+ td(
705
+ `${s.currentPeriodStart ? new Date(s.currentPeriodStart).toLocaleDateString() : "\u2014"} \u2192 ${s.currentPeriodEnd ? new Date(s.currentPeriodEnd).toLocaleDateString() : "\u2014"}`
706
+ )
707
+ ]),
708
+ h11("tr", [td("provider"), td(s.providerSubscriptionId ?? "\u2014", styles.mono)])
709
+ ])
710
+ ]) : h11("p", { style: styles.muted }, "No subscription."),
711
+ h11("h2", { style: styles.subtitle }, "Wallet"),
712
+ w ? [
713
+ h11("p", [
714
+ h11("strong", w.balance),
715
+ ` ${w.currency} `,
716
+ h11(
717
+ "span",
718
+ { style: styles.muted },
719
+ `(granted ${w.granted ?? "0"} \xB7 purchased ${w.purchased ?? "0"}) \xB7 minor units, precision ${w.precision}`
720
+ )
721
+ ]),
722
+ h11(
723
+ "form",
724
+ {
725
+ style: styles.toolbar,
726
+ onSubmit: (e) => {
727
+ e.preventDefault();
728
+ const sub = subscriber.value;
729
+ if (!sub) return;
730
+ const key = `admin-grant-${sub.type}-${sub.id}-${Date.now()}`;
731
+ const amt = amount.value.trim();
732
+ void grant({
733
+ amount: amt,
734
+ ...note.value ? { description: note.value } : {},
735
+ idempotencyKey: key
736
+ }).then(() => {
737
+ granted.value = `Granted ${amt} ${w.currency}.`;
738
+ amount.value = "";
739
+ note.value = "";
740
+ }).catch(() => {
741
+ });
742
+ }
743
+ },
744
+ [
745
+ input(amount, "amount (minor units)"),
746
+ input(note, "reason (optional)", { minWidth: "220px" }),
747
+ h11(
748
+ "button",
749
+ {
750
+ type: "submit",
751
+ style: styles.button,
752
+ disabled: !/^\d+$/.test(amount.value.trim())
753
+ },
754
+ "Grant"
755
+ ),
756
+ granted.value ? h11("span", { style: styles.ok }, granted.value) : null
757
+ ]
758
+ ),
759
+ h11("h2", { style: styles.subtitle }, "Ledger"),
760
+ ledger.value.length === 0 ? h11("p", { style: styles.muted }, "Nothing moved yet.") : table(
761
+ ["When", "Kind", "Amount", "Note"],
762
+ ledger.value.map(
763
+ (t) => h11("tr", { key: t.id }, [
764
+ td(new Date(t.createdAt).toLocaleString(), styles.muted),
765
+ td(t.type),
766
+ td(t.amount, styles.mono),
767
+ td(t.description ?? "", styles.muted)
768
+ ])
769
+ )
770
+ ),
771
+ hasMoreLedger.value ? h11(
772
+ "button",
773
+ {
774
+ type: "button",
775
+ style: { ...styles.button, marginTop: "8px" },
776
+ onClick: () => void loadMoreLedger()
777
+ },
778
+ "Load more"
779
+ ) : null
780
+ ] : h11(
781
+ "p",
782
+ { style: styles.muted },
783
+ "No wallet \u2014 billing has no wallet configuration."
784
+ )
785
+ ] : null
786
+ ]);
787
+ };
788
+ }
789
+ });
790
+
791
+ // src/screens/AuditScreen.ts
792
+ import { useAdminAudit } from "@fonderie/vue-admin";
793
+ import { defineComponent as defineComponent11, h as h12, ref as ref3 } from "vue";
794
+ var AuditScreen = defineComponent11({
795
+ name: "FonderieAuditScreen",
796
+ props: {
797
+ client: { type: Object, required: true },
798
+ pageSize: { type: Number, default: 50 }
799
+ },
800
+ setup(props) {
801
+ const draft = ref3({ workspaceId: "", type: "", actorId: "" });
802
+ const query = ref3({ limit: props.pageSize });
803
+ const { events, hasMore, isLoading, error, refresh, loadMore } = useAdminAudit(
804
+ props.client,
805
+ query
806
+ );
807
+ const field = (key, placeholder) => h12("input", {
808
+ value: draft.value[key],
809
+ onInput: (e) => draft.value = { ...draft.value, [key]: e.target.value },
810
+ placeholder,
811
+ style: { ...styles.button, cursor: "text" },
812
+ "aria-label": placeholder
813
+ });
814
+ return () => h12("div", { style: styles.container }, [
815
+ h12("h1", { style: styles.title }, "Audit"),
816
+ h12(
817
+ "form",
818
+ {
819
+ style: styles.toolbar,
820
+ onSubmit: (e) => {
821
+ e.preventDefault();
822
+ const d = draft.value;
823
+ query.value = {
824
+ limit: props.pageSize,
825
+ ...d.workspaceId.trim() ? { workspaceId: d.workspaceId.trim() } : {},
826
+ ...d.type.trim() ? { type: d.type.trim() } : {},
827
+ ...d.actorId.trim() ? { actorId: d.actorId.trim() } : {}
828
+ };
829
+ }
830
+ },
831
+ [
832
+ field("workspaceId", "workspace id (all if empty)"),
833
+ field("type", "event type"),
834
+ field("actorId", "actor id"),
835
+ h12(
836
+ "button",
837
+ { type: "submit", style: styles.button, disabled: isLoading.value },
838
+ "Filter"
839
+ ),
840
+ refreshButton("Refresh", isLoading.value, () => void refresh())
841
+ ]
842
+ ),
843
+ error.value ? h12("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
844
+ events.value.length === 0 && !isLoading.value ? h12("p", { style: styles.muted }, "Nothing recorded for this filter.") : table(
845
+ ["When", "Type", "Workspace", "Actor", "Request"],
846
+ events.value.map(
847
+ (e) => h12("tr", { key: e.id }, [
848
+ td(new Date(e.createdAt).toLocaleString(), styles.muted),
849
+ td(e.type, styles.mono),
850
+ td(String(e.payload["workspaceId"] ?? "\u2014"), styles.mono),
851
+ td(e.actorId ?? "\u2014", styles.mono),
852
+ td(e.requestId ?? "", styles.muted)
853
+ ])
854
+ )
855
+ ),
856
+ isLoading.value ? h12("p", { style: styles.status }, "Loading\u2026") : null,
857
+ hasMore.value && !isLoading.value ? h12(
858
+ "button",
859
+ {
860
+ type: "button",
861
+ style: { ...styles.button, marginTop: "8px" },
862
+ onClick: () => void loadMore()
863
+ },
864
+ "Load more"
865
+ ) : null
866
+ ]);
867
+ }
868
+ });
869
+
564
870
  // src/screens/AdminShell.ts
565
871
  var NAV = [
566
872
  { group: "Today", items: [{ page: "attention", label: "Attention" }] },
@@ -574,17 +880,25 @@ var NAV = [
574
880
  ]
575
881
  },
576
882
  { group: "People", items: [{ page: "users", label: "Users", needs: "auth" }] },
883
+ {
884
+ group: "Money",
885
+ items: [
886
+ { page: "catalog", label: "Catalog", needs: "billing" },
887
+ { page: "subscriber", label: "Subscriber", needs: "billing" }
888
+ ]
889
+ },
577
890
  { group: "Settings", items: [{ page: "settings", label: "Config & secrets", needs: "config" }] },
578
891
  { group: "Messaging", items: [{ page: "templates", label: "Templates", needs: "courier" }] },
579
892
  {
580
893
  group: "Activity",
581
894
  items: [
895
+ { page: "audit", label: "Audit", needs: "audit" },
582
896
  { page: "log", label: "Admin log" },
583
897
  { page: "tokens", label: "Access" }
584
898
  ]
585
899
  }
586
900
  ];
587
- var AdminShell = defineComponent9({
901
+ var AdminShell = defineComponent12({
588
902
  name: "FonderieAdminShell",
589
903
  props: {
590
904
  client: { type: Object, required: true },
@@ -594,6 +908,10 @@ var AdminShell = defineComponent9({
594
908
  courierClient: { type: Object, default: void 0 },
595
909
  // Given ⇒ the People page (users) appears; needs @fonderie/auth ≥ 7.8.
596
910
  authClient: { type: Object, default: void 0 },
911
+ // Given ⇒ the Money pages (catalog, subscriber) appear; needs @fonderie/billing ≥ 9.10.
912
+ billingClient: { type: Object, default: void 0 },
913
+ // Given ⇒ the Audit page appears; needs @fonderie/audit ≥ 5.2.
914
+ auditClient: { type: Object, default: void 0 },
597
915
  environment: { type: String, default: void 0 },
598
916
  // Controlled navigation: pass `page` and listen to `navigate` to own the
599
917
  // URL. Omit `page` and the shell keeps it itself.
@@ -601,15 +919,15 @@ var AdminShell = defineComponent9({
601
919
  },
602
920
  emits: { navigate: (_page) => true },
603
921
  setup(props, { emit }) {
604
- const own = ref2("attention");
922
+ const own = ref4("attention");
605
923
  const current = computed2(() => props.page ?? own.value);
606
- const editing = ref2(null);
924
+ const editing = ref4(null);
607
925
  const go = (p) => {
608
926
  editing.value = null;
609
927
  emit("navigate", p);
610
928
  if (props.page === void 0) own.value = p;
611
929
  };
612
- const back = () => h10(
930
+ const back = () => h13(
613
931
  "button",
614
932
  { type: "button", style: styles.button, onClick: () => editing.value = null },
615
933
  "\u2190 Back"
@@ -618,25 +936,43 @@ var AdminShell = defineComponent9({
618
936
  const c = props.client;
619
937
  switch (current.value) {
620
938
  case "attention":
621
- return h10(AttentionScreen, { client: c });
939
+ return h13(AttentionScreen, { client: c });
622
940
  case "modules":
623
- return h10(ModulesScreen, { client: c });
941
+ return h13(ModulesScreen, { client: c });
624
942
  case "doctor":
625
- return h10(DoctorScreen, { client: c });
943
+ return h13(DoctorScreen, { client: c });
626
944
  case "config":
627
- return h10(ConfigScreen, { client: c });
945
+ return h13(ConfigScreen, { client: c });
628
946
  case "routes":
629
- return h10(RoutesScreen, { client: c });
947
+ return h13(RoutesScreen, { client: c });
630
948
  case "tokens":
631
- return h10(TokensScreen, { client: c });
949
+ return h13(TokensScreen, { client: c });
632
950
  case "log":
633
- return h10(AdminLogScreen, { client: c });
951
+ return h13(AdminLogScreen, { client: c });
952
+ case "catalog":
953
+ return props.billingClient ? h13(CatalogScreen, { client: props.billingClient }) : h13(
954
+ "p",
955
+ { style: styles.status },
956
+ "Pass a BillingAdminClient to see the catalog here."
957
+ );
958
+ case "subscriber":
959
+ return props.billingClient ? h13(SubscriberScreen, { client: props.billingClient }) : h13(
960
+ "p",
961
+ { style: styles.status },
962
+ "Pass a BillingAdminClient to look up subscribers here."
963
+ );
964
+ case "audit":
965
+ return props.auditClient ? h13(AuditScreen, { client: props.auditClient }) : h13(
966
+ "p",
967
+ { style: styles.status },
968
+ "Pass an AuditAdminClient to see the audit trail here."
969
+ );
634
970
  case "users":
635
- return props.authClient ? h10(UsersScreen, { client: props.authClient }) : h10("p", { style: styles.status }, "Pass an AuthAdminClient to look up users here.");
971
+ return props.authClient ? h13(UsersScreen, { client: props.authClient }) : h13("p", { style: styles.status }, "Pass an AuthAdminClient to look up users here.");
636
972
  case "settings": {
637
973
  const cc = props.configClient;
638
974
  if (!cc)
639
- return h10(
975
+ return h13(
640
976
  "p",
641
977
  { style: styles.status },
642
978
  "Pass a ConfigAdminClient to manage config and secrets here."
@@ -645,7 +981,7 @@ var AdminShell = defineComponent9({
645
981
  if (e && e.kind !== "template")
646
982
  return [
647
983
  back(),
648
- h10(ConfigEditorScreen, {
984
+ h13(ConfigEditorScreen, {
649
985
  client: cc,
650
986
  kind: e.kind,
651
987
  configKey: e.key,
@@ -653,7 +989,7 @@ var AdminShell = defineComponent9({
653
989
  onSaved: () => editing.value = null
654
990
  })
655
991
  ];
656
- return h10(ConfigListScreen, {
992
+ return h13(ConfigListScreen, {
657
993
  client: cc,
658
994
  ...props.environment !== void 0 ? { environment: props.environment } : {},
659
995
  "onSelect-config": (key) => editing.value = { kind: "config", key },
@@ -663,7 +999,7 @@ var AdminShell = defineComponent9({
663
999
  case "templates": {
664
1000
  const kc = props.courierClient;
665
1001
  if (!kc)
666
- return h10(
1002
+ return h13(
667
1003
  "p",
668
1004
  { style: styles.status },
669
1005
  "Pass a CourierAdminClient to manage templates here."
@@ -672,32 +1008,32 @@ var AdminShell = defineComponent9({
672
1008
  if (e?.kind === "template")
673
1009
  return [
674
1010
  back(),
675
- h10(TemplateEditorScreen, {
1011
+ h13(TemplateEditorScreen, {
676
1012
  client: kc,
677
1013
  type: e.type,
678
1014
  onSaved: () => editing.value = null
679
1015
  })
680
1016
  ];
681
- return h10(TemplateListScreen, {
1017
+ return h13(TemplateListScreen, {
682
1018
  client: kc,
683
1019
  "onSelect-template": (t) => editing.value = { kind: "template", type: t.type }
684
1020
  });
685
1021
  }
686
1022
  }
687
1023
  };
688
- return () => h10("div", { style: styles.shell }, [
689
- h10(
1024
+ return () => h13("div", { style: styles.shell }, [
1025
+ h13(
690
1026
  "nav",
691
1027
  { style: styles.nav, "aria-label": "Admin" },
692
1028
  NAV.map((g) => {
693
1029
  const items = g.items.filter(
694
- (i) => !i.needs || (i.needs === "config" ? props.configClient : i.needs === "courier" ? props.courierClient : props.authClient)
1030
+ (i) => !i.needs || (i.needs === "config" ? props.configClient : i.needs === "courier" ? props.courierClient : i.needs === "auth" ? props.authClient : i.needs === "billing" ? props.billingClient : props.auditClient)
695
1031
  );
696
1032
  if (items.length === 0) return null;
697
- return h10("div", { key: g.group }, [
698
- h10("div", { style: styles.navGroup }, g.group),
1033
+ return h13("div", { key: g.group }, [
1034
+ h13("div", { style: styles.navGroup }, g.group),
699
1035
  ...items.map(
700
- (i) => h10(
1036
+ (i) => h13(
701
1037
  "button",
702
1038
  {
703
1039
  key: i.page,
@@ -715,7 +1051,7 @@ var AdminShell = defineComponent9({
715
1051
  ]);
716
1052
  })
717
1053
  ),
718
- h10("main", { style: styles.main }, body())
1054
+ h13("main", { style: styles.main }, body())
719
1055
  ]);
720
1056
  }
721
1057
  });
@@ -723,10 +1059,13 @@ export {
723
1059
  AdminLogScreen,
724
1060
  AdminShell,
725
1061
  AttentionScreen,
1062
+ AuditScreen,
1063
+ CatalogScreen,
726
1064
  ConfigScreen,
727
1065
  DoctorScreen,
728
1066
  ModulesScreen,
729
1067
  RoutesScreen,
1068
+ SubscriberScreen,
730
1069
  TokensScreen,
731
1070
  UsersScreen
732
1071
  };