Revert "fix: resolve chat conversation hanging and stream interruption issues (#1971)"
This reverts commit e68593f22d.
This commit is contained in:
@@ -3,8 +3,7 @@ import { toast } from 'react-toastify';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { netlifyConnection, updateNetlifyConnection, initializeNetlifyConnection } from '~/lib/stores/netlify';
|
||||
import type { NetlifySite, NetlifyDeploy, NetlifyBuild } from '~/types/netlify';
|
||||
import { NetlifyQuickConnect } from './NetlifyQuickConnect';
|
||||
import type { NetlifySite, NetlifyDeploy, NetlifyBuild, NetlifyUser } from '~/types/netlify';
|
||||
import {
|
||||
CloudIcon,
|
||||
BuildingLibraryIcon,
|
||||
@@ -43,16 +42,29 @@ interface SiteAction {
|
||||
}
|
||||
|
||||
export default function NetlifyConnection() {
|
||||
console.log('NetlifyConnection component mounted');
|
||||
|
||||
const connection = useStore(netlifyConnection);
|
||||
const [tokenInput, setTokenInput] = useState('');
|
||||
const [fetchingStats, setFetchingStats] = useState(false);
|
||||
const [sites, setSites] = useState<NetlifySite[]>([]);
|
||||
const [deploys, setDeploys] = useState<NetlifyDeploy[]>([]);
|
||||
const [builds, setBuilds] = useState<NetlifyBuild[]>([]);
|
||||
|
||||
console.log('NetlifyConnection initial state:', {
|
||||
connection: {
|
||||
user: connection.user,
|
||||
token: connection.token ? '[TOKEN_EXISTS]' : '[NO_TOKEN]',
|
||||
},
|
||||
envToken: import.meta.env?.VITE_NETLIFY_ACCESS_TOKEN ? '[ENV_TOKEN_EXISTS]' : '[NO_ENV_TOKEN]',
|
||||
});
|
||||
|
||||
const [deploymentCount, setDeploymentCount] = useState(0);
|
||||
const [lastUpdated, setLastUpdated] = useState('');
|
||||
const [isStatsOpen, setIsStatsOpen] = useState(false);
|
||||
const [activeSiteIndex, setActiveSiteIndex] = useState(0);
|
||||
const [isActionLoading, setIsActionLoading] = useState(false);
|
||||
const [isConnecting, setIsConnecting] = useState(false);
|
||||
|
||||
// Add site actions
|
||||
const siteActions: SiteAction[] = [
|
||||
@@ -139,6 +151,8 @@ export default function NetlifyConnection() {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Netlify: Running initialization useEffect');
|
||||
|
||||
// Initialize connection with environment token if available
|
||||
initializeNetlifyConnection();
|
||||
}, []);
|
||||
@@ -159,6 +173,46 @@ export default function NetlifyConnection() {
|
||||
}
|
||||
}, [connection]);
|
||||
|
||||
const handleConnect = async () => {
|
||||
if (!tokenInput) {
|
||||
toast.error('Please enter a Netlify API token');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsConnecting(true);
|
||||
|
||||
try {
|
||||
const response = await fetch('https://api.netlify.com/api/v1/user', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokenInput}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
const userData = (await response.json()) as NetlifyUser;
|
||||
|
||||
// Update the connection store
|
||||
updateNetlifyConnection({
|
||||
user: userData,
|
||||
token: tokenInput,
|
||||
});
|
||||
|
||||
toast.success('Connected to Netlify successfully');
|
||||
|
||||
// Fetch stats after successful connection
|
||||
fetchNetlifyStats(tokenInput);
|
||||
} catch (error) {
|
||||
console.error('Error connecting to Netlify:', error);
|
||||
toast.error(`Failed to connect to Netlify: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
} finally {
|
||||
setIsConnecting(false);
|
||||
setTokenInput('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDisconnect = () => {
|
||||
// Clear from localStorage
|
||||
localStorage.removeItem('netlify_connection');
|
||||
@@ -608,15 +662,76 @@ export default function NetlifyConnection() {
|
||||
|
||||
{!connection.user ? (
|
||||
<div className="mt-4">
|
||||
<NetlifyQuickConnect
|
||||
onSuccess={() => {
|
||||
// Fetch stats after successful connection
|
||||
if (connection.token) {
|
||||
fetchNetlifyStats(connection.token);
|
||||
}
|
||||
}}
|
||||
showInstructions={true}
|
||||
<label className="block text-sm text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary mb-2">
|
||||
API Token
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
value={tokenInput}
|
||||
onChange={(e) => setTokenInput(e.target.value)}
|
||||
placeholder="Enter your Netlify API token"
|
||||
className={classNames(
|
||||
'w-full px-3 py-2 rounded-lg text-sm',
|
||||
'bg-[#F8F8F8] dark:bg-[#1A1A1A]',
|
||||
'border border-[#E5E5E5] dark:border-[#333333]',
|
||||
'text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary',
|
||||
'focus:outline-none focus:ring-1 focus:ring-bolt-elements-borderColorActive',
|
||||
'disabled:opacity-50',
|
||||
)}
|
||||
/>
|
||||
<div className="mt-2 text-sm text-bolt-elements-textSecondary">
|
||||
<a
|
||||
href="https://app.netlify.com/user/applications#personal-access-tokens"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-bolt-elements-borderColorActive hover:underline inline-flex items-center gap-1"
|
||||
>
|
||||
Get your token
|
||||
<div className="i-ph:arrow-square-out w-4 h-4" />
|
||||
</a>
|
||||
</div>
|
||||
{/* Debug info - remove this later */}
|
||||
<div className="mt-2 text-xs text-gray-500">
|
||||
<p>Debug: Token present: {connection.token ? '✅' : '❌'}</p>
|
||||
<p>Debug: User present: {connection.user ? '✅' : '❌'}</p>
|
||||
<p>Debug: Env token: {import.meta.env?.VITE_NETLIFY_ACCESS_TOKEN ? '✅' : '❌'}</p>
|
||||
</div>
|
||||
<div className="flex gap-2 mt-4">
|
||||
<button
|
||||
onClick={handleConnect}
|
||||
disabled={isConnecting || !tokenInput}
|
||||
className={classNames(
|
||||
'px-4 py-2 rounded-lg text-sm flex items-center gap-2',
|
||||
'bg-[#303030] text-white',
|
||||
'hover:bg-[#5E41D0] hover:text-white',
|
||||
'disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200',
|
||||
'transform active:scale-95',
|
||||
)}
|
||||
>
|
||||
{isConnecting ? (
|
||||
<>
|
||||
<div className="i-ph:spinner-gap animate-spin" />
|
||||
Connecting...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="i-ph:plug-charging w-4 h-4" />
|
||||
Connect
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Debug button - remove this later */}
|
||||
<button
|
||||
onClick={async () => {
|
||||
console.log('Manual Netlify auto-connect test');
|
||||
await initializeNetlifyConnection();
|
||||
}}
|
||||
className="px-3 py-2 rounded-lg text-xs bg-blue-500 text-white hover:bg-blue-600"
|
||||
>
|
||||
Test Auto-Connect
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col w-full gap-4 mt-4">
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { updateNetlifyConnection } from '~/lib/stores/netlify';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
|
||||
interface NetlifyQuickConnectProps {
|
||||
onSuccess?: () => void;
|
||||
showInstructions?: boolean;
|
||||
}
|
||||
|
||||
export const NetlifyQuickConnect: React.FC<NetlifyQuickConnectProps> = ({ onSuccess, showInstructions = true }) => {
|
||||
const [token, setToken] = useState('');
|
||||
const [isConnecting, setIsConnecting] = useState(false);
|
||||
const [showHelp, setShowHelp] = useState(false);
|
||||
|
||||
const handleConnect = async () => {
|
||||
if (!token.trim()) {
|
||||
toast.error('Please enter your Netlify API token');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsConnecting(true);
|
||||
|
||||
try {
|
||||
// Validate token with Netlify API
|
||||
const response = await fetch('https://api.netlify.com/api/v1/user', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Invalid token or authentication failed');
|
||||
}
|
||||
|
||||
const userData = (await response.json()) as any;
|
||||
|
||||
// Fetch initial site statistics
|
||||
const sitesResponse = await fetch('https://api.netlify.com/api/v1/sites', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
let sites: any[] = [];
|
||||
|
||||
if (sitesResponse.ok) {
|
||||
sites = (await sitesResponse.json()) as any[];
|
||||
}
|
||||
|
||||
// Update the connection store
|
||||
updateNetlifyConnection({
|
||||
user: userData,
|
||||
token,
|
||||
stats: {
|
||||
sites,
|
||||
totalSites: sites.length,
|
||||
deploys: [],
|
||||
builds: [],
|
||||
lastDeployTime: '',
|
||||
},
|
||||
});
|
||||
|
||||
toast.success(`Connected to Netlify as ${userData.email || userData.name || 'User'}`);
|
||||
setToken(''); // Clear the token field
|
||||
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Netlify connection error:', error);
|
||||
toast.error('Failed to connect to Netlify. Please check your token.');
|
||||
} finally {
|
||||
setIsConnecting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="block text-sm font-medium text-bolt-elements-textPrimary">Personal Access Token</label>
|
||||
{showInstructions && (
|
||||
<button
|
||||
onClick={() => setShowHelp(!showHelp)}
|
||||
className="text-xs text-accent-500 hover:text-accent-600 flex items-center gap-1"
|
||||
>
|
||||
<span className={classNames('i-ph:question-circle', showHelp ? 'text-accent-600' : '')} />
|
||||
How to get token
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="password"
|
||||
value={token}
|
||||
onChange={(e) => setToken(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && token.trim() && !isConnecting) {
|
||||
handleConnect();
|
||||
}
|
||||
}}
|
||||
placeholder="Enter your Netlify API token"
|
||||
className={classNames(
|
||||
'w-full px-3 py-2 pr-10 rounded-lg text-sm',
|
||||
'bg-bolt-elements-background-depth-1',
|
||||
'border border-bolt-elements-borderColor',
|
||||
'text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary',
|
||||
'focus:outline-none focus:ring-2 focus:ring-accent-500 focus:border-transparent',
|
||||
'disabled:opacity-50',
|
||||
)}
|
||||
disabled={isConnecting}
|
||||
/>
|
||||
{token && (
|
||||
<button
|
||||
onClick={() => setToken('')}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-bolt-elements-textTertiary hover:text-bolt-elements-textSecondary"
|
||||
>
|
||||
<span className="i-ph:x text-lg" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showHelp && showInstructions && (
|
||||
<div className="bg-bolt-elements-background-depth-2 rounded-lg p-4 space-y-3 animate-in fade-in-0 slide-in-from-top-2">
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="i-ph:info text-accent-500 mt-0.5" />
|
||||
<div className="space-y-2 text-sm">
|
||||
<p className="font-medium text-bolt-elements-textPrimary">
|
||||
Getting your Netlify Personal Access Token:
|
||||
</p>
|
||||
<ol className="space-y-2 text-bolt-elements-textSecondary">
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-accent-500 font-medium">1.</span>
|
||||
<span>
|
||||
Go to{' '}
|
||||
<a
|
||||
href="https://app.netlify.com/user/applications#personal-access-tokens"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-accent-500 hover:text-accent-600 underline inline-flex items-center gap-1"
|
||||
>
|
||||
Netlify Account Settings
|
||||
<span className="i-ph:arrow-square-out text-xs" />
|
||||
</a>
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-accent-500 font-medium">2.</span>
|
||||
<span>Navigate to "Applications" → "Personal access tokens"</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-accent-500 font-medium">3.</span>
|
||||
<span>Click "New access token"</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-accent-500 font-medium">4.</span>
|
||||
<span>Give it a descriptive name (e.g., "bolt.diy deployment")</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-accent-500 font-medium">5.</span>
|
||||
<span>Copy the token and paste it above</span>
|
||||
</li>
|
||||
</ol>
|
||||
<div className="pt-2 border-t border-bolt-elements-borderColor">
|
||||
<p className="text-xs text-bolt-elements-textTertiary">
|
||||
<strong>Note:</strong> Keep your token safe! It provides full access to your Netlify account.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3">
|
||||
<a
|
||||
href="https://app.netlify.com/user/applications#personal-access-tokens"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="px-4 py-2 rounded-lg border border-bolt-elements-borderColor text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-2 transition-all text-sm font-medium flex items-center gap-2"
|
||||
>
|
||||
<span className="i-ph:arrow-square-out" />
|
||||
Get Token
|
||||
</a>
|
||||
<button
|
||||
onClick={handleConnect}
|
||||
disabled={isConnecting || !token.trim()}
|
||||
className={classNames(
|
||||
'flex-1 px-4 py-2 rounded-lg font-medium transition-all text-sm',
|
||||
'bg-accent-500 text-white',
|
||||
'hover:bg-accent-600',
|
||||
'disabled:opacity-50 disabled:cursor-not-allowed',
|
||||
'flex items-center justify-center gap-2',
|
||||
)}
|
||||
>
|
||||
{isConnecting ? (
|
||||
<>
|
||||
<span className="i-svg-spinners:3-dots-scale" />
|
||||
Connecting...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="i-ph:plug-charging" />
|
||||
Connect to Netlify
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-3 bg-accent-500/10 border border-accent-500/20 rounded-lg">
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="i-ph:lightning text-accent-500 mt-0.5" />
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-bolt-elements-textPrimary">Quick Tip</p>
|
||||
<p className="text-xs text-bolt-elements-textSecondary">
|
||||
Once connected, you can deploy any project with a single click directly from the editor!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user