Skip to content

Commit 163d575

Browse files
committed
Improve Welcome graph creation flow and enhance UI styling
- Move Welcome graph creation from server to frontend (per-user instead of shared) - Add comprehensive guards to prevent duplicate Welcome graph creation - Use ref instead of state to prevent race conditions during graph creation - Enhance NodeTypeSelector with emerald/green theme and better visual feedback - Update TagInput with improved focus indicators and consistent border styling - Add detailed logging for debugging graph loading and creation flow
1 parent 10c0e9b commit 163d575

4 files changed

Lines changed: 84 additions & 56 deletions

File tree

packages/server/src/index.ts

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import { promisify } from 'util';
3232
import fs from 'fs';
3333
import rateLimit from 'express-rate-limit';
3434
import { createCaptchaChallenge, verifyCaptcha } from './utils/captcha.js';
35-
import { createSharedWelcomeGraph, sharedWelcomeGraphExists } from './services/onboarding.js';
3635

3736
const execAsync = promisify(exec);
3837

@@ -295,22 +294,7 @@ async function startServer() {
295294
console.log('ℹ️ Default admin setup completed'); // eslint-disable-line no-console
296295
}
297296

298-
// Ensure shared Welcome graph exists for all users
299-
try {
300-
const welcomeStart = Date.now();
301-
const hasWelcome = await sharedWelcomeGraphExists(driver);
302-
if (!hasWelcome) {
303-
await createSharedWelcomeGraph(driver);
304-
const welcomeTime = Date.now() - welcomeStart;
305-
console.log(`🎉 Shared Welcome graph created (${welcomeTime}ms)`); // eslint-disable-line no-console
306-
steps.push('✅ Created shared Welcome graph for onboarding');
307-
} else {
308-
console.log('✅ Shared Welcome graph already exists'); // eslint-disable-line no-console
309-
steps.push('✅ Verified shared Welcome graph exists');
310-
}
311-
} catch (welcomeError) {
312-
console.error('⚠️ Failed to create Welcome graph:', (welcomeError as Error).message); // eslint-disable-line no-console
313-
}
297+
// Welcome graphs are now created per-user on first login (handled by frontend)
314298

315299
// Merge type definitions (Neo4j schema + auth schema)
316300
const mergedTypeDefs = mergeTypeDefs([typeDefs, authTypeDefs]);

