@adpal/app-switcher
v1.0.5
Published
Shared app switcher and multi-account components for AdPal products
Maintainers
Readme
@adpal/app-switcher
Shared app switcher and multi-account components for AdPal products. Provides a Google Workspace-style header with app grid and multi-account support.
Features
- AppSwitcher - Google Workspace-style app grid for switching between AdPal products
- AccountDropdown - Multi-account support with account switching
- AccountAvatar - Reusable avatar component with loading states and fallbacks
- Header - Combined header component with app switcher and account menu
- useLinkedAccounts - Hook for cross-subdomain account persistence
- Custom SVG Icons - Gradient icons with glow effects (BizIntel, Data, Voice, MCP)
Installation
# From GitHub (recommended for private repos)
npm install github:Data-Subsystems/adpal-app-switcher
# Or from npm (if published)
npm install @adpal/app-switcherPeer Dependencies
react>= 18.0.0react-dom>= 18.0.0next>= 14.0.0next-auth>= 4.0.0
Quick Start
1. Install the package
npm install github:Data-Subsystems/adpal-app-switcher2. Create Header component
// components/layout/Header.tsx
'use client';
import { useSession, signOut } from 'next-auth/react';
import { useRouter } from 'next/navigation';
import { Header as SharedHeader, themes } from '@adpal/app-switcher';
export function Header() {
const router = useRouter();
const { data: session, status } = useSession();
const handleSwitchAccount = async (account: { email: string }) => {
await signOut({ redirect: false });
const response = await fetch('/api/auth/switch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: account.email }),
});
if (response.ok) {
const { redirectUrl } = await response.json();
window.location.href = redirectUrl;
}
};
const handleAddAccount = async () => {
const response = await fetch('/api/auth/switch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ addNew: true }),
});
if (response.ok) {
const { redirectUrl } = await response.json();
window.location.href = redirectUrl;
}
};
return (
<SharedHeader
user={session?.user ?? null}
isLoading={status === 'loading'}
theme={themes.blue} // or themes.cyan
heightClass="h-14"
appSwitcher={{
currentAppId: 'bizintel', // or 'data'
}}
accountDropdown={{
onSwitchAccount: handleSwitchAccount,
onAddAccount: handleAddAccount,
onSignOut: () => signOut({ callbackUrl: '/auth/signin' }),
}}
/>
);
}3. Create API routes
// app/api/auth/switch/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { email, addNew } = body;
const baseUrl = process.env.NEXTAUTH_URL || 'http://localhost:3000';
const params = new URLSearchParams({ callbackUrl: `${baseUrl}/` });
if (email && !addNew) {
params.set('login_hint', email);
}
const redirectUrl = `${baseUrl}/api/auth/signin/google?${params.toString()}`;
return NextResponse.json({ redirectUrl });
} catch {
return NextResponse.json({ error: 'Invalid request' }, { status: 400 });
}
}4. Update auth config
// lib/auth-options.ts
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
authorization: {
params: {
prompt: 'select_account', // Enable account picker
},
},
}),5. Update middleware
// middleware.ts
const publicPaths = [
// ... existing paths
'/api/auth/accounts',
'/api/auth/switch',
];Available Apps
| ID | Name | URL | Icon Color |
|----|------|-----|------------|
| bizintel | Business Intelligence | https://b.adpal.com | Gold |
| data | Data Processing | https://d.adpal.com | Cyan |
| voice | Voice Assistant | https://voice.adpal.com | Green |
| mcp-demo | MCP Demo | https://mcp-demo.adpal.com | Purple |
Themes
import { themes } from '@adpal/app-switcher';
// Available themes
themes.blue // Default - blue accent
themes.cyan // Cyan accent (#00BCD4)
themes.emerald // Green accent
// Or create custom
const customTheme = {
accent: '#FF5722',
ringColor: 'ring-orange-200',
currentBg: 'bg-orange-50',
};Components API
Header
<Header
user={session?.user ?? null}
isLoading={status === 'loading'}
theme={themes.blue}
heightClass="h-14" // Header height (h-12, h-14, h-16)
sidebarOffset="left-64" // Left offset for sidebar
appSwitcher={{
currentAppId: 'bizintel',
showHeader: true,
showFooter: true,
columns: 3,
}}
accountDropdown={{
onSwitchAccount: (account) => {},
onAddAccount: () => {},
onSignOut: () => {},
showManageAccount: true,
}}
/>AppSwitcher
<AppSwitcher
currentAppId="bizintel" // Highlight current app
apps={ADPAL_APPS} // Custom apps list
showInternalApps={true} // Show internal-only apps
iconSize={48} // Icon size in pixels
columns={3} // Grid columns (2 or 3)
showHeader={true} // Show "Adpal Apps" header
showFooter={true} // Show "More from Adpal" footer
onAppClick={(app) => {}} // Custom click handler
/>AccountDropdown
<AccountDropdown
user={{ id, email, name, image }}
isLoading={false}
onSwitchAccount={(account) => {}}
onAddAccount={() => {}}
onSignOut={() => {}}
showManageAccount={true}
manageAccountUrl="https://myaccount.google.com"
/>useLinkedAccounts Hook
const {
accounts, // LinkedAccount[]
isLoaded, // boolean
addAccount, // (account) => void
removeAccount, // (accountId) => void
updateLastUsed, // (accountId) => void
clearAllAccounts, // () => void
} = useLinkedAccounts({
cookieDomain: '.adpal.com', // Optional
maxAccounts: 5, // Optional
});Cross-Subdomain Account Persistence
Linked accounts are stored in a cookie with domain .adpal.com, enabling account persistence across all AdPal subdomains:
- Cookie name:
adpal-linked-accounts - Max accounts: 5
- Expiry: 30 days
- Fallback: localStorage (if cookie exceeds 4KB)
Projects Using This Package
| Project | Subdomain | Theme | Current App ID |
|---------|-----------|-------|----------------|
| adpal-ai-bizintel | b.adpal.com | themes.blue | bizintel |
| adpal-ai-data | d.adpal.com | themes.cyan | data |
Development
# Install dependencies
npm install
# Build
npm run build
# Watch mode
npm run dev
# Type check
npm run typecheckUpdating the Package
After making changes:
# Build and commit
npm run build
git add -A
git commit -m "description"
git push
# Update in consuming projects
cd ../adpal-ai-data
npm update @adpal/app-switcher
cd ../adpal-ai-bizintel
npm update @adpal/app-switcherRepository
https://github.com/Data-Subsystems/adpal-app-switcher
License
MIT
