@imtbl/auth-nextjs 2.12.4-alpha.6 → 2.12.4-alpha.7

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/README.md CHANGED
@@ -61,10 +61,12 @@ const config = {
61
61
  };
62
62
 
63
63
  export default function Callback() {
64
- return <CallbackPage config={config} />;
64
+ return <CallbackPage config={config} redirectTo="/dashboard" />;
65
65
  }
66
66
  ```
67
67
 
68
+ See [CallbackPage Props](#callbackpage-props) for all available options.
69
+
68
70
  ### 4. Add Provider to Layout
69
71
 
70
72
  ```typescript
@@ -223,6 +225,69 @@ openssl rand -base64 32
223
225
  | `useAccessToken()` | Hook returning `getAccessToken` function |
224
226
  | `CallbackPage` | Pre-built callback page component for OAuth redirects |
225
227
 
228
+ #### CallbackPage Props
229
+
230
+ | Prop | Type | Default | Description |
231
+ | ------------------ | ----------------------------------------------------- | ------- | ------------------------------------------------------------------ |
232
+ | `config` | `ImmutableAuthConfig` | - | Required. Immutable auth configuration |
233
+ | `redirectTo` | `string \| ((user: ImmutableUser) => string \| void)` | `"/"` | Where to redirect after successful auth (supports dynamic routing) |
234
+ | `loadingComponent` | `React.ReactElement \| null` | `null` | Custom loading UI while processing authentication |
235
+ | `errorComponent` | `(error: string) => React.ReactElement \| null` | - | Custom error UI component |
236
+ | `onSuccess` | `(user: ImmutableUser) => void \| Promise<void>` | - | Callback fired after successful authentication |
237
+ | `onError` | `(error: string) => void` | - | Callback fired when authentication fails |
238
+
239
+ **Example with all props:**
240
+
241
+ ```tsx
242
+ // app/callback/page.tsx
243
+ "use client";
244
+
245
+ import { CallbackPage } from "@imtbl/auth-nextjs/client";
246
+ import { Spinner } from "@/components/ui/spinner";
247
+
248
+ const config = {
249
+ clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
250
+ redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
251
+ };
252
+
253
+ export default function Callback() {
254
+ return (
255
+ <CallbackPage
256
+ config={config}
257
+ // Dynamic redirect based on user
258
+ redirectTo={(user) => {
259
+ if (user.email?.endsWith("@admin.com")) return "/admin";
260
+ return "/dashboard";
261
+ }}
262
+ // Custom loading UI
263
+ loadingComponent={
264
+ <div className="flex items-center justify-center min-h-screen">
265
+ <Spinner />
266
+ <span>Completing authentication...</span>
267
+ </div>
268
+ }
269
+ // Custom error UI
270
+ errorComponent={(error) => (
271
+ <div className="text-center p-8">
272
+ <h2 className="text-red-500">Authentication Error</h2>
273
+ <p>{error}</p>
274
+ <a href="/">Return Home</a>
275
+ </div>
276
+ )}
277
+ // Success callback for analytics
278
+ onSuccess={async (user) => {
279
+ await analytics.track("login_success", { userId: user.sub });
280
+ }}
281
+ // Error callback for logging
282
+ onError={(error) => {
283
+ console.error("Auth failed:", error);
284
+ Sentry.captureMessage(error);
285
+ }}
286
+ />
287
+ );
288
+ }
289
+ ```
290
+
226
291
  **`useImmutableAuth()` Return Value:**
227
292
 
228
293
  | Property | Type | Description |
