From 8c629a1a050f93f49d027aa6e87043716aa3bab7 Mon Sep 17 00:00:00 2001 From: simos Date: Mon, 17 Nov 2025 15:26:46 +0100 Subject: [PATCH] feat: onboarding page & adding git settings --- server/database/db.js | 64 ++++ server/database/init.sql | 5 +- server/index.js | 4 + server/routes/user.js | 93 +++++ src/components/GitSettings.jsx | 129 +++++++ src/components/Onboarding.jsx | 570 ++++++++++++++++++++++++++++++ src/components/ProtectedRoute.jsx | 17 +- src/components/Settings.jsx | 18 +- src/contexts/AuthContext.jsx | 27 +- src/utils/api.js | 17 +- 10 files changed, 934 insertions(+), 10 deletions(-) create mode 100644 server/routes/user.js create mode 100644 src/components/GitSettings.jsx create mode 100644 src/components/Onboarding.jsx diff --git a/server/database/db.js b/server/database/db.js index 335e387..dbb9d37 100644 --- a/server/database/db.js +++ b/server/database/db.js @@ -55,12 +55,40 @@ if (process.env.DATABASE_PATH) { console.log(c.dim('═'.repeat(60))); console.log(''); +const runMigrations = () => { + try { + const tableInfo = db.prepare("PRAGMA table_info(users)").all(); + const columnNames = tableInfo.map(col => col.name); + + if (!columnNames.includes('git_name')) { + console.log('Running migration: Adding git_name column'); + db.exec('ALTER TABLE users ADD COLUMN git_name TEXT'); + } + + if (!columnNames.includes('git_email')) { + console.log('Running migration: Adding git_email column'); + db.exec('ALTER TABLE users ADD COLUMN git_email TEXT'); + } + + if (!columnNames.includes('has_completed_onboarding')) { + console.log('Running migration: Adding has_completed_onboarding column'); + db.exec('ALTER TABLE users ADD COLUMN has_completed_onboarding BOOLEAN DEFAULT 0'); + } + + console.log('Database migrations completed successfully'); + } catch (error) { + console.error('Error running migrations:', error.message); + throw error; + } +}; + // Initialize database with schema const initializeDatabase = async () => { try { const initSQL = fs.readFileSync(INIT_SQL_PATH, 'utf8'); db.exec(initSQL); console.log('Database initialized successfully'); + runMigrations(); } catch (error) { console.error('Error initializing database:', error.message); throw error; @@ -126,6 +154,42 @@ const userDb = { } catch (err) { throw err; } + }, + + updateGitConfig: (userId, gitName, gitEmail) => { + try { + const stmt = db.prepare('UPDATE users SET git_name = ?, git_email = ? WHERE id = ?'); + stmt.run(gitName, gitEmail, userId); + } catch (err) { + throw err; + } + }, + + getGitConfig: (userId) => { + try { + const row = db.prepare('SELECT git_name, git_email FROM users WHERE id = ?').get(userId); + return row; + } catch (err) { + throw err; + } + }, + + completeOnboarding: (userId) => { + try { + const stmt = db.prepare('UPDATE users SET has_completed_onboarding = 1 WHERE id = ?'); + stmt.run(userId); + } catch (err) { + throw err; + } + }, + + hasCompletedOnboarding: (userId) => { + try { + const row = db.prepare('SELECT has_completed_onboarding FROM users WHERE id = ?').get(userId); + return row?.has_completed_onboarding === 1; + } catch (err) { + throw err; + } } }; diff --git a/server/database/init.sql b/server/database/init.sql index 5304481..e52daef 100644 --- a/server/database/init.sql +++ b/server/database/init.sql @@ -8,7 +8,10 @@ CREATE TABLE IF NOT EXISTS users ( password_hash TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, last_login DATETIME, - is_active BOOLEAN DEFAULT 1 + is_active BOOLEAN DEFAULT 1, + git_name TEXT, + git_email TEXT, + has_completed_onboarding BOOLEAN DEFAULT 0 ); -- Indexes for performance diff --git a/server/index.js b/server/index.js index 0514346..a094d9d 100755 --- a/server/index.js +++ b/server/index.js @@ -71,6 +71,7 @@ import settingsRoutes from './routes/settings.js'; import agentRoutes from './routes/agent.js'; import projectsRoutes from './routes/projects.js'; import cliAuthRoutes from './routes/cli-auth.js'; +import userRoutes from './routes/user.js'; import { initializeDatabase } from './database/db.js'; import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js'; @@ -254,6 +255,9 @@ app.use('/api/settings', authenticateToken, settingsRoutes); // CLI Authentication API Routes (protected) app.use('/api/cli', authenticateToken, cliAuthRoutes); +// User API Routes (protected) +app.use('/api/user', authenticateToken, userRoutes); + // Agent API Routes (uses API key authentication) app.use('/api/agent', agentRoutes); diff --git a/server/routes/user.js b/server/routes/user.js new file mode 100644 index 0000000..ef54078 --- /dev/null +++ b/server/routes/user.js @@ -0,0 +1,93 @@ +import express from 'express'; +import { userDb } from '../database/db.js'; +import { authenticateToken } from '../middleware/auth.js'; +import { exec } from 'child_process'; +import { promisify } from 'util'; + +const execAsync = promisify(exec); +const router = express.Router(); + +router.get('/git-config', authenticateToken, async (req, res) => { + try { + const userId = req.user.id; + const gitConfig = userDb.getGitConfig(userId); + + res.json({ + success: true, + gitName: gitConfig?.git_name || null, + gitEmail: gitConfig?.git_email || null + }); + } catch (error) { + console.error('Error getting git config:', error); + res.status(500).json({ error: 'Failed to get git configuration' }); + } +}); + +// Apply git config globally via git config --global +router.post('/git-config', authenticateToken, async (req, res) => { + try { + const userId = req.user.id; + const { gitName, gitEmail } = req.body; + + if (!gitName || !gitEmail) { + return res.status(400).json({ error: 'Git name and email are required' }); + } + + // Validate email format + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(gitEmail)) { + return res.status(400).json({ error: 'Invalid email format' }); + } + + userDb.updateGitConfig(userId, gitName, gitEmail); + + try { + await execAsync(`git config --global user.name "${gitName.replace(/"/g, '\\"')}"`); + await execAsync(`git config --global user.email "${gitEmail.replace(/"/g, '\\"')}"`); + console.log(`Applied git config globally: ${gitName} <${gitEmail}>`); + } catch (gitError) { + console.error('Error applying git config:', gitError); + } + + res.json({ + success: true, + gitName, + gitEmail + }); + } catch (error) { + console.error('Error updating git config:', error); + res.status(500).json({ error: 'Failed to update git configuration' }); + } +}); + +router.post('/complete-onboarding', authenticateToken, async (req, res) => { + try { + const userId = req.user.id; + userDb.completeOnboarding(userId); + + res.json({ + success: true, + message: 'Onboarding completed successfully' + }); + } catch (error) { + console.error('Error completing onboarding:', error); + res.status(500).json({ error: 'Failed to complete onboarding' }); + } +}); + +router.get('/onboarding-status', authenticateToken, async (req, res) => { + try { + const userId = req.user.id; + const hasCompleted = userDb.hasCompletedOnboarding(userId); + + res.json({ + success: true, + hasCompletedOnboarding: hasCompleted + }); + } catch (error) { + console.error('Error checking onboarding status:', error); + res.status(500).json({ error: 'Failed to check onboarding status' }); + } +}); + +export default router; diff --git a/src/components/GitSettings.jsx b/src/components/GitSettings.jsx new file mode 100644 index 0000000..91b0902 --- /dev/null +++ b/src/components/GitSettings.jsx @@ -0,0 +1,129 @@ +import { useState, useEffect } from 'react'; +import { Button } from './ui/button'; +import { Input } from './ui/input'; +import { GitBranch, Check } from 'lucide-react'; +import { authenticatedFetch } from '../utils/api'; + +function GitSettings() { + const [gitName, setGitName] = useState(''); + const [gitEmail, setGitEmail] = useState(''); + const [gitConfigLoading, setGitConfigLoading] = useState(false); + const [gitConfigSaving, setGitConfigSaving] = useState(false); + const [saveStatus, setSaveStatus] = useState(null); + + useEffect(() => { + loadGitConfig(); + }, []); + + const loadGitConfig = async () => { + try { + setGitConfigLoading(true); + const response = await authenticatedFetch('/api/user/git-config'); + if (response.ok) { + const data = await response.json(); + setGitName(data.gitName || ''); + setGitEmail(data.gitEmail || ''); + } + } catch (error) { + console.error('Error loading git config:', error); + } finally { + setGitConfigLoading(false); + } + }; + + const saveGitConfig = async () => { + try { + setGitConfigSaving(true); + const response = await authenticatedFetch('/api/user/git-config', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ gitName, gitEmail }) + }); + + if (response.ok) { + setSaveStatus('success'); + setTimeout(() => setSaveStatus(null), 3000); + } else { + const data = await response.json(); + setSaveStatus('error'); + console.error('Failed to save git config:', data.error); + } + } catch (error) { + console.error('Error saving git config:', error); + setSaveStatus('error'); + } finally { + setGitConfigSaving(false); + } + }; + + return ( +
+
+
+ +

Git Configuration

+
+ +

+ Configure your git identity for commits. These settings will be applied globally via git config --global +

+ +
+
+ + setGitName(e.target.value)} + placeholder="John Doe" + disabled={gitConfigLoading} + className="w-full" + /> +

