@elvix.is/sdk 0.5.1 → 0.5.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/react.d.ts CHANGED
@@ -20,11 +20,59 @@ declare function ElvixCard({ title, footer, className, children, }: {
20
20
  children: ReactNode;
21
21
  }): react.JSX.Element;
22
22
 
23
+ /**
24
+ * Editable sign-in copy.
25
+ *
26
+ * Every user-facing string in the sign-in surface is overridable. Precedence,
27
+ * lowest to highest:
28
+ *
29
+ * built-in English defaults < Console-configured (bootstrap `strings`) < `copy` prop
30
+ *
31
+ * So an integrating developer edits copy in the elvix Console (no redeploy) and
32
+ * can still override per-embed in code. `title` and `submitButton` are left out
33
+ * of the defaults because their built-in value depends on the Application's
34
+ * sign-in verb ("Sign in" vs "Log in"); the component fills them from the verb
35
+ * when neither Console nor prop sets them.
36
+ *
37
+ * Strings may contain `{app}` / `{email}` tokens; `fillCopy` interpolates them.
38
+ */
39
+ type ElvixCopy = {
40
+ /** Heading. Token: {app}. Built-in: "Sign in to {app}" / "Log in to {app}". */
41
+ title?: string;
42
+ /** Subtitle under the heading. */
43
+ subtitle?: string;
44
+ /** Google factor button. */
45
+ googleButton?: string;
46
+ /** Email field placeholder. */
47
+ emailPlaceholder?: string;
48
+ /** Email submit button (identify step). */
49
+ sendCodeButton?: string;
50
+ /** Email submit button while the request is in flight. */
51
+ sendingLabel?: string;
52
+ /** Code step subtitle. Token: {email}. */
53
+ codeSentSubtitle?: string;
54
+ /** OTP field placeholder. */
55
+ codePlaceholder?: string;
56
+ /** OTP submit button. Built-in: the sign-in verb. */
57
+ submitButton?: string;
58
+ /** OTP submit button while verifying. */
59
+ verifyingLabel?: string;
60
+ /** Terminal "done" pane text. */
61
+ signedInText?: string;
62
+ /** Validation: empty email. */
63
+ errorEnterEmail?: string;
64
+ /** Validation: code not 6 digits. */
65
+ errorEnterCode?: string;
66
+ };
67
+ /** Built-in English defaults for the verb-independent strings. */
68
+ declare const DEFAULT_COPY: ElvixCopy;
69
+
23
70
  /**
24
71
  * Public types for the React surface. Mirrors the elvix.is bootstrap
25
72
  * envelope so customers can type their host code without importing
26
73
  * private elvix internals.
27
74
  */