@@ -262,7 +262,9 @@ function CallbackPage({
262
262
  config,
263
263
  redirectTo = "/",
264
264
  loadingComponent = null,
265
- errorComponent
265
+ errorComponent,
266
+ onSuccess,
267
+ onError
266
268
  }) {
267
269
  const router = (0, import_navigation.useRouter)();
268
270
  const searchParams = (0, import_navigation.useSearchParams)();
@@ -284,6 +286,14 @@ function CallbackPage({
284
286
  if (!authUser) {
285
287
  throw new Error("Authentication failed: no user data received from login callback");
286
288
  }
289
+ const user = {
290
+ sub: authUser.profile.sub,
291
+ email: authUser.profile.email,
292
+ nickname: authUser.profile.nickname
293
+ };
294
+ if (onSuccess) {
295
+ await onSuccess(user);
296
+ }
287
297
  window.close();
288
298
  } else if (authUser) {
289
299
  const tokenData = {
@@ -308,18 +318,34 @@ function CallbackPage({
308
318
  if (!result?.ok) {
309
319
  throw new Error("NextAuth sign-in failed: unknown error");
310
320
  }
311
- router.replace(redirectTo);
321
+ const user = {
322
+ sub: authUser.profile.sub,
323
+ email: authUser.profile.email,
324
+ nickname: authUser.profile.nickname
325
+ };
326
+ if (onSuccess) {
327
+ await onSuccess(user);
328
+ }
329
+ const resolvedRedirectTo = typeof redirectTo === "function" ? redirectTo(user) || "/" : redirectTo;
330
+ router.replace(resolvedRedirectTo);
312
331
  } else {
313
332
  throw new Error("Authentication failed: no user data received from login callback");
314
333
  }
315
334
  } catch (err) {
316
- setError(err instanceof Error ? err.message : "Authentication failed");
335
+ const errorMessage = err instanceof Error ? err.message : "Authentication failed";
336
+ if (onError) {
337
+ onError(errorMessage);
338
+ }
339
+ setError(errorMessage);
317
340
  }
318
341
  };
319
342
  const handleOAuthError = () => {
320
343
  const errorCode = searchParams.get("error");
321
344
  const errorDescription = searchParams.get("error_description");
322
345
  const errorMessage = errorDescription || errorCode || "Authentication failed";
346
+ if (onError) {
347
+ onError(errorMessage);
348
+ }
323
349
  setError(errorMessage);
324
350
  };
325
351
  if (searchParams.get("error")) {
@@ -330,7 +356,7 @@ function CallbackPage({
330
356
  callbackProcessedRef.current = true;
331
357
  handleCallback();
332
358
  }
333
- }, [searchParams, router, config, redirectTo]);
359
+ }, [searchParams, router, config, redirectTo, onSuccess, onError]);
334
360
  if (error) {
335
361
  if (errorComponent) {
336
362
  return errorComponent(error);
@@ -248,7 +248,9 @@ function CallbackPage({
248
248
  config,
249
249
  redirectTo = "/",
250
250
  loadingComponent = null,
251
- errorComponent
251
+ errorComponent,
252
+ onSuccess,
253
+ onError
252
254
  }) {
253
255
  const router = useRouter();
254
256
  const searchParams = useSearchParams();
@@ -270,6 +272,14 @@ function CallbackPage({
270
272
  if (!authUser) {
271
273
  throw new Error("Authentication failed: no user data received from login callback");
272
274
  }
275
+ const user = {
276
+ sub: authUser.profile.sub,
277
+ email: authUser.profile.email,
278
+ nickname: authUser.profile.nickname
279
+ };
280
+ if (onSuccess) {
281
+ await onSuccess(user);
282
+ }
273
283
  window.close();
274
284
  } else if (authUser) {
275
285
  const tokenData = {
@@ -294,18 +304,34 @@ function CallbackPage({
294
304
  if (!result?.ok) {
295
305
  throw new Error("NextAuth sign-in failed: unknown error");
296
306
  }
297
- router.replace(redirectTo);
307
+ const user = {
308
+ sub: authUser.profile.sub,
309
+ email: authUser.profile.email,
310
+ nickname: authUser.profile.nickname
311
+ };
312
+ if (onSuccess) {
313
+ await onSuccess(user);
314
+ }
315
+ const resolvedRedirectTo = typeof redirectTo === "function" ? redirectTo(user) || "/" : redirectTo;
316
+ router.replace(resolvedRedirectTo);
298
317
  } else {
299
318
  throw new Error("Authentication failed: no user data received from login callback");
300
319
  }
301
320
  } catch (err) {
302
- setError(err instanceof Error ? err.message : "Authentication failed");
321
+ const errorMessage = err instanceof Error ? err.message : "Authentication failed";
322
+ if (onError) {
323
+ onError(errorMessage);
324
+ }
325
+ setError(errorMessage);
303
326
  }
304
327
  };
305
328
  const handleOAuthError = () => {
306
329
  const errorCode = searchParams.get("error");
307
330
  const errorDescription = searchParams.get("error_description");
308
331
  const errorMessage = errorDescription || errorCode || "Authentication failed";
332
+ if (onError) {
333
+ onError(errorMessage);
334
+ }
309
335
  setError(errorMessage);
310
336
  };
311
337
  if (searchParams.get("error")) {
@@ -316,7 +342,7 @@ function CallbackPage({
316
342
  callbackProcessedRef.current = true;
317
343
  handleCallback();
318
344
  }
319
- }, [searchParams, router, config, redirectTo]);
345
+ }, [searchParams, router, config, redirectTo, onSuccess, onError]);
320
346
  if (error) {
321
347
  if (errorComponent) {
322
348
  return errorComponent(error);
@@ -1,14 +1,30 @@
1
1
  import React from 'react';
2
- import type { ImmutableAuthConfig } from '../types';
2
+ import type { ImmutableAuthConfig, ImmutableUser } from '../types';
3
3
  export interface CallbackPageProps {
4
4
  /**
5
5
  * Immutable auth configuration
6
6
  */
7
7
  config: ImmutableAuthConfig;
8
8
  /**
9
- * URL to redirect to after successful authentication (when not in popup)
9
+ * URL to redirect to after successful authentication (when not in popup).
10
+ * Can be a string or a function that receives the authenticated user.
11
+ * If a function returns void/undefined, defaults to "/".
12
+ * @default "/"
13
+ *
14
+ * @example Static redirect
15
+ * ```tsx
16
+ * <CallbackPage config={config} redirectTo="/dashboard" />
17
+ * ```
18
+ *
19
+ * @example Dynamic redirect based on user
20
+ * ```tsx
21
+ * <CallbackPage
22
+ * config={config}
23
+ * redirectTo={(user) => user.email?.endsWith('@admin.com') ? '/admin' : '/dashboard'}
24
+ * />
25
+ * ```
10
26
  */
11
- redirectTo?: string;
27
+ redirectTo?: string | ((user: ImmutableUser) => string | void);
12
28
  /**
13
29
  * Custom loading component
14
30
  */
@@ -17,6 +33,19 @@ export interface CallbackPageProps {
17
33
  * Custom error component
18
34
  */
19
35
  errorComponent?: (error: string) => React.ReactElement | null;
36
+ /**
37
+ * Callback fired after successful authentication.
38
+ * Receives the authenticated user as a parameter.
39
+ * Called before redirect (non-popup) or before window.close (popup).
40
+ * If this callback returns a Promise, it will be awaited before proceeding.
41
+ */
42
+ onSuccess?: (user: ImmutableUser) => void | Promise<void>;
43
+ /**
44
+ * Callback fired when authentication fails.
45
+ * Receives the error message as a parameter.
46
+ * Called before the error UI is displayed.
47
+ */
48
+ onError?: (error: string) => void;
20
49
  }
21
50
  /**
22
51
  * Callback page component for handling OAuth redirects (App Router version).
@@ -39,4 +68,4 @@ export interface CallbackPageProps {
39
68
  * }
40
69
  * ```
41
70
  */
42
- export declare function CallbackPage({ config, redirectTo, loadingComponent, errorComponent, }: CallbackPageProps): import("react/jsx-runtime").JSX.Element | null;
71
+ export declare function CallbackPage({ config, redirectTo, loadingComponent, errorComponent, onSuccess, onError, }: CallbackPageProps): import("react/jsx-runtime").JSX.Element | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imtbl/auth-nextjs",
3
- "version": "2.12.4-alpha.6",
3
+ "version": "2.12.4-alpha.7",
4
4
  "description": "Next.js App Router authentication integration for Immutable SDK using Auth.js v5",
5
5
  "author": "Immutable",
6
6
  "bugs": "https://github.com/immutable/ts-immutable-sdk/issues",
@@ -51,7 +51,7 @@
51
51
  "dist"
52
52
  ],
53
53
  "dependencies": {
54
- "@imtbl/auth": "2.12.4-alpha.6"
54
+ "@imtbl/auth": "2.12.4-alpha.7"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "next": "14.2.25",