+ Your name for git commits +

+
+ +
+ + setGitEmail(e.target.value)} + placeholder="john@example.com" + disabled={gitConfigLoading} + className="w-full" + /> +

+ Your email for git commits +

+
+ +
+ + + {saveStatus === 'success' && ( +
+ + Saved successfully +
+ )} +
+
+
+
+ ); +} + +export default GitSettings; diff --git a/src/components/Onboarding.jsx b/src/components/Onboarding.jsx new file mode 100644 index 0000000..69dfdd8 --- /dev/null +++ b/src/components/Onboarding.jsx @@ -0,0 +1,570 @@ +import React, { useState, useEffect } from 'react'; +import { ChevronRight, ChevronLeft, Check, GitBranch, User, Mail, LogIn, ExternalLink, Loader2 } from 'lucide-react'; +import ClaudeLogo from './ClaudeLogo'; +import CursorLogo from './CursorLogo'; +import LoginModal from './LoginModal'; +import { authenticatedFetch } from '../utils/api'; +import { useAuth } from '../contexts/AuthContext'; + +const Onboarding = ({ onComplete }) => { + const [currentStep, setCurrentStep] = useState(0); + const [gitName, setGitName] = useState(''); + const [gitEmail, setGitEmail] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(''); + + // CLI authentication states + const [showLoginModal, setShowLoginModal] = useState(false); + const [loginProvider, setLoginProvider] = useState(''); + const [selectedProject, setSelectedProject] = useState({ name: 'default', fullPath: process.cwd() }); + + const [claudeAuthStatus, setClaudeAuthStatus] = useState({ + authenticated: false, + email: null, + loading: true, + error: null + }); + + const [cursorAuthStatus, setCursorAuthStatus] = useState({ + authenticated: false, + email: null, + loading: true, + error: null + }); + + const { user } = useAuth(); + + // Check authentication status on mount and when modal closes + useEffect(() => { + checkClaudeAuthStatus(); + checkCursorAuthStatus(); + }, []); + + // Auto-check authentication status periodically when on CLI steps + useEffect(() => { + if (currentStep === 1 || currentStep === 2) { + const interval = setInterval(() => { + if (currentStep === 1) { + checkClaudeAuthStatus(); + } else if (currentStep === 2) { + checkCursorAuthStatus(); + } + }, 3000); // Check every 3 seconds + + return () => clearInterval(interval); + } + }, [currentStep]); + + const checkClaudeAuthStatus = async () => { + try { + const response = await authenticatedFetch('/api/cli/claude/status'); + if (response.ok) { + const data = await response.json(); + setClaudeAuthStatus({ + authenticated: data.authenticated, + email: data.email, + loading: false, + error: data.error || null + }); + } else { + setClaudeAuthStatus({ + authenticated: false, + email: null, + loading: false, + error: 'Failed to check authentication status' + }); + } + } catch (error) { + console.error('Error checking Claude auth status:', error); + setClaudeAuthStatus({ + authenticated: false, + email: null, + loading: false, + error: error.message + }); + } + }; + + const checkCursorAuthStatus = async () => { + try { + const response = await authenticatedFetch('/api/cli/cursor/status'); + if (response.ok) { + const data = await response.json(); + setCursorAuthStatus({ + authenticated: data.authenticated, + email: data.email, + loading: false, + error: data.error || null + }); + } else { + setCursorAuthStatus({ + authenticated: false, + email: null, + loading: false, + error: 'Failed to check authentication status' + }); + } + } catch (error) { + console.error('Error checking Cursor auth status:', error); + setCursorAuthStatus({ + authenticated: false, + email: null, + loading: false, + error: error.message + }); + } + }; + + const handleClaudeLogin = () => { + setLoginProvider('claude'); + setShowLoginModal(true); + }; + + const handleCursorLogin = () => { + setLoginProvider('cursor'); + setShowLoginModal(true); + }; + + const handleLoginComplete = (exitCode) => { + if (exitCode === 0) { + if (loginProvider === 'claude') { + checkClaudeAuthStatus(); + } else if (loginProvider === 'cursor') { + checkCursorAuthStatus(); + } + } + }; + + const handleNextStep = async () => { + setError(''); + + // Step 0: Git config validation and submission + if (currentStep === 0) { + if (!gitName.trim() || !gitEmail.trim()) { + setError('Both git name and email are required'); + return; + } + + // Validate email format + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(gitEmail)) { + setError('Please enter a valid email address'); + return; + } + + setIsSubmitting(true); + try { + // Save git config to backend (which will also apply git config --global) + const response = await authenticatedFetch('/api/user/git-config', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ gitName, gitEmail }) + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to save git configuration'); + } + + setCurrentStep(currentStep + 1); + } catch (err) { + setError(err.message); + } finally { + setIsSubmitting(false); + } + return; + } + + // Other steps: just move forward + setCurrentStep(currentStep + 1); + }; + + const handlePrevStep = () => { + setError(''); + setCurrentStep(currentStep - 1); + }; + + const handleFinish = async () => { + setIsSubmitting(true); + setError(''); + + try { + // Mark onboarding as complete + const response = await authenticatedFetch('/api/user/complete-onboarding', { + method: 'POST' + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to complete onboarding'); + } + + // Call the onComplete callback + if (onComplete) { + onComplete(); + } + } catch (err) { + setError(err.message); + } finally { + setIsSubmitting(false); + } + }; + + const steps = [ + { + title: 'Git Configuration', + description: 'Set up your git identity for commits', + icon: GitBranch, + required: true + }, + { + title: 'Claude Code CLI', + description: 'Connect your Claude Code account', + icon: () => , + required: false + }, + { + title: 'Cursor CLI', + description: 'Connect your Cursor account', + icon: () => , + required: false + } + ]; + + const renderStepContent = () => { + switch (currentStep) { + case 0: + return ( +
+
+
+ +
+

Git Configuration

+

+ Configure your git identity to ensure proper attribution for your commits +

+
+ +
+
+ + setGitName(e.target.value)} + className="w-full px-4 py-3 border border-border rounded-lg bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="John Doe" + required + disabled={isSubmitting} + /> +