75
+
28
76
  type ElvixBrand = {
29
77
  light: {
30
78
  primary: string;
@@ -60,6 +108,12 @@ type ElvixBootstrapEnvelope = {
60
108
  };
61
109
  signInVerb: "signin" | "login";
62
110
  signinGate: "public" | "private_beta" | "closed";
111
+ /**
112
+ * Console-configured sign-in copy overrides. Any subset of the strings the
113
+ * sign-in surface renders; missing keys fall back to the built-in English
114
+ * defaults. A `copy` prop on the component overrides these in turn.
115
+ */
116
+ strings?: Partial<ElvixCopy>;
63
117
  };
64
118
  type ElvixSignInResultOk = {
65
119
  ok: true;
@@ -119,13 +173,63 @@ declare function ElvixProvider({ clientId, theme, brand, baseUrl, children, clas
119
173
  * exposes. Hosts navigate from the callback; this component never
120
174
  * calls `router.push` itself.
121
175
  */
122
- declare function ElvixSignIn({ onResult, redirectAfterSignIn, className, }: {
176
+ declare function ElvixSignIn({ onResult, redirectAfterSignIn, copy: copyProp, className, }: {
123
177
  onResult?: (r: ElvixSignInResult) => void;
124
178
  /** Default redirect target on success when the server doesn't echo one. */
125
179
  redirectAfterSignIn?: string;
180
+ /**
181
+ * Thin per-embed copy override. The primary way to edit copy is the elvix
182
+ * Console (served live in the bootstrap `strings`); this prop just lets a
183
+ * single embed tweak a string or two without a Console change.
184
+ */
185
+ copy?: Partial<ElvixCopy>;
126
186
  className?: string;
127
187
  }): react.JSX.Element;
128
188
 
189
+ type ElvixSignInButtonSize = "sm" | "md" | "lg";
190
+ type ElvixSignInButtonTheme = "light" | "dark" | "auto";
191
+ type ElvixSignInButtonVariant = "filled" | "filled-black" | "white" | "outline" | "ghost";
192
+ type ElvixSignInButtonShape = "rectangle" | "pill" | "square" | "circle";
193
+ type ElvixSignInButtonType = "standard" | "icon";
194
+ type ElvixSignInButtonMode = "redirect" | "callback" | "embed";
195
+ type ElvixSignInPreset = "sign-in-with-elvix" | "continue-with-elvix" | "sign-up-with-elvix" | "sign-in" | "log-in" | "continue";
196
+ type ElvixSignInButtonProps = {
197
+ clientId?: string;
198
+ /** elvix origin for redirect mode. Defaults to https://elvix.is. */
199
+ baseUrl?: string;
200
+ returnUrl?: string;
201
+ type?: ElvixSignInButtonType;
202
+ variant?: ElvixSignInButtonVariant;
203
+ shape?: ElvixSignInButtonShape;
204
+ size?: ElvixSignInButtonSize;
205
+ theme?: ElvixSignInButtonTheme;
206
+ preset?: ElvixSignInPreset;
207
+ label?: string;
208
+ className?: string;
209
+ href?: string;
210
+ mode?: ElvixSignInButtonMode;
211
+ onClick?: () => void;
212
+ /** Terminal outcome of mode="embed": success (with token) or error. */
213
+ onResult?: (result: ElvixSignInResult) => void;
214
+ };
215
+ declare function ElvixSignInButton({ clientId, baseUrl, returnUrl, type, variant, shape, size, theme, preset, label, className, href, mode, onClick, onResult, }: ElvixSignInButtonProps): react.JSX.Element;
216
+
217
+ type ElvixSecuredBadgeVariant = "white" | "dark" | "outline";
218
+ type ElvixSecuredBadgeSize = "sm" | "md" | "lg";
219
+ type ElvixSecuredBadgeTheme = "light" | "dark";
220
+ type ElvixSecuredBadgeProps = {
221
+ variant?: ElvixSecuredBadgeVariant;
222
+ size?: ElvixSecuredBadgeSize;
223
+ /** Which side of the colour wheel the host page is on (for the outline variant). */
224
+ theme?: ElvixSecuredBadgeTheme;
225
+ /** Active-protection dot colour. Defaults to brand lavender. */
226
+ accentColor?: string;
227
+ /** Where the badge links. Defaults to elvix. */
228
+ href?: string;
229
+ className?: string;
230
+ };
231
+ declare function ElvixSecuredBadge({ variant, size, theme, accentColor, href, className, }: ElvixSecuredBadgeProps): react.JSX.Element;
232
+
129
233
  /**
130
234
  * Session token store for cross-origin SDK use.
131
235
  *
@@ -280,4 +384,4 @@ declare function ElvixLegalEntities({ onResult, }: {
280
384
  onResult?: (r: ElvixActionResult) => void;
281
385
  }): react.JSX.Element;
282
386
 
283
- export { ElvixActionResult, ElvixAddressBook, ElvixAvatar, ElvixBanner, type ElvixBootstrapEnvelope, type ElvixBrand, ElvixCard, ElvixDeactivate, ElvixExport, ElvixIdentityForm, ElvixLanguages, ElvixLeave, ElvixLegalEntities, ElvixLifecycleWatcher, ElvixProvider, ElvixRegion, ElvixSessions, ElvixSignIn, type ElvixSignInMethod, type ElvixSignInResult, type ElvixSignInResultErr, type ElvixSignInResultOk, type ElvixTheme, ElvixUsername, type UseUserListResult, getElvixToken, setElvixToken, useElvixApp, useElvixContext, useUserMemberships, useUserRoles, useUserScopes };
387
+ export { DEFAULT_COPY, ElvixActionResult, ElvixAddressBook, ElvixAvatar, ElvixBanner, type ElvixBootstrapEnvelope, type ElvixBrand, ElvixCard, type ElvixCopy, ElvixDeactivate, ElvixExport, ElvixIdentityForm, ElvixLanguages, ElvixLeave, ElvixLegalEntities, ElvixLifecycleWatcher, ElvixProvider, ElvixRegion, ElvixSecuredBadge, ElvixSessions, ElvixSignIn, ElvixSignInButton, type ElvixSignInMethod, type ElvixSignInResult, type ElvixSignInResultErr, type ElvixSignInResultOk, type ElvixTheme, ElvixUsername, type UseUserListResult, getElvixToken, setElvixToken, useElvixApp, useElvixContext, useUserMemberships, useUserRoles, useUserScopes };
package/dist/react.js CHANGED
@@ -167,6 +167,42 @@ function withAlpha(hex, a) {
167
167
  // src/react/elvix-sign-in.tsx
168
168
  import { useState as useState2 } from "react";
169
169
 
170
+ // src/react/copy.ts
171
+ var DEFAULT_COPY = {
172
+ subtitle: "Pick how you want to continue.",
173
+ googleButton: "Continue with Google",
174
+ emailPlaceholder: "you@example.com",
175
+ sendCodeButton: "Send code",
176
+ sendingLabel: "Sending\u2026",
177
+ codeSentSubtitle: "We sent a 6-digit code to {email}.",
178
+ codePlaceholder: "123456",
179
+ verifyingLabel: "Verifying\u2026",
180
+ signedInText: "Signed in.",
181
+ errorEnterEmail: "Enter an email.",
182
+ errorEnterCode: "Enter the 6-digit code."
183
+ };
184
+ function resolveCopy(bootstrap, prop) {
185
+ return {
186
+ ...DEFAULT_COPY,
187
+ ...stripUndefined(bootstrap),
188
+ ...stripUndefined(prop)
189
+ };
190
+ }
191
+ function fillCopy(template, tokens) {
192
+ return template.replace(
193
+ /\{(\w+)\}/g,
194
+ (whole, key) => key in tokens ? tokens[key] : whole
195
+ );
196
+ }
197
+ function stripUndefined(o) {
198
+ if (!o) return {};
199
+ const out = {};
200
+ for (const [k, v] of Object.entries(o)) {
201
+ if (v !== void 0) out[k] = v;
202
+ }
203
+ return out;
204
+ }
205
+
170
206
  // src/react/session.ts
171
207
  var STORAGE_KEY = "elvix.session.token";
172
208
  var memToken = null;
@@ -207,10 +243,12 @@ function isSameOrigin(baseUrl) {
207
243
  function ElvixSignIn({
208
244
  onResult,
209
245
  redirectAfterSignIn,
246
+ copy: copyProp,
210
247
  className = ""
211
248
  }) {
212
249
  const ctx = useElvixContext();
213
250
  const app = useElvixApp();
251
+ const copy = resolveCopy(app?.strings, copyProp);
214
252
  const [step, setStep] = useState2("identify");
215
253
  const [email, setEmail] = useState2("");
216
254
  const [code, setCode] = useState2("");
@@ -218,6 +256,9 @@ function ElvixSignIn({
218
256
  const [busy, setBusy] = useState2(false);
219
257
  const [error, setError] = useState2(null);
220
258
  const verb = app?.signInVerb === "login" ? "Log in" : "Sign in";
259
+ const defaultTitle = app?.appName ? `${verb} to ${app.appName}` : verb;
260
+ const title = copy.title ? fillCopy(copy.title, { app: app?.appName ?? "" }) : defaultTitle;
261
+ const submitLabel = copy.submitButton ?? verb;
221
262
  function fail(error2, message) {
222
263
  setError(message ?? error2);
223
264
  onResult?.({ ok: false, error: error2, message });
@@ -230,7 +271,7 @@ function ElvixSignIn({
230
271
  }
231
272
  async function startOtp(e) {
232
273
  e.preventDefault();
233
- if (!email.trim()) return fail("invalid_input", "Enter an email.");
274
+ if (!email.trim()) return fail("invalid_input", copy.errorEnterEmail);
234
275
  setBusy(true);
235
276
  setError(null);
236
277
  try {
@@ -259,7 +300,7 @@ function ElvixSignIn({
259
300
  async function verifyOtp(e) {
260
301
  e.preventDefault();
261
302
  if (!challengeId) return;
262
- if (code.trim().length !== 6) return fail("invalid_input", "Enter the 6-digit code.");
303
+ if (code.trim().length !== 6) return fail("invalid_input", copy.errorEnterCode);
263
304
  setBusy(true);
264
305
  setError(null);
265
306
  try {
@@ -289,9 +330,9 @@ function ElvixSignIn({
289
330
  }
290
331
  const card = `elvix-card ${className}`.trim();
291
332
  if (step === "done") {
292
- return /* @__PURE__ */ React.createElement("div", { className: card, "data-elvix-pane": "done" }, /* @__PURE__ */ React.createElement("p", null, "Signed in."));
333
+ return /* @__PURE__ */ React.createElement("div", { className: card, "data-elvix-pane": "done" }, /* @__PURE__ */ React.createElement("p", null, copy.signedInText));
293
334
  }
294
- return /* @__PURE__ */ React.createElement("div", { className: card, "data-elvix-pane": step }, /* @__PURE__ */ React.createElement("h2", { className: "elvix-h" }, app?.appName ? `${verb} to ${app.appName}` : verb), step === "identify" && /* @__PURE__ */ React.createElement(React.Fragment, null, app?.methods.google && /* @__PURE__ */ React.createElement(
335
+ return /* @__PURE__ */ React.createElement("div", { className: card, "data-elvix-pane": step }, /* @__PURE__ */ React.createElement("h2", { className: "elvix-h" }, title), copy.subtitle && /* @__PURE__ */ React.createElement("p", { className: "elvix-muted elvix-subtitle" }, copy.subtitle), step === "identify" && /* @__PURE__ */ React.createElement(React.Fragment, null, app?.methods.google && /* @__PURE__ */ React.createElement(
295
336
  "button",
296
337
  {
297
338
  type: "button",
@@ -300,19 +341,19 @@ function ElvixSignIn({
300
341
  className: "elvix-btn elvix-btn-google",
301
342
  "data-elvix-method": "google"
302
343
  },
303
- "Continue with Google"
344
+ copy.googleButton
304
345
  ), app?.methods.emailOtp && /* @__PURE__ */ React.createElement("form", { onSubmit: startOtp, "data-elvix-method": "email_otp", className: "elvix-otp-form" }, /* @__PURE__ */ React.createElement(
305
346
  "input",
306
347
  {
307
348
  type: "email",
308
349
  value: email,
309
350
  onChange: (ev) => setEmail(ev.target.value),
310
- placeholder: "you@example.com",
351
+ placeholder: copy.emailPlaceholder,
311
352
  required: true,
312
353
  disabled: busy,
313
354
  className: "elvix-input"
314
355
  }
315
- ), /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: busy, className: "elvix-btn elvix-btn-primary" }, busy ? "Sending\u2026" : "Send code"))), step === "code" && /* @__PURE__ */ React.createElement("form", { onSubmit: verifyOtp, className: "elvix-otp-form" }, /* @__PURE__ */ React.createElement("p", { className: "elvix-muted" }, "We sent a 6-digit code to ", /* @__PURE__ */ React.createElement("strong", null, email), "."), /* @__PURE__ */ React.createElement(
356
+ ), /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: busy, className: "elvix-btn elvix-btn-primary" }, busy ? copy.sendingLabel : copy.sendCodeButton))), step === "code" && /* @__PURE__ */ React.createElement("form", { onSubmit: verifyOtp, className: "elvix-otp-form" }, /* @__PURE__ */ React.createElement("p", { className: "elvix-muted" }, fillCopy(copy.codeSentSubtitle ?? "", { email })), /* @__PURE__ */ React.createElement(
316
357
  "input",
317
358
  {
318
359
  type: "text",
@@ -321,22 +362,216 @@ function ElvixSignIn({
321
362
  maxLength: 6,
322
363
  value: code,
323
364
  onChange: (ev) => setCode(ev.target.value.replace(/\D/g, "")),
324
- placeholder: "123456",
365
+ placeholder: copy.codePlaceholder,
325
366
  required: true,
326
367
  disabled: busy,
327
368
  className: "elvix-input"
328
369
  }
329
- ), /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: busy, className: "elvix-btn elvix-btn-primary" }, busy ? "Verifying\u2026" : verb)), error && /* @__PURE__ */ React.createElement("p", { role: "alert", className: "elvix-error" }, error));
370
+ ), /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: busy, className: "elvix-btn elvix-btn-primary" }, busy ? copy.verifyingLabel : submitLabel)), error && /* @__PURE__ */ React.createElement("p", { role: "alert", className: "elvix-error" }, error));
371
+ }
372
+
373
+ // src/react/elvix-sign-in-button.tsx
374
+ import { useState as useState3 } from "react";
375
+
376
+ // src/react/elvix-shield.tsx
377
+ function ElvixShield({
378
+ size,
379
+ fill,
380
+ accent
381
+ }) {
382
+ return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "2 2 20 20", "aria-hidden": true, style: { display: "block" } }, /* @__PURE__ */ React.createElement(
383
+ "path",
384
+ {
385
+ fill,
386
+ fillRule: "evenodd",
387
+ d: "M 6 2.5 C 4.34 2.5 3 3.84 3 5.5 L 3 12.5 C 3 17.5 7 20.7 12 22 C 17 20.7 21 17.5 21 12.5 L 21 5.5 C 21 3.84 19.66 2.5 18 2.5 L 6 2.5 Z M 12 8.4 C 9.79 8.4 8 10.19 8 12.4 C 8 14.61 9.79 16.4 12 16.4 C 13.21 16.4 14.3 15.86 15.04 15 L 13.6 13.77 C 13.21 14.23 12.64 14.5 12 14.5 C 11.04 14.5 10.21 13.86 9.91 13 L 15.95 13 C 15.98 12.8 16 12.6 16 12.4 C 16 10.19 14.21 8.4 12 8.4 Z M 9.91 11.8 L 14.09 11.8 C 13.79 10.94 12.96 10.3 12 10.3 C 11.04 10.3 10.21 10.94 9.91 11.8 Z"
388
+ }
389
+ ), /* @__PURE__ */ React.createElement("circle", { cx: "19.5", cy: "4.5", r: "2.4", fill: accent }));
390
+ }
391
+
392
+ // src/react/elvix-sign-in-button.tsx
393
+ var ELVIX_URL = "https://elvix.is";
394
+ var PRESET_LABEL = {
395
+ "sign-in-with-elvix": "Sign in with elvix",
396
+ "continue-with-elvix": "Continue with elvix",
397
+ "sign-up-with-elvix": "Sign up with elvix",
398
+ "sign-in": "Sign in",
399
+ "log-in": "Log in",
400
+ continue: "Continue"
401
+ };
402
+ var SIZE_STANDARD = {
403
+ sm: { height: 36, padX: 12, font: 14, gap: 8 },
404
+ md: { height: 40, padX: 12, font: 14, gap: 10 },
405
+ lg: { height: 48, padX: 16, font: 15, gap: 12 }
406
+ };
407
+ var SIZE_ICON = { sm: 36, md: 40, lg: 48 };
408
+ var ICON_SIZE = { sm: 18, md: 20, lg: 22 };
409
+ var RADIUS = { rectangle: 10, pill: 9999, square: 10, circle: 9999 };
410
+ function variantTone(variant, theme) {
411
+ const dark = theme === "dark" || theme === "auto";
412
+ switch (variant) {
413
+ case "filled":
414
+ return { bg: "#6c5ce7", color: "#fff", border: "1px solid rgba(0,0,0,0.1)", shadow: "0 4px 16px -4px rgba(108,92,231,0.45)" };
415
+ case "filled-black":
416
+ return { bg: "#0a0a0b", color: "#fff", border: `1px solid ${dark ? "rgba(255,255,255,0.12)" : "rgba(0,0,0,0.1)"}` };
417
+ case "white":
418
+ return { bg: "#fff", color: "#0a0a0b", border: `1px solid ${dark ? "transparent" : "#e4e4e7"}` };
419
+ case "outline":
420
+ return dark ? { bg: "transparent", color: "#fff", border: "1px solid rgba(142,125,255,0.4)" } : { bg: "#fff", color: "#0a0a0b", border: "1px solid rgba(0,0,0,0.15)" };
421
+ case "ghost":
422
+ return { bg: "transparent", color: dark ? "#fff" : "#0a0a0b", border: "1px solid transparent" };
423
+ }
424
+ }
425
+ function shieldColor(variant, theme) {
426
+ if (variant === "filled" || variant === "filled-black") return "#ffffff";
427
+ if (variant === "white") return "#0a0a0b";
428
+ return theme === "light" ? "#0a0a0b" : "#ffffff";
429
+ }
430
+ function ElvixSignInButton({
431
+ clientId,
432
+ baseUrl = ELVIX_URL,
433
+ returnUrl,
434
+ type = "standard",
435
+ variant = "filled",
436
+ shape = "rectangle",
437
+ size = "md",
438
+ theme = "dark",
439
+ preset = "sign-in-with-elvix",
440
+ label,
441
+ className,
442
+ href,
443
+ mode = "redirect",
444
+ onClick,
445
+ onResult
446
+ }) {
447
+ const [embedOpen, setEmbedOpen] = useState3(false);
448
+ const isIcon = type === "icon";
449
+ const resolvedLabel = label ?? PRESET_LABEL[preset];
450
+ const tone = variantTone(variant, theme);
451
+ const std = SIZE_STANDARD[size];
452
+ const effectiveShape = isIcon ? shape === "pill" || shape === "circle" ? "circle" : "square" : shape;
453
+ const style = {
454
+ display: "inline-flex",
455
+ alignItems: "center",
456
+ justifyContent: "center",
457
+ fontWeight: 500,
458
+ fontSize: std.font,
459
+ cursor: "pointer",
460
+ userSelect: "none",
461
+ textDecoration: "none",
462
+ borderRadius: RADIUS[effectiveShape],
463
+ background: tone.bg,
464
+ color: tone.color,
465
+ border: tone.border,
466
+ boxShadow: tone.shadow,
467
+ transition: "background 0.15s, border-color 0.15s",
468
+ ...isIcon ? { height: SIZE_ICON[size], width: SIZE_ICON[size] } : { height: std.height, paddingLeft: std.padX, paddingRight: std.padX, gap: std.gap }
469
+ };
470
+ const content = /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ElvixShield, { size: ICON_SIZE[size], fill: shieldColor(variant, theme), accent: "#8e7dff" }), isIcon ? null : /* @__PURE__ */ React.createElement("span", null, resolvedLabel));
471
+ if (mode === "callback") {
472
+ return /* @__PURE__ */ React.createElement("button", { type: "button", onClick, className, style, "aria-label": isIcon ? resolvedLabel : void 0 }, content);
473
+ }
474
+ if (mode === "embed") {
475
+ return /* @__PURE__ */ React.createElement("div", { "data-elvix-signin-button-embed": "" }, !embedOpen && /* @__PURE__ */ React.createElement(
476
+ "button",
477
+ {
478
+ type: "button",
479
+ onClick: () => setEmbedOpen(true),
480
+ className,
481
+ style,
482
+ "aria-label": isIcon ? resolvedLabel : void 0
483
+ },
484
+ content
485
+ ), embedOpen && /* @__PURE__ */ React.createElement(
486
+ ElvixSignIn,
487
+ {
488
+ onResult: (r) => {
489
+ onResult?.(r);
490
+ }
491
+ }
492
+ ));
493
+ }
494
+ const destination = (() => {
495
+ if (href) return href;
496
+ const base = clientId ? `${baseUrl}/sign-in/${clientId}` : `${baseUrl}/sign-in`;
497
+ if (!returnUrl) return base;
498
+ const sep = base.includes("?") ? "&" : "?";
499
+ return `${base}${sep}return=${encodeURIComponent(returnUrl)}`;
500
+ })();
501
+ return /* @__PURE__ */ React.createElement("a", { href: destination, className, style, "aria-label": isIcon ? resolvedLabel : void 0 }, content);
502
+ }
503
+
504
+ // src/react/elvix-secured-badge.tsx
505
+ var ELVIX_URL2 = "https://elvix.is";
506
+ var SIZE = {
507
+ sm: { height: 28, padX: 10, font: 11.5, icon: 14, gap: 6 },
508
+ md: { height: 32, padX: 12, font: 12.5, icon: 16, gap: 7 },
509
+ lg: { height: 36, padX: 14, font: 13, icon: 18, gap: 8 }
510
+ };
511
+ var TONE = {
512
+ white: {
513
+ light: { bg: "#ffffff", border: "#e4e4e7", lead: "#71717a", brand: "#0a0a0b", shield: "#0a0a0b" },
514
+ dark: { bg: "#ffffff", border: "transparent", lead: "#71717a", brand: "#0a0a0b", shield: "#0a0a0b" }
515
+ },
516
+ dark: {
517
+ light: { bg: "#0a0a0b", border: "rgba(0,0,0,0.1)", lead: "#d4d4d8", brand: "#ffffff", shield: "#ffffff" },
518
+ dark: { bg: "#0a0a0b", border: "rgba(255,255,255,0.1)", lead: "#d4d4d8", brand: "#ffffff", shield: "#ffffff" }
519
+ },
520
+ outline: {
521
+ light: { bg: "transparent", border: "rgba(0,0,0,0.15)", lead: "#71717a", brand: "#0a0a0b", shield: "#0a0a0b" },
522
+ dark: { bg: "transparent", border: "rgba(142,125,255,0.4)", lead: "#d4d4d8", brand: "#ffffff", shield: "#ffffff" }
523
+ }
524
+ };
525
+ function ElvixSecuredBadge({
526
+ variant = "white",
527
+ size = "md",
528
+ theme = "dark",
529
+ accentColor = "#8e7dff",
530
+ href = ELVIX_URL2,
531
+ className = ""
532
+ }) {
533
+ const s = SIZE[size];
534
+ const t = TONE[variant][theme];
535
+ const style = {
536
+ display: "inline-flex",
537
+ alignItems: "center",
538
+ gap: s.gap,
539
+ height: s.height,
540
+ paddingLeft: s.padX,
541
+ paddingRight: s.padX,
542
+ fontSize: s.font,
543
+ fontWeight: 500,
544
+ borderRadius: 9999,
545
+ background: t.bg,
546
+ border: `1px solid ${t.border}`,
547
+ color: t.brand,
548
+ textDecoration: "none",
549
+ userSelect: "none",
550
+ lineHeight: 1
551
+ };
552
+ return /* @__PURE__ */ React.createElement(
553
+ "a",
554
+ {
555
+ href,
556
+ target: "_blank",
557
+ rel: "noopener noreferrer",
558
+ className,
559
+ style,
560
+ "data-elvix-secured-badge": ""
561
+ },
562
+ /* @__PURE__ */ React.createElement(ElvixShield, { size: s.icon, fill: t.shield, accent: accentColor }),
563
+ /* @__PURE__ */ React.createElement("span", { style: { display: "inline-flex", alignItems: "baseline", gap: 4 } }, /* @__PURE__ */ React.createElement("span", { style: { color: t.lead } }, "Secured by"), /* @__PURE__ */ React.createElement("span", { style: { color: t.brand, fontWeight: 600 } }, "elvix"))
564
+ );
330
565
  }
