mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
Fix close button styling in dialog components to address ghost white issue in dark mode
This commit is contained in:
parent
0775c82e00
commit
306bb29053
@ -55,6 +55,7 @@ export function GitHubAuthDialog({ isOpen, onClose }: GitHubAuthDialogProps) {
|
||||
Cookies.set('git:github.com', JSON.stringify({ username: token, password: 'x-oauth-basic' }));
|
||||
|
||||
toast.success(`Successfully connected as ${userData.login}`);
|
||||
setToken('');
|
||||
onClose();
|
||||
} else {
|
||||
if (response.status === 401) {
|
||||
@ -174,7 +175,7 @@ export function GitHubAuthDialog({ isOpen, onClose }: GitHubAuthDialogProps) {
|
||||
<Dialog.Close asChild>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-4 py-1.5 bg-[#F5F5F5] hover:bg-[#E5E5E5] dark:bg-[#252525] dark:hover:bg-[#333333] rounded-lg text-[#111111] dark:text-white transition-colors text-sm"
|
||||
className="px-4 py-1.5 bg-transparent bg-[#F5F5F5] hover:bg-[#E5E5E5] dark:bg-[#252525] dark:hover:bg-[#333333] rounded-lg text-[#111111] dark:text-white transition-colors text-sm"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
|
@ -1,7 +1,11 @@
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
|
||||
// Internal imports
|
||||
import { getLocalStorage } from '~/lib/persistence';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
import type { GitHubUserResponse } from '~/types/GitHub';
|
||||
@ -10,10 +14,9 @@ import { workbenchStore } from '~/lib/stores/workbench';
|
||||
import { extractRelativePath } from '~/utils/diff';
|
||||
import { formatSize } from '~/utils/formatSize';
|
||||
import type { FileMap, File } from '~/lib/stores/files';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
|
||||
// Import UI components
|
||||
import { Badge, EmptyState, StatusIndicator, SearchInput, CloseButton } from '~/components/ui';
|
||||
// UI Components
|
||||
import { Badge, EmptyState, StatusIndicator, SearchInput } from '~/components/ui';
|
||||
|
||||
interface PushToGitHubDialogProps {
|
||||
isOpen: boolean;
|
||||
@ -64,6 +67,8 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial
|
||||
}, [isOpen]);
|
||||
|
||||
// Filter repositories based on search query
|
||||
// const debouncedSetRepoSearchQuery = useDebouncedCallback((value: string) => setRepoSearchQuery(value), 300);
|
||||
|
||||
useEffect(() => {
|
||||
if (recentRepos.length === 0) {
|
||||
setFilteredRepos([]);
|
||||
@ -86,7 +91,7 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial
|
||||
setFilteredRepos(filtered);
|
||||
}, [recentRepos, repoSearchQuery]);
|
||||
|
||||
const fetchRecentRepos = async (token: string) => {
|
||||
const fetchRecentRepos = useCallback(async (token: string) => {
|
||||
if (!token) {
|
||||
logStore.logError('No GitHub token available');
|
||||
toast.error('GitHub authentication required');
|
||||
@ -98,84 +103,77 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial
|
||||
setIsFetchingRepos(true);
|
||||
console.log('Fetching GitHub repositories with token:', token.substring(0, 5) + '...');
|
||||
|
||||
/*
|
||||
* Log the request details for debugging
|
||||
* According to GitHub API docs, we can't use both 'type' and 'affiliation' parameters together
|
||||
*/
|
||||
const requestUrl =
|
||||
'https://api.github.com/user/repos?sort=updated&per_page=10&affiliation=owner,organization_member';
|
||||
console.log('Request URL:', requestUrl);
|
||||
console.log('Request headers:', {
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${token.substring(0, 5)}...`,
|
||||
});
|
||||
// Fetch ALL repos by paginating through all pages
|
||||
let allRepos: GitHubRepo[] = [];
|
||||
let page = 1;
|
||||
let hasMore = true;
|
||||
|
||||
const response = await fetch(requestUrl, {
|
||||
headers: {
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${token.trim()}`,
|
||||
},
|
||||
});
|
||||
while (hasMore) {
|
||||
const requestUrl = `https://api.github.com/user/repos?sort=updated&per_page=100&page=${page}&affiliation=owner,organization_member`;
|
||||
const response = await fetch(requestUrl, {
|
||||
headers: {
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${token.trim()}`,
|
||||
},
|
||||
});
|
||||
|
||||
// Log response status for debugging
|
||||
console.log('Response status:', response.status, response.statusText);
|
||||
console.log('Response headers:', {
|
||||
'X-RateLimit-Limit': response.headers.get('x-ratelimit-limit'),
|
||||
'X-RateLimit-Remaining': response.headers.get('x-ratelimit-remaining'),
|
||||
'X-RateLimit-Used': response.headers.get('x-ratelimit-used'),
|
||||
});
|
||||
if (!response.ok) {
|
||||
let errorData: { message?: string } = {};
|
||||
|
||||
if (!response.ok) {
|
||||
let errorData: { message?: string } = {};
|
||||
try {
|
||||
errorData = await response.json();
|
||||
console.error('Error response data:', errorData);
|
||||
} catch (e) {
|
||||
errorData = { message: 'Could not parse error response' };
|
||||
console.error('Could not parse error response:', e);
|
||||
}
|
||||
|
||||
if (response.status === 401) {
|
||||
toast.error('GitHub token expired. Please reconnect your account.');
|
||||
|
||||
// Clear invalid token
|
||||
const connection = getLocalStorage('github_connection');
|
||||
|
||||
if (connection) {
|
||||
localStorage.removeItem('github_connection');
|
||||
setUser(null);
|
||||
}
|
||||
} else if (response.status === 403 && response.headers.get('x-ratelimit-remaining') === '0') {
|
||||
// Rate limit exceeded
|
||||
const resetTime = response.headers.get('x-ratelimit-reset');
|
||||
const resetDate = resetTime ? new Date(parseInt(resetTime) * 1000).toLocaleTimeString() : 'soon';
|
||||
toast.error(`GitHub API rate limit exceeded. Limit resets at ${resetDate}`);
|
||||
} else {
|
||||
logStore.logError('Failed to fetch GitHub repositories', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorData,
|
||||
});
|
||||
toast.error(`Failed to fetch repositories: ${errorData.message || response.statusText}`);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
errorData = await response.json();
|
||||
console.error('Error response data:', errorData);
|
||||
} catch (e) {
|
||||
errorData = { message: 'Could not parse error response' };
|
||||
console.error('Could not parse error response:', e);
|
||||
}
|
||||
const repos = (await response.json()) as GitHubRepo[];
|
||||
allRepos = allRepos.concat(repos);
|
||||
|
||||
if (response.status === 401) {
|
||||
toast.error('GitHub token expired. Please reconnect your account.');
|
||||
|
||||
// Clear invalid token
|
||||
const connection = getLocalStorage('github_connection');
|
||||
|
||||
if (connection) {
|
||||
localStorage.removeItem('github_connection');
|
||||
setUser(null);
|
||||
if (repos.length < 100) {
|
||||
hasMore = false;
|
||||
} else {
|
||||
page += 1;
|
||||
}
|
||||
} else if (response.status === 403 && response.headers.get('x-ratelimit-remaining') === '0') {
|
||||
// Rate limit exceeded
|
||||
const resetTime = response.headers.get('x-ratelimit-reset');
|
||||
const resetDate = resetTime ? new Date(parseInt(resetTime) * 1000).toLocaleTimeString() : 'soon';
|
||||
toast.error(`GitHub API rate limit exceeded. Limit resets at ${resetDate}`);
|
||||
} else {
|
||||
logStore.logError('Failed to fetch GitHub repositories', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorData,
|
||||
});
|
||||
toast.error(`Failed to fetch repositories: ${errorData.message || response.statusText}`);
|
||||
} catch (parseError) {
|
||||
console.error('Error parsing JSON response:', parseError);
|
||||
logStore.logError('Failed to parse GitHub repositories response', { parseError });
|
||||
toast.error('Failed to parse repository data');
|
||||
setRecentRepos([]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Clone the response before parsing it to avoid the "body already read" error
|
||||
const responseClone = response.clone();
|
||||
|
||||
try {
|
||||
const repos = (await responseClone.json()) as GitHubRepo[];
|
||||
console.log(`Successfully fetched ${repos.length} repositories`);
|
||||
setRecentRepos(repos);
|
||||
} catch (parseError) {
|
||||
console.error('Error parsing JSON response:', parseError);
|
||||
logStore.logError('Failed to parse GitHub repositories response', { parseError });
|
||||
toast.error('Failed to parse repository data');
|
||||
setRecentRepos([]);
|
||||
}
|
||||
setRecentRepos(allRepos);
|
||||
} catch (error) {
|
||||
console.error('Exception while fetching GitHub repositories:', error);
|
||||
logStore.logError('Failed to fetch GitHub repositories', { error });
|
||||
@ -183,9 +181,9 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial
|
||||
} finally {
|
||||
setIsFetchingRepos(false);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
async function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
|
||||
const connection = getLocalStorage('github_connection');
|
||||
@ -257,7 +255,7 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setRepoName('');
|
||||
@ -304,7 +302,13 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial
|
||||
</div>
|
||||
</div>
|
||||
<Dialog.Close asChild>
|
||||
<CloseButton onClick={handleClose} size="md" />
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="p-2 rounded-lg transition-all duration-200 ease-in-out bg-transparent text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary dark:text-bolt-elements-textTertiary-dark dark:hover:text-bolt-elements-textPrimary-dark hover:bg-bolt-elements-background-depth-2 dark:hover:bg-bolt-elements-background-depth-3 focus:outline-none focus:ring-2 focus:ring-bolt-elements-borderColor dark:focus:ring-bolt-elements-borderColor-dark"
|
||||
>
|
||||
<span className="i-ph:x block w-5 h-5" aria-hidden="true" />
|
||||
<span className="sr-only">Close dialog</span>
|
||||
</button>
|
||||
</Dialog.Close>
|
||||
</div>
|
||||
|
||||
@ -412,7 +416,13 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial
|
||||
>
|
||||
<div className="relative text-center space-y-4">
|
||||
<Dialog.Close asChild>
|
||||
<CloseButton onClick={handleClose} className="absolute right-0 top-0" size="md" />
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="absolute right-0 top-0 p-2 rounded-lg transition-all duration-200 ease-in-out bg-transparent text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary dark:text-bolt-elements-textTertiary-dark dark:hover:text-bolt-elements-textPrimary-dark hover:bg-bolt-elements-background-depth-2 dark:hover:bg-bolt-elements-background-depth-3 focus:outline-none focus:ring-2 focus:ring-bolt-elements-borderColor dark:focus:ring-bolt-elements-borderColor-dark"
|
||||
>
|
||||
<span className="i-ph:x block w-5 h-5" aria-hidden="true" />
|
||||
<span className="sr-only">Close dialog</span>
|
||||
</button>
|
||||
</Dialog.Close>
|
||||
<motion.div
|
||||
initial={{ scale: 0.8 }}
|
||||
@ -498,7 +508,13 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial
|
||||
</p>
|
||||
</div>
|
||||
<Dialog.Close asChild>
|
||||
<CloseButton onClick={handleClose} className="ml-auto" size="md" />
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="ml-auto p-2 rounded-lg transition-all duration-200 ease-in-out bg-transparent text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary dark:text-bolt-elements-textTertiary-dark dark:hover:text-bolt-elements-textPrimary-dark hover:bg-bolt-elements-background-depth-2 dark:hover:bg-bolt-elements-background-depth-3 focus:outline-none focus:ring-2 focus:ring-bolt-elements-borderColor dark:focus:ring-bolt-elements-borderColor-dark"
|
||||
>
|
||||
<span className="i-ph:x block w-5 h-5" aria-hidden="true" />
|
||||
<span className="sr-only">Close dialog</span>
|
||||
</button>
|
||||
</Dialog.Close>
|
||||
</div>
|
||||
|
||||
@ -635,7 +651,6 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial
|
||||
<StatusIndicator status="loading" pulse={true} label="Loading repositories..." />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="p-3 bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3 rounded-lg border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor-dark">
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
|
@ -7,6 +7,8 @@ interface RepositoryCardProps {
|
||||
onSelect: () => void;
|
||||
}
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export function RepositoryCard({ repo, onSelect }: RepositoryCardProps) {
|
||||
// Calculate a gradient color based on the repository name for visual variety
|
||||
const getGradientColor = (name: string) => {
|
||||
@ -55,9 +57,12 @@ export function RepositoryCard({ repo, onSelect }: RepositoryCardProps) {
|
||||
});
|
||||
};
|
||||
|
||||
const gradient = useMemo(() => getGradientColor(repo.name), [repo.name]);
|
||||
// const formattedDate = useMemo(() => formatDate(repo.updated_at), [repo.updated_at]);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className={`p-5 rounded-xl bg-gradient-to-br ${getGradientColor(repo.name)} border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor-dark hover:border-purple-500/40 transition-all duration-300 shadow-sm hover:shadow-md`}
|
||||
className={`p-5 rounded-xl bg-gradient-to-br ${gradient} border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor-dark hover:border-purple-500/40 transition-all duration-300 shadow-sm hover:shadow-md`}
|
||||
whileHover={{
|
||||
scale: 1.02,
|
||||
y: -2,
|
||||
|
@ -8,10 +8,9 @@ import { motion, AnimatePresence } from 'framer-motion';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
// Import UI components
|
||||
import { Input, SearchInput, Badge, FilterChip } from '~/components/ui';
|
||||
import { Input, SearchInput, Badge, FilterChip, Tabs, TabsList, TabsTrigger } from '~/components/ui';
|
||||
|
||||
// Import the components we've extracted
|
||||
import { TabButton } from './TabButton';
|
||||
import { RepositoryList } from './RepositoryList';
|
||||
import { StatsDialog } from './StatsDialog';
|
||||
import { GitHubAuthDialog } from './GitHubAuthDialog';
|
||||
@ -564,7 +563,7 @@ export function RepositorySelectionDialog({ isOpen, onClose, onSelect }: Reposit
|
||||
<Dialog.Close
|
||||
onClick={handleClose}
|
||||
className={classNames(
|
||||
'p-2 rounded-lg transition-all duration-200 ease-in-out',
|
||||
'p-2 rounded-lg transition-all duration-200 ease-in-out bg-transparent',
|
||||
'text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary',
|
||||
'dark:text-bolt-elements-textTertiary-dark dark:hover:text-bolt-elements-textPrimary-dark',
|
||||
'hover:bg-bolt-elements-background-depth-2 dark:hover:bg-bolt-elements-background-depth-3',
|
||||
@ -599,18 +598,13 @@ export function RepositorySelectionDialog({ isOpen, onClose, onSelect }: Reposit
|
||||
<div className="p-5">
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-2 mb-6">
|
||||
<TabButton active={activeTab === 'my-repos'} onClick={() => setActiveTab('my-repos')}>
|
||||
<span className="i-ph:book-bookmark" />
|
||||
My Repos
|
||||
</TabButton>
|
||||
<TabButton active={activeTab === 'search'} onClick={() => setActiveTab('search')}>
|
||||
<span className="i-ph:magnifying-glass" />
|
||||
Search
|
||||
</TabButton>
|
||||
<TabButton active={activeTab === 'url'} onClick={() => setActiveTab('url')}>
|
||||
<span className="i-ph:link" />
|
||||
URL
|
||||
</TabButton>
|
||||
<Tabs value={activeTab} onValueChange={(v) => setActiveTab(v as 'my-repos' | 'search' | 'url')}>
|
||||
<TabsList>
|
||||
<TabsTrigger value="my-repos">My Repos</TabsTrigger>
|
||||
<TabsTrigger value="search">Search</TabsTrigger>
|
||||
<TabsTrigger value="url">From URL</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
{activeTab === 'url' ? (
|
||||
|
@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
|
||||
interface TabButtonProps {
|
||||
active: boolean;
|
||||
onClick: () => void;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function TabButton({ active, onClick, children }: TabButtonProps) {
|
||||
return (
|
||||
<motion.button
|
||||
onClick={onClick}
|
||||
className={classNames(
|
||||
'px-4 py-2 h-10 rounded-lg transition-all duration-200 flex items-center gap-2 min-w-[120px] justify-center relative overflow-hidden',
|
||||
active
|
||||
? 'bg-purple-500 text-white hover:bg-purple-600 shadow-sm shadow-purple-500/20'
|
||||
: 'bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary-dark hover:bg-bolt-elements-background-depth-3 dark:hover:bg-bolt-elements-background-depth-4 border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor-dark',
|
||||
)}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
layout
|
||||
>
|
||||
{active && (
|
||||
<motion.span
|
||||
layoutId="activeTab"
|
||||
className="absolute inset-0 bg-purple-500 -z-10"
|
||||
initial={false}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
|
||||
/>
|
||||
)}
|
||||
<span className={classNames('flex items-center gap-2', active ? 'font-medium' : '')}>{children}</span>
|
||||
</motion.button>
|
||||
);
|
||||
}
|
@ -149,6 +149,7 @@
|
||||
"shiki": "^1.24.0",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"use-debounce": "^10.0.4",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"zod": "^3.24.1",
|
||||
"zustand": "^5.0.3"
|
||||
|
@ -326,6 +326,9 @@ importers:
|
||||
unist-util-visit:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0
|
||||
use-debounce:
|
||||
specifier: ^10.0.4
|
||||
version: 10.0.4(react@18.3.1)
|
||||
vite-plugin-node-polyfills:
|
||||
specifier: ^0.22.0
|
||||
version: 0.22.0(rollup@4.38.0)(vite@5.4.15(@types/node@22.13.14)(sass-embedded@1.86.0))
|
||||
@ -7743,6 +7746,12 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
use-debounce@10.0.4:
|
||||
resolution: {integrity: sha512-6Cf7Yr7Wk7Kdv77nnJMf6de4HuDE4dTxKij+RqE9rufDsI6zsbjyAxcH5y2ueJCQAnfgKbzXbZHYlkFwmBlWkw==}
|
||||
engines: {node: '>= 16.0.0'}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
|
||||
use-memo-one@1.1.3:
|
||||
resolution: {integrity: sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==}
|
||||
peerDependencies:
|
||||
@ -16864,6 +16873,10 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.20
|
||||
|
||||
use-debounce@10.0.4(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
use-memo-one@1.1.3(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
Loading…
Reference in New Issue
Block a user