@dreamtree-org/landing-kit 0.10.0 → 0.10.1
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/README.md +40 -0
- package/auth.css +57 -0
- package/auth.d.ts +2 -2
- package/dist/auth.cjs +143 -38
- package/dist/auth.js +139 -34
- package/dist/index.cjs +605 -540
- package/dist/index.js +573 -508
- package/dist/platform.cjs +172 -68
- package/dist/platform.js +160 -56
- package/index.d.ts +25 -0
- package/package.json +3 -2
- package/styles.css +57 -0
package/dist/platform.js
CHANGED
|
@@ -36,7 +36,7 @@ function readPlatformCsrf(cookieString, name) {
|
|
|
36
36
|
return null;
|
|
37
37
|
}
|
|
38
38
|
function joinUrl(basePath, path, query) {
|
|
39
|
-
const
|
|
39
|
+
const base3 = basePath.replace(/\/+$/, "");
|
|
40
40
|
const rel = path ? `/${String(path).replace(/^\/+/, "")}` : "";
|
|
41
41
|
const params = new URLSearchParams();
|
|
42
42
|
for (const [k, v] of Object.entries(query != null ? query : {})) {
|
|
@@ -44,7 +44,7 @@ function joinUrl(basePath, path, query) {
|
|
|
44
44
|
params.append(k, String(v));
|
|
45
45
|
}
|
|
46
46
|
const qs = params.toString();
|
|
47
|
-
return `${
|
|
47
|
+
return `${base3}${rel}${qs ? `?${qs}` : ""}`;
|
|
48
48
|
}
|
|
49
49
|
function createPlatformClient(opts = {}) {
|
|
50
50
|
var _a, _b, _c, _d, _e;
|
|
@@ -2780,10 +2780,10 @@ function PlatformConsole({
|
|
|
2780
2780
|
const browser = useBrowserLocation(!routed);
|
|
2781
2781
|
const pathname = routed ? givenPath : browser.pathname;
|
|
2782
2782
|
const navigate = givenNavigate != null ? givenNavigate : browser.navigate;
|
|
2783
|
-
const
|
|
2783
|
+
const base3 = `/${trim(basePath)}`.replace(/\/$/, "") || "";
|
|
2784
2784
|
const client = useMemo3(
|
|
2785
|
-
() => given != null ? given : createPlatformClient({ basePath: apiBasePath, loginPath: loginPath != null ? loginPath : `${
|
|
2786
|
-
[given, apiBasePath, loginPath,
|
|
2785
|
+
() => given != null ? given : createPlatformClient({ basePath: apiBasePath, loginPath: loginPath != null ? loginPath : `${base3}/login` }),
|
|
2786
|
+
[given, apiBasePath, loginPath, base3]
|
|
2787
2787
|
);
|
|
2788
2788
|
const Link = useMemo3(() => makeLink(linkAs, navigate), [linkAs, navigate]);
|
|
2789
2789
|
const [me, setMe] = useState14(initialMe);
|
|
@@ -2797,7 +2797,7 @@ function PlatformConsole({
|
|
|
2797
2797
|
useEffect7(() => {
|
|
2798
2798
|
void reloadMe();
|
|
2799
2799
|
}, [reloadMe]);
|
|
2800
|
-
const href = useCallback4((rel2 = "") => `${
|
|
2800
|
+
const href = useCallback4((rel2 = "") => `${base3}/${trim(rel2)}`.replace(/\/$/, "") || "/", [base3]);
|
|
2801
2801
|
const companyLoginPath = useCallback4(
|
|
2802
2802
|
(slug) => {
|
|
2803
2803
|
var _a2, _b2;
|
|
@@ -2815,10 +2815,10 @@ function PlatformConsole({
|
|
|
2815
2815
|
await client.post("auth/logout", {}, { skipAuthRedirect: true });
|
|
2816
2816
|
} finally {
|
|
2817
2817
|
if (onSignOut) onSignOut();
|
|
2818
|
-
else if (typeof window !== "undefined") window.location.assign(loginPath != null ? loginPath : `${
|
|
2818
|
+
else if (typeof window !== "undefined") window.location.assign(loginPath != null ? loginPath : `${base3}/login`);
|
|
2819
2819
|
}
|
|
2820
2820
|
};
|
|
2821
|
-
const rel = pathname.startsWith(
|
|
2821
|
+
const rel = pathname.startsWith(base3) ? pathname.slice(base3.length) : pathname;
|
|
2822
2822
|
const route = matchConsoleRoute(rel, visiblePages);
|
|
2823
2823
|
const gate = me && me.access && me.access.console === false;
|
|
2824
2824
|
const active = (prefix) => prefix === "" ? route.view === "overview" : trim(rel) === prefix || trim(rel).startsWith(`${prefix}/`);
|
|
@@ -2844,7 +2844,7 @@ function PlatformConsole({
|
|
|
2844
2844
|
features: enabled,
|
|
2845
2845
|
companyLoginPath,
|
|
2846
2846
|
companyLoginUrl: (slug) => `${origin}${companyLoginPath(slug)}`,
|
|
2847
|
-
loginUrl: `${origin}${loginPath != null ? loginPath : `${
|
|
2847
|
+
loginUrl: `${origin}${loginPath != null ? loginPath : `${base3}/login`}`,
|
|
2848
2848
|
brand,
|
|
2849
2849
|
appName: appName != null ? appName : (_a = me == null ? void 0 : me.app) == null ? void 0 : _a.name
|
|
2850
2850
|
};
|
|
@@ -2867,7 +2867,7 @@ function PlatformConsole({
|
|
|
2867
2867
|
|
|
2868
2868
|
// src/platform/PlatformLogin.jsx
|
|
2869
2869
|
import React17, { useMemo as useMemo4, useState as useState17 } from "react";
|
|
2870
|
-
import { Alert as
|
|
2870
|
+
import { Alert as Alert10, Button as Button13, Input as Input11 } from "@dreamtree-org/twreact-ui";
|
|
2871
2871
|
|
|
2872
2872
|
// src/AuthShell.jsx
|
|
2873
2873
|
import { useId } from "react";
|
|
@@ -2928,12 +2928,115 @@ function DemoAccounts({ load, onSelect, className = "" }) {
|
|
|
2928
2928
|
] });
|
|
2929
2929
|
}
|
|
2930
2930
|
|
|
2931
|
+
// src/DreamtreeSso.jsx
|
|
2932
|
+
import { useEffect as useEffect9, useRef as useRef4 } from "react";
|
|
2933
|
+
|
|
2934
|
+
// src/sso.js
|
|
2935
|
+
function decodeKey(part) {
|
|
2936
|
+
const key = part.split("=")[0];
|
|
2937
|
+
try {
|
|
2938
|
+
return decodeURIComponent(key.replace(/\+/g, " "));
|
|
2939
|
+
} catch {
|
|
2940
|
+
return key;
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
function buildSsoUrl(startUrl, { returnTo, companySlug } = {}) {
|
|
2944
|
+
const raw = String(startUrl != null ? startUrl : "");
|
|
2945
|
+
const hashAt = raw.indexOf("#");
|
|
2946
|
+
const hash = hashAt === -1 ? "" : raw.slice(hashAt);
|
|
2947
|
+
const beforeHash = hashAt === -1 ? raw : raw.slice(0, hashAt);
|
|
2948
|
+
const queryAt = beforeHash.indexOf("?");
|
|
2949
|
+
const path = queryAt === -1 ? beforeHash : beforeHash.slice(0, queryAt);
|
|
2950
|
+
const query = queryAt === -1 ? "" : beforeHash.slice(queryAt + 1);
|
|
2951
|
+
const add = [
|
|
2952
|
+
["return_to", returnTo],
|
|
2953
|
+
["dt_company", companySlug]
|
|
2954
|
+
].filter(([, v]) => v != null && v !== "");
|
|
2955
|
+
const replaced = new Set(add.map(([k]) => k));
|
|
2956
|
+
const params = query.split("&").filter((p) => p && !replaced.has(decodeKey(p))).concat(add.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`));
|
|
2957
|
+
return `${path}${params.length ? `?${params.join("&")}` : ""}${hash}`;
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
// src/icons.jsx
|
|
2961
|
+
import { createElement, isValidElement } from "react";
|
|
2962
|
+
import { Fragment as Fragment7, jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2963
|
+
var base2 = {
|
|
2964
|
+
viewBox: "0 0 24 24",
|
|
2965
|
+
fill: "none",
|
|
2966
|
+
stroke: "currentColor",
|
|
2967
|
+
strokeWidth: 2,
|
|
2968
|
+
strokeLinecap: "round",
|
|
2969
|
+
strokeLinejoin: "round",
|
|
2970
|
+
"aria-hidden": "true",
|
|
2971
|
+
focusable: "false"
|
|
2972
|
+
};
|
|
2973
|
+
var make2 = (paths) => function Icon({ size = 16, className = "dt-lp-icon" }) {
|
|
2974
|
+
return /* @__PURE__ */ jsx19("svg", { ...base2, width: size, height: size, className, children: paths });
|
|
2975
|
+
};
|
|
2976
|
+
var ArrowRight = make2(/* @__PURE__ */ jsx19("path", { d: "M5 12h14M13 6l6 6-6 6" }));
|
|
2977
|
+
var ChevronRight = make2(/* @__PURE__ */ jsx19("path", { d: "m9 6 6 6-6 6" }));
|
|
2978
|
+
var Check = make2(/* @__PURE__ */ jsx19("path", { d: "M20 6 9 17l-5-5" }));
|
|
2979
|
+
var Help = make2(
|
|
2980
|
+
/* @__PURE__ */ jsxs17(Fragment7, { children: [
|
|
2981
|
+
/* @__PURE__ */ jsx19("circle", { cx: "12", cy: "12", r: "10" }),
|
|
2982
|
+
/* @__PURE__ */ jsx19("path", { d: "M9.1 9a3 3 0 0 1 5.8 1c0 2-3 3-3 3M12 17h.01" })
|
|
2983
|
+
] })
|
|
2984
|
+
);
|
|
2985
|
+
var Alert9 = make2(
|
|
2986
|
+
/* @__PURE__ */ jsxs17(Fragment7, { children: [
|
|
2987
|
+
/* @__PURE__ */ jsx19("path", { d: "M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z" }),
|
|
2988
|
+
/* @__PURE__ */ jsx19("path", { d: "M12 9v4M12 17h.01" })
|
|
2989
|
+
] })
|
|
2990
|
+
);
|
|
2991
|
+
var MenuIcon = make2(/* @__PURE__ */ jsx19("path", { d: "M4 6h16M4 12h16M4 18h16" }));
|
|
2992
|
+
var Close = make2(/* @__PURE__ */ jsx19("path", { d: "M18 6 6 18M6 6l12 12" }));
|
|
2993
|
+
var Calendar = make2(
|
|
2994
|
+
/* @__PURE__ */ jsxs17(Fragment7, { children: [
|
|
2995
|
+
/* @__PURE__ */ jsx19("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
|
|
2996
|
+
/* @__PURE__ */ jsx19("path", { d: "M16 2v4M8 2v4M3 10h18m-12 6 2 2 4-4" })
|
|
2997
|
+
] })
|
|
2998
|
+
);
|
|
2999
|
+
|
|
3000
|
+
// src/DreamtreeSso.jsx
|
|
3001
|
+
import { Fragment as Fragment8, jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
3002
|
+
var DEFAULT_SSO_LABEL = "Continue with Dreamtree";
|
|
3003
|
+
function DreamtreeSso({ provider, children }) {
|
|
3004
|
+
const { startUrl, mode, label = DEFAULT_SSO_LABEL, companySlug, returnTo } = provider;
|
|
3005
|
+
const isSso = mode === "sso";
|
|
3006
|
+
const autoRedirect = isSso && provider.autoRedirect !== false;
|
|
3007
|
+
const href = buildSsoUrl(startUrl, { returnTo, companySlug });
|
|
3008
|
+
const redirected = useRef4(false);
|
|
3009
|
+
useEffect9(() => {
|
|
3010
|
+
if (!autoRedirect || redirected.current) return;
|
|
3011
|
+
redirected.current = true;
|
|
3012
|
+
window.location.assign(href);
|
|
3013
|
+
}, [autoRedirect, href]);
|
|
3014
|
+
const button = /* @__PURE__ */ jsxs18("a", { className: "dt-auth-sso-btn", href, children: [
|
|
3015
|
+
/* @__PURE__ */ jsx20("span", { children: label }),
|
|
3016
|
+
/* @__PURE__ */ jsx20(ArrowRight, { size: 16, className: "dt-auth-sso-icon" })
|
|
3017
|
+
] });
|
|
3018
|
+
if (isSso) {
|
|
3019
|
+
return /* @__PURE__ */ jsxs18("div", { className: "dt-auth-sso dt-auth-sso--only", children: [
|
|
3020
|
+
autoRedirect ? /* @__PURE__ */ jsxs18("p", { className: "dt-auth-sso-status", role: "status", "aria-live": "polite", children: [
|
|
3021
|
+
/* @__PURE__ */ jsx20("span", { className: "dt-auth-sso-spinner", "aria-hidden": "true" }),
|
|
3022
|
+
"Redirecting to Dreamtree ID\u2026"
|
|
3023
|
+
] }) : null,
|
|
3024
|
+
button
|
|
3025
|
+
] });
|
|
3026
|
+
}
|
|
3027
|
+
return /* @__PURE__ */ jsxs18(Fragment8, { children: [
|
|
3028
|
+
/* @__PURE__ */ jsx20("div", { className: "dt-auth-sso", children: button }),
|
|
3029
|
+
/* @__PURE__ */ jsx20("div", { className: "dt-auth-or", children: /* @__PURE__ */ jsx20("span", { children: "or" }) }),
|
|
3030
|
+
children
|
|
3031
|
+
] });
|
|
3032
|
+
}
|
|
3033
|
+
|
|
2931
3034
|
// src/useTapReveal.js
|
|
2932
|
-
import { useCallback as useCallback5, useRef as
|
|
3035
|
+
import { useCallback as useCallback5, useRef as useRef5, useState as useState16 } from "react";
|
|
2933
3036
|
function useTapReveal({ taps = 4, windowMs = 1500 } = {}) {
|
|
2934
3037
|
const [revealed, setRevealed] = useState16(false);
|
|
2935
|
-
const count =
|
|
2936
|
-
const last =
|
|
3038
|
+
const count = useRef5(0);
|
|
3039
|
+
const last = useRef5(0);
|
|
2937
3040
|
const onClick = useCallback5(() => {
|
|
2938
3041
|
if (revealed) return;
|
|
2939
3042
|
const now = Date.now();
|
|
@@ -2945,7 +3048,7 @@ function useTapReveal({ taps = 4, windowMs = 1500 } = {}) {
|
|
|
2945
3048
|
}
|
|
2946
3049
|
|
|
2947
3050
|
// src/AuthShell.jsx
|
|
2948
|
-
import { jsx as
|
|
3051
|
+
import { jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
2949
3052
|
var DEFAULT_LEGAL = [
|
|
2950
3053
|
{ label: "Privacy Policy", href: "/privacy" },
|
|
2951
3054
|
{ label: "Terms", href: "/terms" },
|
|
@@ -2953,10 +3056,10 @@ var DEFAULT_LEGAL = [
|
|
|
2953
3056
|
];
|
|
2954
3057
|
function BrandMark({ brand, size }) {
|
|
2955
3058
|
if (brand.logoSrc) {
|
|
2956
|
-
return /* @__PURE__ */
|
|
3059
|
+
return /* @__PURE__ */ jsx21("img", { className: "dt-auth-mark-img", src: brand.logoSrc, alt: "", width: size, height: size });
|
|
2957
3060
|
}
|
|
2958
|
-
if (brand.mark) return /* @__PURE__ */
|
|
2959
|
-
return /* @__PURE__ */
|
|
3061
|
+
if (brand.mark) return /* @__PURE__ */ jsx21("span", { className: "dt-auth-mark-node", children: brand.mark });
|
|
3062
|
+
return /* @__PURE__ */ jsx21("span", { className: "dt-auth-monogram", style: { width: size, height: size }, "aria-hidden": "true", children: brand.name.charAt(0) });
|
|
2960
3063
|
}
|
|
2961
3064
|
function brandStyle(brand) {
|
|
2962
3065
|
return {
|
|
@@ -2975,7 +3078,8 @@ function AuthShell({
|
|
|
2975
3078
|
legalLinks = DEFAULT_LEGAL,
|
|
2976
3079
|
linkComponent: LinkComponent,
|
|
2977
3080
|
className = "",
|
|
2978
|
-
demoAccounts
|
|
3081
|
+
demoAccounts,
|
|
3082
|
+
ssoProvider
|
|
2979
3083
|
}) {
|
|
2980
3084
|
var _a, _b;
|
|
2981
3085
|
const headingId = useId();
|
|
@@ -2983,44 +3087,44 @@ function AuthShell({
|
|
|
2983
3087
|
const isPlatform = variant === "platform";
|
|
2984
3088
|
const headline = isPlatform ? brand.platformHeadline || "Platform administration" : brand.headline;
|
|
2985
3089
|
const pitch = isPlatform ? brand.platformPitch || `Super administrator console for ${brand.name}. Every action here is audited.` : brand.pitch;
|
|
2986
|
-
const renderLink = (l) => LinkComponent ? /* @__PURE__ */
|
|
2987
|
-
return /* @__PURE__ */
|
|
3090
|
+
const renderLink = (l) => LinkComponent ? /* @__PURE__ */ jsx21(LinkComponent, { to: l.href, href: l.href, className: "dt-auth-legal-link", children: l.label }, l.href) : /* @__PURE__ */ jsx21("a", { href: l.href, className: "dt-auth-legal-link", children: l.label }, l.href);
|
|
3091
|
+
return /* @__PURE__ */ jsxs19(
|
|
2988
3092
|
"div",
|
|
2989
3093
|
{
|
|
2990
3094
|
className: `dt-auth ${isPlatform ? "dt-auth--platform" : ""} ${className}`,
|
|
2991
3095
|
style: brandStyle(brand),
|
|
2992
3096
|
"data-product": brand.id,
|
|
2993
3097
|
children: [
|
|
2994
|
-
/* @__PURE__ */
|
|
2995
|
-
/* @__PURE__ */
|
|
2996
|
-
/* @__PURE__ */
|
|
2997
|
-
/* @__PURE__ */
|
|
2998
|
-
isPlatform ? /* @__PURE__ */
|
|
3098
|
+
/* @__PURE__ */ jsxs19("aside", { className: "dt-auth-panel", children: [
|
|
3099
|
+
/* @__PURE__ */ jsxs19("a", { href: "/", className: "dt-auth-brand", children: [
|
|
3100
|
+
/* @__PURE__ */ jsx21(BrandMark, { brand, size: 28 }),
|
|
3101
|
+
/* @__PURE__ */ jsx21("span", { className: "dt-auth-brand-name", children: brand.name }),
|
|
3102
|
+
isPlatform ? /* @__PURE__ */ jsx21("span", { className: "dt-auth-badge", children: "Platform" }) : null
|
|
2999
3103
|
] }),
|
|
3000
|
-
/* @__PURE__ */
|
|
3001
|
-
/* @__PURE__ */
|
|
3002
|
-
pitch ? /* @__PURE__ */
|
|
3003
|
-
!isPlatform && ((_b = brand.points) == null ? void 0 : _b.length) ? /* @__PURE__ */
|
|
3104
|
+
/* @__PURE__ */ jsxs19("div", { className: "dt-auth-pitch", children: [
|
|
3105
|
+
/* @__PURE__ */ jsx21("h2", { children: headline }),
|
|
3106
|
+
pitch ? /* @__PURE__ */ jsx21("p", { children: pitch }) : null,
|
|
3107
|
+
!isPlatform && ((_b = brand.points) == null ? void 0 : _b.length) ? /* @__PURE__ */ jsx21("ul", { children: brand.points.map((p) => /* @__PURE__ */ jsx21("li", { children: p }, p)) }) : null
|
|
3004
3108
|
] }),
|
|
3005
|
-
/* @__PURE__ */
|
|
3109
|
+
/* @__PURE__ */ jsx21("p", { className: "dt-auth-panel-foot", children: brand.category || "by Dreamtree-Org" })
|
|
3006
3110
|
] }),
|
|
3007
|
-
/* @__PURE__ */
|
|
3008
|
-
/* @__PURE__ */
|
|
3009
|
-
/* @__PURE__ */
|
|
3010
|
-
/* @__PURE__ */
|
|
3011
|
-
isPlatform ? /* @__PURE__ */
|
|
3111
|
+
/* @__PURE__ */ jsx21("main", { className: "dt-auth-main", children: /* @__PURE__ */ jsxs19("div", { className: "dt-auth-column", children: [
|
|
3112
|
+
/* @__PURE__ */ jsxs19("a", { href: "/", className: "dt-auth-brand dt-auth-brand--mobile", children: [
|
|
3113
|
+
/* @__PURE__ */ jsx21(BrandMark, { brand, size: 24 }),
|
|
3114
|
+
/* @__PURE__ */ jsx21("span", { className: "dt-auth-brand-name", children: brand.name }),
|
|
3115
|
+
isPlatform ? /* @__PURE__ */ jsx21("span", { className: "dt-auth-badge", children: "Platform" }) : null
|
|
3012
3116
|
] }),
|
|
3013
|
-
/* @__PURE__ */
|
|
3014
|
-
/* @__PURE__ */
|
|
3015
|
-
/* @__PURE__ */
|
|
3016
|
-
subtitle ? /* @__PURE__ */
|
|
3117
|
+
/* @__PURE__ */ jsxs19("section", { className: "dt-auth-card", "aria-labelledby": headingId, children: [
|
|
3118
|
+
/* @__PURE__ */ jsxs19("header", { className: "dt-auth-head", children: [
|
|
3119
|
+
/* @__PURE__ */ jsx21("h1", { id: headingId, ...demoAccounts ? tapProps : {}, children: title }),
|
|
3120
|
+
subtitle ? /* @__PURE__ */ jsx21("p", { children: subtitle }) : null
|
|
3017
3121
|
] }),
|
|
3018
|
-
children,
|
|
3019
|
-
demoAccounts && revealed ? /* @__PURE__ */
|
|
3122
|
+
ssoProvider ? /* @__PURE__ */ jsx21(DreamtreeSso, { provider: ssoProvider, children }) : children,
|
|
3123
|
+
demoAccounts && revealed && (ssoProvider == null ? void 0 : ssoProvider.mode) !== "sso" ? /* @__PURE__ */ jsx21(DemoAccounts, { load: demoAccounts.load, onSelect: demoAccounts.onSelect }) : null
|
|
3020
3124
|
] }),
|
|
3021
|
-
footer ? /* @__PURE__ */
|
|
3022
|
-
(legalLinks == null ? void 0 : legalLinks.length) ? /* @__PURE__ */
|
|
3023
|
-
i > 0 ? /* @__PURE__ */
|
|
3125
|
+
footer ? /* @__PURE__ */ jsx21("div", { className: "dt-auth-footer", children: footer }) : null,
|
|
3126
|
+
(legalLinks == null ? void 0 : legalLinks.length) ? /* @__PURE__ */ jsx21("nav", { className: "dt-auth-legal", "aria-label": "Legal", children: legalLinks.map((l, i) => /* @__PURE__ */ jsxs19("span", { className: "dt-auth-legal-item", children: [
|
|
3127
|
+
i > 0 ? /* @__PURE__ */ jsx21("span", { "aria-hidden": "true", children: "\xB7" }) : null,
|
|
3024
3128
|
renderLink(l)
|
|
3025
3129
|
] }, l.href)) }) : null
|
|
3026
3130
|
] }) })
|
|
@@ -3030,7 +3134,7 @@ function AuthShell({
|
|
|
3030
3134
|
}
|
|
3031
3135
|
|
|
3032
3136
|
// src/platform/PlatformLogin.jsx
|
|
3033
|
-
import { jsx as
|
|
3137
|
+
import { jsx as jsx22, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
3034
3138
|
function PlatformLogin({ brand, basePath, client: given, onSignedIn, redirectTo = "/platform", title = "Platform administration", subtitle = "Administrator sign-in", linkComponent, legalLinks }) {
|
|
3035
3139
|
const client = useMemo4(() => given != null ? given : createPlatformClient({ basePath, onUnauthorized: () => {
|
|
3036
3140
|
} }), [given, basePath]);
|
|
@@ -3080,17 +3184,17 @@ function PlatformLogin({ brand, basePath, client: given, onSignedIn, redirectTo
|
|
|
3080
3184
|
setBusy(false);
|
|
3081
3185
|
}
|
|
3082
3186
|
};
|
|
3083
|
-
return /* @__PURE__ */
|
|
3084
|
-
/* @__PURE__ */
|
|
3085
|
-
/* @__PURE__ */
|
|
3086
|
-
error && /* @__PURE__ */
|
|
3087
|
-
/* @__PURE__ */
|
|
3088
|
-
] }) : /* @__PURE__ */
|
|
3089
|
-
/* @__PURE__ */
|
|
3090
|
-
/* @__PURE__ */
|
|
3091
|
-
error && /* @__PURE__ */
|
|
3092
|
-
/* @__PURE__ */
|
|
3093
|
-
/* @__PURE__ */
|
|
3187
|
+
return /* @__PURE__ */ jsx22(AuthShell, { brand, variant: "platform", title, subtitle, linkComponent, legalLinks, children: step === "password" ? /* @__PURE__ */ jsxs20("form", { onSubmit: submitPassword, className: "flex flex-col gap-4", children: [
|
|
3188
|
+
/* @__PURE__ */ jsx22(Input11, { label: "Admin email", type: "email", autoComplete: "username", autoFocus: true, value: email, onChange: (e) => setEmail(e.target.value) }),
|
|
3189
|
+
/* @__PURE__ */ jsx22(Input11, { label: "Password", type: "password", autoComplete: "current-password", value: password, onChange: (e) => setPassword(e.target.value) }),
|
|
3190
|
+
error && /* @__PURE__ */ jsx22(Alert10, { variant: "error", children: error }),
|
|
3191
|
+
/* @__PURE__ */ jsx22(Button13, { type: "submit", loading: busy, disabled: !email || !password, fullWidth: true, children: "Sign in" })
|
|
3192
|
+
] }) : /* @__PURE__ */ jsxs20("form", { onSubmit: submitCode, className: "flex flex-col gap-4", children: [
|
|
3193
|
+
/* @__PURE__ */ jsx22("p", { className: "text-sm", children: "Enter the 6-digit code from your authenticator app." }),
|
|
3194
|
+
/* @__PURE__ */ jsx22(Input11, { label: "Authenticator code", otp: true, autoFocus: true, value: code, onChange: (e) => setCode(e.target.value.replace(/\s/g, "")) }),
|
|
3195
|
+
error && /* @__PURE__ */ jsx22(Alert10, { variant: "error", children: error }),
|
|
3196
|
+
/* @__PURE__ */ jsx22(Button13, { type: "submit", loading: busy, disabled: code.length < 6, fullWidth: true, children: "Verify" }),
|
|
3197
|
+
/* @__PURE__ */ jsx22(
|
|
3094
3198
|
Button13,
|
|
3095
3199
|
{
|
|
3096
3200
|
variant: "ghost",
|
package/index.d.ts
CHANGED
|
@@ -57,6 +57,26 @@ export interface AuthShellProps {
|
|
|
57
57
|
* the form. Nothing renders when `load` returns no accounts (demo mode off).
|
|
58
58
|
*/
|
|
59
59
|
demoAccounts?: AuthDemoAccounts | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Dreamtree ID single sign-on (DTID-32). Absent = today's local form, unchanged.
|
|
62
|
+
* 'hybrid' adds a "Continue with Dreamtree" button and an "or" divider above `children`;
|
|
63
|
+
* 'sso' drops `children` (and the demo-accounts panel) and redirects to the start URL on mount.
|
|
64
|
+
*/
|
|
65
|
+
ssoProvider?: SsoProvider | undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface SsoProvider {
|
|
69
|
+
/** The app's SSO entry point, e.g. '/auth/sso/start'. May already carry a query or #hash. */
|
|
70
|
+
startUrl: string;
|
|
71
|
+
mode: 'hybrid' | 'sso';
|
|
72
|
+
/** Button text. Default "Continue with Dreamtree". */
|
|
73
|
+
label?: string | undefined;
|
|
74
|
+
/** Sent as `dt_company` (the /c/[slug]/login company). */
|
|
75
|
+
companySlug?: string | undefined;
|
|
76
|
+
/** Sent as `return_to`: where the app lands after sign-in. */
|
|
77
|
+
returnTo?: string | undefined;
|
|
78
|
+
/** 'sso' only: redirect with window.location.assign after mount. Default true; the button always stays as a fallback. */
|
|
79
|
+
autoRedirect?: boolean | undefined;
|
|
60
80
|
}
|
|
61
81
|
|
|
62
82
|
export interface DemoAccount {
|
|
@@ -89,6 +109,11 @@ export interface DemoAccountsProps {
|
|
|
89
109
|
}
|
|
90
110
|
|
|
91
111
|
export declare function AuthShell(props: AuthShellProps): ReactElement;
|
|
112
|
+
/**
|
|
113
|
+
* `startUrl` plus `return_to` / `dt_company` (URI-encoded, replacing same-named params already
|
|
114
|
+
* there; other params and the #hash are kept). Pure: safe during SSR.
|
|
115
|
+
*/
|
|
116
|
+
export declare function buildSsoUrl(startUrl: string, opts?: Pick<SsoProvider, 'returnTo' | 'companySlug'>): string;
|
|
92
117
|
/** The chip row AuthShell reveals; render it yourself for a login outside AuthShell. */
|
|
93
118
|
export declare function DemoAccounts(props: DemoAccountsProps): ReactElement | null;
|
|
94
119
|
/** `taps` clicks, each within `windowMs` of the last, set `revealed` for the page's life. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dreamtree-org/landing-kit",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "The shared marketing site, auth frame (AuthShell + brands) and Platform admin console for every Dreamtree product: content-driven landing pages, login/signup shell, platform console client, Express routes, prerender and SEO files.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Partha Preetham Krishna",
|
|
@@ -106,7 +106,8 @@
|
|
|
106
106
|
"esbuild": "^0.25.0",
|
|
107
107
|
"express": "^4.22.3",
|
|
108
108
|
"react": "^18.3.1",
|
|
109
|
-
"react-dom": "^18.3.1"
|
|
109
|
+
"react-dom": "^18.3.1",
|
|
110
|
+
"react-test-renderer": "^18.3.1"
|
|
110
111
|
},
|
|
111
112
|
"engines": {
|
|
112
113
|
"node": ">=18"
|
package/styles.css
CHANGED
|
@@ -850,6 +850,63 @@ html:has(.dt-landing) { scroll-behavior: smooth; scroll-padding-top: calc(var(--
|
|
|
850
850
|
.dt-auth-demo-note { margin: 0.625rem 0 0; font-size: 0.75rem; color: var(--dt-auth-muted); }
|
|
851
851
|
.dt-auth-demo-note code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.75rem; color: inherit; }
|
|
852
852
|
|
|
853
|
+
/* ---- Dreamtree ID single sign-on (ssoProvider, DTID-32) ---- */
|
|
854
|
+
.dt-auth-sso-btn {
|
|
855
|
+
display: flex;
|
|
856
|
+
align-items: center;
|
|
857
|
+
justify-content: center;
|
|
858
|
+
gap: 0.5rem;
|
|
859
|
+
width: 100%;
|
|
860
|
+
min-height: 2.75rem;
|
|
861
|
+
padding: 0.625rem 1rem;
|
|
862
|
+
border: 1px solid var(--dt-brand);
|
|
863
|
+
border-radius: 0.5rem;
|
|
864
|
+
background: var(--dt-brand);
|
|
865
|
+
color: #fff;
|
|
866
|
+
font: inherit;
|
|
867
|
+
font-size: 0.9375rem;
|
|
868
|
+
font-weight: 600;
|
|
869
|
+
text-decoration: none;
|
|
870
|
+
transition: background-color 120ms;
|
|
871
|
+
}
|
|
872
|
+
.dt-auth-sso-btn:hover { background: color-mix(in oklab, var(--dt-brand) 88%, black); }
|
|
873
|
+
.dt-auth-sso-btn:focus-visible { outline: 2px solid var(--dt-brand); outline-offset: 2px; }
|
|
874
|
+
.dt-auth-sso-icon { flex: none; }
|
|
875
|
+
.dt-auth-or {
|
|
876
|
+
display: flex;
|
|
877
|
+
align-items: center;
|
|
878
|
+
gap: 0.75rem;
|
|
879
|
+
margin: 1.25rem 0;
|
|
880
|
+
font-size: 0.75rem;
|
|
881
|
+
letter-spacing: 0.08em;
|
|
882
|
+
text-transform: uppercase;
|
|
883
|
+
color: var(--dt-auth-muted);
|
|
884
|
+
}
|
|
885
|
+
.dt-auth-or::before,
|
|
886
|
+
.dt-auth-or::after { content: ''; flex: 1; border-top: 1px solid var(--dt-auth-border); }
|
|
887
|
+
.dt-auth-sso-status {
|
|
888
|
+
display: flex;
|
|
889
|
+
align-items: center;
|
|
890
|
+
gap: 0.625rem;
|
|
891
|
+
margin: 0 0 1rem;
|
|
892
|
+
font-size: 0.875rem;
|
|
893
|
+
color: var(--dt-auth-muted);
|
|
894
|
+
}
|
|
895
|
+
.dt-auth-sso-spinner {
|
|
896
|
+
flex: none;
|
|
897
|
+
width: 1rem;
|
|
898
|
+
height: 1rem;
|
|
899
|
+
border: 2px solid var(--dt-brand-soft);
|
|
900
|
+
border-top-color: var(--dt-brand);
|
|
901
|
+
border-radius: 999px;
|
|
902
|
+
animation: dt-auth-sso-spin 0.8s linear infinite;
|
|
903
|
+
}
|
|
904
|
+
@keyframes dt-auth-sso-spin { to { transform: rotate(360deg); } }
|
|
905
|
+
@media (prefers-reduced-motion: reduce) {
|
|
906
|
+
.dt-auth-sso-spinner { animation: none; }
|
|
907
|
+
.dt-auth-sso-btn { transition: none; }
|
|
908
|
+
}
|
|
909
|
+
|
|
853
910
|
/* ---- phones ---- */
|
|
854
911
|
.dt-auth-column,
|
|
855
912
|
.dt-auth-card { min-width: 0; }
|