diff --git a/examples/README.md b/examples/README.md index b061fd5d45..3d967f2c49 100644 --- a/examples/README.md +++ b/examples/README.md @@ -329,11 +329,11 @@ pnpm test # Generating example apps using cursor -1) Open the product folder under /examples/{product} +1) Open the product folder under /examples/{product}/_prompts -2) Under the {product} directory, open the prompt-part2.txt file. +2) Under the {product}/_prompts directory, open the example-app-2-add-feature.txt file. -3) Copy all of the content in the prompt-part2.txt file. +3) Copy all of the content in the example-app-2-add-feature.txt file. 4) Paste it on to Cursor. Make sure that you're using Agent mode and the model to be used is Claude 3.7 (Thinking) @@ -347,7 +347,7 @@ the has the manually-edited field value as true in the feature.js 7) If the flag above exists, it will ask for a confirmation, otherwise, it will use best practices to update the existing feature page. -8) Once the feature page is completed, under the same directory as prompt-part2.txt, open prompt-part3.txt and copy all of its contents. +8) Once the feature page is completed, under the same directory as example-app-2-add-feature.txt, open example-app-3-fix-ui.txt and copy all of its contents. 9) Paste it on to a new Cursor window and in the chat window you can type: @@ -355,7 +355,7 @@ feature name: Here, cursor will check and fix pages with styling issues to ensure that it looks consistent with other example apps. -10) Once the styling changes have been made, under the same directory as prompt-part2.txt, open prompt-part4.txt and copy all of its contents. +10) Once the styling changes have been made, under the same directory as example-app-2-add-feature.txt, open example-app-4-testing.txt and copy all of its contents. 11) Paste it on to a new Cursor window and in the chat window you can type: app name: @@ -369,7 +369,7 @@ feature name: ## If an App Doesn't Exist Yet -You must create the app first by going to the prompt-part1.txt file and paste it on to Cursor's chat window and use Agent Mode + Claude 3.7 Sonnet Thinking. +You must create the app first by going to the example-app-1-create-app-template.txt file and paste it on to Cursor's chat window and use Agent Mode + Claude 3.7 Sonnet Thinking. In the chat window, type in: feature name: @@ -383,35 +383,35 @@ IMPORTANT: For any prompts, if cursor is not done with its operations but it has The example generation process uses three different prompt files, each with a specific purpose: -### prompt-part1.txt +### example-app-1-create-app-template.txt - **Purpose**: Creates the initial app structure and boilerplate - **When to use**: Only when you need to create a completely new example app - **What it does**: Sets up the project structure, configuration files, basic components, and placeholder pages - **Output**: A functioning but minimal app with no implemented features -### prompt-part2.txt +### example-app-2-add-feature.txt - **Purpose**: Implements or updates a specific feature within an existing app -- **When to use**: After creating an app with prompt-part1.txt, or when adding/updating features +- **When to use**: After creating an app with example-app-1-create-app-template.txt, or when adding/updating features - **What it does**: Creates or modifies the feature implementation with proper error handling, UI states, and best practices - **Output**: A fully implemented feature page within the app structure -### prompt-part3.txt +### example-app-3-fix-ui.txt - **Purpose**: Apply styling changes to the given example app if possible to ensure styling consistency with other example apps -- **When to use**: After running prompt-part2.txt to implement a feature +- **When to use**: After running example-app-2-add-feature.txt to implement a feature - **What it does**: Modifies the example app's UI/UX to ensure that the app looks consistent with other example apps - **Output**: A fully implemented feature page within the app structure with consistent UI/UX looks. -### prompt-part4.txt +### example-app-4-testing.txt - **Purpose**: Tests, validates, and fixes issues in the implementation -- **When to use**: After running prompt-part2.txt to implement a feature +- **When to use**: After running example-app-2-add-feature.txt to implement a feature - **What it does**: Runs tests, checks coverage, builds the app, and fixes any detected issues - **Output**: A tested, validated feature ready for use **Typical Workflow:** -1. Use prompt-part1.txt to create a new app (only once per app) -1. Use prompt-part2.txt to implement each feature in the app -1. Use prompt-part3.txt to fix the app's UI/UX styling and make it look consistent to other example apps. -1. Use prompt-part4.txt after each feature implementation to test and validate +1. Use example-app-1-create-app-template.txt to create a new app (only once per app) +1. Use example-app-2-add-feature.txt to implement each feature in the app +1. Use example-app-3-fix-ui.txt to fix the app's UI/UX styling and make it look consistent to other example apps. +1. Use example-app-4-testing.txt after each feature implementation to test and validate 1. Once you've generated the example app or feature, you should manually review the implementation and the UI. It's likely you will need to make some manual adjustments to get the app to function and look like our other example apps because the cursor can not reliably get it 100% correct. 1. Once you're happy with your example app or feature, you need to [re-run the tutorial generation prompt](#generating-tutorials-and-metadata-with-cursor) for your example app before creating your PR so the new example app or feature is piped into the docs site with it's corresponding tutorial. @@ -507,7 +507,7 @@ If this happens you will need to log into the Netlify site, check the error and # Generating tutorials and metadata with cursor -Whenever you add a new example app, or update an existing example app, you can use the prompts in the `prompt.txt` files in each `examples/product` folder to generate the tutorials and metadata for the example apps using Cursor AI. +Whenever you add a new example app, or update an existing example app, you can use the prompts in the `tutorial-generation-prompt.txt` files in each `examples/product/_prompts` folder to generate the tutorials and metadata for the example apps using Cursor AI. These AI generated tutorials and metadata files are then piped through to the docs site in the CI/CD pipeline, where they are used to display the example apps and their code walkthroughs. If you don't follow these steps, your example app will not be displayed on the docs site. @@ -520,7 +520,7 @@ Follow these steps to generate the tutorials and metadata for the example apps: 1. Delete the existing tutorial.md and metadata.json files in the example app you are wanting to generate the tutorials and metadata for. 2. Open the Composer window in Cursor IDE (Claude 3.7-sonnet-thinking). 3. Press the `+` button clear the context of the composer window. -4. Open the `prompt.txt` file in the examples/product folder you are wanting to generate the tutorials and metadata for e.g. `examples/passport/prompt.txt`. +4. Open the `tutorial-generation-prompt.txt` file in the examples/product/_prompts folder you are wanting to generate the tutorials and metadata for e.g. `examples/passport/_prompts/tutorial-generation-prompt.txt`. 5. Copy and pate the prompt into the composer window, or attach it as a file. 6. After adding the prompt, in the composer window, type `app name: ` e.g. `app name: login-with-nextjs` where the app name is the folder name of the example app in the examples/product folder you are wanting to generate the tutorials and metadata for. 7. Press enter and let the AI generate the tutorials and metadata. diff --git a/examples/checkout/prompt-part1.txt b/examples/checkout/_prompts/example-app-1-create-app-template.txt similarity index 100% rename from examples/checkout/prompt-part1.txt rename to examples/checkout/_prompts/example-app-1-create-app-template.txt diff --git a/examples/checkout/prompt-part2.txt b/examples/checkout/_prompts/example-app-2-add-feature.txt similarity index 100% rename from examples/checkout/prompt-part2.txt rename to examples/checkout/_prompts/example-app-2-add-feature.txt diff --git a/examples/checkout/prompt-part3.txt b/examples/checkout/_prompts/example-app-3-fix-ui.txt similarity index 100% rename from examples/checkout/prompt-part3.txt rename to examples/checkout/_prompts/example-app-3-fix-ui.txt diff --git a/examples/checkout/prompt-part4.txt b/examples/checkout/_prompts/example-app-4-testing.txt similarity index 100% rename from examples/checkout/prompt-part4.txt rename to examples/checkout/_prompts/example-app-4-testing.txt diff --git a/examples/checkout/prompt.txt b/examples/checkout/_prompts/tutorial-generation-prompt.txt similarity index 100% rename from examples/checkout/prompt.txt rename to examples/checkout/_prompts/tutorial-generation-prompt.txt diff --git a/examples/contracts/prompt-part1.txt b/examples/contracts/_prompts/example-app-1-create-app-template.txt similarity index 100% rename from examples/contracts/prompt-part1.txt rename to examples/contracts/_prompts/example-app-1-create-app-template.txt diff --git a/examples/contracts/prompt-part2.txt b/examples/contracts/_prompts/example-app-2-add-feature.txt similarity index 100% rename from examples/contracts/prompt-part2.txt rename to examples/contracts/_prompts/example-app-2-add-feature.txt diff --git a/examples/contracts/prompt-part3.txt b/examples/contracts/_prompts/example-app-3-fix-ui.txt similarity index 100% rename from examples/contracts/prompt-part3.txt rename to examples/contracts/_prompts/example-app-3-fix-ui.txt diff --git a/examples/contracts/prompt-part4.txt b/examples/contracts/_prompts/example-app-4-testing.txt similarity index 100% rename from examples/contracts/prompt-part4.txt rename to examples/contracts/_prompts/example-app-4-testing.txt diff --git a/examples/contracts/prompt.txt b/examples/contracts/_prompts/tutorial-generation-prompt.txt similarity index 100% rename from examples/contracts/prompt.txt rename to examples/contracts/_prompts/tutorial-generation-prompt.txt diff --git a/examples/orderbook/prompt-part1.txt b/examples/orderbook/_prompts/example-app-1-create-app-template.txt similarity index 100% rename from examples/orderbook/prompt-part1.txt rename to examples/orderbook/_prompts/example-app-1-create-app-template.txt diff --git a/examples/orderbook/prompt-part2.txt b/examples/orderbook/_prompts/example-app-2-add-feature.txt similarity index 100% rename from examples/orderbook/prompt-part2.txt rename to examples/orderbook/_prompts/example-app-2-add-feature.txt diff --git a/examples/orderbook/prompt-part3.txt b/examples/orderbook/_prompts/example-app-3-fix-ui.txt similarity index 100% rename from examples/orderbook/prompt-part3.txt rename to examples/orderbook/_prompts/example-app-3-fix-ui.txt diff --git a/examples/orderbook/prompt-part4.txt b/examples/orderbook/_prompts/example-app-4-testing.txt similarity index 100% rename from examples/orderbook/prompt-part4.txt rename to examples/orderbook/_prompts/example-app-4-testing.txt diff --git a/examples/orderbook/prompt.txt b/examples/orderbook/_prompts/tutorial-generation-prompt.txt similarity index 100% rename from examples/orderbook/prompt.txt rename to examples/orderbook/_prompts/tutorial-generation-prompt.txt diff --git a/examples/passport/prompt-part1.txt b/examples/passport/_prompts/example-app-1-create-app-template.txt similarity index 100% rename from examples/passport/prompt-part1.txt rename to examples/passport/_prompts/example-app-1-create-app-template.txt diff --git a/examples/passport/prompt-part2.txt b/examples/passport/_prompts/example-app-2-add-feature.txt similarity index 100% rename from examples/passport/prompt-part2.txt rename to examples/passport/_prompts/example-app-2-add-feature.txt diff --git a/examples/passport/prompt-part3.txt b/examples/passport/_prompts/example-app-3-fix-ui.txt similarity index 100% rename from examples/passport/prompt-part3.txt rename to examples/passport/_prompts/example-app-3-fix-ui.txt diff --git a/examples/passport/prompt-part4.txt b/examples/passport/_prompts/example-app-4-testing.txt similarity index 100% rename from examples/passport/prompt-part4.txt rename to examples/passport/_prompts/example-app-4-testing.txt diff --git a/examples/passport/prompt.txt b/examples/passport/_prompts/tutorial-generation-prompt.txt similarity index 100% rename from examples/passport/prompt.txt rename to examples/passport/_prompts/tutorial-generation-prompt.txt diff --git a/examples/passport/login-with-nextjs/src/app/auth-event-handling/page.tsx b/examples/passport/login-with-nextjs/src/app/auth-event-handling/page.tsx new file mode 100644 index 0000000000..ef3df74034 --- /dev/null +++ b/examples/passport/login-with-nextjs/src/app/auth-event-handling/page.tsx @@ -0,0 +1,274 @@ +'use client'; + +import { useEffect, useState, useCallback } from 'react'; +import { Button, Heading, Stack, Body, Table, Link } from '@biom3/react'; +import NextLink from 'next/link'; +import { passportInstance } from '../utils/setupLogoutSilent'; +import { Provider, ProviderEvent } from '@imtbl/sdk/passport'; + + +export default function EventHandlingPage() { + const [isLoggedIn, setIsLoggedIn] = useState(false); + const [provider, setProvider] = useState(undefined); + const [events, setEvents] = useState>([]); + const [address, setAddress] = useState(''); + const [chainId, setChainId] = useState(''); + const [loading, setLoading] = useState(false); + const [accountsState, setAccountsState] = useState([]); + + // Add a new event to the event log + const logEvent = useCallback((eventName: string, data: any) => { + setEvents(prev => [ + { + event: eventName, + data: JSON.stringify(data, null, 2), + timestamp: new Date().toLocaleTimeString() + }, + ...prev + ].slice(0, 10)); // Keep only the last 10 events + }, []); + + // Handler for accountsChanged event + const handleAccountsChanged = useCallback((accounts: string[]) => { + console.log('accounts changed:', accounts); + setAccountsState(accounts); + logEvent(ProviderEvent.ACCOUNTS_CHANGED, { accounts }); + + if (accounts.length === 0) { + // User has disconnected their account + setIsLoggedIn(false); + setAddress(''); + setChainId(''); // Clear chainId on disconnect + } else { + setAddress(accounts[0]); + // Potentially fetch chainId again if needed, or assume it hasn't changed + } + }, [logEvent]); + + // Initialize provider on mount + useEffect(() => { + const fetchPassportProvider = async () => { + // Check if user is already logged in + const user = await passportInstance.getUserInfo(); + if (user) { + const provider = await passportInstance.connectEvm(); + setProvider(provider); + if (provider) { + const accounts = await provider.request({ method: 'eth_accounts' }); + if (accounts && accounts.length > 0) { + setAddress(accounts[0]); + setAccountsState(accounts); + logEvent('initial_accounts', { accounts }); + const chainId = await provider.request({ method: 'eth_chainId' }); + setChainId(chainId); + logEvent('initial_chain_id', { chainId }); + } + } + setIsLoggedIn(true); + } else { + // Optionally connectEvm even if not logged in to set up listeners early + // const provider = await passportInstance.connectEvm(); + // setProvider(provider); + } + }; + + fetchPassportProvider(); + }, [logEvent]); // Added logEvent dependency + + // Set up accountsChanged event listener + useEffect(() => { + if (!provider) return; + + // Register event listener + provider.on(ProviderEvent.ACCOUNTS_CHANGED, handleAccountsChanged); + + // Log that event listener was registered + logEvent('provider_event_registered', { event: ProviderEvent.ACCOUNTS_CHANGED }); + + // Cleanup function to remove event listener + return () => { + provider.removeListener(ProviderEvent.ACCOUNTS_CHANGED, handleAccountsChanged); + }; + }, [provider, handleAccountsChanged, logEvent]); + + // Handle login + const handleLogin = async () => { + try { + setLoading(true); + await passportInstance.login(); + + // After login, get the provider + const provider = await passportInstance.connectEvm(); + setProvider(provider); + + if (provider) { + // Get accounts + const accounts = await provider.request({ method: 'eth_requestAccounts' }); + if (accounts && accounts.length > 0) { + setAddress(accounts[0]); + setAccountsState(accounts); + // Log the accounts change manually since the event might not fire + logEvent(ProviderEvent.ACCOUNTS_CHANGED, { accounts }); + } + + // Get chain ID + const chainId = await provider.request({ method: 'eth_chainId' }); + setChainId(chainId); + } + + setIsLoggedIn(true); + } catch (error) { + console.error('Login error:', error); + logEvent('login_error', { message: error instanceof Error ? error.message : String(error) }); + } finally { + setLoading(false); + } + }; + + // Handle logout + const handleLogout = async () => { + try { + setLoading(true); + await passportInstance.logout(); + setIsLoggedIn(false); + setAddress(''); + setChainId(''); + setAccountsState([]); + setProvider(undefined); // Clear provider on logout + logEvent('logout_success', {}); // Log successful logout + } catch (error) { + console.error('Logout error:', error); + logEvent('logout_error', { message: error instanceof Error ? error.message : String(error) }); + } finally { + setLoading(false); + } + }; + + return ( + <> + Passport SDK - Event Handling Example + + {/* Buttons Section */} + + {!isLoggedIn && ( + + )} + {isLoggedIn && ( + + )} + + {/* State Data Table */} + {(isLoggedIn || accountsState.length > 0) && ( + <> + + + + Key + Value + + + + + Status + {isLoggedIn ? 'Logged In' : 'Logged Out'} + + {address && ( + + Address + {address} + + )} + {chainId && ( + + Chain ID + {chainId} + + )} + {accountsState.length > 0 && ( + + Accounts ({accountsState.length}) + +
+ {accountsState.map((account, idx) => ( +
+ {account} {idx === 0 && (active)} +
+ ))} +
+
+
+ )} +
+
+ + )} +
+ {/* Event Log Section */} + <> + Event Log: +
+ {events.length === 0 ? ( + No events logged yet + ) : ( + events.map((event, index) => ( +
+ + {/* Use a more distinct tag style */} + + {event.event} + + {event.timestamp} + +
+                  {event.data}
+                
+
+ )) + )} +
+ +
+ }>Return to Examples + + ); +} \ No newline at end of file diff --git a/examples/passport/login-with-nextjs/src/app/page.tsx b/examples/passport/login-with-nextjs/src/app/page.tsx index 7496b67093..a6219f2513 100644 --- a/examples/passport/login-with-nextjs/src/app/page.tsx +++ b/examples/passport/login-with-nextjs/src/app/page.tsx @@ -39,5 +39,11 @@ export default function Home() { rc={}> Logout with Silent Mode + ); } \ No newline at end of file diff --git a/examples/passport/login-with-nextjs/tests/base.spec.ts b/examples/passport/login-with-nextjs/tests/base.spec.ts index dee5e54860..996f6893bc 100644 --- a/examples/passport/login-with-nextjs/tests/base.spec.ts +++ b/examples/passport/login-with-nextjs/tests/base.spec.ts @@ -13,7 +13,8 @@ test.describe("home page", () => { "Login with EtherJS", "Login with Identity only", "Logout with Redirect Mode", - "Logout with Silent Mode" + "Logout with Silent Mode", + "Auth Event Handling" ]; for (const name of buttonNames) { @@ -66,4 +67,10 @@ test.describe("sub-pages navigation", () => { await expect(page.getByRole("button", { name: /Login|Logout/ })).toBeVisible(); await expect(page.getByRole("link", { name: "Return to Examples" })).toBeVisible(); }); -}); \ No newline at end of file + + test("Check Auth Event Handling", async ({ page }) => { + await page.click("text=Auth Event Handling"); + await expect(page.getByRole("heading", { name: "Passport SDK - Event Handling Example" })).toBeVisible(); + await expect(page.getByRole("link", { name: "Return to Examples" })).toBeVisible(); + }); +}); diff --git a/examples/passport/login-with-nextjs/tutorial.md b/examples/passport/login-with-nextjs/tutorial.md index 03a220e5b4..752d727f94 100644 --- a/examples/passport/login-with-nextjs/tutorial.md +++ b/examples/passport/login-with-nextjs/tutorial.md @@ -1,10 +1,10 @@ -This example demonstrates various login and logout implementations using Immutable Passport with Next.js. The app showcases different authentication methods and logout strategies including standard login, login with ethers.js, identity-only login, and different logout modes. +This example demonstrates various login and logout implementations using the Immutable Passport SDK in a Next.js application. It showcases different authentication methods including login with EthersJS, identity-only login, and various logout strategies, along with authentication event handling.
@@ -13,90 +13,40 @@ This example demonstrates various login and logout implementations using Immutab
## Features Overview - -- Login with Passport (EVM wallet connection) -- Login with ethers.js integration -- Login with identity only (without wallet) +- Login with EthersJS integration +- Identity-only login (without wallet connection) - Standard logout functionality - Logout with redirect mode -- Logout with silent mode +- Silent logout implementation +- Authentication event handling ## SDK Integration Details -### Login with Passport -**Feature Name**: Standard login with Passport connecting an EVM wallet -**Source Code**: [Source code file](https://github.com/immutable/ts-immutable-sdk/blob/main/examples/passport/login-with-nextjs/src/app/login-with-passport/page.tsx) -**Implementation**: -```typescript -const loginWithPassport = async () => { - if (!passportInstance) return; - try { - const provider = await passportInstance.connectEvm(); - const accounts = await provider.request({ method: 'eth_requestAccounts' }); - if (accounts) { - setIsLoggedIn(true); - setAccountAddress(accounts[0] || null); - } else { - setIsLoggedIn(false); - } - } catch (error) { - console.error('Error connecting to Passport:', error); - setIsLoggedIn(false); - } -}; -``` -**Explanation**: This implementation creates a connection to the user's EVM wallet through Passport. The `connectEvm()` method returns a provider that follows the EIP-1193 interface, which can then be used to request account access with `eth_requestAccounts`. Once connected, the user's wallet address is stored in the application state. +### Login with EthersJS +**Feature Name**: Login with EthersJS allows users to connect their wallet and authenticate using EthersJS provider. + +**Source Code**: [login-with-etherjs/page.tsx](https://github.com/immutable/ts-immutable-sdk/blob/main/examples/passport/login-with-nextjs/src/app/login-with-etherjs/page.tsx) -### Login with ethers.js -**Feature Name**: Login integration with ethers.js library -**Source Code**: [Source code file](https://github.com/immutable/ts-immutable-sdk/blob/main/examples/passport/login-with-nextjs/src/app/login-with-etherjs/page.tsx) **Implementation**: ```typescript -const loginWithEthersjs = async () => { - if (!passportInstance) return; - try { - const passportProvider = await passportInstance.connectEvm(); - const web3Provider = new BrowserProvider(passportProvider); - const accounts = await web3Provider.send('eth_requestAccounts', []); - if (accounts && accounts.length > 0) { - setIsLoggedIn(true); - setAccountAddress(accounts[0] || null); - } else { - setIsLoggedIn(false); - } - } catch (error) { - console.error('Error connecting to Passport with Ethers.js:', error); - setIsLoggedIn(false); - } -}; +const passportProvider = await passportInstance.connectEvm(); +const web3Provider = new BrowserProvider(passportProvider); +const accounts = await web3Provider.send('eth_requestAccounts', []); ``` -**Explanation**: This implementation demonstrates how to integrate Passport with ethers.js. It gets the Passport provider and wraps it in an ethers.js BrowserProvider, allowing developers to leverage ethers.js functionality with Passport authentication. -### Login with Identity Only -**Feature Name**: Login with Passport identity without connecting a wallet -**Source Code**: [Source code file](https://github.com/immutable/ts-immutable-sdk/blob/main/examples/passport/login-with-nextjs/src/app/login-with-identity-only/page.tsx) +**Explanation**: This implementation uses EthersJS's BrowserProvider to interact with the Passport provider. It requests user accounts and manages the connection state, displaying the connected account address when successful. + +### Identity-only Login +**Feature Name**: Login without wallet connection, focusing on user identity authentication. + +**Source Code**: [login-with-identity-only/page.tsx](https://github.com/immutable/ts-immutable-sdk/blob/main/examples/passport/login-with-nextjs/src/app/login-with-identity-only/page.tsx) + **Implementation**: ```typescript -const loginWithIdentiy = async () => { - if (!passportInstance) return; - try { - const profile: passport.UserProfile | null = await passportInstance.login(); - if (profile) { - console.log(profile.email); - console.log(profile.sub); - setIsLoggedIn(true); - setEmail(profile.email || 'No Email'); - setSub(profile.sub || 'No Subject'); - } else { - setIsLoggedIn(false); - } - } catch (error) { - console.error('Error connecting to Passport', error); - setIsLoggedIn(false); - } -}; +const profile: passport.UserProfile | null = await passportInstance.login(); ``` -**Explanation**: This approach allows users to authenticate with Passport without connecting a wallet. The `login()` method returns a UserProfile object containing user information like email and subject identifier (sub), enabling user identification without requiring wallet interaction. + +**Explanation**: This implementation demonstrates how to authenticate users without requiring a wallet connection. It retrieves the user's profile information including email and subject ID. ### Logout **Feature Name**: Standard logout functionality @@ -116,6 +66,7 @@ const logout = async () => { ``` **Explanation**: The standard logout implementation disconnects the user from Passport using the `logout()` method. This ends the user's session and clears any authentication state. + ### Logout with Redirect Mode **Feature Name**: Logout with redirect to a specific page **Source Code**: [Source code file](https://github.com/immutable/ts-immutable-sdk/blob/main/examples/passport/login-with-nextjs/src/app/utils/setupLogoutRedirect.ts) @@ -137,60 +88,73 @@ export const passportInstance = new passport.Passport({ ``` **Explanation**: This configuration sets up Passport to use 'redirect' mode for logout. When `logout()` is called, the user will be redirected to the specified `logoutRedirectUri` after being logged out. -### Logout with Silent Mode -**Feature Name**: Logout without page redirection -**Source Code**: [Source code file](https://github.com/immutable/ts-immutable-sdk/blob/main/examples/passport/login-with-nextjs/src/app/utils/setupLogoutSilent.ts) + +### Silent Logout +**Feature Name**: Logout implementation that silently signs out the user without redirects. + +**Source Code**: [logout-with-silent-mode/page.tsx](https://github.com/immutable/ts-immutable-sdk/blob/main/examples/passport/login-with-nextjs/src/app/logout-with-silent-mode/page.tsx) + **Implementation**: ```typescript -export const passportInstance = new passport.Passport({ - baseConfig: { - environment: config.Environment.SANDBOX, - publishableKey: - process.env.NEXT_PUBLIC_PUBLISHABLE_KEY || '', - }, - clientId: process.env.NEXT_PUBLIC_CLIENT_ID || '', - redirectUri: 'http://localhost:3000/redirect', - logoutMode: 'silent', - logoutRedirectUri: 'http://localhost:3000/silent-logout', - audience: 'platform_api', - scope: 'openid offline_access email transact', - }); +await passportInstance.logout(); +``` + +**Explanation**: This implementation shows how to implement a silent logout that disconnects the user without redirecting to another page, providing a seamless user experience. + +### Authentication Event Handling +**Feature Name**: Handling authentication-related events such as account changes. + +**Source Code**: [auth-event-handling/page.tsx](https://github.com/immutable/ts-immutable-sdk/blob/main/examples/passport/login-with-nextjs/src/app/auth-event-handling/page.tsx) + +**Implementation**: +```typescript +provider.on(ProviderEvent.ACCOUNTS_CHANGED, handleAccountsChanged); + +const handleAccountsChanged = (accounts: string[]) => { + setAccountsState(accounts); + if (accounts.length === 0) { + setIsLoggedIn(false); + setAddress(''); + setChainId(''); + } else { + setAddress(accounts[0]); + } +}; ``` -**Explanation**: This configuration sets up Passport to use 'silent' mode for logout. When `logout()` is called, the user remains on the current page while the logout process happens in the background, providing a smoother user experience. + +**Explanation**: This implementation shows how to listen for and handle authentication events, specifically the `ACCOUNTS_CHANGED` event. It updates the application state when accounts are changed or disconnected, maintaining synchronization between the wallet state and the application. ## Running the App ### Prerequisites -1. Node.js installed on your machine -2. [Immutable Hub account](https://hub.immutable.com/) for environment setup -3. A registered application in Immutable Hub with client ID and publishable key +- Node.js 16 or higher +- pnpm package manager +- [Immutable Developer Hub](https://hub.immutable.com/) account for environment setup + +### Local Setup Steps +1. Clone the repository: +```bash +git clone https://github.com/immutable/ts-immutable-sdk.git +``` -### Setup and Run -1. Clone the repository 2. Navigate to the example directory: - ```bash - cd examples/passport/login-with-nextjs - ``` +```bash +cd examples/passport/login-with-nextjs +``` + 3. Install dependencies: - ```bash - pnpm install - ``` -4. Create a `.env.local` file in the root directory with your credentials: - ``` - NEXT_PUBLIC_CLIENT_ID= - NEXT_PUBLIC_PUBLISHABLE_KEY= - ``` -5. Start the development server: - ```bash - pnpm dev - ``` -6. Open [http://localhost:3000](http://localhost:3000) in your browser +```bash +pnpm install +``` -## Summary +4. Copy the `.env.example` file to `.env` and update with your Passport client credentials from the [Immutable Developer Hub](https://hub.immutable.com/). -This example demonstrates various ways to implement authentication with Immutable Passport in a Next.js application. It showcases: -- Different login methods: standard Passport login, ethers.js integration, and identity-only login -- Multiple logout strategies: standard logout, redirect mode, and silent mode -- How to set up Passport SDK with different configurations +5. Start the development server: +```bash +pnpm dev +``` + +6. Open your browser and navigate to `http://localhost:3000` -By exploring these examples, developers can choose the authentication approach that best suits their application's needs while maintaining a smooth user experience. \ No newline at end of file +## Summary +This example demonstrates various authentication patterns using the Immutable Passport SDK in a Next.js application. It showcases different login methods including EthersJS integration and identity-only authentication, various logout strategies (redirect and silent modes), and proper handling of authentication events. Developers can use this example as a reference for implementing comprehensive Passport authentication in their own Next.js applications. \ No newline at end of file