@hanzo/build 0.2.45 → 0.2.47
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/lib/claims.d.ts +4 -4
- package/lib/claims.js +10 -5
- package/lib/host.d.ts +1 -1
- package/lib/settings/billing.js +6 -4
- package/lib/settings/card.js +1 -1
- package/package.json +1 -1
package/lib/claims.d.ts
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
/** The token's claims, or null when it is not a JWT. */
|
|
6
6
|
export declare function claims(token: string | null): Record<string, unknown> | null;
|
|
7
7
|
/**
|
|
8
|
-
* Whether the token says its person administers `org
|
|
9
|
-
* token's own `orgs` claim — never the reserved `admin` org, which is
|
|
10
|
-
* authority and not what a builder decides anything by. The platform
|
|
11
|
-
* it again; this only decides which
|
|
8
|
+
* Whether the token says its person administers `org`: owner or admin, read off
|
|
9
|
+
* the token's own `orgs` claim — never the reserved `admin` org, which is
|
|
10
|
+
* platform authority and not what a builder decides anything by. The platform
|
|
11
|
+
* enforces it again; this only decides which controls are offered.
|
|
12
12
|
*/
|
|
13
13
|
export declare function administers(token: string | null, org: string | null): boolean;
|
package/lib/claims.js
CHANGED
|
@@ -16,15 +16,20 @@ export function claims(token) {
|
|
|
16
16
|
return null;
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
/** The coarse IAM roles that administer an org: its owner and its admins. */
|
|
20
|
+
const ADMIN = ['owner', 'admin'];
|
|
19
21
|
/**
|
|
20
|
-
* Whether the token says its person administers `org
|
|
21
|
-
* token's own `orgs` claim — never the reserved `admin` org, which is
|
|
22
|
-
* authority and not what a builder decides anything by. The platform
|
|
23
|
-
* it again; this only decides which
|
|
22
|
+
* Whether the token says its person administers `org`: owner or admin, read off
|
|
23
|
+
* the token's own `orgs` claim — never the reserved `admin` org, which is
|
|
24
|
+
* platform authority and not what a builder decides anything by. The platform
|
|
25
|
+
* enforces it again; this only decides which controls are offered.
|
|
24
26
|
*/
|
|
25
27
|
export function administers(token, org) {
|
|
26
28
|
if (!org)
|
|
27
29
|
return false;
|
|
28
30
|
const set = claims(token)?.orgs;
|
|
29
|
-
return (Array.isArray(set) ? set : []).some((r) =>
|
|
31
|
+
return (Array.isArray(set) ? set : []).some((r) => {
|
|
32
|
+
const m = r;
|
|
33
|
+
return m?.org === org && ADMIN.includes(m.role);
|
|
34
|
+
});
|
|
30
35
|
}
|
package/lib/host.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export interface Host {
|
|
|
37
37
|
chooseTheme?: (theme: Theme) => void;
|
|
38
38
|
/** Who is signed in, or null. */
|
|
39
39
|
person: Person | null;
|
|
40
|
-
/** Whether the person administers `org
|
|
40
|
+
/** Whether the person administers `org` (its owner or an admin): they publish to main and change its money; anyone else opens a review and reads. */
|
|
41
41
|
admin: boolean;
|
|
42
42
|
/** The address under the mount: '' | a session id | a slug | '-/codebases' | '-/projects' | '-/issues' | '-/artifacts' | '-/templates'. */
|
|
43
43
|
path: string;
|
package/lib/settings/billing.js
CHANGED
|
@@ -6,7 +6,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
*/
|
|
7
7
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
8
8
|
import { CreditCard, Download, Plus, X } from '@hanzogui/lucide-icons-2';
|
|
9
|
-
import { Button, Dialog, DialogContent, DialogTitle } from '@hanzo/ui';
|
|
9
|
+
import { Button, Dialog, DialogContent, DialogTitle, toast } from '@hanzo/ui';
|
|
10
10
|
import { useState } from 'react';
|
|
11
11
|
import { cancel, current, detach, invoices, label, methods, money, pdf, reactivate, subscriptions, } from '../api/billing.js';
|
|
12
12
|
import { useRead } from '../data.js';
|
|
@@ -30,13 +30,15 @@ export function Billing() {
|
|
|
30
30
|
return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Billing" }), _jsx(Soft, { children: "Sign in to see this organization\u2019s billing." })] }));
|
|
31
31
|
}
|
|
32
32
|
const plan = current(subs.value);
|
|
33
|
+
const empty = !cards.error && !cards.loading && cards.value.length === 0;
|
|
34
|
+
// A done act is a toast; a refused one stays on the page, beside what was tried.
|
|
33
35
|
const act = async (what, done, after) => {
|
|
34
36
|
setWorking(true);
|
|
35
37
|
setNote('');
|
|
36
38
|
try {
|
|
37
39
|
await what();
|
|
38
40
|
after();
|
|
39
|
-
|
|
41
|
+
toast.success(done);
|
|
40
42
|
}
|
|
41
43
|
catch (e) {
|
|
42
44
|
setNote(e.message);
|
|
@@ -49,9 +51,9 @@ export function Billing() {
|
|
|
49
51
|
? subs.error.message
|
|
50
52
|
: plan
|
|
51
53
|
? terms(plan)
|
|
52
|
-
: 'No paid plan. Usage is paid from the balance.' })] }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => host.go(path({ kind: 'screen', screen: 'plans' })), children: "Adjust plan" })] }),
|
|
54
|
+
: 'No paid plan. Usage is paid from the balance.' })] }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => host.go(path({ kind: 'screen', screen: 'plans' })), children: "Adjust plan" })] }), _jsxs(Group, { title: "Payment", detail: "The card a plan renews on and a top-up charges.", action: host.admin && (cards.error || cards.value.length > 0) ? (_jsxs(Button, { size: "sm", variant: "outline", onPress: () => setAdding(true), children: [_jsx(Plus, { size: 14 }), " Add card"] })) : undefined, children: [cards.error ? (_jsx(Soft, { children: cards.error.message })) : cards.loading && !cards.value.length ? (_jsx(Soft, { children: "Reading cards\u2026" })) : empty ? (_jsxs(XStack, { items: "center", gap: "$3", px: "$4", py: "$4", borderWidth: 1, borderColor: "$borderColor", rounded: "$3", flexWrap: "wrap", children: [_jsx(CreditCard, { size: 20 }), _jsxs(YStack, { flex: 1, minW: 180, gap: "$1", children: [_jsx(SizableText, { size: "$3", color: "$ink", children: "No card on file." }), _jsx(SizableText, { size: "$2", color: "$soft", children: host.admin ? 'Add one to pay for a plan or a top-up. Adding it charges nothing.' : 'An org admin adds cards.' })] }), host.admin ? (_jsxs(Button, { onPress: () => setAdding(true), children: [_jsx(Plus, { size: 16 }), " Add card"] })) : null] })) : (_jsx(Card, { children: cards.value.map((m, i) => (_jsx(Row, { first: i === 0, leading: _jsx(CreditCard, { size: 16 }), title: label(m), detail: [m.expires ? `Expires ${m.expires}` : '', m.default ? 'Default' : ''].filter(Boolean).join(' · ') || undefined, trailing: host.admin ? (_jsx(Button, { size: "sm", variant: "ghost", "aria-label": `Remove ${label(m)}`, disabled: working, onPress: () => void act(() => detach(t, m.id), `${label(m)} is removed`, cards.reload), children: "Remove" })) : undefined }, m.id))) })), !host.admin && cards.value.length ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "An org admin adds and removes cards." })) : null] }), _jsx(Group, { title: "Invoices", children: bills.error ? (_jsx(Soft, { children: bills.error.message })) : bills.loading && !bills.value.length ? (_jsx(Soft, { children: "Reading invoices\u2026" })) : bills.value.length === 0 ? (_jsx(Card, { children: _jsx(Soft, { children: "No invoices yet." }) })) : (_jsxs(Card, { children: [_jsxs(XStack, { px: "$3", py: "$2", gap: "$3", children: [_jsx(SizableText, { flex: 1, size: "$1", color: "$soft", children: "Date" }), _jsx(SizableText, { width: 90, size: "$1", color: "$soft", style: { textAlign: 'right' }, children: "Total" }), _jsx(SizableText, { width: 70, size: "$1", color: "$soft", "$max-sm": { display: 'none' }, children: "Status" }), _jsx(YStack, { width: 64 })] }), bills.value.map((b) => (_jsxs(XStack, { items: "center", gap: "$3", px: "$3", py: "$2.5", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { flex: 1, size: "$2", color: "$ink", numberOfLines: 1, children: day(b.date) || b.number || b.id }), _jsx(SizableText, { width: 90, size: "$2", color: "$ink", style: { textAlign: 'right' }, children: money(b.total, b.currency) }), _jsx(SizableText, { width: 70, size: "$1", color: "$soft", "$max-sm": { display: 'none' }, children: word(b.status) }), _jsx(XStack, { width: 64, justify: "flex-end", children: _jsx(Button, { size: "sm", variant: "ghost", "aria-label": `View invoice ${b.number || day(b.date)}`, onPress: () => setOpen(b), children: "View" }) })] }, b.id)))] })) }), plan ? (_jsx(Group, { title: "Cancellation", children: _jsx(Card, { children: _jsx(Row, { first: true, title: plan.ending ? `Your plan ends on ${day(plan.ends)}` : 'Cancel plan', detail: plan.ending ? 'Keep it, and it renews as before.' : 'It runs to the end of the period you paid for, then ends.', trailing: !host.admin ? undefined : plan.ending ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void act(() => reactivate(t, plan.id), 'Your plan renews as before', subs.reload), children: "Keep plan" })) : (_jsx(Button, { size: "sm", variant: "destructive", disabled: working, onPress: () => setEnding(true), children: "Cancel" })) }) }) })) : null, _jsx(Note, { children: note }), _jsx(AddCard, { open: adding, onOpenChange: setAdding, onSaved: (m) => {
|
|
53
55
|
cards.reload();
|
|
54
|
-
|
|
56
|
+
toast.success(`${label(m)} is saved`);
|
|
55
57
|
} }), plan ? (_jsx(Ending, { plan: plan, open: ending, working: working, onOpenChange: setEnding, onConfirm: () => void act(() => cancel(t, plan.id), `Your plan ends on ${day(plan.ends)}`, () => {
|
|
56
58
|
setEnding(false);
|
|
57
59
|
subs.reload();
|
package/lib/settings/card.js
CHANGED
|
@@ -101,5 +101,5 @@ export function AddCard({ open, onOpenChange, onSaved }) {
|
|
|
101
101
|
const why = cfg.error ? cfg.error.message : !cfg.loading && !ready(p) ? 'This deployment takes no cards yet.' : '';
|
|
102
102
|
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: 440, showCloseButton: false, children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: "$2", children: [_jsx(DialogTitle, { children: "Add a card" }), _jsx(XStack, { render: "button", "aria-label": "Close", p: "$1", onPress: () => onOpenChange(false), children: _jsx(X, { size: 16 }) })] }), _jsxs(YStack, { gap: "$3", children: [why ? (_jsx(SizableText, { size: "$2", color: "$soft", children: why })) : (_jsxs(_Fragment, { children: [_jsxs(YStack, { minH: 96, "aria-label": "Card details", children: [_jsx("div", { ref: setBox, style: { minHeight: 96 } }), !mounted && !note ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "Loading the card form\u2026" })) : null] }), _jsx(SizableText, { size: "$1", color: "$soft", children: p && p.environment !== 'production'
|
|
103
103
|
? 'Sandbox: use a test card. Nothing is charged.'
|
|
104
|
-
: 'The card number goes to the payment processor, never to Hanzo.' })] })), note ? (_jsx(SizableText, { size: "$
|
|
104
|
+
: 'The card number goes to the payment processor, never to Hanzo. Saving it charges nothing.' })] })), note ? (_jsx(SizableText, { size: "$2", color: "$red10", role: "alert", children: note })) : null, _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => onOpenChange(false), children: "Cancel" }), _jsx(Button, { size: "sm", disabled: !mounted || working, onPress: () => void add(), children: "Save card" })] })] })] }) }));
|
|
105
105
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/build",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.47",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"description": "Hanzo Build — say what you want, watch it built, deployed and running. The builder as a component, mounted by the Hanzo app at hanzo.ai.",
|