@poly-x/react 0.1.0-alpha.1 → 0.1.0-alpha.3
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 +176 -8
- package/dist/index.d.cts +49 -9
- package/dist/index.d.mts +49 -9
- package/dist/index.mjs +178 -10
- package/dist/styles.css +274 -0
- package/package.json +8 -5
package/dist/index.cjs
CHANGED
|
@@ -352,14 +352,182 @@ function Protected({ children, fallback = null, loading = null }) {
|
|
|
352
352
|
}
|
|
353
353
|
//#endregion
|
|
354
354
|
//#region src/components/sign-in.tsx
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
355
|
+
const DEFAULT_LABELS = {
|
|
356
|
+
heading: "Sign in",
|
|
357
|
+
subheading: "Enter your credentials to continue.",
|
|
358
|
+
emailLabel: "Email",
|
|
359
|
+
emailPlaceholder: "you@company.com",
|
|
360
|
+
passwordLabel: "Password",
|
|
361
|
+
passwordPlaceholder: "••••••••",
|
|
362
|
+
submit: "Sign in",
|
|
363
|
+
submitting: "Signing in…",
|
|
364
|
+
chooseWorkspace: "Choose a workspace",
|
|
365
|
+
chooseWorkspaceSubheading: "You have access to more than one workspace.",
|
|
366
|
+
invalidCredentials: "Invalid email or password.",
|
|
367
|
+
noAccess: "You don't have access to this application.",
|
|
368
|
+
passwordChangeRequired: "You must reset your password before you can sign in.",
|
|
369
|
+
genericError: "Something went wrong. Please try again."
|
|
370
|
+
};
|
|
371
|
+
/**
|
|
372
|
+
* The embedded, Clerk-style sign-in form (D2b). Renders email/password inside the
|
|
373
|
+
* consuming app and POSTs to the SDK's BFF (`@poly-x/next` `authenticate`), which
|
|
374
|
+
* runs the credential authorize server-side and seals an httpOnly session — no
|
|
375
|
+
* redirect to a hosted PolyX login page, no tokens or credentials in the browser
|
|
376
|
+
* after submit. Handles the workspace picker (select_tenant) inline.
|
|
377
|
+
*
|
|
378
|
+
* Ships styled: `import "@poly-x/react/styles.css"` once, then theme it by
|
|
379
|
+
* overriding the `--polyx-*` custom properties on any ancestor.
|
|
380
|
+
*/
|
|
381
|
+
function SignIn(props) {
|
|
382
|
+
const action = props.action ?? "/api/polyx/authenticate";
|
|
383
|
+
const variant = props.variant ?? "card";
|
|
384
|
+
const labels = {
|
|
385
|
+
...DEFAULT_LABELS,
|
|
386
|
+
...props.labels
|
|
387
|
+
};
|
|
388
|
+
const [email, setEmail] = (0, react.useState)("");
|
|
389
|
+
const [password, setPassword] = (0, react.useState)("");
|
|
390
|
+
const [tenants, setTenants] = (0, react.useState)(null);
|
|
391
|
+
const [submitting, setSubmitting] = (0, react.useState)(false);
|
|
392
|
+
const [error, setError] = (0, react.useState)(null);
|
|
393
|
+
async function authenticate(tenantId) {
|
|
394
|
+
setSubmitting(true);
|
|
395
|
+
setError(null);
|
|
396
|
+
try {
|
|
397
|
+
const body = await (await fetch(action, {
|
|
398
|
+
method: "POST",
|
|
399
|
+
headers: { "content-type": "application/json" },
|
|
400
|
+
body: JSON.stringify({
|
|
401
|
+
email,
|
|
402
|
+
password,
|
|
403
|
+
organizationCode: props.organizationCode,
|
|
404
|
+
tenantId
|
|
405
|
+
})
|
|
406
|
+
})).json().catch(() => ({}));
|
|
407
|
+
switch (body.status) {
|
|
408
|
+
case "success":
|
|
409
|
+
window.location.assign(props.afterSignInPath ?? body.redirectTo ?? "/");
|
|
410
|
+
return;
|
|
411
|
+
case "select_tenant":
|
|
412
|
+
setTenants(body.tenants ?? []);
|
|
413
|
+
return;
|
|
414
|
+
case "invalid_credentials":
|
|
415
|
+
setError(labels.invalidCredentials);
|
|
416
|
+
return;
|
|
417
|
+
case "no_access":
|
|
418
|
+
setError(labels.noAccess);
|
|
419
|
+
return;
|
|
420
|
+
case "password_change_required":
|
|
421
|
+
setError(labels.passwordChangeRequired);
|
|
422
|
+
if (body.userId) props.onPasswordChangeRequired?.(body.userId);
|
|
423
|
+
return;
|
|
424
|
+
default:
|
|
425
|
+
setError(labels.genericError);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
} catch {
|
|
429
|
+
setError(labels.genericError);
|
|
430
|
+
} finally {
|
|
431
|
+
setSubmitting(false);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function onSubmit(event) {
|
|
435
|
+
event.preventDefault();
|
|
436
|
+
authenticate();
|
|
437
|
+
}
|
|
438
|
+
const rootClass = [
|
|
439
|
+
"polyx-signin",
|
|
440
|
+
`polyx-signin--${variant}`,
|
|
441
|
+
props.className
|
|
442
|
+
].filter(Boolean).join(" ");
|
|
443
|
+
const picking = tenants !== null;
|
|
444
|
+
const header = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
445
|
+
className: "polyx-signin__header",
|
|
446
|
+
children: [
|
|
447
|
+
props.logo ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
448
|
+
className: "polyx-signin__logo",
|
|
449
|
+
children: props.logo
|
|
450
|
+
}) : null,
|
|
451
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
|
|
452
|
+
className: "polyx-signin__heading",
|
|
453
|
+
children: picking ? labels.chooseWorkspace : labels.heading
|
|
454
|
+
}),
|
|
455
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
456
|
+
className: "polyx-signin__subheading",
|
|
457
|
+
children: picking ? labels.chooseWorkspaceSubheading : labels.subheading
|
|
458
|
+
})
|
|
459
|
+
]
|
|
460
|
+
});
|
|
461
|
+
const errorNode = error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
462
|
+
role: "alert",
|
|
463
|
+
className: "polyx-signin__error",
|
|
464
|
+
children: error
|
|
465
|
+
}) : null;
|
|
466
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
467
|
+
className: rootClass,
|
|
468
|
+
"data-polyx-signin": picking ? "select-tenant" : "credentials",
|
|
469
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
470
|
+
className: "polyx-signin__card",
|
|
471
|
+
children: [header, picking ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
472
|
+
className: "polyx-signin__form",
|
|
473
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
474
|
+
className: "polyx-signin__tenants",
|
|
475
|
+
children: tenants.map((tenant) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
476
|
+
type: "button",
|
|
477
|
+
className: "polyx-signin__tenant",
|
|
478
|
+
disabled: submitting,
|
|
479
|
+
onClick: () => void authenticate(tenant.tenantId),
|
|
480
|
+
children: tenant.name ?? tenant.slug ?? tenant.organizationCode ?? tenant.tenantId
|
|
481
|
+
}) }, tenant.tenantId))
|
|
482
|
+
}), errorNode]
|
|
483
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
484
|
+
className: "polyx-signin__form",
|
|
485
|
+
onSubmit,
|
|
486
|
+
children: [
|
|
487
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
488
|
+
className: "polyx-signin__field",
|
|
489
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
490
|
+
className: "polyx-signin__label",
|
|
491
|
+
children: labels.emailLabel
|
|
492
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
493
|
+
className: "polyx-signin__input",
|
|
494
|
+
type: "email",
|
|
495
|
+
name: "email",
|
|
496
|
+
autoComplete: "username",
|
|
497
|
+
placeholder: labels.emailPlaceholder,
|
|
498
|
+
required: true,
|
|
499
|
+
value: email,
|
|
500
|
+
disabled: submitting,
|
|
501
|
+
onChange: (event) => setEmail(event.target.value)
|
|
502
|
+
})]
|
|
503
|
+
}),
|
|
504
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
505
|
+
className: "polyx-signin__field",
|
|
506
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
507
|
+
className: "polyx-signin__label",
|
|
508
|
+
children: labels.passwordLabel
|
|
509
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
510
|
+
className: "polyx-signin__input",
|
|
511
|
+
type: "password",
|
|
512
|
+
name: "password",
|
|
513
|
+
autoComplete: "current-password",
|
|
514
|
+
placeholder: labels.passwordPlaceholder,
|
|
515
|
+
required: true,
|
|
516
|
+
value: password,
|
|
517
|
+
disabled: submitting,
|
|
518
|
+
onChange: (event) => setPassword(event.target.value)
|
|
519
|
+
})]
|
|
520
|
+
}),
|
|
521
|
+
errorNode,
|
|
522
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
523
|
+
type: "submit",
|
|
524
|
+
className: "polyx-signin__submit",
|
|
525
|
+
disabled: submitting,
|
|
526
|
+
children: submitting ? labels.submitting : labels.submit
|
|
527
|
+
})
|
|
528
|
+
]
|
|
529
|
+
})]
|
|
530
|
+
})
|
|
363
531
|
});
|
|
364
532
|
}
|
|
365
533
|
//#endregion
|
package/dist/index.d.cts
CHANGED
|
@@ -191,16 +191,56 @@ declare function Protected({
|
|
|
191
191
|
}: ProtectedProps): ReactNode;
|
|
192
192
|
//#endregion
|
|
193
193
|
//#region src/components/sign-in.d.ts
|
|
194
|
-
|
|
195
|
-
|
|
194
|
+
/** Every user-facing string, overridable for i18n / white-labelling. */
|
|
195
|
+
interface SignInLabels {
|
|
196
|
+
heading: string;
|
|
197
|
+
subheading: string;
|
|
198
|
+
emailLabel: string;
|
|
199
|
+
emailPlaceholder: string;
|
|
200
|
+
passwordLabel: string;
|
|
201
|
+
passwordPlaceholder: string;
|
|
202
|
+
submit: string;
|
|
203
|
+
submitting: string;
|
|
204
|
+
chooseWorkspace: string;
|
|
205
|
+
chooseWorkspaceSubheading: string;
|
|
206
|
+
invalidCredentials: string;
|
|
207
|
+
noAccess: string;
|
|
208
|
+
passwordChangeRequired: string;
|
|
209
|
+
genericError: string;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* `card` renders just the card — drop it into your own layout.
|
|
213
|
+
* `page` owns the viewport — a centered card on a full-bleed background.
|
|
214
|
+
*/
|
|
215
|
+
type SignInVariant = "card" | "page";
|
|
216
|
+
interface SignInProps {
|
|
217
|
+
/** Layout preset. Default `card`. */
|
|
218
|
+
variant?: SignInVariant;
|
|
219
|
+
/** BFF endpoint backed by `createAuthHandlers().authenticate`. Default `/api/polyx/authenticate`. */
|
|
220
|
+
action?: string;
|
|
221
|
+
/** Where to send the browser after success. Overrides the server's default redirect. */
|
|
222
|
+
afterSignInPath?: string;
|
|
223
|
+
/** Pre-scope the login to a single organization (skips org discovery). */
|
|
224
|
+
organizationCode?: string;
|
|
225
|
+
/** Rendered above the heading — your wordmark or logo. */
|
|
226
|
+
logo?: ReactNode;
|
|
227
|
+
/** Appended to the root element's class list, for escape-hatch styling. */
|
|
196
228
|
className?: string;
|
|
229
|
+
labels?: Partial<SignInLabels>;
|
|
230
|
+
/** Fired when the backend requires the user to set a new password before continuing. */
|
|
231
|
+
onPasswordChangeRequired?: (userId: string) => void;
|
|
197
232
|
}
|
|
198
|
-
/**
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
233
|
+
/**
|
|
234
|
+
* The embedded, Clerk-style sign-in form (D2b). Renders email/password inside the
|
|
235
|
+
* consuming app and POSTs to the SDK's BFF (`@poly-x/next` `authenticate`), which
|
|
236
|
+
* runs the credential authorize server-side and seals an httpOnly session — no
|
|
237
|
+
* redirect to a hosted PolyX login page, no tokens or credentials in the browser
|
|
238
|
+
* after submit. Handles the workspace picker (select_tenant) inline.
|
|
239
|
+
*
|
|
240
|
+
* Ships styled: `import "@poly-x/react/styles.css"` once, then theme it by
|
|
241
|
+
* overriding the `--polyx-*` custom properties on any ancestor.
|
|
242
|
+
*/
|
|
243
|
+
declare function SignIn(props: SignInProps): ReactNode;
|
|
204
244
|
//#endregion
|
|
205
245
|
//#region src/components/auth-callback.d.ts
|
|
206
246
|
interface AuthCallbackProps {
|
|
@@ -220,4 +260,4 @@ declare function AuthCallback({
|
|
|
220
260
|
declare const PACKAGE_NAME = "@poly-x/react";
|
|
221
261
|
declare const version = "0.0.0";
|
|
222
262
|
//#endregion
|
|
223
|
-
export { AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, type LoginController, type LoginControllerDeps, PACKAGE_NAME, PolyXContext, type PolyXContextValue, PolyXProvider, type PolyXProviderProps, PopupCancelledError, type PopupHandle, Protected, type ProtectedProps, SessionStoragePkceStore, SignIn, type SignInOptions, type SignInProps, type UseAuth, WebLocksLock, WindowBrowserBridge, createLoginController, useAuth, useAuthCallback, useSession, useUser, version };
|
|
263
|
+
export { AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, type LoginController, type LoginControllerDeps, PACKAGE_NAME, PolyXContext, type PolyXContextValue, PolyXProvider, type PolyXProviderProps, PopupCancelledError, type PopupHandle, Protected, type ProtectedProps, SessionStoragePkceStore, SignIn, type SignInLabels, type SignInOptions, type SignInProps, type SignInVariant, type UseAuth, WebLocksLock, WindowBrowserBridge, createLoginController, useAuth, useAuthCallback, useSession, useUser, version };
|
package/dist/index.d.mts
CHANGED
|
@@ -191,16 +191,56 @@ declare function Protected({
|
|
|
191
191
|
}: ProtectedProps): ReactNode;
|
|
192
192
|
//#endregion
|
|
193
193
|
//#region src/components/sign-in.d.ts
|
|
194
|
-
|
|
195
|
-
|
|
194
|
+
/** Every user-facing string, overridable for i18n / white-labelling. */
|
|
195
|
+
interface SignInLabels {
|
|
196
|
+
heading: string;
|
|
197
|
+
subheading: string;
|
|
198
|
+
emailLabel: string;
|
|
199
|
+
emailPlaceholder: string;
|
|
200
|
+
passwordLabel: string;
|
|
201
|
+
passwordPlaceholder: string;
|
|
202
|
+
submit: string;
|
|
203
|
+
submitting: string;
|
|
204
|
+
chooseWorkspace: string;
|
|
205
|
+
chooseWorkspaceSubheading: string;
|
|
206
|
+
invalidCredentials: string;
|
|
207
|
+
noAccess: string;
|
|
208
|
+
passwordChangeRequired: string;
|
|
209
|
+
genericError: string;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* `card` renders just the card — drop it into your own layout.
|
|
213
|
+
* `page` owns the viewport — a centered card on a full-bleed background.
|
|
214
|
+
*/
|
|
215
|
+
type SignInVariant = "card" | "page";
|
|
216
|
+
interface SignInProps {
|
|
217
|
+
/** Layout preset. Default `card`. */
|
|
218
|
+
variant?: SignInVariant;
|
|
219
|
+
/** BFF endpoint backed by `createAuthHandlers().authenticate`. Default `/api/polyx/authenticate`. */
|
|
220
|
+
action?: string;
|
|
221
|
+
/** Where to send the browser after success. Overrides the server's default redirect. */
|
|
222
|
+
afterSignInPath?: string;
|
|
223
|
+
/** Pre-scope the login to a single organization (skips org discovery). */
|
|
224
|
+
organizationCode?: string;
|
|
225
|
+
/** Rendered above the heading — your wordmark or logo. */
|
|
226
|
+
logo?: ReactNode;
|
|
227
|
+
/** Appended to the root element's class list, for escape-hatch styling. */
|
|
196
228
|
className?: string;
|
|
229
|
+
labels?: Partial<SignInLabels>;
|
|
230
|
+
/** Fired when the backend requires the user to set a new password before continuing. */
|
|
231
|
+
onPasswordChangeRequired?: (userId: string) => void;
|
|
197
232
|
}
|
|
198
|
-
/**
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
233
|
+
/**
|
|
234
|
+
* The embedded, Clerk-style sign-in form (D2b). Renders email/password inside the
|
|
235
|
+
* consuming app and POSTs to the SDK's BFF (`@poly-x/next` `authenticate`), which
|
|
236
|
+
* runs the credential authorize server-side and seals an httpOnly session — no
|
|
237
|
+
* redirect to a hosted PolyX login page, no tokens or credentials in the browser
|
|
238
|
+
* after submit. Handles the workspace picker (select_tenant) inline.
|
|
239
|
+
*
|
|
240
|
+
* Ships styled: `import "@poly-x/react/styles.css"` once, then theme it by
|
|
241
|
+
* overriding the `--polyx-*` custom properties on any ancestor.
|
|
242
|
+
*/
|
|
243
|
+
declare function SignIn(props: SignInProps): ReactNode;
|
|
204
244
|
//#endregion
|
|
205
245
|
//#region src/components/auth-callback.d.ts
|
|
206
246
|
interface AuthCallbackProps {
|
|
@@ -220,4 +260,4 @@ declare function AuthCallback({
|
|
|
220
260
|
declare const PACKAGE_NAME = "@poly-x/react";
|
|
221
261
|
declare const version = "0.0.0";
|
|
222
262
|
//#endregion
|
|
223
|
-
export { AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, type LoginController, type LoginControllerDeps, PACKAGE_NAME, PolyXContext, type PolyXContextValue, PolyXProvider, type PolyXProviderProps, PopupCancelledError, type PopupHandle, Protected, type ProtectedProps, SessionStoragePkceStore, SignIn, type SignInOptions, type SignInProps, type UseAuth, WebLocksLock, WindowBrowserBridge, createLoginController, useAuth, useAuthCallback, useSession, useUser, version };
|
|
263
|
+
export { AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, type LoginController, type LoginControllerDeps, PACKAGE_NAME, PolyXContext, type PolyXContextValue, PolyXProvider, type PolyXProviderProps, PopupCancelledError, type PopupHandle, Protected, type ProtectedProps, SessionStoragePkceStore, SignIn, type SignInLabels, type SignInOptions, type SignInProps, type SignInVariant, type UseAuth, WebLocksLock, WindowBrowserBridge, createLoginController, useAuth, useAuthCallback, useSession, useUser, version };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { AuthClient, FetchTransport, InMemoryLock, InMemoryPkceStore, InMemorySessionChannel, InMemorySessionStore, createSessionEngine, generatePkce, randomState, resolveConfig } from "@poly-x/core";
|
|
3
|
-
import { createContext, useCallback, useContext, useEffect, useMemo, useSyncExternalStore } from "react";
|
|
4
|
-
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useState, useSyncExternalStore } from "react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
//#region src/auth-channel.ts
|
|
6
6
|
var BroadcastAuthChannel = class {
|
|
7
7
|
supported;
|
|
@@ -351,14 +351,182 @@ function Protected({ children, fallback = null, loading = null }) {
|
|
|
351
351
|
}
|
|
352
352
|
//#endregion
|
|
353
353
|
//#region src/components/sign-in.tsx
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
354
|
+
const DEFAULT_LABELS = {
|
|
355
|
+
heading: "Sign in",
|
|
356
|
+
subheading: "Enter your credentials to continue.",
|
|
357
|
+
emailLabel: "Email",
|
|
358
|
+
emailPlaceholder: "you@company.com",
|
|
359
|
+
passwordLabel: "Password",
|
|
360
|
+
passwordPlaceholder: "••••••••",
|
|
361
|
+
submit: "Sign in",
|
|
362
|
+
submitting: "Signing in…",
|
|
363
|
+
chooseWorkspace: "Choose a workspace",
|
|
364
|
+
chooseWorkspaceSubheading: "You have access to more than one workspace.",
|
|
365
|
+
invalidCredentials: "Invalid email or password.",
|
|
366
|
+
noAccess: "You don't have access to this application.",
|
|
367
|
+
passwordChangeRequired: "You must reset your password before you can sign in.",
|
|
368
|
+
genericError: "Something went wrong. Please try again."
|
|
369
|
+
};
|
|
370
|
+
/**
|
|
371
|
+
* The embedded, Clerk-style sign-in form (D2b). Renders email/password inside the
|
|
372
|
+
* consuming app and POSTs to the SDK's BFF (`@poly-x/next` `authenticate`), which
|
|
373
|
+
* runs the credential authorize server-side and seals an httpOnly session — no
|
|
374
|
+
* redirect to a hosted PolyX login page, no tokens or credentials in the browser
|
|
375
|
+
* after submit. Handles the workspace picker (select_tenant) inline.
|
|
376
|
+
*
|
|
377
|
+
* Ships styled: `import "@poly-x/react/styles.css"` once, then theme it by
|
|
378
|
+
* overriding the `--polyx-*` custom properties on any ancestor.
|
|
379
|
+
*/
|
|
380
|
+
function SignIn(props) {
|
|
381
|
+
const action = props.action ?? "/api/polyx/authenticate";
|
|
382
|
+
const variant = props.variant ?? "card";
|
|
383
|
+
const labels = {
|
|
384
|
+
...DEFAULT_LABELS,
|
|
385
|
+
...props.labels
|
|
386
|
+
};
|
|
387
|
+
const [email, setEmail] = useState("");
|
|
388
|
+
const [password, setPassword] = useState("");
|
|
389
|
+
const [tenants, setTenants] = useState(null);
|
|
390
|
+
const [submitting, setSubmitting] = useState(false);
|
|
391
|
+
const [error, setError] = useState(null);
|
|
392
|
+
async function authenticate(tenantId) {
|
|
393
|
+
setSubmitting(true);
|
|
394
|
+
setError(null);
|
|
395
|
+
try {
|
|
396
|
+
const body = await (await fetch(action, {
|
|
397
|
+
method: "POST",
|
|
398
|
+
headers: { "content-type": "application/json" },
|
|
399
|
+
body: JSON.stringify({
|
|
400
|
+
email,
|
|
401
|
+
password,
|
|
402
|
+
organizationCode: props.organizationCode,
|
|
403
|
+
tenantId
|
|
404
|
+
})
|
|
405
|
+
})).json().catch(() => ({}));
|
|
406
|
+
switch (body.status) {
|
|
407
|
+
case "success":
|
|
408
|
+
window.location.assign(props.afterSignInPath ?? body.redirectTo ?? "/");
|
|
409
|
+
return;
|
|
410
|
+
case "select_tenant":
|
|
411
|
+
setTenants(body.tenants ?? []);
|
|
412
|
+
return;
|
|
413
|
+
case "invalid_credentials":
|
|
414
|
+
setError(labels.invalidCredentials);
|
|
415
|
+
return;
|
|
416
|
+
case "no_access":
|
|
417
|
+
setError(labels.noAccess);
|
|
418
|
+
return;
|
|
419
|
+
case "password_change_required":
|
|
420
|
+
setError(labels.passwordChangeRequired);
|
|
421
|
+
if (body.userId) props.onPasswordChangeRequired?.(body.userId);
|
|
422
|
+
return;
|
|
423
|
+
default:
|
|
424
|
+
setError(labels.genericError);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
} catch {
|
|
428
|
+
setError(labels.genericError);
|
|
429
|
+
} finally {
|
|
430
|
+
setSubmitting(false);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function onSubmit(event) {
|
|
434
|
+
event.preventDefault();
|
|
435
|
+
authenticate();
|
|
436
|
+
}
|
|
437
|
+
const rootClass = [
|
|
438
|
+
"polyx-signin",
|
|
439
|
+
`polyx-signin--${variant}`,
|
|
440
|
+
props.className
|
|
441
|
+
].filter(Boolean).join(" ");
|
|
442
|
+
const picking = tenants !== null;
|
|
443
|
+
const header = /* @__PURE__ */ jsxs("div", {
|
|
444
|
+
className: "polyx-signin__header",
|
|
445
|
+
children: [
|
|
446
|
+
props.logo ? /* @__PURE__ */ jsx("div", {
|
|
447
|
+
className: "polyx-signin__logo",
|
|
448
|
+
children: props.logo
|
|
449
|
+
}) : null,
|
|
450
|
+
/* @__PURE__ */ jsx("h1", {
|
|
451
|
+
className: "polyx-signin__heading",
|
|
452
|
+
children: picking ? labels.chooseWorkspace : labels.heading
|
|
453
|
+
}),
|
|
454
|
+
/* @__PURE__ */ jsx("p", {
|
|
455
|
+
className: "polyx-signin__subheading",
|
|
456
|
+
children: picking ? labels.chooseWorkspaceSubheading : labels.subheading
|
|
457
|
+
})
|
|
458
|
+
]
|
|
459
|
+
});
|
|
460
|
+
const errorNode = error ? /* @__PURE__ */ jsx("p", {
|
|
461
|
+
role: "alert",
|
|
462
|
+
className: "polyx-signin__error",
|
|
463
|
+
children: error
|
|
464
|
+
}) : null;
|
|
465
|
+
return /* @__PURE__ */ jsx("div", {
|
|
466
|
+
className: rootClass,
|
|
467
|
+
"data-polyx-signin": picking ? "select-tenant" : "credentials",
|
|
468
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
469
|
+
className: "polyx-signin__card",
|
|
470
|
+
children: [header, picking ? /* @__PURE__ */ jsxs("div", {
|
|
471
|
+
className: "polyx-signin__form",
|
|
472
|
+
children: [/* @__PURE__ */ jsx("ul", {
|
|
473
|
+
className: "polyx-signin__tenants",
|
|
474
|
+
children: tenants.map((tenant) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
|
|
475
|
+
type: "button",
|
|
476
|
+
className: "polyx-signin__tenant",
|
|
477
|
+
disabled: submitting,
|
|
478
|
+
onClick: () => void authenticate(tenant.tenantId),
|
|
479
|
+
children: tenant.name ?? tenant.slug ?? tenant.organizationCode ?? tenant.tenantId
|
|
480
|
+
}) }, tenant.tenantId))
|
|
481
|
+
}), errorNode]
|
|
482
|
+
}) : /* @__PURE__ */ jsxs("form", {
|
|
483
|
+
className: "polyx-signin__form",
|
|
484
|
+
onSubmit,
|
|
485
|
+
children: [
|
|
486
|
+
/* @__PURE__ */ jsxs("label", {
|
|
487
|
+
className: "polyx-signin__field",
|
|
488
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
489
|
+
className: "polyx-signin__label",
|
|
490
|
+
children: labels.emailLabel
|
|
491
|
+
}), /* @__PURE__ */ jsx("input", {
|
|
492
|
+
className: "polyx-signin__input",
|
|
493
|
+
type: "email",
|
|
494
|
+
name: "email",
|
|
495
|
+
autoComplete: "username",
|
|
496
|
+
placeholder: labels.emailPlaceholder,
|
|
497
|
+
required: true,
|
|
498
|
+
value: email,
|
|
499
|
+
disabled: submitting,
|
|
500
|
+
onChange: (event) => setEmail(event.target.value)
|
|
501
|
+
})]
|
|
502
|
+
}),
|
|
503
|
+
/* @__PURE__ */ jsxs("label", {
|
|
504
|
+
className: "polyx-signin__field",
|
|
505
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
506
|
+
className: "polyx-signin__label",
|
|
507
|
+
children: labels.passwordLabel
|
|
508
|
+
}), /* @__PURE__ */ jsx("input", {
|
|
509
|
+
className: "polyx-signin__input",
|
|
510
|
+
type: "password",
|
|
511
|
+
name: "password",
|
|
512
|
+
autoComplete: "current-password",
|
|
513
|
+
placeholder: labels.passwordPlaceholder,
|
|
514
|
+
required: true,
|
|
515
|
+
value: password,
|
|
516
|
+
disabled: submitting,
|
|
517
|
+
onChange: (event) => setPassword(event.target.value)
|
|
518
|
+
})]
|
|
519
|
+
}),
|
|
520
|
+
errorNode,
|
|
521
|
+
/* @__PURE__ */ jsx("button", {
|
|
522
|
+
type: "submit",
|
|
523
|
+
className: "polyx-signin__submit",
|
|
524
|
+
disabled: submitting,
|
|
525
|
+
children: submitting ? labels.submitting : labels.submit
|
|
526
|
+
})
|
|
527
|
+
]
|
|
528
|
+
})]
|
|
529
|
+
})
|
|
362
530
|
});
|
|
363
531
|
}
|
|
364
532
|
//#endregion
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @poly-x/react — default component styles.
|
|
3
|
+
*
|
|
4
|
+
* Import once, near the root of your app:
|
|
5
|
+
* import "@poly-x/react/styles.css"; // or "@poly-x/next/styles.css"
|
|
6
|
+
*
|
|
7
|
+
* The SDK is a guest in your app: every rule is scoped under a `.polyx-*` class,
|
|
8
|
+
* there are no global element selectors, and nothing here leaks into your own
|
|
9
|
+
* markup. To theme it, override the custom properties below on any ancestor
|
|
10
|
+
* (`:root`, a wrapper div, …) — that is the supported theming surface. Dark mode
|
|
11
|
+
* follows the OS by default and defers to an explicit `.dark` / `[data-theme]`
|
|
12
|
+
* on a host ancestor when one is present.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
.polyx-signin {
|
|
16
|
+
/* Colors */
|
|
17
|
+
--polyx-brand: #4f46e5;
|
|
18
|
+
--polyx-brand-hover: #4338ca;
|
|
19
|
+
--polyx-brand-fg: #ffffff;
|
|
20
|
+
--polyx-bg: #ffffff;
|
|
21
|
+
--polyx-fg: #18181b;
|
|
22
|
+
--polyx-muted-fg: #71717a;
|
|
23
|
+
--polyx-border: #e4e4e7;
|
|
24
|
+
--polyx-input-bg: #ffffff;
|
|
25
|
+
--polyx-ring: #a5b4fc;
|
|
26
|
+
--polyx-danger: #dc2626;
|
|
27
|
+
--polyx-danger-bg: #fef2f2;
|
|
28
|
+
--polyx-page-bg: #fafafa;
|
|
29
|
+
|
|
30
|
+
/* Shape + type */
|
|
31
|
+
--polyx-radius: 0.75rem;
|
|
32
|
+
--polyx-radius-sm: 0.5rem;
|
|
33
|
+
--polyx-font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
34
|
+
--polyx-shadow: 0 1px 3px rgb(0 0 0 / 0.06), 0 10px 30px rgb(0 0 0 / 0.07);
|
|
35
|
+
|
|
36
|
+
font-family: var(--polyx-font);
|
|
37
|
+
color: var(--polyx-fg);
|
|
38
|
+
-webkit-font-smoothing: antialiased;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Scoped box model — applies only inside our own subtree. */
|
|
42
|
+
.polyx-signin,
|
|
43
|
+
.polyx-signin *,
|
|
44
|
+
.polyx-signin *::before,
|
|
45
|
+
.polyx-signin *::after {
|
|
46
|
+
box-sizing: border-box;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Dark: follow the OS… */
|
|
50
|
+
@media (prefers-color-scheme: dark) {
|
|
51
|
+
.polyx-signin {
|
|
52
|
+
--polyx-brand: #6366f1;
|
|
53
|
+
--polyx-brand-hover: #818cf8;
|
|
54
|
+
--polyx-bg: #18181b;
|
|
55
|
+
--polyx-fg: #fafafa;
|
|
56
|
+
--polyx-muted-fg: #a1a1aa;
|
|
57
|
+
--polyx-border: #3f3f46;
|
|
58
|
+
--polyx-input-bg: #27272a;
|
|
59
|
+
--polyx-ring: #4f46e5;
|
|
60
|
+
--polyx-danger: #f87171;
|
|
61
|
+
--polyx-danger-bg: #2a1616;
|
|
62
|
+
--polyx-page-bg: #09090b;
|
|
63
|
+
--polyx-shadow: 0 1px 3px rgb(0 0 0 / 0.4), 0 10px 30px rgb(0 0 0 / 0.35);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* …but an explicit host theme wins over the OS preference (`:where` keeps
|
|
68
|
+
specificity at 0 so these stay as easy to override as the base rule). */
|
|
69
|
+
:where(.dark, [data-theme="dark"]) .polyx-signin {
|
|
70
|
+
--polyx-brand: #6366f1;
|
|
71
|
+
--polyx-brand-hover: #818cf8;
|
|
72
|
+
--polyx-bg: #18181b;
|
|
73
|
+
--polyx-fg: #fafafa;
|
|
74
|
+
--polyx-muted-fg: #a1a1aa;
|
|
75
|
+
--polyx-border: #3f3f46;
|
|
76
|
+
--polyx-input-bg: #27272a;
|
|
77
|
+
--polyx-ring: #4f46e5;
|
|
78
|
+
--polyx-danger: #f87171;
|
|
79
|
+
--polyx-danger-bg: #2a1616;
|
|
80
|
+
--polyx-page-bg: #09090b;
|
|
81
|
+
--polyx-shadow: 0 1px 3px rgb(0 0 0 / 0.4), 0 10px 30px rgb(0 0 0 / 0.35);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
:where(.light, [data-theme="light"]) .polyx-signin {
|
|
85
|
+
--polyx-brand: #4f46e5;
|
|
86
|
+
--polyx-brand-hover: #4338ca;
|
|
87
|
+
--polyx-bg: #ffffff;
|
|
88
|
+
--polyx-fg: #18181b;
|
|
89
|
+
--polyx-muted-fg: #71717a;
|
|
90
|
+
--polyx-border: #e4e4e7;
|
|
91
|
+
--polyx-input-bg: #ffffff;
|
|
92
|
+
--polyx-ring: #a5b4fc;
|
|
93
|
+
--polyx-danger: #dc2626;
|
|
94
|
+
--polyx-danger-bg: #fef2f2;
|
|
95
|
+
--polyx-page-bg: #fafafa;
|
|
96
|
+
--polyx-shadow: 0 1px 3px rgb(0 0 0 / 0.06), 0 10px 30px rgb(0 0 0 / 0.07);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* --- Variants ------------------------------------------------------------ */
|
|
100
|
+
|
|
101
|
+
/* `page`: owns the viewport — centered card on a full-bleed background. */
|
|
102
|
+
.polyx-signin--page {
|
|
103
|
+
display: grid;
|
|
104
|
+
place-items: center;
|
|
105
|
+
min-height: 100dvh;
|
|
106
|
+
padding: 1.5rem;
|
|
107
|
+
background: var(--polyx-page-bg);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* `card`: just the card — drop it anywhere in your own layout. */
|
|
111
|
+
.polyx-signin--card {
|
|
112
|
+
display: block;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.polyx-signin__card {
|
|
116
|
+
width: 100%;
|
|
117
|
+
max-width: 24rem;
|
|
118
|
+
margin-inline: auto;
|
|
119
|
+
padding: 2rem;
|
|
120
|
+
background: var(--polyx-bg);
|
|
121
|
+
border: 1px solid var(--polyx-border);
|
|
122
|
+
border-radius: var(--polyx-radius);
|
|
123
|
+
box-shadow: var(--polyx-shadow);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* --- Header -------------------------------------------------------------- */
|
|
127
|
+
|
|
128
|
+
.polyx-signin__header {
|
|
129
|
+
display: flex;
|
|
130
|
+
flex-direction: column;
|
|
131
|
+
gap: 0.375rem;
|
|
132
|
+
margin-bottom: 1.75rem;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.polyx-signin__logo {
|
|
136
|
+
display: flex;
|
|
137
|
+
margin-bottom: 0.75rem;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.polyx-signin__heading {
|
|
141
|
+
margin: 0;
|
|
142
|
+
font-size: 1.375rem;
|
|
143
|
+
font-weight: 600;
|
|
144
|
+
letter-spacing: -0.01em;
|
|
145
|
+
color: var(--polyx-fg);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.polyx-signin__subheading {
|
|
149
|
+
margin: 0;
|
|
150
|
+
font-size: 0.875rem;
|
|
151
|
+
color: var(--polyx-muted-fg);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* --- Form ---------------------------------------------------------------- */
|
|
155
|
+
|
|
156
|
+
.polyx-signin__form {
|
|
157
|
+
display: flex;
|
|
158
|
+
flex-direction: column;
|
|
159
|
+
gap: 1.125rem;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.polyx-signin__field {
|
|
163
|
+
display: flex;
|
|
164
|
+
flex-direction: column;
|
|
165
|
+
gap: 0.4375rem;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.polyx-signin__label {
|
|
169
|
+
font-size: 0.875rem;
|
|
170
|
+
font-weight: 500;
|
|
171
|
+
color: var(--polyx-fg);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.polyx-signin__input {
|
|
175
|
+
width: 100%;
|
|
176
|
+
height: 2.5rem;
|
|
177
|
+
padding: 0 0.75rem;
|
|
178
|
+
font: inherit;
|
|
179
|
+
font-size: 0.875rem;
|
|
180
|
+
color: var(--polyx-fg);
|
|
181
|
+
background: var(--polyx-input-bg);
|
|
182
|
+
border: 1px solid var(--polyx-border);
|
|
183
|
+
border-radius: var(--polyx-radius-sm);
|
|
184
|
+
outline: none;
|
|
185
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.polyx-signin__input::placeholder {
|
|
189
|
+
color: var(--polyx-muted-fg);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.polyx-signin__input:focus-visible {
|
|
193
|
+
border-color: var(--polyx-brand);
|
|
194
|
+
box-shadow: 0 0 0 3px var(--polyx-ring);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.polyx-signin__input:disabled {
|
|
198
|
+
opacity: 0.6;
|
|
199
|
+
cursor: not-allowed;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* --- Buttons ------------------------------------------------------------- */
|
|
203
|
+
|
|
204
|
+
.polyx-signin__submit,
|
|
205
|
+
.polyx-signin__tenant {
|
|
206
|
+
display: inline-flex;
|
|
207
|
+
align-items: center;
|
|
208
|
+
justify-content: center;
|
|
209
|
+
width: 100%;
|
|
210
|
+
min-height: 2.5rem;
|
|
211
|
+
padding: 0 1rem;
|
|
212
|
+
font: inherit;
|
|
213
|
+
font-size: 0.875rem;
|
|
214
|
+
font-weight: 500;
|
|
215
|
+
color: var(--polyx-brand-fg);
|
|
216
|
+
background: var(--polyx-brand);
|
|
217
|
+
border: 1px solid transparent;
|
|
218
|
+
border-radius: var(--polyx-radius-sm);
|
|
219
|
+
cursor: pointer;
|
|
220
|
+
transition: background-color 0.15s ease;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.polyx-signin__submit:hover:not(:disabled),
|
|
224
|
+
.polyx-signin__tenant:hover:not(:disabled) {
|
|
225
|
+
background: var(--polyx-brand-hover);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.polyx-signin__submit:focus-visible,
|
|
229
|
+
.polyx-signin__tenant:focus-visible {
|
|
230
|
+
outline: none;
|
|
231
|
+
box-shadow: 0 0 0 3px var(--polyx-ring);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.polyx-signin__submit:disabled,
|
|
235
|
+
.polyx-signin__tenant:disabled {
|
|
236
|
+
opacity: 0.6;
|
|
237
|
+
cursor: not-allowed;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* --- Error --------------------------------------------------------------- */
|
|
241
|
+
|
|
242
|
+
.polyx-signin__error {
|
|
243
|
+
margin: 0;
|
|
244
|
+
padding: 0.625rem 0.75rem;
|
|
245
|
+
font-size: 0.8125rem;
|
|
246
|
+
color: var(--polyx-danger);
|
|
247
|
+
background: var(--polyx-danger-bg);
|
|
248
|
+
border: 1px solid color-mix(in srgb, var(--polyx-danger) 30%, transparent);
|
|
249
|
+
border-radius: var(--polyx-radius-sm);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* --- Workspace picker ---------------------------------------------------- */
|
|
253
|
+
|
|
254
|
+
.polyx-signin__tenants {
|
|
255
|
+
display: flex;
|
|
256
|
+
flex-direction: column;
|
|
257
|
+
gap: 0.625rem;
|
|
258
|
+
margin: 0;
|
|
259
|
+
padding: 0;
|
|
260
|
+
list-style: none;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* The picker's buttons read as choices, not as the primary CTA. */
|
|
264
|
+
.polyx-signin__tenant {
|
|
265
|
+
justify-content: flex-start;
|
|
266
|
+
color: var(--polyx-fg);
|
|
267
|
+
background: var(--polyx-input-bg);
|
|
268
|
+
border-color: var(--polyx-border);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.polyx-signin__tenant:hover:not(:disabled) {
|
|
272
|
+
background: var(--polyx-input-bg);
|
|
273
|
+
border-color: var(--polyx-brand);
|
|
274
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poly-x/react",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.3",
|
|
4
4
|
"description": "PolyX SDK for React SPAs - provider, hooks, drop-in auth UI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"sideEffects":
|
|
7
|
+
"sideEffects": [
|
|
8
|
+
"**/*.css"
|
|
9
|
+
],
|
|
8
10
|
"files": [
|
|
9
11
|
"dist"
|
|
10
12
|
],
|
|
@@ -18,13 +20,14 @@
|
|
|
18
20
|
"types": "./dist/index.d.cts",
|
|
19
21
|
"default": "./dist/index.cjs"
|
|
20
22
|
}
|
|
21
|
-
}
|
|
23
|
+
},
|
|
24
|
+
"./styles.css": "./dist/styles.css"
|
|
22
25
|
},
|
|
23
26
|
"main": "./dist/index.cjs",
|
|
24
27
|
"module": "./dist/index.mjs",
|
|
25
28
|
"types": "./dist/index.d.cts",
|
|
26
29
|
"dependencies": {
|
|
27
|
-
"@poly-x/core": "0.1.0-alpha.
|
|
30
|
+
"@poly-x/core": "0.1.0-alpha.3"
|
|
28
31
|
},
|
|
29
32
|
"peerDependencies": {
|
|
30
33
|
"react": ">=18",
|
|
@@ -51,7 +54,7 @@
|
|
|
51
54
|
"react-dom": "^19.2.7"
|
|
52
55
|
},
|
|
53
56
|
"scripts": {
|
|
54
|
-
"build": "tsdown",
|
|
57
|
+
"build": "tsdown && node scripts/copy-styles.mjs",
|
|
55
58
|
"test": "vitest run",
|
|
56
59
|
"lint": "eslint src"
|
|
57
60
|
}
|