packages/web/src/components/NodeCategorySelector.tsx

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export function NodeTypeSelector({
6969
<button
7070
type="button"
7171
onClick={() => setIsOpen(!isOpen)}
72-
className="w-full flex items-center justify-between px-3 py-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-gray-900 dark:text-white hover:border-gray-300 dark:hover:border-gray-600 transition-all duration-200 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 shadow-sm text-sm"
72+
className="w-full flex items-center justify-between px-4 py-3 bg-gray-800 border-2 border-gray-600 rounded-lg text-white transition-all duration-200 focus:!border-green-400 focus:outline-none text-sm"
7373
>
7474
<div className="flex items-center space-x-2">
7575
{selectedNodeType ? (
@@ -86,45 +86,45 @@ export function NodeTypeSelector({
8686
<span className="text-gray-600 dark:text-gray-300 font-medium text-xs">{placeholder}</span>
8787
)}
8888
</div>
89-
<ChevronDown className={`h-4 w-4 text-gray-400 transition-all duration-200 ${isOpen ? 'rotate-180 text-blue-500' : ''}`} />
89+
<ChevronDown className={`h-4 w-4 text-gray-400 transition-all duration-200 ${isOpen ? 'rotate-180 text-green-500' : ''}`} />
9090
</button>
9191

9292
{isOpen && (
93-
<div className="absolute top-full left-0 mt-1 w-full bg-black/90 backdrop-blur-xl rounded-lg border border-white/10 shadow-2xl z-50 max-h-80 overflow-y-auto">
94-
<div className="p-1">
93+
<div className="absolute top-full left-0 mt-1 w-full bg-gray-800/95 backdrop-blur-xl rounded-xl border border-gray-600/30 shadow-2xl z-50 max-h-80 overflow-y-auto">
94+
<div className="p-2">
9595
{nodeTypes.map((nodeType, index) => (
9696
<button
9797
key={nodeType.value}
9898
type="button"
9999
onClick={() => handleTypeSelect(nodeType)}
100-
className={`w-full px-2 py-2 text-left hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-all duration-200 rounded-lg group ${
100+
className={`w-full px-3 py-2.5 text-left transition-all duration-200 rounded-lg group ${
101101
selectedType === nodeType.value
102-
? 'bg-blue-50 dark:bg-blue-900/30 ring-1 ring-blue-200 dark:ring-blue-700'
103-
: 'hover:shadow-sm'
104-
} ${index !== 0 ? 'mt-0.5' : ''}`}
102+
? 'bg-gradient-to-r from-emerald-500/20 to-green-500/20 border-2 border-emerald-400/50 shadow-lg shadow-emerald-500/10'
103+
: 'hover:bg-gray-700/50 border-2 border-transparent hover:border-gray-600/50'
104+
} ${index !== 0 ? 'mt-1' : ''}`}
105105
>
106106
<div className="flex items-center justify-between">
107-
<div className="flex items-center space-x-2">
108-
<div className={`${nodeType.color} text-base`}>
107+
<div className="flex items-center space-x-2.5">
108+
<div className={`${nodeType.color} text-base transition-transform group-hover:scale-110`}>
109109
{nodeType.icon}
110110
</div>
111111
<div>
112112
<div className={`font-semibold text-xs ${
113113
selectedType === nodeType.value
114-
? 'text-blue-700 dark:text-blue-300'
115-
: 'text-gray-900 dark:text-gray-100'
114+
? 'text-emerald-400'
115+
: 'text-gray-100'
116116
}`}>{nodeType.label}</div>
117117
<div className={`text-[10px] mt-0.5 ${
118118
selectedType === nodeType.value
119-
? 'text-blue-600 dark:text-blue-400'
120-
: 'text-gray-600 dark:text-gray-300'
119+
? 'text-emerald-300'
120+
: 'text-gray-400'
121121
}`}>
122122
{nodeType.description}
123123
</div>
124124
</div>
125125
</div>
126126
{selectedType === nodeType.value && (
127-
<div className="w-4 h-4 bg-blue-600 text-white rounded-full flex items-center justify-center text-[10px] ml-1 flex-shrink-0 shadow-sm">
127+
<div className="w-5 h-5 bg-gradient-to-br from-emerald-500 to-green-600 text-white rounded-full flex items-center justify-center text-[10px] ml-1 flex-shrink-0 shadow-lg shadow-emerald-500/30 animate-in zoom-in duration-200">
128128
129129
</div>
130130
)}

packages/web/src/components/TagInput.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ export function TagInput({
8686
return (
8787
<div className="space-y-2">
8888
<div className={`
89-
min-h-[42px] border rounded-lg p-2 flex flex-wrap gap-1.5 items-center
90-
${isInputFocused ? 'ring-2 ring-blue-500 border-blue-500' : 'border-gray-300 dark:border-gray-600'}
89+
min-h-[48px] border-2 rounded-lg px-4 py-3 flex flex-wrap gap-1.5 items-center
90+
${isInputFocused ? 'border-green-400' : 'border-gray-600'}
9191
${disabled ? 'bg-gray-100 dark:bg-gray-700 cursor-not-allowed' : 'bg-white dark:bg-gray-800'}
9292
transition-all duration-200
9393
`}>

packages/web/src/contexts/GraphContext.tsx

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
1+
import { createContext, useContext, useState, useEffect, useRef, ReactNode } from 'react';
22
import { useQuery, useMutation, useApolloClient } from '@apollo/client';
33
import { useAuth } from './AuthContext';
44
import { Graph, GraphHierarchy, CreateGraphInput, GraphContextType, GraphPermissions, ShareSettings } from '../types/graph';
@@ -25,7 +25,7 @@ export function GraphProvider({ children }: GraphProviderProps) {
2525
const [currentGraph, setCurrentGraph] = useState<Graph | null>(null);
2626
const [availableGraphs, setAvailableGraphs] = useState<Graph[]>([]);
2727
const [isCreating, setIsCreating] = useState(false);
28-
const [isCreatingWelcomeGraph, setIsCreatingWelcomeGraph] = useState(false);
28+
const isCreatingWelcomeGraphRef = useRef(false);
2929

3030

3131
// GraphQL operations - Load graphs for current user only
@@ -77,6 +77,18 @@ export function GraphProvider({ children }: GraphProviderProps) {
7777

7878
// Load graphs from GraphQL response only
7979
useEffect(() => {
80+
// GUARD: Don't run if we don't have a logged-in user
81+
if (!currentUser) {
82+
console.log('⏸️ No current user - skipping graph loading');
83+
return;
84+
}
85+
86+
// GUARD: Don't run while GraphQL query is loading
87+
if (isLoading) {
88+
console.log('⏸️ Still loading graphs - waiting...');
89+
return;
90+
}
91+
8092
// Combine user graphs, shared graphs, and system graphs
8193
// Remove duplicates by graph ID (some graphs may appear in multiple categories)
8294
const graphsMap = new Map();
@@ -91,18 +103,21 @@ export function GraphProvider({ children }: GraphProviderProps) {
91103
});
92104

93105
const allGraphs = Array.from(graphsMap.values());
106+
const userGraphs = graphsData?.userGraphs || [];
94107

95108
console.log('🔍 GraphContext useEffect triggered:', {
96109
isLoading,
97110
hasGraphsError: !!graphsError,
98111
hasCurrentUser: !!currentUser,
99112
currentUserId: currentUser?.id,
100113
allGraphsCount: allGraphs.length,
114+
userGraphsCount: userGraphs.length,
101115
systemGraphsCount: graphsData?.systemGraphs?.length || 0,
102-
userGraphsCount: graphsData?.userGraphs?.length || 0,
103-
sharedGraphsCount: graphsData?.sharedGraphs?.length || 0
116+
sharedGraphsCount: graphsData?.sharedGraphs?.length || 0,
117+
isCreatingWelcomeGraph: isCreatingWelcomeGraphRef.current
104118
});
105119

120+
// CASE 1: User has graphs - display them
106121
if (allGraphs.length > 0) {
107122
// Parse JSON strings in settings, permissions, shareSettings
108123
const parsedGraphs = allGraphs.map((g: any) => ({
@@ -148,24 +163,41 @@ export function GraphProvider({ children }: GraphProviderProps) {
148163
localStorage.setItem('currentGraphId', selectedGraph.id);
149164
}
150165
}
151-
} else if (!isLoading && !graphsError && currentUser && !isCreatingWelcomeGraph) {
152-
// Check if shared Welcome graph exists (created by server onboarding service)
153-
const hasWelcomeGraph = [...(graphsData?.systemGraphs || []), ...(graphsData?.sharedGraphs || [])]
154-
.some(g => g.name === 'Welcome');
155-
156-
if (!hasWelcomeGraph) {
157-
console.log('🎉 No graphs found for user:', currentUser.id, '- Triggering Welcome graph creation');
158-
// No graphs available - automatically create Welcome graph for new users
159-
setAvailableGraphs([]);
160-
setCurrentGraph(null);
161-
setIsCreatingWelcomeGraph(true);
162-
} else {
163-
console.log('✅ Shared Welcome graph already exists, skipping creation');
166+
} else {
167+
// CASE 2: User has NO graphs - check if we should create a Welcome graph
168+
169+
// GUARD: Don't create multiple Welcome graphs (prevent race condition)
170+
if (isCreatingWelcomeGraphRef.current) {
171+
console.log('⏸️ Already creating Welcome graph - skipping duplicate creation');
164172
return;
165173
}
166174

167-
// Create Welcome graph automatically with tutorial content
168-
const createWelcomeGraphWithTutorial = async () => {
175+
// Check if user already has a Welcome graph
176+
const hasWelcomeGraph = userGraphs.some((g: any) => g.name === 'Welcome');
177+
178+
console.log('🔍 DEBUG: No graphs found - checking Welcome graph creation', {
179+
userGraphsCount: userGraphs.length,
180+
allGraphsCount: allGraphs.length,
181+
hasWelcomeGraph,
182+
isCreatingWelcomeGraph: isCreatingWelcomeGraphRef.current,
183+
currentUserId: currentUser.id
184+
});
185+
186+
// CONDITION: Create Welcome graph ONLY if:
187+
// 1. User has NO graphs at all (allGraphs.length === 0)
188+
// 2. User doesn't already have a Welcome graph (!hasWelcomeGraph)
189+
// 3. We're not already creating one (!isCreatingWelcomeGraphRef.current)
190+
if (!hasWelcomeGraph && allGraphs.length === 0) {
191+
console.log('🎉 Creating Welcome graph for new user:', currentUser.id);
192+
193+
// Set flag IMMEDIATELY to prevent duplicate creation
194+
isCreatingWelcomeGraphRef.current = true;
195+
196+
setAvailableGraphs([]);
197+
setCurrentGraph(null);
198+
199+
// Create Welcome graph automatically with tutorial content
200+
const createWelcomeGraphWithTutorial = async () => {
169201
console.log('📝 Starting Welcome graph creation process...');
170202
try {
171203
// Step 1: Create the Welcome graph
@@ -347,13 +379,25 @@ export function GraphProvider({ children }: GraphProviderProps) {
347379
await refetchGraphs();
348380
} catch (error) {
349381
console.error('❌ Failed to create Welcome graph:', error);
350-
setIsCreatingWelcomeGraph(false); // Allow retry on next attempt
382+
isCreatingWelcomeGraphRef.current = false; // Allow retry on next attempt
351383
}
352384
};
353385

354-
createWelcomeGraphWithTutorial();
386+
createWelcomeGraphWithTutorial();
387+
}
355388
}
356-
}, [graphsData, currentTeam, currentUser, isLoading, graphsError, createGraphMutation, createWorkItemMutation, createEdgeMutation, refetchGraphs]);
389+
}, [
390+
graphsData,
391+
currentTeam,
392+
currentUser,
393+
isLoading,
394+
graphsError,
395+
createGraphMutation,
396+
createWorkItemMutation,
397+
createEdgeMutation,
398+
updateGraphMutation,
399+
refetchGraphs
400+
]);
357401

358402
// Build graph hierarchy
359403
const graphHierarchy: GraphHierarchy[] = availableGraphs

0 commit comments

Comments
 (0)