@poly-x/react 0.1.0-alpha.1 → 0.1.0-alpha.2

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 CHANGED
@@ -352,14 +352,146 @@ function Protected({ children, fallback = null, loading = null }) {
352
352
  }
353
353
  //#endregion
354
354
  //#region src/components/sign-in.tsx
355
- /** Minimal sign-in trigger — popup by default. Style via `className`/children. */
356
- function SignIn({ children, className, ...options }) {
357
- const { signIn } = useAuth();
358
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
359
- type: "button",
360
- className,
361
- onClick: () => void signIn(options),
362
- children: children ?? "Sign in"
355
+ const DEFAULT_LABELS = {
356
+ emailLabel: "Email",
357
+ passwordLabel: "Password",
358
+ submit: "Sign in",
359
+ submitting: "Signing in…",
360
+ chooseWorkspace: "Choose a workspace",
361
+ invalidCredentials: "Invalid email or password.",
362
+ noAccess: "You don't have access to this application.",
363
+ passwordChangeRequired: "You must reset your password before you can sign in.",
364
+ genericError: "Something went wrong. Please try again."
365
+ };
366
+ /**
367
+ * The embedded, Clerk-style sign-in form (D2b). Renders email/password inside the
368
+ * consuming app and POSTs to the SDK's BFF (`@poly-x/next` `authenticate`), which
369
+ * runs the credential authorize server-side and seals an httpOnly session — no
370
+ * redirect to a hosted PolyX login page, no tokens or credentials in the browser
371
+ * after submit. Handles the workspace picker (select_tenant) inline.
372
+ */
373
+ function SignIn(props) {
374
+ const action = props.action ?? "/api/polyx/authenticate";
375
+ const labels = {
376
+ ...DEFAULT_LABELS,
377
+ ...props.labels
378
+ };
379
+ const [email, setEmail] = (0, react.useState)("");
380
+ const [password, setPassword] = (0, react.useState)("");
381
+ const [tenants, setTenants] = (0, react.useState)(null);
382
+ const [submitting, setSubmitting] = (0, react.useState)(false);
383
+ const [error, setError] = (0, react.useState)(null);
384
+ async function authenticate(tenantId) {
385
+ setSubmitting(true);
386
+ setError(null);
387
+ try {
388
+ const body = await (await fetch(action, {
389
+ method: "POST",
390
+ headers: { "content-type": "application/json" },
391
+ body: JSON.stringify({
392
+ email,
393
+ password,
394
+ organizationCode: props.organizationCode,
395
+ tenantId
396
+ })
397
+ })).json().catch(() => ({}));
398
+ switch (body.status) {
399
+ case "success":
400
+ window.location.assign(props.afterSignInPath ?? body.redirectTo ?? "/");
401
+ return;
402
+ case "select_tenant":
403
+ setTenants(body.tenants ?? []);
404
+ return;
405
+ case "invalid_credentials":
406
+ setError(labels.invalidCredentials);
407
+ return;
408
+ case "no_access":
409
+ setError(labels.noAccess);
410
+ return;
411
+ case "password_change_required":
412
+ setError(labels.passwordChangeRequired);
413
+ if (body.userId) props.onPasswordChangeRequired?.(body.userId);
414
+ return;
415
+ default:
416
+ setError(labels.genericError);
417
+ return;
418
+ }
419
+ } catch {
420
+ setError(labels.genericError);
421
+ } finally {
422
+ setSubmitting(false);
423
+ }
424
+ }
425
+ function onSubmit(event) {
426
+ event.preventDefault();
427
+ authenticate();
428
+ }
429
+ if (tenants) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
430
+ className: props.className,
431
+ "data-polyx-signin": "select-tenant",
432
+ children: [
433
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
434
+ className: "polyx-signin__heading",
435
+ children: labels.chooseWorkspace
436
+ }),
437
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
438
+ className: "polyx-signin__tenants",
439
+ children: tenants.map((tenant) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
440
+ type: "button",
441
+ className: "polyx-signin__tenant",
442
+ disabled: submitting,
443
+ onClick: () => void authenticate(tenant.tenantId),
444
+ children: tenant.name ?? tenant.slug ?? tenant.organizationCode ?? tenant.tenantId
445
+ }) }, tenant.tenantId))
446
+ }),
447
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
448
+ role: "alert",
449
+ className: "polyx-signin__error",
450
+ children: error
451
+ }) : null
452
+ ]
453
+ });
454
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
455
+ className: props.className,
456
+ "data-polyx-signin": "credentials",
457
+ onSubmit,
458
+ children: [
459
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
460
+ className: "polyx-signin__field",
461
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: labels.emailLabel }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
462
+ type: "email",
463
+ name: "email",
464
+ autoComplete: "username",
465
+ required: true,
466
+ value: email,
467
+ disabled: submitting,
468
+ onChange: (event) => setEmail(event.target.value)
469
+ })]
470
+ }),
471
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
472
+ className: "polyx-signin__field",
473
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: labels.passwordLabel }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
474
+ type: "password",
475
+ name: "password",
476
+ autoComplete: "current-password",
477
+ required: true,
478
+ value: password,
479
+ disabled: submitting,
480
+ onChange: (event) => setPassword(event.target.value)
481
+ })]
482
+ }),
483
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
484
+ role: "alert",
485
+ className: "polyx-signin__error",
486
+ children: error
487
+ }) : null,
488
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
489
+ type: "submit",
490
+ className: "polyx-signin__submit",
491
+ disabled: submitting,
492
+ children: submitting ? labels.submitting : labels.submit
493
+ })
494
+ ]
363
495
  });