+ This will be used as: git config --global user.name +

+
+ +
+ + setGitEmail(e.target.value)} + className="w-full px-4 py-3 border border-border rounded-lg bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="john@example.com" + required + disabled={isSubmitting} + /> +

+ This will be used as: git config --global user.email +

+
+
+
+ ); + + case 1: + return ( +
+
+
+ +
+

Claude Code CLI

+

+ Connect your Claude account to enable AI-powered coding features +

+
+ + {/* Auth Status Card */} +
+
+
+
+ + {claudeAuthStatus.loading ? 'Checking...' : + claudeAuthStatus.authenticated ? 'Connected' : 'Not Connected'} + +
+ {claudeAuthStatus.authenticated && ( + + )} +
+ + {claudeAuthStatus.authenticated && claudeAuthStatus.email && ( +

+ Signed in as: {claudeAuthStatus.email} +

+ )} + + {!claudeAuthStatus.authenticated && ( + <> +

+ Click the button below to authenticate with Claude Code CLI. A terminal will open with authentication instructions. +

+ +

+ Or manually run: claude auth login +

+ + )} + + {claudeAuthStatus.error && !claudeAuthStatus.authenticated && ( +
+

{claudeAuthStatus.error}

+
+ )} +
+ +
+

This step is optional. You can skip and configure it later in Settings.

+
+
+ ); + + case 2: + return ( +
+
+
+ +
+

Cursor CLI

+

+ Connect your Cursor account to enable AI-powered features +

+
+ + {/* Auth Status Card */} +
+
+
+
+ + {cursorAuthStatus.loading ? 'Checking...' : + cursorAuthStatus.authenticated ? 'Connected' : 'Not Connected'} + +
+ {cursorAuthStatus.authenticated && ( + + )} +
+ + {cursorAuthStatus.authenticated && cursorAuthStatus.email && ( +

+ Signed in as: {cursorAuthStatus.email} +

+ )} + + {!cursorAuthStatus.authenticated && ( + <> +

+ Click the button below to authenticate with Cursor CLI. A terminal will open with authentication instructions. +

+ +

+ Or manually run: cursor auth login +

+ + )} + + {cursorAuthStatus.error && !cursorAuthStatus.authenticated && ( +
+

{cursorAuthStatus.error}

+
+ )} +
+ +
+

This step is optional. You can skip and configure it later in Settings.

+
+
+ ); + + default: + return null; + } + }; + + const isStepValid = () => { + switch (currentStep) { + case 0: + return gitName.trim() && gitEmail.trim() && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(gitEmail); + case 1: + case 2: + return true; // CLI steps are optional + default: + return false; + } + }; + + return ( + <> +
+
+ {/* Progress Steps */} +
+
+ {steps.map((step, index) => ( + +
+
+ {index < currentStep ? ( + + ) : typeof step.icon === 'function' ? ( + + ) : ( + + )} +
+
+

+ {step.title} +

+ {step.required && ( + Required + )} +
+
+ {index < steps.length - 1 && ( +
+ )} + + ))} +
+
+ + {/* Main Card */} +
+ {renderStepContent()} + + {/* Error Message */} + {error && ( +
+

{error}

+
+ )} + + {/* Navigation Buttons */} +
+ + +
+ {currentStep < steps.length - 1 ? ( + + ) : ( + + )} +
+
+
+
+
+ + {/* Login Modal */} + {showLoginModal && ( + setShowLoginModal(false)} + provider={loginProvider} + project={selectedProject} + onLoginComplete={handleLoginComplete} + /> + )} + + ); +}; + +export default Onboarding; diff --git a/src/components/ProtectedRoute.jsx b/src/components/ProtectedRoute.jsx index 16419d0..56343ae 100644 --- a/src/components/ProtectedRoute.jsx +++ b/src/components/ProtectedRoute.jsx @@ -2,6 +2,7 @@ import React from 'react'; import { useAuth } from '../contexts/AuthContext'; import SetupForm from './SetupForm'; import LoginForm from './LoginForm'; +import Onboarding from './Onboarding'; import { MessageSquare } from 'lucide-react'; const LoadingScreen = () => ( @@ -24,14 +25,20 @@ const LoadingScreen = () => ( ); const ProtectedRoute = ({ children }) => { - const { user, isLoading, needsSetup } = useAuth(); + const { user, isLoading, needsSetup, hasCompletedOnboarding, refreshOnboardingStatus } = useAuth(); - // Platform mode: skip all auth UI and directly render children if (import.meta.env.VITE_IS_PLATFORM === 'true') { + if (isLoading) { + return ; + } + + if (!hasCompletedOnboarding) { + return ; + } + return children; } - // Normal OSS mode: standard auth flow if (isLoading) { return ; } @@ -44,6 +51,10 @@ const ProtectedRoute = ({ children }) => { return ; } + if (!hasCompletedOnboarding) { + return ; + } + return children; }; diff --git a/src/components/Settings.jsx b/src/components/Settings.jsx index d474f98..60fe047 100644 --- a/src/components/Settings.jsx +++ b/src/components/Settings.jsx @@ -2,12 +2,13 @@ import { useState, useEffect } from 'react'; import { Button } from './ui/button'; import { Input } from './ui/input'; import { Badge } from './ui/badge'; -import { X, Plus, Settings as SettingsIcon, Shield, AlertTriangle, Moon, Sun, Server, Edit3, Trash2, Globe, Terminal, Zap, FolderOpen, LogIn, Key } from 'lucide-react'; +import { X, Plus, Settings as SettingsIcon, Shield, AlertTriangle, Moon, Sun, Server, Edit3, Trash2, Globe, Terminal, Zap, FolderOpen, LogIn, Key, GitBranch, Check } from 'lucide-react'; import { useTheme } from '../contexts/ThemeContext'; import { useTasksSettings } from '../contexts/TasksSettingsContext'; import ClaudeLogo from './ClaudeLogo'; import CursorLogo from './CursorLogo'; import CredentialsSettings from './CredentialsSettings'; +import GitSettings from './GitSettings'; import LoginModal from './LoginModal'; import { authenticatedFetch } from '../utils/api'; @@ -387,7 +388,6 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) { await fetchCursorMcpServers(); } catch (error) { console.error('Error loading tool settings:', error); - // Set defaults on error setAllowedTools([]); setDisallowedTools([]); setSkipPermissions(false); @@ -743,6 +743,17 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) { > Appearance +
)} + {/* Git Tab */} + {activeTab === 'git' && } + {/* Tools Tab */} {activeTab === 'tools' && (
diff --git a/src/contexts/AuthContext.jsx b/src/contexts/AuthContext.jsx index 4f9b200..ae1ed43 100644 --- a/src/contexts/AuthContext.jsx +++ b/src/contexts/AuthContext.jsx @@ -9,6 +9,8 @@ const AuthContext = createContext({ logout: () => {}, isLoading: true, needsSetup: false, + hasCompletedOnboarding: true, + refreshOnboardingStatus: () => {}, error: null }); @@ -25,22 +27,38 @@ export const AuthProvider = ({ children }) => { const [token, setToken] = useState(localStorage.getItem('auth-token')); const [isLoading, setIsLoading] = useState(true); const [needsSetup, setNeedsSetup] = useState(false); + const [hasCompletedOnboarding, setHasCompletedOnboarding] = useState(true); const [error, setError] = useState(null); - // Check authentication status on mount useEffect(() => { - // Platform mode: skip all auth checks, set dummy user if (import.meta.env.VITE_IS_PLATFORM === 'true') { setUser({ username: 'platform-user' }); setNeedsSetup(false); + checkOnboardingStatus(); setIsLoading(false); return; } - // Normal OSS mode: check auth status checkAuthStatus(); }, []); + const checkOnboardingStatus = async () => { + try { + const response = await api.user.onboardingStatus(); + if (response.ok) { + const data = await response.json(); + setHasCompletedOnboarding(data.hasCompletedOnboarding); + } + } catch (error) { + console.error('Error checking onboarding status:', error); + setHasCompletedOnboarding(true); + } + }; + + const refreshOnboardingStatus = async () => { + await checkOnboardingStatus(); + }; + const checkAuthStatus = async () => { try { setIsLoading(true); @@ -65,6 +83,7 @@ export const AuthProvider = ({ children }) => { const userData = await userResponse.json(); setUser(userData.user); setNeedsSetup(false); + await checkOnboardingStatus(); } else { // Token is invalid localStorage.removeItem('auth-token'); @@ -156,6 +175,8 @@ export const AuthProvider = ({ children }) => { logout, isLoading, needsSetup, + hasCompletedOnboarding, + refreshOnboardingStatus, error }; diff --git a/src/utils/api.js b/src/utils/api.js index 9d4220a..f0b8e6f 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -138,10 +138,25 @@ export const api = { browseFilesystem: (dirPath = null) => { const params = new URLSearchParams(); if (dirPath) params.append('path', dirPath); - + return authenticatedFetch(`/api/browse-filesystem?${params}`); }, + // User endpoints + user: { + gitConfig: () => authenticatedFetch('/api/user/git-config'), + updateGitConfig: (gitName, gitEmail) => + authenticatedFetch('/api/user/git-config', { + method: 'POST', + body: JSON.stringify({ gitName, gitEmail }), + }), + onboardingStatus: () => authenticatedFetch('/api/user/onboarding-status'), + completeOnboarding: () => + authenticatedFetch('/api/user/complete-onboarding', { + method: 'POST', + }), + }, + // Generic GET method for any endpoint get: (endpoint) => authenticatedFetch(`/api${endpoint}`), }; \ No newline at end of file