@usethrottle/auth 0.3.1 → 0.5.0

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/styles.css CHANGED
@@ -200,11 +200,13 @@
200
200
  align-items: center;
201
201
  justify-content: center;
202
202
  cursor: pointer;
203
+ /* A label that wraps turns a control into a two-line block of text. */
204
+ white-space: nowrap;
203
205
  transition: opacity var(--throttle-transition), background-color var(--throttle-transition), border-color var(--throttle-transition), filter var(--throttle-transition), box-shadow var(--throttle-transition);
204
206
  }
205
207
  .throttle-button:disabled { opacity: .55; cursor: not-allowed; }
206
208
  .throttle-button:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--throttle-color-background), 0 0 0 4px var(--throttle-color-primary); }
207
- .throttle-button--primary { background: var(--throttle-color-primary); color: var(--throttle-color-primary-foreground); width: 100%; }
209
+ .throttle-button--primary { background: var(--throttle-color-primary); color: var(--throttle-color-primary-foreground); width: var(--throttle-button-width, 100%); }
208
210
  .throttle-button--primary:hover:not(:disabled) { filter: brightness(0.92); }
209
211
  .throttle-button--secondary { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); border-color: var(--throttle-color-border-control); }
210
212
  .throttle-button--secondary:hover:not(:disabled) { background: var(--throttle-color-muted); }
@@ -371,6 +373,10 @@
371
373
  .throttle-addresses { grid-template-columns: 1fr; }
372
374
  .throttle-fieldrow { grid-template-columns: 1fr; }
373
375
  .throttle-tabs__rail { width: 100%; }
376
+ /* One column, and the rail stops sticking — on a phone a sticky summary
377
+ covers the very line items it is summarising. */
378
+ .throttle-detail { grid-template-columns: 1fr; }
379
+ .throttle-detail__rail { position: static; }
374
380
  }
375
381
 
376
382
  .throttle-table { width: 100%; border-collapse: collapse; text-align: left; }
@@ -448,12 +454,50 @@
448
454
  padding: var(--throttle-space-3) 0;
449
455
  }
450
456
  .throttle-rowitem:first-child { border-top: none; padding-top: 0; }
451
- .throttle-actions { display: flex; gap: var(--throttle-space-2); flex-wrap: wrap; }
457
+ .throttle-actions {
458
+ display: flex; gap: var(--throttle-space-2); flex-wrap: wrap; align-items: center;
459
+ /* Primary buttons are full-width so a form's submit fills its card. In a row
460
+ of sibling actions that same rule made every button claim 100% and then
461
+ shrink to share the line, squeezing Resume to 59px so its label wrapped to
462
+ two lines inside a 48px box. A custom property INHERITS to the buttons, so
463
+ the row opts them back to content width without a descendant selector —
464
+ those outrank the merchant overrides in appearance.elements. */
465
+ --throttle-button-width: auto;
466
+ }
452
467
  .throttle-table__money { display: block; text-align: right; font-variant-numeric: tabular-nums; }
453
- .throttle-lineitem { display: flex; align-items: center; gap: var(--throttle-space-3); padding: var(--throttle-space-2) 0; }
468
+ .throttle-lineitem { display: flex; align-items: flex-start; gap: var(--throttle-space-3); padding: var(--throttle-space-2) 0; }
469
+ /* The UA's 1em paragraph margins pushed a two-line item to 96px, so five
470
+ items filled a screen with mostly whitespace. These carry the spacing
471
+ instead. Separate classes, not a ".throttle-lineitem p" descendant
472
+ selector -- those outrank the merchant's appearance.elements overrides. */
473
+ .throttle-lineitem__desc { margin: var(--throttle-space-1) 0 0; color: var(--throttle-color-muted-foreground); }
474
+ .throttle-lineitem__meta { margin: var(--throttle-space-1) 0 0; color: var(--throttle-color-muted-foreground); font-size: var(--throttle-font-size-sm); }
454
475
  .throttle-lineitem__image { width: 48px; height: 48px; object-fit: cover; border-radius: var(--throttle-radius-sm); flex: none; }
