@usethrottle/auth 0.1.2 → 0.2.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.
@@ -1,131 +1,24 @@
1
- import "../chunk-ZMNCURDE.js";
1
+ "use client";
2
2
  import {
3
- ThrottleAuthProvider,
4
- useAuth,
5
- useAuthContext
6
- } from "../chunk-5GLLPF6L.js";
3
+ useAddresses,
4
+ useCustomer,
5
+ useInvoice,
6
+ useInvoices,
7
+ useOrder,
8
+ useOrders,
9
+ useSavedCards,
10
+ useSubscriptions
11
+ } from "../chunk-HGBAIM6B.js";
7
12
  import {
8
13
  ThrottleAuthError,
14
+ ThrottleAuthProvider,
9
15
  createAccount,
10
16
  createThrottleAuth,
11
17
  localStorageAdapter,
12
- memoryStorage
13
- } from "../chunk-SOPTBXF3.js";
14
-
15
- // src/react/hooks.ts
16
- import { useCallback, useEffect, useRef, useState } from "react";
17
- function asError(err) {
18
- if (err instanceof ThrottleAuthError) return err;
19
- const anyErr = err;
20
- return new ThrottleAuthError({
21
- code: anyErr?.code ?? "request_failed",
22
- message: anyErr?.message ?? String(err),
23
- statusCode: anyErr?.statusCode ?? 0
24
- });
25
- }
26
- function useResource(empty, load, deps) {
27
- const { status, emailVerified } = useAuth();
28
- const [data, setData] = useState(empty);
29
- const [isLoading, setIsLoading] = useState(status === "loading");
30
- const [error, setError] = useState(null);
31
- const gen = useRef(0);
32
- const alive = useRef(true);
33
- useEffect(() => {
34
- alive.current = true;
35
- return () => {
36
- alive.current = false;
37
- gen.current++;
38
- };
39
- }, []);
40
- const refetch = useCallback(async () => {
41
- const mine = ++gen.current;
42
- setIsLoading(true);
43
- try {
44
- const next = await load();
45
- if (!alive.current || gen.current !== mine) return;
46
- setData(next);
47
- setError(null);
48
- } catch (err) {
49
- if (!alive.current || gen.current !== mine) return;
50
- setError(asError(err));
51
- } finally {
52
- if (alive.current && gen.current === mine) setIsLoading(false);
53
- }
54
- }, deps);
55
- useEffect(() => {
56
- if (status !== "authenticated") {
57
- gen.current++;
58
- setData(empty);
59
- setError(null);
60
- setIsLoading(status === "loading");
61
- return;
62
- }
63
- void refetch();
64
- }, [status, emailVerified, refetch]);
65
- return { data, isLoading, error, refetch };
66
- }
67
- function useCustomer() {
68
- const { account } = useAuth();
69
- const res = useResource(null, () => account.getProfile(), [account]);
70
- const update = useCallback(async (patch) => {
71
- await account.updateProfile(patch);
72
- await res.refetch();
73
- }, [account, res]);
74
- return { ...res, update };
75
- }
76
- function useAddresses() {
77
- const { account } = useAuth();
78
- const res = useResource([], () => account.listAddresses(), [account]);
79
- const after = async (p) => {
80
- await p;
81
- await res.refetch();
82
- };
83
- return {
84
- ...res,
85
- create: (input) => after(account.createAddress(input)),
86
- update: (id, patch) => after(account.updateAddress(id, patch)),
87
- remove: (id) => after(account.deleteAddress(id))
88
- };
89
- }
90
- function useSavedCards() {
91
- const { account } = useAuth();
92
- const res = useResource([], () => account.listPaymentMethods(), [account]);
93
- const after = async (p) => {
94
- await p;
95
- await res.refetch();
96
- };
97
- return {
98
- ...res,
99
- setDefault: (id) => after(account.setDefaultPaymentMethod(id)),
100
- remove: (id) => after(account.deletePaymentMethod(id))
101
- };
102
- }
103
- function useOrders(page) {
104
- const { account } = useAuth();
105
- return useResource([], () => account.listOrders(page), [account, page?.cursor, page?.limit]);
106
- }
107
- function useInvoices(page) {
108
- const { account } = useAuth();
109
- return useResource([], () => account.listInvoices(page), [account, page?.cursor, page?.limit]);
110
- }
111
- function useSubscriptions(page) {
112
- const { account } = useAuth();
113
- const res = useResource(
114
- [],
115
- () => account.listSubscriptions(page),
116
- [account, page?.cursor, page?.limit]
117
- );
118
- const after = async (p) => {
119
- await p;
120
- await res.refetch();
121
- };
122
- return {
123
- ...res,
124
- cancel: (id, opts) => after(account.cancelSubscription(id, opts)),
125
- pause: (id) => after(account.pauseSubscription(id)),
126
- resume: (id) => after(account.resumeSubscription(id))
127
- };
128
- }
18
+ memoryStorage,
19
+ useAuth,
20
+ useAuthContext
21
+ } from "../chunk-SZ427BQ3.js";
129
22
  export {
130
23
  ThrottleAuthError,
131
24
  ThrottleAuthProvider,
@@ -137,7 +30,9 @@ export {
137
30
  useAuth,
138
31
  useAuthContext,
139
32
  useCustomer,
33
+ useInvoice,
140
34
  useInvoices,
35
+ useOrder,
141
36
  useOrders,
142
37
  useSavedCards,
143
38
  useSubscriptions
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/hooks.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { ThrottleAuthError } from '../errors';\nimport type { Address, AddressInput, Customer, Invoice, Order, PaymentMethod, ProfilePatch, Subscription } from '../types';\nimport type { Page } from '../account';\nimport { useAuth } from './context';\n\nexport interface Resource<T> {\n data: T;\n isLoading: boolean;\n error: ThrottleAuthError | null;\n refetch(): Promise<void>;\n}\n\nfunction asError(err: unknown): ThrottleAuthError {\n if (err instanceof ThrottleAuthError) return err;\n const anyErr = err as any;\n return new ThrottleAuthError({\n code: anyErr?.code ?? 'request_failed',\n message: anyErr?.message ?? String(err),\n statusCode: anyErr?.statusCode ?? 0,\n });\n}\n\n/**\n * One fetch-on-auth-change primitive behind every resource hook. `emailVerified`\n * is a dependency on purpose: verifying lifts the claim cutoff, and lists that\n * were legitimately empty a moment ago are not empty any more.\n */\nfunction useResource<T>(empty: T, load: () => Promise<T>, deps: unknown[]): Resource<T> {\n const { status, emailVerified } = useAuth();\n const [data, setData] = useState<T>(empty);\n const [isLoading, setIsLoading] = useState(status === 'loading');\n const [error, setError] = useState<ThrottleAuthError | null>(null);\n const gen = useRef(0);\n const alive = useRef(true);\n\n useEffect(() => {\n // Set on mount, not only cleared on unmount: React StrictMode mounts,\n // unmounts and remounts in development, and a ref that is only ever set\n // to false would silently discard every response for the rest of the\n // component's life.\n alive.current = true;\n return () => { alive.current = false; gen.current++; };\n }, []);\n\n const refetch = useCallback(async () => {\n const mine = ++gen.current;\n setIsLoading(true);\n try {\n const next = await load();\n if (!alive.current || gen.current !== mine) return;\n setData(next);\n setError(null);\n } catch (err) {\n if (!alive.current || gen.current !== mine) return;\n setError(asError(err));\n } finally {\n if (alive.current && gen.current === mine) setIsLoading(false);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n\n useEffect(() => {\n if (status !== 'authenticated') {\n gen.current++; // abandon anything in flight\n setData(empty);\n setError(null);\n setIsLoading(status === 'loading');\n return;\n }\n void refetch();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [status, emailVerified, refetch]);\n\n return { data, isLoading, error, refetch };\n}\n\nexport function useCustomer(): Resource<Customer | null> & { update(patch: ProfilePatch): Promise<void> } {\n const { account } = useAuth();\n const res = useResource<Customer | null>(null, () => account.getProfile(), [account]);\n const update = useCallback(async (patch: ProfilePatch) => {\n await account.updateProfile(patch);\n await res.refetch();\n }, [account, res]);\n return { ...res, update };\n}\n\nexport function useAddresses(): Resource<Address[]> & {\n create(input: AddressInput): Promise<void>;\n update(id: string, patch: Partial<AddressInput>): Promise<void>;\n remove(id: string): Promise<void>;\n} {\n const { account } = useAuth();\n const res = useResource<Address[]>([], () => account.listAddresses(), [account]);\n const after = async (p: Promise<unknown>) => { await p; await res.refetch(); };\n return {\n ...res,\n create: (input) => after(account.createAddress(input)),\n update: (id, patch) => after(account.updateAddress(id, patch)),\n remove: (id) => after(account.deleteAddress(id)),\n };\n}\n\nexport function useSavedCards(): Resource<PaymentMethod[]> & {\n setDefault(id: string): Promise<void>;\n remove(id: string): Promise<void>;\n} {\n const { account } = useAuth();\n const res = useResource<PaymentMethod[]>([], () => account.listPaymentMethods(), [account]);\n const after = async (p: Promise<unknown>) => { await p; await res.refetch(); };\n return {\n ...res,\n setDefault: (id) => after(account.setDefaultPaymentMethod(id)),\n remove: (id) => after(account.deletePaymentMethod(id)),\n };\n}\n\nexport function useOrders(page?: Page): Resource<Order[]> {\n const { account } = useAuth();\n return useResource<Order[]>([], () => account.listOrders(page), [account, page?.cursor, page?.limit]);\n}\n\nexport function useInvoices(page?: Page): Resource<Invoice[]> {\n const { account } = useAuth();\n return useResource<Invoice[]>([], () => account.listInvoices(page), [account, page?.cursor, page?.limit]);\n}\n\nexport function useSubscriptions(page?: Page): Resource<Subscription[]> & {\n cancel(id: string, opts?: { atPeriodEnd?: boolean }): Promise<void>;\n pause(id: string): Promise<void>;\n resume(id: string): Promise<void>;\n} {\n const { account } = useAuth();\n const res = useResource<Subscription[]>([], () => account.listSubscriptions(page),\n [account, page?.cursor, page?.limit]);\n const after = async (p: Promise<unknown>) => { await p; await res.refetch(); };\n return {\n ...res,\n cancel: (id, opts) => after(account.cancelSubscription(id, opts)),\n pause: (id) => after(account.pauseSubscription(id)),\n resume: (id) => after(account.resumeSubscription(id)),\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,422 @@
1
+
2
+ .throttle-root {
3
+ --throttle-color-primary: #18181b;
4
+ --throttle-color-primary-foreground: #ffffff;
5
+ --throttle-color-background: #ffffff;
6
+ --throttle-color-surface: #f8fafc;
7
+ --throttle-color-foreground: #0e1116;
8
+ --throttle-color-muted: #eceef2;
9
+ --throttle-color-muted-foreground: #5b6472;
10
+ --throttle-color-border: #e2e8f0;
11
+ --throttle-color-danger: #c2321f;
12
+ --throttle-color-danger-surface: #fef2f2;
13
+ --throttle-color-danger-border: #fecaca;
14
+ --throttle-color-success: #127a4a;
15
+ --throttle-color-success-surface: #ecfdf5;
16
+ --throttle-color-success-border: #a7f3d0;
17
+ --throttle-color-warning: #92400e;
18
+ --throttle-color-warning-surface: #fffbeb;
19
+ --throttle-color-warning-border: #fde68a;
20
+ --throttle-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
21
+ --throttle-font-size: 16px;
22
+ --throttle-font-size-xs: 12px;
23
+ --throttle-font-size-sm: 14px;
24
+ --throttle-font-size-lg: 20px;
25
+ --throttle-font-size-xl: 24px;
26
+ --throttle-line-height: 1.5;
27
+ --throttle-line-height-tight: 1.25;
28
+ --throttle-radius: 12px;
29
+ --throttle-radius-sm: 8px;
30
+ --throttle-radius-full: 999px;
31
+ --throttle-border-width: 1px;
32
+ --throttle-spacing: 8px;
33
+ --throttle-space-1: 4px;
34
+ --throttle-space-2: var(--throttle-spacing);
35
+ --throttle-space-3: 12px;
36
+ --throttle-space-4: 16px;
37
+ --throttle-space-5: 24px;
38
+ --throttle-space-6: 32px;
39
+ --throttle-control-height: 48px;
40
+ --throttle-control-padding-x: 16px;
41
+ --throttle-shadow-card: 0 1px 3px rgba(15,23,42,.08), 0 1px 2px rgba(15,23,42,.04);
42
+ --throttle-shadow-popover: 0 10px 24px rgba(15,23,42,.12), 0 2px 6px rgba(15,23,42,.06);
43
+ --throttle-transition: 150ms cubic-bezier(.2,0,.2,1);
44
+ --throttle-z-modal: 2147483000;
45
+ --throttle-z-popover: 2147482000;
46
+ font-family: var(--throttle-font-family);
47
+ font-size: var(--throttle-font-size);
48
+ line-height: var(--throttle-line-height);
49
+ color: var(--throttle-color-foreground);
50
+ box-sizing: border-box;
51
+ }
52
+ .throttle-root *, .throttle-root *::before, .throttle-root *::after { box-sizing: inherit; }
53
+
54
+ .throttle-root[data-throttle-theme="dark"] {
55
+ --throttle-color-primary: #e6edf3;
56
+ --throttle-color-primary-foreground: #0d1117;
57
+ --throttle-color-background: #0d1117;
58
+ --throttle-color-surface: #1c2129;
59
+ --throttle-color-foreground: #e6edf3;
60
+ --throttle-color-muted: #21262d;
61
+ --throttle-color-muted-foreground: #9aa4b2;
62
+ --throttle-color-border: #30363d;
63
+ --throttle-color-danger: #ff6b58;
64
+ --throttle-color-danger-surface: #2a1513;
65
+ --throttle-color-danger-border: #5c2b25;
66
+ --throttle-color-success: #45c08a;
67
+ --throttle-color-success-surface: #0f2419;
68
+ --throttle-color-success-border: #1f4d36;
69
+ --throttle-color-warning: #e0a53a;
70
+ --throttle-color-warning-surface: #2a2113;
71
+ --throttle-color-warning-border: #5c4a25;
72
+ --throttle-shadow-card: 0 1px 3px rgba(0,0,0,.6), 0 1px 2px rgba(0,0,0,.4);
73
+ --throttle-shadow-popover: 0 10px 24px rgba(0,0,0,.5), 0 2px 6px rgba(0,0,0,.35);
74
+ }
75
+ @media (prefers-color-scheme: dark) {
76
+ .throttle-root[data-throttle-theme="auto"] {
77
+ --throttle-color-primary: #e6edf3;
78
+ --throttle-color-primary-foreground: #0d1117;
79
+ --throttle-color-background: #0d1117;
80
+ --throttle-color-surface: #1c2129;
81
+ --throttle-color-foreground: #e6edf3;
82
+ --throttle-color-muted: #21262d;
83
+ --throttle-color-muted-foreground: #9aa4b2;
84
+ --throttle-color-border: #30363d;
85
+ --throttle-color-danger: #ff6b58;
86
+ --throttle-color-danger-surface: #2a1513;
87
+ --throttle-color-danger-border: #5c2b25;
88
+ --throttle-color-success: #45c08a;
89
+ --throttle-color-success-surface: #0f2419;
90
+ --throttle-color-success-border: #1f4d36;
91
+ --throttle-color-warning: #e0a53a;
92
+ --throttle-color-warning-surface: #2a2113;
93
+ --throttle-color-warning-border: #5c4a25;
94
+ --throttle-shadow-card: 0 1px 3px rgba(0,0,0,.6), 0 1px 2px rgba(0,0,0,.4);
95
+ --throttle-shadow-popover: 0 10px 24px rgba(0,0,0,.5), 0 2px 6px rgba(0,0,0,.35);
96
+ }
97
+ }
98
+
99
+ .throttle-card {
100
+ background: var(--throttle-color-background);
101
+ border: var(--throttle-border-width) solid var(--throttle-color-border);
102
+ border-radius: var(--throttle-radius);
103
+ box-shadow: var(--throttle-shadow-card);
104
+ padding: var(--throttle-space-5);
105
+ width: 100%;
106
+ max-width: 400px;
107
+ margin-inline: auto;
108
+ }
109
+ /* Inside a Modal the dialog already provides the surface — background, radius
110
+ and elevation. A card that draws its own on top is the "box inside a box":
111
+ two shadowed rounded rectangles nested, with the card's max-width stranding
112
+ it in the middle of a much wider dialog. */
113
+ .throttle-card--bare { border: none; box-shadow: none; background: none; max-width: none; }
114
+ .throttle-card__header { margin-bottom: var(--throttle-space-4); }
115
+ .throttle-card__header--branded { text-align: center; }
116
+ .throttle-card__logo { display: flex; justify-content: center; margin-bottom: var(--throttle-space-3); }
117
+ .throttle-card__title { font-size: var(--throttle-font-size-xl); line-height: var(--throttle-line-height-tight); font-weight: 600; letter-spacing: -0.02em; margin: 0; }
118
+ .throttle-card__title--branded { font-size: var(--throttle-font-size-xl); }
119
+ .throttle-card__description { color: var(--throttle-color-muted-foreground); margin: var(--throttle-space-1) 0 0; }
120
+ .throttle-card__body { display: flex; flex-direction: column; gap: var(--throttle-space-3); }
121
+ .throttle-card__footer { margin-top: var(--throttle-space-4); text-align: center; }
122
+ .throttle-card__footer--ruled { border-top: var(--throttle-border-width) solid var(--throttle-color-border); padding-top: var(--throttle-space-4); }
123
+
124
+ .throttle-field { display: flex; flex-direction: column; gap: var(--throttle-space-1); }
125
+ .throttle-field__labelrow { display: flex; align-items: baseline; justify-content: space-between; gap: var(--throttle-space-2); }
126
+ .throttle-field__label { font-weight: 500; }
127
+ .throttle-field__input {
128
+ font: inherit;
129
+ font-size: var(--throttle-font-size);
130
+ color: var(--throttle-color-foreground);
131
+ background: var(--throttle-color-background);
132
+ border: var(--throttle-border-width) solid var(--throttle-color-border);
133
+ border-radius: var(--throttle-radius-sm);
134
+ /* padding-block stays 0 so min-height alone owns the height, exactly as
135
+ .throttle-button does. With 12px vertical padding the box computed to
136
+ 50px against the button's 48px — a 2px mismatch, measured in a browser
137
+ across 19 controls. An input element centres its own text vertically. */
138
+ padding-block: 0;
139
+ padding-inline: var(--throttle-control-padding-x);
140
+ min-height: var(--throttle-control-height);
141
+ width: 100%;
142
+ transition: border-color var(--throttle-transition), box-shadow var(--throttle-transition);
143
+ }
144
+ .throttle-field__input:focus-visible {
145
+ outline: none;
146
+ border-color: var(--throttle-color-primary);
147
+ box-shadow: 0 0 0 2px var(--throttle-color-background), 0 0 0 4px var(--throttle-color-primary);
148
+ }
149
+ .throttle-field__input:disabled { background: var(--throttle-color-muted); color: var(--throttle-color-muted-foreground); cursor: not-allowed; }
150
+ .throttle-field__input::placeholder { color: var(--throttle-color-muted-foreground); opacity: 1; }
151
+ .throttle-field__input--grouped { border: none; border-radius: 0; padding-inline: 0; }
152
+ .throttle-field__hint { color: var(--throttle-color-muted-foreground); font-size: var(--throttle-font-size-xs); }
153
+ /* Always mounted, so it reserves its line whether or not there is an error:
154
+ an error appearing on submit must not push the submit button out from
155
+ under the buyer's pointer. */
156
+ .throttle-field__error { color: var(--throttle-color-danger); font-size: var(--throttle-font-size-xs); min-height: 1rem; line-height: 1rem; }
157
+
158
+ .throttle-field-group {
159
+ display: flex;
160
+ flex-direction: column;
161
+ border: var(--throttle-border-width) solid var(--throttle-color-border);
162
+ border-radius: var(--throttle-radius-sm);
163
+ overflow: hidden;
164
+ }
165
+ .throttle-field-group__item { padding: var(--throttle-space-3) var(--throttle-control-padding-x); }
166
+ .throttle-field-group__divider { height: var(--throttle-border-width); background: var(--throttle-color-border); }
167
+ .throttle-field-group__forgot { margin-left: auto; }
168
+
169
+ .throttle-button {
170
+ font: inherit;
171
+ font-weight: 500;
172
+ border-radius: var(--throttle-radius-sm);
173
+ border: var(--throttle-border-width) solid transparent;
174
+ padding-inline: var(--throttle-control-padding-x);
175
+ padding-block: 0;
176
+ min-height: var(--throttle-control-height);
177
+ display: inline-flex;
178
+ align-items: center;
179
+ justify-content: center;
180
+ cursor: pointer;
181
+ transition: opacity var(--throttle-transition), background-color var(--throttle-transition), border-color var(--throttle-transition), filter var(--throttle-transition), box-shadow var(--throttle-transition);
182
+ }
183
+ .throttle-button:disabled { opacity: .55; cursor: not-allowed; }
184
+ .throttle-button:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--throttle-color-background), 0 0 0 4px var(--throttle-color-primary); }
185
+ .throttle-button--primary { background: var(--throttle-color-primary); color: var(--throttle-color-primary-foreground); width: 100%; }
186
+ .throttle-button--primary:hover:not(:disabled) { filter: brightness(0.92); }
187
+ .throttle-button--secondary { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); border-color: var(--throttle-color-border); }
188
+ .throttle-button--secondary:hover:not(:disabled) { background: var(--throttle-color-muted); }
189
+ .throttle-button--danger { background: transparent; color: var(--throttle-color-danger); border-color: var(--throttle-color-border); }
190
+ .throttle-button--danger:hover:not(:disabled) { background: color-mix(in srgb, var(--throttle-color-danger) 10%, transparent); }
191
+ /* Link-style actions are the rows panel default, and they carry Delete,
192
+ Remove, Cancel and Sign out. Zero padding with min-height 0 made those
193
+ roughly 20px tall and ~8px apart — a mis-tap on a phone lands on something
194
+ irreversible. The target is expanded with vertical padding and pulled back
195
+ with an equal negative margin, so the hit box clears 44px while the text
196
+ sits exactly where it did. Collapsing this back to zero padding shrinks the
197
+ target again. */
198
+ .throttle-button--link { background: none; border: none; color: var(--throttle-color-primary); padding-inline: 0; padding-block: var(--throttle-space-3); margin-block: calc(var(--throttle-space-3) * -1); min-height: 0; text-decoration: underline; width: auto; }
199
+ .throttle-button--link:hover:not(:disabled) { opacity: .8; }
200
+ .throttle-button--linkDanger { background: none; border: none; color: var(--throttle-color-danger); padding-inline: 0; padding-block: var(--throttle-space-3); margin-block: calc(var(--throttle-space-3) * -1); min-height: 0; text-decoration: underline; width: auto; }
201
+ .throttle-button--linkDanger:hover:not(:disabled) { opacity: .8; }
202
+ .throttle-button--xs { font-size: var(--throttle-font-size-xs); }
203
+
204
+ .throttle-alert { border-radius: var(--throttle-radius-sm); padding: var(--throttle-space-3); font-size: var(--throttle-font-size-sm); }
205
+ .throttle-alert--error { background: var(--throttle-color-danger-surface); border: var(--throttle-border-width) solid var(--throttle-color-danger-border); color: var(--throttle-color-danger); }
206
+ .throttle-alert--success { background: var(--throttle-color-success-surface); border: var(--throttle-border-width) solid var(--throttle-color-success-border); color: var(--throttle-color-success); }
207
+ .throttle-alert--info { background: var(--throttle-color-surface); color: var(--throttle-color-muted-foreground); }
208
+
209
+ .throttle-spinner {
210
+ display: inline-block; width: 1em; height: 1em;
211
+ border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%;
212
+ animation: throttle-spin 700ms linear infinite;
213
+ }
214
+ @keyframes throttle-spin { to { transform: rotate(360deg); } }
215
+ @media (prefers-reduced-motion: reduce) { .throttle-spinner { animation-duration: 2.4s; } }
216
+
217
+ .throttle-skeleton {
218
+ background: var(--throttle-color-muted);
219
+ border-radius: var(--throttle-radius-sm);
220
+ animation: throttle-pulse 1.4s ease-in-out infinite; height: 2.5em; }
221
+ @keyframes throttle-pulse { 50% { opacity: .5; } }
222
+ @media (prefers-reduced-motion: reduce) { .throttle-skeleton { animation: none; } }
223
+
224
+ .throttle-modal__overlay {
225
+ position: fixed; inset: 0; z-index: var(--throttle-z-modal);
226
+ background: rgba(14,17,22,.45);
227
+ display: flex; align-items: center; justify-content: center;
228
+ padding: var(--throttle-space-4);
229
+ overflow-y: auto;
230
+ }
231
+ .throttle-modal__container {
232
+ background: var(--throttle-color-background);
233
+ border-radius: var(--throttle-radius);
234
+ box-shadow: var(--throttle-shadow-popover);
235
+ max-width: 720px; width: 100%; max-height: 90vh; overflow-y: auto;
236
+ position: relative;
237
+ }
238
+ /* After the base rule on purpose: both are single-class, so equal specificity
239
+ means source order decides. Declared before it, this silently lost. */
240
+ .throttle-modal__container--compact { max-width: 440px; }
241
+ .throttle-modal__close {
242
+ position: absolute; top: var(--throttle-space-3); right: var(--throttle-space-3);
243
+ background: none; border: none; cursor: pointer; font-size: var(--throttle-font-size-lg);
244
+ color: var(--throttle-color-muted-foreground);
245
+ transition: color var(--throttle-transition);
246
+ }
247
+ .throttle-modal__close:hover { color: var(--throttle-color-foreground); }
248
+ .throttle-modal__header { padding: var(--throttle-space-4) var(--throttle-space-4) 0; }
249
+ .throttle-modal__title { font-size: var(--throttle-font-size-lg); line-height: var(--throttle-line-height-tight); font-weight: 600; margin: 0; }
250
+ .throttle-modal__body { padding: var(--throttle-space-4); }
251
+
252
+ .throttle-dropdown__trigger { background: none; border: none; cursor: pointer; padding: 0; border-radius: var(--throttle-radius-full); transition: background-color var(--throttle-transition); }
253
+ .throttle-dropdown__trigger:hover { background: var(--throttle-color-surface); }
254
+ .throttle-dropdown__trigger:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: 2px; }
255
+ .throttle-dropdown__menu {
256
+ position: absolute; right: 0; top: calc(100% + var(--throttle-space-2));
257
+ z-index: var(--throttle-z-popover);
258
+ min-width: 260px;
259
+ background: var(--throttle-color-background);
260
+ border: var(--throttle-border-width) solid var(--throttle-color-border);
261
+ border-radius: var(--throttle-radius);
262
+ box-shadow: var(--throttle-shadow-popover);
263
+ padding: var(--throttle-space-1);
264
+ }
265
+ .throttle-dropdown__item {
266
+ display: flex; align-items: center; gap: var(--throttle-space-2);
267
+ width: 100%; text-align: left; font: inherit; color: inherit;
268
+ background: none; border: none; cursor: pointer;
269
+ padding: var(--throttle-space-3);
270
+ border-radius: var(--throttle-radius-sm);
271
+ text-decoration: none;
272
+ transition: background-color var(--throttle-transition);
273
+ }
274
+ .throttle-dropdown__item:hover { background: var(--throttle-color-surface); }
275
+ .throttle-dropdown__item:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }
276
+ .throttle-dropdown__separator { height: 1px; background: var(--throttle-color-border); margin: var(--throttle-space-1) 0; border: 0; }
277
+
278
+ .throttle-tabs__list {
279
+ display: flex; gap: var(--throttle-space-1);
280
+ border-bottom: var(--throttle-border-width) solid var(--throttle-color-border);
281
+ overflow-x: auto; scrollbar-width: thin;
282
+ }
283
+ .throttle-tabs__tab {
284
+ font: inherit; background: none; border: none; cursor: pointer; white-space: nowrap;
285
+ padding: var(--throttle-space-3);
286
+ color: var(--throttle-color-muted-foreground);
287
+ border-bottom: 2px solid transparent;
288
+ transition: color var(--throttle-transition), border-color var(--throttle-transition);
289
+ }
290
+ .throttle-tabs__tab:hover { color: var(--throttle-color-foreground); }
291
+ .throttle-tabs__tab--active { color: var(--throttle-color-foreground); border-bottom-color: var(--throttle-color-primary); }
292
+ .throttle-tabs__panel { padding-top: var(--throttle-space-4); }
293
+
294
+ /* Paints its own ground. A UserButton in a navbar should stay transparent, but
295
+ the dashboard is a large surface of themed text: in dark theme its foreground
296
+ is near-white, so on a light host page it was rendering white-on-white. */
297
+ .throttle-account-root { container-type: inline-size; background: var(--throttle-color-background); color: var(--throttle-color-foreground); }
298
+ .throttle-account { display: flex; gap: var(--throttle-space-5); align-items: flex-start; }
299
+ .throttle-account__content { flex: 1; min-width: 0; }
300
+ .throttle-tabs__rail { flex: none; width: 220px; display: flex; flex-direction: column; gap: 2px; }
301
+ .throttle-tabs__railitem {
302
+ font: inherit;
303
+ font-size: var(--throttle-font-size-sm);
304
+ display: flex;
305
+ align-items: center;
306
+ gap: var(--throttle-space-3);
307
+ width: 100%;
308
+ text-align: left;
309
+ background: none;
310
+ border: none;
311
+ border-radius: var(--throttle-radius-sm);
312
+ padding: 0 var(--throttle-space-3);
313
+ min-height: var(--throttle-control-height);
314
+ color: var(--throttle-color-muted-foreground);
315
+ cursor: pointer;
316
+ transition: background var(--throttle-transition), color var(--throttle-transition);
317
+ }
318
+ .throttle-tabs__railitem:hover { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); }
319
+ .throttle-tabs__railitem:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }
320
+ .throttle-tabs__tab:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }
321
+ .throttle-tabs__railitem--active { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); font-weight: 500; }
322
+ .throttle-tabs__railicon { flex: none; opacity: .75; }
323
+
324
+ /* A CONTAINER query, not a viewport one: these components render inside a
325
+ merchant's page, so the viewport says nothing about how much room the
326
+ dashboard actually has — it could sit in a 400px column on a 1920px screen.
327
+ Below ~560px of container the rail becomes a full-width list above the
328
+ panel: all seven still visible, each still a 44px target. */
329
+ @container (max-width: 560px) {
330
+ /* align-items: flex-start is right in row direction and wrong in column —
331
+ it shrinks the rail to its content width, so width:100% alone cannot make
332
+ it span. Stretch it explicitly when stacked. */
333
+ .throttle-account { flex-direction: column; align-items: stretch; gap: var(--throttle-space-4); }
334
+ .throttle-tabs__rail { width: 100%; }
335
+ }
336
+
337
+ .throttle-table { width: 100%; border-collapse: collapse; text-align: left; }
338
+ .throttle-table__head { color: var(--throttle-color-muted-foreground); font-weight: 500; font-size: var(--throttle-font-size-sm); }
339
+ .throttle-table__row { border-top: var(--throttle-border-width) solid var(--throttle-color-border); cursor: pointer; transition: background-color var(--throttle-transition); }
340
+ .throttle-table__row:hover { background: var(--throttle-color-surface); }
341
+ .throttle-table__row:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }
342
+ .throttle-table__cell { padding: var(--throttle-space-3) var(--throttle-space-2); height: 56px; box-sizing: border-box; }
343
+ .throttle-table__scroll { overflow-x: auto; }
344
+
345
+ .throttle-empty { text-align: center; padding: var(--throttle-space-6) var(--throttle-space-4); }
346
+ .throttle-empty__title { font-weight: 600; margin: 0 0 var(--throttle-space-1); }
347
+ .throttle-empty__description { color: var(--throttle-color-muted-foreground); margin: 0; }
348
+
349
+ .throttle-avatar {
350
+ display: inline-flex; align-items: center; justify-content: center;
351
+ width: 32px; height: 32px; border-radius: var(--throttle-radius-full);
352
+ background: var(--throttle-color-primary); color: var(--throttle-color-primary-foreground);
353
+ font-size: var(--throttle-font-size-xs); font-weight: 600; text-transform: uppercase;
354
+ }
355
+
356
+ .throttle-badge {
357
+ display: inline-block; font-size: var(--throttle-font-size-xs); font-weight: 500;
358
+ padding: 2px var(--throttle-space-2); border-radius: var(--throttle-radius-full);
359
+ background: var(--throttle-color-muted); color: var(--throttle-color-muted-foreground);
360
+ }
361
+ .throttle-badge--muted { background: none; border: var(--throttle-border-width) solid var(--throttle-color-border); color: var(--throttle-color-muted-foreground); font-weight: 400; padding: 1px calc(var(--throttle-space-2) - 1px); }
362
+ .throttle-badge--success { background: color-mix(in srgb, var(--throttle-color-success) 15%, transparent); color: var(--throttle-color-success); }
363
+ .throttle-badge--warning { background: color-mix(in srgb, var(--throttle-color-warning) 15%, transparent); color: var(--throttle-color-warning); }
364
+ .throttle-badge--danger { background: color-mix(in srgb, var(--throttle-color-danger) 15%, transparent); color: var(--throttle-color-danger); }
365
+
366
+ .throttle-stack { display: flex; flex-direction: column; gap: var(--throttle-space-3); }
367
+ .throttle-row { display: flex; align-items: center; justify-content: space-between; gap: var(--throttle-space-2); flex-wrap: wrap; }
368
+ .throttle-muted { color: var(--throttle-color-muted-foreground); }
369
+ .throttle-banner {
370
+ display: flex; align-items: center; justify-content: space-between; gap: var(--throttle-space-2);
371
+ flex-wrap: wrap;
372
+ background: color-mix(in srgb, var(--throttle-color-warning) 12%, transparent);
373
+ color: var(--throttle-color-warning);
374
+ border-radius: var(--throttle-radius-sm);
375
+ padding: var(--throttle-space-3);
376
+ margin-bottom: var(--throttle-space-4);
377
+ }
378
+ .throttle-panel { display: flex; flex-direction: column; gap: var(--throttle-space-4); }
379
+ /* 100dvh, not 100vh: on mobile browsers 100vh includes the space behind the
380
+ dynamic address/toolbar, so the page would be taller than the visible
381
+ viewport and the card would sit off-centre until the toolbar collapsed. */
382
+ .throttle-page {
383
+ min-height: 100dvh;
384
+ width: 100%;
385
+ background: var(--throttle-color-surface);
386
+ display: flex;
387
+ flex-direction: column;
388
+ align-items: center;
389
+ justify-content: center;
390
+ gap: var(--throttle-space-5);
391
+ padding: var(--throttle-space-5);
392
+ }
393
+ .throttle-page__logo { display: flex; justify-content: center; }
394
+ .throttle-page__footer { color: var(--throttle-color-muted-foreground); text-align: center; font-size: var(--throttle-font-size-sm); }
395
+ .throttle-listitem {
396
+ display: flex; align-items: flex-start; justify-content: space-between; gap: var(--throttle-space-2);
397
+ flex-wrap: wrap;
398
+ border: var(--throttle-border-width) solid var(--throttle-color-border);
399
+ border-radius: var(--throttle-radius-sm);
400
+ padding: var(--throttle-space-3);
401
+ }
402
+ /* 'rows' variant: hairline rules between items instead of each item carrying
403
+ its own border+radius, so a panel does not read as a card inside a card. */
404
+ .throttle-rows { display: flex; flex-direction: column; }
405
+ .throttle-rowitem {
406
+ display: flex; align-items: flex-start; justify-content: space-between; gap: var(--throttle-space-2);
407
+ flex-wrap: wrap;
408
+ border-top: var(--throttle-border-width) solid var(--throttle-color-border);
409
+ padding: var(--throttle-space-3) 0;
410
+ }
411
+ .throttle-rowitem:first-child { border-top: none; padding-top: 0; }
412
+ .throttle-actions { display: flex; gap: var(--throttle-space-2); flex-wrap: wrap; }
413
+ .throttle-table__money { display: block; text-align: right; font-variant-numeric: tabular-nums; }
414
+ .throttle-lineitem { display: flex; align-items: center; gap: var(--throttle-space-3); padding: var(--throttle-space-2) 0; }
415
+ .throttle-lineitem__image { width: 48px; height: 48px; object-fit: cover; border-radius: var(--throttle-radius-sm); flex: none; }
416
+ .throttle-totals { display: flex; flex-direction: column; gap: var(--throttle-space-1); }
417
+
418
+ @media (prefers-reduced-motion: reduce) {
419
+ .throttle-button, .throttle-field__input, .throttle-dropdown__item, .throttle-dropdown__trigger, .throttle-tabs__tab, .throttle-table__row, .throttle-modal__close {
420
+ transition: none;
421
+ }
422
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usethrottle/auth",
3
- "version": "0.1.2",
3
+ "version": "0.2.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",
@@ -10,7 +10,9 @@
10
10
  "exports": {
11
11
  ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" },
12
12
  "./react": { "types": "./dist/react/index.d.ts", "import": "./dist/react/index.js", "require": "./dist/react/index.cjs" },
13
- "./forms": { "types": "./dist/react/forms/index.d.ts", "import": "./dist/react/forms/index.js", "require": "./dist/react/forms/index.cjs" }
13
+ "./forms": { "types": "./dist/react/forms/index.d.ts", "import": "./dist/react/forms/index.js", "require": "./dist/react/forms/index.cjs" },
14
+ "./components": { "types": "./dist/react/components/index.d.ts", "import": "./dist/react/components/index.js", "require": "./dist/react/components/index.cjs" },
15
+ "./styles.css": "./dist/styles.css"
14
16
  },
15
17
  "scripts": {
16
18
  "build": "tsup",
@@ -23,6 +25,7 @@
23
25
  "devDependencies": {
24
26
  "@testing-library/react": "^16.1.0",
25
27
  "@types/react": "^19.1.0",
28
+ "@types/react-dom": "^19.1.0",
26
29
  "@vitejs/plugin-react": "^4.3.4",
27
30
  "jsdom": "^25.0.1",
28
31
  "react": "^19.1.0",
@@ -1,49 +0,0 @@
1
- import {
2
- createAccount,
3
- createThrottleAuth
4
- } from "./chunk-SOPTBXF3.js";
5
-
6
- // src/react/context.tsx
7
- import { createContext, useContext, useEffect, useMemo, useRef, useSyncExternalStore } from "react";
8
- import { jsx } from "react/jsx-runtime";
9
- var Ctx = createContext(null);
10
- function ThrottleAuthProvider(props) {
11
- const { auth: injected, publishableKey, baseUrl, storage, onStepUpRequired, children } = props;
12
- const value = useMemo(() => {
13
- const auth = injected ?? createThrottleAuth({ publishableKey, baseUrl, storage, onStepUpRequired });
14
- return { auth, account: createAccount(auth) };
15
- }, [injected, publishableKey, baseUrl]);
16
- const restored = useRef(null);
17
- useEffect(() => {
18
- if (restored.current === value.auth) return;
19
- restored.current = value.auth;
20
- void value.auth.restore();
21
- }, [value]);
22
- return /* @__PURE__ */ jsx(Ctx.Provider, { value, children });
23
- }
24
- function useCtx() {
25
- const ctx = useContext(Ctx);
26
- if (!ctx) throw new Error("useAuth must be used inside a <ThrottleAuthProvider>");
27
- return ctx;
28
- }
29
- function useAuthContext() {
30
- return useCtx();
31
- }
32
- function useAuth() {
33
- const { auth, account } = useCtx();
34
- const snapshot = useSyncExternalStore(auth.subscribe, auth.getSnapshot, auth.getSnapshot);
35
- return {
36
- ...snapshot,
37
- isLoading: snapshot.status === "loading",
38
- isAuthenticated: snapshot.status === "authenticated",
39
- auth,
40
- account
41
- };
42
- }
43
-
44
- export {
45
- ThrottleAuthProvider,
46
- useAuthContext,
47
- useAuth
48
- };
49
- //# sourceMappingURL=chunk-5GLLPF6L.js.map