331
566
 
332
567
  // src/react/hooks.ts
333
- import { useCallback, useEffect as useEffect2, useState as useState3 } from "react";
568
+ import { useCallback, useEffect as useEffect2, useState as useState4 } from "react";
334
569
  var POLL_MS = 7e3;
335
570
  function useUserList(kind, opts) {
336
571
  const { applicationId, baseUrl = "", pollMs = POLL_MS } = opts;
337
- const [slugs, setSlugs] = useState3([]);
338
- const [loading, setLoading] = useState3(true);
339
- const [error, setError] = useState3(null);
572
+ const [slugs, setSlugs] = useState4([]);
573
+ const [loading, setLoading] = useState4(true);
574
+ const [error, setError] = useState4(null);
340
575
  const refresh = useCallback(async () => {
341
576
  setError(null);
342
577
  try {
@@ -403,7 +638,7 @@ function ElvixLifecycleWatcher({
403
638
  }
404
639
 
405
640
  // src/react/elvix-username.tsx
406
- import { useState as useState4 } from "react";
641
+ import { useState as useState5 } from "react";
407
642
 
408
643
  // src/react/lib.ts
409
644
  async function appPost(opts, path, body) {
@@ -465,10 +700,10 @@ function ElvixUsername({
465
700
  onResult
466
701
  }) {
467
702
  const ctx = useElvixContext();
468
- const [value, setValue] = useState4("");
469
- const [busy, setBusy] = useState4(false);
470
- const [error, setError] = useState4(null);
471
- const [done, setDone] = useState4(null);
703
+ const [value, setValue] = useState5("");
704
+ const [busy, setBusy] = useState5(false);
705
+ const [error, setError] = useState5(null);
706
+ const [done, setDone] = useState5(null);
472
707
  async function submit(e) {
473
708
  e.preventDefault();
474
709
  if (!ctx.app) return;
@@ -506,14 +741,14 @@ function ElvixUsername({
506
741
  }
507
742
 
508
743
  // src/react/elvix-avatar.tsx
509
- import { useState as useState5 } from "react";
744
+ import { useState as useState6 } from "react";
510
745
  function ElvixAvatar({
511
746
  onResult
512
747
  }) {
513
748
  const ctx = useElvixContext();
514
- const [busy, setBusy] = useState5(false);
515
- const [error, setError] = useState5(null);
516
- const [preview, setPreview] = useState5(null);
749
+ const [busy, setBusy] = useState6(false);
750
+ const [error, setError] = useState6(null);
751
+ const [preview, setPreview] = useState6(null);
517
752
  async function onFile(e) {
518
753
  const file = e.target.files?.[0];
519
754
  if (!file || !ctx.app) return;
@@ -547,14 +782,14 @@ function ElvixAvatar({
547
782
  }
548
783
 
549
784
  // src/react/elvix-banner.tsx
550
- import { useState as useState6 } from "react";
785
+ import { useState as useState7 } from "react";
551
786
  function ElvixBanner({
552
787
  onResult
553
788
  }) {
554
789
  const ctx = useElvixContext();
555
- const [busy, setBusy] = useState6(false);
556
- const [error, setError] = useState6(null);
557
- const [preview, setPreview] = useState6(null);
790
+ const [busy, setBusy] = useState7(false);
791
+ const [error, setError] = useState7(null);
792
+ const [preview, setPreview] = useState7(null);
558
793
  async function onFile(e) {
559
794
  const file = e.target.files?.[0];
560
795
  if (!file || !ctx.app) return;
@@ -588,18 +823,18 @@ function ElvixBanner({
588
823
  }
589
824
 
590
825
  // src/react/elvix-identity-form.tsx
591
- import { useState as useState7 } from "react";
826
+ import { useState as useState8 } from "react";
592
827
  function ElvixIdentityForm({
593
828
  initialName = "",
594
829
  initialBio = "",
595
830
  onResult
596
831
  }) {
597
832
  const ctx = useElvixContext();
598
- const [name, setName] = useState7(initialName);
599
- const [bio, setBio] = useState7(initialBio);
600
- const [busy, setBusy] = useState7(false);
601
- const [error, setError] = useState7(null);
602
- const [saved, setSaved] = useState7(false);
833
+ const [name, setName] = useState8(initialName);
834
+ const [bio, setBio] = useState8(initialBio);
835
+ const [busy, setBusy] = useState8(false);
836
+ const [error, setError] = useState8(null);
837
+ const [saved, setSaved] = useState8(false);
603
838
  async function submit(e) {
604
839
  e.preventDefault();
605
840
  if (!ctx.app) return;
@@ -619,18 +854,18 @@ function ElvixIdentityForm({
619
854
  }
620
855
 
621
856
  // src/react/elvix-region.tsx
622
- import { useState as useState8 } from "react";
857
+ import { useState as useState9 } from "react";
623
858
  function ElvixRegion({
624
859
  initialCountry = "",
625
860
  initialTimezone = "",
626
861
  onResult
627
862
  }) {
628
863
  const ctx = useElvixContext();
629
- const [country, setCountry] = useState8(initialCountry);
630
- const [timezone, setTimezone] = useState8(initialTimezone);
631
- const [busy, setBusy] = useState8(false);
632
- const [error, setError] = useState8(null);
633
- const [saved, setSaved] = useState8(false);
864
+ const [country, setCountry] = useState9(initialCountry);
865
+ const [timezone, setTimezone] = useState9(initialTimezone);
866
+ const [busy, setBusy] = useState9(false);
867
+ const [error, setError] = useState9(null);
868
+ const [saved, setSaved] = useState9(false);
634
869
  async function submit(e) {
635
870
  e.preventDefault();
636
871
  if (!ctx.app) return;
@@ -650,16 +885,16 @@ function ElvixRegion({
650
885
  }
651
886
 
652
887
  // src/react/elvix-languages.tsx
653
- import { useState as useState9 } from "react";
888
+ import { useState as useState10 } from "react";
654
889
  function ElvixLanguages({
655
890
  initial = [],
656
891
  onResult
657
892
  }) {
658
893
  const ctx = useElvixContext();
659
- const [raw, setRaw] = useState9(initial.join(", "));
660
- const [busy, setBusy] = useState9(false);
661
- const [error, setError] = useState9(null);
662
- const [saved, setSaved] = useState9(false);
894
+ const [raw, setRaw] = useState10(initial.join(", "));
895
+ const [busy, setBusy] = useState10(false);
896
+ const [error, setError] = useState10(null);
897
+ const [saved, setSaved] = useState10(false);
663
898
  async function submit(e) {
664
899
  e.preventDefault();
665
900
  if (!ctx.app) return;
@@ -680,14 +915,14 @@ function ElvixLanguages({
680
915
  }
681
916
 
682
917
  // src/react/elvix-sessions.tsx
683
- import { useEffect as useEffect4, useState as useState10 } from "react";
918
+ import { useEffect as useEffect4, useState as useState11 } from "react";
684
919
  function ElvixSessions({
685
920
  onResult
686
921
  }) {
687
922
  const ctx = useElvixContext();
688
- const [rows, setRows] = useState10(null);
689
- const [error, setError] = useState10(null);
690
- const [busy, setBusy] = useState10(false);
923
+ const [rows, setRows] = useState11(null);
924
+ const [error, setError] = useState11(null);
925
+ const [busy, setBusy] = useState11(false);
691
926
  useEffect4(() => {
692
927
  if (!ctx.app) return;
693
928
  fetch(`${ctx.baseUrl}/api/account/apps/${ctx.app.applicationId}/sessions`, {
@@ -712,14 +947,14 @@ function ElvixSessions({
712
947
  }
713
948
 
714
949
  // src/react/elvix-export.tsx
715
- import { useState as useState11 } from "react";
950
+ import { useState as useState12 } from "react";
716
951
  function ElvixExport({
717
952
  onResult
718
953
  }) {
719
954
  const ctx = useElvixContext();
720
- const [busy, setBusy] = useState11(false);
721
- const [done, setDone] = useState11(false);
722
- const [error, setError] = useState11(null);
955
+ const [busy, setBusy] = useState12(false);
956
+ const [done, setDone] = useState12(false);
957
+ const [error, setError] = useState12(null);
723
958
  async function start() {
724
959
  if (!ctx.app) return;
725
960
  setBusy(true);
@@ -738,16 +973,16 @@ function ElvixExport({
738
973
  }
739
974
 
740
975
  // src/react/elvix-deactivate.tsx
741
- import { useState as useState12 } from "react";
976
+ import { useState as useState13 } from "react";
742
977
  function ElvixDeactivate({
743
978
  onResult
744
979
  }) {
745
980
  const ctx = useElvixContext();
746
- const [pane, setPane] = useState12("warn");
747
- const [challengeId, setChallengeId] = useState12(null);
748
- const [code, setCode] = useState12("");
749
- const [busy, setBusy] = useState12(false);
750
- const [error, setError] = useState12(null);
981
+ const [pane, setPane] = useState13("warn");
982
+ const [challengeId, setChallengeId] = useState13(null);
983
+ const [code, setCode] = useState13("");
984
+ const [busy, setBusy] = useState13(false);
985
+ const [error, setError] = useState13(null);
751
986
  async function startChallenge() {
752
987
  if (!ctx.app) return;
753
988
  setBusy(true);
@@ -804,16 +1039,16 @@ function ElvixDeactivate({
804
1039
  }
805
1040
 
806
1041
  // src/react/elvix-leave.tsx
807
- import { useState as useState13 } from "react";
1042
+ import { useState as useState14 } from "react";
808
1043
  function ElvixLeave({
809
1044
  onResult
810
1045
  }) {
811
1046
  const ctx = useElvixContext();
812
- const [pane, setPane] = useState13("warn");
813
- const [challengeId, setChallengeId] = useState13(null);
814
- const [code, setCode] = useState13("");
815
- const [busy, setBusy] = useState13(false);
816
- const [error, setError] = useState13(null);
1047
+ const [pane, setPane] = useState14("warn");
1048
+ const [challengeId, setChallengeId] = useState14(null);
1049
+ const [code, setCode] = useState14("");
1050
+ const [busy, setBusy] = useState14(false);
1051
+ const [error, setError] = useState14(null);
817
1052
  async function startChallenge() {
818
1053
  if (!ctx.app) return;
819
1054
  setBusy(true);
@@ -870,16 +1105,16 @@ function ElvixLeave({
870
1105
  }
871
1106
 
872
1107
  // src/react/elvix-address-book.tsx
873
- import { useEffect as useEffect5, useState as useState14 } from "react";
1108
+ import { useEffect as useEffect5, useState as useState15 } from "react";
874
1109
  function ElvixAddressBook({
875
1110
  onResult
876
1111
  }) {
877
1112
  const ctx = useElvixContext();
878
- const [rows, setRows] = useState14(null);
879
- const [error, setError] = useState14(null);
880
- const [busy, setBusy] = useState14(false);
881
- const [adding, setAdding] = useState14(false);
882
- const [form, setForm] = useState14({
1113
+ const [rows, setRows] = useState15(null);
1114
+ const [error, setError] = useState15(null);
1115
+ const [busy, setBusy] = useState15(false);
1116
+ const [adding, setAdding] = useState15(false);
1117
+ const [form, setForm] = useState15({
883
1118
  label: "Home",
884
1119
  line1: "",
885
1120
  postalCode: "",
@@ -932,16 +1167,16 @@ function ElvixAddressBook({
932
1167
  }
933
1168
 
934
1169
  // src/react/elvix-legal-entities.tsx
935
- import { useEffect as useEffect6, useState as useState15 } from "react";
1170
+ import { useEffect as useEffect6, useState as useState16 } from "react";
936
1171
  function ElvixLegalEntities({
937
1172
  onResult
938
1173
  }) {
939
1174
  const ctx = useElvixContext();
940
- const [rows, setRows] = useState15(null);
941
- const [error, setError] = useState15(null);
942
- const [busy, setBusy] = useState15(false);
943
- const [adding, setAdding] = useState15(false);
944
- const [form, setForm] = useState15({
1175
+ const [rows, setRows] = useState16(null);
1176
+ const [error, setError] = useState16(null);
1177
+ const [busy, setBusy] = useState16(false);
1178
+ const [adding, setAdding] = useState16(false);
1179
+ const [form, setForm] = useState16({
945
1180
  legalName: "",
946
1181
  taxId: "",
947
1182
  country: ""
@@ -991,6 +1226,7 @@ function ElvixLegalEntities({
991
1226
  return /* @__PURE__ */ React.createElement(ElvixCard, { title: "Legal entities" }, error && /* @__PURE__ */ React.createElement("p", { role: "alert", className: "elvix-error" }, error), !rows && !error && /* @__PURE__ */ React.createElement("p", null, "Loading\u2026"), rows && rows.length === 0 && /* @__PURE__ */ React.createElement("p", { className: "elvix-muted" }, "No legal entities yet."), rows?.map((e) => /* @__PURE__ */ React.createElement("div", { key: e.id, style: { padding: "8px 0", borderBottom: "1px solid rgba(0,0,0,0.06)", display: "flex", justifyContent: "space-between", gap: 12 } }, /* @__PURE__ */ React.createElement("div", { style: { fontSize: 13 } }, /* @__PURE__ */ React.createElement("div", { style: { fontWeight: 500 } }, e.legalName), /* @__PURE__ */ React.createElement("div", { style: { color: "rgba(0,0,0,0.55)" } }, e.taxId, " \xB7 ", e.country)), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: busy, onClick: () => remove(e.id), className: "elvix-btn elvix-btn-ghost" }, "Remove"))), !adding && /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setAdding(true), className: "elvix-btn elvix-btn-primary", style: { marginTop: 12 } }, "Add entity"), adding && /* @__PURE__ */ React.createElement("form", { onSubmit: add, className: "elvix-form", style: { marginTop: 12 } }, /* @__PURE__ */ React.createElement("input", { value: form.legalName, onChange: (e) => setForm({ ...form, legalName: e.target.value }), placeholder: "Legal name", required: true, className: "elvix-input" }), /* @__PURE__ */ React.createElement("input", { value: form.taxId, onChange: (e) => setForm({ ...form, taxId: e.target.value }), placeholder: "Tax / VAT ID", required: true, className: "elvix-input" }), /* @__PURE__ */ React.createElement("input", { value: form.country, onChange: (e) => setForm({ ...form, country: e.target.value.toUpperCase() }), placeholder: "Country (ISO-2)", maxLength: 2, required: true, className: "elvix-input" }), /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: busy, className: "elvix-btn elvix-btn-primary" }, busy ? "Saving\u2026" : "Save")));
992
1227
  }
993
1228
  export {
1229
+ DEFAULT_COPY,
994
1230
  ElvixAddressBook,
995
1231
  ElvixAvatar,
996
1232
  ElvixBanner,
@@ -1004,8 +1240,10 @@ export {
1004
1240
  ElvixLifecycleWatcher,
1005
1241
  ElvixProvider,
1006
1242
  ElvixRegion,
1243
+ ElvixSecuredBadge,
1007
1244
  ElvixSessions,
1008
1245
  ElvixSignIn,
1246
+ ElvixSignInButton,
1009
1247
  ElvixUsername,
1010
1248
  getElvixToken,
1011
1249
  setElvixToken,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elvix.is/sdk",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Official elvix SDK. Drop-in React components, server helpers, and an MCP server so AI coding agents integrate elvix on the first try.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://elvix.is",