364
496
  }
365
497
  //#endregion
package/dist/index.d.cts CHANGED
@@ -191,16 +191,39 @@ declare function Protected({
191
191
  }: ProtectedProps): ReactNode;
192
192
  //#endregion
193
193
  //#region src/components/sign-in.d.ts
194
- interface SignInProps extends SignInOptions {
195
- children?: ReactNode;
194
+ /** Every user-facing string, overridable for i18n / white-labelling. */
195
+ interface SignInLabels {
196
+ emailLabel: string;
197
+ passwordLabel: string;
198
+ submit: string;
199
+ submitting: string;
200
+ chooseWorkspace: string;
201
+ invalidCredentials: string;
202
+ noAccess: string;
203
+ passwordChangeRequired: string;
204
+ genericError: string;
205
+ }
206
+ interface SignInProps {
207
+ /** BFF endpoint backed by `createAuthHandlers().authenticate`. Default `/api/polyx/authenticate`. */
208
+ action?: string;
209
+ /** Where to send the browser after success. Overrides the server's default redirect. */
210
+ afterSignInPath?: string;
211
+ /** Pre-scope the login to a single organization (skips org discovery). */
212
+ organizationCode?: string;
213
+ /** Applied to the root `<form>` / picker container — style via your own CSS. */
196
214
  className?: string;
215
+ labels?: Partial<SignInLabels>;
216
+ /** Fired when the backend requires the user to set a new password before continuing. */
217
+ onPasswordChangeRequired?: (userId: string) => void;
197
218
  }
198
- /** Minimal sign-in trigger — popup by default. Style via `className`/children. */
199
- declare function SignIn({
200
- children,
201
- className,
202
- ...options
203
- }: SignInProps): ReactNode;
219
+ /**
220
+ * The embedded, Clerk-style sign-in form (D2b). Renders email/password inside the
221
+ * consuming app and POSTs to the SDK's BFF (`@poly-x/next` `authenticate`), which
222
+ * runs the credential authorize server-side and seals an httpOnly session — no
223
+ * redirect to a hosted PolyX login page, no tokens or credentials in the browser
224
+ * after submit. Handles the workspace picker (select_tenant) inline.
225
+ */
226
+ declare function SignIn(props: SignInProps): ReactNode;
204
227
  //#endregion
205
228
  //#region src/components/auth-callback.d.ts
206
229
  interface AuthCallbackProps {
@@ -220,4 +243,4 @@ declare function AuthCallback({
220
243
  declare const PACKAGE_NAME = "@poly-x/react";
221
244
  declare const version = "0.0.0";
222
245
  //#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 };
246
+ 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 UseAuth, WebLocksLock, WindowBrowserBridge, createLoginController, useAuth, useAuthCallback, useSession, useUser, version };
package/dist/index.d.mts CHANGED
@@ -191,16 +191,39 @@ declare function Protected({
191
191
  }: ProtectedProps): ReactNode;
192
192
  //#endregion
193
193
  //#region src/components/sign-in.d.ts
194
- interface SignInProps extends SignInOptions {
195
- children?: ReactNode;
194
+ /** Every user-facing string, overridable for i18n / white-labelling. */
195
+ interface SignInLabels {
196
+ emailLabel: string;
197
+ passwordLabel: string;
198
+ submit: string;
199
+ submitting: string;
200
+ chooseWorkspace: string;
201
+ invalidCredentials: string;
202
+ noAccess: string;
203
+ passwordChangeRequired: string;
204
+ genericError: string;
205
+ }
206
+ interface SignInProps {
207
+ /** BFF endpoint backed by `createAuthHandlers().authenticate`. Default `/api/polyx/authenticate`. */
208
+ action?: string;
209
+ /** Where to send the browser after success. Overrides the server's default redirect. */
210
+ afterSignInPath?: string;
211
+ /** Pre-scope the login to a single organization (skips org discovery). */
212
+ organizationCode?: string;
213
+ /** Applied to the root `<form>` / picker container — style via your own CSS. */
196
214
  className?: string;
215
+ labels?: Partial<SignInLabels>;
216
+ /** Fired when the backend requires the user to set a new password before continuing. */
217
+ onPasswordChangeRequired?: (userId: string) => void;
197
218
  }
198
- /** Minimal sign-in trigger — popup by default. Style via `className`/children. */
199
- declare function SignIn({
200
- children,
201
- className,
202
- ...options
203
- }: SignInProps): ReactNode;
219
+ /**
220
+ * The embedded, Clerk-style sign-in form (D2b). Renders email/password inside the
221
+ * consuming app and POSTs to the SDK's BFF (`@poly-x/next` `authenticate`), which
222
+ * runs the credential authorize server-side and seals an httpOnly session — no
223
+ * redirect to a hosted PolyX login page, no tokens or credentials in the browser
224
+ * after submit. Handles the workspace picker (select_tenant) inline.
225
+ */
226
+ declare function SignIn(props: SignInProps): ReactNode;
204
227
  //#endregion
205
228
  //#region src/components/auth-callback.d.ts
206
229
  interface AuthCallbackProps {
@@ -220,4 +243,4 @@ declare function AuthCallback({
220
243
  declare const PACKAGE_NAME = "@poly-x/react";
221
244
  declare const version = "0.0.0";
222
245
  //#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 };
246
+ 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 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,146 @@ function Protected({ children, fallback = null, loading = null }) {
351
351
  }
352
352
  //#endregion
353
353
  //#region src/components/sign-in.tsx
354
- /** Minimal sign-in trigger — popup by default. Style via `className`/children. */
355
- function SignIn({ children, className, ...options }) {
356
- const { signIn } = useAuth();
357
- return /* @__PURE__ */ jsx("button", {
358
- type: "button",
359
- className,
360
- onClick: () => void signIn(options),
361
- children: children ?? "Sign in"
354
+ const DEFAULT_LABELS = {
355
+ emailLabel: "Email",
356
+ passwordLabel: "Password",
357
+ submit: "Sign in",
358
+ submitting: "Signing in…",
359
+ chooseWorkspace: "Choose a workspace",
360
+ invalidCredentials: "Invalid email or password.",
361
+ noAccess: "You don't have access to this application.",
362
+ passwordChangeRequired: "You must reset your password before you can sign in.",
363
+ genericError: "Something went wrong. Please try again."
364
+ };
365
+ /**
366
+ * The embedded, Clerk-style sign-in form (D2b). Renders email/password inside the
367
+ * consuming app and POSTs to the SDK's BFF (`@poly-x/next` `authenticate`), which
368
+ * runs the credential authorize server-side and seals an httpOnly session — no
369
+ * redirect to a hosted PolyX login page, no tokens or credentials in the browser
370
+ * after submit. Handles the workspace picker (select_tenant) inline.
371
+ */
372
+ function SignIn(props) {
373
+ const action = props.action ?? "/api/polyx/authenticate";
374
+ const labels = {
375
+ ...DEFAULT_LABELS,
376
+ ...props.labels
377
+ };
378
+ const [email, setEmail] = useState("");
379
+ const [password, setPassword] = useState("");
380
+ const [tenants, setTenants] = useState(null);
381
+ const [submitting, setSubmitting] = useState(false);
382
+ const [error, setError] = useState(null);
383
+ async function authenticate(tenantId) {
384
+ setSubmitting(true);
385
+ setError(null);
386
+ try {
387
+ const body = await (await fetch(action, {
388
+ method: "POST",
389
+ headers: { "content-type": "application/json" },
390
+ body: JSON.stringify({
391
+ email,
392
+ password,
393
+ organizationCode: props.organizationCode,
394
+ tenantId
395
+ })
396
+ })).json().catch(() => ({}));
397
+ switch (body.status) {
398
+ case "success":
399
+ window.location.assign(props.afterSignInPath ?? body.redirectTo ?? "/");
400
+ return;
401
+ case "select_tenant":
402
+ setTenants(body.tenants ?? []);
403
+ return;
404
+ case "invalid_credentials":
405
+ setError(labels.invalidCredentials);
406
+ return;
407
+ case "no_access":
408
+ setError(labels.noAccess);
409
+ return;
410
+ case "password_change_required":
411
+ setError(labels.passwordChangeRequired);
412
+ if (body.userId) props.onPasswordChangeRequired?.(body.userId);
413
+ return;
414
+ default:
415
+ setError(labels.genericError);
416
+ return;
417
+ }
418
+ } catch {
419
+ setError(labels.genericError);
420
+ } finally {
421
+ setSubmitting(false);
422
+ }
423
+ }
424
+ function onSubmit(event) {
425
+ event.preventDefault();
426
+ authenticate();
427
+ }
428
+ if (tenants) return /* @__PURE__ */ jsxs("div", {
429
+ className: props.className,
430
+ "data-polyx-signin": "select-tenant",
431
+ children: [
432
+ /* @__PURE__ */ jsx("p", {
433
+ className: "polyx-signin__heading",
434
+ children: labels.chooseWorkspace
435
+ }),
436
+ /* @__PURE__ */ jsx("ul", {
437
+ className: "polyx-signin__tenants",
438
+ children: tenants.map((tenant) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
439
+ type: "button",
440
+ className: "polyx-signin__tenant",
441
+ disabled: submitting,
442
+ onClick: () => void authenticate(tenant.tenantId),
443
+ children: tenant.name ?? tenant.slug ?? tenant.organizationCode ?? tenant.tenantId
444
+ }) }, tenant.tenantId))
445
+ }),
446
+ error ? /* @__PURE__ */ jsx("p", {
447
+ role: "alert",
448
+ className: "polyx-signin__error",
449
+ children: error
450
+ }) : null
451
+ ]
452
+ });
453
+ return /* @__PURE__ */ jsxs("form", {
454
+ className: props.className,
455
+ "data-polyx-signin": "credentials",
456
+ onSubmit,
457
+ children: [
458
+ /* @__PURE__ */ jsxs("label", {
459
+ className: "polyx-signin__field",
460
+ children: [/* @__PURE__ */ jsx("span", { children: labels.emailLabel }), /* @__PURE__ */ jsx("input", {
461
+ type: "email",
462
+ name: "email",
463
+ autoComplete: "username",
464
+ required: true,
465
+ value: email,
466
+ disabled: submitting,
467
+ onChange: (event) => setEmail(event.target.value)
468
+ })]
469
+ }),
470
+ /* @__PURE__ */ jsxs("label", {
471
+ className: "polyx-signin__field",
472
+ children: [/* @__PURE__ */ jsx("span", { children: labels.passwordLabel }), /* @__PURE__ */ jsx("input", {
473
+ type: "password",
474
+ name: "password",
475
+ autoComplete: "current-password",
476
+ required: true,
477
+ value: password,
478
+ disabled: submitting,
479
+ onChange: (event) => setPassword(event.target.value)
480
+ })]
481
+ }),
482
+ error ? /* @__PURE__ */ jsx("p", {
483
+ role: "alert",
484
+ className: "polyx-signin__error",
485
+ children: error
486
+ }) : null,
487
+ /* @__PURE__ */ jsx("button", {
488
+ type: "submit",
489
+ className: "polyx-signin__submit",
490
+ disabled: submitting,
491
+ children: submitting ? labels.submitting : labels.submit
492
+ })
493
+ ]
362
494
  });
363
495
  }
364
496
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poly-x/react",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.2",
4
4
  "description": "PolyX SDK for React SPAs - provider, hooks, drop-in auth UI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -24,7 +24,7 @@
24
24
  "module": "./dist/index.mjs",
25
25
  "types": "./dist/index.d.cts",
26
26
  "dependencies": {
27
- "@poly-x/core": "0.1.0-alpha.1"
27
+ "@poly-x/core": "0.1.0-alpha.2"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "react": ">=18",