@usequota/nextjs 0.1.0 → 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.
package/dist/index.d.mts CHANGED
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
  import * as react from 'react';
4
4
  import { ReactNode } from 'react';
5
- import { QuotaUser } from '@usequota/types';
5
+ import { QuotaUser, CreditPackage } from '@usequota/types';
6
6
  export { ChatCompletionRequest, ChatCompletionResponse, ChatMessage, CreditPackage, QuotaError as QuotaErrorResponse, QuotaMetadata, QuotaSession, QuotaUser, Tool, ToolCall } from '@usequota/types';
7
7
  export { Q as QuotaError, a as QuotaInsufficientCreditsError, b as QuotaNotConnectedError, d as QuotaRateLimitError, c as QuotaTokenExpiredError } from './errors-CmNx3kSz.mjs';
8
8
 
@@ -115,8 +115,19 @@ interface QuotaContextValue {
115
115
  * Refetches user data from the server
116
116
  */
117
117
  refetch: () => Promise<void>;
118
+ /**
119
+ * Quota API base URL
120
+ */
121
+ baseUrl: string;
118
122
  }
119
123
  declare const QuotaContext: react.Context<QuotaContextValue | null>;
124
+ /**
125
+ * Controls when the provider fetches user data.
126
+ *
127
+ * - `"eager"` — fetches on mount (default, current behavior)
128
+ * - `"lazy"` — does NOT fetch on mount; only fetches when `refetch()` is called
129
+ */
130
+ type QuotaFetchStrategy = "eager" | "lazy";
120
131
  interface QuotaProviderProps {
121
132
  children: ReactNode;
122
133
  /**
@@ -138,6 +149,15 @@ interface QuotaProviderProps {
138
149
  * @default '/api/quota/me'
139
150
  */
140
151
  apiPath?: string;
152
+ /**
153
+ * Controls when user data is fetched.
154
+ *
155
+ * - `"eager"` (default) — fetches on mount
156
+ * - `"lazy"` — does NOT fetch on mount; only fetches when `refetch()` is called
157
+ *
158
+ * @default 'eager'
159
+ */
160
+ fetchStrategy?: "eager" | "lazy";
141
161
  }
142
162
  /**
143
163
  * React context provider for Quota authentication
@@ -160,7 +180,7 @@ interface QuotaProviderProps {
160
180
  * }
161
181
  * ```
162
182
  */
163
- declare function QuotaProvider({ children, clientId, baseUrl, callbackPath, apiPath, }: QuotaProviderProps): react_jsx_runtime.JSX.Element;
183
+ declare function QuotaProvider({ children, clientId, baseUrl, callbackPath, apiPath, fetchStrategy, }: QuotaProviderProps): react_jsx_runtime.JSX.Element;
164
184
  /**
165
185
  * Hook to access Quota authentication context
166
186
  *
@@ -262,6 +282,42 @@ declare function useQuotaBalance(): {
262
282
  error: Error | null;
263
283
  refetch: () => Promise<void>;
264
284
  };
285
+ /**
286
+ * Hook to fetch available credit packages from the Quota API
287
+ *
288
+ * Fetches from the public `/v1/packages` endpoint (no auth required).
289
+ * Results are cached for the lifetime of the component — subsequent calls
290
+ * return the cached data without re-fetching.
291
+ *
292
+ * @example
293
+ * ```tsx
294
+ * 'use client';
295
+ * import { useQuotaPackages } from '@usequota/nextjs';
296
+ *
297
+ * export function BuyCredits() {
298
+ * const { packages, isLoading, error, refetch } = useQuotaPackages();
299
+ *
300
+ * if (isLoading) return <div>Loading packages...</div>;
301
+ * if (error) return <div>Error: {error.message}</div>;
302
+ *
303
+ * return (
304
+ * <ul>
305
+ * {packages.map((pkg) => (
306
+ * <li key={pkg.id}>
307
+ * {pkg.credits} credits — {pkg.price_display}
308
+ * </li>
309
+ * ))}
310
+ * </ul>
311
+ * );
312
+ * }
313
+ * ```
314
+ */
315
+ declare function useQuotaPackages(): {
316
+ packages: CreditPackage[];
317
+ isLoading: boolean;
318
+ error: Error | null;
319
+ refetch: () => Promise<void>;
320
+ };
265
321
 
