From 2eb02a336993da736573a8f067c136835f3f8827 Mon Sep 17 00:00:00 2001 From: Emir Karabeg Date: Tue, 4 Mar 2025 16:39:49 -0800 Subject: [PATCH] feat(account): added account to settings --- .../components/account/account.tsx | 253 ++++++++++++++++++ .../components/environment/environment.tsx | 2 +- .../components/general/general.tsx | 2 +- .../settings-navigation.tsx | 9 +- .../settings-modal/settings-modal.tsx | 6 +- .../toolbar-block/toolbar-block.tsx | 4 +- components/ui/separator.tsx | 25 ++ package-lock.json | 47 ++++ package.json | 1 + stores/index.ts | 64 ++++- stores/sync-registry.ts | 16 ++ 11 files changed, 414 insertions(+), 15 deletions(-) create mode 100644 app/w/components/sidebar/components/settings-modal/components/account/account.tsx create mode 100644 components/ui/separator.tsx diff --git a/app/w/components/sidebar/components/settings-modal/components/account/account.tsx b/app/w/components/sidebar/components/settings-modal/components/account/account.tsx new file mode 100644 index 0000000000..93cf223580 --- /dev/null +++ b/app/w/components/sidebar/components/settings-modal/components/account/account.tsx @@ -0,0 +1,253 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useRouter } from 'next/navigation' +import { ChevronDown, LogOut, Plus, User, UserPlus } from 'lucide-react' +import { AgentIcon } from '@/components/icons' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' +import { signOut, useSession } from '@/lib/auth-client' +import { cn } from '@/lib/utils' +import { clearUserData } from '@/stores' + +interface AccountProps { + onOpenChange: (open: boolean) => void +} + +// Mock user data - in a real app, this would come from an auth provider +interface UserData { + isLoggedIn: boolean + name?: string + email?: string +} + +interface AccountData { + id: string + name: string + email: string + isActive?: boolean +} + +export function Account({ onOpenChange }: AccountProps) { + const router = useRouter() + + // In a real app, this would be fetched from an auth provider + const [userData, setUserData] = useState({ + isLoggedIn: false, + name: '', + email: '', + }) + + // Get session data using the client hook + const { data: session, isPending, error } = useSession() + const [isLoadingUserData, setIsLoadingUserData] = useState(false) + + // Mock accounts for the multi-account UI + const [accounts, setAccounts] = useState([]) + const [open, setOpen] = useState(false) + + // Update user data when session changes + useEffect(() => { + const updateUserData = async () => { + if (!isPending && session?.user) { + // User is logged in + setUserData({ + isLoggedIn: true, + name: session.user.name || 'User', + email: session.user.email, + }) + + setAccounts([ + { + id: '1', + name: session.user.name || 'User', + email: session.user.email, + isActive: true, + }, + ]) + } else if (!isPending) { + // User is not logged in + setUserData({ + isLoggedIn: false, + name: '', + email: '', + }) + setAccounts([]) + } + } + + updateUserData() + }, [session, isPending]) + + const handleSignIn = () => { + // Use Next.js router to navigate to login page + router.push('/login') + setOpen(false) + } + + const handleSignOut = async () => { + try { + // Start the sign-out process + const signOutPromise = signOut() + + // Clear all user data to prevent persistence between accounts + await clearUserData() + + // Set a short timeout to improve perceived performance + // while still ensuring auth state starts to clear + setTimeout(() => { + router.push('/login?fromLogout=true') + }, 100) + + // Still wait for the promise to resolve/reject to catch errors + await signOutPromise + } catch (error) { + console.error('Error signing out:', error) + // Still navigate even if there's an error + router.push('/login?fromLogout=true') + } finally { + setOpen(false) + } + } + + const activeAccount = accounts.find((acc) => acc.isActive) || accounts[0] + + // Loading animation component + const LoadingAccountBlock = () => ( +
+
+
+
+
+
+
+
+
+
+
+
+ ) + + return ( +
+
+

Account

+
+ + {/* Account Dropdown Component */} +
+
+ {isPending || isLoadingUserData ? ( + + ) : ( + + +
+
+
+ {userData.isLoggedIn ? ( +
+ +
+ ) : ( +
+ +
+ )} + {userData.isLoggedIn && accounts.length > 1 && ( +
+ {accounts.length} +
+ )} +
+
+

+ {userData.isLoggedIn ? activeAccount?.name : 'Sign in'} +

+

+ {userData.isLoggedIn ? activeAccount?.email : 'Click to sign in'} +

+
+
+ +
+
+ + {userData.isLoggedIn ? ( + <> + {accounts.length > 1 && ( + <> +
+ Switch Account +
+ {accounts.map((account) => ( + +
+ +
+
+ {account.name} + {account.email} +
+
+ ))} + + + )} + + + Sign Out + + + ) : ( + <> + + + Sign in + + + )} +
+
+ )} +
+
+
+ ) +} diff --git a/app/w/components/sidebar/components/settings-modal/components/environment/environment.tsx b/app/w/components/sidebar/components/settings-modal/components/environment/environment.tsx index 8d004e2c56..994bff1228 100644 --- a/app/w/components/sidebar/components/settings-modal/components/environment/environment.tsx +++ b/app/w/components/sidebar/components/settings-modal/components/environment/environment.tsx @@ -243,7 +243,7 @@ export function EnvironmentVariables({ onOpenChange }: EnvironmentVariablesProps
{/* Fixed Header */}
-

Environment Variables

+

Environment Variables

diff --git a/app/w/components/sidebar/components/settings-modal/components/general/general.tsx b/app/w/components/sidebar/components/settings-modal/components/general/general.tsx index 7002eaece2..9015f242fc 100644 --- a/app/w/components/sidebar/components/settings-modal/components/general/general.tsx +++ b/app/w/components/sidebar/components/settings-modal/components/general/general.tsx @@ -40,7 +40,7 @@ export function General() { return (
-

General Settings

+

General Settings

diff --git a/app/w/components/toolbar/components/toolbar-block/toolbar-block.tsx b/app/w/components/toolbar/components/toolbar-block/toolbar-block.tsx index d31cc768ce..b84ac8d001 100644 --- a/app/w/components/toolbar/components/toolbar-block/toolbar-block.tsx +++ b/app/w/components/toolbar/components/toolbar-block/toolbar-block.tsx @@ -26,9 +26,9 @@ export function ToolbarBlock({ config }: ToolbarBlockProps) { }`} />
-
+

{config.name}

-

{config.description}

+

{config.description}

) diff --git a/components/ui/separator.tsx b/components/ui/separator.tsx new file mode 100644 index 0000000000..b497617461 --- /dev/null +++ b/components/ui/separator.tsx @@ -0,0 +1,25 @@ +'use client' + +import * as React from 'react' +import * as SeparatorPrimitive from '@radix-ui/react-separator' +import { cn } from '@/lib/utils' + +const Separator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => ( + +)) +Separator.displayName = SeparatorPrimitive.Root.displayName + +export { Separator } diff --git a/package-lock.json b/package-lock.json index c05354a312..482d29ce98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@radix-ui/react-popover": "^1.1.5", "@radix-ui/react-scroll-area": "^1.2.2", "@radix-ui/react-select": "^2.1.4", + "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slider": "^1.2.2", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-switch": "^1.1.2", @@ -2554,6 +2555,52 @@ } } }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.2.tgz", + "integrity": "sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slider": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.2.2.tgz", diff --git a/package.json b/package.json index c10dd17fd9..fcf59e94b2 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@radix-ui/react-popover": "^1.1.5", "@radix-ui/react-scroll-area": "^1.2.2", "@radix-ui/react-select": "^2.1.4", + "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slider": "^1.2.2", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-switch": "^1.1.2", diff --git a/stores/index.ts b/stores/index.ts index fcb9208f03..4ea7e7ab83 100644 --- a/stores/index.ts +++ b/stores/index.ts @@ -5,7 +5,7 @@ import { useCustomToolsStore } from './custom-tools/store' import { useExecutionStore } from './execution/store' import { useNotificationStore } from './notifications/store' import { useEnvironmentStore } from './settings/environment/store' -import { getSyncManagers, initializeSyncManagers } from './sync-registry' +import { getSyncManagers, initializeSyncManagers, resetSyncManagers } from './sync-registry' import { loadRegistry, loadSubblockValues, @@ -145,6 +145,31 @@ function cleanupApplication(): void { getSyncManagers().forEach((manager) => manager.dispose()) } +/** + * Clear all user data when signing out + * This ensures data from one account doesn't persist to another + */ +export async function clearUserData(): Promise { + if (typeof window === 'undefined') return + + try { + // 1. Reset all sync managers to prevent any pending syncs + resetSyncManagers() + + // 2. Reset all stores to their initial state + resetAllStores() + + // 3. Clear localStorage except for essential app settings + const keysToKeep = ['next-favicon', 'theme'] + const keysToRemove = Object.keys(localStorage).filter((key) => !keysToKeep.includes(key)) + keysToRemove.forEach((key) => localStorage.removeItem(key)) + + console.log('User data cleared successfully') + } catch (error) { + console.error('Error clearing user data:', error) + } +} + /** * Hook to manage application lifecycle */ @@ -159,6 +184,16 @@ export function useAppInitialization() { }, []) } +/** + * Hook to reinitialize the application after successful login + * Use this in the login success handler or post-login page + */ +export function useLoginInitialization() { + useEffect(() => { + reinitializeAfterLogin() + }, []) +} + // Initialize immediately when imported on client if (typeof window !== 'undefined') { initializeApplication() @@ -178,13 +213,6 @@ export { // Helper function to reset all stores export const resetAllStores = () => { - if (typeof window !== 'undefined') { - // Selectively clear localStorage items - const keysToKeep = ['next-favicon'] - const keysToRemove = Object.keys(localStorage).filter((key) => !keysToKeep.includes(key)) - keysToRemove.forEach((key) => localStorage.removeItem(key)) - } - // Reset all stores to initial state useWorkflowRegistry.setState({ workflows: {}, @@ -229,3 +257,23 @@ export const logAllStores = () => { // Re-export sync managers export { workflowSync, environmentSync } from './sync-registry' + +/** + * Reinitialize the application after login + * This ensures we load fresh data from the database for the new user + */ +export async function reinitializeAfterLogin(): Promise { + if (typeof window === 'undefined') return + + try { + // Reset initialization flags to force a fresh load + isInitializing = false + + // Reinitialize the application + await initializeApplication() + + console.log('Application reinitialized after login') + } catch (error) { + console.error('Error reinitializing application:', error) + } +} diff --git a/stores/sync-registry.ts b/stores/sync-registry.ts index fa3c3cbba1..685e37cf23 100644 --- a/stores/sync-registry.ts +++ b/stores/sync-registry.ts @@ -69,5 +69,21 @@ export function getSyncManagers(): SyncManager[] { return managers } +/** + * Reset all sync managers + * This is used during sign-out to ensure clean state for the next user + */ +export function resetSyncManagers(): void { + // Dispose all existing managers + managers.forEach((manager) => manager.dispose()) + + // Reset the managers array + managers = [] + + // Reset initialization flags + initialized = false + initializing = false +} + // Export individual sync managers for direct use export { workflowSync, environmentSync }