@fonderie/vue-admin-screens 1.3.1 → 1.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.
package/dist/index.d.cts CHANGED
@@ -52,6 +52,10 @@ declare const AdminShell: vue.DefineComponent<vue.ExtractPropTypes<{
52
52
  type: StringConstructor;
53
53
  default: undefined;
54
54
  };
55
+ publicConfigUrl: {
56
+ type: StringConstructor;
57
+ default: undefined;
58
+ };
55
59
  }>, () => VNode<vue.RendererNode, vue.RendererElement, {
56
60
  [key: string]: any;
57
61
  }>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {
@@ -105,6 +109,10 @@ declare const AdminShell: vue.DefineComponent<vue.ExtractPropTypes<{
105
109
  type: StringConstructor;
106
110
  default: undefined;
107
111
  };
112
+ publicConfigUrl: {
113
+ type: StringConstructor;
114
+ default: undefined;
115
+ };
108
116
  }>> & Readonly<{
109
117
  onNavigate?: (_page: AdminPage) => any;
110
118
  }>, {
@@ -119,6 +127,7 @@ declare const AdminShell: vue.DefineComponent<vue.ExtractPropTypes<{
119
127
  appName: string;
120
128
  envLabel: string;
121
129
  currentOperator: string;
130
+ publicConfigUrl: string;
122
131
  }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
123
132
 
124
133
  declare const AttentionScreen: vue.DefineComponent<vue.ExtractPropTypes<{
package/dist/index.d.ts CHANGED
@@ -52,6 +52,10 @@ declare const AdminShell: vue.DefineComponent<vue.ExtractPropTypes<{
52
52
  type: StringConstructor;
53
53
  default: undefined;
54
54
  };
55
+ publicConfigUrl: {
56
+ type: StringConstructor;
57
+ default: undefined;
58
+ };
55
59
  }>, () => VNode<vue.RendererNode, vue.RendererElement, {
56
60
  [key: string]: any;
57
61
  }>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {
@@ -105,6 +109,10 @@ declare const AdminShell: vue.DefineComponent<vue.ExtractPropTypes<{
105
109
  type: StringConstructor;
106
110
  default: undefined;
107
111
  };
112
+ publicConfigUrl: {
113
+ type: StringConstructor;
114
+ default: undefined;
115
+ };
108
116
  }>> & Readonly<{
109
117
  onNavigate?: (_page: AdminPage) => any;
110
118
  }>, {
@@ -119,6 +127,7 @@ declare const AdminShell: vue.DefineComponent<vue.ExtractPropTypes<{
119
127
  appName: string;
120
128
  envLabel: string;
121
129
  currentOperator: string;
130
+ publicConfigUrl: string;
122
131
  }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
123
132
 
124
133
  declare const AttentionScreen: vue.DefineComponent<vue.ExtractPropTypes<{
package/dist/index.js CHANGED
@@ -1,6 +1,10 @@
1
1
  // src/screens/AdminShell.ts
2
2
  import { ConfigEditorScreen, ConfigListScreen } from "@fonderie/vue-config-admin-screens";
3
- import { TemplateEditorScreen, TemplateListScreen } from "@fonderie/vue-courier-admin-screens";
3
+ import {
4
+ TemplateCreateScreen,
5
+ TemplateEditorScreen,
6
+ TemplateListScreen
7
+ } from "@fonderie/vue-courier-admin-screens";
4
8
  import { computed as computed3, defineComponent as defineComponent15, h as h17, onBeforeUnmount, onMounted, ref as ref9 } from "vue";
5
9
 
6
10
  // src/styles.ts
@@ -1720,16 +1724,22 @@ function useSubscriptionIndex(client) {
1720
1724
  // src/screens/SubscriberBilling.ts
1721
1725
  import { useAdminSubscriber } from "@fonderie/vue-admin";
1722
1726
  import { computed, defineComponent as defineComponent10, h as h12, ref as ref5 } from "vue";
1723
- var SubscriberBilling = defineComponent10({
1724
- name: "FonderieSubscriberBilling",
1727
+ var SubscriberBillingInner = defineComponent10({
1728
+ name: "FonderieSubscriberBillingInner",
1725
1729
  props: {
1726
1730
  client: { type: Object, required: true },
1727
1731
  subscriberType: { type: String, required: true },
1728
- subscriberId: { type: String, required: true }
1732
+ subscriberId: { type: String, required: true },
1733
+ currency: { type: String, default: "" }
1729
1734
  },
1730
- setup(props) {
1735
+ emits: { "change-currency": (_currency) => true },
1736
+ setup(props, { emit }) {
1731
1737
  const subscriber = computed(() => ({ type: props.subscriberType, id: props.subscriberId }));
1732
- const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = useAdminSubscriber(props.client, subscriber, { limit: 20 });
1738
+ const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = useAdminSubscriber(props.client, subscriber, {
1739
+ limit: 20,
1740
+ ...props.currency ? { currency: props.currency } : {}
1741
+ });
1742
+ const currencyDraft = ref5(props.currency);
1733
1743
  const amount = ref5("");
1734
1744
  const note = ref5("");
1735
1745
  const granted = ref5(null);
@@ -1770,7 +1780,43 @@ var SubscriberBilling = defineComponent10({
1770
1780
  ) : null
1771
1781
  ]),
1772
1782
  h12("div", { style: styles.card }, [
1773
- h12("div", { style: styles.statLabel }, "Credits"),
1783
+ h12(
1784
+ "div",
1785
+ {
1786
+ style: {
1787
+ display: "flex",
1788
+ justifyContent: "space-between",
1789
+ alignItems: "center",
1790
+ gap: "8px"
1791
+ }
1792
+ },
1793
+ [
1794
+ h12("div", { style: styles.statLabel }, "Credits"),
1795
+ h12(
1796
+ "form",
1797
+ {
1798
+ style: { display: "flex", gap: "4px" },
1799
+ onSubmit: (e) => {
1800
+ e.preventDefault();
1801
+ emit("change-currency", currencyDraft.value.trim().toUpperCase());
1802
+ }
1803
+ },
1804
+ [
1805
+ h12("input", {
1806
+ value: currencyDraft.value,
1807
+ onInput: (e) => {
1808
+ currencyDraft.value = e.target.value;
1809
+ },
1810
+ placeholder: w?.currency ?? "currency",
1811
+ maxlength: 8,
1812
+ style: { ...styles.input, height: "26px", width: "84px", fontSize: "12px" },
1813
+ "aria-label": "Wallet currency",
1814
+ title: "Show the wallet in another currency"
1815
+ })
1816
+ ]
1817
+ )
1818
+ ]
1819
+ ),
1774
1820
  ...w ? [
1775
1821
  h12("div", { style: styles.statValue }, [
1776
1822
  `${w.balance} `,
@@ -1867,6 +1913,27 @@ var SubscriberBilling = defineComponent10({
1867
1913
  };
1868
1914
  }
1869
1915
  });
1916
+ var SubscriberBilling = defineComponent10({
1917
+ name: "FonderieSubscriberBilling",
1918
+ props: {
1919
+ client: { type: Object, required: true },
1920
+ subscriberType: { type: String, required: true },
1921
+ subscriberId: { type: String, required: true }
1922
+ },
1923
+ setup(props) {
1924
+ const currency = ref5("");
1925
+ return () => h12(SubscriberBillingInner, {
1926
+ key: `${props.subscriberType}:${props.subscriberId}:${currency.value}`,
1927
+ client: props.client,
1928
+ subscriberType: props.subscriberType,
1929
+ subscriberId: props.subscriberId,
1930
+ currency: currency.value,
1931
+ "onChange-currency": (c) => {
1932
+ currency.value = c;
1933
+ }
1934
+ });
1935
+ }
1936
+ });
1870
1937
 
1871
1938
  // src/screens/UsersScreen.ts
1872
1939
  var UsersScreen = defineComponent11({
@@ -1885,7 +1952,9 @@ var UsersScreen = defineComponent11({
1885
1952
  const email = ref6("");
1886
1953
  const selectedId = ref6(props.openUserId ?? "");
1887
1954
  const plans = useSubscriptionIndex(props.billingClient);
1888
- const list = useAdminUsers(props.client, { limit: props.pageSize });
1955
+ const showDeleted = ref6(false);
1956
+ const activeList = useAdminUsers(props.client, { limit: props.pageSize });
1957
+ const deletedList = useAdminUsers(props.client, { limit: props.pageSize, deleted: true });
1889
1958
  const { user, isLoading, error, suspend, unsuspend, revokeSessions } = useAdminUser(
1890
1959
  props.client,
1891
1960
  { email, id: selectedId }
@@ -1913,6 +1982,7 @@ var UsersScreen = defineComponent11({
1913
1982
  };
1914
1983
  return () => {
1915
1984
  const u = user.value;
1985
+ const list = showDeleted.value ? deletedList : activeList;
1916
1986
  return h13("div", { style: styles.container }, [
1917
1987
  pageHeader(
1918
1988
  "Users",
@@ -1942,7 +2012,37 @@ var UsersScreen = defineComponent11({
1942
2012
  { type: "submit", style: styles.buttonPrimary, disabled: isLoading.value },
1943
2013
  [icon("search", 14), "Look up"]
1944
2014
  ),
1945
- showList.value ? refreshButton2("Refresh", list.isLoading.value, () => void list.refresh()) : actionButton([icon("back", 14), "All users"], false, clear, styles.buttonGhost)
2015
+ showList.value ? refreshButton2("Refresh", list.isLoading.value, () => void list.refresh()) : actionButton([icon("back", 14), "All users"], false, clear, styles.buttonGhost),
2016
+ showList.value ? h13(
2017
+ "fieldset",
2018
+ {
2019
+ style: {
2020
+ display: "flex",
2021
+ gap: "6px",
2022
+ border: "none",
2023
+ margin: "0 0 0 auto",
2024
+ padding: "0",
2025
+ minWidth: "0"
2026
+ },
2027
+ "aria-label": "Which accounts"
2028
+ },
2029
+ [false, true].map(
2030
+ (d) => h13(
2031
+ "button",
2032
+ {
2033
+ key: String(d),
2034
+ type: "button",
2035
+ "aria-pressed": showDeleted.value === d,
2036
+ onClick: () => showDeleted.value = d,
2037
+ style: {
2038
+ ...showDeleted.value === d ? styles.buttonPrimary : styles.button,
2039
+ height: "28px"
2040
+ }
2041
+ },
2042
+ d ? "Deleted" : "Active"
2043
+ )
2044
+ )
2045
+ ) : null
1946
2046
  ]
1947
2047
  ),
1948
2048
  showList.value ? h13("div", [
@@ -2022,7 +2122,10 @@ var UsersScreen = defineComponent11({
2022
2122
  row("created", new Date(u.createdAt).toLocaleString())
2023
2123
  ])
2024
2124
  ]),
2025
- h13("div", { style: { ...styles.toolbar, marginTop: "12px" } }, [
2125
+ u.deletedAt ? h13("div", { style: { ...styles.notice, marginTop: "12px" }, role: "status" }, [
2126
+ h13("strong", `Deleted on ${new Date(u.deletedAt).toLocaleString()}.`),
2127
+ " The account cannot sign in, and it is erased when the retention purge runs. Its billing records stay."
2128
+ ]) : h13("div", { style: { ...styles.toolbar, marginTop: "12px" } }, [
2026
2129
  u.suspended ? actionButton("Unsuspend", isLoading.value, () => void unsuspend()) : actionButton(
2027
2130
  "Suspend",
2028
2131
  isLoading.value,
@@ -2042,46 +2145,60 @@ var UsersScreen = defineComponent11({
2042
2145
  subscriberType: "user",
2043
2146
  subscriberId: u.id
2044
2147
  }) : null,
2045
- h13("h2", { style: styles.subtitle }, "Live sessions"),
2046
- sessions.sessions.value.length === 0 ? h13("p", { style: styles.muted }, "None.") : h13(
2047
- "ul",
2048
- { style: styles.list },
2049
- sessions.sessions.value.map(
2050
- (s) => h13("li", { key: s.id, style: styles.row }, [
2051
- h13("span", { style: styles.mono }, s.ipAddress ?? "\u2014"),
2052
- " ",
2053
- h13("span", { style: styles.muted }, describeLocation(s.location) ?? ""),
2054
- " ",
2055
- h13("span", { style: styles.muted }, s.userAgent ?? ""),
2056
- " ",
2057
- h13(
2058
- "span",
2059
- { style: styles.muted },
2060
- `since ${new Date(s.createdAt).toLocaleString()}`
2061
- )
2062
- ])
2063
- )
2064
- ),
2065
- h13("h2", { style: styles.subtitle }, "Recent sign-ins"),
2066
- history.events.value.length === 0 && !history.isLoading.value ? h13("p", { style: styles.muted }, "None recorded.") : h13(
2067
- "ul",
2068
- { style: styles.list },
2069
- history.events.value.map(
2070
- (e) => h13("li", { key: e.id, style: styles.row }, [
2071
- pill(e.outcome === "success" ? "ok" : "bad", e.outcome),
2072
- " ",
2073
- h13("span", { style: styles.muted }, e.method),
2074
- " ",
2075
- h13("span", { style: styles.mono }, e.ipAddress ?? "\u2014"),
2076
- " ",
2077
- h13("span", { style: styles.muted }, describeLocation(e.location) ?? ""),
2078
- e.location?.proxy || e.location?.hosting ? [" ", pill("warn", e.location.proxy ? "proxy/VPN" : "hosting")] : "",
2079
- " ",
2080
- h13("span", { style: styles.muted }, new Date(e.createdAt).toLocaleString())
2081
- ])
2082
- )
2083
- ),
2084
- history.hasMore.value && !history.isLoading.value ? loadMoreButton(() => void history.loadMore()) : null
2148
+ u.deletedAt ? null : [
2149
+ h13("h2", { style: styles.subtitle }, "Live sessions"),
2150
+ sessions.sessions.value.length === 0 ? h13("p", { style: styles.muted }, "None.") : h13(
2151
+ "ul",
2152
+ { style: styles.list },
2153
+ sessions.sessions.value.map(
2154
+ (s) => h13("li", { key: s.id, style: styles.row }, [
2155
+ h13("span", { style: styles.mono }, s.ipAddress ?? "\u2014"),
2156
+ " ",
2157
+ h13(
2158
+ "span",
2159
+ { style: styles.muted },
2160
+ describeLocation(s.location) ?? ""
2161
+ ),
2162
+ " ",
2163
+ h13("span", { style: styles.muted }, s.userAgent ?? ""),
2164
+ " ",
2165
+ h13(
2166
+ "span",
2167
+ { style: styles.muted },
2168
+ `since ${new Date(s.createdAt).toLocaleString()}`
2169
+ )
2170
+ ])
2171
+ )
2172
+ ),
2173
+ h13("h2", { style: styles.subtitle }, "Recent sign-ins"),
2174
+ history.events.value.length === 0 && !history.isLoading.value ? h13("p", { style: styles.muted }, "None recorded.") : h13(
2175
+ "ul",
2176
+ { style: styles.list },
2177
+ history.events.value.map(
2178
+ (e) => h13("li", { key: e.id, style: styles.row }, [
2179
+ pill(e.outcome === "success" ? "ok" : "bad", e.outcome),
2180
+ " ",
2181
+ h13("span", { style: styles.muted }, e.method),
2182
+ " ",
2183
+ h13("span", { style: styles.mono }, e.ipAddress ?? "\u2014"),
2184
+ " ",
2185
+ h13(
2186
+ "span",
2187
+ { style: styles.muted },
2188
+ describeLocation(e.location) ?? ""
2189
+ ),
2190
+ e.location?.proxy || e.location?.hosting ? [" ", pill("warn", e.location.proxy ? "proxy/VPN" : "hosting")] : "",
2191
+ " ",
2192
+ h13(
2193
+ "span",
2194
+ { style: styles.muted },
2195
+ new Date(e.createdAt).toLocaleString()
2196
+ )
2197
+ ])
2198
+ )
2199
+ ),
2200
+ history.hasMore.value && !history.isLoading.value ? loadMoreButton(() => void history.loadMore()) : null
2201
+ ]
2085
2202
  ] : null
2086
2203
  ]);
2087
2204
  };
@@ -2346,7 +2463,7 @@ var AuditScreen = defineComponent14({
2346
2463
  pageSize: { type: Number, default: 50 }
2347
2464
  },
2348
2465
  setup(props) {
2349
- const draft = ref8({ workspaceId: "", type: "", actorId: "" });
2466
+ const draft = ref8({ workspaceId: "", type: "", actorId: "", from: "", to: "" });
2350
2467
  const query = ref8({ limit: props.pageSize });
2351
2468
  const { events, hasMore, isLoading, error, refresh, loadMore } = useAdminAudit(
2352
2469
  props.client,
@@ -2375,7 +2492,10 @@ var AuditScreen = defineComponent14({
2375
2492
  limit: props.pageSize,
2376
2493
  ...d.workspaceId.trim() ? { workspaceId: d.workspaceId.trim() } : {},
2377
2494
  ...d.type.trim() ? { type: d.type.trim() } : {},
2378
- ...d.actorId.trim() ? { actorId: d.actorId.trim() } : {}
2495
+ ...d.actorId.trim() ? { actorId: d.actorId.trim() } : {},
2496
+ // Dates are whole days in the operator's time zone: "to" includes that day.
2497
+ ...d.from ? { from: /* @__PURE__ */ new Date(`${d.from}T00:00:00`) } : {},
2498
+ ...d.to ? { to: /* @__PURE__ */ new Date(`${d.to}T23:59:59.999`) } : {}
2379
2499
  };
2380
2500
  }
2381
2501
  },
@@ -2383,6 +2503,16 @@ var AuditScreen = defineComponent14({
2383
2503
  field("workspaceId", "workspace id (all if empty)"),
2384
2504
  field("type", "event type"),
2385
2505
  field("actorId", "actor id"),
2506
+ ...["from", "to"].map(
2507
+ (k) => h16("input", {
2508
+ type: "date",
2509
+ value: draft.value[k],
2510
+ onInput: (e) => draft.value = { ...draft.value, [k]: e.target.value },
2511
+ style: styles.input,
2512
+ "aria-label": k === "from" ? "From date" : "To date",
2513
+ title: k === "from" ? "From (inclusive)" : "To (inclusive)"
2514
+ })
2515
+ ),
2386
2516
  h16(
2387
2517
  "button",
2388
2518
  { type: "submit", style: styles.buttonPrimary, disabled: isLoading.value },
@@ -2488,7 +2618,10 @@ var AdminShell = defineComponent15({
2488
2618
  // shows the Operators page.
2489
2619
  operators: { type: Boolean, default: false },
2490
2620
  // The signed-in operator's email, when a person (not a token) is signed in.
2491
- currentOperator: { type: String, default: void 0 }
2621
+ currentOperator: { type: String, default: void 0 },
2622
+ // Where the app serves GET /config/public — marks public keys on the
2623
+ // Config page and previews exactly what frontends receive.
2624
+ publicConfigUrl: { type: String, default: void 0 }
2492
2625
  },
2493
2626
  // `footer` slot: pinned to the bottom of the sidebar — session controls
2494
2627
  // (theme, sign out). The React shell takes the same thing as a `footer` prop.
@@ -2567,43 +2700,83 @@ var AdminShell = defineComponent15({
2567
2700
  const cc = props.configClient;
2568
2701
  if (!cc) return missing("Pass a ConfigAdminClient to manage config and secrets here.");
2569
2702
  const e = editing.value;
2570
- if (e && e.kind !== "template")
2703
+ if (e && (e.kind === "config" || e.kind === "secret")) {
2704
+ const env = e.environment ?? props.environment;
2571
2705
  return [
2572
2706
  back("Config & secrets"),
2573
2707
  h17(ConfigEditorScreen, {
2708
+ key: `${e.kind}:${e.key}:${e.environment ?? ""}`,
2574
2709
  client: cc,
2575
2710
  kind: e.kind,
2576
2711
  configKey: e.key,
2577
- ...props.environment !== void 0 ? { environment: props.environment } : {},
2578
- onSaved: () => editing.value = null
2712
+ ...env !== void 0 ? { environment: env } : {},
2713
+ ...e.environments ? { environments: e.environments } : {},
2714
+ onSaved: () => editing.value = null,
2715
+ onDeleted: () => editing.value = null
2579
2716
  })
2580
2717
  ];
2718
+ }
2719
+ const create = (kind) => (c2) => editing.value = {
2720
+ kind,
2721
+ key: "",
2722
+ environments: c2.environments,
2723
+ ...c2.environment ? { environment: c2.environment } : {}
2724
+ };
2581
2725
  return h17(ConfigListScreen, {
2582
2726
  client: cc,
2583
2727
  ...props.environment !== void 0 ? { environment: props.environment } : {},
2584
- "onSelect-config": (key) => editing.value = { kind: "config", key },
2585
- "onSelect-secret": (key) => editing.value = { kind: "secret", key },
2728
+ ...props.publicConfigUrl ? { publicConfigUrl: props.publicConfigUrl } : {},
2729
+ "onSelect-config": (key, environment) => editing.value = { kind: "config", key, environment },
2730
+ "onSelect-secret": (key, environment) => editing.value = { kind: "secret", key, environment },
2586
2731
  allowCreate: true,
2587
- "onCreate-config": () => editing.value = { kind: "config", key: "" },
2588
- "onCreate-secret": () => editing.value = { kind: "secret", key: "" }
2732
+ "onCreate-config": create("config"),
2733
+ "onCreate-secret": create("secret")
2589
2734
  });
2590
2735
  }
2591
2736
  case "templates": {
2592
2737
  const kc = props.courierClient;
2593
2738
  if (!kc) return missing("Pass a CourierAdminClient to manage templates here.");
2594
2739
  const e = editing.value;
2740
+ if (e?.kind === "template-new")
2741
+ return [
2742
+ back("Templates"),
2743
+ h17(TemplateCreateScreen, {
2744
+ client: kc,
2745
+ ...e.type ? { type: e.type } : {},
2746
+ ...e.locales ? { locales: e.locales } : {},
2747
+ onCreated: (c2) => editing.value = {
2748
+ kind: "template",
2749
+ type: c2.type,
2750
+ locale: c2.locale,
2751
+ system: false
2752
+ }
2753
+ })
2754
+ ];
2595
2755
  if (e?.kind === "template")
2596
2756
  return [
2597
2757
  back("Templates"),
2598
2758
  h17(TemplateEditorScreen, {
2759
+ key: `${e.type}:${e.locale ?? ""}`,
2599
2760
  client: kc,
2600
2761
  type: e.type,
2601
- onSaved: () => editing.value = null
2762
+ locale: e.locale,
2763
+ system: e.system,
2764
+ allowAddLocale: true,
2765
+ onSaved: () => editing.value = null,
2766
+ onDeleted: () => editing.value = null,
2767
+ "onAdd-locale": (type) => editing.value = { kind: "template-new", type }
2602
2768
  })
2603
2769
  ];
2604
2770
  return h17(TemplateListScreen, {
2605
2771
  client: kc,
2606
- "onSelect-template": (t2) => editing.value = { kind: "template", type: t2.type }
2772
+ allowCreate: true,
2773
+ "onSelect-template": (t2) => editing.value = {
2774
+ kind: "template",
2775
+ type: t2.type,
2776
+ locale: t2.locale ?? null,
2777
+ system: t2.system === true
2778
+ },
2779
+ "onCreate-template": (c2) => editing.value = { kind: "template-new", locales: c2.locales }
2607
2780
  });
2608
2781
  }
2609
2782
  }