@fonderie/vue-admin-screens 1.2.0 → 1.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.cjs +517 -367
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -3
- package/dist/index.d.ts +68 -3
- package/dist/index.js +505 -356
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
4
|
+
import { computed as computed3, defineComponent as defineComponent15, h as h17, onBeforeUnmount, onMounted, ref as ref9 } from "vue";
|
|
5
5
|
|
|
6
6
|
// src/styles.ts
|
|
7
7
|
var t = {
|
|
@@ -557,6 +557,12 @@ function empty(title, body, iconName = "check") {
|
|
|
557
557
|
body ? h("div", body) : null
|
|
558
558
|
]);
|
|
559
559
|
}
|
|
560
|
+
function refreshButton(onClick, busy, label = "Refresh") {
|
|
561
|
+
return h("button", { type: "button", style: styles.button, onClick, disabled: busy }, [
|
|
562
|
+
icon("refresh", 14),
|
|
563
|
+
busy ? "Working\u2026" : label
|
|
564
|
+
]);
|
|
565
|
+
}
|
|
560
566
|
function methodChip(method) {
|
|
561
567
|
const tone = method === "GET" ? "ok" : method === "DELETE" ? "bad" : method === "POST" ? "info" : "warn";
|
|
562
568
|
const color = toneColor[tone];
|
|
@@ -608,7 +614,7 @@ function table(headers, rows) {
|
|
|
608
614
|
]);
|
|
609
615
|
}
|
|
610
616
|
var td = (children, extra = {}) => h2("td", { style: { ...styles.td, ...extra } }, children);
|
|
611
|
-
var
|
|
617
|
+
var refreshButton2 = (label, disabled, onClick) => h2("button", { type: "button", style: styles.button, disabled, onClick }, [
|
|
612
618
|
icon("refresh", 14),
|
|
613
619
|
disabled ? "Working\u2026" : label
|
|
614
620
|
]);
|
|
@@ -634,7 +640,7 @@ var AdminLogScreen = defineComponent({
|
|
|
634
640
|
pageHeader(
|
|
635
641
|
"Admin log",
|
|
636
642
|
"Every request made through this surface, newest first \u2014 refused ones included.",
|
|
637
|
-
[
|
|
643
|
+
[refreshButton2("Refresh", isLoading.value, () => void refresh())]
|
|
638
644
|
),
|
|
639
645
|
error.value ? h3(
|
|
640
646
|
"p",
|
|
@@ -775,7 +781,7 @@ var AttentionScreen = defineComponent2({
|
|
|
775
781
|
)
|
|
776
782
|
];
|
|
777
783
|
},
|
|
778
|
-
[
|
|
784
|
+
[refreshButton2("Refresh", isLoading.value, () => void refresh())],
|
|
779
785
|
{
|
|
780
786
|
lead: a ? `What needs you on this deployment \xB7 checked ${new Date(a.generatedAt).toLocaleTimeString()}` : "What needs you on this deployment"
|
|
781
787
|
}
|
|
@@ -955,7 +961,7 @@ var DoctorScreen = defineComponent4({
|
|
|
955
961
|
report.value.ok ? "ok" : "bad",
|
|
956
962
|
report.value.ok ? "all checks pass" : "a check failed"
|
|
957
963
|
) : null,
|
|
958
|
-
|
|
964
|
+
refreshButton2("Run again", isLoading.value, () => void refresh())
|
|
959
965
|
],
|
|
960
966
|
{
|
|
961
967
|
loadingText: "Running the checks\u2026",
|
|
@@ -1158,7 +1164,7 @@ var MigrationsScreen = defineComponent7({
|
|
|
1158
1164
|
...behind.map((m) => section(m, r.everApplied))
|
|
1159
1165
|
].filter(Boolean);
|
|
1160
1166
|
},
|
|
1161
|
-
[
|
|
1167
|
+
[refreshButton2("Refresh", isLoading.value, () => void refresh())],
|
|
1162
1168
|
{
|
|
1163
1169
|
lead: "Schema changes each module ships, and whether this database has them.",
|
|
1164
1170
|
errorText: (e) => e.status === 403 ? "Applying migrations needs a token with the write scope." : e.explanation
|
|
@@ -1673,28 +1679,233 @@ import {
|
|
|
1673
1679
|
useAdminUserSessions,
|
|
1674
1680
|
useAdminUsers
|
|
1675
1681
|
} from "@fonderie/vue-admin";
|
|
1676
|
-
import { computed, defineComponent as
|
|
1677
|
-
|
|
1682
|
+
import { computed as computed2, defineComponent as defineComponent11, h as h13, ref as ref6 } from "vue";
|
|
1683
|
+
|
|
1684
|
+
// src/billing.ts
|
|
1685
|
+
import { ref as ref4 } from "vue";
|
|
1686
|
+
function periodEnd(s) {
|
|
1687
|
+
if (!s.currentPeriodEnd) return null;
|
|
1688
|
+
const date = new Date(s.currentPeriodEnd).toLocaleDateString();
|
|
1689
|
+
if (s.status === "canceled")
|
|
1690
|
+
return { label: new Date(s.currentPeriodEnd) < /* @__PURE__ */ new Date() ? "Ended" : "Ends", date };
|
|
1691
|
+
if (s.cancelAtPeriodEnd) return { label: "Ends", date };
|
|
1692
|
+
return { label: "Renews", date };
|
|
1693
|
+
}
|
|
1694
|
+
var statusTone = (status) => status === "active" ? "ok" : status === "trialing" ? "info" : status === "past_due" || status === "unpaid" ? "warn" : "neutral";
|
|
1695
|
+
function useSubscriptionIndex(client) {
|
|
1696
|
+
const index = ref4(null);
|
|
1697
|
+
if (!client) return index;
|
|
1698
|
+
void (async () => {
|
|
1699
|
+
const map = /* @__PURE__ */ new Map();
|
|
1700
|
+
let cursor;
|
|
1701
|
+
for (let page2 = 0; page2 < 20; page2++) {
|
|
1702
|
+
const { result } = await client.listSubscriptions({
|
|
1703
|
+
limit: 100,
|
|
1704
|
+
...cursor ? { cursor } : {}
|
|
1705
|
+
});
|
|
1706
|
+
for (const s of result.subscriptions) {
|
|
1707
|
+
const key = `${s.subscriberType}/${s.subscriberId}`;
|
|
1708
|
+
if (!map.has(key)) map.set(key, s);
|
|
1709
|
+
}
|
|
1710
|
+
if (!result.nextCursor) break;
|
|
1711
|
+
cursor = result.nextCursor;
|
|
1712
|
+
}
|
|
1713
|
+
index.value = map;
|
|
1714
|
+
})().catch(() => {
|
|
1715
|
+
index.value = /* @__PURE__ */ new Map();
|
|
1716
|
+
});
|
|
1717
|
+
return index;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
// src/screens/SubscriberBilling.ts
|
|
1721
|
+
import { useAdminSubscriber } from "@fonderie/vue-admin";
|
|
1722
|
+
import { computed, defineComponent as defineComponent10, h as h12, ref as ref5 } from "vue";
|
|
1723
|
+
var SubscriberBilling = defineComponent10({
|
|
1724
|
+
name: "FonderieSubscriberBilling",
|
|
1725
|
+
props: {
|
|
1726
|
+
client: { type: Object, required: true },
|
|
1727
|
+
subscriberType: { type: String, required: true },
|
|
1728
|
+
subscriberId: { type: String, required: true }
|
|
1729
|
+
},
|
|
1730
|
+
setup(props) {
|
|
1731
|
+
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 });
|
|
1733
|
+
const amount = ref5("");
|
|
1734
|
+
const note = ref5("");
|
|
1735
|
+
const granted = ref5(null);
|
|
1736
|
+
return () => {
|
|
1737
|
+
const s = subscription.value;
|
|
1738
|
+
const w = wallet.value;
|
|
1739
|
+
if (isLoading.value && !w && !s) return h12("p", { style: styles.status }, "Loading billing\u2026");
|
|
1740
|
+
const end = s ? periodEnd(s) : null;
|
|
1741
|
+
return h12("div", [
|
|
1742
|
+
error.value ? h12("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
|
|
1743
|
+
h12("div", { style: { ...styles.grid, marginBottom: "16px" } }, [
|
|
1744
|
+
h12("div", { style: styles.card }, [
|
|
1745
|
+
h12("div", { style: styles.statLabel }, "Plan"),
|
|
1746
|
+
h12(
|
|
1747
|
+
"div",
|
|
1748
|
+
{
|
|
1749
|
+
style: {
|
|
1750
|
+
display: "flex",
|
|
1751
|
+
alignItems: "center",
|
|
1752
|
+
gap: "8px",
|
|
1753
|
+
marginTop: "6px",
|
|
1754
|
+
flexWrap: "wrap"
|
|
1755
|
+
}
|
|
1756
|
+
},
|
|
1757
|
+
[
|
|
1758
|
+
h12("strong", { style: { fontSize: "18px" } }, s ? s.plan : "Free"),
|
|
1759
|
+
...s ? [
|
|
1760
|
+
h12("span", { style: styles.muted }, `\xB7 ${s.interval}`),
|
|
1761
|
+
pill(statusTone(s.status), s.status)
|
|
1762
|
+
] : [h12("span", { style: styles.muted }, "no subscription")]
|
|
1763
|
+
]
|
|
1764
|
+
),
|
|
1765
|
+
end ? h12("div", { style: styles.statHint }, `${end.label} ${end.date}`) : null,
|
|
1766
|
+
s?.providerSubscriptionId ? h12(
|
|
1767
|
+
"div",
|
|
1768
|
+
{ style: { ...styles.statHint, fontFamily: "var(--fonderie-mono,monospace)" } },
|
|
1769
|
+
s.providerSubscriptionId
|
|
1770
|
+
) : null
|
|
1771
|
+
]),
|
|
1772
|
+
h12("div", { style: styles.card }, [
|
|
1773
|
+
h12("div", { style: styles.statLabel }, "Credits"),
|
|
1774
|
+
...w ? [
|
|
1775
|
+
h12("div", { style: styles.statValue }, [
|
|
1776
|
+
`${w.balance} `,
|
|
1777
|
+
h12("span", { style: { fontSize: "14px", fontWeight: 500 } }, w.currency)
|
|
1778
|
+
]),
|
|
1779
|
+
h12(
|
|
1780
|
+
"div",
|
|
1781
|
+
{ style: styles.statHint },
|
|
1782
|
+
`${w.purchased ?? "0"} permanent \xB7 ${w.granted ?? "0"} plan allowance${w.grantedExpiresAt ? ` (expires ${new Date(w.grantedExpiresAt).toLocaleDateString()})` : ""}`
|
|
1783
|
+
)
|
|
1784
|
+
] : [
|
|
1785
|
+
h12(
|
|
1786
|
+
"div",
|
|
1787
|
+
{ style: styles.statHint },
|
|
1788
|
+
"No wallet \u2014 billing has no wallet configuration."
|
|
1789
|
+
)
|
|
1790
|
+
]
|
|
1791
|
+
])
|
|
1792
|
+
]),
|
|
1793
|
+
w ? h12(
|
|
1794
|
+
"form",
|
|
1795
|
+
{
|
|
1796
|
+
style: {
|
|
1797
|
+
...styles.card,
|
|
1798
|
+
display: "flex",
|
|
1799
|
+
gap: "8px",
|
|
1800
|
+
alignItems: "center",
|
|
1801
|
+
flexWrap: "wrap",
|
|
1802
|
+
marginBottom: "16px"
|
|
1803
|
+
},
|
|
1804
|
+
onSubmit: (e) => {
|
|
1805
|
+
e.preventDefault();
|
|
1806
|
+
const key = `admin-grant-${props.subscriberType}-${props.subscriberId}-${Date.now()}`;
|
|
1807
|
+
const a = amount.value.trim();
|
|
1808
|
+
void grant({
|
|
1809
|
+
amount: a,
|
|
1810
|
+
...note.value ? { description: note.value } : {},
|
|
1811
|
+
idempotencyKey: key
|
|
1812
|
+
}).then(() => {
|
|
1813
|
+
granted.value = `Granted ${a} ${w.currency}.`;
|
|
1814
|
+
amount.value = "";
|
|
1815
|
+
note.value = "";
|
|
1816
|
+
}).catch(() => {
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
},
|
|
1820
|
+
[
|
|
1821
|
+
h12("strong", { style: { fontSize: "13.5px", marginRight: "4px" } }, "Grant credits"),
|
|
1822
|
+
h12("input", {
|
|
1823
|
+
value: amount.value,
|
|
1824
|
+
onInput: (e) => amount.value = e.target.value,
|
|
1825
|
+
placeholder: `amount (${w.currency})`,
|
|
1826
|
+
inputmode: "numeric",
|
|
1827
|
+
style: { ...styles.input, width: "150px" },
|
|
1828
|
+
"aria-label": "Amount"
|
|
1829
|
+
}),
|
|
1830
|
+
h12("input", {
|
|
1831
|
+
value: note.value,
|
|
1832
|
+
onInput: (e) => note.value = e.target.value,
|
|
1833
|
+
placeholder: "reason (optional)",
|
|
1834
|
+
style: { ...styles.input, flex: 1, minWidth: "180px" },
|
|
1835
|
+
"aria-label": "Reason"
|
|
1836
|
+
}),
|
|
1837
|
+
h12(
|
|
1838
|
+
"button",
|
|
1839
|
+
{
|
|
1840
|
+
type: "submit",
|
|
1841
|
+
style: styles.buttonPrimary,
|
|
1842
|
+
disabled: !/^\d+$/.test(amount.value.trim())
|
|
1843
|
+
},
|
|
1844
|
+
[icon("plus", 14), "Grant"]
|
|
1845
|
+
),
|
|
1846
|
+
granted.value ? h12("span", { style: styles.ok }, granted.value) : null
|
|
1847
|
+
]
|
|
1848
|
+
) : null,
|
|
1849
|
+
w ? ledger.value.length === 0 ? empty("No credit movements yet", void 0, "subscriber") : h12("div", [
|
|
1850
|
+
table(
|
|
1851
|
+
["When", "Kind", "Amount", "Note"],
|
|
1852
|
+
ledger.value.map(
|
|
1853
|
+
(t2) => h12("tr", { key: t2.id }, [
|
|
1854
|
+
td(new Date(t2.createdAt).toLocaleString(), {
|
|
1855
|
+
...styles.muted,
|
|
1856
|
+
whiteSpace: "nowrap"
|
|
1857
|
+
}),
|
|
1858
|
+
td(t2.type),
|
|
1859
|
+
td(t2.amount, styles.mono),
|
|
1860
|
+
td(t2.description ?? "", styles.muted)
|
|
1861
|
+
])
|
|
1862
|
+
)
|
|
1863
|
+
),
|
|
1864
|
+
hasMoreLedger.value ? loadMoreButton(() => void loadMoreLedger()) : null
|
|
1865
|
+
]) : null
|
|
1866
|
+
]);
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
});
|
|
1870
|
+
|
|
1871
|
+
// src/screens/UsersScreen.ts
|
|
1872
|
+
var UsersScreen = defineComponent11({
|
|
1678
1873
|
name: "FonderieUsersScreen",
|
|
1679
1874
|
props: {
|
|
1680
1875
|
client: { type: Object, required: true },
|
|
1681
|
-
pageSize: { type: Number, default: 50 }
|
|
1876
|
+
pageSize: { type: Number, default: 50 },
|
|
1877
|
+
// Given ⇒ a Plan column, and each user's plan, credits and grant form on
|
|
1878
|
+
// their page. Everyone has a wallet; no subscription means the free tier.
|
|
1879
|
+
billingClient: { type: Object, default: void 0 },
|
|
1880
|
+
// Open this user on arrival (e.g. from the Subscriptions list).
|
|
1881
|
+
openUserId: { type: String, default: void 0 }
|
|
1682
1882
|
},
|
|
1683
1883
|
setup(props) {
|
|
1684
|
-
const input =
|
|
1685
|
-
const email =
|
|
1686
|
-
const selectedId =
|
|
1884
|
+
const input = ref6("");
|
|
1885
|
+
const email = ref6("");
|
|
1886
|
+
const selectedId = ref6(props.openUserId ?? "");
|
|
1887
|
+
const plans = useSubscriptionIndex(props.billingClient);
|
|
1687
1888
|
const list = useAdminUsers(props.client, { limit: props.pageSize });
|
|
1688
1889
|
const { user, isLoading, error, suspend, unsuspend, revokeSessions } = useAdminUser(
|
|
1689
1890
|
props.client,
|
|
1690
1891
|
{ email, id: selectedId }
|
|
1691
1892
|
);
|
|
1692
|
-
const userId =
|
|
1893
|
+
const userId = computed2(() => user.value?.id ?? null);
|
|
1693
1894
|
const sessions = useAdminUserSessions(props.client, userId);
|
|
1694
1895
|
const history = useAdminLoginHistory(props.client, userId, { limit: 20 });
|
|
1695
|
-
const showList =
|
|
1896
|
+
const showList = computed2(() => !email.value && !selectedId.value);
|
|
1696
1897
|
const yesNo = (v) => v ? pill("ok", "yes") : pill("neutral", "no");
|
|
1697
|
-
const row = (k, v) =>
|
|
1898
|
+
const row = (k, v) => h13("tr", [td(k), td(v)]);
|
|
1899
|
+
const planCell = (id) => {
|
|
1900
|
+
if (!plans.value) return h13("span", { style: styles.muted }, "\u2026");
|
|
1901
|
+
const sub = plans.value.get(`user/${id}`);
|
|
1902
|
+
if (!sub || sub.status === "canceled") return h13("span", { style: styles.muted }, "free");
|
|
1903
|
+
return [
|
|
1904
|
+
h13("strong", sub.plan),
|
|
1905
|
+
" ",
|
|
1906
|
+
sub.status !== "active" ? pill(statusTone(sub.status), sub.status) : null
|
|
1907
|
+
];
|
|
1908
|
+
};
|
|
1698
1909
|
const clear = () => {
|
|
1699
1910
|
email.value = "";
|
|
1700
1911
|
selectedId.value = "";
|
|
@@ -1702,12 +1913,12 @@ var UsersScreen = defineComponent10({
|
|
|
1702
1913
|
};
|
|
1703
1914
|
return () => {
|
|
1704
1915
|
const u = user.value;
|
|
1705
|
-
return
|
|
1916
|
+
return h13("div", { style: styles.container }, [
|
|
1706
1917
|
pageHeader(
|
|
1707
1918
|
"Users",
|
|
1708
1919
|
"Everyone signed up. Open an account for its sessions, sign-ins and the suspend and sign-out controls."
|
|
1709
1920
|
),
|
|
1710
|
-
|
|
1921
|
+
h13(
|
|
1711
1922
|
"form",
|
|
1712
1923
|
{
|
|
1713
1924
|
style: styles.toolbar,
|
|
@@ -1718,7 +1929,7 @@ var UsersScreen = defineComponent10({
|
|
|
1718
1929
|
}
|
|
1719
1930
|
},
|
|
1720
1931
|
[
|
|
1721
|
-
|
|
1932
|
+
h13("input", {
|
|
1722
1933
|
type: "email",
|
|
1723
1934
|
value: input.value,
|
|
1724
1935
|
onInput: (e) => input.value = e.target.value,
|
|
@@ -1726,22 +1937,22 @@ var UsersScreen = defineComponent10({
|
|
|
1726
1937
|
style: { ...styles.input, minWidth: "280px" },
|
|
1727
1938
|
"aria-label": "Email"
|
|
1728
1939
|
}),
|
|
1729
|
-
|
|
1940
|
+
h13(
|
|
1730
1941
|
"button",
|
|
1731
1942
|
{ type: "submit", style: styles.buttonPrimary, disabled: isLoading.value },
|
|
1732
1943
|
[icon("search", 14), "Look up"]
|
|
1733
1944
|
),
|
|
1734
|
-
showList.value ?
|
|
1945
|
+
showList.value ? refreshButton2("Refresh", list.isLoading.value, () => void list.refresh()) : actionButton([icon("back", 14), "All users"], false, clear, styles.buttonGhost)
|
|
1735
1946
|
]
|
|
1736
1947
|
),
|
|
1737
|
-
showList.value ?
|
|
1738
|
-
list.error.value ?
|
|
1948
|
+
showList.value ? h13("div", [
|
|
1949
|
+
list.error.value ? h13("p", { style: styles.error, role: "alert" }, list.error.value.explanation) : null,
|
|
1739
1950
|
list.users.value.length === 0 && !list.isLoading.value ? empty("No users yet", "Sign-ups appear here as they happen.", "users") : table(
|
|
1740
|
-
["Email", "Name", "Created", "Status"],
|
|
1951
|
+
props.billingClient ? ["Email", "Name", "Plan", "Created", "Status"] : ["Email", "Name", "Created", "Status"],
|
|
1741
1952
|
list.users.value.map(
|
|
1742
|
-
(u2) =>
|
|
1953
|
+
(u2) => h13("tr", { key: u2.id }, [
|
|
1743
1954
|
td(
|
|
1744
|
-
|
|
1955
|
+
h13(
|
|
1745
1956
|
"button",
|
|
1746
1957
|
{
|
|
1747
1958
|
type: "button",
|
|
@@ -1752,8 +1963,9 @@ var UsersScreen = defineComponent10({
|
|
|
1752
1963
|
)
|
|
1753
1964
|
),
|
|
1754
1965
|
td(
|
|
1755
|
-
`${u2.firstName ?? ""} ${u2.lastName ?? ""}`.trim() ||
|
|
1966
|
+
`${u2.firstName ?? ""} ${u2.lastName ?? ""}`.trim() || h13("span", { style: styles.muted }, "\u2014")
|
|
1756
1967
|
),
|
|
1968
|
+
props.billingClient ? td(planCell(u2.id)) : null,
|
|
1757
1969
|
td(new Date(u2.createdAt).toLocaleDateString()),
|
|
1758
1970
|
td(
|
|
1759
1971
|
u2.suspended ? pill("warn", "suspended") : u2.deletedAt ? pill("neutral", "deleted") : pill("ok", "active")
|
|
@@ -1761,24 +1973,24 @@ var UsersScreen = defineComponent10({
|
|
|
1761
1973
|
])
|
|
1762
1974
|
)
|
|
1763
1975
|
),
|
|
1764
|
-
list.isLoading.value ?
|
|
1976
|
+
list.isLoading.value ? h13("p", { style: styles.status }, "Loading\u2026") : null,
|
|
1765
1977
|
list.hasMore.value && !list.isLoading.value ? loadMoreButton(() => void list.loadMore()) : null
|
|
1766
1978
|
]) : null,
|
|
1767
|
-
error.value ?
|
|
1979
|
+
error.value ? h13(
|
|
1768
1980
|
"p",
|
|
1769
1981
|
{ style: styles.error, role: "alert" },
|
|
1770
1982
|
error.value.status === 404 ? "No user with that email." : error.value.explanation
|
|
1771
1983
|
) : null,
|
|
1772
1984
|
u ? [
|
|
1773
|
-
|
|
1985
|
+
h13("h2", { style: styles.subtitle }, [
|
|
1774
1986
|
u.firstName || u.lastName ? `${u.firstName} ${u.lastName}`.trim() : u.email,
|
|
1775
1987
|
" ",
|
|
1776
1988
|
u.suspended ? pill("warn", "suspended") : null,
|
|
1777
1989
|
u.deletedAt ? pill("neutral", "deleted") : null
|
|
1778
1990
|
]),
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
row("id",
|
|
1991
|
+
h13("table", { style: styles.table }, [
|
|
1992
|
+
h13("tbody", [
|
|
1993
|
+
row("id", h13("span", { style: styles.mono }, u.id)),
|
|
1782
1994
|
row("email", [u.email, " \xB7 verified ", yesNo(u.isEmailVerified)]),
|
|
1783
1995
|
row("MFA", yesNo(u.mfaEnabled)),
|
|
1784
1996
|
row(
|
|
@@ -1787,12 +1999,12 @@ var UsersScreen = defineComponent10({
|
|
|
1787
1999
|
),
|
|
1788
2000
|
row(
|
|
1789
2001
|
"last login",
|
|
1790
|
-
u.lastLogin ? new Date(u.lastLogin).toLocaleString() :
|
|
2002
|
+
u.lastLogin ? new Date(u.lastLogin).toLocaleString() : h13("span", { style: styles.muted }, "never")
|
|
1791
2003
|
),
|
|
1792
2004
|
row("created", new Date(u.createdAt).toLocaleString())
|
|
1793
2005
|
])
|
|
1794
2006
|
]),
|
|
1795
|
-
|
|
2007
|
+
h13("div", { style: { ...styles.toolbar, marginTop: "12px" } }, [
|
|
1796
2008
|
u.suspended ? actionButton("Unsuspend", isLoading.value, () => void unsuspend()) : actionButton(
|
|
1797
2009
|
"Suspend",
|
|
1798
2010
|
isLoading.value,
|
|
@@ -1805,19 +2017,26 @@ var UsersScreen = defineComponent10({
|
|
|
1805
2017
|
() => void revokeSessions().then(() => sessions.refresh())
|
|
1806
2018
|
)
|
|
1807
2019
|
]),
|
|
1808
|
-
|
|
1809
|
-
|
|
2020
|
+
props.billingClient ? h13("h2", { style: styles.subtitle }, "Plan & credits") : null,
|
|
2021
|
+
props.billingClient ? h13(SubscriberBilling, {
|
|
2022
|
+
key: u.id,
|
|
2023
|
+
client: props.billingClient,
|
|
2024
|
+
subscriberType: "user",
|
|
2025
|
+
subscriberId: u.id
|
|
2026
|
+
}) : null,
|
|
2027
|
+
h13("h2", { style: styles.subtitle }, "Live sessions"),
|
|
2028
|
+
sessions.sessions.value.length === 0 ? h13("p", { style: styles.muted }, "None.") : h13(
|
|
1810
2029
|
"ul",
|
|
1811
2030
|
{ style: styles.list },
|
|
1812
2031
|
sessions.sessions.value.map(
|
|
1813
|
-
(s) =>
|
|
1814
|
-
|
|
2032
|
+
(s) => h13("li", { key: s.id, style: styles.row }, [
|
|
2033
|
+
h13("span", { style: styles.mono }, s.ipAddress ?? "\u2014"),
|
|
1815
2034
|
" ",
|
|
1816
|
-
|
|
2035
|
+
h13("span", { style: styles.muted }, describeLocation(s.location) ?? ""),
|
|
1817
2036
|
" ",
|
|
1818
|
-
|
|
2037
|
+
h13("span", { style: styles.muted }, s.userAgent ?? ""),
|
|
1819
2038
|
" ",
|
|
1820
|
-
|
|
2039
|
+
h13(
|
|
1821
2040
|
"span",
|
|
1822
2041
|
{ style: styles.muted },
|
|
1823
2042
|
`since ${new Date(s.createdAt).toLocaleString()}`
|
|
@@ -1825,22 +2044,22 @@ var UsersScreen = defineComponent10({
|
|
|
1825
2044
|
])
|
|
1826
2045
|
)
|
|
1827
2046
|
),
|
|
1828
|
-
|
|
1829
|
-
history.events.value.length === 0 && !history.isLoading.value ?
|
|
2047
|
+
h13("h2", { style: styles.subtitle }, "Recent sign-ins"),
|
|
2048
|
+
history.events.value.length === 0 && !history.isLoading.value ? h13("p", { style: styles.muted }, "None recorded.") : h13(
|
|
1830
2049
|
"ul",
|
|
1831
2050
|
{ style: styles.list },
|
|
1832
2051
|
history.events.value.map(
|
|
1833
|
-
(e) =>
|
|
2052
|
+
(e) => h13("li", { key: e.id, style: styles.row }, [
|
|
1834
2053
|
pill(e.outcome === "success" ? "ok" : "bad", e.outcome),
|
|
1835
2054
|
" ",
|
|
1836
|
-
|
|
2055
|
+
h13("span", { style: styles.muted }, e.method),
|
|
1837
2056
|
" ",
|
|
1838
|
-
|
|
2057
|
+
h13("span", { style: styles.mono }, e.ipAddress ?? "\u2014"),
|
|
1839
2058
|
" ",
|
|
1840
|
-
|
|
2059
|
+
h13("span", { style: styles.muted }, describeLocation(e.location) ?? ""),
|
|
1841
2060
|
e.location?.proxy || e.location?.hosting ? [" ", pill("warn", e.location.proxy ? "proxy/VPN" : "hosting")] : "",
|
|
1842
2061
|
" ",
|
|
1843
|
-
|
|
2062
|
+
h13("span", { style: styles.muted }, new Date(e.createdAt).toLocaleString())
|
|
1844
2063
|
])
|
|
1845
2064
|
)
|
|
1846
2065
|
),
|
|
@@ -1853,8 +2072,8 @@ var UsersScreen = defineComponent10({
|
|
|
1853
2072
|
|
|
1854
2073
|
// src/screens/CatalogScreen.ts
|
|
1855
2074
|
import { useAdminCatalog } from "@fonderie/vue-admin";
|
|
1856
|
-
import { defineComponent as
|
|
1857
|
-
var CatalogScreen =
|
|
2075
|
+
import { defineComponent as defineComponent12, h as h14 } from "vue";
|
|
2076
|
+
var CatalogScreen = defineComponent12({
|
|
1858
2077
|
name: "FonderieCatalogScreen",
|
|
1859
2078
|
props: { client: { type: Object, required: true } },
|
|
1860
2079
|
setup(props) {
|
|
@@ -1862,17 +2081,17 @@ var CatalogScreen = defineComponent11({
|
|
|
1862
2081
|
const money = (v) => v == null ? "\u2014" : (v / 100).toFixed(2);
|
|
1863
2082
|
return () => {
|
|
1864
2083
|
const c = catalog.value;
|
|
1865
|
-
return
|
|
2084
|
+
return h14("div", { style: styles.container }, [
|
|
1866
2085
|
pageHeader(
|
|
1867
2086
|
"Catalog",
|
|
1868
2087
|
"What you sell: plans as configured in code, and as stored in the database, side by side.",
|
|
1869
|
-
[
|
|
2088
|
+
[refreshButton2("Refresh", isLoading.value, () => void refresh())]
|
|
1870
2089
|
),
|
|
1871
|
-
error.value ?
|
|
1872
|
-
isLoading.value && !c ?
|
|
2090
|
+
error.value ? h14("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
|
|
2091
|
+
isLoading.value && !c ? h14("p", { style: styles.status }, "Loading\u2026") : null,
|
|
1873
2092
|
c ? [
|
|
1874
|
-
|
|
1875
|
-
|
|
2093
|
+
h14("h2", { style: { ...styles.subtitle, marginTop: 0 } }, "Configured (code)"),
|
|
2094
|
+
h14(
|
|
1876
2095
|
"pre",
|
|
1877
2096
|
{
|
|
1878
2097
|
style: {
|
|
@@ -1886,7 +2105,7 @@ var CatalogScreen = defineComponent11({
|
|
|
1886
2105
|
},
|
|
1887
2106
|
JSON.stringify(c.configured, null, 2)
|
|
1888
2107
|
),
|
|
1889
|
-
|
|
2108
|
+
h14("h2", { style: styles.subtitle }, "Stored (database)"),
|
|
1890
2109
|
c.stored.length === 0 ? empty(
|
|
1891
2110
|
"No stored plans",
|
|
1892
2111
|
"Plans are configured in code; nothing has been written to the database.",
|
|
@@ -1894,10 +2113,10 @@ var CatalogScreen = defineComponent11({
|
|
|
1894
2113
|
) : table(
|
|
1895
2114
|
["Plan", "Tier", "Seats", "Monthly", "Yearly", "Trial", ""],
|
|
1896
2115
|
c.stored.map(
|
|
1897
|
-
(p) =>
|
|
2116
|
+
(p) => h14("tr", { key: p.id }, [
|
|
1898
2117
|
td([
|
|
1899
|
-
|
|
1900
|
-
p.description ?
|
|
2118
|
+
h14("strong", p.name),
|
|
2119
|
+
p.description ? h14("div", { style: styles.muted }, p.description) : ""
|
|
1901
2120
|
]),
|
|
1902
2121
|
td(String(p.tier)),
|
|
1903
2122
|
td(p.seats == null ? "\u221E" : String(p.seats)),
|
|
@@ -1905,7 +2124,7 @@ var CatalogScreen = defineComponent11({
|
|
|
1905
2124
|
td(money(p.pricing?.yearly)),
|
|
1906
2125
|
td(p.trialDays ? `${p.trialDays} d` : "\u2014"),
|
|
1907
2126
|
td(
|
|
1908
|
-
|
|
2127
|
+
h14(
|
|
1909
2128
|
"button",
|
|
1910
2129
|
{
|
|
1911
2130
|
type: "button",
|
|
@@ -1929,259 +2148,171 @@ var CatalogScreen = defineComponent11({
|
|
|
1929
2148
|
});
|
|
1930
2149
|
|
|
1931
2150
|
// src/screens/SubscriberScreen.ts
|
|
1932
|
-
import {
|
|
1933
|
-
import { defineComponent as
|
|
1934
|
-
var
|
|
2151
|
+
import { useAdminSubscribers } from "@fonderie/vue-admin";
|
|
2152
|
+
import { defineComponent as defineComponent13, h as h15, ref as ref7 } from "vue";
|
|
2153
|
+
var FILTERS = [
|
|
2154
|
+
{ label: "All", match: () => true },
|
|
2155
|
+
{ label: "Active", match: (s) => s === "active" },
|
|
2156
|
+
{ label: "Trialing", match: (s) => s === "trialing" },
|
|
2157
|
+
{ label: "Past due", match: (s) => s === "past_due" || s === "unpaid" },
|
|
2158
|
+
{ label: "Canceled", match: (s) => s === "canceled" }
|
|
2159
|
+
];
|
|
2160
|
+
var SubscriberScreen = defineComponent13({
|
|
1935
2161
|
name: "FonderieSubscriberScreen",
|
|
1936
2162
|
props: {
|
|
1937
2163
|
client: { type: Object, required: true },
|
|
1938
|
-
pageSize: { type: Number, default: 50 }
|
|
2164
|
+
pageSize: { type: Number, default: 50 },
|
|
2165
|
+
/**
|
|
2166
|
+
* A user subscriber opens on the Users page, where their plan and credits
|
|
2167
|
+
* live beside their account. Omit to open everything here.
|
|
2168
|
+
*/
|
|
2169
|
+
onOpenUser: { type: Function, default: void 0 }
|
|
1939
2170
|
},
|
|
1940
2171
|
setup(props) {
|
|
1941
|
-
const type = ref5("user");
|
|
1942
|
-
const idInput = ref5("");
|
|
1943
|
-
const subscriber = ref5(null);
|
|
1944
2172
|
const list = useAdminSubscribers(props.client, { limit: props.pageSize });
|
|
1945
|
-
const
|
|
1946
|
-
const open = (
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
2173
|
+
const filter = ref7("All");
|
|
2174
|
+
const open = ref7(null);
|
|
2175
|
+
const type = ref7("workspace");
|
|
2176
|
+
const idInput = ref7("");
|
|
2177
|
+
const openRow = (t2, id) => {
|
|
2178
|
+
if (t2 === "user" && props.onOpenUser) props.onOpenUser(id);
|
|
2179
|
+
else open.value = { type: t2, id };
|
|
1951
2180
|
};
|
|
1952
|
-
const amount = ref5("");
|
|
1953
|
-
const note = ref5("");
|
|
1954
|
-
const granted = ref5(null);
|
|
1955
|
-
const input = (model, placeholder, extra = {}) => h14("input", {
|
|
1956
|
-
value: model.value,
|
|
1957
|
-
onInput: (e) => model.value = e.target.value,
|
|
1958
|
-
placeholder,
|
|
1959
|
-
style: { ...styles.input, ...extra },
|
|
1960
|
-
"aria-label": placeholder
|
|
1961
|
-
});
|
|
1962
2181
|
return () => {
|
|
1963
|
-
const
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
"Who is subscribed. Open one for their plan, wallet and ledger, and to grant credit."
|
|
1969
|
-
),
|
|
1970
|
-
h14(
|
|
1971
|
-
"form",
|
|
1972
|
-
{
|
|
1973
|
-
style: styles.toolbar,
|
|
1974
|
-
onSubmit: (e) => {
|
|
1975
|
-
e.preventDefault();
|
|
1976
|
-
granted.value = null;
|
|
1977
|
-
subscriber.value = idInput.value.trim() ? { type: type.value, id: idInput.value.trim() } : null;
|
|
1978
|
-
}
|
|
1979
|
-
},
|
|
1980
|
-
[
|
|
1981
|
-
h14(
|
|
1982
|
-
"select",
|
|
1983
|
-
{
|
|
1984
|
-
value: type.value,
|
|
1985
|
-
onChange: (e) => type.value = e.target.value,
|
|
1986
|
-
style: styles.input,
|
|
1987
|
-
"aria-label": "Type"
|
|
1988
|
-
},
|
|
1989
|
-
[
|
|
1990
|
-
h14("option", { value: "user" }, "user"),
|
|
1991
|
-
h14("option", { value: "workspace" }, "workspace")
|
|
1992
|
-
]
|
|
1993
|
-
),
|
|
1994
|
-
input(idInput, "subscriber id", { minWidth: "280px" }),
|
|
1995
|
-
h14(
|
|
2182
|
+
const o = open.value;
|
|
2183
|
+
if (o) {
|
|
2184
|
+
return h15("div", { style: styles.container }, [
|
|
2185
|
+
pageHeader(`${o.type}/${o.id}`, "Plan, credits and what moved.", [
|
|
2186
|
+
h15(
|
|
1996
2187
|
"button",
|
|
1997
|
-
{ type: "
|
|
1998
|
-
[icon("
|
|
1999
|
-
),
|
|
2000
|
-
subscriber.value ? h14(
|
|
2001
|
-
"button",
|
|
2002
|
-
{
|
|
2003
|
-
type: "button",
|
|
2004
|
-
style: styles.button,
|
|
2005
|
-
onClick: () => {
|
|
2006
|
-
subscriber.value = null;
|
|
2007
|
-
idInput.value = "";
|
|
2008
|
-
granted.value = null;
|
|
2009
|
-
}
|
|
2010
|
-
},
|
|
2011
|
-
[icon("back", 14), "All subscribers"]
|
|
2012
|
-
) : h14(
|
|
2013
|
-
"button",
|
|
2014
|
-
{
|
|
2015
|
-
type: "button",
|
|
2016
|
-
style: styles.button,
|
|
2017
|
-
disabled: list.isLoading.value,
|
|
2018
|
-
onClick: () => void list.refresh()
|
|
2019
|
-
},
|
|
2020
|
-
"Refresh"
|
|
2188
|
+
{ type: "button", style: styles.buttonGhost, onClick: () => open.value = null },
|
|
2189
|
+
[icon("back", 14), "All subscriptions"]
|
|
2021
2190
|
)
|
|
2022
|
-
]
|
|
2191
|
+
]),
|
|
2192
|
+
h15(SubscriberBilling, {
|
|
2193
|
+
key: `${o.type}/${o.id}`,
|
|
2194
|
+
client: props.client,
|
|
2195
|
+
subscriberType: o.type,
|
|
2196
|
+
subscriberId: o.id
|
|
2197
|
+
})
|
|
2198
|
+
]);
|
|
2199
|
+
}
|
|
2200
|
+
const match = FILTERS.find((f) => f.label === filter.value)?.match ?? (() => true);
|
|
2201
|
+
const rows = list.subscriptions.value.filter((s) => match(s.status));
|
|
2202
|
+
return h15("div", { style: styles.container }, [
|
|
2203
|
+
pageHeader(
|
|
2204
|
+
"Subscriptions",
|
|
2205
|
+
"Who pays, and who is behind. A user's plan and credits are also on their page under Users \u2014 free users appear only there.",
|
|
2206
|
+
[refreshButton(() => void list.refresh(), list.isLoading.value)]
|
|
2023
2207
|
),
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
sub.currentPeriodEnd ? new Date(sub.currentPeriodEnd).toLocaleDateString() : h14("span", { style: styles.muted }, "\u2014")
|
|
2055
|
-
)
|
|
2056
|
-
])
|
|
2057
|
-
)
|
|
2208
|
+
h15("div", { style: { ...styles.toolbar, justifyContent: "space-between" } }, [
|
|
2209
|
+
h15(
|
|
2210
|
+
"fieldset",
|
|
2211
|
+
{
|
|
2212
|
+
style: {
|
|
2213
|
+
display: "flex",
|
|
2214
|
+
gap: "6px",
|
|
2215
|
+
flexWrap: "wrap",
|
|
2216
|
+
border: "none",
|
|
2217
|
+
margin: 0,
|
|
2218
|
+
padding: 0,
|
|
2219
|
+
minWidth: 0
|
|
2220
|
+
},
|
|
2221
|
+
"aria-label": "Filter by status"
|
|
2222
|
+
},
|
|
2223
|
+
FILTERS.map((f) => {
|
|
2224
|
+
const n = list.subscriptions.value.filter((s) => f.match(s.status)).length;
|
|
2225
|
+
const on = filter.value === f.label;
|
|
2226
|
+
return h15(
|
|
2227
|
+
"button",
|
|
2228
|
+
{
|
|
2229
|
+
key: f.label,
|
|
2230
|
+
type: "button",
|
|
2231
|
+
"aria-pressed": on,
|
|
2232
|
+
onClick: () => filter.value = f.label,
|
|
2233
|
+
style: { ...on ? styles.buttonPrimary : styles.button, height: "28px" }
|
|
2234
|
+
},
|
|
2235
|
+
[f.label, h15("span", { style: { opacity: 0.7 } }, String(n))]
|
|
2236
|
+
);
|
|
2237
|
+
})
|
|
2058
2238
|
),
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
"button",
|
|
2239
|
+
h15(
|
|
2240
|
+
"form",
|
|
2062
2241
|
{
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2242
|
+
style: { display: "flex", gap: "6px" },
|
|
2243
|
+
onSubmit: (e) => {
|
|
2244
|
+
e.preventDefault();
|
|
2245
|
+
if (idInput.value.trim()) openRow(type.value, idInput.value.trim());
|
|
2246
|
+
}
|
|
2066
2247
|
},
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
letterSpacing: "var(--fonderie-tracking-display,-0.05em)"
|
|
2109
|
-
}
|
|
2110
|
-
},
|
|
2111
|
-
w.balance
|
|
2112
|
-
),
|
|
2113
|
-
` ${w.currency} `,
|
|
2114
|
-
h14(
|
|
2115
|
-
"span",
|
|
2116
|
-
{ style: styles.muted },
|
|
2117
|
-
`(granted ${w.granted ?? "0"} \xB7 purchased ${w.purchased ?? "0"}) \xB7 minor units, precision ${w.precision}`
|
|
2118
|
-
)
|
|
2119
|
-
]
|
|
2120
|
-
),
|
|
2121
|
-
h14(
|
|
2122
|
-
"form",
|
|
2123
|
-
{
|
|
2124
|
-
style: styles.toolbar,
|
|
2125
|
-
onSubmit: (e) => {
|
|
2126
|
-
e.preventDefault();
|
|
2127
|
-
const sub = subscriber.value;
|
|
2128
|
-
if (!sub) return;
|
|
2129
|
-
const key = `admin-grant-${sub.type}-${sub.id}-${Date.now()}`;
|
|
2130
|
-
const amt = amount.value.trim();
|
|
2131
|
-
void grant({
|
|
2132
|
-
amount: amt,
|
|
2133
|
-
...note.value ? { description: note.value } : {},
|
|
2134
|
-
idempotencyKey: key
|
|
2135
|
-
}).then(() => {
|
|
2136
|
-
granted.value = `Granted ${amt} ${w.currency}.`;
|
|
2137
|
-
amount.value = "";
|
|
2138
|
-
note.value = "";
|
|
2139
|
-
}).catch(() => {
|
|
2140
|
-
});
|
|
2141
|
-
}
|
|
2142
|
-
},
|
|
2143
|
-
[
|
|
2144
|
-
input(amount, "amount (minor units)"),
|
|
2145
|
-
input(note, "reason (optional)", { minWidth: "220px" }),
|
|
2146
|
-
h14(
|
|
2248
|
+
[
|
|
2249
|
+
h15(
|
|
2250
|
+
"select",
|
|
2251
|
+
{
|
|
2252
|
+
value: type.value,
|
|
2253
|
+
onChange: (e) => type.value = e.target.value,
|
|
2254
|
+
style: styles.input,
|
|
2255
|
+
"aria-label": "Type"
|
|
2256
|
+
},
|
|
2257
|
+
[
|
|
2258
|
+
h15("option", { value: "workspace" }, "workspace"),
|
|
2259
|
+
h15("option", { value: "user" }, "user")
|
|
2260
|
+
]
|
|
2261
|
+
),
|
|
2262
|
+
h15("input", {
|
|
2263
|
+
value: idInput.value,
|
|
2264
|
+
onInput: (e) => idInput.value = e.target.value,
|
|
2265
|
+
placeholder: "subscriber id",
|
|
2266
|
+
style: { ...styles.input, width: "220px" },
|
|
2267
|
+
"aria-label": "Subscriber id"
|
|
2268
|
+
}),
|
|
2269
|
+
h15(
|
|
2270
|
+
"button",
|
|
2271
|
+
{ type: "submit", style: styles.button, disabled: !idInput.value.trim() },
|
|
2272
|
+
[icon("search", 14), "Open"]
|
|
2273
|
+
)
|
|
2274
|
+
]
|
|
2275
|
+
)
|
|
2276
|
+
]),
|
|
2277
|
+
list.error.value ? h15("p", { style: styles.error, role: "alert" }, list.error.value.explanation) : null,
|
|
2278
|
+
rows.length === 0 && !list.isLoading.value ? empty(
|
|
2279
|
+
filter.value === "All" ? "No subscriptions yet" : `No ${filter.value.toLowerCase()} subscriptions`,
|
|
2280
|
+
"Subscriptions appear here once someone checks out.",
|
|
2281
|
+
"subscriber"
|
|
2282
|
+
) : table(
|
|
2283
|
+
["Subscriber", "Plan", "Status", "Renews / ends"],
|
|
2284
|
+
rows.map((s) => {
|
|
2285
|
+
const end = periodEnd(s);
|
|
2286
|
+
return h15("tr", { key: s.id }, [
|
|
2287
|
+
td(
|
|
2288
|
+
h15(
|
|
2147
2289
|
"button",
|
|
2148
2290
|
{
|
|
2149
|
-
type: "
|
|
2150
|
-
style: styles.
|
|
2151
|
-
|
|
2291
|
+
type: "button",
|
|
2292
|
+
style: { ...styles.link, ...styles.mono },
|
|
2293
|
+
onClick: () => openRow(s.subscriberType, s.subscriberId)
|
|
2152
2294
|
},
|
|
2153
|
-
|
|
2154
|
-
)
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
(
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2295
|
+
`${s.subscriberType}/${s.subscriberId}`
|
|
2296
|
+
)
|
|
2297
|
+
),
|
|
2298
|
+
td([
|
|
2299
|
+
h15("strong", s.plan),
|
|
2300
|
+
" ",
|
|
2301
|
+
h15("span", { style: styles.muted }, `\xB7 ${s.interval}`)
|
|
2302
|
+
]),
|
|
2303
|
+
td([
|
|
2304
|
+
pill(statusTone(s.status), s.status),
|
|
2305
|
+
s.cancelAtPeriodEnd && s.status !== "canceled" ? h15("span", [" ", pill("warn", "cancels", false)]) : null
|
|
2306
|
+
]),
|
|
2307
|
+
td(
|
|
2308
|
+
end ? [h15("span", { style: styles.muted }, end.label), ` ${end.date}`] : h15("span", { style: styles.muted }, "\u2014"),
|
|
2309
|
+
{ whiteSpace: "nowrap" }
|
|
2168
2310
|
)
|
|
2169
|
-
)
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
style: { ...styles.button, marginTop: "8px" },
|
|
2175
|
-
onClick: () => void loadMoreLedger()
|
|
2176
|
-
},
|
|
2177
|
-
"Load more"
|
|
2178
|
-
) : null
|
|
2179
|
-
] : h14(
|
|
2180
|
-
"p",
|
|
2181
|
-
{ style: styles.muted },
|
|
2182
|
-
"No wallet \u2014 billing has no wallet configuration."
|
|
2183
|
-
)
|
|
2184
|
-
] : null
|
|
2311
|
+
]);
|
|
2312
|
+
})
|
|
2313
|
+
),
|
|
2314
|
+
list.isLoading.value ? h15("p", { style: styles.status }, "Loading\u2026") : null,
|
|
2315
|
+
list.hasMore.value && !list.isLoading.value ? loadMoreButton(() => void list.loadMore()) : null
|
|
2185
2316
|
]);
|
|
2186
2317
|
};
|
|
2187
2318
|
}
|
|
@@ -2189,33 +2320,33 @@ var SubscriberScreen = defineComponent12({
|
|
|
2189
2320
|
|
|
2190
2321
|
// src/screens/AuditScreen.ts
|
|
2191
2322
|
import { useAdminAudit } from "@fonderie/vue-admin";
|
|
2192
|
-
import { defineComponent as
|
|
2193
|
-
var AuditScreen =
|
|
2323
|
+
import { defineComponent as defineComponent14, h as h16, ref as ref8 } from "vue";
|
|
2324
|
+
var AuditScreen = defineComponent14({
|
|
2194
2325
|
name: "FonderieAuditScreen",
|
|
2195
2326
|
props: {
|
|
2196
2327
|
client: { type: Object, required: true },
|
|
2197
2328
|
pageSize: { type: Number, default: 50 }
|
|
2198
2329
|
},
|
|
2199
2330
|
setup(props) {
|
|
2200
|
-
const draft =
|
|
2201
|
-
const query =
|
|
2331
|
+
const draft = ref8({ workspaceId: "", type: "", actorId: "" });
|
|
2332
|
+
const query = ref8({ limit: props.pageSize });
|
|
2202
2333
|
const { events, hasMore, isLoading, error, refresh, loadMore } = useAdminAudit(
|
|
2203
2334
|
props.client,
|
|
2204
2335
|
query
|
|
2205
2336
|
);
|
|
2206
|
-
const field = (key, placeholder) =>
|
|
2337
|
+
const field = (key, placeholder) => h16("input", {
|
|
2207
2338
|
value: draft.value[key],
|
|
2208
2339
|
onInput: (e) => draft.value = { ...draft.value, [key]: e.target.value },
|
|
2209
2340
|
placeholder,
|
|
2210
2341
|
style: styles.input,
|
|
2211
2342
|
"aria-label": placeholder
|
|
2212
2343
|
});
|
|
2213
|
-
return () =>
|
|
2344
|
+
return () => h16("div", { style: styles.container }, [
|
|
2214
2345
|
pageHeader(
|
|
2215
2346
|
"Audit",
|
|
2216
2347
|
"What happened, across every workspace unless you name one. The chain's integrity verdict is on the Doctor page."
|
|
2217
2348
|
),
|
|
2218
|
-
|
|
2349
|
+
h16(
|
|
2219
2350
|
"form",
|
|
2220
2351
|
{
|
|
2221
2352
|
style: styles.toolbar,
|
|
@@ -2234,19 +2365,19 @@ var AuditScreen = defineComponent13({
|
|
|
2234
2365
|
field("workspaceId", "workspace id (all if empty)"),
|
|
2235
2366
|
field("type", "event type"),
|
|
2236
2367
|
field("actorId", "actor id"),
|
|
2237
|
-
|
|
2368
|
+
h16(
|
|
2238
2369
|
"button",
|
|
2239
2370
|
{ type: "submit", style: styles.buttonPrimary, disabled: isLoading.value },
|
|
2240
2371
|
[icon("search", 14), "Filter"]
|
|
2241
2372
|
),
|
|
2242
|
-
|
|
2373
|
+
refreshButton2("Refresh", isLoading.value, () => void refresh())
|
|
2243
2374
|
]
|
|
2244
2375
|
),
|
|
2245
|
-
error.value ?
|
|
2376
|
+
error.value ? h16("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
|
|
2246
2377
|
events.value.length === 0 && !isLoading.value ? empty("Nothing recorded for this filter", void 0, "audit") : table(
|
|
2247
2378
|
["When", "Type", "Workspace", "Actor", "Request"],
|
|
2248
2379
|
events.value.map(
|
|
2249
|
-
(e) =>
|
|
2380
|
+
(e) => h16("tr", { key: e.id }, [
|
|
2250
2381
|
td(new Date(e.createdAt).toLocaleString(), styles.muted),
|
|
2251
2382
|
td(e.type, styles.mono),
|
|
2252
2383
|
td(String(e.payload["workspaceId"] ?? "\u2014"), styles.mono),
|
|
@@ -2255,7 +2386,7 @@ var AuditScreen = defineComponent13({
|
|
|
2255
2386
|
])
|
|
2256
2387
|
)
|
|
2257
2388
|
),
|
|
2258
|
-
isLoading.value ?
|
|
2389
|
+
isLoading.value ? h16("p", { style: styles.status }, "Loading\u2026") : null,
|
|
2259
2390
|
hasMore.value && !isLoading.value ? loadMoreButton(() => void loadMore()) : null
|
|
2260
2391
|
]);
|
|
2261
2392
|
}
|
|
@@ -2278,7 +2409,7 @@ var NAV = [
|
|
|
2278
2409
|
group: "Money",
|
|
2279
2410
|
items: [
|
|
2280
2411
|
{ page: "catalog", label: "Catalog", needs: "billing" },
|
|
2281
|
-
{ page: "subscriber", label: "
|
|
2412
|
+
{ page: "subscriber", label: "Subscriptions", needs: "billing" }
|
|
2282
2413
|
]
|
|
2283
2414
|
},
|
|
2284
2415
|
{ group: "Settings", items: [{ page: "settings", label: "Config & secrets", needs: "config" }] },
|
|
@@ -2312,7 +2443,7 @@ var ICON = {
|
|
|
2312
2443
|
migrations: "migrations"
|
|
2313
2444
|
};
|
|
2314
2445
|
var NARROW = "(max-width: 820px)";
|
|
2315
|
-
var AdminShell =
|
|
2446
|
+
var AdminShell = defineComponent15({
|
|
2316
2447
|
name: "FonderieAdminShell",
|
|
2317
2448
|
props: {
|
|
2318
2449
|
client: { type: Object, required: true },
|
|
@@ -2346,60 +2477,74 @@ var AdminShell = defineComponent14({
|
|
|
2346
2477
|
emits: { navigate: (_page) => true },
|
|
2347
2478
|
setup(props, { emit, slots }) {
|
|
2348
2479
|
const query = typeof window !== "undefined" && window.matchMedia ? window.matchMedia(NARROW) : null;
|
|
2349
|
-
const narrow =
|
|
2350
|
-
const drawer =
|
|
2480
|
+
const narrow = ref9(query?.matches ?? false);
|
|
2481
|
+
const drawer = ref9(false);
|
|
2351
2482
|
const onMedia = (e) => narrow.value = e.matches;
|
|
2352
2483
|
onMounted(() => query?.addEventListener("change", onMedia));
|
|
2353
2484
|
onBeforeUnmount(() => query?.removeEventListener("change", onMedia));
|
|
2354
|
-
const own =
|
|
2355
|
-
const current =
|
|
2356
|
-
const editing =
|
|
2485
|
+
const own = ref9("attention");
|
|
2486
|
+
const current = computed3(() => props.page ?? own.value);
|
|
2487
|
+
const editing = ref9(null);
|
|
2488
|
+
const openUser = ref9(void 0);
|
|
2357
2489
|
const go = (p) => {
|
|
2358
2490
|
editing.value = null;
|
|
2359
2491
|
emit("navigate", p);
|
|
2360
2492
|
if (props.page === void 0) own.value = p;
|
|
2361
2493
|
drawer.value = false;
|
|
2362
2494
|
};
|
|
2363
|
-
const back = (label) =>
|
|
2364
|
-
|
|
2495
|
+
const back = (label) => h17("div", { style: { padding: "24px 40px 0" } }, [
|
|
2496
|
+
h17(
|
|
2365
2497
|
"button",
|
|
2366
2498
|
{ type: "button", style: styles.buttonGhost, onClick: () => editing.value = null },
|
|
2367
2499
|
[icon("back", 14), label]
|
|
2368
2500
|
)
|
|
2369
2501
|
]);
|
|
2370
|
-
const missing = (text) =>
|
|
2502
|
+
const missing = (text) => h17("p", { style: { ...styles.status, padding: "32px 40px" } }, text);
|
|
2371
2503
|
const body = () => {
|
|
2372
2504
|
const c = props.client;
|
|
2373
2505
|
switch (current.value) {
|
|
2374
2506
|
case "attention":
|
|
2375
|
-
return
|
|
2507
|
+
return h17(AttentionScreen, { client: c });
|
|
2376
2508
|
case "modules":
|
|
2377
|
-
return
|
|
2509
|
+
return h17(ModulesScreen, { client: c });
|
|
2378
2510
|
case "doctor":
|
|
2379
|
-
return
|
|
2511
|
+
return h17(DoctorScreen, { client: c });
|
|
2380
2512
|
case "environment":
|
|
2381
|
-
return
|
|
2513
|
+
return h17(EnvironmentScreen, { client: c });
|
|
2382
2514
|
case "routes":
|
|
2383
|
-
return
|
|
2515
|
+
return h17(RoutesScreen, { client: c });
|
|
2384
2516
|
case "tokens":
|
|
2385
|
-
return
|
|
2517
|
+
return h17(TokensScreen, { client: c });
|
|
2386
2518
|
case "operators":
|
|
2387
|
-
return
|
|
2519
|
+
return h17(OperatorsScreen, {
|
|
2388
2520
|
client: c,
|
|
2389
2521
|
...props.currentOperator ? { me: props.currentOperator } : {}
|
|
2390
2522
|
});
|
|
2391
2523
|
case "migrations":
|
|
2392
|
-
return
|
|
2524
|
+
return h17(MigrationsScreen, { client: c });
|
|
2393
2525
|
case "log":
|
|
2394
|
-
return
|
|
2526
|
+
return h17(AdminLogScreen, { client: c });
|
|
2395
2527
|
case "catalog":
|
|
2396
|
-
return props.billingClient ?
|
|
2528
|
+
return props.billingClient ? h17(CatalogScreen, { client: props.billingClient }) : missing("Pass a BillingAdminClient to see the catalog here.");
|
|
2397
2529
|
case "subscriber":
|
|
2398
|
-
return props.billingClient ?
|
|
2530
|
+
return props.billingClient ? h17(SubscriberScreen, {
|
|
2531
|
+
client: props.billingClient,
|
|
2532
|
+
...props.authClient ? {
|
|
2533
|
+
onOpenUser: (id) => {
|
|
2534
|
+
openUser.value = id;
|
|
2535
|
+
go("users");
|
|
2536
|
+
}
|
|
2537
|
+
} : {}
|
|
2538
|
+
}) : missing("Pass a BillingAdminClient to look up subscribers here.");
|
|
2399
2539
|
case "audit":
|
|
2400
|
-
return props.auditClient ?
|
|
2540
|
+
return props.auditClient ? h17(AuditScreen, { client: props.auditClient }) : missing("Pass an AuditAdminClient to see the audit trail here.");
|
|
2401
2541
|
case "users":
|
|
2402
|
-
return props.authClient ?
|
|
2542
|
+
return props.authClient ? h17(UsersScreen, {
|
|
2543
|
+
key: openUser.value ?? "list",
|
|
2544
|
+
client: props.authClient,
|
|
2545
|
+
...props.billingClient ? { billingClient: props.billingClient } : {},
|
|
2546
|
+
...openUser.value ? { openUserId: openUser.value } : {}
|
|
2547
|
+
}) : missing("Pass an AuthAdminClient to look up users here.");
|
|
2403
2548
|
case "settings": {
|
|
2404
2549
|
const cc = props.configClient;
|
|
2405
2550
|
if (!cc) return missing("Pass a ConfigAdminClient to manage config and secrets here.");
|
|
@@ -2407,7 +2552,7 @@ var AdminShell = defineComponent14({
|
|
|
2407
2552
|
if (e && e.kind !== "template")
|
|
2408
2553
|
return [
|
|
2409
2554
|
back("Config & secrets"),
|
|
2410
|
-
|
|
2555
|
+
h17(ConfigEditorScreen, {
|
|
2411
2556
|
client: cc,
|
|
2412
2557
|
kind: e.kind,
|
|
2413
2558
|
configKey: e.key,
|
|
@@ -2415,7 +2560,7 @@ var AdminShell = defineComponent14({
|
|
|
2415
2560
|
onSaved: () => editing.value = null
|
|
2416
2561
|
})
|
|
2417
2562
|
];
|
|
2418
|
-
return
|
|
2563
|
+
return h17(ConfigListScreen, {
|
|
2419
2564
|
client: cc,
|
|
2420
2565
|
...props.environment !== void 0 ? { environment: props.environment } : {},
|
|
2421
2566
|
"onSelect-config": (key) => editing.value = { kind: "config", key },
|
|
@@ -2432,13 +2577,13 @@ var AdminShell = defineComponent14({
|
|
|
2432
2577
|
if (e?.kind === "template")
|
|
2433
2578
|
return [
|
|
2434
2579
|
back("Templates"),
|
|
2435
|
-
|
|
2580
|
+
h17(TemplateEditorScreen, {
|
|
2436
2581
|
client: kc,
|
|
2437
2582
|
type: e.type,
|
|
2438
2583
|
onSaved: () => editing.value = null
|
|
2439
2584
|
})
|
|
2440
2585
|
];
|
|
2441
|
-
return
|
|
2586
|
+
return h17(TemplateListScreen, {
|
|
2442
2587
|
client: kc,
|
|
2443
2588
|
"onSelect-template": (t2) => editing.value = { kind: "template", type: t2.type }
|
|
2444
2589
|
});
|
|
@@ -2448,11 +2593,11 @@ var AdminShell = defineComponent14({
|
|
|
2448
2593
|
const visible = (i) => !i.needs || (i.needs === "operators" ? props.operators : i.needs === "config" ? props.configClient : i.needs === "courier" ? props.courierClient : i.needs === "auth" ? props.authClient : i.needs === "billing" ? props.billingClient : props.auditClient);
|
|
2449
2594
|
const brand = () => {
|
|
2450
2595
|
const production = props.envLabel ? /^prod/i.test(props.envLabel) : false;
|
|
2451
|
-
return
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
props.envLabel ?
|
|
2596
|
+
return h17("div", { style: styles.navBrand }, [
|
|
2597
|
+
h17("div", { style: styles.navMark }, [icon("mark", 15)]),
|
|
2598
|
+
h17("div", { style: { minWidth: 0, flex: 1 } }, [
|
|
2599
|
+
h17("div", { style: styles.navBrandTitle }, props.appName),
|
|
2600
|
+
props.envLabel ? h17(
|
|
2456
2601
|
"span",
|
|
2457
2602
|
{
|
|
2458
2603
|
style: {
|
|
@@ -2469,7 +2614,7 @@ var AdminShell = defineComponent14({
|
|
|
2469
2614
|
props.envLabel
|
|
2470
2615
|
) : null
|
|
2471
2616
|
]),
|
|
2472
|
-
narrow.value ?
|
|
2617
|
+
narrow.value ? h17(
|
|
2473
2618
|
"button",
|
|
2474
2619
|
{
|
|
2475
2620
|
type: "button",
|
|
@@ -2481,7 +2626,7 @@ var AdminShell = defineComponent14({
|
|
|
2481
2626
|
) : null
|
|
2482
2627
|
]);
|
|
2483
2628
|
};
|
|
2484
|
-
const nav = () =>
|
|
2629
|
+
const nav = () => h17(
|
|
2485
2630
|
"nav",
|
|
2486
2631
|
{
|
|
2487
2632
|
style: {
|
|
@@ -2492,17 +2637,17 @@ var AdminShell = defineComponent14({
|
|
|
2492
2637
|
},
|
|
2493
2638
|
[
|
|
2494
2639
|
brand(),
|
|
2495
|
-
|
|
2640
|
+
h17(
|
|
2496
2641
|
"div",
|
|
2497
2642
|
{ style: styles.navScroll },
|
|
2498
2643
|
NAV.map((g) => {
|
|
2499
2644
|
const items = g.items.filter(visible);
|
|
2500
2645
|
if (items.length === 0) return null;
|
|
2501
|
-
return
|
|
2502
|
-
|
|
2646
|
+
return h17("div", { key: g.group }, [
|
|
2647
|
+
h17("div", { style: styles.navGroup }, g.group),
|
|
2503
2648
|
...items.map((i) => {
|
|
2504
2649
|
const active = current.value === i.page;
|
|
2505
|
-
return
|
|
2650
|
+
return h17(
|
|
2506
2651
|
"button",
|
|
2507
2652
|
{
|
|
2508
2653
|
key: i.page,
|
|
@@ -2510,7 +2655,10 @@ var AdminShell = defineComponent14({
|
|
|
2510
2655
|
class: "fonderie-admin-nav-item",
|
|
2511
2656
|
style: { ...styles.navItem, ...active ? styles.navItemActive : {} },
|
|
2512
2657
|
"aria-current": active ? "page" : void 0,
|
|
2513
|
-
onClick: () =>
|
|
2658
|
+
onClick: () => {
|
|
2659
|
+
openUser.value = void 0;
|
|
2660
|
+
go(i.page);
|
|
2661
|
+
}
|
|
2514
2662
|
},
|
|
2515
2663
|
[icon(ICON[i.page], 16, active ? styles.navIconActive : void 0), i.label]
|
|
2516
2664
|
);
|
|
@@ -2518,18 +2666,18 @@ var AdminShell = defineComponent14({
|
|
|
2518
2666
|
]);
|
|
2519
2667
|
})
|
|
2520
2668
|
),
|
|
2521
|
-
slots["footer"] ?
|
|
2669
|
+
slots["footer"] ? h17("div", { style: styles.navFooter }, slots["footer"]()) : null
|
|
2522
2670
|
]
|
|
2523
2671
|
);
|
|
2524
2672
|
return () => {
|
|
2525
2673
|
const title = NAV.flatMap((g) => g.items).find((i) => i.page === current.value)?.label ?? "";
|
|
2526
|
-
return
|
|
2674
|
+
return h17(
|
|
2527
2675
|
"div",
|
|
2528
2676
|
{ style: { ...styles.shell, ...narrow.value ? { flexDirection: "column" } : {} } },
|
|
2529
2677
|
[
|
|
2530
2678
|
...narrow.value ? [
|
|
2531
|
-
|
|
2532
|
-
|
|
2679
|
+
h17("div", { style: styles.topbar }, [
|
|
2680
|
+
h17(
|
|
2533
2681
|
"button",
|
|
2534
2682
|
{
|
|
2535
2683
|
type: "button",
|
|
@@ -2540,19 +2688,19 @@ var AdminShell = defineComponent14({
|
|
|
2540
2688
|
},
|
|
2541
2689
|
[icon("menu")]
|
|
2542
2690
|
),
|
|
2543
|
-
|
|
2544
|
-
props.envLabel ?
|
|
2691
|
+
h17("span", { style: { fontWeight: 600, fontSize: "14px" } }, title),
|
|
2692
|
+
props.envLabel ? h17("span", { style: { ...styles.badge, marginLeft: "auto" } }, props.envLabel) : null
|
|
2545
2693
|
]),
|
|
2546
|
-
drawer.value ?
|
|
2694
|
+
drawer.value ? h17("div", { style: styles.drawer }, [
|
|
2547
2695
|
nav(),
|
|
2548
|
-
|
|
2696
|
+
h17("div", {
|
|
2549
2697
|
style: styles.scrim,
|
|
2550
2698
|
"aria-hidden": "true",
|
|
2551
2699
|
onClick: () => drawer.value = false
|
|
2552
2700
|
})
|
|
2553
2701
|
]) : null
|
|
2554
2702
|
] : [nav()],
|
|
2555
|
-
|
|
2703
|
+
h17("main", { class: "fonderie-admin-main", style: styles.main }, body())
|
|
2556
2704
|
]
|
|
2557
2705
|
);
|
|
2558
2706
|
};
|
|
@@ -2570,6 +2718,7 @@ export {
|
|
|
2570
2718
|
ModulesScreen,
|
|
2571
2719
|
OperatorsScreen,
|
|
2572
2720
|
RoutesScreen,
|
|
2721
|
+
SubscriberBilling,
|
|
2573
2722
|
SubscriberScreen,
|
|
2574
2723
|
TokensScreen,
|
|
2575
2724
|
UsersScreen
|