Refactor: Add button feedback

This commit is contained in:
gpt-engineer-app[bot]
2025-11-04 18:48:39 +00:00
parent b07004ed03
commit ded4dfd59c
7 changed files with 90 additions and 52 deletions

View File

@@ -1,5 +1,6 @@
import { Shield, ArrowLeft, Settings, RefreshCw, Menu } from 'lucide-react';
import { Shield, ArrowLeft, Settings, Menu } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { RefreshButton } from '@/components/ui/refresh-button';
import { Link, useLocation } from 'react-router-dom';
import { ThemeToggle } from '@/components/theme/ThemeToggle';
import { AuthButtons } from '@/components/auth/AuthButtons';
@@ -15,7 +16,7 @@ import {
SheetTrigger,
} from '@/components/ui/sheet';
export function AdminHeader({ onRefresh }: { onRefresh?: () => void }) {
export function AdminHeader({ onRefresh, isRefreshing }: { onRefresh?: () => void; isRefreshing?: boolean }) {
const { permissions } = useUserRole();
const { user } = useAuth();
const location = useLocation();
@@ -68,14 +69,12 @@ export function AdminHeader({ onRefresh }: { onRefresh?: () => void }) {
<span className="text-sm font-medium">Theme</span>
<ThemeToggle />
</div>
<Button
variant="ghost"
onClick={onRefresh}
className="justify-start"
>
<RefreshCw className="w-4 h-4 mr-2" />
Refresh
</Button>
<RefreshButton
onRefresh={onRefresh!}
isLoading={isRefreshing}
variant="ghost"
className="justify-start w-full"
/>
{permissions?.role_level === 'superuser' && !isSettingsPage && (
<Button variant="ghost" asChild className="justify-start">
<Link to="/admin/settings">
@@ -89,16 +88,15 @@ export function AdminHeader({ onRefresh }: { onRefresh?: () => void }) {
</Sheet>
{/* Desktop Actions */}
<Button
variant="ghost"
size="sm"
onClick={onRefresh}
title="Refresh admin data"
className="hidden md:flex"
>
<RefreshCw className="w-4 h-4" />
<span className="hidden sm:ml-2 sm:inline">Refresh</span>
</Button>
{onRefresh && (
<RefreshButton
onRefresh={onRefresh}
isLoading={isRefreshing}
variant="ghost"
size="sm"
className="hidden md:flex"
/>
)}
{permissions?.role_level === 'superuser' && !isSettingsPage && (
<Button variant="ghost" size="sm" asChild className="hidden md:flex">
<Link to="/admin/settings">

View File

@@ -1,5 +1,5 @@
import { RefreshCw } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { RefreshButton } from '@/components/ui/refresh-button';
import { ThemeToggle } from '@/components/theme/ThemeToggle';
import { AuthButtons } from '@/components/auth/AuthButtons';
import { NotificationCenter } from '@/components/notifications/NotificationCenter';
@@ -50,16 +50,12 @@ export function AdminTopBar({
{/* Right Section */}
<div className="flex items-center gap-2">
{onRefresh && (
<Button
<RefreshButton
onRefresh={onRefresh}
isLoading={isRefreshing}
variant="ghost"
size="sm"
onClick={onRefresh}
disabled={isRefreshing}
title="Refresh data"
>
<RefreshCw className={`w-4 h-4 ${isRefreshing ? 'animate-spin' : ''}`} />
<span className="hidden sm:ml-2 sm:inline">Refresh</span>
</Button>
/>
)}
<ThemeToggle />
{user && <NotificationCenter />}

View File

@@ -166,8 +166,15 @@ export default function AdminSettings() {
setLocalValue(updated);
};
const saveBanDurations = () => {
updateSetting(setting.setting_key, banDurations);
const [savingBanDurations, setSavingBanDurations] = useState(false);
const saveBanDurations = async () => {
setSavingBanDurations(true);
try {
await updateSetting(setting.setting_key, banDurations);
} finally {
setSavingBanDurations(false);
}
};
return (
@@ -239,7 +246,13 @@ export default function AdminSettings() {
</div>
</div>
<Button onClick={saveBanDurations} disabled={isUpdating} className="w-full">
<Button
onClick={saveBanDurations}
loading={savingBanDurations}
loadingText="Saving..."
className="w-full"
trackingLabel="save-ban-duration-settings"
>
<Save className="w-4 h-4 mr-2" />
Save Ban Duration Settings
</Button>
@@ -431,7 +444,13 @@ export default function AdminSettings() {
className="w-24"
min="0"
/>
<Button onClick={handleSubmit} disabled={isUpdating} size="sm">
<Button
onClick={handleSubmit}
loading={isUpdating}
loadingText=""
size="sm"
trackingLabel="save-threshold-setting"
>
<Save className="w-4 h-4" />
</Button>
</div>
@@ -458,7 +477,13 @@ export default function AdminSettings() {
}}
className="flex-1"
/>
<Button onClick={handleSubmit} disabled={isUpdating} size="sm">
<Button
onClick={handleSubmit}
loading={isUpdating}
loadingText=""
size="sm"
trackingLabel="save-setting"
>
<Save className="w-4 h-4" />
</Button>
</div>

View File

@@ -275,15 +275,14 @@ export default function AuthCallback() {
required
/>
</div>
<Button type="submit" className="w-full" disabled={settingPassword}>
{settingPassword ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Setting Password...
</>
) : (
'Set Password'
)}
<Button
type="submit"
className="w-full"
loading={settingPassword}
loadingText="Setting Password..."
trackingLabel="set-password-recovery"
>
Set Password
</Button>
</form>
</CardContent>

View File

@@ -525,6 +525,8 @@ export default function Profile() {
setFormErrors({});
return true;
};
const [isSaving, setIsSaving] = useState(false);
const handleSaveProfile = async () => {
if (!profile || !currentUser) return;
if (!validateForm()) return;
@@ -533,6 +535,8 @@ export default function Profile() {
setShowUsernameDialog(true);
return;
}
setIsSaving(true);
try {
const updateData: any = {
display_name: editForm.display_name,
@@ -572,6 +576,8 @@ export default function Profile() {
title: "Error updating profile",
description: getErrorMessage(error)
});
} finally {
setIsSaving(false);
}
};
const confirmUsernameChange = () => {
@@ -730,7 +736,14 @@ export default function Profile() {
</div>
<div className="flex gap-2">
<Button onClick={handleSaveProfile} size="sm" disabled={usernameValidation.isChecking || editForm.username !== profile?.username && !usernameValidation.isValid}>
<Button
onClick={handleSaveProfile}
size="sm"
loading={isSaving}
loadingText="Saving..."
disabled={usernameValidation.isChecking || editForm.username !== profile?.username && !usernameValidation.isValid}
trackingLabel="save-profile-changes"
>
<Save className="w-4 h-4 mr-2" />
Save Changes
</Button>

View File

@@ -83,7 +83,12 @@ export default function ErrorLookup() {
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
className="font-mono"
/>
<Button onClick={handleSearch} disabled={loading}>
<Button
onClick={handleSearch}
loading={loading}
loadingText="Searching..."
trackingLabel="error-lookup-search"
>
<Search className="w-4 h-4 mr-2" />
Search
</Button>

View File

@@ -5,9 +5,9 @@ import { AdminLayout } from '@/components/layout/AdminLayout';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { AlertCircle, RefreshCw } from 'lucide-react';
import { AlertCircle } from 'lucide-react';
import { RefreshButton } from '@/components/ui/refresh-button';
import { ErrorDetailsModal } from '@/components/admin/ErrorDetailsModal';
import { ErrorAnalytics } from '@/components/admin/ErrorAnalytics';
import { format } from 'date-fns';
@@ -33,7 +33,7 @@ export default function ErrorMonitoring() {
const [dateRange, setDateRange] = useState<'1h' | '24h' | '7d' | '30d'>('24h');
// Fetch recent errors
const { data: errors, isLoading, refetch } = useQuery({
const { data: errors, isLoading, refetch, isFetching } = useQuery({
queryKey: ['admin-errors', dateRange, errorTypeFilter, searchTerm],
queryFn: async () => {
let query = supabase
@@ -88,10 +88,12 @@ export default function ErrorMonitoring() {
<h1 className="text-3xl font-bold tracking-tight">Error Monitoring</h1>
<p className="text-muted-foreground">Track and analyze application errors</p>
</div>
<Button onClick={() => refetch()} variant="outline" size="sm">
<RefreshCw className="w-4 h-4 mr-2" />
Refresh
</Button>
<RefreshButton
onRefresh={async () => { await refetch(); }}
isLoading={isFetching}
variant="outline"
size="sm"
/>
</div>
{/* Analytics Section */}