455
476
  .throttle-totals { display: flex; flex-direction: column; gap: var(--throttle-space-1); }
456
477
 
478
+ /* Order and invoice detail: the record on the left, what it costs and what
479
+ you can do about it on the right. The rail sticks so the total and the
480
+ actions stay reachable on an order with thirty lines, which is exactly when
481
+ a buyer most wants them. Collapses below, where a sticky rail would eat a
482
+ phone screen. */
483
+ .throttle-detail { display: grid; grid-template-columns: minmax(0, 1fr) 17rem; gap: var(--throttle-space-6); align-items: start; }
484
+ .throttle-detail__main { display: flex; flex-direction: column; gap: var(--throttle-space-5); min-width: 0; }
485
+ .throttle-detail__rail {
486
+ display: flex; flex-direction: column; gap: var(--throttle-space-4);
487
+ position: sticky; top: var(--throttle-space-4);
488
+ padding: var(--throttle-space-4);
489
+ border: var(--throttle-border-width) solid var(--throttle-color-border);
490
+ border-radius: var(--throttle-radius-md);
491
+ background: var(--throttle-color-surface);
492
+ }
493
+ /* The one number the buyer came for. */
494
+ .throttle-detail__headline { font-size: var(--throttle-font-size-xl); font-weight: 600; line-height: 1.2; }
495
+ .throttle-detail__section { display: flex; flex-direction: column; gap: var(--throttle-space-2); }
496
+ .throttle-detail__label { font-size: var(--throttle-font-size-sm); font-weight: 500; color: var(--throttle-color-muted-foreground); letter-spacing: .04em; text-transform: uppercase; }
497
+ /* A rail action column: full width, stacked, so the primary reads as primary. */
498
+ .throttle-detail__actions { display: flex; flex-direction: column; gap: var(--throttle-space-2); --throttle-button-width: 100%; }
499
+ .throttle-detail__link { display: inline-flex; align-items: center; gap: var(--throttle-space-1); }
500
+
457
501
  @media (prefers-reduced-motion: reduce) {
458
502
  .throttle-button, .throttle-field__input, .throttle-dropdown__item, .throttle-dropdown__trigger, .throttle-tabs__tab, .throttle-table__row, .throttle-modal__close {
459
503
  transition: none;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usethrottle/auth",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "Buyer authentication for Throttle storefronts — headless core, React hooks, and drop-in forms.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/react/forms/messages.ts"],"sourcesContent":["import { ThrottleAuthError } from '../../errors';\n\nfunction waitCopy(seconds?: number): string {\n if (!seconds || !Number.isFinite(seconds)) return 'Please wait a moment and try again.';\n if (seconds < 60) return `Please wait ${Math.ceil(seconds)} seconds and try again.`;\n const minutes = Math.ceil(seconds / 60);\n return `Please wait ${minutes} ${minutes === 1 ? 'minute' : 'minutes'} and try again.`;\n}\n\n\n/**\n * `addressLine1` -> `Address line 1`. The API names fields for programs; this\n * is the same name written for the person reading the form.\n */\nfunction fieldLabel(path: string): string {\n const words = path\n .replace(/([a-z])([A-Z])/g, '$1 $2')\n .replace(/([a-zA-Z])([0-9])/g, '$1 $2')\n .toLowerCase()\n .trim();\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n\ninterface ValidationDetail { path?: string; message?: string }\n\n/**\n * Buyer-facing copy for every code the storefront API returns. Two codes are\n * deliberately NOT passed through from the server: `invalid_credentials` (the\n * server is vague on purpose — enumerating accounts through our own UI would\n * undo that) and anything unrecognised.\n */\nexport function messageForError(err: unknown): string {\n const e = err as Partial<ThrottleAuthError> & { code?: string };\n switch (e?.code) {\n case 'invalid_credentials':\n return 'That email or password is not correct.';\n case 'weak_password':\n // The server's message IS the policy reason (\"Use at least 12 characters\").\n return e.message || 'Please choose a stronger password.';\n case 'too_many_attempts':\n return `Too many attempts. ${waitCopy(e.retryAfter)}`;\n case 'verification_required':\n return 'Please verify your email address to continue.';\n case 'step_up_required':\n return 'Please re-enter your password to continue.';\n case 'session_revoked':\n return 'Your session has ended. Please sign in again.';\n case 'token_expired':\n case 'token_invalid':\n case 'token_already_used':\n return 'That link is no longer valid. Please request a new link.';\n case 'auth_unavailable':\n return 'Accounts are briefly unavailable. Please try again shortly.';\n case 'origin_not_allowed':\n case 'auth_not_enabled':\n case 'invalid_api_key':\n case 'environment_mismatch':\n // Merchant misconfiguration, not something the buyer can fix.\n return 'Accounts are not available right now. Please contact support.';\n case 'cart_already_claimed':\n return 'That cart belongs to a different account.';\n case 'not_found':\n return 'We could not find that.';\n case 'payment_method_in_use':\n // The server's message IS the fix (\"set another card as default\n // first\") — same rationale as weak_password below.\n return e.message || 'That payment method is in use and cannot be removed.';\n case 'validation_error': {\n // The server already said which field is wrong and why. Dropping that on\n // the floor left buyers staring at \"Something went wrong\" in front of a\n // one-word fix (a country name typed where ISO-3166 alpha-2 belongs).\n // The raw `message` is an ajv path (\"body/countryCode must ...\"), so it\n // is the details we want, not the summary.\n const first = (e.details as ValidationDetail[] | undefined)?.[0];\n if (first?.path && first?.message) {\n return `${fieldLabel(first.path)} ${first.message.replace(/\\bNOT\\b/g, 'not')}.`;\n }\n return 'Please check the highlighted fields and try again.';\n }\n default:\n return 'Something went wrong. Please try again.';\n }\n}\n"],"mappings":";;;AAEA,SAAS,SAAS,SAA0B;AAC1C,MAAI,CAAC,WAAW,CAAC,OAAO,SAAS,OAAO,EAAG,QAAO;AAClD,MAAI,UAAU,GAAI,QAAO,eAAe,KAAK,KAAK,OAAO,CAAC;AAC1D,QAAM,UAAU,KAAK,KAAK,UAAU,EAAE;AACtC,SAAO,eAAe,OAAO,IAAI,YAAY,IAAI,WAAW,SAAS;AACvE;AAOA,SAAS,WAAW,MAAsB;AACxC,QAAM,QAAQ,KACX,QAAQ,mBAAmB,OAAO,EAClC,QAAQ,sBAAsB,OAAO,EACrC,YAAY,EACZ,KAAK;AACR,SAAO,MAAM,OAAO,CAAC,EAAE,YAAY,IAAI,MAAM,MAAM,CAAC;AACtD;AAUO,SAAS,gBAAgB,KAAsB;AACpD,QAAM,IAAI;AACV,UAAQ,GAAG,MAAM;AAAA,IACf,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAEH,aAAO,EAAE,WAAW;AAAA,IACtB,KAAK;AACH,aAAO,sBAAsB,SAAS,EAAE,UAAU,CAAC;AAAA,IACrD,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAEH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAGH,aAAO,EAAE,WAAW;AAAA,IACtB,KAAK,oBAAoB;AAMvB,YAAM,QAAS,EAAE,UAA6C,CAAC;AAC/D,UAAI,OAAO,QAAQ,OAAO,SAAS;AACjC,eAAO,GAAG,WAAW,MAAM,IAAI,CAAC,IAAI,MAAM,QAAQ,QAAQ,YAAY,KAAK,CAAC;AAAA,MAC9E;AACA,aAAO;AAAA,IACT;AAAA,IACA;AACE,aAAO;AAAA,EACX;AACF;","names":[]}