@fonderie/vue-admin-screens 0.2.0 → 0.3.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 defineComponent11, h as h12, ref as ref3 } from "vue";
5
5
 
6
6
  // src/styles.ts
7
7
  var styles = {
@@ -561,6 +561,233 @@ 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
+
564
791
  // src/screens/AdminShell.ts
565
792
  var NAV = [
566
793
  { group: "Today", items: [{ page: "attention", label: "Attention" }] },
@@ -574,6 +801,13 @@ var NAV = [
574
801
  ]
575
802
  },
576
803
  { group: "People", items: [{ page: "users", label: "Users", needs: "auth" }] },
804
+ {
805
+ group: "Money",
806
+ items: [
807
+ { page: "catalog", label: "Catalog", needs: "billing" },
808
+ { page: "subscriber", label: "Subscriber", needs: "billing" }
809
+ ]
810
+ },
577
811
  { group: "Settings", items: [{ page: "settings", label: "Config & secrets", needs: "config" }] },
578
812
  { group: "Messaging", items: [{ page: "templates", label: "Templates", needs: "courier" }] },
579
813
  {
@@ -584,7 +818,7 @@ var NAV = [
584
818
  ]
585
819
  }
586
820
  ];
587
- var AdminShell = defineComponent9({
821
+ var AdminShell = defineComponent11({
588
822
  name: "FonderieAdminShell",
589
823
  props: {
590
824
  client: { type: Object, required: true },
@@ -594,6 +828,8 @@ var AdminShell = defineComponent9({
594
828
  courierClient: { type: Object, default: void 0 },
595
829
  // Given ⇒ the People page (users) appears; needs @fonderie/auth ≥ 7.8.
596
830
  authClient: { type: Object, default: void 0 },
831
+ // Given ⇒ the Money pages (catalog, subscriber) appear; needs @fonderie/billing ≥ 9.10.
832
+ billingClient: { type: Object, default: void 0 },
597
833
  environment: { type: String, default: void 0 },
598
834
  // Controlled navigation: pass `page` and listen to `navigate` to own the
599
835
  // URL. Omit `page` and the shell keeps it itself.
@@ -601,15 +837,15 @@ var AdminShell = defineComponent9({
601
837
  },
602
838
  emits: { navigate: (_page) => true },
603
839
  setup(props, { emit }) {
604
- const own = ref2("attention");
840
+ const own = ref3("attention");
605
841
  const current = computed2(() => props.page ?? own.value);
606
- const editing = ref2(null);
842
+ const editing = ref3(null);
607
843
  const go = (p) => {
608
844
  editing.value = null;
609
845
  emit("navigate", p);
610
846
  if (props.page === void 0) own.value = p;
611
847
  };
612
- const back = () => h10(
848
+ const back = () => h12(
613
849
  "button",
614
850
  { type: "button", style: styles.button, onClick: () => editing.value = null },
615
851
  "\u2190 Back"
@@ -618,25 +854,37 @@ var AdminShell = defineComponent9({
618
854
  const c = props.client;
619
855
  switch (current.value) {
620
856
  case "attention":
621
- return h10(AttentionScreen, { client: c });
857
+ return h12(AttentionScreen, { client: c });
622
858
  case "modules":
623
- return h10(ModulesScreen, { client: c });
859
+ return h12(ModulesScreen, { client: c });
624
860
  case "doctor":
625
- return h10(DoctorScreen, { client: c });
861
+ return h12(DoctorScreen, { client: c });
626
862
  case "config":
627
- return h10(ConfigScreen, { client: c });
863
+ return h12(ConfigScreen, { client: c });
628
864
  case "routes":
629
- return h10(RoutesScreen, { client: c });
865
+ return h12(RoutesScreen, { client: c });
630
866
  case "tokens":
631
- return h10(TokensScreen, { client: c });
867
+ return h12(TokensScreen, { client: c });
632
868
  case "log":
633
- return h10(AdminLogScreen, { client: c });
869
+ return h12(AdminLogScreen, { client: c });
870
+ case "catalog":
871
+ return props.billingClient ? h12(CatalogScreen, { client: props.billingClient }) : h12(
872
+ "p",
873
+ { style: styles.status },
874
+ "Pass a BillingAdminClient to see the catalog here."
875
+ );
876
+ case "subscriber":
877
+ return props.billingClient ? h12(SubscriberScreen, { client: props.billingClient }) : h12(
878
+ "p",
879
+ { style: styles.status },
880
+ "Pass a BillingAdminClient to look up subscribers here."
881
+ );
634
882
  case "users":
635
- return props.authClient ? h10(UsersScreen, { client: props.authClient }) : h10("p", { style: styles.status }, "Pass an AuthAdminClient to look up users here.");
883
+ return props.authClient ? h12(UsersScreen, { client: props.authClient }) : h12("p", { style: styles.status }, "Pass an AuthAdminClient to look up users here.");
636
884
  case "settings": {
637
885
  const cc = props.configClient;
638
886
  if (!cc)
639
- return h10(
887
+ return h12(
640
888
  "p",
641
889
  { style: styles.status },
642
890
  "Pass a ConfigAdminClient to manage config and secrets here."
@@ -645,7 +893,7 @@ var AdminShell = defineComponent9({
645
893
  if (e && e.kind !== "template")
646
894
  return [
647
895
  back(),
648
- h10(ConfigEditorScreen, {
896
+ h12(ConfigEditorScreen, {
649
897
  client: cc,
650
898
  kind: e.kind,
651
899
  configKey: e.key,
@@ -653,7 +901,7 @@ var AdminShell = defineComponent9({
653
901
  onSaved: () => editing.value = null
654
902
  })
655
903
  ];
656
- return h10(ConfigListScreen, {
904
+ return h12(ConfigListScreen, {
657
905
  client: cc,
658
906
  ...props.environment !== void 0 ? { environment: props.environment } : {},
659
907
  "onSelect-config": (key) => editing.value = { kind: "config", key },
@@ -663,7 +911,7 @@ var AdminShell = defineComponent9({
663
911
  case "templates": {
664
912
  const kc = props.courierClient;
665
913
  if (!kc)
666
- return h10(
914
+ return h12(
667
915
  "p",
668
916
  { style: styles.status },
669
917
  "Pass a CourierAdminClient to manage templates here."
@@ -672,32 +920,32 @@ var AdminShell = defineComponent9({
672
920
  if (e?.kind === "template")
673
921
  return [
674
922
  back(),
675
- h10(TemplateEditorScreen, {
923
+ h12(TemplateEditorScreen, {
676
924
  client: kc,
677
925
  type: e.type,
678
926
  onSaved: () => editing.value = null
679
927
  })
680
928
  ];
681
- return h10(TemplateListScreen, {
929
+ return h12(TemplateListScreen, {
682
930
  client: kc,
683
931
  "onSelect-template": (t) => editing.value = { kind: "template", type: t.type }
684
932
  });
685
933
  }
686
934
  }
687
935
  };
688
- return () => h10("div", { style: styles.shell }, [
689
- h10(
936
+ return () => h12("div", { style: styles.shell }, [
937
+ h12(
690
938
  "nav",
691
939
  { style: styles.nav, "aria-label": "Admin" },
692
940
  NAV.map((g) => {
693
941
  const items = g.items.filter(
694
- (i) => !i.needs || (i.needs === "config" ? props.configClient : i.needs === "courier" ? props.courierClient : props.authClient)
942
+ (i) => !i.needs || (i.needs === "config" ? props.configClient : i.needs === "courier" ? props.courierClient : i.needs === "auth" ? props.authClient : props.billingClient)
695
943
  );
696
944
  if (items.length === 0) return null;
697
- return h10("div", { key: g.group }, [
698
- h10("div", { style: styles.navGroup }, g.group),
945
+ return h12("div", { key: g.group }, [
946
+ h12("div", { style: styles.navGroup }, g.group),
699
947
  ...items.map(
700
- (i) => h10(
948
+ (i) => h12(
701
949
  "button",
702
950
  {
703
951
  key: i.page,
@@ -715,7 +963,7 @@ var AdminShell = defineComponent9({
715
963
  ]);
716
964
  })
717
965
  ),
718
- h10("main", { style: styles.main }, body())
966
+ h12("main", { style: styles.main }, body())
719
967
  ]);
720
968
  }
721
969
  });
@@ -723,10 +971,12 @@ export {
723
971
  AdminLogScreen,
724
972
  AdminShell,
725
973
  AttentionScreen,
974
+ CatalogScreen,
726
975
  ConfigScreen,
727
976
  DoctorScreen,
728
977
  ModulesScreen,
729
978
  RoutesScreen,
979
+ SubscriberScreen,
730
980
  TokensScreen,
731
981
  UsersScreen
732
982
  };