266
322
  interface QuotaConnectButtonProps {
267
323
  /**
@@ -582,4 +638,4 @@ declare function parseWebhook(req: Request, secret: string): Promise<WebhookEven
582
638
  */
583
639
  declare function createWebhookHandler(secret: string, handlers: Partial<Record<WebhookEvent["type"], (event: WebhookEvent) => Promise<void>>>): (req: Request) => Promise<Response>;
584
640
 
585
- export { QuotaBalance, type QuotaBalanceProps, QuotaBuyCredits, type QuotaBuyCreditsProps, type QuotaConfig, QuotaConnectButton, type QuotaConnectButtonProps, QuotaContext, type QuotaContextValue, QuotaProvider, type QuotaProviderProps, QuotaUserMenu, type QuotaUserMenuProps, type VerifyWebhookOptions, type WebhookEvent, createQuotaMiddleware, createWebhookHandler, parseWebhook, useQuota, useQuotaAuth, useQuotaBalance, useQuotaUser, verifyWebhookSignature };
641
+ export { QuotaBalance, type QuotaBalanceProps, QuotaBuyCredits, type QuotaBuyCreditsProps, type QuotaConfig, QuotaConnectButton, type QuotaConnectButtonProps, QuotaContext, type QuotaContextValue, type QuotaFetchStrategy, QuotaProvider, type QuotaProviderProps, QuotaUserMenu, type QuotaUserMenuProps, type VerifyWebhookOptions, type WebhookEvent, createQuotaMiddleware, createWebhookHandler, parseWebhook, useQuota, useQuotaAuth, useQuotaBalance, useQuotaPackages, useQuotaUser, verifyWebhookSignature };
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
  import * as react from 'react';
4
4
  import { ReactNode } from 'react';
5
- import { QuotaUser } from '@usequota/types';
5
+ import { QuotaUser, CreditPackage } from '@usequota/types';
6
6
  export { ChatCompletionRequest, ChatCompletionResponse, ChatMessage, CreditPackage, QuotaError as QuotaErrorResponse, QuotaMetadata, QuotaSession, QuotaUser, Tool, ToolCall } from '@usequota/types';
7
7
  export { Q as QuotaError, a as QuotaInsufficientCreditsError, b as QuotaNotConnectedError, d as QuotaRateLimitError, c as QuotaTokenExpiredError } from './errors-CmNx3kSz.js';
8
8
 
@@ -115,8 +115,19 @@ interface QuotaContextValue {
115
115
  * Refetches user data from the server
116
116
  */
117
117
  refetch: () => Promise<void>;
118
+ /**
119
+ * Quota API base URL
120
+ */
121
+ baseUrl: string;
118
122
  }
119
123
  declare const QuotaContext: react.Context<QuotaContextValue | null>;
124
+ /**
125
+ * Controls when the provider fetches user data.
126
+ *
127
+ * - `"eager"` — fetches on mount (default, current behavior)
128
+ * - `"lazy"` — does NOT fetch on mount; only fetches when `refetch()` is called
129
+ */
130
+ type QuotaFetchStrategy = "eager" | "lazy";
120
131
  interface QuotaProviderProps {
121
132
  children: ReactNode;
122
133
  /**
@@ -138,6 +149,15 @@ interface QuotaProviderProps {
138
149
  * @default '/api/quota/me'
139
150
  */
140
151
  apiPath?: string;
152
+ /**
153
+ * Controls when user data is fetched.
154
+ *
155
+ * - `"eager"` (default) — fetches on mount
156
+ * - `"lazy"` — does NOT fetch on mount; only fetches when `refetch()` is called
157
+ *
158
+ * @default 'eager'
159
+ */
160
+ fetchStrategy?: "eager" | "lazy";
141
161
  }
142
162
  /**
143
163
  * React context provider for Quota authentication
@@ -160,7 +180,7 @@ interface QuotaProviderProps {
160
180
  * }
161
181
  * ```
162
182
  */
163
- declare function QuotaProvider({ children, clientId, baseUrl, callbackPath, apiPath, }: QuotaProviderProps): react_jsx_runtime.JSX.Element;
183
+ declare function QuotaProvider({ children, clientId, baseUrl, callbackPath, apiPath, fetchStrategy, }: QuotaProviderProps): react_jsx_runtime.JSX.Element;
164
184
  /**
165
185
  * Hook to access Quota authentication context
166
186
  *
@@ -262,6 +282,42 @@ declare function useQuotaBalance(): {
262
282
  error: Error | null;
263
283
  refetch: () => Promise<void>;
264
284
  };
285
+ /**
286
+ * Hook to fetch available credit packages from the Quota API
287
+ *
288
+ * Fetches from the public `/v1/packages` endpoint (no auth required).
289
+ * Results are cached for the lifetime of the component — subsequent calls
290
+ * return the cached data without re-fetching.
291
+ *
292
+ * @example
293
+ * ```tsx
294
+ * 'use client';
295
+ * import { useQuotaPackages } from '@usequota/nextjs';
296
+ *
297
+ * export function BuyCredits() {
298
+ * const { packages, isLoading, error, refetch } = useQuotaPackages();
299
+ *
300
+ * if (isLoading) return <div>Loading packages...</div>;
301
+ * if (error) return <div>Error: {error.message}</div>;
302
+ *
303
+ * return (
304
+ * <ul>
305
+ * {packages.map((pkg) => (
306
+ * <li key={pkg.id}>
307
+ * {pkg.credits} credits — {pkg.price_display}
308
+ * </li>
309
+ * ))}
310
+ * </ul>
311
+ * );
312
+ * }
313
+ * ```
314
+ */
315
+ declare function useQuotaPackages(): {
316
+ packages: CreditPackage[];
317
+ isLoading: boolean;
318
+ error: Error | null;
319
+ refetch: () => Promise<void>;
320
+ };
265
321
 
266
322
  interface QuotaConnectButtonProps {
267
323
  /**
@@ -582,4 +638,4 @@ declare function parseWebhook(req: Request, secret: string): Promise<WebhookEven
582
638
  */
583
639
  declare function createWebhookHandler(secret: string, handlers: Partial<Record<WebhookEvent["type"], (event: WebhookEvent) => Promise<void>>>): (req: Request) => Promise<Response>;
584
640
 
585
- export { QuotaBalance, type QuotaBalanceProps, QuotaBuyCredits, type QuotaBuyCreditsProps, type QuotaConfig, QuotaConnectButton, type QuotaConnectButtonProps, QuotaContext, type QuotaContextValue, QuotaProvider, type QuotaProviderProps, QuotaUserMenu, type QuotaUserMenuProps, type VerifyWebhookOptions, type WebhookEvent, createQuotaMiddleware, createWebhookHandler, parseWebhook, useQuota, useQuotaAuth, useQuotaBalance, useQuotaUser, verifyWebhookSignature };
641
+ export { QuotaBalance, type QuotaBalanceProps, QuotaBuyCredits, type QuotaBuyCreditsProps, type QuotaConfig, QuotaConnectButton, type QuotaConnectButtonProps, QuotaContext, type QuotaContextValue, type QuotaFetchStrategy, QuotaProvider, type QuotaProviderProps, QuotaUserMenu, type QuotaUserMenuProps, type VerifyWebhookOptions, type WebhookEvent, createQuotaMiddleware, createWebhookHandler, parseWebhook, useQuota, useQuotaAuth, useQuotaBalance, useQuotaPackages, useQuotaUser, verifyWebhookSignature };
package/dist/index.js CHANGED
@@ -47,6 +47,7 @@ __export(index_exports, {
47
47
  useQuota: () => useQuota,
48
48
  useQuotaAuth: () => useQuotaAuth,
49
49
  useQuotaBalance: () => useQuotaBalance,
50
+ useQuotaPackages: () => useQuotaPackages,
50
51
  useQuotaUser: () => useQuotaUser,
51
52
  verifyWebhookSignature: () => verifyWebhookSignature
52
53
  });
@@ -210,10 +211,12 @@ function QuotaProvider({
210
211
  clientId,
211
212
  baseUrl = DEFAULT_BASE_URL2,
212
213
  callbackPath = DEFAULT_CALLBACK_PATH2,
213
- apiPath = DEFAULT_API_PATH
214
+ apiPath = DEFAULT_API_PATH,
215
+ fetchStrategy = "eager"
214
216
  }) {
217
+ const isLazy = fetchStrategy === "lazy";
215
218
  const [user, setUser] = (0, import_react.useState)(null);
216
- const [isLoading, setIsLoading] = (0, import_react.useState)(true);
219
+ const [isLoading, setIsLoading] = (0, import_react.useState)(!isLazy);
217
220
  const [error, setError] = (0, import_react.useState)(null);
218
221
  const fetchUser = (0, import_react.useCallback)(async () => {
219
222
  try {
@@ -240,8 +243,9 @@ function QuotaProvider({
240
243
  }
241
244
  }, [apiPath]);
242
245
  (0, import_react.useEffect)(() => {
246
+ if (isLazy) return;
243
247
  void fetchUser();
244
- }, [fetchUser]);
248
+ }, [fetchUser, isLazy]);
245
249
  const login = (0, import_react.useCallback)(() => {
246
250
  const state = generateRandomState();
247
251
  if (typeof window !== "undefined") {
@@ -283,7 +287,8 @@ function QuotaProvider({
283
287
  error,
284
288
  login,
285
289
  logout,
286
- refetch
290
+ refetch,
291
+ baseUrl
287
292
  };
288
293
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QuotaContext.Provider, { value, children });
289
294
  }
@@ -311,6 +316,7 @@ function getRedirectUri(callbackPath) {
311
316
  }
312
317
 
313
318
  // src/hooks.ts
319
+ var import_react2 = require("react");
314
320
  function useQuotaUser() {
315
321
  const { user } = useQuota();
316
322
  return user;
@@ -333,9 +339,42 @@ function useQuotaBalance() {
333
339
  refetch
334
340
  };
335
341
  }
342
+ function useQuotaPackages() {
343
+ const { baseUrl } = useQuota();
344
+ const [packages, setPackages] = (0, import_react2.useState)([]);
345
+ const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
346
+ const [error, setError] = (0, import_react2.useState)(null);
347
+ const hasFetched = (0, import_react2.useRef)(false);
348
+ const fetchPackages = (0, import_react2.useCallback)(async () => {
349
+ try {
350
+ setIsLoading(true);
351
+ setError(null);
352
+ const response = await fetch(`${baseUrl}/v1/packages`);
353
+ if (!response.ok) {
354
+ throw new Error(`Failed to fetch packages: ${response.statusText}`);
355
+ }
356
+ const data = await response.json();
357
+ setPackages(data.packages);
358
+ hasFetched.current = true;
359
+ } catch (err) {
360
+ const errorObj = err instanceof Error ? err : new Error("Unknown error");
361
+ setError(errorObj);
362
+ } finally {
363
+ setIsLoading(false);
364
+ }
365
+ }, [baseUrl]);
366
+ if (!hasFetched.current && !isLoading && error === null) {
367
+ void fetchPackages();
368
+ }
369
+ const refetch = (0, import_react2.useCallback)(async () => {
370
+ hasFetched.current = false;
371
+ await fetchPackages();
372
+ }, [fetchPackages]);
373
+ return { packages, isLoading, error, refetch };
374
+ }
336
375
 
337
376
  // src/components/QuotaConnectButton.tsx
338
- var import_react2 = require("react");
377
+ var import_react3 = require("react");
339
378
  var import_jsx_runtime2 = require("react/jsx-runtime");
340
379
  function QuotaConnectButton({
341
380
  children,
@@ -347,9 +386,9 @@ function QuotaConnectButton({
347
386
  showWhenConnected = false
348
387
  }) {
349
388
  const { user, login, isLoading, error } = useQuota();
350
- const wasLoadingRef = (0, import_react2.useRef)(isLoading);
351
- const hadUserRef = (0, import_react2.useRef)(!!user);
352
- (0, import_react2.useEffect)(() => {
389
+ const wasLoadingRef = (0, import_react3.useRef)(isLoading);
390
+ const hadUserRef = (0, import_react3.useRef)(!!user);
391
+ (0, import_react3.useEffect)(() => {
353
392
  const wasLoading = wasLoadingRef.current;
354
393
  const hadUser = hadUserRef.current;
355
394
  wasLoadingRef.current = isLoading;
@@ -358,7 +397,7 @@ function QuotaConnectButton({
358
397
  onSuccess?.();
359
398
  }
360
399
  }, [user, isLoading, onSuccess]);
361
- const handleClick = (0, import_react2.useCallback)(() => {
400
+ const handleClick = (0, import_react3.useCallback)(() => {
362
401
  try {
363
402
  login();
364
403
  } catch (err) {
@@ -595,7 +634,7 @@ function ErrorIcon() {
595
634
  }
596
635
 
597
636
  // src/components/QuotaBuyCredits.tsx
598
- var import_react3 = require("react");
637
+ var import_react4 = require("react");
599
638
  var import_jsx_runtime4 = require("react/jsx-runtime");
600
639
  function QuotaBuyCredits({
601
640
  packageId,
@@ -609,9 +648,9 @@ function QuotaBuyCredits({
609
648
  disabled = false
610
649
  }) {
611
650
  const { user } = useQuota();
612
- const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
613
- const [error, setError] = (0, import_react3.useState)(null);
614
- const handlePurchase = (0, import_react3.useCallback)(async () => {
651
+ const [isLoading, setIsLoading] = (0, import_react4.useState)(false);
652
+ const [error, setError] = (0, import_react4.useState)(null);
653
+ const handlePurchase = (0, import_react4.useCallback)(async () => {
615
654
  if (!user) {
616
655
  const err = new Error("Must be logged in to purchase credits");
617
656
  setError(err);
@@ -704,7 +743,7 @@ function CartIcon() {
704
743
  }
705
744
 
706
745
  // src/components/QuotaUserMenu.tsx
707
- var import_react4 = require("react");
746
+ var import_react5 = require("react");
708
747
  var import_jsx_runtime5 = require("react/jsx-runtime");
709
748
  function QuotaUserMenu({
710
749
  className,
@@ -715,11 +754,11 @@ function QuotaUserMenu({
715
754
  }) {
716
755
  const { user, logout, isLoading: authLoading } = useQuota();
717
756
  const { balance } = useQuotaBalance();
718
- const [isOpen, setIsOpen] = (0, import_react4.useState)(false);
719
- const [isLoggingOut, setIsLoggingOut] = (0, import_react4.useState)(false);
720
- const menuRef = (0, import_react4.useRef)(null);
721
- const triggerRef = (0, import_react4.useRef)(null);
722
- (0, import_react4.useEffect)(() => {
757
+ const [isOpen, setIsOpen] = (0, import_react5.useState)(false);
758
+ const [isLoggingOut, setIsLoggingOut] = (0, import_react5.useState)(false);
759
+ const menuRef = (0, import_react5.useRef)(null);
760
+ const triggerRef = (0, import_react5.useRef)(null);
761
+ (0, import_react5.useEffect)(() => {
723
762
  if (!isOpen) return;
724
763
  const handleClickOutside = (event) => {
725
764
  if (menuRef.current && !menuRef.current.contains(event.target)) {
@@ -739,10 +778,10 @@ function QuotaUserMenu({
739
778
  document.removeEventListener("keydown", handleEscape);
740
779
  };
741
780
  }, [isOpen]);
742
- const handleToggle = (0, import_react4.useCallback)(() => {
781
+ const handleToggle = (0, import_react5.useCallback)(() => {
743
782
  setIsOpen((prev) => !prev);
744
783
  }, []);
745
- const handleKeyDown = (0, import_react4.useCallback)(
784
+ const handleKeyDown = (0, import_react5.useCallback)(
746
785
  (event) => {
747
786
  if (event.key === "Enter" || event.key === " ") {
748
787
  event.preventDefault();
@@ -755,7 +794,7 @@ function QuotaUserMenu({
755
794
  },
756
795
  [isOpen]
757
796
  );
758
- const handleLogout = (0, import_react4.useCallback)(async () => {
797
+ const handleLogout = (0, import_react5.useCallback)(async () => {
759
798
  setIsLoggingOut(true);
760
799
  try {
761
800
  await logout();
@@ -765,7 +804,7 @@ function QuotaUserMenu({
765
804
  setIsOpen(false);
766
805
  }
767
806
  }, [logout, onLogout]);
768
- const handleBuyCredits = (0, import_react4.useCallback)(() => {
807
+ const handleBuyCredits = (0, import_react5.useCallback)(() => {
769
808
  setIsOpen(false);
770
809
  onBuyCredits?.();
771
810
  }, [onBuyCredits]);
@@ -1074,6 +1113,7 @@ function createWebhookHandler(secret, handlers) {
1074
1113
  useQuota,
1075
1114
  useQuotaAuth,
1076
1115
  useQuotaBalance,
1116
+ useQuotaPackages,
1077
1117
  useQuotaUser,
1078
1118
  verifyWebhookSignature
1079
1119
  });
package/dist/index.mjs CHANGED
@@ -170,10 +170,12 @@ function QuotaProvider({
170
170
  clientId,
171
171
  baseUrl = DEFAULT_BASE_URL2,
172
172
  callbackPath = DEFAULT_CALLBACK_PATH2,
173
- apiPath = DEFAULT_API_PATH
173
+ apiPath = DEFAULT_API_PATH,
174
+ fetchStrategy = "eager"
174
175
  }) {
176
+ const isLazy = fetchStrategy === "lazy";
175
177
  const [user, setUser] = useState(null);
176
- const [isLoading, setIsLoading] = useState(true);
178
+ const [isLoading, setIsLoading] = useState(!isLazy);
177
179
  const [error, setError] = useState(null);
178
180
  const fetchUser = useCallback(async () => {
179
181
  try {
@@ -200,8 +202,9 @@ function QuotaProvider({
200
202
  }
201
203
  }, [apiPath]);
202
204
  useEffect(() => {
205
+ if (isLazy) return;
203
206
  void fetchUser();
204
- }, [fetchUser]);
207
+ }, [fetchUser, isLazy]);
205
208
  const login = useCallback(() => {
206
209
  const state = generateRandomState();
207
210
  if (typeof window !== "undefined") {
@@ -243,7 +246,8 @@ function QuotaProvider({
243
246
  error,
244
247
  login,
245
248
  logout,
246
- refetch
249
+ refetch,
250
+ baseUrl
247
251
  };
248
252
  return /* @__PURE__ */ jsx(QuotaContext.Provider, { value, children });
249
253
  }
@@ -271,6 +275,7 @@ function getRedirectUri(callbackPath) {
271
275
  }
272
276
 
273
277
  // src/hooks.ts
278
+ import { useState as useState2, useCallback as useCallback2, useRef } from "react";
274
279
  function useQuotaUser() {
275
280
  const { user } = useQuota();
276
281
  return user;
@@ -293,9 +298,42 @@ function useQuotaBalance() {
293
298
  refetch
294
299
  };
295
300
  }
301
+ function useQuotaPackages() {
302
+ const { baseUrl } = useQuota();
303
+ const [packages, setPackages] = useState2([]);
304
+ const [isLoading, setIsLoading] = useState2(false);
305
+ const [error, setError] = useState2(null);
306
+ const hasFetched = useRef(false);
307
+ const fetchPackages = useCallback2(async () => {
308
+ try {
309
+ setIsLoading(true);
310
+ setError(null);
311
+ const response = await fetch(`${baseUrl}/v1/packages`);
312
+ if (!response.ok) {
313
+ throw new Error(`Failed to fetch packages: ${response.statusText}`);
314
+ }
315
+ const data = await response.json();
316
+ setPackages(data.packages);
317
+ hasFetched.current = true;
318
+ } catch (err) {
319
+ const errorObj = err instanceof Error ? err : new Error("Unknown error");
320
+ setError(errorObj);
321
+ } finally {
322
+ setIsLoading(false);
323
+ }
324
+ }, [baseUrl]);
325
+ if (!hasFetched.current && !isLoading && error === null) {
326
+ void fetchPackages();
327
+ }
328
+ const refetch = useCallback2(async () => {
329
+ hasFetched.current = false;
330
+ await fetchPackages();
331
+ }, [fetchPackages]);
332
+ return { packages, isLoading, error, refetch };
333
+ }
296
334
 
297
335
  // src/components/QuotaConnectButton.tsx
298
- import { useCallback as useCallback2, useEffect as useEffect2, useRef } from "react";
336
+ import { useCallback as useCallback3, useEffect as useEffect2, useRef as useRef2 } from "react";
299
337
  import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
300
338
  function QuotaConnectButton({
301
339
  children,
@@ -307,8 +345,8 @@ function QuotaConnectButton({
307
345
  showWhenConnected = false
308
346
  }) {
309
347
  const { user, login, isLoading, error } = useQuota();
310
- const wasLoadingRef = useRef(isLoading);
311
- const hadUserRef = useRef(!!user);
348
+ const wasLoadingRef = useRef2(isLoading);
349
+ const hadUserRef = useRef2(!!user);
312
350
  useEffect2(() => {
313
351
  const wasLoading = wasLoadingRef.current;
314
352
  const hadUser = hadUserRef.current;
@@ -318,7 +356,7 @@ function QuotaConnectButton({
318
356
  onSuccess?.();
319
357
  }
320
358
  }, [user, isLoading, onSuccess]);
321
- const handleClick = useCallback2(() => {
359
+ const handleClick = useCallback3(() => {
322
360
  try {
323
361
  login();
324
362
  } catch (err) {
@@ -555,7 +593,7 @@ function ErrorIcon() {
555
593
  }
556
594
 
557
595
  // src/components/QuotaBuyCredits.tsx
558
- import { useState as useState2, useCallback as useCallback3 } from "react";
596
+ import { useState as useState3, useCallback as useCallback4 } from "react";
559
597
  import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
560
598
  function QuotaBuyCredits({
561
599
  packageId,
@@ -569,9 +607,9 @@ function QuotaBuyCredits({
569
607
  disabled = false
570
608
  }) {
571
609
  const { user } = useQuota();
572
- const [isLoading, setIsLoading] = useState2(false);
573
- const [error, setError] = useState2(null);
574
- const handlePurchase = useCallback3(async () => {
610
+ const [isLoading, setIsLoading] = useState3(false);
611
+ const [error, setError] = useState3(null);
612
+ const handlePurchase = useCallback4(async () => {
575
613
  if (!user) {
576
614
  const err = new Error("Must be logged in to purchase credits");
577
615
  setError(err);
@@ -665,9 +703,9 @@ function CartIcon() {
665
703
 
666
704
  // src/components/QuotaUserMenu.tsx
667
705
  import {
668
- useState as useState3,
669
- useCallback as useCallback4,
670
- useRef as useRef2,
706
+ useState as useState4,
707
+ useCallback as useCallback5,
708
+ useRef as useRef3,
671
709
  useEffect as useEffect3
672
710
  } from "react";
673
711
  import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
@@ -680,10 +718,10 @@ function QuotaUserMenu({
680
718
  }) {
681
719
  const { user, logout, isLoading: authLoading } = useQuota();
682
720
  const { balance } = useQuotaBalance();
683
- const [isOpen, setIsOpen] = useState3(false);
684
- const [isLoggingOut, setIsLoggingOut] = useState3(false);
685
- const menuRef = useRef2(null);
686
- const triggerRef = useRef2(null);
721
+ const [isOpen, setIsOpen] = useState4(false);
722
+ const [isLoggingOut, setIsLoggingOut] = useState4(false);
723
+ const menuRef = useRef3(null);
724
+ const triggerRef = useRef3(null);
687
725
  useEffect3(() => {
688
726
  if (!isOpen) return;
689
727
  const handleClickOutside = (event) => {
@@ -704,10 +742,10 @@ function QuotaUserMenu({
704
742
  document.removeEventListener("keydown", handleEscape);
705
743
  };
706
744
  }, [isOpen]);
707
- const handleToggle = useCallback4(() => {
745
+ const handleToggle = useCallback5(() => {
708
746
  setIsOpen((prev) => !prev);
709
747
  }, []);
710
- const handleKeyDown = useCallback4(
748
+ const handleKeyDown = useCallback5(
711
749
  (event) => {
712
750
  if (event.key === "Enter" || event.key === " ") {
713
751
  event.preventDefault();
@@ -720,7 +758,7 @@ function QuotaUserMenu({
720
758
  },
721
759
  [isOpen]
722
760
  );
723
- const handleLogout = useCallback4(async () => {
761
+ const handleLogout = useCallback5(async () => {
724
762
  setIsLoggingOut(true);
725
763
  try {
726
764
  await logout();
@@ -730,7 +768,7 @@ function QuotaUserMenu({
730
768
  setIsOpen(false);
731
769
  }
732
770
  }, [logout, onLogout]);
733
- const handleBuyCredits = useCallback4(() => {
771
+ const handleBuyCredits = useCallback5(() => {
734
772
  setIsOpen(false);
735
773
  onBuyCredits?.();
736
774
  }, [onBuyCredits]);
@@ -963,6 +1001,7 @@ export {
963
1001
  useQuota,
964
1002
  useQuotaAuth,
965
1003
  useQuotaBalance,
1004
+ useQuotaPackages,
966
1005
  useQuotaUser,
967
1006
  verifyWebhookSignature
968
1007
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usequota/nextjs",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Next.js SDK for Quota — AI credit billing middleware, hooks, and components",
5
5
  "license": "MIT",
6
6
  "repository": {