@hanzo/ui 8.27.32 → 8.27.38
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/auth/SignIn.cjs +180 -17
- package/dist/auth/SignIn.d.ts +25 -1
- package/dist/auth/SignIn.d.ts.map +1 -1
- package/dist/auth/SignIn.js +146 -18
- package/dist/auth/SignIn.js.map +1 -1
- package/dist/consent/Consent.cjs +35 -14
- package/dist/consent/Consent.d.ts +1 -1
- package/dist/consent/Consent.d.ts.map +1 -1
- package/dist/consent/Consent.js +36 -15
- package/dist/consent/Consent.js.map +1 -1
- package/dist/gallery.cjs +13 -1
- package/dist/gallery.d.ts.map +1 -1
- package/dist/gallery.html +10 -6
- package/dist/gallery.js +14 -2
- package/dist/gallery.js.map +1 -1
- package/dist/marketing/ClosingCta.cjs +4 -4
- package/dist/marketing/ClosingCta.d.ts +5 -3
- package/dist/marketing/ClosingCta.d.ts.map +1 -1
- package/dist/marketing/ClosingCta.js +4 -4
- package/dist/marketing/ClosingCta.js.map +1 -1
- package/dist/marketing/Faq.cjs +1 -1
- package/dist/marketing/Faq.js +1 -1
- package/dist/marketing/Faq.js.map +1 -1
- package/dist/marketing/Footer.cjs +27 -0
- package/dist/marketing/Footer.d.ts +25 -0
- package/dist/marketing/Footer.d.ts.map +1 -0
- package/dist/marketing/Footer.js +25 -0
- package/dist/marketing/Footer.js.map +1 -0
- package/dist/marketing/Hero.cjs +2 -2
- package/dist/marketing/Hero.js +2 -2
- package/dist/marketing/Hero.js.map +1 -1
- package/dist/marketing/Plans.cjs +15 -9
- package/dist/marketing/Plans.d.ts +5 -3
- package/dist/marketing/Plans.d.ts.map +1 -1
- package/dist/marketing/Plans.js +16 -10
- package/dist/marketing/Plans.js.map +1 -1
- package/dist/marketing/blocks.cjs +3 -3
- package/dist/marketing/blocks.js +3 -3
- package/dist/marketing/blocks.js.map +1 -1
- package/dist/marketing/index.cjs +4 -1
- package/dist/marketing/index.d.ts +2 -1
- package/dist/marketing/index.d.ts.map +1 -1
- package/dist/marketing/index.js +2 -1
- package/dist/marketing/index.js.map +1 -1
- package/dist/marketing/rows.cjs +26 -0
- package/dist/marketing/rows.d.ts +10 -0
- package/dist/marketing/rows.d.ts.map +1 -1
- package/dist/marketing/rows.js +25 -0
- package/dist/marketing/rows.js.map +1 -1
- package/dist/onboarding/ChatOnboarding.cjs +1 -1
- package/dist/onboarding/ChatOnboarding.js +2 -2
- package/dist/onboarding/ChatOnboarding.js.map +1 -1
- package/dist/onboarding/Checkout.cjs +19 -5
- package/dist/onboarding/Checkout.d.ts +3 -1
- package/dist/onboarding/Checkout.d.ts.map +1 -1
- package/dist/onboarding/Checkout.js +20 -6
- package/dist/onboarding/Checkout.js.map +1 -1
- package/dist/onboarding/Onboarding.cjs +99 -21
- package/dist/onboarding/Onboarding.d.ts +32 -4
- package/dist/onboarding/Onboarding.d.ts.map +1 -1
- package/dist/onboarding/Onboarding.js +102 -24
- package/dist/onboarding/Onboarding.js.map +1 -1
- package/dist/onboarding/account.cjs +16 -0
- package/dist/onboarding/account.d.ts +21 -0
- package/dist/onboarding/account.d.ts.map +1 -0
- package/dist/onboarding/account.js +14 -0
- package/dist/onboarding/account.js.map +1 -0
- package/dist/onboarding/api.cjs +4 -1
- package/dist/onboarding/api.d.ts +5 -1
- package/dist/onboarding/api.d.ts.map +1 -1
- package/dist/onboarding/api.js +2 -0
- package/dist/onboarding/api.js.map +1 -1
- package/dist/onboarding/catalog.cjs +30 -0
- package/dist/onboarding/catalog.d.ts +5 -0
- package/dist/onboarding/catalog.d.ts.map +1 -0
- package/dist/onboarding/catalog.js +27 -0
- package/dist/onboarding/catalog.js.map +1 -0
- package/dist/onboarding/frame.cjs +15 -5
- package/dist/onboarding/frame.d.ts +14 -4
- package/dist/onboarding/frame.d.ts.map +1 -1
- package/dist/onboarding/frame.js +14 -5
- package/dist/onboarding/frame.js.map +1 -1
- package/dist/onboarding/hooks.cjs +25 -7
- package/dist/onboarding/hooks.d.ts +15 -4
- package/dist/onboarding/hooks.d.ts.map +1 -1
- package/dist/onboarding/hooks.js +26 -9
- package/dist/onboarding/hooks.js.map +1 -1
- package/dist/onboarding/index.cjs +16 -4
- package/dist/onboarding/index.d.ts +8 -5
- package/dist/onboarding/index.d.ts.map +1 -1
- package/dist/onboarding/index.js +8 -5
- package/dist/onboarding/index.js.map +1 -1
- package/dist/onboarding/plans.cjs +75 -68
- package/dist/onboarding/plans.d.ts +41 -22
- package/dist/onboarding/plans.d.ts.map +1 -1
- package/dist/onboarding/plans.js +66 -64
- package/dist/onboarding/plans.js.map +1 -1
- package/dist/onboarding/screens.cjs +20 -8
- package/dist/onboarding/screens.d.ts +12 -2
- package/dist/onboarding/screens.d.ts.map +1 -1
- package/dist/onboarding/screens.js +20 -9
- package/dist/onboarding/screens.js.map +1 -1
- package/dist/onboarding/state.cjs +17 -2
- package/dist/onboarding/state.d.ts +22 -3
- package/dist/onboarding/state.d.ts.map +1 -1
- package/dist/onboarding/state.js +15 -2
- package/dist/onboarding/state.js.map +1 -1
- package/dist/styles.css +3 -0
- package/package.json +3 -3
|
@@ -1,40 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* What Hanzo sells, priced from
|
|
2
|
+
* What Hanzo sells, priced from commerce's live catalog (GET /v1/billing/plans)
|
|
3
|
+
* and nothing else: no price is typed in this package. `adopt` takes the rows,
|
|
4
|
+
* after which every function here answers from them.
|
|
3
5
|
*
|
|
4
6
|
* free chat only; offered on the personal path alone
|
|
5
|
-
* dev,
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* A year is the month times 0.82, paid at once.
|
|
7
|
+
* dev Pro, the personal plan
|
|
8
|
+
* max_5x, max_20x one Max card; the tier is picked at checkout
|
|
9
|
+
* team_standard the catalog's per-seat Team plan, two seats at least
|
|
10
|
+
* team_premium a heavier seat, offered only while the catalog lists one
|
|
10
11
|
*/
|
|
11
|
-
export type PlanId = 'free' | 'dev' | '
|
|
12
|
+
export type PlanId = 'free' | 'dev' | 'max_5x' | 'max_20x' | 'team_standard' | 'team_premium';
|
|
12
13
|
export type Interval = 'monthly' | 'annual';
|
|
13
14
|
export type Category = 'individual' | 'team';
|
|
14
|
-
/** Annual price is the monthly price times this. */
|
|
15
|
-
export declare const ANNUAL = 0.82;
|
|
16
|
-
/** What a team seat adds to the personal plan it is built on, per month. */
|
|
17
|
-
export declare const SEAT_ADDON = 5;
|
|
18
15
|
export declare const MIN_SEATS = 2;
|
|
19
16
|
export declare const MAX_SEATS = 150;
|
|
20
17
|
/** From 20 people the team plan becomes a conversation. */
|
|
21
18
|
export declare const ENTERPRISE_FROM = 20;
|
|
19
|
+
/** The commerce catalog's slug for each plan. */
|
|
20
|
+
export declare const SLUG: Record<PlanId, string>;
|
|
21
|
+
/** A row of the catalog, as commerce sends it (prices in cents). */
|
|
22
|
+
export interface Row {
|
|
23
|
+
slug: string;
|
|
24
|
+
name: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
price: number;
|
|
27
|
+
annualTotal?: number;
|
|
28
|
+
features?: string[];
|
|
29
|
+
limits?: Record<string, number>;
|
|
30
|
+
}
|
|
22
31
|
export interface Offer {
|
|
23
32
|
id: PlanId;
|
|
33
|
+
slug: string;
|
|
24
34
|
name: string;
|
|
25
35
|
category: Category;
|
|
26
|
-
/**
|
|
36
|
+
/** Dollars per month (per seat for a team plan). */
|
|
27
37
|
monthly: number;
|
|
38
|
+
/** Dollars for a year paid at once, where the catalog sells one. */
|
|
39
|
+
yearly: number | null;
|
|
28
40
|
blurb: string;
|
|
29
41
|
features: string[];
|
|
30
42
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
/** The offers as loaded: a plan the catalog does not list is absent. */
|
|
44
|
+
export declare const OFFERS: Partial<Record<PlanId, Offer>>;
|
|
45
|
+
/** Take the catalog's rows as what is sold. */
|
|
46
|
+
export declare function adopt(rows: Row[]): void;
|
|
47
|
+
/** The offer for a plan; throws while the catalog has not loaded or does not list it. */
|
|
48
|
+
export declare function offer(id: PlanId): Offer;
|
|
49
|
+
/** Whether the catalog lists this plan. */
|
|
50
|
+
export declare const has: (id: PlanId) => boolean;
|
|
51
|
+
/** Whether this plan is sold by the year. */
|
|
52
|
+
export declare const yearly: (id: PlanId) => boolean;
|
|
53
|
+
/** One unit's price per month at this interval (a year spread over its twelve months). */
|
|
54
|
+
export declare function unit(id: PlanId, interval: Interval): number;
|
|
34
55
|
/** What one unit costs per billing period: the month, or the year at once. */
|
|
35
|
-
export declare
|
|
56
|
+
export declare function period(id: PlanId, interval: Interval): number;
|
|
36
57
|
/** What a year saves over twelve months, as a whole percent. */
|
|
37
|
-
export declare
|
|
58
|
+
export declare function saving(id: PlanId): number;
|
|
38
59
|
export interface Lines {
|
|
39
60
|
seats: number;
|
|
40
61
|
/** What is charged today (before tax): the period's price times the seats. */
|
|
@@ -43,9 +64,9 @@ export interface Lines {
|
|
|
43
64
|
perMonth: number;
|
|
44
65
|
}
|
|
45
66
|
export declare function lines(id: PlanId, interval: Interval, seats?: number): Lines;
|
|
46
|
-
/** GA4-shaped item for a commerce event: price is per unit, quantity is seats. */
|
|
67
|
+
/** GA4-shaped item for a commerce event: price is per unit, quantity is seats. Both Max tiers are item `max`. */
|
|
47
68
|
export declare function item(id: PlanId, interval: Interval, seats?: number): {
|
|
48
|
-
item_id:
|
|
69
|
+
item_id: string;
|
|
49
70
|
item_name: string;
|
|
50
71
|
item_category: Category;
|
|
51
72
|
item_variant: Interval;
|
|
@@ -53,8 +74,6 @@ export declare function item(id: PlanId, interval: Interval, seats?: number): {
|
|
|
53
74
|
quantity: number;
|
|
54
75
|
};
|
|
55
76
|
export declare const usd: (n: number) => string;
|
|
56
|
-
/** `$
|
|
77
|
+
/** `$20` when whole, `$16.67` when not. */
|
|
57
78
|
export declare const money: (n: number) => string;
|
|
58
|
-
/** The commerce catalog's slug for a plan, and the quantity it is bought at. */
|
|
59
|
-
export declare const SLUG: Record<PlanId, string>;
|
|
60
79
|
//# sourceMappingURL=plans.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plans.d.ts","sourceRoot":"","sources":["../../src/onboarding/plans.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plans.d.ts","sourceRoot":"","sources":["../../src/onboarding/plans.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,eAAe,GAAG,cAAc,CAAA;AAC7F,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAA;AAC3C,MAAM,MAAM,QAAQ,GAAG,YAAY,GAAG,MAAM,CAAA;AAE5C,eAAO,MAAM,SAAS,IAAI,CAAA;AAC1B,eAAO,MAAM,SAAS,MAAM,CAAA;AAC5B,2DAA2D;AAC3D,eAAO,MAAM,eAAe,KAAK,CAAA;AAEjC,iDAAiD;AACjD,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAA2H,CAAA;AAEnK,oEAAoE;AACpE,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,QAAQ,CAAA;IAClB,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAA;IACf,oEAAoE;IACpE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAMD,wEAAwE;AACxE,eAAO,MAAM,MAAM,gCAAS,CAAA;AAE5B,+CAA+C;AAC/C,wBAAgB,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAiBvC;AAED,yFAAyF;AACzF,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,CAIvC;AAED,2CAA2C;AAC3C,eAAO,MAAM,GAAG,OAAQ,MAAM,KAAG,OAAmC,CAAA;AAEpE,6CAA6C;AAC7C,eAAO,MAAM,MAAM,OAAQ,MAAM,KAAG,OAAoC,CAAA;AAExE,0FAA0F;AAC1F,wBAAgB,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAG3D;AAED,8EAA8E;AAC9E,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAG7D;AAED,gEAAgE;AAChE,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAGzC;AAED,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAA;IAChB,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,SAAI,GAAG,KAAK,CAGtE;AAED,iHAAiH;AACjH,wBAAgB,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,SAAI;;;;;;;EAG7D;AAED,eAAO,MAAM,GAAG,MAAO,MAAM,KAAG,MAA4B,CAAA;AAC5D,2CAA2C;AAC3C,eAAO,MAAM,KAAK,MAAO,MAAM,KAAG,MAAkD,CAAA"}
|
package/dist/onboarding/plans.js
CHANGED
|
@@ -1,79 +1,81 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
/**
|
|
2
|
+
* What Hanzo sells, priced from commerce's live catalog (GET /v1/billing/plans)
|
|
3
|
+
* and nothing else: no price is typed in this package. `adopt` takes the rows,
|
|
4
|
+
* after which every function here answers from them.
|
|
5
|
+
*
|
|
6
|
+
* free chat only; offered on the personal path alone
|
|
7
|
+
* dev Pro, the personal plan
|
|
8
|
+
* max_5x, max_20x one Max card; the tier is picked at checkout
|
|
9
|
+
* team_standard the catalog's per-seat Team plan, two seats at least
|
|
10
|
+
* team_premium a heavier seat, offered only while the catalog lists one
|
|
11
|
+
*/
|
|
6
12
|
export const MIN_SEATS = 2;
|
|
7
13
|
export const MAX_SEATS = 150;
|
|
8
14
|
/** From 20 people the team plan becomes a conversation. */
|
|
9
15
|
export const ENTERPRISE_FROM = 20;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (!p || p.priceMonthly == null)
|
|
13
|
-
throw new Error(`@hanzo/plans has no monthly price for ${slug}`);
|
|
14
|
-
return p.priceMonthly;
|
|
15
|
-
};
|
|
16
|
+
/** The commerce catalog's slug for each plan. */
|
|
17
|
+
export const SLUG = { free: 'free', dev: 'dev', max_5x: 'max-5x', max_20x: 'max-20x', team_standard: 'team', team_premium: 'team-premium' };
|
|
16
18
|
const cents = (n) => Math.round(n * 100) / 100;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
/** One unit's price per month at this interval (a year, spread over its twelve months). */
|
|
60
|
-
export const unit = (id, interval) => cents(interval === 'annual' ? OFFERS[id].monthly * ANNUAL : OFFERS[id].monthly);
|
|
19
|
+
const offers = {};
|
|
20
|
+
/** The offers as loaded: a plan the catalog does not list is absent. */
|
|
21
|
+
export const OFFERS = offers;
|
|
22
|
+
/** Take the catalog's rows as what is sold. */
|
|
23
|
+
export function adopt(rows) {
|
|
24
|
+
for (const k of Object.keys(offers))
|
|
25
|
+
delete offers[k];
|
|
26
|
+
for (const id of Object.keys(SLUG)) {
|
|
27
|
+
const r = rows.find((x) => x.slug === SLUG[id]);
|
|
28
|
+
if (!r)
|
|
29
|
+
continue;
|
|
30
|
+
const team = id.startsWith('team');
|
|
31
|
+
offers[id] = {
|
|
32
|
+
id,
|
|
33
|
+
slug: r.slug,
|
|
34
|
+
name: team ? (id === 'team_standard' ? 'Standard seat' : 'Premium seat') : r.name,
|
|
35
|
+
category: team ? 'team' : 'individual',
|
|
36
|
+
monthly: cents(r.price / 100),
|
|
37
|
+
yearly: r.annualTotal ? cents(r.annualTotal / 100) : null,
|
|
38
|
+
blurb: r.description ?? '',
|
|
39
|
+
features: r.features ?? [],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** The offer for a plan; throws while the catalog has not loaded or does not list it. */
|
|
44
|
+
export function offer(id) {
|
|
45
|
+
const o = offers[id];
|
|
46
|
+
if (!o)
|
|
47
|
+
throw new Error(`the catalog lists no ${id} plan`);
|
|
48
|
+
return o;
|
|
49
|
+
}
|
|
50
|
+
/** Whether the catalog lists this plan. */
|
|
51
|
+
export const has = (id) => offers[id] !== undefined;
|
|
52
|
+
/** Whether this plan is sold by the year. */
|
|
53
|
+
export const yearly = (id) => offer(id).yearly !== null;
|
|
54
|
+
/** One unit's price per month at this interval (a year spread over its twelve months). */
|
|
55
|
+
export function unit(id, interval) {
|
|
56
|
+
const o = offer(id);
|
|
57
|
+
return interval === 'annual' && o.yearly !== null ? cents(o.yearly / 12) : o.monthly;
|
|
58
|
+
}
|
|
61
59
|
/** What one unit costs per billing period: the month, or the year at once. */
|
|
62
|
-
export
|
|
60
|
+
export function period(id, interval) {
|
|
61
|
+
const o = offer(id);
|
|
62
|
+
return interval === 'annual' && o.yearly !== null ? o.yearly : o.monthly;
|
|
63
|
+
}
|
|
63
64
|
/** What a year saves over twelve months, as a whole percent. */
|
|
64
|
-
export
|
|
65
|
+
export function saving(id) {
|
|
66
|
+
const o = offer(id);
|
|
67
|
+
return o.yearly !== null && o.monthly > 0 ? Math.round((1 - o.yearly / (12 * o.monthly)) * 100) : 0;
|
|
68
|
+
}
|
|
65
69
|
export function lines(id, interval, seats = 1) {
|
|
66
|
-
const n =
|
|
70
|
+
const n = offer(id).category === 'team' ? Math.max(MIN_SEATS, Math.min(MAX_SEATS, Math.floor(seats))) : 1;
|
|
67
71
|
return { seats: n, subtotal: cents(period(id, interval) * n), perMonth: cents(unit(id, interval) * n) };
|
|
68
72
|
}
|
|
69
|
-
/** GA4-shaped item for a commerce event: price is per unit, quantity is seats. */
|
|
73
|
+
/** GA4-shaped item for a commerce event: price is per unit, quantity is seats. Both Max tiers are item `max`. */
|
|
70
74
|
export function item(id, interval, seats = 1) {
|
|
71
|
-
const o =
|
|
72
|
-
return { item_id: id, item_name: o.name, item_category: o.category, item_variant: interval, price: period(id, interval), quantity: lines(id, interval, seats).seats };
|
|
75
|
+
const o = offer(id);
|
|
76
|
+
return { item_id: id.startsWith('max') ? 'max' : id, item_name: o.name, item_category: o.category, item_variant: interval, price: period(id, interval), quantity: lines(id, interval, seats).seats };
|
|
73
77
|
}
|
|
74
78
|
export const usd = (n) => `$${n.toFixed(2)}`;
|
|
75
|
-
/** `$
|
|
79
|
+
/** `$20` when whole, `$16.67` when not. */
|
|
76
80
|
export const money = (n) => (Number.isInteger(n) ? `$${n}` : usd(n));
|
|
77
|
-
/** The commerce catalog's slug for a plan, and the quantity it is bought at. */
|
|
78
|
-
export const SLUG = { free: 'free', dev: 'dev', max: 'max', team_standard: 'team-standard', team_premium: 'team-premium' };
|
|
79
81
|
//# sourceMappingURL=plans.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plans.js","sourceRoot":"","sources":["../../src/onboarding/plans.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"plans.js","sourceRoot":"","sources":["../../src/onboarding/plans.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,7 @@ exports.slug = void 0;
|
|
|
5
5
|
exports.Primary = Primary;
|
|
6
6
|
exports.UseCards = UseCards;
|
|
7
7
|
exports.Plans = Plans;
|
|
8
|
+
exports.Pro = Pro;
|
|
8
9
|
exports.TeamName = TeamName;
|
|
9
10
|
exports.IntervalChoice = IntervalChoice;
|
|
10
11
|
exports.Seats = Seats;
|
|
@@ -12,6 +13,7 @@ exports.Enterprise = Enterprise;
|
|
|
12
13
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
13
14
|
const react_1 = require("react");
|
|
14
15
|
const lucide_icons_2_1 = require("@hanzogui/lucide-icons-2");
|
|
16
|
+
const badge_1 = require("../backends/gui/badge.cjs");
|
|
15
17
|
const button_1 = require("../backends/gui/button.cjs");
|
|
16
18
|
const card_1 = require("../backends/gui/card.cjs");
|
|
17
19
|
const choice_1 = require("../backends/gui/choice.cjs");
|
|
@@ -49,12 +51,21 @@ function UseCards({ onPick, onAsk }) {
|
|
|
49
51
|
return ((0, jsx_runtime_1.jsxs)(layout_1.YStack, { items: "center", gap: "$5", width: "100%", children: [(0, jsx_runtime_1.jsxs)(layout_1.XStack, { role: "group", "aria-label": "How you will use Hanzo", flexWrap: "wrap", justify: "center", gap: "$6", width: "100%", children: [(0, jsx_runtime_1.jsx)(UseCard, { icon: (0, jsx_runtime_1.jsx)(lucide_icons_2_1.User, { size: 64 }), name: "For personal use", note: "For individuals who want to build and experiment with their own projects", dim: team, onPress: () => onPick('personal') }), (0, jsx_runtime_1.jsx)(UseCard, { icon: (0, jsx_runtime_1.jsx)(lucide_icons_2_1.Users, { size: 64 }), name: "With my team", note: "For organizations who want to collaborate and build at scale", on: team, onPress: () => setTeam(true) })] }), team ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(layout_1.XStack, { role: "group", "aria-label": "Team size", flexWrap: "wrap", justify: "center", gap: "$6", width: "100%", children: [(0, jsx_runtime_1.jsx)(UseCard, { size: "small", name: "Team", note: `For ${plans_1.MIN_SEATS}–${plans_1.MAX_SEATS} people. Predictable per-seat pricing, set up in minutes.`, onPress: () => onPick('team') }), (0, jsx_runtime_1.jsx)(UseCard, { size: "small", name: "Enterprise", note: `For ${plans_1.ENTERPRISE_FROM} or more people. Pooled usage, advanced admin and security controls.`, onPress: () => onPick('enterprise') })] }), (0, jsx_runtime_1.jsxs)(layout_1.XStack, { items: "center", gap: "$1", children: [(0, jsx_runtime_1.jsx)(layout_1.SizableText, { size: "$2", color: "$quiet", children: "Questions?" }), (0, jsx_runtime_1.jsx)(Ask, { onAsk: onAsk })] })] })) : null] }));
|
|
50
52
|
}
|
|
51
53
|
/* ---------------------------------------------------------------- plans */
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return ((0, jsx_runtime_1.
|
|
54
|
+
/** A plan's features: a line ending in a colon is a heading for the ones under it, the rest are ticked. */
|
|
55
|
+
function Features({ items }) {
|
|
56
|
+
return ((0, jsx_runtime_1.jsx)(layout_1.YStack, { render: "ul", p: "$5", gap: "$2.5", m: 0, children: items.map((f) => f.endsWith(':') ? ((0, jsx_runtime_1.jsx)(layout_1.XStack, { render: "li", children: (0, jsx_runtime_1.jsx)(layout_1.SizableText, { size: "$2", color: "$ink", fontWeight: "500", children: f }) }, f)) : ((0, jsx_runtime_1.jsxs)(layout_1.XStack, { render: "li", items: "flex-start", gap: "$2.5", children: [(0, jsx_runtime_1.jsx)(lucide_icons_2_1.Check, { size: 16 }), (0, jsx_runtime_1.jsx)(layout_1.SizableText, { size: "$2", color: "$quiet", flex: 1, children: f })] }, f))) }));
|
|
55
57
|
}
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
+
function PlanCard({ id, cta, onPick, interval, setInterval, toggle, from }) {
|
|
59
|
+
const o = (0, plans_1.offer)(id);
|
|
60
|
+
return ((0, jsx_runtime_1.jsxs)(card_1.Card, { "aria-label": from ? 'Max' : o.name, bg: "$panel", flex: 1, flexBasis: 300, maxW: 380, gap: "$0", py: 0, children: [(0, jsx_runtime_1.jsxs)(layout_1.YStack, { p: "$5", gap: "$3", borderBottomWidth: 1, borderBottomColor: "$borderColor", children: [(0, jsx_runtime_1.jsx)(layout_1.XStack, { minH: 36, justify: "flex-end", children: toggle && (0, plans_1.yearly)(id) ? (0, jsx_runtime_1.jsx)(frame_1.IntervalToggle, { interval: interval, onChange: setInterval, name: o.name, save: (0, plans_1.saving)(id) }) : null }), (0, jsx_runtime_1.jsx)(layout_1.Heading, { render: "h2", size: "$7", fontWeight: "600", color: "$ink", m: 0, children: from ? 'Max' : o.name }), (0, jsx_runtime_1.jsx)(layout_1.Paragraph, { size: "$3", color: "$quiet", m: 0, children: o.blurb }), (0, jsx_runtime_1.jsxs)(layout_1.XStack, { items: "center", gap: "$2", my: "$2", children: [(0, jsx_runtime_1.jsx)(layout_1.SizableText, { size: "$10", fontWeight: "600", color: "$ink", children: `${from ? 'From ' : ''}${(0, plans_1.money)((0, plans_1.unit)(id, interval))}` }), (0, jsx_runtime_1.jsx)(layout_1.SizableText, { size: "$1", color: "$quiet", children: `USD / month\nbilled ${interval === 'annual' && (0, plans_1.yearly)(id) ? 'yearly' : 'monthly'}` })] }), (0, jsx_runtime_1.jsx)(Primary, { onClick: onPick, children: cta }), o.monthly > 0 ? ((0, jsx_runtime_1.jsx)(layout_1.Paragraph, { size: "$1", color: "$quiet", text: "center", m: 0, children: "No commitment \u00B7 Cancel anytime" })) : null] }), (0, jsx_runtime_1.jsx)(Features, { items: o.features })] }));
|
|
61
|
+
}
|
|
62
|
+
function Plans({ interval, setInterval, onFree, onPick, onTeam }) {
|
|
63
|
+
return ((0, jsx_runtime_1.jsxs)(layout_1.YStack, { items: "center", gap: "$4", width: "100%", children: [(0, jsx_runtime_1.jsxs)(layout_1.XStack, { flexWrap: "wrap", justify: "center", gap: "$5", width: "100%", children: [(0, jsx_runtime_1.jsx)(PlanCard, { id: "free", cta: "Use Hanzo for free", onPick: onFree, interval: interval, setInterval: setInterval }), (0, jsx_runtime_1.jsx)(PlanCard, { id: "dev", cta: `Get ${(0, plans_1.offer)('dev').name} plan`, onPick: () => onPick('dev'), interval: interval, setInterval: setInterval, toggle: true }), (0, jsx_runtime_1.jsx)(PlanCard, { id: "max_5x", cta: "Get Max plan", onPick: () => onPick('max_5x'), interval: interval, setInterval: setInterval, from: true })] }), (0, jsx_runtime_1.jsx)(layout_1.Paragraph, { size: "$1", color: "$quiet", text: "center", m: 0, children: "Prices are in US dollars. Usage limits apply. Prices and plans are subject to change at Hanzo\u2019s discretion." }), onTeam ? ((0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", variant: "link", size: "sm", onClick: onTeam, children: "Using Hanzo with a team?" })) : null] }));
|
|
64
|
+
}
|
|
65
|
+
/** "Do more with Hanzo Pro": the one plan worth recommending, with the way to stay free and the way to see the rest. */
|
|
66
|
+
function Pro({ interval, setInterval, onPro, onFree, onAll }) {
|
|
67
|
+
const o = (0, plans_1.offer)('dev');
|
|
68
|
+
return ((0, jsx_runtime_1.jsxs)(layout_1.YStack, { items: "center", gap: "$4", width: "100%", children: [(0, jsx_runtime_1.jsx)(frame_1.IntervalToggle, { interval: interval, onChange: setInterval, name: o.name, save: (0, plans_1.saving)('dev') }), (0, jsx_runtime_1.jsxs)(card_1.Card, { "aria-label": o.name, bg: "$panel", width: "100%", gap: "$0", py: 0, children: [(0, jsx_runtime_1.jsxs)(layout_1.YStack, { p: "$5", gap: "$3", borderBottomWidth: 1, borderBottomColor: "$borderColor", children: [(0, jsx_runtime_1.jsx)(layout_1.XStack, { justify: "flex-end", minH: 24, children: (0, jsx_runtime_1.jsx)(badge_1.Badge, { variant: "secondary", children: "Recommended" }) }), (0, jsx_runtime_1.jsx)(layout_1.Heading, { render: "h2", size: "$7", fontWeight: "600", color: "$ink", m: 0, children: o.name }), (0, jsx_runtime_1.jsx)(layout_1.Paragraph, { size: "$3", color: "$quiet", m: 0, children: o.blurb }), (0, jsx_runtime_1.jsxs)(layout_1.XStack, { items: "center", gap: "$2", my: "$2", children: [(0, jsx_runtime_1.jsx)(layout_1.SizableText, { size: "$10", fontWeight: "600", color: "$ink", children: (0, plans_1.money)((0, plans_1.unit)('dev', interval)) }), (0, jsx_runtime_1.jsx)(layout_1.SizableText, { size: "$1", color: "$quiet", children: `USD / month\nbilled ${interval === 'annual' && (0, plans_1.yearly)('dev') ? 'yearly' : 'monthly'}` })] }), (0, jsx_runtime_1.jsx)(Primary, { onClick: onPro, children: `Get ${o.name} plan` }), (0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", variant: "secondary", size: "lg", rounded: "$10", onClick: onFree, children: "Use Hanzo for free" }), (0, jsx_runtime_1.jsx)(layout_1.Paragraph, { size: "$1", color: "$quiet", text: "center", m: 0, children: "No commitment \u00B7 Cancel anytime" })] }), (0, jsx_runtime_1.jsx)(Features, { items: o.features })] }), (0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", variant: "secondary", size: "lg", width: "100%", onClick: onAll, children: "View all plans" }), (0, jsx_runtime_1.jsx)(layout_1.Paragraph, { size: "$1", color: "$quiet", text: "center", m: 0, children: "Prices are in US dollars. Prices and plans are subject to change at Hanzo\u2019s discretion." })] }));
|
|
58
69
|
}
|
|
59
70
|
/* ----------------------------------------------------------------- team */
|
|
60
71
|
function TeamName({ api, onCreated }) {
|
|
@@ -84,15 +95,16 @@ function TeamName({ api, onCreated }) {
|
|
|
84
95
|
return ((0, jsx_runtime_1.jsxs)(layout_1.YStack, { render: (0, jsx_runtime_1.jsx)("form", { onSubmit: submit, noValidate: true }), gap: "$4", items: "stretch", children: [(0, jsx_runtime_1.jsxs)(field_1.Field, { gap: "$2", children: [(0, jsx_runtime_1.jsx)(field_1.FieldLabel, { htmlFor: "hanzo-team", children: "Team name" }), (0, jsx_runtime_1.jsx)(input_1.Input, { id: "hanzo-team", name: "team", autoComplete: "organization", value: name, onChangeText: setName, maxLength: 60, height: 48, autoFocus: true })] }), wrong ? (0, jsx_runtime_1.jsx)(field_1.FieldError, { children: wrong }) : null, (0, jsx_runtime_1.jsx)(Primary, { type: "submit", disabled: !handle || busy, self: "flex-start", px: "$6", children: busy ? 'One moment…' : 'Continue' })] }));
|
|
85
96
|
}
|
|
86
97
|
/* ---------------------------------------------------------------- seats */
|
|
87
|
-
function IntervalChoice({ interval, setInterval, price }) {
|
|
88
|
-
return ((0, jsx_runtime_1.jsx)(choice_1.ChoiceGroup, { label: "Billing interval", flexWrap: "wrap", children: ['monthly', 'annual'].map((i) => ((0, jsx_runtime_1.jsxs)(choice_1.ChoiceCard, { selected: interval === i, onSelect: () => setInterval(i), flex: 1, flexBasis: 200, children: [i === 'annual' ? ((0, jsx_runtime_1.jsx)(layout_1.SizableText, { size: "$1", color: "$ink", self: "flex-end", bg: "$edge", px: "$2", rounded: "$1", children: `Save ${
|
|
98
|
+
function IntervalChoice({ interval, setInterval, price, save }) {
|
|
99
|
+
return ((0, jsx_runtime_1.jsx)(choice_1.ChoiceGroup, { label: "Billing interval", flexWrap: "wrap", children: ['monthly', 'annual'].filter((i) => i === 'monthly' || save > 0).map((i) => ((0, jsx_runtime_1.jsxs)(choice_1.ChoiceCard, { selected: interval === i, onSelect: () => setInterval(i), flex: 1, flexBasis: 200, children: [i === 'annual' ? ((0, jsx_runtime_1.jsx)(layout_1.SizableText, { size: "$1", color: "$ink", self: "flex-end", bg: "$edge", px: "$2", rounded: "$1", children: `Save ${save}%` })) : null, (0, jsx_runtime_1.jsx)(layout_1.SizableText, { size: "$3", fontWeight: "600", color: "$ink", children: i === 'annual' ? 'Annually' : 'Monthly' }), (0, jsx_runtime_1.jsx)(layout_1.SizableText, { size: "$2", color: "$ink", children: price(i) })] }, i))) }));
|
|
89
100
|
}
|
|
90
101
|
function Seats({ interval, setInterval, value, setValue, onContinue, onEnterprise, onAsk, }) {
|
|
91
102
|
const [adjust, setAdjust] = (0, react_1.useState)(false);
|
|
92
103
|
const std = value.plan === 'team_standard';
|
|
104
|
+
const premium = (0, plans_1.has)('team_premium');
|
|
93
105
|
const l = (0, plans_1.lines)(value.plan, interval, value.seats);
|
|
94
106
|
const set = (plan, seats) => setValue({ plan, seats: Math.max(plans_1.MIN_SEATS, Math.min(plans_1.MAX_SEATS, seats)) });
|
|
95
|
-
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(IntervalChoice, { interval: interval, setInterval: setInterval, price: (i) => `${(0, plans_1.usd)((0, plans_1.lines)(value.plan, i, value.seats).subtotal)}/${i === 'annual' ? 'year' : 'month'} + tax` }), (0, jsx_runtime_1.jsxs)(frame_1.Panel, { label: "Order details", children: [(0, jsx_runtime_1.jsx)(layout_1.Heading, { render: "h2", size: "$5", fontWeight: "600", color: "$ink", m: 0, children: "Order details" }), (0, jsx_runtime_1.jsx)(frame_1.Line, { label: `${value.seats} ${std ? 'Standard' : 'Premium'} seats`, note: `× ${(0, plans_1.usd)((0, plans_1.unit)(value.plan, interval))} per seat /month${interval === 'annual' ? ', billed yearly' : ''}`, value: (0, plans_1.usd)(l.subtotal), strong: true }), (0, jsx_runtime_1.jsxs)(button_1.Button, { type: "button", variant: "secondary", size: "lg", "aria-expanded": adjust, onClick: () => setAdjust((a) => !a), children: ["Adjust seats", (0, jsx_runtime_1.jsx)(lucide_icons_2_1.ChevronDown, { size: 16 })] }), adjust ? ((0, jsx_runtime_1.jsxs)(layout_1.YStack, { gap: "$3", children: [(0, jsx_runtime_1.jsxs)(layout_1.XStack, { justify: "space-between", items: "center", gap: "$3", children: [(0, jsx_runtime_1.jsx)(frame_1.Line, { label: "Standard seat", note:
|
|
107
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(IntervalChoice, { interval: interval, setInterval: setInterval, save: (0, plans_1.saving)(value.plan), price: (i) => `${(0, plans_1.usd)((0, plans_1.lines)(value.plan, i, value.seats).subtotal)}/${i === 'annual' ? 'year' : 'month'} + tax` }), (0, jsx_runtime_1.jsxs)(frame_1.Panel, { label: "Order details", children: [(0, jsx_runtime_1.jsx)(layout_1.Heading, { render: "h2", size: "$5", fontWeight: "600", color: "$ink", m: 0, children: "Order details" }), (0, jsx_runtime_1.jsx)(frame_1.Line, { label: `${value.seats} ${std ? 'Standard' : 'Premium'} seats`, note: `× ${(0, plans_1.usd)((0, plans_1.unit)(value.plan, interval))} per seat /month${interval === 'annual' ? ', billed yearly' : ''}`, value: (0, plans_1.usd)(l.subtotal), strong: true }), (0, jsx_runtime_1.jsxs)(button_1.Button, { type: "button", variant: "secondary", size: "lg", "aria-expanded": adjust, onClick: () => setAdjust((a) => !a), children: ["Adjust seats", (0, jsx_runtime_1.jsx)(lucide_icons_2_1.ChevronDown, { size: 16 })] }), adjust ? ((0, jsx_runtime_1.jsxs)(layout_1.YStack, { gap: "$3", children: [(0, jsx_runtime_1.jsxs)(layout_1.XStack, { justify: "space-between", items: "center", gap: "$3", children: [(0, jsx_runtime_1.jsx)(frame_1.Line, { label: "Standard seat", note: `${(0, plans_1.usd)((0, plans_1.unit)('team_standard', interval))}/seat/month`, value: "" }), (0, jsx_runtime_1.jsx)(stepper_1.Stepper, { label: "Standard seats", value: std ? value.seats : 0, min: 0, max: plans_1.MAX_SEATS, onChange: (n) => (n === 0 && premium ? set('team_premium', value.seats) : set('team_standard', Math.max(n, plans_1.MIN_SEATS))) })] }), premium ? ((0, jsx_runtime_1.jsxs)(layout_1.XStack, { justify: "space-between", items: "center", gap: "$3", children: [(0, jsx_runtime_1.jsx)(frame_1.Line, { label: "Premium seat", note: `${(0, plans_1.usd)((0, plans_1.unit)('team_premium', interval))}/seat/month`, value: "" }), (0, jsx_runtime_1.jsx)(stepper_1.Stepper, { label: "Premium seats", value: std ? 0 : value.seats, min: 0, max: plans_1.MAX_SEATS, onChange: (n) => (n === 0 ? set('team_standard', value.seats) : set('team_premium', n)) })] })) : null, value.seats >= plans_1.ENTERPRISE_FROM ? ((0, jsx_runtime_1.jsxs)(layout_1.XStack, { items: "center", gap: "$1", children: [(0, jsx_runtime_1.jsxs)(layout_1.SizableText, { size: "$1", color: "$quiet", children: [plans_1.ENTERPRISE_FROM, " or more?"] }), onAsk ? ((0, jsx_runtime_1.jsx)(Ask, { onAsk: onAsk })) : ((0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", variant: "link", size: "sm", px: 0, minH: 0, onClick: onEnterprise, children: "Talk to sales" }))] })) : null] })) : null, (0, jsx_runtime_1.jsx)(frame_1.Line, { label: "Subtotal", value: (0, plans_1.usd)(l.subtotal), strong: true }), (0, jsx_runtime_1.jsx)(frame_1.Line, { label: "Tax (if applicable)", value: "Calculated at payment", muted: true }), (0, jsx_runtime_1.jsx)(frame_1.Line, { label: "Total due today", value: `${(0, plans_1.usd)(l.subtotal)} + tax`, strong: true })] }), (0, jsx_runtime_1.jsx)(Primary, { onClick: onContinue, children: "Continue to payment" })] }));
|
|
96
108
|
}
|
|
97
109
|
/* ----------------------------------------------------------- enterprise */
|
|
98
110
|
function Enterprise({ api, onSent }) {
|
|
@@ -9,20 +9,30 @@ export declare function UseCards({ onPick, onAsk }: {
|
|
|
9
9
|
onPick: (u: Use) => void;
|
|
10
10
|
onAsk?: () => void;
|
|
11
11
|
}): import("react").JSX.Element;
|
|
12
|
-
export declare function Plans({ interval, setInterval, onFree, onPick }: {
|
|
12
|
+
export declare function Plans({ interval, setInterval, onFree, onPick, onTeam }: {
|
|
13
13
|
interval: Interval;
|
|
14
14
|
setInterval: (i: Interval) => void;
|
|
15
15
|
onFree: () => void;
|
|
16
16
|
onPick: (p: PlanId) => void;
|
|
17
|
+
onTeam?: () => void;
|
|
18
|
+
}): import("react").JSX.Element;
|
|
19
|
+
/** "Do more with Hanzo Pro": the one plan worth recommending, with the way to stay free and the way to see the rest. */
|
|
20
|
+
export declare function Pro({ interval, setInterval, onPro, onFree, onAll }: {
|
|
21
|
+
interval: Interval;
|
|
22
|
+
setInterval: (i: Interval) => void;
|
|
23
|
+
onPro: () => void;
|
|
24
|
+
onFree: () => void;
|
|
25
|
+
onAll: () => void;
|
|
17
26
|
}): import("react").JSX.Element;
|
|
18
27
|
export declare function TeamName({ api, onCreated }: {
|
|
19
28
|
api: string;
|
|
20
29
|
onCreated: (org: string) => void | Promise<void>;
|
|
21
30
|
}): import("react").JSX.Element;
|
|
22
|
-
export declare function IntervalChoice({ interval, setInterval, price }: {
|
|
31
|
+
export declare function IntervalChoice({ interval, setInterval, price, save }: {
|
|
23
32
|
interval: Interval;
|
|
24
33
|
setInterval: (i: Interval) => void;
|
|
25
34
|
price: (i: Interval) => string;
|
|
35
|
+
save: number;
|
|
26
36
|
}): import("react").JSX.Element;
|
|
27
37
|
export declare function Seats({ interval, setInterval, value, setValue, onContinue, onEnterprise, onAsk, }: {
|
|
28
38
|
interval: Interval;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screens.d.ts","sourceRoot":"","sources":["../../src/onboarding/screens.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"screens.d.ts","sourceRoot":"","sources":["../../src/onboarding/screens.tsx"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useMemo, useState } from 'react';
|
|
4
4
|
import { Check, ChevronDown, User, Users } from '@hanzogui/lucide-icons-2';
|
|
5
|
+
import { Badge } from '../backends/gui/badge.js';
|
|
5
6
|
import { Button } from '../backends/gui/button.js';
|
|
6
7
|
import { Card } from '../backends/gui/card.js';
|
|
7
8
|
import { ChoiceCard, ChoiceGroup } from '../backends/gui/choice.js';
|
|
@@ -12,7 +13,7 @@ import { Stepper } from '../backends/gui/stepper.js';
|
|
|
12
13
|
import { Textarea } from '../backends/gui/textarea.js';
|
|
13
14
|
import { ApiError, call } from './api.js';
|
|
14
15
|
import { IntervalToggle, Line, Panel } from './frame.js';
|
|
15
|
-
import { ENTERPRISE_FROM, MAX_SEATS, MIN_SEATS,
|
|
16
|
+
import { ENTERPRISE_FROM, MAX_SEATS, MIN_SEATS, has, lines, money, offer, saving, unit, usd, yearly } from './plans.js';
|
|
16
17
|
/** A team's IAM handle from its name: lowercase words joined by hyphens, 39 characters at most. */
|
|
17
18
|
export const slug = (name) => name
|
|
18
19
|
.normalize('NFKD')
|
|
@@ -38,12 +39,21 @@ export function UseCards({ onPick, onAsk }) {
|
|
|
38
39
|
return (_jsxs(YStack, { items: "center", gap: "$5", width: "100%", children: [_jsxs(XStack, { role: "group", "aria-label": "How you will use Hanzo", flexWrap: "wrap", justify: "center", gap: "$6", width: "100%", children: [_jsx(UseCard, { icon: _jsx(User, { size: 64 }), name: "For personal use", note: "For individuals who want to build and experiment with their own projects", dim: team, onPress: () => onPick('personal') }), _jsx(UseCard, { icon: _jsx(Users, { size: 64 }), name: "With my team", note: "For organizations who want to collaborate and build at scale", on: team, onPress: () => setTeam(true) })] }), team ? (_jsxs(_Fragment, { children: [_jsxs(XStack, { role: "group", "aria-label": "Team size", flexWrap: "wrap", justify: "center", gap: "$6", width: "100%", children: [_jsx(UseCard, { size: "small", name: "Team", note: `For ${MIN_SEATS}–${MAX_SEATS} people. Predictable per-seat pricing, set up in minutes.`, onPress: () => onPick('team') }), _jsx(UseCard, { size: "small", name: "Enterprise", note: `For ${ENTERPRISE_FROM} or more people. Pooled usage, advanced admin and security controls.`, onPress: () => onPick('enterprise') })] }), _jsxs(XStack, { items: "center", gap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$quiet", children: "Questions?" }), _jsx(Ask, { onAsk: onAsk })] })] })) : null] }));
|
|
39
40
|
}
|
|
40
41
|
/* ---------------------------------------------------------------- plans */
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return (
|
|
42
|
+
/** A plan's features: a line ending in a colon is a heading for the ones under it, the rest are ticked. */
|
|
43
|
+
function Features({ items }) {
|
|
44
|
+
return (_jsx(YStack, { render: "ul", p: "$5", gap: "$2.5", m: 0, children: items.map((f) => f.endsWith(':') ? (_jsx(XStack, { render: "li", children: _jsx(SizableText, { size: "$2", color: "$ink", fontWeight: "500", children: f }) }, f)) : (_jsxs(XStack, { render: "li", items: "flex-start", gap: "$2.5", children: [_jsx(Check, { size: 16 }), _jsx(SizableText, { size: "$2", color: "$quiet", flex: 1, children: f })] }, f))) }));
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
function PlanCard({ id, cta, onPick, interval, setInterval, toggle, from }) {
|
|
47
|
+
const o = offer(id);
|
|
48
|
+
return (_jsxs(Card, { "aria-label": from ? 'Max' : o.name, bg: "$panel", flex: 1, flexBasis: 300, maxW: 380, gap: "$0", py: 0, children: [_jsxs(YStack, { p: "$5", gap: "$3", borderBottomWidth: 1, borderBottomColor: "$borderColor", children: [_jsx(XStack, { minH: 36, justify: "flex-end", children: toggle && yearly(id) ? _jsx(IntervalToggle, { interval: interval, onChange: setInterval, name: o.name, save: saving(id) }) : null }), _jsx(Heading, { render: "h2", size: "$7", fontWeight: "600", color: "$ink", m: 0, children: from ? 'Max' : o.name }), _jsx(Paragraph, { size: "$3", color: "$quiet", m: 0, children: o.blurb }), _jsxs(XStack, { items: "center", gap: "$2", my: "$2", children: [_jsx(SizableText, { size: "$10", fontWeight: "600", color: "$ink", children: `${from ? 'From ' : ''}${money(unit(id, interval))}` }), _jsx(SizableText, { size: "$1", color: "$quiet", children: `USD / month\nbilled ${interval === 'annual' && yearly(id) ? 'yearly' : 'monthly'}` })] }), _jsx(Primary, { onClick: onPick, children: cta }), o.monthly > 0 ? (_jsx(Paragraph, { size: "$1", color: "$quiet", text: "center", m: 0, children: "No commitment \u00B7 Cancel anytime" })) : null] }), _jsx(Features, { items: o.features })] }));
|
|
49
|
+
}
|
|
50
|
+
export function Plans({ interval, setInterval, onFree, onPick, onTeam }) {
|
|
51
|
+
return (_jsxs(YStack, { items: "center", gap: "$4", width: "100%", children: [_jsxs(XStack, { flexWrap: "wrap", justify: "center", gap: "$5", width: "100%", children: [_jsx(PlanCard, { id: "free", cta: "Use Hanzo for free", onPick: onFree, interval: interval, setInterval: setInterval }), _jsx(PlanCard, { id: "dev", cta: `Get ${offer('dev').name} plan`, onPick: () => onPick('dev'), interval: interval, setInterval: setInterval, toggle: true }), _jsx(PlanCard, { id: "max_5x", cta: "Get Max plan", onPick: () => onPick('max_5x'), interval: interval, setInterval: setInterval, from: true })] }), _jsx(Paragraph, { size: "$1", color: "$quiet", text: "center", m: 0, children: "Prices are in US dollars. Usage limits apply. Prices and plans are subject to change at Hanzo\u2019s discretion." }), onTeam ? (_jsx(Button, { type: "button", variant: "link", size: "sm", onClick: onTeam, children: "Using Hanzo with a team?" })) : null] }));
|
|
52
|
+
}
|
|
53
|
+
/** "Do more with Hanzo Pro": the one plan worth recommending, with the way to stay free and the way to see the rest. */
|
|
54
|
+
export function Pro({ interval, setInterval, onPro, onFree, onAll }) {
|
|
55
|
+
const o = offer('dev');
|
|
56
|
+
return (_jsxs(YStack, { items: "center", gap: "$4", width: "100%", children: [_jsx(IntervalToggle, { interval: interval, onChange: setInterval, name: o.name, save: saving('dev') }), _jsxs(Card, { "aria-label": o.name, bg: "$panel", width: "100%", gap: "$0", py: 0, children: [_jsxs(YStack, { p: "$5", gap: "$3", borderBottomWidth: 1, borderBottomColor: "$borderColor", children: [_jsx(XStack, { justify: "flex-end", minH: 24, children: _jsx(Badge, { variant: "secondary", children: "Recommended" }) }), _jsx(Heading, { render: "h2", size: "$7", fontWeight: "600", color: "$ink", m: 0, children: o.name }), _jsx(Paragraph, { size: "$3", color: "$quiet", m: 0, children: o.blurb }), _jsxs(XStack, { items: "center", gap: "$2", my: "$2", children: [_jsx(SizableText, { size: "$10", fontWeight: "600", color: "$ink", children: money(unit('dev', interval)) }), _jsx(SizableText, { size: "$1", color: "$quiet", children: `USD / month\nbilled ${interval === 'annual' && yearly('dev') ? 'yearly' : 'monthly'}` })] }), _jsx(Primary, { onClick: onPro, children: `Get ${o.name} plan` }), _jsx(Button, { type: "button", variant: "secondary", size: "lg", rounded: "$10", onClick: onFree, children: "Use Hanzo for free" }), _jsx(Paragraph, { size: "$1", color: "$quiet", text: "center", m: 0, children: "No commitment \u00B7 Cancel anytime" })] }), _jsx(Features, { items: o.features })] }), _jsx(Button, { type: "button", variant: "secondary", size: "lg", width: "100%", onClick: onAll, children: "View all plans" }), _jsx(Paragraph, { size: "$1", color: "$quiet", text: "center", m: 0, children: "Prices are in US dollars. Prices and plans are subject to change at Hanzo\u2019s discretion." })] }));
|
|
47
57
|
}
|
|
48
58
|
/* ----------------------------------------------------------------- team */
|
|
49
59
|
export function TeamName({ api, onCreated }) {
|
|
@@ -73,15 +83,16 @@ export function TeamName({ api, onCreated }) {
|
|
|
73
83
|
return (_jsxs(YStack, { render: _jsx("form", { onSubmit: submit, noValidate: true }), gap: "$4", items: "stretch", children: [_jsxs(Field, { gap: "$2", children: [_jsx(FieldLabel, { htmlFor: "hanzo-team", children: "Team name" }), _jsx(Input, { id: "hanzo-team", name: "team", autoComplete: "organization", value: name, onChangeText: setName, maxLength: 60, height: 48, autoFocus: true })] }), wrong ? _jsx(FieldError, { children: wrong }) : null, _jsx(Primary, { type: "submit", disabled: !handle || busy, self: "flex-start", px: "$6", children: busy ? 'One moment…' : 'Continue' })] }));
|
|
74
84
|
}
|
|
75
85
|
/* ---------------------------------------------------------------- seats */
|
|
76
|
-
export function IntervalChoice({ interval, setInterval, price }) {
|
|
77
|
-
return (_jsx(ChoiceGroup, { label: "Billing interval", flexWrap: "wrap", children: ['monthly', 'annual'].map((i) => (_jsxs(ChoiceCard, { selected: interval === i, onSelect: () => setInterval(i), flex: 1, flexBasis: 200, children: [i === 'annual' ? (_jsx(SizableText, { size: "$1", color: "$ink", self: "flex-end", bg: "$edge", px: "$2", rounded: "$1", children: `Save ${
|
|
86
|
+
export function IntervalChoice({ interval, setInterval, price, save }) {
|
|
87
|
+
return (_jsx(ChoiceGroup, { label: "Billing interval", flexWrap: "wrap", children: ['monthly', 'annual'].filter((i) => i === 'monthly' || save > 0).map((i) => (_jsxs(ChoiceCard, { selected: interval === i, onSelect: () => setInterval(i), flex: 1, flexBasis: 200, children: [i === 'annual' ? (_jsx(SizableText, { size: "$1", color: "$ink", self: "flex-end", bg: "$edge", px: "$2", rounded: "$1", children: `Save ${save}%` })) : null, _jsx(SizableText, { size: "$3", fontWeight: "600", color: "$ink", children: i === 'annual' ? 'Annually' : 'Monthly' }), _jsx(SizableText, { size: "$2", color: "$ink", children: price(i) })] }, i))) }));
|
|
78
88
|
}
|
|
79
89
|
export function Seats({ interval, setInterval, value, setValue, onContinue, onEnterprise, onAsk, }) {
|
|
80
90
|
const [adjust, setAdjust] = useState(false);
|
|
81
91
|
const std = value.plan === 'team_standard';
|
|
92
|
+
const premium = has('team_premium');
|
|
82
93
|
const l = lines(value.plan, interval, value.seats);
|
|
83
94
|
const set = (plan, seats) => setValue({ plan, seats: Math.max(MIN_SEATS, Math.min(MAX_SEATS, seats)) });
|
|
84
|
-
return (_jsxs(_Fragment, { children: [_jsx(IntervalChoice, { interval: interval, setInterval: setInterval, price: (i) => `${usd(lines(value.plan, i, value.seats).subtotal)}/${i === 'annual' ? 'year' : 'month'} + tax` }), _jsxs(Panel, { label: "Order details", children: [_jsx(Heading, { render: "h2", size: "$5", fontWeight: "600", color: "$ink", m: 0, children: "Order details" }), _jsx(Line, { label: `${value.seats} ${std ? 'Standard' : 'Premium'} seats`, note: `× ${usd(unit(value.plan, interval))} per seat /month${interval === 'annual' ? ', billed yearly' : ''}`, value: usd(l.subtotal), strong: true }), _jsxs(Button, { type: "button", variant: "secondary", size: "lg", "aria-expanded": adjust, onClick: () => setAdjust((a) => !a), children: ["Adjust seats", _jsx(ChevronDown, { size: 16 })] }), adjust ? (_jsxs(YStack, { gap: "$3", children: [_jsxs(XStack, { justify: "space-between", items: "center", gap: "$3", children: [_jsx(Line, { label: "Standard seat", note:
|
|
95
|
+
return (_jsxs(_Fragment, { children: [_jsx(IntervalChoice, { interval: interval, setInterval: setInterval, save: saving(value.plan), price: (i) => `${usd(lines(value.plan, i, value.seats).subtotal)}/${i === 'annual' ? 'year' : 'month'} + tax` }), _jsxs(Panel, { label: "Order details", children: [_jsx(Heading, { render: "h2", size: "$5", fontWeight: "600", color: "$ink", m: 0, children: "Order details" }), _jsx(Line, { label: `${value.seats} ${std ? 'Standard' : 'Premium'} seats`, note: `× ${usd(unit(value.plan, interval))} per seat /month${interval === 'annual' ? ', billed yearly' : ''}`, value: usd(l.subtotal), strong: true }), _jsxs(Button, { type: "button", variant: "secondary", size: "lg", "aria-expanded": adjust, onClick: () => setAdjust((a) => !a), children: ["Adjust seats", _jsx(ChevronDown, { size: 16 })] }), adjust ? (_jsxs(YStack, { gap: "$3", children: [_jsxs(XStack, { justify: "space-between", items: "center", gap: "$3", children: [_jsx(Line, { label: "Standard seat", note: `${usd(unit('team_standard', interval))}/seat/month`, value: "" }), _jsx(Stepper, { label: "Standard seats", value: std ? value.seats : 0, min: 0, max: MAX_SEATS, onChange: (n) => (n === 0 && premium ? set('team_premium', value.seats) : set('team_standard', Math.max(n, MIN_SEATS))) })] }), premium ? (_jsxs(XStack, { justify: "space-between", items: "center", gap: "$3", children: [_jsx(Line, { label: "Premium seat", note: `${usd(unit('team_premium', interval))}/seat/month`, value: "" }), _jsx(Stepper, { label: "Premium seats", value: std ? 0 : value.seats, min: 0, max: MAX_SEATS, onChange: (n) => (n === 0 ? set('team_standard', value.seats) : set('team_premium', n)) })] })) : null, value.seats >= ENTERPRISE_FROM ? (_jsxs(XStack, { items: "center", gap: "$1", children: [_jsxs(SizableText, { size: "$1", color: "$quiet", children: [ENTERPRISE_FROM, " or more?"] }), onAsk ? (_jsx(Ask, { onAsk: onAsk })) : (_jsx(Button, { type: "button", variant: "link", size: "sm", px: 0, minH: 0, onClick: onEnterprise, children: "Talk to sales" }))] })) : null] })) : null, _jsx(Line, { label: "Subtotal", value: usd(l.subtotal), strong: true }), _jsx(Line, { label: "Tax (if applicable)", value: "Calculated at payment", muted: true }), _jsx(Line, { label: "Total due today", value: `${usd(l.subtotal)} + tax`, strong: true })] }), _jsx(Primary, { onClick: onContinue, children: "Continue to payment" })] }));
|
|
85
96
|
}
|
|
86
97
|
/* ----------------------------------------------------------- enterprise */
|
|
87
98
|
export function Enterprise({ api, onSent }) {
|